RelayBot/relaybot.py

233 lines
8.8 KiB
Python
Raw Normal View History

from twisted.words.protocols import irc
from twisted.internet import reactor, protocol
from twisted.internet.protocol import ReconnectingClientFactory
from twisted.python import log
from twisted.internet.endpoints import clientFromString
from twisted.application import service
from signal import signal, SIGINT
from ConfigParser import SafeConfigParser
2012-03-03 03:54:28 +00:00
import re, sys
2012-03-03 03:54:28 +00:00
#
# RelayBot is a derivative of http://code.google.com/p/relaybot/
#
log.startLogging(sys.stdout)
2012-03-03 03:56:25 +00:00
__version__ = "0.1"
application = service.Application("RelayBot")
def main():
config = SafeConfigParser()
config.read("relaybot.config")
defaults = config.defaults()
for section in config.sections():
2012-03-04 19:51:06 +00:00
def get(option):
if option in defaults or config.has_option(section, option):
return config.get(section, option) or defaults[option]
else:
return None
2012-03-04 19:51:06 +00:00
options = {}
for option in [ "timeout", "host", "port", "nick", "channel", "info", "heartbeat", "password" ]:
options[option] = get(option)
mode = get("mode")
#Not using endpoints pending http://twistedmatrix.com/trac/ticket/4735
#(ReconnectingClientFactory equivalent for endpoints.)
factory = None
2012-03-11 07:10:29 +00:00
if mode == "Default":
factory = RelayFactory
2012-03-11 07:10:29 +00:00
elif mode == "FLIP":
factory = FLIPFactory
elif mode == "NickServ":
factory = NickServFactory
options["nickServPassword"] = get("nickServPassword")
factory = factory(options)
reactor.connectTCP(options['host'], int(options['port']), factory, int(options['timeout']))
reactor.callWhenRunning(signal, SIGINT, handler)
2012-03-02 18:09:00 +00:00
class Communicator:
def __init__(self):
self.protocolInstances = {}
def register(self, protocol):
self.protocolInstances[protocol.identifier] = protocol
def isRegistered(self, protocol):
return protocol.identifier in self.protocolInstances
2012-03-02 18:09:00 +00:00
def unregister(self, protocol):
if protocol.identifier not in self.protocolInstances:
log.msg("No protocol instance with identifier %s."%protocol.identifier)
return
2012-03-02 18:09:00 +00:00
del self.protocolInstances[protocol.identifier]
def relay(self, protocol, message):
for identifier in self.protocolInstances.keys():
if identifier == protocol.identifier:
continue
instance = self.protocolInstances[identifier]
instance.twoWaySay(message)
#Global scope: all protocol instances will need this.
communicator = Communicator()
class IRCRelayer(irc.IRCClient):
realname = "Relay P. Botternson"
username = "RelayBot"
def __init__(self, config):
self.network = config['host']
self.password = config['password']
self.channel = config['channel']
self.nickname = config['nick']
self.identifier = config['identifier']
self.privMsgResponse = config['info']
2012-03-14 15:57:29 +00:00
self.heartbeatInterval = float(config['heartbeat'])
log.msg("IRC Relay created. Name: %s | Host: %s | Channel: %s"%(self.nickname, self.network, self.channel))
def formatUsername(self, username):
return username.split("!")[0]
def relay(self, message):
2012-03-02 18:09:00 +00:00
communicator.relay(self, message)
def signedOn(self):
log.msg("[%s] Connected to network."%self.network)
2012-03-14 15:57:29 +00:00
self.startHeartbeat()
self.join(self.channel, "")
def connectionLost(self, reason):
log.msg("[%s] Connection lost, unregistering."%self.network)
2012-03-02 18:09:00 +00:00
communicator.unregister(self)
def twoWaySay(self, message, args=None):
self.say(self.channel, message)
def joined(self, channel):
log.msg("Joined channel %s, registering."%channel)
2012-03-02 18:09:00 +00:00
communicator.register(self)
def privmsg(self, user, channel, message):
#If someone addresses the bot directly, respond in the same way.
if channel == self.nickname:
log.msg("Recieved privmsg from %s."%user)
self.msg(user, self.privMsgResponse)
else:
self.relay("[%s] %s"%(self.formatUsername(user), message))
if message.startswith(self.nickname + ':'):
self.say(self.channel, self.privMsgResponse)
#For consistancy, if anyone responds to the bot's response:
self.relay("[%s] %s"%(self.formatUsername(self.nickname), self.privMsgResponse))
def kickedFrom(self, channel, kicker, message):
2012-03-02 18:09:00 +00:00
log.msg("Kicked by %s. Message \"%s\""%(kicker, message))
communicator.unregister(self)
def userJoined(self, user, channel):
self.relay("%s joined."%self.formatUsername(user))
def userLeft(self, user, channel):
self.relay("%s left."%self.formatUsername(user))
def userQuit(self, user, quitMessage):
self.relay("%s quit. (%s)"%(self.formatUsername(user), quitMessage))
def action(self, user, channel, data):
self.relay("* %s %s"%(self.formatUsername(user), data))
def userRenamed(self, oldname, newname):
self.relay("%s is now known as %s."%(self.formatUsername(oldname), self.formatUsername(newname)))
2012-03-02 18:09:00 +00:00
class RelayFactory(ReconnectingClientFactory):
protocol = IRCRelayer
#Log information which includes reconnection status.
noisy = True
def __init__(self, config):
config["identifier"] = "{0}{1}{2}".format(config["host"], config["port"], config["channel"])
self.config = config
def buildProtocol(self, addr):
#Connected - reset reconnect attempt delay.
self.resetDelay()
x = self.protocol(self.config)
x.factory = self
return x
class SilentJoinPart(IRCRelayer):
def userJoined(self, user, channel):
pass
def userLeft(self, user, channel):
pass
def userQuit(self, user, quitMessage):
pass
#Remove the _<numbers> that FLIP puts on the end of usernames.
class FLIPRelayer(SilentJoinPart):
def formatUsername(self, username):
return re.sub("_\d+$", "", IRCRelayer.formatUsername(self, username))
class FLIPFactory(RelayFactory):
protocol = FLIPRelayer
2012-03-11 07:10:29 +00:00
#Identify with NickServ upon connecting, and wait for recognition before joining the channel.
class NickServRelayer(SilentJoinPart):
2012-03-11 07:10:29 +00:00
NickServ = "nickserv"
2012-03-11 07:10:29 +00:00
def signedOn(self):
log.msg("[%s] Connected to network."%self.network)
2012-03-14 15:57:29 +00:00
self.startHeartbeat()
if self.nickname == self.desiredNick:
log.msg("[%s] Identifying with %s."%(self.network, NickServRelayer.NickServ))
self.msg(NickServRelayer.NickServ, "IDENTIFY %s"%self.password)
else:
log.msg("[%s] Using GHOST to reclaim nick %s."%(self.network, self.desiredNick))
self.msg(NickServRelayer.NickServ, "GHOST %s %s"%(self.desiredNick, self.password))
2012-03-11 07:10:29 +00:00
def noticed(self, user, channel, message):
#Either identification was successful, GHOSTing was successful, or the GHOST disconnected in the interim.
#NickServ implementations differ in the exact messages, and contain bolding/formatting.
if IRCRelayer.formatUsername(self, user).lower() == NickServRelayer.NickServ\
and (message.lower().startswith("password accepted") or\
message.lower() == "ghost with your nickname has been killed." or\
message.lower() == "ghost with your nick has been killed." or\
message.lower().endswith("isn't currently in use.")):
if self.passwordAccepted:
log.msg("[%s] Recieved duplicate password acception from %s."%(self.network, NickServRelayer.NickServ))
return
2012-03-11 07:10:29 +00:00
log.msg("[%s] Identified with %s; joining %s."%(self.network, NickServRelayer.NickServ, self.channel))
self.passwordAccepted = True
if self.nickname != self.desiredNick:
log.msg("[%s] GHOST successful, reclaiming nick %s."%(self.network,self.desiredNick))
self.setNick(self.desiredNick)
2012-03-11 07:10:29 +00:00
self.join(self.channel, "")
else:
2012-03-11 19:14:04 +00:00
log.msg("[%s] Recieved notice \"%s\" from %s."%(self.network, message, user))
2012-03-11 07:10:29 +00:00
def __init__(self, config):
IRCRelayer.__init__(self, config)
#super(NickServRelayer, self).__init__(config)
self.password = config['nickServPassword']
self.desiredNick = config['nick']
self.passwordAccepted = False
2012-03-11 07:10:29 +00:00
class NickServFactory(RelayFactory):
protocol = NickServRelayer
def handler(signum, frame):
reactor.stop()
#Main if run as script, builtin for twistd.
if __name__ in ["__main__", "__builtin__"]:
main()