changing magical discipline from arcane to serpentine

This commit is contained in:
Ryan Hitchman 2009-03-15 22:30:46 -06:00
parent 29e7fef87f
commit d560cb90dc
11 changed files with 141 additions and 58 deletions

75
bot.py
View File

@ -11,17 +11,18 @@ import imp
import re import re
import thread import thread
import Queue import Queue
import copy import collections
import irc import irc
import yaml import yaml
os.chdir(sys.path[0]) #do stuff relative to the installation directory os.chdir(sys.path[0]) #do stuff relative to the installation directory
sys.path += ['plugins'] # so 'import hook' works without duplication
class Bot(object): class Bot(object):
def __init__(self, nick, channel, network): def __init__(self, nick, channel, network):
self.commands = [] # fn, name, func, args self.commands = [] # (fn, funcname, name, line), func, args
self.filters = [] #fn, name, func self.filters = [] #(fn, funcname, line), func
self.nick = nick self.nick = nick
self.channel = channel self.channel = channel
self.network = network self.network = network
@ -30,55 +31,43 @@ bot = Bot(nick, channel, network)
print 'Loading plugins' print 'Loading plugins'
typs = '|'.join('command filter event'.split()) typs = '|'.join('command filter event'.split())
magic_re = re.compile(r'^\s*#(%s)(?:: +(\S+) *(\S.*)?)?\s*$' % typs) magic_re = re.compile(r'^\s*#!(%s)(?:: +(\S+) *(\S.*)?)?\s*$' % typs)
def reload_plugins(mtime=[0]): def reload_plugins(mtime=[0]):
new_mtime = os.stat('plugins') new_mtime = os.stat('plugins')
if new_mtime == mtime[0]: if new_mtime == mtime[0]:
return return
bot.commands = [] bot.plugs = collections.defaultdict(lambda: [])
bot.filters = []
for filename in glob.glob("plugins/*.py"): for filename in glob.glob("plugins/*.py"):
shortname = os.path.splitext(os.path.basename(filename))[0] shortname = os.path.splitext(os.path.basename(filename))[0]
try: try:
plugin = imp.load_source(shortname, filename) plugin = imp.load_source(shortname, filename)
source = open(filename).read().split('\n') for thing in dir(plugin):
#this is a nasty hack, but it simplifies registration thing = getattr(plugin, thing)
funcs = [x for x in dir(plugin) if hasattr(thing, '_skybot_hook'):
if str(type(getattr(plugin,x))) == "<type 'function'>"] for type, data in thing._skybot_hook:
for func in funcs: bot.plugs[type] += [data]
#read the line before the function definition, looking for a
# comment that tells the bot about what it does
func = getattr(plugin, func)
lineno = func.func_code.co_firstlineno
if lineno == 1:
continue #can't have a line before the first line...
m = magic_re.match(source[lineno-2])
if m:
typ, nam, rest = m.groups()
if nam is None:
nam = func.__name__
if rest is None:
rest = '\s*(.*)'
if typ == 'command':
args = {'name': nam, 'hook': nam + rest}
bot.commands.append((filename, nam, func, args))
elif typ == 'filter':
bot.filters.append((filename, nam, func))
elif typ == 'event':
args = {'name': nam, 'prefix':False,
'events': [nam] + rest.split()}
bot.commands.append((filename, nam, func, args))
except Exception, e: except Exception, e:
print e print ' error:', e
mtime[0] = new_mtime mtime[0] = new_mtime
reload_plugins() reload_plugins()
print ' plugin listing:'
for type, plugs in sorted(bot.plugs.iteritems()):
print ' %s:' % type
for plug in plugs:
out = ' %s:%s:%s' % (plug[0])
print out,
if len(plug) == 3 and 'hook' in plug[2]:
print '%s%s' % (' ' * (35 - len(out)), plug[2]['hook'])
else:
print
print
print 'Connecting to IRC' print 'Connecting to IRC'
bot.irc = irc.irc(network, nick) bot.irc = irc.irc(network, nick)
bot.irc.join(channel) bot.irc.join(channel)
@ -100,13 +89,12 @@ class Input(object):
self.msg = msg self.msg = msg
class FakeBot(object): class FakeBot(object):
def __init__(self, bot, input, fn, func): def __init__(self, bot, input, func):
self.bot = bot self.bot = bot
self.input = input self.input = input
self.msg = bot.irc.msg self.msg = bot.irc.msg
self.cmd = bot.irc.cmd self.cmd = bot.irc.cmd
self.join = bot.irc.join self.join = bot.irc.join
self.fn = func
self.func = func self.func = func
self.doreply = True self.doreply = True
if input.command == "PRIVMSG": if input.command == "PRIVMSG":
@ -130,22 +118,19 @@ class FakeBot(object):
else: else:
self.say(unicode(out)) self.say(unicode(out))
print bot.commands
print bot.filters
while True: while True:
try: try:
out = bot.irc.out.get(timeout=1) out = bot.irc.out.get(timeout=1)
reload_plugins() reload_plugins()
for fn, name, func, args in bot.commands: for csig, func, args in (bot.plugs['command'] + bot.plugs['event']):
input = Input(*out) input = Input(*out)
for fn, nam, filt in bot.filters: for fsig, sieve in bot.plugs['sieve']:
input = filt(bot, func, args, input) input = sieve(bot, input, func, args)
if input == None: if input == None:
break break
if input == None: if input == None:
continue continue
print '<<<', input.raw print '<<<', input.raw
thread.start_new_thread(FakeBot(bot, input, fn, func).run, ()) thread.start_new_thread(FakeBot(bot, input, func).run, ())
except Queue.Empty: except Queue.Empty:
pass pass

View File

@ -4,10 +4,12 @@ http://brainfuck.sourceforge.net/brain.py'''
import re import re
import random import random
import hook
BUFFER_SIZE = 5000 BUFFER_SIZE = 5000
MAX_STEPS = 1000000 MAX_STEPS = 1000000
#command @hook.command
def bf(input): def bf(input):
"""Runs a Brainfuck program.""" """Runs a Brainfuck program."""
@ -50,7 +52,8 @@ def bf(input):
if mp > rightmost: if mp > rightmost:
rightmost = mp rightmost = mp
if mp >= len(memory): if mp >= len(memory):
memory.extend([0]*BUFFER_SIZE) # no restriction on memory growth! # no restriction on memory growth!
memory.extend([0]*BUFFER_SIZE)
elif c == '<': elif c == '<':
mp = mp - 1 % len(memory) mp = mp - 1 % len(memory)
elif c == '.': elif c == '.':

View File

@ -6,6 +6,8 @@ simulates dicerolls
import re import re
import random import random
import hook
whitespace_re = re.compile(r'\s+') whitespace_re = re.compile(r'\s+')
valid_diceroll_re = re.compile(r'^[+-]?(\d+|\d*d\d+)([+-](\d+|\d*d\d+))*$') valid_diceroll_re = re.compile(r'^[+-]?(\d+|\d*d\d+)([+-](\d+|\d*d\d+))*$')
sign_re = re.compile(r'[+-]?(?:\d*d)?\d+') sign_re = re.compile(r'[+-]?(?:\d*d)?\d+')
@ -25,7 +27,7 @@ def nrolls(count, n):
return int(random.normalvariate(.5*(1+n)*count, return int(random.normalvariate(.5*(1+n)*count,
(((n+1)*(2*n+1)/6.-(.5*(1+n))**2)*count)**.5)) (((n+1)*(2*n+1)/6.-(.5*(1+n))**2)*count)**.5))
#command @hook.command
def dice(input): def dice(input):
".dice <diceroll> - simulates dicerolls, e.g. .dice 2d20-d5+4 roll 2 " \ ".dice <diceroll> - simulates dicerolls, e.g. .dice 2d20-d5+4 roll 2 " \
"D20s, subtract 1D5, add 4" "D20s, subtract 1D5, add 4"

View File

@ -1,7 +1,9 @@
import re import re
#filter import hook
def filter_suite(bot, func, args, input):
@hook.sieve
def filter_suite(bot, input, func, args):
args.setdefault('events', ['PRIVMSG']) args.setdefault('events', ['PRIVMSG'])
if input.command not in args['events']: if input.command not in args['events']:
@ -15,6 +17,9 @@ def filter_suite(bot, func, args, input):
if args['prefix']: if args['prefix']:
hook = bot.commandprefix + args['hook'] hook = bot.commandprefix + args['hook']
if input.command == 'INVITE':
print func, hook
input.re = re.match(hook, input.msg) input.re = re.match(hook, input.msg)
if input.re is None: if input.re is None:
return None return None

View File

@ -1,14 +1,16 @@
import hashlib import hashlib
#command import hook
@hook.command
def md5(input): def md5(input):
return hashlib.md5(input).hexdigest() return hashlib.md5(input).hexdigest()
#command @hook.command
def sha1(input): def sha1(input):
return hashlib.sha1(input).hexdigest() return hashlib.sha1(input).hexdigest()
#command @hook.command
def hash(input): def hash(input):
return ', '.join(x + ": " + getattr(hashlib, x)(input).hexdigest() return ', '.join(x + ": " + getattr(hashlib, x)(input).hexdigest()
for x in 'md5 sha1 sha256'.split()) for x in 'md5 sha1 sha256'.split())

58
plugins/hook.py Normal file
View File

@ -0,0 +1,58 @@
def _isfunc(x):
if type(x) == type(_isfunc):
return True
return False
def _hook_add(func, add):
if not hasattr(func, '_skybot_hook'):
func._skybot_hook = []
func._skybot_hook.append(add)
def _make_sig(f):
return f.func_code.co_filename, f.func_name, f.func_code.co_firstlineno
def sieve(func):
if func.func_code.co_argcount != 4:
raise ValueError, \
'sieves must take 4 arguments: (bot, input, func, args)'
_hook_add(func, ['sieve', (_make_sig(func), func)])
return func
def command(func, hook=None, **kwargs):
args = {}
def command_wrapper(func):
if func.func_code.co_argcount not in (1, 2):
raise ValueError, \
'commands must take 1 or 2 arguments: (inp) or (bot, input)'
args.setdefault('name', func.func_name)
args.setdefault('hook', args['name'] + r'\s*(.*)')
_hook_add(func, ['command', (_make_sig(func), func, args)])
return func
if hook is not None or kwargs or not _isfunc(func):
if func is not None:
args['name'] = func
if hook is not None:
args['hook'] = hook
args.update(kwargs)
return command_wrapper
else:
return command_wrapper(func)
def event(arg):
args = {}
def event_wrapper(func):
if func.func_code.co_argcount != 2:
raise ValueError, \
'events must take 2 arguments: (bot, input)'
args['name'] = func.func_name
args['prefix'] = False
args.setdefault('events', '*')
_hook_add(func, ['event', (_make_sig(func), func, args)])
return func
if _isfunc(arg):
return event_wrapper(arg)
else:
args['events'] = arg.split()
return event_wrapper

View File

@ -1,8 +1,13 @@
#event: KICK INVITE import hook
@hook.event('KICK INVITE')
def rejoin(bot, input): def rejoin(bot, input):
print input.command, input.inp
if input.command == 'KICK': if input.command == 'KICK':
if input.paraml[1] == bot.bot.nick: if input.paraml[1] == bot.bot.nick:
bot.join(input.paraml[0]) if input.paraml[0] == bot.bot.channel:
bot.join(input.paraml[0])
if input.command == 'INVITE': if input.command == 'INVITE':
bot.join(input.inp) bot.join(input.inp)

View File

@ -1,9 +1,11 @@
import urllib import urllib
import re import re
import hook
re_lineends = re.compile(r'[\r\n]*') re_lineends = re.compile(r'[\r\n]*')
#command @hook.command
def py(input): def py(input):
res = urllib.urlopen("http://eval.appspot.com/eval?statement=%s" % res = urllib.urlopen("http://eval.appspot.com/eval?statement=%s" %
urllib.quote(input.strip(),safe='')).readlines() urllib.quote(input.strip(),safe='')).readlines()

View File

@ -6,7 +6,9 @@ retrieves most recent tweets
import urllib import urllib
from xml.etree import ElementTree from xml.etree import ElementTree
#command import hook
@hook.command
def twitter(bot, input): def twitter(bot, input):
'''.twitter <user> - gets most recent tweet from <user>''' '''.twitter <user> - gets most recent tweet from <user>'''
if not input.inp.strip(): if not input.inp.strip():

View File

@ -4,7 +4,9 @@
import urllib import urllib
from xml.etree import ElementTree from xml.etree import ElementTree
#command: weather import hook
@hook.command
def weather(bot, input): def weather(bot, input):
".weather <location> -- queries the google weather API for weather data" ".weather <location> -- queries the google weather API for weather data"

17
plugins/youtube.py Normal file
View File

@ -0,0 +1,17 @@
import urllib
from xml.etree import ElementTree
def ytdata(id):
url = 'http://gdata.youtube.com/feeds/api/videos/' + idt
print url
data = urllib.urlopen(url).read()
if len(data) < 50: # it's some error message; ignore
print data
return
global x
def
x = ElementTree.XML(data)
if __name__ == '__main__':
ytdata('the0KZLEacs')