diff --git a/core/main.py b/core/main.py index cd3a00f..70026eb 100644 --- a/core/main.py +++ b/core/main.py @@ -1,43 +1,44 @@ import thread import traceback -class Input(object): +class Input(dict): + def __init__(self, conn, raw, prefix, command, params, + nick, user, host, paraml, msg): + + chan = paraml[0].lower() + if chan == conn.nick: # is a PM + chan = nick - def __init__(self, conn, raw, prefix, command, - params, nick, user, host, paraml, msg): - self.conn = conn # irc object - self.server = conn.server # hostname of server - self.raw = raw # unprocessed line of text - self.prefix = prefix # usually hostmask - self.command = command # PRIVMSG, JOIN, etc. - self.params = params - self.nick = nick - self.user = user # user@host - self.host = host - self.paraml = paraml # params[-1] without the : - self.msg = msg - self.chan = paraml[0].lower() - if self.chan == conn.nick: # is a PM - self.chan = nick + def say(msg): + conn.msg(chan, msg) - def say(self, msg): - self.conn.msg(self.chan, msg) + def reply(msg): + conn.msg(chan, nick + ': ' + msg) - def reply(self, msg): - self.say(self.nick + ': ' + msg) + def pm(msg): + conn.msg(nick, msg) - def pm(self, msg): - self.conn.msg(self.nick, msg) + dict.__init__(self, conn=conn, raw=raw, prefix=prefix, command=command, + params=params, nick=nick, user=user, host=host, + paraml=paraml, msg=msg, server=conn.server, chan=chan, + say=say, reply=reply, pm=pm, bot=bot) + self.__dict__ = self # permits attribute access to values def run(func, input): - ac = func.func_code.co_argcount - if ac == 2: - out = func(bot, input) - elif ac == 1: - out = func(input.inp) + args = func._skybot_args + if args: + if 'db' in args: + input['db'] = get_db_connection(input['server']) + if 0 in args: + out = func(input['inp'], **input) + else: + kw = dict((key, input[key]) for key in args if key in input) + out = func(input['inp'], **kw) + else: + out = func(input['inp']) if out is not None: - input.reply(unicode(out)) + input['reply'](unicode(out)) def main(conn, out): diff --git a/plugins/goonsay.py b/plugins/goonsay.py index 7cd3623..42c8eb9 100644 --- a/plugins/goonsay.py +++ b/plugins/goonsay.py @@ -2,7 +2,7 @@ from util import hook #Scaevolus: factormystic if you commit a re-enabled goonsay I'm going to revoke your commit access #@hook.command -def goonsay(bot, input): - input.say(' __________ /') - input.say('(--[. ]-[ .] /') - input.say('(_______o__)') +def goonsay(inp, say=None): + say(' __________ /') + say('(--[. ]-[ .] /') + say('(_______o__)') diff --git a/plugins/help.py b/plugins/help.py index af55d2d..c616a9e 100644 --- a/plugins/help.py +++ b/plugins/help.py @@ -1,7 +1,7 @@ from util import hook @hook.command -def help(bot, input): +def help(inp, bot=None, pm=None): ".help [command] -- gives a list of commands/help for a command" funcs = {} @@ -10,8 +10,8 @@ def help(bot, input): if func.__doc__ is not None: funcs[csig[1]] = func - if not input.inp: - input.pm('available commands: ' + ' '.join(sorted(funcs))) + if not inp: + pm('available commands: ' + ' '.join(sorted(funcs))) else: - if input.inp in funcs: - input.pm(funcs[input.inp].__doc__) + if inp in funcs: + pm(funcs[inp].__doc__) diff --git a/plugins/misc.py b/plugins/misc.py index 181db84..8a94cb1 100644 --- a/plugins/misc.py +++ b/plugins/misc.py @@ -6,25 +6,25 @@ socket.setdefaulttimeout(5) # global setting #autorejoin channels @hook.event('KICK') -def rejoin(bot, input): - if input.paraml[1] == input.conn.nick: - if input.paraml[0] in input.conn.channels: - input.conn.join(input.paraml[0]) +def rejoin(inp, paraml=[], conn=None): + if paraml[1] == conn.nick: + if paraml[0].lower() in conn.channels: + conn.join(paraml[0]) #join channels when invited @hook.event('INVITE') -def invite(bot, input): - if input.command == 'INVITE': - input.conn.join(input.inp) +def invite(inp, command='', conn=None): + if command == 'INVITE': + conn.join(inp) #join channels when server says hello & identify bot @hook.event('004') -def onjoin(bot, input): - for channel in input.conn.channels: - input.conn.join(channel) +def onjoin(inp, conn=None): + for channel in conn.channels: + conn.join(channel) - nickserv_password = input.conn.conf.get('nickserv_password', '') - nickserv_name = input.conn.conf.get('nickserv_name', 'nickserv') - nickserv_command = input.conn.conf.get('nickserv_command', 'IDENTIFY %s') + nickserv_password = conn.conf.get('nickserv_password', '') + nickserv_name = conn.conf.get('nickserv_name', 'nickserv') + nickserv_command = conn.conf.get('nickserv_command', 'IDENTIFY %s') if nickserv_password: - input.conn.msg(nickserv_name, nickserv_command % nickserv_password) + conn.msg(nickserv_name, nickserv_command % nickserv_password) diff --git a/plugins/mtg.py b/plugins/mtg.py index 0d34180..2ea9d63 100644 --- a/plugins/mtg.py +++ b/plugins/mtg.py @@ -164,6 +164,7 @@ set_abbrevs = { 'Zendikar': 'ZEN'} rarity_abbrevs = { + 'Land': 'L', 'Common': 'C', 'Uncommon': 'UC', 'Rare': 'R', diff --git a/plugins/quote.py b/plugins/quote.py index 1481a2a..3a1b0a8 100644 --- a/plugins/quote.py +++ b/plugins/quote.py @@ -5,20 +5,20 @@ import time from util import hook -def add_quote(conn, chan, nick, add_nick, msg): +def add_quote(db, chan, nick, add_nick, msg): now = time.time() - conn.execute('''insert or fail into quote (chan, nick, add_nick, + db.execute('''insert or fail into quote (chan, nick, add_nick, msg, time) values(?,?,?,?,?)''', (chan, nick, add_nick, msg, now)) - conn.commit() + db.commit() -def get_quotes_by_nick(conn, chan, nick): - return conn.execute("select time, nick, msg from quote where deleted!=1 " +def get_quotes_by_nick(db, chan, nick): + return db.execute("select time, nick, msg from quote where deleted!=1 " "and chan=? and lower(nick)=lower(?) order by time", (chan, nick)).fetchall() -def get_quotes_by_chan(conn, chan): - return conn.execute("select time, nick, msg from quote where deleted!=1 " +def get_quotes_by_chan(db, chan): + return db.execute("select time, nick, msg from quote where deleted!=1 " "and chan=? order by time", (chan,)).fetchall() @@ -29,26 +29,24 @@ def format_quote(q, num, n_quotes): @hook.command('q') @hook.command -def quote(bot, input): +def quote(inp, nick='', chan='', db=None): ".q/.quote [#n]/.quote add -- gets " \ "random or [#n]th quote by or from <#chan>/adds quote" - conn = bot.get_db_connection(input.server) - conn.execute("create table if not exists quote" + db.execute("create table if not exists quote" "(chan, nick, add_nick, msg, time real, deleted default 0, " "primary key (chan, nick, msg))") - conn.commit() + db.commit() try: - add = re.match(r"add\s+?\s+(.*)", input.inp, re.I) - retrieve = re.match(r"(\S+)(?:\s+#?(-?\d+))?", input.inp) - chan = input.chan + add = re.match(r"add\s+?\s+(.*)", inp, re.I) + retrieve = re.match(r"(\S+)(?:\s+#?(-?\d+))?", inp) if add: - nick, msg = add.groups() + quoted_nick, msg = add.groups() try: - add_quote(conn, chan, nick, input.nick, msg) - except conn.IntegrityError: + add_quote(db, chan, quoted_nick, nick, msg) + except db.IntegrityError: return "message already stored, doing nothing." return "quote added." elif retrieve: @@ -57,9 +55,9 @@ def quote(bot, input): by_chan = False if select.startswith('#'): by_chan = True - quotes = get_quotes_by_chan(conn, select) + quotes = get_quotes_by_chan(db, select) else: - quotes = get_quotes_by_nick(conn, chan, select) + quotes = get_quotes_by_nick(db, chan, select) n_quotes = len(quotes) @@ -83,5 +81,5 @@ def quote(bot, input): else: return quote.__doc__ finally: - conn.commit() - conn.close() + db.commit() + db.close() diff --git a/plugins/regular.py b/plugins/regular.py index f8d1d15..47d9ffa 100644 --- a/plugins/regular.py +++ b/plugins/regular.py @@ -5,34 +5,20 @@ skybot plugin for testing regular expressions by Ipsum ''' -import thread -import codecs import re from util import hook - @hook.command('re') -def reg(bot, input): - ".re -- matches regular expression in given (seperate regex and string by 2 spaces)" +def reg(inp): + ".re -- matches regular expression in given "\ + "(leave 2 spaces between)" - m = "" - - if len(input.msg) < 3: - return reg.__doc__ - - query = input.inp.partition(" ") - - - if query[2] != "": - r = re.compile(query[0]) + query = inp.split(" ", 1) + + if not inp or len(query) != 2: + return reg.__doc__ + + return '|'.join(re.findall(query[0], query[1])) - matches = r.findall(query[2]) - for match in matches: - m += match + "|" - - return m.rstrip('|') - - else: - return reg.__doc__ \ No newline at end of file diff --git a/plugins/seen.py b/plugins/seen.py index 7e4d463..a917dc5 100644 --- a/plugins/seen.py +++ b/plugins/seen.py @@ -10,47 +10,41 @@ def seeninput(bot, input): if input.command != 'PRIVMSG': return - conn = db_connect(bot, input.server) - cursor = conn.cursor() - cursor.execute("insert or replace into seen(name, time, quote, chan)" - "values(?,?,?,?)", (input.nick.lower(), time.time(), - input.msg, input.chan)) - conn.commit() + db = bot.get_db_connection(input.server) + db_init(db) + db.execute("insert or replace into seen(name, time, quote, chan)" + "values(?,?,?,?)", (input.nick.lower(), time.time(), input.msg, + input.chan)) + db.commit() @hook.command -def seen(bot, input): +def seen(inp, nick='', chan='', db=None): ".seen -- Tell when a nickname was last in active in irc" - if not input.inp: + if not inp: return seen.__doc__ - query = input.inp - - if query.lower() == input.nick.lower(): + if inp.lower() == nick.lower(): return "Have you looked in a mirror lately?" - conn = db_connect(bot, input.server) - cursor = conn.cursor() + db_init(db) - command = "select time, quote FROM seen WHERE name LIKE ? AND chan = ?" - cursor.execute(command, (query, input.chan)) - results = cursor.fetchone() + last_seen = db.execute("select name, time, quote from seen where name" + " like ? and chan = ?", (inp, chan)).fetchone() - if results: - reltime = timesince.timesince(results[0]) + if last_seen: + reltime = timesince.timesince(last_seen[1]) + if last_seen[0] != inp.lower(): # for glob matching + inp = last_seen[0] return '%s was last seen %s ago saying: %s' % \ - (query, reltime, results[1]) + (inp, reltime, last_seen[2]) else: - return "I've never seen %s" % query + return "I've never seen %s" % inp -def db_connect(bot, server): +def db_init(db): "check to see that our db has the the seen table and return a connection." - conn = bot.get_db_connection(server) - - conn.execute("create table if not exists seen(name, time, quote, chan, " + db.execute("create table if not exists seen(name, time, quote, chan, " "primary key(name, chan))") - conn.commit() - - return conn + db.commit() diff --git a/plugins/suggest.py b/plugins/suggest.py index c92c821..5be7a26 100644 --- a/plugins/suggest.py +++ b/plugins/suggest.py @@ -7,12 +7,12 @@ import json from util import hook @hook.command -def suggest(bot, input): +def suggest(inp, inp_unstripped=''): ".suggest [#n] -- gets a random/the nth suggested google search" - if not input.inp: + if not inp: return suggest.__doc__ - inp = input.inp_unstripped + inp = inp_unstripped m = re.match('^#(\d+) (.+)$', inp) if m: num, inp = m.groups() diff --git a/plugins/tell.py b/plugins/tell.py index ba93f4d..8353173 100644 --- a/plugins/tell.py +++ b/plugins/tell.py @@ -5,20 +5,24 @@ import time from util import hook, timesince -def get_tells(conn, user_to, chan): - return conn.execute("select user_from, message, time from tell where" +def get_tells(db, user_to, chan): + return db.execute("select user_from, message, time from tell where" " user_to=lower(?) and chan=? order by time", (user_to.lower(), chan)).fetchall() -@hook.command(hook=r'(.*)', prefix=False) +@hook.tee def tellinput(bot, input): - if 'showtells' in input.inp.lower(): + if input.command != 'PRIVMSG': + return + + if 'showtells' in input.msg.lower(): return - conn = db_connect(bot, input.server) + db = bot.get_db_connection(input.server) + db = db_init(db) - tells = get_tells(conn, input.nick, input.chan) + tells = get_tells(db, input.nick, input.chan) if tells: user_from, message, time = tells[0] @@ -28,72 +32,70 @@ def tellinput(bot, input): if len(tells) > 1: reply += " (+%d more, .showtells to view)" % (len(tells) - 1) - conn.execute("delete from tell where user_to=lower(?) and message=?", + db.execute("delete from tell where user_to=lower(?) and message=?", (input.nick, message)) - conn.commit() - return reply + db.commit() + input.reply(reply) @hook.command -def showtells(bot, input): +def showtells(inp, nick='', chan='', pm=None, db=None): ".showtells -- view all pending tell messages (sent in PM)." - conn = db_connect(bot, input.server) + db_init(db) - tells = get_tells(conn, input.nick, input.chan) + tells = get_tells(db, nick, chan) if not tells: - input.pm("You have no pending tells.") + pm("You have no pending tells.") return for tell in tells: user_from, message, time = tell reltime = timesince.timesince(time) - input.pm("%s said %s ago: %s" % (user_from, reltime, message)) + pm("%s said %s ago: %s" % (user_from, reltime, message)) - conn.execute("delete from tell where user_to=lower(?) and chan=?", - (input.nick, input.chan)) - conn.commit() + db.execute("delete from tell where user_to=lower(?) and chan=?", + (nick, chan)) + db.commit() @hook.command -def tell(bot, input): +def tell(inp, nick='', chan='', db=None): ".tell -- relay to when is around" - query = input.inp.split(' ', 1) + query = inp.split(' ', 1) - if len(query) != 2 or not input.inp: + if not inp or len(query) != 2: return tell.__doc__ user_to = query[0].lower() message = query[1].strip() - user_from = input.nick + user_from = nick if user_to == user_from.lower(): return "No." - conn = db_connect(bot, input.server) + db_init(db) - if conn.execute("select count() from tell where user_to=?", + if db.execute("select count() from tell where user_to=?", (user_to,)).fetchone()[0] >= 5: return "That person has too many things queued." try: - conn.execute("insert into tell(user_to, user_from, message, chan," + db.execute("insert into tell(user_to, user_from, message, chan," "time) values(?,?,?,?,?)", (user_to, user_from, message, - input.chan, time.time())) - conn.commit() - except conn.IntegrityError: + chan, time.time())) + db.commit() + except db.IntegrityError: return "Message has already been queued." return "I'll pass that along." -def db_connect(bot, server): - "check to see that our db has the tell table and return a connection." - conn = bot.get_db_connection(server) - - conn.execute("create table if not exists tell" +def db_init(db): + "check to see that our db has the tell table and return a dbection." + db.execute("create table if not exists tell" "(user_to, user_from, message, chan, time," "primary key(user_to, message))") - conn.commit() + db.commit() - return conn + return db diff --git a/plugins/urlhistory.py b/plugins/urlhistory.py index c16f27a..37cd21b 100644 --- a/plugins/urlhistory.py +++ b/plugins/urlhistory.py @@ -11,23 +11,23 @@ expiration_period_text = "24 hours" ignored_urls = [urlnorm.normalize("http://google.com")] def db_connect(bot, server): - "check to see that our db has the the seen table and return a connection." - conn = bot.get_db_connection(server) - conn.execute("create table if not exists urlhistory" + "check to see that our db has the the seen table and return a dbection." + db = bot.get_db_connection(server) + db.execute("create table if not exists urlhistory" "(chan, url, nick, time)") - conn.commit() - return conn + db.commit() + return db -def insert_history(conn, chan, url, nick): +def insert_history(db, chan, url, nick): now = time.time() - conn.execute("insert into urlhistory(chan, url, nick, time) " + db.execute("insert into urlhistory(chan, url, nick, time) " "values(?,?,?,?)", (chan, url, nick, time.time())) - conn.commit() + db.commit() -def get_history(conn, chan, url): - conn.execute("delete from urlhistory where time < ?", +def get_history(db, chan, url): + db.execute("delete from urlhistory where time < ?", (time.time() - expiration_period,)) - nicks = conn.execute("select nick from urlhistory where " + nicks = db.execute("select nick from urlhistory where " "chan=? and url=?", (chan, url)).fetchall() return [x[0] for x in nicks] @@ -42,22 +42,22 @@ def ordinal(count): return ["once", "twice", "%d times" % count][min(count, 3) - 1] @hook.command(hook=r'(.*)', prefix=False) -def urlinput(bot, input): - m = url_re.search(input.msg.encode('utf8')) +def urlinput(inp, nick='', chan='', server='', reply=None, bot=None): + m = url_re.search(inp.encode('utf8')) if not m: return # URL detected - conn = db_connect(bot, input.server) + db = db_connect(bot, server) try: url = urlnorm.normalize(m.group(0)) if url not in ignored_urls: - dupes = get_history(conn, input.chan, url) - insert_history(conn, input.chan, url, input.nick) - if dupes and input.nick not in dupes: - input.reply("That link has been posted " + ordinal(len(dupes)) + dupes = get_history(db, chan, url) + insert_history(db, chan, url, nick) + if dupes and nick not in dupes: + reply("That link has been posted " + ordinal(len(dupes)) + " in the past " + expiration_period_text + " by " + get_nicklist(dupes)) finally: - conn.commit() - conn.close() + db.commit() + db.close() diff --git a/plugins/util/hook.py b/plugins/util/hook.py index f6e257a..22963f4 100644 --- a/plugins/util/hook.py +++ b/plugins/util/hook.py @@ -1,6 +1,7 @@ -import Queue +import inspect import thread import traceback +import Queue def _isfunc(x): if type(x) == type(_isfunc): @@ -8,11 +9,33 @@ def _isfunc(x): return False -def _hook_add(func, add): +def _hook_add(func, add, name=''): if not hasattr(func, '_skybot_hook'): func._skybot_hook = [] func._skybot_hook.append(add) + if not hasattr(func, '_skybot_args'): + argspec = inspect.getargspec(func) + if name: + n_args = len(argspec.args) + if argspec.defaults: + n_args -= len(argspec.defaults) + if argspec.keywords: + n_args -= 1 + if argspec.varargs: + n_args -= 1 + if n_args != 1: + err = '%ss must take 1 non-keyword argument (%s)' % (name, + func.__name__) + raise ValueError(err) + args = [] + if argspec.defaults: + end = bool(argspec.keywords) + bool(argspec.varargs) + args.extend(argspec.args[-len(argspec.defaults): + end if end else None]) + if argspec.keywords: + args.append(0) # means kwargs present + func._skybot_args = args def _make_sig(f): return f.func_code.co_filename, f.func_name, f.func_code.co_firstlineno @@ -30,12 +53,9 @@ def command(func=None, 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)]) + _hook_add(func, ['command', (_make_sig(func), func, args)], 'command') return func if hook is not None or kwargs or not _isfunc(func): @@ -53,12 +73,10 @@ def event(arg=None, **kwargs): args = kwargs 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)]) + _hook_add(func, ['event', (_make_sig(func), func, args)], 'event') return func if _isfunc(arg): @@ -74,7 +92,7 @@ def tee(func, **kwargs): if func.func_code.co_argcount != 2: raise ValueError('tees must take 2 arguments: (bot, input)') - + _hook_add(func, ['tee', (_make_sig(func), func, kwargs)]) func._iqueue = Queue.Queue() diff --git a/plugins/weather.py b/plugins/weather.py index d487319..6c9481e 100644 --- a/plugins/weather.py +++ b/plugins/weather.py @@ -7,20 +7,20 @@ from util import hook @hook.command -def weather(bot, input): +def weather(inp, nick='', server='', reply=None, db=None): ".weather [dontsave] -- queries the google weather API for weather data" - loc = input.inp + + loc = inp dontsave = loc.endswith(" dontsave") if dontsave: loc = loc[:-9].strip().lower() - conn = bot.get_db_connection(input.server) - conn.execute("create table if not exists weather(nick primary key, loc)") + db.execute("create table if not exists weather(nick primary key, loc)") if not loc: # blank line - loc = conn.execute("select loc from weather where nick=lower(?)", - (input.nick,)).fetchone() + loc = db.execute("select loc from weather where nick=lower(?)", + (nick,)).fetchone() if not loc: return weather.__doc__ loc = loc[0] @@ -31,17 +31,17 @@ def weather(bot, input): if w.find('problem_cause') is not None: return "Couldn't fetch weather data for '%s', try using a zip or " \ - "postal code." % input.inp + "postal code." % inp info = dict((e.tag, e.get('data')) for e in w.find('current_conditions')) info['city'] = w.find('forecast_information/city').get('data') info['high'] = w.find('forecast_conditions/high').get('data') info['low'] = w.find('forecast_conditions/low').get('data') - input.reply('%(city)s: %(condition)s, %(temp_f)sF/%(temp_c)sC (H:%(high)sF'\ + reply('%(city)s: %(condition)s, %(temp_f)sF/%(temp_c)sC (H:%(high)sF'\ ', L:%(low)sF), %(humidity)s, %(wind_condition)s.' % info) - if input.inp and not dontsave: - conn.execute("insert or replace into weather(nick, loc) values (?,?)", - (input.nick.lower(), loc)) - conn.commit() + if inp and not dontsave: + db.execute("insert or replace into weather(nick, loc) values (?,?)", + (nick.lower(), loc)) + db.commit()