#!/usr/bin/env python from twisted.words.protocols import irc from twisted.internet import reactor, protocol from twisted.python import log from twisted.protocols.basic import LineReceiver import os, re from ConfigParser import ConfigParser import login, ipv6 config = ConfigParser() def load_config(): print 'Loading config...' config.read([os.path.expanduser('~/.fot')]) print 'Done!' load_config() modules = {} def load_modules(): print 'Loading modules...' for s in config.sections(): if not s.startswith('module/'): continue m = s[7:] if not config.has_option(s, 'load') or config.getboolean(s, 'load'): try: if modules.has_key(m): reload(modules[m]) else: modules[m] = __import__('modules.' + m, globals(), locals(), ['Module']) mod = modules[m] mod.config = config print '%s by %s' % (mod.info['title'], mod.info['author']) del mod except: print 'Failed to load', m raise else: print 'Not loading', m print 'Done!' load_modules() bots = [] def refresh_modules(): print 'Re-applying modules to bots...' for bot in bots: bot.apply_modules() print 'Done!' class Bot(irc.IRCClient): def __init__(self): bots.append(self) self.modules = {} self.keywords = {} self.msg_callbacks = [] self.module_channels = None def __repr__(self): return '' % (self.nickname, self.factory.server) def register_keyword(self, kw, mod): if kw in self.keywords and self.keywords[kw] != mod: raise Exception('Keyword "%s" already in use' % kw) self.keywords[kw] = mod def register_callback(self, mod): if not mod in self.msg_callbacks: self.msg_callbacks.append(mod) def apply_modules(self): # Call stop() for each module if neccessary (can't rely on __del__ here) for m in self.modules.itervalues(): if hasattr(m, 'stop'): m.stop() self.modules = {} server = self.factory.server.split('/', 1)[1] channel_list = config.get(self.factory.server, 'channels').split() self.module_channels = dict([(x, []) for x in channel_list]) for m in modules.keys(): if config.has_option('module/' + m, 'filter'): mod_re = re.compile(config.get('module/' + m, 'filter')) else: mod_re = None mod = None # the module instance for channel in channel_list: if not mod_re or mod_re.match('%s/%s' % (server, x)) != None: if not mod: mod = modules[m].Module(self) self.module_channels[channel].append(mod) if mod: self.modules[m] = mod def connectionMade(self): self.apply_modules() self.nickname = config.get(self.factory.server, 'nickname') irc.IRCClient.connectionMade(self) def signedOn(self): for chan in config.get(self.factory.server, 'channels').split(' '): self.join(chan) def privmsg(self, nick, channel, msg): priv = channel == self.nickname # filter out server stuff if not priv and not '!' in nick: return for mod in self.msg_callbacks: if priv or mod in self.module_channels[channel]: mod.privmsg(nick, channel, msg) msg = msg.split(None, 1) if not len(msg): return elif len(msg) == 1: kw, msg = msg[0], '' else: kw, msg = msg if kw in self.keywords: mod = self.keywords[kw] if priv or mod in self.module_channels[channel]: mod.keyword(nick, channel, kw, msg) def kickedFrom(self, channel, kicker, message): self.join(channel) def connectionLost(self, reason): for m in self.modules.itervalues(): if hasattr(m, 'stop'): m.stop() self.modules = {} irc.IRCClient.connectionLost(self, reason) def reconnect(self): self.quit('reconnecting') class BotFactory(protocol.ReconnectingClientFactory): protocol = Bot def __init__(self, server, nickname): self.server = server self.nickname = nickname self.maxDelay = 120 def buildProtocol(self, addr): self.resetDelay() return protocol.ReconnectingClientFactory.buildProtocol(self, addr) def startedConnecting(self, connector): print 'Connecting to', connector.host def clientConnectionFailed(self, connector, reason): print 'Connection failed:', reason.getErrorMessage() protocol.ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) def clientConnectionLost(self, connector, reason): print 'Connection lost:', reason.getErrorMessage() protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason) def start_server(server): if not config.has_option(server,'host') or not config.has_option(server, 'port') \ or not config.has_option(server, 'channels') or config.has_option(server, 'disabled'): return print '%s' % server factory = BotFactory(server, config.get(server, 'nickname')) if config.has_option(server, 'host6'): bind6 = (config.get(server, 'bind6'), 0) if config.has_option(server, 'bind6') else None ipv6.connectTCP6(config.get(server, 'host6'), config.getint(server, 'port6'), factory, bindAddress = bind6) else: reactor.connectTCP(config.get(server, 'host'), config.getint(server, 'port'), factory) print 'Starting per-network instances...' for server in (server for server in config.sections() if server.startswith('server/')): start_server(server) loginfactory = login.getManholeFactory(globals(), os.path.expanduser('~/.fot.users')) reactor.listenTCP(3333, loginfactory) reactor.run()