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 thread
import Queue
import copy
import collections
import irc
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):
def __init__(self, nick, channel, network):
self.commands = [] # fn, name, func, args
self.filters = [] #fn, name, func
self.commands = [] # (fn, funcname, name, line), func, args
self.filters = [] #(fn, funcname, line), func
self.nick = nick
self.channel = channel
self.network = network
@ -30,55 +31,43 @@ bot = Bot(nick, channel, network)
print 'Loading plugins'
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]):
new_mtime = os.stat('plugins')
if new_mtime == mtime[0]:
return
bot.commands = []
bot.filters = []
bot.plugs = collections.defaultdict(lambda: [])
for filename in glob.glob("plugins/*.py"):
shortname = os.path.splitext(os.path.basename(filename))[0]
try:
plugin = imp.load_source(shortname, filename)
source = open(filename).read().split('\n')
#this is a nasty hack, but it simplifies registration
funcs = [x for x in dir(plugin)
if str(type(getattr(plugin,x))) == "<type 'function'>"]
for func in funcs:
#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))
for thing in dir(plugin):
thing = getattr(plugin, thing)
if hasattr(thing, '_skybot_hook'):
for type, data in thing._skybot_hook:
bot.plugs[type] += [data]
except Exception, e:
print e
print ' error:', e
mtime[0] = new_mtime
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'
bot.irc = irc.irc(network, nick)
bot.irc.join(channel)
@ -100,13 +89,12 @@ class Input(object):
self.msg = msg
class FakeBot(object):
def __init__(self, bot, input, fn, func):
def __init__(self, bot, input, func):
self.bot = bot
self.input = input
self.msg = bot.irc.msg
self.cmd = bot.irc.cmd
self.join = bot.irc.join
self.fn = func
self.func = func
self.doreply = True
if input.command == "PRIVMSG":
@ -130,22 +118,19 @@ class FakeBot(object):
else:
self.say(unicode(out))
print bot.commands
print bot.filters
while True:
try:
out = bot.irc.out.get(timeout=1)
reload_plugins()
for fn, name, func, args in bot.commands:
for csig, func, args in (bot.plugs['command'] + bot.plugs['event']):
input = Input(*out)
for fn, nam, filt in bot.filters:
input = filt(bot, func, args, input)
for fsig, sieve in bot.plugs['sieve']:
input = sieve(bot, input, func, args)
if input == None:
break
if input == None:
continue
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:
pass

View File

@ -4,10 +4,12 @@ http://brainfuck.sourceforge.net/brain.py'''
import re
import random
import hook
BUFFER_SIZE = 5000
MAX_STEPS = 1000000
#command
@hook.command
def bf(input):
"""Runs a Brainfuck program."""
@ -50,7 +52,8 @@ def bf(input):
if mp > rightmost:
rightmost = mp
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 == '<':
mp = mp - 1 % len(memory)
elif c == '.':

View File

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

View File

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

View File

@ -1,14 +1,16 @@
import hashlib
#command
import hook
@hook.command
def md5(input):
return hashlib.md5(input).hexdigest()
#command
@hook.command
def sha1(input):
return hashlib.sha1(input).hexdigest()
#command
@hook.command
def hash(input):
return ', '.join(x + ": " + getattr(hashlib, x)(input).hexdigest()
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):
print input.command, input.inp
if input.command == 'KICK':
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':
bot.join(input.inp)

View File

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

View File

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

View File

@ -4,7 +4,9 @@
import urllib
from xml.etree import ElementTree
#command: weather
import hook
@hook.command
def weather(bot, input):
".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')