From 2aa369add73d9e59882010ea3ed74153f1557b75 Mon Sep 17 00:00:00 2001 From: "Brian S. Stephan" Date: Thu, 17 Feb 2011 01:08:45 -0600 Subject: [PATCH] rewrite recursion/alias code for the 500th time. more of a moving of the code, actually, it now exists in (an overridden) _handle_event, so that recursions happen against irc events directly, rather than an already partially interpreted object. with this change, modules don't need to implement do() nor do we have a need for the internal_bus, which was doing an additional walk of the modules after the irc event was already handled and turned into text. now the core event handler does the recursion scans. to support this, we bring back the old replypath trick and use it again, so we know when to send a privmsg reply and when to return text so that it may be chained in recursion. this feels old hat by now, but if you haven't been following along, you should really look at the diff. that's the meat of the change. the rest is updating modules to use self.reply() and reimplementing (un)register_handlers where appropriate --- DrBotIRC.py | 115 +++++++++++++++++++++++-------------- Module.py | 63 ++++++++++++++++++-- modules/Countdown.py | 8 +-- modules/Dice.py | 4 +- modules/Echo.py | 2 +- modules/EightBall.py | 2 +- modules/FactFile.py | 2 +- modules/Facts.py | 8 +-- modules/GoogleTranslate.py | 2 +- modules/Help.py | 6 +- modules/IrcAdmin.py | 28 +++++---- modules/Karma.py | 8 +-- modules/Markov.py | 16 ++++-- modules/MegaHAL.py | 8 ++- modules/Pi.py | 4 +- modules/Seen.py | 2 +- modules/Storycraft.py | 16 +++--- modules/TextTransform.py | 6 +- modules/Trigger.py | 2 +- modules/Twitter.py | 12 ++-- modules/Urls.py | 4 +- modules/Weather.py | 6 +- 22 files changed, 208 insertions(+), 116 deletions(-) diff --git a/DrBotIRC.py b/DrBotIRC.py index 0418b27..73aa110 100644 --- a/DrBotIRC.py +++ b/DrBotIRC.py @@ -17,6 +17,7 @@ along with this program. If not, see . """ import bisect +import copy from ConfigParser import NoOptionError, NoSectionError import re import signal @@ -82,7 +83,6 @@ class DrBotIRC(irclib.IRC): """Implement a customized irclib IRC.""" modlist = [] - internal_bus = [] config = None server = None @@ -104,6 +104,31 @@ class DrBotIRC(irclib.IRC): return self.server + def _handle_event(self, connection, event): + """Override event handler to do recursion.""" + + self.try_recursion(connection, event) + self.try_alias(connection, event) + + h = self.handlers + for handler in h.get("all_events", []) + h.get(event.eventtype(), []): + if handler[1](connection, event) == "NO MORE": + return + + def try_to_replace_event_text_with_module_text(self, connection, event): + """Do something very similar to _handle_event, but for recursion. + + The intent here is that we replace [text] with whatever a module + provides to us. + """ + + h = self.handlers + event._target = None + for handler in h.get("all_events", []) + h.get(event.eventtype(), []): + ret = handler[1](connection, event) + if ret: + event.arguments()[0] = ret + def on_pubmsg(self, connection, event): """See if there is an alias ("!command") in the text, and if so, translate it into an internal bot command and run it. @@ -156,11 +181,10 @@ class DrBotIRC(irclib.IRC): except NoOptionError: pass except NoSectionError: pass - return self.reply(connection, replypath, self.try_recursion(connection, event, nick, userhost, what, admin_unlocked)) - - def try_alias(self, connection, event, nick, userhost, what, admin_unlocked): + def try_alias(self, connection, event): # try doing alias work try: + what = event.arguments()[0] alias_list = self.config.options('Alias') for alias in alias_list: @@ -168,19 +192,24 @@ class DrBotIRC(irclib.IRC): if alias_re.search(what): # we found an alias for our given string, doing a replace command = re.sub(alias, self.config.get('Alias', alias), what) + event.arguments()[0] = command + + # now we have to check it for recursions again + self.try_recursion(connection, event) # i guess someone could have an alias of an alias... try again - command = self.try_alias(connection, event, nick, userhost, command, admin_unlocked) - return command + return self.try_alias(connection, event) except NoOptionError: pass except NoSectionError: pass + except IndexError: pass # if we got here, there are no matching aliases, so return what we got - return what + return event - def reply(self, connection, replypath, replystr, stop_responding=False): + def reply(self, connection, event, replystr, stop_responding=False): """Reply over IRC to replypath or return a string with the reply.""" + replypath = event.target() if replystr is not None: if replypath is None: return replystr @@ -191,7 +220,7 @@ class DrBotIRC(irclib.IRC): if stop_responding: return "NO MORE" - def try_recursion(self, connection, event, nick, userhost, what, admin_unlocked): + def try_recursion(self, connection, event): """Scan message for subcommands to execute and use as part of this command. Upon seeing a line intended for this module, see if there are subcommands @@ -203,44 +232,44 @@ class DrBotIRC(irclib.IRC): where the output of anothercommand is command's arg2..n. """ - attempt = what + try: + # begin recursion search + attempt = event.arguments()[0] - # check for aliases - attempt = self.try_alias(connection, event, nick, userhost, attempt, admin_unlocked) + start_idx = attempt.find('[') + subcmd = attempt[start_idx+1:] + end_idx = subcmd.rfind(']') + subcmd = subcmd[:end_idx] - # begin recursion search + if start_idx != -1 and end_idx != -1 and len(subcmd) > 0: + # found recursion candidate + # copy the event and see if IT has recursion to do + newevent = copy.deepcopy(event) + newevent.arguments()[0] = subcmd + self.try_recursion(connection, newevent) - start_idx = attempt.find('[') - subcmd = attempt[start_idx+1:] - end_idx = subcmd.rfind(']') - subcmd = subcmd[:end_idx] + # recursion over, check for aliases + self.try_alias(connection, newevent) - if start_idx == -1 or end_idx == -1 or len(subcmd) == 0: - # no recursion, so see if there's a module to handle this - return self.scan_modules(connection, event, nick, userhost, attempt, admin_unlocked) - else: - # found recursion, search again - ret = self.try_recursion(connection, event, nick, userhost, subcmd, admin_unlocked) - if ret is not None: - # recursion search had a hit, replace [foo] with it and re-recurse - return self.try_recursion(connection, event, nick, userhost, attempt.replace('['+subcmd+']', ret), admin_unlocked) - else: - # recursion search didn't have a hit, so see if there's a module to handle this - return self.scan_modules(connection, event, nick, userhost, attempt, admin_unlocked) + # now that we have a string that has been de-aliased and + # recursed all the way deeper into its text, see if any + # modules can do something with it. this calls the same + # event handlers in the same way as if this were a native + # event. + self.try_to_replace_event_text_with_module_text(connection, newevent) - def scan_modules(self, connection, event, nick, userhost, attempt, admin_unlocked): - """Walk the loaded modules, see if any reply to input text.""" + # we have done all we can do with the sub-event. whatever + # the text of that event now is, we should replace the parent + # event's [] section with it. + oldtext = event.arguments()[0] + newtext = oldtext.replace('['+subcmd+']', newevent.arguments()[0]) + event.arguments()[0] = newtext - # aliases resolved. run result against each module - for (priority, handler) in self.internal_bus: - try: - ret = handler(connection, event, nick, userhost, attempt, admin_unlocked) - if ret is not None: - # a module had a result for us, post-alias, so return it - # TODO: scan all modules with compounding results - return ret - except Exception as e: - print('EXCEPTION: ' + str(e)) + # we have now resolved the []. recursion will unfold, replacing + # it further and further, until we eventually get back to the + # original irc event in _handle_event, which will do one + # last search on the text. + except IndexError: pass def quit_irc(self, connection, msg): for module in self.modlist: @@ -277,8 +306,7 @@ class DrBotIRC(irclib.IRC): module = sys.modules[modstr] botmod = eval('module.' + modname + '(self, self.config, self.server)') self.modlist.append(botmod) - bisect.insort(self.internal_bus, (botmod.priority(), botmod.do)) - botmod.register_handlers(self.server) + botmod.register_handlers() # might as well add it to the list modset = set(self.config.get('dr.botzo', 'module_list').split(',')) @@ -301,7 +329,6 @@ class DrBotIRC(irclib.IRC): # remove module references self.modlist.remove(module) - self.internal_bus.remove((module.priority(), module.do)) module.unregister_handlers() # del it diff --git a/Module.py b/Module.py index 5a30be0..1fa4aba 100644 --- a/Module.py +++ b/Module.py @@ -57,27 +57,80 @@ class Module(object): # print what was loaded, for debugging print("loaded " + self.__class__.__name__) - def register_handlers(self, server): + def register_handlers(self): """Hook handler functions into the IRC library. This is called when the module is loaded. Classes with special stuff to do could implement this and set up the appropriate handlers, e.g.: self.server.add_global_handler('welcome', self.on_connect) - Unless you're sure what you're doing, don't register pubmsg or privmsg. - They are handled by the alias support in DrBotIRC, and will call your - module's do(). + By default, a module attaches to pubmsg/privmsg, which sets up some common + variables and then calls do(). You are free to implement do() (see below), + or override this and do whatever you want. """ + self.server.add_global_handler('pubmsg', self.on_pub_or_privmsg, self.priority()) + self.server.add_global_handler('privmsg', self.on_pub_or_privmsg, self.priority()) + def unregister_handlers(self): """Unhook handler functions from the IRC library. Inverse of the above. This is called by reload, to remove the soon-to-be old object from the server global handlers (or whatever has been added via register_handlers). Classes - inheriting from Module could implement this, e.g.: + inheriting from Module could reimplement this, e.g.: self.server.remove_global_handler('welcome', self.on_connect) """ + self.server.remove_global_handler('pubmsg', self.on_pub_or_privmsg) + self.server.remove_global_handler('privmsg', self.on_pub_or_privmsg) + + def on_pub_or_privmsg(self, connection, event): + """Do a default thing on a pubmsg or privmsg. + + Sets up a couple variables and then calls do(), which by default we + expect implementers to implement. + """ + + nick = irclib.nm_to_n(event.source()) + userhost = irclib.nm_to_uh(event.source()) + replypath = event.target() + what = event.arguments()[0] + admin_unlocked = False + + # privmsg + if replypath == connection.get_nickname(): + replypath = nick + + try: + if userhost == self.config.get('dr.botzo', 'admin_userhost'): + admin_unlocked = True + except NoOptionError: pass + + return self.do(connection, event, nick, userhost, what, admin_unlocked) + + def reply(self, connection, event, replystr, stop_responding=False): + """Reply over IRC to replypath or return a string with the reply. + + The primary utility for this is to properly handle recursion. The recursion + code in DrBotIRC will set up a couple hints that this method picks up on and + will appropriately send an IRC event or return a string. + + Unless you know what you are doing, the modules you write should return + this method rather than send a privmsg reply, as failing to call this + method will certainly have recursion do odd things with your module. + """ + + replypath = event.target() + if replystr is not None: + if replypath is None: + return replystr + else: + replies = replystr.split('\n') + for reply in replies: + connection.privmsg(replypath, reply) + if stop_responding: + return "NO MORE" + def save(self): """Save whatever the module may need to save. Sync files, etc. diff --git a/modules/Countdown.py b/modules/Countdown.py index b0c4bf9..876bd61 100644 --- a/modules/Countdown.py +++ b/modules/Countdown.py @@ -44,7 +44,7 @@ class Countdown(Module): self.config.set(self.__class__.__name__, item, target.astimezone(tzutc()).isoformat()) replystr = 'added countdown item ' + item - return replystr + return self.reply(connection, event, replystr) match = re.search('^!countdown\s+remove\s+(\S+)$', what) if match: @@ -52,7 +52,7 @@ class Countdown(Module): item = match.group(1) if self.config.remove_option(self.__class__.__name__, item): replystr = 'removed countdown item ' + item - return replystr + return self.reply(connection, event, replystr) except NoSectionError: pass match = re.search('^!countdown\s+list$', what) @@ -62,7 +62,7 @@ class Countdown(Module): self.remove_metaoptions(cdlist) cdlist.sort() liststr = ', '.join(cdlist) - return liststr + return self.reply(connection, event, liststr) except NoSectionError: pass match = re.search('^!countdown\s+(\S+)$', what) @@ -103,7 +103,7 @@ class Countdown(Module): if rdelta.seconds > 1: relstr += 's' relstr += ', ' - return relstr[0:-2] + return self.reply(connection, event, relstr[0:-2]) except NoOptionError: pass # vi:tabstop=4:expandtab:autoindent diff --git a/modules/Dice.py b/modules/Dice.py index c2089f8..f88dba9 100644 --- a/modules/Dice.py +++ b/modules/Dice.py @@ -268,7 +268,7 @@ class Dice(Module): yacc.parse(dicestr) reply = self.get_result() if reply is not "": - return nick + ': ' + reply + return self.reply(connection, event, nick + ': ' + reply) match = re.search('^!ctech\s+(.*)$', what) if match: @@ -373,7 +373,7 @@ class Dice(Module): if count is not len(rollitrs)-1: reply += "; " if reply is not "": - return nick + ': ' + reply + return self.reply(connection, event, nick + ': ' + reply) # vi:tabstop=4:expandtab:autoindent # kate: indent-mode python;indent-width 4;replace-tabs on; diff --git a/modules/Echo.py b/modules/Echo.py index 5ba959d..f21ceff 100644 --- a/modules/Echo.py +++ b/modules/Echo.py @@ -31,7 +31,7 @@ class Echo(Module): match = re.search('^!echo\s+(.*)$', what) if match: - return match.group(1) + return self.reply(connection, event, match.group(1)) # vi:tabstop=4:expandtab:autoindent # kate: indent-mode python;indent-width 4;replace-tabs on; diff --git a/modules/EightBall.py b/modules/EightBall.py index d5a0675..2cc3cfc 100644 --- a/modules/EightBall.py +++ b/modules/EightBall.py @@ -63,7 +63,7 @@ class EightBall(Module): match = re.search('^!8ball', what) if match: response = self.responses[random.randint(1,len(self.responses))-1] - return response + return self.reply(connection, event, response) # vi:tabstop=4:expandtab:autoindent # kate: indent-mode python;indent-width 4;replace-tabs on; diff --git a/modules/FactFile.py b/modules/FactFile.py index 2e25e17..85aebca 100644 --- a/modules/FactFile.py +++ b/modules/FactFile.py @@ -53,7 +53,7 @@ class FactFile(Module): to_print = facts[random.randint(1, len(facts))-1] # return text - return to_print.rstrip() + return self.reply(connection, event, to_print.rstrip()) else: print('filename in config file for \'' + whats[0] + '\' is wrong') diff --git a/modules/Facts.py b/modules/Facts.py index 1f7b78d..84732f5 100644 --- a/modules/Facts.py +++ b/modules/Facts.py @@ -70,7 +70,7 @@ class Facts(Module): cur.execute('''INSERT INTO facts_facts (category, fact, who, userhost) VALUES (?, ?, ?, ?)''', (category, fact, nick, userhost)) db.commit() - return category + ' added.' + return self.reply(connection, event, category + ' added.') match = re.search('^!facts\s+(\S+)\s+(.*)$', what) if match: @@ -80,7 +80,7 @@ class Facts(Module): facts = category_facts.fetchall() if len(facts) > 0: fact = facts[random.randint(1,len(facts))-1] - return fact['fact'].rstrip().encode('utf-8', 'ignore') + return self.reply(connection, event, fact['fact'].rstrip().encode('utf-8', 'ignore')) match = re.search('^!facts\s+(\S+)$', what) if match: @@ -89,10 +89,10 @@ class Facts(Module): facts = category_facts.fetchall() if len(facts) > 0: fact = facts[random.randint(1,len(facts))-1] - return fact['fact'].rstrip().encode('utf-8', 'ignore') + return self.reply(connection, event, fact['fact'].rstrip().encode('utf-8', 'ignore')) except sqlite3.Error as e: - return "sqlite error: " + str(e) + return self.reply(connection, event, "sqlite error: " + str(e)) # vi:tabstop=4:expandtab:autoindent # kate: indent-mode python;indent-width 4;replace-tabs on; diff --git a/modules/GoogleTranslate.py b/modules/GoogleTranslate.py index 7c6a8c3..9e4f0c1 100644 --- a/modules/GoogleTranslate.py +++ b/modules/GoogleTranslate.py @@ -61,7 +61,7 @@ class GoogleTranslate(Module): translation = translation.replace('\\u0026gt;', '>') translation = translation.replace('\\u0026#39;', '\'') - return translation + return self.reply(connection, event, translation) # vi:tabstop=4:expandtab:autoindent # kate: indent-mode python;indent-width 4;replace-tabs on; diff --git a/modules/Help.py b/modules/Help.py index 8bc16a4..eabbf1c 100755 --- a/modules/Help.py +++ b/modules/Help.py @@ -43,11 +43,11 @@ class Help(Module): if match: (module, key) = match.group(2,4) if (module == None): - return self.handle_help_descriptions() + return self.reply(connection, event, self.handle_help_descriptions()) elif (key == None): - return self.handle_help_module(module) + return self.reply(connection, event, self.handle_help_module(module)) else: - return self.handle_help_detail(module, key) + return self.reply(connection, event, self.handle_help_detail(module, key)) def handle_help_descriptions(self): """print the general help""" diff --git a/modules/IrcAdmin.py b/modules/IrcAdmin.py index 7e3e550..5fe715c 100644 --- a/modules/IrcAdmin.py +++ b/modules/IrcAdmin.py @@ -28,10 +28,14 @@ class IrcAdmin(Module): """Support miscellaneous IRC stuff --- joining channels, changing the nick, etc.""" - def register_handlers(self, server): - server.add_global_handler('welcome', self.on_connect, self.priority()) + def register_handlers(self): + self.server.add_global_handler('pubmsg', self.on_pub_or_privmsg, self.priority()) + self.server.add_global_handler('privmsg', self.on_pub_or_privmsg, self.priority()) + self.server.add_global_handler('welcome', self.on_connect, self.priority()) def unregister_handlers(self): + self.server.remove_global_handler('pubmsg', self.on_pub_or_privmsg) + self.server.remove_global_handler('privmsg', self.on_pub_or_privmsg) self.server.remove_global_handler('welcome', self.on_connect) def on_connect(self, connection, event): @@ -60,27 +64,27 @@ class IrcAdmin(Module): whats = what.split(' ') if whats[0] == '!join' and admin_unlocked and len(whats) >= 2: - return self.sub_join_channel(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.sub_join_channel(connection, event, nick, userhost, what, admin_unlocked)) elif whats[0] == '!part' and admin_unlocked and len(whats) >= 2: - return self.sub_part_channel(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.sub_part_channel(connection, event, nick, userhost, what, admin_unlocked)) elif whats[0] == '!quit' and admin_unlocked: - return self.sub_quit_irc(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.sub_quit_irc(connection, event, nick, userhost, what, admin_unlocked)) elif whats[0] == '!autojoin' and admin_unlocked and len(whats) >= 3: - return self.sub_autojoin_manipulate(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.sub_autojoin_manipulate(connection, event, nick, userhost, what, admin_unlocked)) elif whats[0] == '!save' and admin_unlocked: self.irc.save_modules() self.irc.save_config() - return 'Saved.' + return self.reply(connection, event, 'Saved.') elif whats[0] == '!nick' and admin_unlocked and len(whats) >= 2: - return self.sub_change_nick(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.sub_change_nick(connection, event, nick, userhost, what, admin_unlocked)) elif whats[0] == '!load' and admin_unlocked and len(whats) >= 2: - return self.sub_load_module(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.sub_load_module(connection, event, nick, userhost, what, admin_unlocked)) elif whats[0] == '!reload' and admin_unlocked and len(whats) >= 2: - return self.sub_reload_module(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.sub_reload_module(connection, event, nick, userhost, what, admin_unlocked)) elif whats[0] == '!unload' and admin_unlocked and len(whats) >= 2: - return self.sub_unload_module(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.sub_unload_module(connection, event, nick, userhost, what, admin_unlocked)) elif whats[0] == '!modules': - return self.sub_list_modules(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.sub_list_modules(connection, event, nick, userhost, what, admin_unlocked)) def sub_join_channel(self, connection, event, nick, userhost, what, admin_unlocked): whats = what.split(' ') diff --git a/modules/Karma.py b/modules/Karma.py index 23195e5..195989e 100644 --- a/modules/Karma.py +++ b/modules/Karma.py @@ -98,13 +98,13 @@ class Karma(Module): """look for karma strings at the start of messages""" if (self.karmare.search(what)): - return self.handle_karma_change(connection, nick, userhost, what) + return self.reply(connection, event, self.handle_karma_change(connection, nick, userhost, what)) elif (self.queryre.search(what)): - return self.handle_karma_query(connection, nick, userhost, what) + return self.reply(connection, event, self.handle_karma_query(connection, nick, userhost, what)) elif (self.statre.search(what)): - return self.handle_stat_query(connection, nick, userhost, what) + return self.reply(connection, event, self.handle_stat_query(connection, nick, userhost, what)) elif (self.reportre.search(what)): - return self.handle_report_query(connection, nick, userhost, what) + return self.reply(connection, event, self.handle_report_query(connection, nick, userhost, what)) def handle_karma_change(self, connection, nick, userhost, what): """ diff --git a/modules/Markov.py b/modules/Markov.py index f120fdc..8f5800a 100644 --- a/modules/Markov.py +++ b/modules/Markov.py @@ -68,13 +68,17 @@ class Markov(Module): self.brain = {} self.brain.setdefault((self.start1, self.start2), []).append(self.stop) - def register_handlers(self, server): + def register_handlers(self): """Handle pubmsg/privmsg, to learn and/or reply to IRC events.""" + self.server.add_global_handler('pubmsg', self.on_pub_or_privmsg, self.priority()) + self.server.add_global_handler('privmsg', self.on_pub_or_privmsg, self.priority()) self.server.add_global_handler('pubmsg', self.learn_from_irc_event) self.server.add_global_handler('privmsg', self.learn_from_irc_event) def unregister_handlers(self): + self.server.remove_global_handler('pubmsg', self.on_pub_or_privmsg) + self.server.remove_global_handler('privmsg', self.on_pub_or_privmsg) self.server.remove_global_handler('pubmsg', self.learn_from_irc_event) self.server.remove_global_handler('privmsg', self.learn_from_irc_event) @@ -100,11 +104,11 @@ class Markov(Module): """Handle commands and inputs.""" if self.trainre.search(what): - return self.markov_train(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.markov_train(connection, event, nick, userhost, what, admin_unlocked)) elif self.learnre.search(what): - return self.markov_learn(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.markov_learn(connection, event, nick, userhost, what, admin_unlocked)) elif self.replyre.search(what): - return self.markov_reply(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.markov_reply(connection, event, nick, userhost, what, admin_unlocked)) # not a command, so see if i'm being mentioned if re.search(connection.get_nickname(), what, re.IGNORECASE) is not None: @@ -112,10 +116,10 @@ class Markov(Module): addressed_re = re.compile(addressed_pattern) if addressed_re.match(what): # i was addressed directly, so respond, addressing the speaker - return '{0:s}: {1:s}'.format(nick, self._reply_to_line(addressed_re.match(what).group(1))) + return self.reply(connection, event, '{0:s}: {1:s}'.format(nick, self._reply_to_line(addressed_re.match(what).group(1)))) else: # i wasn't addressed directly, so just respond - return '{0:s}'.format(self._reply_to_line(what)) + return self.reply(connection, event, '{0:s}'.format(self._reply_to_line(what))) def markov_train(self, connection, event, nick, userhost, what, admin_unlocked): """Learn lines from a file. Good for initializing a brain.""" diff --git a/modules/MegaHAL.py b/modules/MegaHAL.py index 4ffdea7..f56ae25 100644 --- a/modules/MegaHAL.py +++ b/modules/MegaHAL.py @@ -39,13 +39,17 @@ class MegaHAL(Module): mh_python.initbrain() - def register_handlers(self, server): + def register_handlers(self): """Handle pubmsg/privmsg, so we can learn only from IRC events.""" + self.server.add_global_handler('pubmsg', self.on_pub_or_privmsg, self.priority()) + self.server.add_global_handler('privmsg', self.on_pub_or_privmsg, self.priority()) self.server.add_global_handler('pubmsg', self.learn_from_irc_event) self.server.add_global_handler('privmsg', self.learn_from_irc_event) def unregister_handlers(self): + self.server.remove_global_handler('pubmsg', self.on_pub_or_privmsg) + self.server.remove_global_handler('privmsg', self.on_pub_or_privmsg) self.server.remove_global_handler('pubmsg', self.learn_from_irc_event) self.server.remove_global_handler('privmsg', self.learn_from_irc_event) @@ -81,7 +85,7 @@ class MegaHAL(Module): if reply is not "": if append_nick: reply = nick + ": " + reply - return reply + return self.reply(connection, event, reply) # vi:tabstop=4:expandtab:autoindent # kate: indent-mode python;indent-width 4;replace-tabs on; diff --git a/modules/Pi.py b/modules/Pi.py index 486f835..8d77610 100644 --- a/modules/Pi.py +++ b/modules/Pi.py @@ -98,9 +98,9 @@ class Pi(Module): db.commit() except sqlite3.Error as e: db.rollback() - return "sqlite error: " + str(e) + return self.reply(connection, event, "sqlite error: " + str(e)) - return "({0:.10f}, {1:.10f}) is {2}within the circle. pi is {5:.10f}. (i:{3:d} p:{4:d})".format(x, y, "" if inside else "not ", count_inside, count, pi) + return self.reply(connection, event, "({0:.10f}, {1:.10f}) is {2}within the circle. pi is {5:.10f}. (i:{3:d} p:{4:d})".format(x, y, "" if inside else "not ", count_inside, count, pi)) # vi:tabstop=4:expandtab:autoindent # kate: indent-mode python;indent-width 4;replace-tabs on; diff --git a/modules/Seen.py b/modules/Seen.py index a6e8114..386d600 100644 --- a/modules/Seen.py +++ b/modules/Seen.py @@ -44,7 +44,7 @@ class Seen(Module): seendata = self.config.get(self.__class__.__name__, query).split('|:|') converted = datetime.strptime(seendata[1], "%Y-%m-%dT%H:%M:%S.%f").replace(tzinfo=tzutc()) replystr = 'last saw ' + query + ' at ' + converted.astimezone(tzlocal()).strftime("%Y/%m/%d %H:%M:%S %Z") + ' saying \'' + seendata[2] + '\'' - return replystr + return self.reply(connection, event, replystr) except NoOptionError: pass # vi:tabstop=4:expandtab:autoindent diff --git a/modules/Storycraft.py b/modules/Storycraft.py index 6339d44..c0f75e7 100644 --- a/modules/Storycraft.py +++ b/modules/Storycraft.py @@ -162,21 +162,21 @@ class Storycraft(Module): """Pass storycraft control commands to the appropriate method based on input.""" if self.statusre.search(what): - return self.storycraft_status(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.storycraft_status(connection, event, nick, userhost, what, admin_unlocked)) elif self.newgamere.search(what): - return self.storycraft_newgame(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.storycraft_newgame(connection, event, nick, userhost, what, admin_unlocked)) elif self.joingamere.search(what): - return self.storycraft_joingame(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.storycraft_joingame(connection, event, nick, userhost, what, admin_unlocked)) elif self.listgamesre.search(what): - return self.storycraft_listgames(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.storycraft_listgames(connection, event, nick, userhost, what, admin_unlocked)) elif self.startgamere.search(what): - return self.storycraft_startgame(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.storycraft_startgame(connection, event, nick, userhost, what, admin_unlocked)) elif self.showlinere.search(what): - return self.storycraft_showline(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.storycraft_showline(connection, event, nick, userhost, what, admin_unlocked)) elif self.addlinere.search(what): - return self.storycraft_addline(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.storycraft_addline(connection, event, nick, userhost, what, admin_unlocked)) elif self.exportre.search(what): - return self.storycraft_export(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.storycraft_export(connection, event, nick, userhost, what, admin_unlocked)) def storycraft_status(self, connection, event, nick, userhost, what, admin_unlocked): """Print information about the storycraft games, or one specific game.""" diff --git a/modules/TextTransform.py b/modules/TextTransform.py index 8719dd7..fec6f44 100644 --- a/modules/TextTransform.py +++ b/modules/TextTransform.py @@ -37,11 +37,11 @@ class TextTransform(Module): reply = [''] if self.rot13(what, reply): - return reply[0] + return self.reply(connection, event, reply[0]) elif self.base64(what, reply): - return reply[0] + return self.reply(connection, event, reply[0]) elif self.upper(what, reply): - return reply[0] + return self.reply(connection, event, reply[0]) def rot13(self, what, reply): """ diff --git a/modules/Trigger.py b/modules/Trigger.py index 29576be..692fd82 100644 --- a/modules/Trigger.py +++ b/modules/Trigger.py @@ -36,7 +36,7 @@ class Trigger(Module): for trigger in trigger_list: if re.search(trigger, what) is not None: output = self.config.get(self.__class__.__name__, trigger) - return output + return self.reply(connection, event, output) except NoOptionError: pass except NoSectionError: pass diff --git a/modules/Twitter.py b/modules/Twitter.py index 24ea76f..413b30f 100644 --- a/modules/Twitter.py +++ b/modules/Twitter.py @@ -99,17 +99,17 @@ class Twitter(Module): """Attempt to do twitter things.""" if self.getstatusre.search(what): - return self.twitter_getstatus(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.twitter_getstatus(connection, event, nick, userhost, what, admin_unlocked)) elif self.getuserstatusre.search(what): - return self.twitter_getuserstatus(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.twitter_getuserstatus(connection, event, nick, userhost, what, admin_unlocked)) elif self.tweetre.search(what): - return self.twitter_tweet(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.twitter_tweet(connection, event, nick, userhost, what, admin_unlocked)) elif self.replytore.search(what): - return self.twitter_replyto(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.twitter_replyto(connection, event, nick, userhost, what, admin_unlocked)) elif self.gettokenre.search(what): - return self.twitter_gettoken(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.twitter_gettoken(connection, event, nick, userhost, what, admin_unlocked)) elif self.authre.search(what): - return self.twitter_auth(connection, event, nick, userhost, what, admin_unlocked) + return self.reply(connection, event, self.twitter_auth(connection, event, nick, userhost, what, admin_unlocked)) def twitter_getstatus(self, connection, event, nick, userhost, what, admin_unlocked): """Get a status by tweet ID.""" diff --git a/modules/Urls.py b/modules/Urls.py index 8c4a224..de1a43d 100644 --- a/modules/Urls.py +++ b/modules/Urls.py @@ -45,7 +45,7 @@ class Urls(Module): urlstr = ' '.join(whats[2:]) + "\n" file.write(urlstr) - return 'added url' + return self.reply(connection, event, 'added url') # default: get a random url @@ -64,7 +64,7 @@ class Urls(Module): to_print = urls[random.randint(1, len(urls))-1] # return text - return to_print.rstrip() + return self.reply(connection, event, to_print.rstrip()) else: print('filename in config file for urls is wrong') except NoOptionError: pass diff --git a/modules/Weather.py b/modules/Weather.py index 312a9ad..a27c417 100644 --- a/modules/Weather.py +++ b/modules/Weather.py @@ -62,11 +62,11 @@ class Weather(Module): for city in google_weather['forecasts']: weatherstr += " " + city['day_of_week'].encode('utf-8') + ": " + city['condition'].encode('utf-8') + ". High " + city['high'].encode('utf-8') + "°F, Low " + city['low'].encode('utf-8') + "°F." - return weatherstr + return self.reply(connection, event, weatherstr) except URLError as e: - return "error connecting to google weather:" + str(e) + return self.reply(connection, event, "error connecting to google weather:" + str(e)) except IndexError as e: - return "error in pywapi: " + str(e) + return self.reply(connection, event, "error in pywapi: " + str(e)) # vi:tabstop=4:expandtab:autoindent # kate: indent-mode python;indent-width 4;replace-tabs on;