summaryrefslogtreecommitdiffstats
path: root/bot.py
diff options
context:
space:
mode:
authorSean B. Palmer <http://inamidst.com/sbp/>2008-02-21 12:06:33 +0000
committerSean B. Palmer <http://inamidst.com/sbp/>2008-02-21 12:06:33 +0000
commit7931fab14599b739c18c8f1ebcc24b75688dbc09 (patch)
treebf4df9757f10c155e3b6f78aed48f15884ebbbe6 /bot.py
downloadbot-7931fab14599b739c18c8f1ebcc24b75688dbc09.tar.gz
bot-7931fab14599b739c18c8f1ebcc24b75688dbc09.tar.bz2
bot-7931fab14599b739c18c8f1ebcc24b75688dbc09.zip
Phenny2, now being tested on Freenode as the main phenny.
Diffstat (limited to 'bot.py')
-rwxr-xr-xbot.py202
1 files changed, 202 insertions, 0 deletions
diff --git a/bot.py b/bot.py
new file mode 100755
index 0000000..d84a635
--- /dev/null
+++ b/bot.py
@@ -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__