diff --git a/bot.py b/bot.py index 3f704f3..786be74 100644 --- a/bot.py +++ b/bot.py @@ -31,8 +31,8 @@ __author__ = 'nemunaire' from consumer import Consumer, EventConsumer, MessageConsumer from event import ModuleEvent -import hooks -from hooksmanager import HooksManager +from hooks.messagehook import MessageHook +from hooks.manager import HooksManager from networkbot import NetworkBot from server.IRC import IRCServer from server.DCC import DCC @@ -82,7 +82,7 @@ class Bot(threading.Thread): def in_ping(msg): if re.match("^ *(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)", msg.text, re.I) is not None: return response.Response(msg.sender, message="pong", channel=msg.receivers, nick=msg.nick) - self.hooks.add_hook(hooks.Hook(in_ping), "in", "PRIVMSG") + self.hooks.add_hook(MessageHook(in_ping), "in", "PRIVMSG") def _help_msg(msg): """Parse and response to help messages""" @@ -119,7 +119,7 @@ class Bot(threading.Thread): " de tous les modules disponibles localement", message=["\x03\x02%s\x03\x02 (%s)" % (im, self.modules[im].__doc__) for im in self.modules if self.modules[im].__doc__]) return res - self.hooks.add_hook(hooks.Hook(_help_msg, "help"), "in", "PRIVMSG", "cmd") + self.hooks.add_hook(MessageHook(_help_msg, "help"), "in", "PRIVMSG", "cmd") # Other known bots, making a bots network self.network = dict() @@ -479,9 +479,10 @@ def reload(): import hooks imp.reload(hooks) - - import hooksmanager - imp.reload(hooksmanager) + import hooks.manager + imp.reload(hooks.manager) + import hooks.messagehook + imp.reload(hooks.messagehook) import importer imp.reload(importer) diff --git a/hooks.py b/hooks.py deleted file mode 100644 index 4d52135..0000000 --- a/hooks.py +++ /dev/null @@ -1,106 +0,0 @@ -# -*- coding: utf-8 -*- - -# Nemubot is a modulable IRC bot, built around XML configuration files. -# Copyright (C) 2012 Mercier Pierre-Olivier -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -import logging -import re - -from response import Response -from exception import IRCException - -logger = logging.getLogger("nemubot.hooks") - -class Hook: - """Class storing hook informations""" - def __init__(self, call, name=None, data=None, regexp=None, channels=list(), server=None, end=None, call_end=None, help=None): - self.name = name - self.end = end - self.call = call - if call_end is None: - self.call_end = self.call - else: - self.call_end = call_end - self.regexp = regexp - self.data = data - self.times = -1 - self.server = server - self.channels = channels - self.help = help - - def match(self, message, channel=None, server=None): - if isinstance(message, Response): - return self.is_matching(None, channel, server) - elif message.qual == "cmd": - return self.is_matching(message.cmds[0], channel, server) - elif hasattr(message, "text"): - return self.is_matching(message.text, channel, server) - elif len(message.params) > 0: - return self.is_matching(message.params[0], channel, server) - else: - return self.is_matching(message.cmd, channel, server) - - def is_matching(self, strcmp, channel=None, server=None): - """Test if the current hook correspond to the message""" - return (channel is None or len(self.channels) <= 0 or - channel in self.channels) and (server is None or - self.server is None or self.server == server) and ( - (self.name is None or strcmp == self.name) and ( - self.end is None or strcmp == self.end) and ( - self.regexp is None or re.match(self.regexp, strcmp))) - - def run(self, msg, data2=None, strcmp=None): - """Run the hook""" - if self.times != 0: - self.times -= 1 - - if (self.end is not None and strcmp is not None and - self.call_end is not None and strcmp == self.end): - call = self.call_end - self.times = 0 - else: - call = self.call - - try: - if self.data is None: - if data2 is None: - return call(msg) - elif isinstance(data2, dict): - return call(msg, **data2) - else: - return call(msg, data2) - elif isinstance(self.data, dict): - if data2 is None: - return call(msg, **self.data) - else: - return call(msg, data2, **self.data) - else: - if data2 is None: - return call(msg, self.data) - elif isinstance(data2, dict): - return call(msg, self.data, **data2) - else: - return call(msg, self.data, data2) - except IRCException as e: - return e.fill_response(msg) - -last_registered = [] - -def hook(store, *args, **kargs): - def sec(call): - last_registered.append((store, Hook(call, *args, **kargs))) - return call - return sec diff --git a/hooks/__init__.py b/hooks/__init__.py new file mode 100644 index 0000000..e1fa0cf --- /dev/null +++ b/hooks/__init__.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +# Nemubot is a smart and modulable IM bot. +# Copyright (C) 2012-2014 nemunaire +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from exception import IRCException + +def call_game(call, *args, **kargs): + """TODO""" + l = list() + d = kargs + + for a in args: + if a is not None: + if isinstance(a, dict): + d.update(a) + else: + l.append(a) + + return call(*l, **d) + + +class AbstractHook: + + """Abstract class for Hook implementation""" + + def __init__(self, call, data=None, mtimes=-1, end_call=None): + self.call = call + self.data = data + + self.times = mtimes + self.end_call = end_call + + + def match(self, data1, server): + return NotImplemented + + + def run(self, data1, *args): + """Run the hook""" + self.times -= 1 + + try: + ret = call_game(self.call, data1, self.data, *args) + except IRCException as e: + ret = e.fill_response(data1) + finally: + if self.times == 0: + self.call_end(ret) + + return ret + + +from hooks.messagehook import MessageHook + +last_registered = [] + +def hook(store, *args, **kargs): + """Function used as a decorator for module loading""" + def sec(call): + last_registered.append((store, MessageHook(call, *args, **kargs))) + return call + return sec diff --git a/hooksmanager.py b/hooks/manager.py similarity index 100% rename from hooksmanager.py rename to hooks/manager.py diff --git a/hooks/messagehook.py b/hooks/messagehook.py new file mode 100644 index 0000000..2ae3572 --- /dev/null +++ b/hooks/messagehook.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +# Nemubot is a smart and modulable IM bot. +# Copyright (C) 2012-2014 nemunaire +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import re + +from exception import IRCException +import hooks +from response import Response + +class MessageHook(hooks.AbstractHook): + + """Class storing hook information, specialized for a generic Message""" + + def __init__(self, call, name=None, data=None, regexp=None, + channels=list(), server=None, mtimes=-1, end_call=None): + + hooks.AbstractHook.__init__(self, call=call, data=data, + end_call=end_call, mtimes=mtimes) + + self.name = name + self.regexp = regexp + self.server = server + self.channels = channels + + + def match(self, message, server=None): + if isinstance(message, Response): + return self.is_matching(None, message.channel, server) + + elif message.qual == "cmd": + return self.is_matching(message.cmds[0], message.channel, server) + elif hasattr(message, "text"): + return self.is_matching(message.text, message.channel, server) + elif len(message.params) > 0: + return self.is_matching(message.params[0], message.channel, server) + else: + return self.is_matching(message.cmd, message.channel, server) + + + def is_matching(self, strcmp, channel=None, server=None): + """Test if the current hook correspond to the message""" + return (channel is None or len(self.channels) <= 0 or + channel in self.channels) and (server is None or + self.server is None or self.server == server) and ( + (self.name is None or strcmp == self.name) and ( + self.regexp is None or re.match(self.regexp, strcmp))) diff --git a/modules/conjugaison.py b/modules/conjugaison.py index 9d9c492..d5ae720 100644 --- a/modules/conjugaison.py +++ b/modules/conjugaison.py @@ -34,7 +34,7 @@ def help_full(): return "!conjugaison : give the conjugaison for in ." -@hook("cmd_hook", "conjugaison", help="!conjugaison : give the conjugaison for in .") +@hook("cmd_hook", "conjugaison") def cmd_conjug(msg): if len(msg.cmds) < 3: raise IRCException("donne moi un temps et un verbe, et je te donnerai sa conjugaison!") diff --git a/modules/events.py b/modules/events.py index f7901c6..90191b1 100644 --- a/modules/events.py +++ b/modules/events.py @@ -14,7 +14,7 @@ import traceback nemubotversion = 3.4 from event import ModuleEvent -from hooks import Hook, hook +from hooks import hook from tools.date import extractDate from tools.countdown import countdown_format, countdown @@ -60,8 +60,9 @@ def cmd_we(msg): "Youhou, on est en week-end depuis %s."), channel=msg.channel) -@hook("cmd_hook", "start", help="!start /something/: launch a timer") +@hook("cmd_hook", "start") def start_countdown(msg): + """!start /something/: launch a timer""" if len(msg.cmds) < 2: raise IRCException("indique le nom d'un événement à chronométrer") if msg.cmds[1] in DATAS.index: @@ -154,8 +155,9 @@ def end_countdown(msg): else: return Response(msg.sender, "%s n'est pas un compteur connu."% (msg.cmds[1]), channel=msg.channel, nick=msg.nick) -@hook("cmd_hook", "eventslist", help="!eventslist: gets list of timer") +@hook("cmd_hook", "eventslist") def liste(msg): + """!eventslist: gets list of timer""" if len(msg.cmds) > 1: res = list() for user in msg.cmds[1:]: diff --git a/modules/mapquest.py b/modules/mapquest.py index 17e7eb8..e2487a6 100644 --- a/modules/mapquest.py +++ b/modules/mapquest.py @@ -15,8 +15,8 @@ def load(context): "http://developer.mapquest.com/") return None - from hooks import Hook - add_hook("cmd_hook", Hook(cmd_geocode, "geocode")) + from hooks.messagehook import MessageHook + add_hook("cmd_hook", MessageHook(cmd_geocode, "geocode")) def help_tiny (): diff --git a/modules/networking.py b/modules/networking.py index e573a34..b83e8f7 100644 --- a/modules/networking.py +++ b/modules/networking.py @@ -9,7 +9,7 @@ import socket import subprocess import urllib -from hooks import Hook, hook +from hooks import hook from tools import web nemubotversion = 3.4 @@ -21,7 +21,8 @@ def load(context): "\nRegister at " "http://www.whoisxmlapi.com/newaccount.php") else: - add_hook("cmd_hook", Hook(cmd_whois, "netwhois")) + from hooks.messagehook import MessageHook + add_hook("cmd_hook", MessageHook(cmd_whois, "netwhois")) def help_full(): return "!traceurl /url/: Follow redirections from /url/." diff --git a/modules/reddit.py b/modules/reddit.py index b7ff786..39eb049 100644 --- a/modules/reddit.py +++ b/modules/reddit.py @@ -15,7 +15,7 @@ def help_full(): LAST_SUBS = dict() -@hook("cmd_hook", "subreddit", help="!subreddit /subreddit/: Display information on the subreddit.") +@hook("cmd_hook", "subreddit") def cmd_subreddit(msg): global LAST_SUBS if len(msg.cmds) <= 1: diff --git a/modules/sleepytime.py b/modules/sleepytime.py index cf45af6..b539a9e 100644 --- a/modules/sleepytime.py +++ b/modules/sleepytime.py @@ -12,9 +12,9 @@ from hooks import hook nemubotversion = 3.4 def help_full(): - return "If you would like to sleep soon, use !sleepytime to know the best time to wake up; use !sleepytime hh:mm if you want to wake up at hh:mm" + return "If you would like to sleep soon, use !sleepytime to know the best time to wake up; use !sleepytime hh:mm if you want to wake up at hh:mm" -@hook("cmd_hook", "sleepytime", help="If you would like to sleep soon, use !sleepytime to know the best time to wake up; use !sleepytime hh:mm if you want to wake up at hh:mm") +@hook("cmd_hook", "sleepytime") def cmd_sleep(msg): if len (msg.cmds) > 1 and re.match("[0-9]{1,2}[h':.,-]([0-9]{1,2})?[m'\":.,-]?", msg.cmds[1]) is not None: diff --git a/modules/syno.py b/modules/syno.py index 548d89f..81e392b 100644 --- a/modules/syno.py +++ b/modules/syno.py @@ -15,7 +15,7 @@ nemubotversion = 3.4 def help_full(): return "!syno : give a list of synonyms for ." -@hook("cmd_hook", "synonymes", help="!syno : give a list of synonyms for .") +@hook("cmd_hook", "synonymes") def cmd_syno(msg): return go("synonymes", msg) diff --git a/modules/translate.py b/modules/translate.py index 8191d3b..6a19640 100644 --- a/modules/translate.py +++ b/modules/translate.py @@ -28,8 +28,8 @@ def load(context): else: URL = URL % CONF.getNode("wrapi")["key"] - from hooks import Hook - add_hook("cmd_hook", Hook(cmd_translate, "translate")) + from hooks.messagehook import MessageHook + add_hook("cmd_hook", MessageHook(cmd_translate, "translate")) def help_full(): diff --git a/modules/weather.py b/modules/weather.py index dfcb583..3f865c8 100644 --- a/modules/weather.py +++ b/modules/weather.py @@ -25,10 +25,10 @@ def load(context): "http://developer.forecast.io/") return None - from hooks import Hook - add_hook("cmd_hook", Hook(cmd_weather, "météo")) - add_hook("cmd_hook", Hook(cmd_alert, "alert")) - add_hook("cmd_hook", Hook(cmd_coordinates, "coordinates")) + from hooks.messagehook import MessageHook + add_hook("cmd_hook", MessageHook(cmd_weather, "météo")) + add_hook("cmd_hook", MessageHook(cmd_alert, "alert")) + add_hook("cmd_hook", MessageHook(cmd_coordinates, "coordinates")) def help_full (): diff --git a/modules/worldcup.py b/modules/worldcup.py index c32b133..04db62a 100644 --- a/modules/worldcup.py +++ b/modules/worldcup.py @@ -10,13 +10,11 @@ from urllib.request import urlopen nemubotversion = 3.4 +from hooks import hook + API_URL="http://worldcup.sfg.io/%s" def load(context): - from hooks import Hook - add_hook("cmd_hook", Hook(cmd_watch, "watch_worldcup")) - add_hook("cmd_hook", Hook(cmd_worldcup, "worldcup")) - from event import ModuleEvent add_event(ModuleEvent(func=lambda url: urlopen(url, timeout=10).read().decode(), func_data=API_URL % "matches/current?by_date=DESC", call=current_match_new_action, intervalle=30)) @@ -37,6 +35,7 @@ def start_watch(msg): save() raise IRCException("This channel is now watching world cup events!") +@hook("cmd_hook", "watch_worldcup") def cmd_watch(msg): global DATAS @@ -178,6 +177,7 @@ def get_matches(url): if is_valid(match): yield match +@hook("cmd_hook", "worldcup") def cmd_worldcup(msg): res = Response(msg.sender, channel=msg.channel, nomore="No more match to display", count=" (%d more matches)") nb = len(msg.cmds) diff --git a/modules/ycc.py b/modules/ycc.py index c450d0d..8c9a3b0 100644 --- a/modules/ycc.py +++ b/modules/ycc.py @@ -24,7 +24,7 @@ def gen_response(res, msg, srv): else: raise IRCException("mauvaise URL : %s" % srv) -@hook("cmd_hook", "ycc", help="!ycc []: with an argument, reduce the given thanks to ycc.fr; without argument, reduce the last URL said on the current channel.") +@hook("cmd_hook", "ycc") def cmd_ycc(msg): if len(msg.cmds) == 1: global LAST_URLS