mirror of https://github.com/str4d/RelayBot.git
233 lines
8.7 KiB
Python
233 lines
8.7 KiB
Python
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
|
|
import re, sys
|
|
|
|
#
|
|
# RelayBot is a derivative of http://code.google.com/p/relaybot/
|
|
#
|
|
|
|
log.startLogging(sys.stdout)
|
|
|
|
__version__ = "0.1"
|
|
application = service.Application("RelayBot")
|
|
|
|
def main():
|
|
config = SafeConfigParser()
|
|
config.read("relaybot.config")
|
|
defaults = config.defaults()
|
|
|
|
for section in config.sections():
|
|
|
|
def get(option):
|
|
if option in defaults or config.has_option(section, option):
|
|
return config.get(section, option) or defaults[option]
|
|
else:
|
|
return None
|
|
|
|
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
|
|
if mode == "Default":
|
|
factory = RelayFactory
|
|
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)
|
|
|
|
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
|
|
|
|
def unregister(self, protocol):
|
|
if protocol.identifier not in self.protocolInstances:
|
|
log.msg("No protocol instance with identifier %s."%protocol.identifier)
|
|
return
|
|
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']
|
|
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):
|
|
communicator.relay(self, message)
|
|
|
|
def signedOn(self):
|
|
log.msg("[%s] Connected to network."%self.network)
|
|
self.startHeartbeat()
|
|
self.join(self.channel, "")
|
|
|
|
def connectionLost(self, reason):
|
|
log.msg("[%s] Connection lost, unregistering."%self.network)
|
|
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)
|
|
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):
|
|
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)))
|
|
|
|
|
|
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
|
|
|
|
#Identify with NickServ upon connecting, and wait for recognition before joining the channel.
|
|
class NickServRelayer(SilentJoinPart):
|
|
NickServ = "nickserv"
|
|
|
|
def signedOn(self):
|
|
log.msg("[%s] Connected to network."%self.network)
|
|
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))
|
|
|
|
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
|
|
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)
|
|
self.join(self.channel, "")
|
|
else:
|
|
log.msg("[%s] Recieved notice \"%s\" from %s."%(self.network, message, user))
|
|
|
|
def __init__(self, config):
|
|
IRCRelayer.__init__(self, config)
|
|
#super(NickServRelayer, self).__init__(config)
|
|
self.password = config['nickServPassword']
|
|
self.desiredNick = config['nick']
|
|
self.passwordAccepted = False
|
|
|
|
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()
|