#!/usr/bin/env python """ bot.py - Phenny IRC Bot Copyright 2008, Sean B. Palmer, inamidst.com Licensed under the Eiffel Forum License 2. http://inamidst.com/phenny/ """ import sys, os, re, time, threading, optparse import irc home = os.getcwd() def decode(bytes): try: text = bytes.decode('utf-8') except UnicodeDecodeError: try: text = bytes.decode('iso-8859-1') except UnicodeDecodeError: text = bytes.decode('cp1252') return text class Phenny(irc.Bot): def __init__(self, config): irc.Bot.__init__(self, config.nick, config.name, config.channels) self.config = config self.doc = {} self.stats = {} self.setup() def setup(self): self.variables = {} if not hasattr(self.config, 'enable'): load = [('modules', filename[:-3]) for filename in os.listdir(os.path.join(home, 'modules')) if filename.endswith('.py') and not filename.startswith('_') and not filename[:-3] in self.config.disable] else: load = [('modules', e) for e in self.config.enable] if hasattr(self.config, 'opt'): load += [('opt', o) for o in self.config.opt] modules = [] for package, name in load: try: module = getattr(__import__(package + '.' + name), name) except Exception, e: print >> sys.stderr, "Error loading %s: %s" % (name, e) else: if hasattr(module, 'setup'): module.setup(self) self.register(vars(module)) modules.append(name) if modules: print >> sys.stderr, 'Registered modules:', ', '.join(modules) else: print >> sys.stderr, "Warning: Couldn't find any modules" self.bind_commands() def register(self, variables): # This is used by reload.py, hence it being methodised for name, obj in variables.iteritems(): if hasattr(obj, 'commands') or hasattr(obj, 'rule'): self.variables[name] = obj def bind_commands(self): self.commands = {'high': {}, 'medium': {}, 'low': {}} def bind(self, priority, regexp, func): print priority, regexp.pattern.encode('utf-8'), func self.commands[priority].setdefault(regexp, []).append(func) # @@ register documentation if func.__doc__: if hasattr(func, 'name'): name = func.name else: name = func.__name__ if hasattr(func, 'example'): example = func.example example = example.replace('$nickname', self.nick) else: example = None self.doc[name] = (func.__doc__, example) def sub(pattern, self=self): # These replacements have significant order pattern = pattern.replace('$nickname', self.nick) return pattern.replace('$nick', r'%s[,:] +' % self.nick) for name, func in self.variables.iteritems(): # print name, func if not hasattr(func, 'priority'): func.priority = 'medium' if not hasattr(func, 'thread'): func.thread = True if not hasattr(func, 'event'): func.event = 'PRIVMSG' else: func.event = func.event.upper() if hasattr(func, 'rule'): if isinstance(func.rule, str): pattern = sub(func.rule) regexp = re.compile(pattern) bind(self, func.priority, regexp, func) if isinstance(func.rule, tuple): # 1) e.g. ('$nick', '(.*)') if len(func.rule) == 2 and isinstance(func.rule[0], str): prefix, pattern = func.rule prefix = sub(prefix) regexp = re.compile(prefix + pattern) bind(self, func.priority, regexp, func) # 2) e.g. (['p', 'q'], '(.*)') elif len(func.rule) == 2 and isinstance(func.rule[0], list): prefix = self.config.prefix commands, pattern = func.rule for command in commands: command = r'(%s) +' % command regexp = re.compile(prefix + command + pattern) bind(self, func.priority, regexp, func) # 3) e.g. ('$nick', ['p', 'q'], '(.*)') elif len(func.rule) == 3: prefix, commands, pattern = func.rule prefix = sub(prefix) for command in commands: command = r'(%s) +' % command regexp = re.compile(prefix + command + pattern) bind(self, func.priority, regexp, func) if hasattr(func, 'commands'): for command in func.commands: template = r'^%s(%s)(?: +(.*))?$' pattern = template % (self.config.prefix, command) regexp = re.compile(pattern) bind(self, func.priority, regexp, func) def wrapped(self, origin, text, match): class PhennyWrapper(object): def __init__(self, phenny): self.bot = phenny def __getattr__(self, attr): if attr == 'reply': return (lambda msg: self.bot.msg(origin.sender, origin.nick + ': ' + msg)) elif attr == 'say': return lambda msg: self.bot.msg(origin.sender, msg) return getattr(self.bot, attr) return PhennyWrapper(self) def input(self, origin, text, bytes, match, event): class CommandInput(unicode): def __new__(cls, text, origin, bytes, match, event): s = unicode.__new__(cls, text) s.sender = origin.sender s.nick = origin.nick s.event = event s.bytes = bytes s.match = match s.group = match.group s.groups = match.groups s.admin = origin.nick in self.config.admins s.owner = origin.nick == self.config.owner return s return CommandInput(text, origin, bytes, match, event) def call(self, func, origin, phenny, input): try: func(phenny, input) except Exception, e: self.error(origin) def dispatch(self, origin, args): bytes, event = args[0], args[1] text = decode(bytes) for priority in ('high', 'medium', 'low'): items = self.commands[priority].items() for regexp, funcs in items: for func in funcs: if event != func.event: continue match = regexp.match(text) if match: # print 'STATS:', origin.sender, func.__name__ phenny = self.wrapped(origin, text, match) input = self.input(origin, text, bytes, match, event) if func.thread: args = (func, origin, phenny, input) t = threading.Thread(target=self.call, args=args) t.start() else: self.call(func, origin, phenny, input) if __name__ == '__main__': print __doc__