diff options
Diffstat (limited to 'bot.py')
-rwxr-xr-x | bot.py | 202 |
1 files changed, 202 insertions, 0 deletions
@@ -0,0 +1,202 @@ +#!/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__ |