Phenny2, now being tested on Freenode as the main phenny.
This commit is contained in:
202
bot.py
Executable file
202
bot.py
Executable file
@@ -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__
|
||||
Reference in New Issue
Block a user