diff --git a/bot.py b/bot.py index f9ebf10..8030b6b 100644 --- a/bot.py +++ b/bot.py @@ -32,6 +32,7 @@ __author__ = 'nemunaire' from consumer import Consumer, EventConsumer, MessageConsumer from event import ModuleEvent import hooks +from hooksmanager import HooksManager from networkbot import NetworkBot from server.IRC import IRCServer from server.DCC import DCC @@ -77,7 +78,11 @@ class Bot(threading.Thread): self.event_timer = None # Own hooks - self.hooks = hooks.MessagesHook(self, self) + self.hooks = HooksManager() + 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") # Other known bots, making a bots network self.network = dict() @@ -88,10 +93,6 @@ class Bot(threading.Thread): self.cnsr_thrd = list() self.cnsr_thrd_size = -1 - self.hooks.add_hook("irc_hook", - hooks.Hook(self.treat_prvmsg, "PRIVMSG"), - self) - def run(self): from server import _rlist, _wlist, _xlist @@ -299,9 +300,6 @@ class Bot(threading.Thread): def add_server(self, node, nick, owner, realname): """Add a new server to the context""" srv = IRCServer(node, nick, owner, realname) - srv.add_hook = lambda h: self.hooks.add_hook("irc_hook", h, self) - srv.add_networkbot = self.add_networkbot - srv.send_bot = lambda d: self.send_networkbot(srv, d) #srv.register_hooks() if srv.id not in self.servers: self.servers[srv.id] = srv @@ -348,7 +346,7 @@ class Bot(threading.Thread): self.modules[name].unload(self) # Remove registered hooks for (s, h) in self.modules[name].REGISTERED_HOOKS: - self.hooks.del_hook(s, h) + self.hooks.del_hook(h, s) # Remove registered events for e in self.modules[name].REGISTERED_EVENTS: self.del_event(e) @@ -361,7 +359,7 @@ class Bot(threading.Thread): def receive_message(self, srv, raw_msg, private=False, data=None): """Queued the message for treatment""" - #print (raw_msg) + #print("READ", raw_msg) self.cnsr_queue.put_nowait(MessageConsumer(srv, raw_msg, datetime.now(), private, data)) # Launch a new thread if necessary @@ -399,57 +397,6 @@ class Bot(threading.Thread): self.stop = True - # Hooks cache - - def create_cache(self, name): - if name not in self.hooks_cache: - if isinstance(self.hooks.__dict__[name], list): - self.hooks_cache[name] = list() - - # Start by adding locals hooks - for h in self.hooks.__dict__[name]: - tpl = (h, 0, self.hooks.__dict__[name], self.hooks.bot) - self.hooks_cache[name].append(tpl) - - # Now, add extermal hooks - level = 0 - while level == 0 or lvl_exist: - lvl_exist = False - for ext in self.network: - if len(self.network[ext].hooks) > level: - lvl_exist = True - for h in self.network[ext].hooks[level].__dict__[name]: - if h not in self.hooks_cache[name]: - self.hooks_cache[name].append((h, level + 1, - self.network[ext].hooks[level].__dict__[name], self.network[ext].hooks[level].bot)) - level += 1 - - elif isinstance(self.hooks.__dict__[name], dict): - self.hooks_cache[name] = dict() - - # Start by adding locals hooks - for h in self.hooks.__dict__[name]: - self.hooks_cache[name][h] = (self.hooks.__dict__[name][h], 0, - self.hooks.__dict__[name], - self.hooks.bot) - - # Now, add extermal hooks - level = 0 - while level == 0 or lvl_exist: - lvl_exist = False - for ext in self.network: - if len(self.network[ext].hooks) > level: - lvl_exist = True - for h in self.network[ext].hooks[level].__dict__[name]: - if h not in self.hooks_cache[name]: - self.hooks_cache[name][h] = (self.network[ext].hooks[level].__dict__[name][h], level + 1, self.network[ext].hooks[level].__dict__[name], self.network[ext].hooks[level].bot) - level += 1 - - else: - raise Exception(name + " hook type unrecognized") - - return self.hooks_cache[name] - # Treatment def check_rest_times(self, store, hook): @@ -462,48 +409,6 @@ class Bot(threading.Thread): elif isinstance(store, list): store.remove(hook) - def treat_pre(self, msg, srv): - """Treat a message before all other treatment""" - # Treat all messages starting with 'nemubot:' as distinct commands - if msg.cmd == "PRIVMSG" and msg.text.find("%s:"%srv.nick) == 0: - # Remove the bot name - msg.text = msg.text[len(srv.nick)+1:].strip() - msg.parse_content() - msg.private = True - - for h, lvl, store, bot in self.create_cache("all_pre"): - if h.is_matching(None, server=srv): - h.run(msg, self.create_cache) - self.check_rest_times(store, h) - - - def treat_post(self, res): - """Treat a message before send""" - for h, lvl, store, bot in self.create_cache("all_post"): - if h.is_matching(None, channel=res.channel, server=res.server): - c = h.run(res) - self.check_rest_times(store, h) - if not c: - return False - return True - - - def treat_irc(self, msg, srv): - """Treat all incoming IRC commands""" - treated = list() - - irc_hooks = self.create_cache("irc_hook") - if msg.cmd in irc_hooks: - (hks, lvl, store, bot) = irc_hooks[msg.cmd] - for h in hks: - if h.is_matching(msg.cmd, server=srv): - res = h.run(msg, srv, msg.cmd) - if res is not None and res != False: - treated.append(res) - self.check_rest_times(store, h) - - return treated - def treat_prvmsg_ask(self, msg, srv): # Treat ping @@ -559,114 +464,6 @@ class Bot(threading.Thread): return res - def treat_cmd(self, msg, srv): - """Treat a command message""" - treated = list() - - # First, treat simple hook - cmd_hook = self.create_cache("cmd_hook") - if msg.cmds[0] in cmd_hook: - (hks, lvl, store, bot) = cmd_hook[msg.cmds[0]] - for h in hks: - if h.is_matching(msg.cmds[0], channel=msg.receivers, server=srv) and (msg.private or lvl == 0 or bot.nick not in srv.channels[msg.receivers].people): - res = h.run(msg, strcmp=msg.cmds[0]) - if res is not None and res != False: - treated.append(res) - self.check_rest_times(store, h) - - # Then, treat regexp based hook - cmd_rgxp = self.create_cache("cmd_rgxp") - for hook, lvl, store, bot in cmd_rgxp: - if hook.is_matching(msg.cmds[0], msg.receivers, server=srv) and (msg.private or lvl == 0 or bot.nick not in srv.channels[msg.receivers].people): - res = hook.run(msg) - if res is not None and res != False: - treated.append(res) - self.check_rest_times(store, hook) - - # Finally, treat default hooks if not catched before - cmd_default = self.create_cache("cmd_default") - for hook, lvl, store, bot in cmd_default: - if treated: - break - res = hook.run(msg) - if res is not None and res != False: - treated.append(res) - self.check_rest_times(store, hook) - - return treated - - def treat_ask(self, msg, srv): - """Treat an ask message""" - treated = list() - - # First, treat simple hook - ask_hook = self.create_cache("ask_hook") - if msg.text in ask_hook: - hks, lvl, store, bot = ask_hook[msg.text] - for h in hks: - if h.is_matching(msg.text, channel=msg.receivers, server=srv) and (msg.private or lvl == 0 or bot.nick not in srv.channels[msg.receivers].people): - res = h.run(msg, strcmp=msg.text) - if res is not None and res != False: - treated.append(res) - self.check_rest_times(store, h) - - # Then, treat regexp based hook - ask_rgxp = self.create_cache("ask_rgxp") - for hook, lvl, store, bot in ask_rgxp: - if hook.is_matching(msg.text, channel=msg.receivers, server=srv) and (msg.private or lvl == 0 or bot.nick not in srv.channels[msg.receivers].people): - res = hook.run(msg, strcmp=msg.text) - if res is not None and res != False: - treated.append(res) - self.check_rest_times(store, hook) - - # Finally, treat default hooks if not catched before - ask_default = self.create_cache("ask_default") - for hook, lvl, store, bot in ask_default: - if treated: - break - res = hook.run(msg) - if res is not None and res != False: - treated.append(res) - self.check_rest_times(store, hook) - - return treated - - def treat_answer(self, msg, srv): - """Treat a normal message""" - treated = list() - - # First, treat simple hook - msg_hook = self.create_cache("msg_hook") - if msg.text in msg_hook: - hks, lvl, store, bot = msg_hook[msg.text] - for h in hks: - if h.is_matching(msg.text, channel=msg.receivers, server=srv) and (msg.private or lvl == 0 or bot.nick not in srv.channels[msg.receivers].people): - res = h.run(msg, strcmp=msg.text) - if res is not None and res != False: - treated.append(res) - self.check_rest_times(store, h) - - # Then, treat regexp based hook - msg_rgxp = self.create_cache("msg_rgxp") - for hook, lvl, store, bot in msg_rgxp: - if hook.is_matching(msg.text, channel=msg.receivers, server=srv) and (msg.private or lvl == 0 or bot.nick not in srv.channels[msg.receivers].people): - res = hook.run(msg, strcmp=msg.text) - if res is not None and res != False: - treated.append(res) - self.check_rest_times(store, hook) - - # Finally, treat default hooks if not catched before - msg_default = self.create_cache("msg_default") - for hook, lvl, store, bot in msg_default: - if len(treated) > 0: - break - res = hook.run(msg) - if res is not None and res != False: - treated.append(res) - self.check_rest_times(store, hook) - - return treated - def _ctcp_response(sndr, msg): return response.Response(sndr, msg, ctcp=True) diff --git a/consumer.py b/consumer.py index b559dd7..0287288 100644 --- a/consumer.py +++ b/consumer.py @@ -42,26 +42,151 @@ class MessageConsumer: self.prvt = prvt self.data = data + self.msgs = list() + self.responses = None - def treat_in(self, context, msg): - """Treat the input message""" - if msg.cmd == "PING": - self.srv.write("%s :%s" % ("PONG", msg.params[0])) - elif hasattr(msg, "receivers"): - if msg.receivers: - # All messages - context.treat_pre(msg, self.srv) - return context.treat_irc(msg, self.srv) + def first_treat(self, msg): + """Qualify a new message/response - def treat_out(self, context, res): - """Treat the output message""" - if isinstance(res, list): - for r in res: - if r is not None: self.treat_out(context, r) + Argument: + msg -- The Message or Response to qualify + """ - elif isinstance(res, response.Response): - # Define the destination server + if not hasattr(msg, "qual") or msg.qual is None: + # Assume this is a message with no particulariry + msg.qual = "def" + + # Define the source server if not already done + if not hasattr(msg, "server") or msg.server is None: + msg.server = self.srv.id + + if isinstance(msg, Message): + if msg.cmd == "PRIVMSG" or msg.cmd == "NOTICE": + msg.is_owner = (msg.nick == self.srv.owner) + msg.private = msg.private or (len(msg.receivers) == 1 and msg.receivers[0] == self.srv.nick) + if msg.private: + msg.qual = "ask" + + # Remove nemubot: + if msg.qual != "cmd" and msg.text.find(self.srv.nick) == 0 and len(msg.text) > len(self.srv.nick) + 2 and msg.text[len(self.srv.nick)] == ":": + msg.text = msg.text[len(self.srv.nick) + 1:].strip() + msg.qual = "ask" + + return msg + + + def pre_treat(self, hm): + """Modify input Messages + + Arguments: + hm -- Hooks manager + """ + + new_msg = list() + new_msg += self.msgs + self.msgs = list() + + while len(new_msg) > 0: + msg = new_msg.pop(0) + for h in hm.get_hooks("pre", msg.cmd, msg.qual): + if h.match(message=msg, server=self.srv): + res = h.run(msg) + if isinstance(res, list): + for i in xrange(len(res)): + if res[i] == msg: + res.pop(i) + break + new_msg += res + elif res is not None and res != msg: + new_msg.append(res) + msg = None + break + elif res is None or res == False: + msg = None + break + if msg is not None: + self.msgs.append(msg) + + + def in_treat(self, hm): + """Treat Messages and store responses + + Arguments: + hm -- Hooks manager + """ + + self.responses = list() + for msg in self.msgs: + for h in hm.get_hooks("in", msg.cmd, msg.qual): + if msg.cmd == "PING": + self.srv.write("%s :%s" % ("PONG", msg.params[0])) + + elif h.match(message=msg, server=self.srv): + res = h.run(msg) + if isinstance(res, list): + self.responses += res + elif res is not None: + self.responses.append(res) + + + def post_treat(self, hm): + """Modify output Messages + + Arguments: + hm -- Hooks manager + """ + + new_msg = list() + new_msg += self.responses + self.responses = list() + + while len(new_msg) > 0: + msg = self.first_treat(new_msg.pop(0)) + for h in hm.get_hooks("post"): + if h.match(message=msg, server=self.srv): + res = h.run(msg) + if isinstance(res, list): + for i in xrange(len(res)): + if res[i] == msg: + res.pop(i) + break + new_msg += res + elif res is not None and res != msg: + new_msg.append(res) + msg = None + break + elif res is None or res == False: + msg = None + break + if msg is not None: + self.responses.append(msg) + + + def run(self, context): + """Create, parse and treat the message""" + try: + # Parse and create the original message + msg = Message(self.raw, self.time, self.prvt) + self.first_treat(msg) + self.msgs.append(msg) + + # Run pre-treatment: from Message to [ Message ] + self.pre_treat(context.hooks) + + # Run in-treatment: from Message to [ Response ] + if len(self.msgs) > 0: + self.in_treat(context.hooks) + + # Run post-treatment: from Response to [ Response ] + if self.responses is not None and len(self.responses) > 0: + self.post_treat(context.hooks) + except: + logger.exception("Error occurred during the processing of the message: %s", self.raw) + return + + #return self.responses + for res in self.responses: to_server = None if res.server is None: to_server = self.srv @@ -75,39 +200,7 @@ class MessageConsumer: return False # Sent the message only if treat_post authorize it - if context.treat_post(res): - if type(res.channel) != list: - res.channel = [ res.channel ] - for channel in res.channel: - if channel is not None and channel != to_server.nick: - to_server.write("%s %s :%s" % ("PRIVMSG", channel, res.get_message())) - else: - channel = res.sender.split("!")[0] - to_server.write("%s %s :%s" % ("NOTICE" if res.is_ctcp else "PRIVMSG", channel, res.get_message())) - - elif res is not None: - logger.error("Unrecognized response type: %s", res) - - def run(self, context): - """Create, parse and treat the message""" - try: - msg = Message(self.raw, self.time, self.prvt) - msg.server = self.srv.id - if msg.cmd == "PRIVMSG": - msg.is_owner = (msg.nick == self.srv.owner) - msg.private = msg.private or (len(msg.receivers) == 1 and msg.receivers[0] == self.srv.nick) - res = self.treat_in(context, msg) - except: - logger.exception("Error occurred during the processing of the message: %s", self.raw) - return - - # Send message - self.treat_out(context, res) - - # Inform that the message has been treated - #self.srv.msg_treated(self.data) - - + to_server.send_response(res) class EventConsumer: """Store a event before treating""" diff --git a/hooks.py b/hooks.py index 25f1019..4d52135 100644 --- a/hooks.py +++ b/hooks.py @@ -24,151 +24,6 @@ from exception import IRCException logger = logging.getLogger("nemubot.hooks") -class MessagesHook: - def __init__(self, context, bot): - self.context = context - self.bot = bot - - # Store specials hooks - self.all_pre = list() # Treated before any parse - self.all_post = list() # Treated before send message to user - - # Store IRC commands hooks - self.irc_hook = dict() - - # Store direct hooks - self.cmd_hook = dict() - self.ask_hook = dict() - self.msg_hook = dict() - - # Store regexp hooks - self.cmd_rgxp = list() - self.ask_rgxp = list() - self.msg_rgxp = list() - - # Store default hooks (after other hooks if no match) - self.cmd_default = list() - self.ask_default = list() - self.msg_default = list() - - - def add_hook(self, store, hook, module_src=None): - """Insert in the right place a hook into the given store""" - logger.info("Adding hook '%s' to store '%s' from module '%s'", hook, store, module_src) - if module_src is None: - logger.warn("No source module was passed to add_hook function, " - "please fix it in order to be compatible with unload " - "feature") - - if store in self.context.hooks_cache: - logger.debug("Cleaning hooks cache for %s", store) - del self.context.hooks_cache[store] - - if not hasattr(self, store): - # TODO: raise custom exception, this is a user problem, not internal one! - logger.error("Unrecognized hook store: %s", store) - return - attr = getattr(self, store) - - if isinstance(attr, dict) and hook.name is not None: - if hook.name not in attr: - attr[hook.name] = list() - attr[hook.name].append(hook) - if hook.end is not None: - if hook.end not in attr: - attr[hook.end] = list() - attr[hook.end].append(hook) - elif isinstance(attr, list): - attr.append(hook) - else: - logger.critical("Unrecognized hook store type: %s", type(attr)) - return - if module_src is not None and hasattr(module_src, "REGISTERED_HOOKS"): - module_src.REGISTERED_HOOKS.append((store, hook)) - - def register_hook_attributes(self, store, module, node): - if node.hasAttribute("data"): - data = node["data"] - else: - data = None - if node.hasAttribute("name"): - self.add_hook(store + "_hook", Hook(getattr(module, node["call"]), - node["name"], data=data), - module) - elif node.hasAttribute("regexp"): - self.add_hook(store + "_rgxp", Hook(getattr(module, node["call"]), - regexp=node["regexp"], data=data), - module) - - def register_hook(self, module, node): - """Create a hook from configuration node""" - if node.name == "message" and node.hasAttribute("type"): - if node["type"] == "cmd" or node["type"] == "all": - self.register_hook_attributes("cmd", module, node) - - if node["type"] == "ask" or node["type"] == "all": - self.register_hook_attributes("ask", module, node) - - if (node["type"] == "msg" or node["type"] == "answer" or - node["type"] == "all"): - self.register_hook_attributes("answer", module, node) - - def clear(self): - for h in self.all_pre: - self.del_hook("all_pre", h) - for h in self.all_post: - self.del_hook("all_post", h) - - for l in self.irc_hook: - for h in self.irc_hook[l]: - self.del_hook("irc_hook", h) - - for l in self.cmd_hook: - for h in self.cmd_hook[l]: - self.del_hook("cmd_hook", h) - for l in self.ask_hook: - for h in self.ask_hook[l]: - self.del_hook("ask_hook", h) - for l in self.msg_hook: - for h in self.msg_hook[l]: - self.del_hook("msg_hook", h) - - for h in self.cmd_rgxp: - self.del_hook("cmd_rgxp", h) - for h in self.ask_rgxp: - self.del_hook("ask_rgxp", h) - for h in self.msg_rgxp: - self.del_hook("msg_rgxp", h) - - for h in self.cmd_default: - self.del_hook("cmd_default", h) - for h in self.ask_default: - self.del_hook("ask_default", h) - for h in self.msg_default: - self.del_hook("msg_default", h) - - def del_hook(self, store, hook, module_src=None): - """Remove a registered hook from a given store""" - if store in self.context.hooks_cache: - del self.context.hooks_cache[store] - - if not hasattr(self, store): - logger.warn("unrecognized hook store type") - return - attr = getattr(self, store) - - if isinstance(attr, dict) and hook.name is not None: - if hook.name in attr: - attr[hook.name].remove(hook) - if hook.end is not None and hook.end in attr: - attr[hook.end].remove(hook) - else: - attr.remove(hook) - - if module_src is not None: - module_src.REGISTERED_HOOKS.remove((store, hook)) - - 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): @@ -186,6 +41,18 @@ class Hook: 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 diff --git a/hooksmanager.py b/hooksmanager.py new file mode 100644 index 0000000..6db0989 --- /dev/null +++ b/hooksmanager.py @@ -0,0 +1,107 @@ +# -*- 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 . + +class HooksManager: + + """Class to manage hooks""" + + def __init__(self): + """Initialize the manager""" + + self.hooks = dict() + + + def add_hook(self, hook, *triggers): + """Add a hook to the manager + + Argument: + hook -- a Hook instance + triggers -- string that trigger the hook + """ + + trigger = "_".join(triggers) + + if trigger not in self.hooks: + self.hooks[trigger] = list() + + #print("ADD hook: %s => %s" % (trigger, hook)) + self.hooks[trigger].append(hook) + + + def del_hook(self, hook=None, *triggers): + """Remove the given hook from the manager + + Return: + Boolean value reporting the deletion success + + Argument: + triggers -- trigger string to remove + + Keyword argument: + hook -- a Hook instance to remove from the trigger string + """ + + trigger = "_".join(triggers) + + if trigger in self.hooks: + if hook is None: + del self.hooks[trigger] + return True + else: + return self.hooks[trigger].remove(hook) + return False + + + def get_hooks(self, *triggers): + """Returns list of trigger hooks that match the given trigger string + + Argument: + triggers -- the trigger string + + Keyword argument: + data -- Data to pass to the hook as argument + """ + + trigger = "_".join(triggers) + + res = list() + + for key in self.hooks: + if trigger.find(key) == 0: + res += self.hooks[key] + + #print("GET hooks: %s => %d" % (trigger, len(res))) + return res + + + def exec_hook(self, *triggers, **data): + """Trigger hooks that match the given trigger string + + Argument: + trigger -- the trigger string + + Keyword argument: + data -- Data to pass to the hook as argument + """ + + trigger = "_".join(triggers) + + for key in self.hooks: + if trigger.find(key) == 0: + for hook in self.hooks[key]: + hook.run(**data) diff --git a/importer.py b/importer.py index 26bc67e..a3554af 100644 --- a/importer.py +++ b/importer.py @@ -159,15 +159,19 @@ class ModuleLoader(SourceLoader): def send_response(server, res): if server in self.context.servers: - return self.context.servers[server].send_response(res, None) + return self.context.servers[server].send_response(res) else: module.logger.error("Try to send a message to the unknown server: %s", server) return False def add_hook(store, hook): - return self.context.hooks.add_hook(store, hook, module) + store = convert_legacy_store(store) + module.REGISTERED_HOOKS.append((store, hook)) + return self.context.hooks.add_hook(hook, store) def del_hook(store, hook): - return self.context.hooks.del_hook(store, hook) + store = convert_legacy_store(store) + module.REGISTERED_HOOKS.remove((store, hook)) + return self.context.hooks.del_hook(hook, store) def add_event(evt, eid=None): return self.context.add_event(evt, eid, module_src=module) def add_event_eid(evt, eid): @@ -236,6 +240,21 @@ class ModuleLoader(SourceLoader): return module +def convert_legacy_store(old): + if old == "cmd_hook" or old == "cmd_rgxp" or old == "cmd_default": + return "in_PRIVMSG_cmd" + elif old == "ask_hook" or old == "ask_rgxp" or old == "ask_default": + return "in_PRIVMSG_ask" + elif old == "msg_hook" or old == "msg_rgxp" or old == "msg_default": + return "in_PRIVMSG_def" + elif old == "all_post": + return "post" + elif old == "all_pre": + return "pre" + else: + print("UNKNOWN store:", old) + return old + def add_cap_hook(prompt, module, cmd): if hasattr(module, cmd["call"]): prompt.add_cap_hook(cmd["name"], getattr(module, cmd["call"])) @@ -250,5 +269,7 @@ def register_hooks(module, context, prompt): if s == "prompt_cmd": prompt.add_cap_hook(h.name, h.call) else: - context.hooks.add_hook(s, h, module) + s = convert_legacy_store(s) + module.REGISTERED_HOOKS.append((s, h)) + context.hooks.add_hook(h, s) hooks.last_registered = [] diff --git a/message.py b/message.py index bfe5612..9c2f44f 100644 --- a/message.py +++ b/message.py @@ -68,7 +68,7 @@ class Message: self.params.append(p.group("trailing")) # Special commands - if self.cmd == 'PRIVMSG': + if self.cmd == 'PRIVMSG' or self.cmd == 'NOTICE': self.receivers = self.decode(self.params[0]).split(',') # If CTCP, remove 0x01 @@ -106,6 +106,11 @@ class Message: def parse_content(self): """Parse or reparse the message content""" + # Remove ! + if self.text[0] == '!': + self.qual = "cmd" + self.text = self.text[1:].strip() + # Split content by words try: self.cmds = shlex.split(self.text) diff --git a/modules/alias.py b/modules/alias.py index a9621fb..7685f02 100644 --- a/modules/alias.py +++ b/modules/alias.py @@ -148,28 +148,22 @@ def treat_variables(res): res.messages[i] = replace_variables(", ".join(res.messages[i]), res) else: res.messages[i] = replace_variables(res.messages[i], res) - return True + return res -@hook("all_pre") -def treat_alias(msg, hooks_cache): - if msg.cmd == "PRIVMSG": - if len(msg.cmds) > 0 and (len(msg.cmds[0]) > 0 - and msg.cmds[0][0] == "!" - and msg.cmds[0][1:] in DATAS.getNode("aliases").index - and msg.cmds[0][1:] not in hooks_cache("cmd_hook")): - msg.text = msg.text.replace(msg.cmds[0], - DATAS.getNode("aliases").index[msg.cmds[0][1:]]["origin"], 1) +@hook("pre_PRIVMSG_cmd") +def treat_alias(msg): + if msg.cmds[0] in DATAS.getNode("aliases").index: + msg.text = msg.text.replace(msg.cmds[0], + DATAS.getNode("aliases").index[msg.cmds[0]]["origin"], 1) + msg.qual = "def" + msg.parse_content() - msg.parse_content() + return treat_alias(msg) - treat_alias(msg, hooks_cache) - return True - - else: - msg.text = replace_variables(msg.text, msg) - msg.parse_content() - return False - return False + else: + msg.text = replace_variables(msg.text, msg) + msg.parse_content() + return msg @hook("ask_default") def parseask(msg): @@ -187,4 +181,4 @@ def parseask(msg): res = Response(msg.sender, "Nouvel alias %s défini avec succès." % result.group(1)) save() return res - return False + return None diff --git a/modules/more.py b/modules/more.py index 202e91a..3956954 100644 --- a/modules/more.py +++ b/modules/more.py @@ -33,7 +33,7 @@ def parseresponse(res): SERVERS[res.server] = dict() for receiver in res.receivers: SERVERS[res.server][receiver] = res - return True + return res @hook("cmd_hook", "more") diff --git a/modules/reddit.py b/modules/reddit.py index 82bed8a..ac04856 100644 --- a/modules/reddit.py +++ b/modules/reddit.py @@ -67,9 +67,8 @@ def parselisten(msg): except: pass - return False + return msg @hook("all_post") def parseresponse(res): - parselisten(res) - return True + return parselisten(res) diff --git a/modules/ycc.py b/modules/ycc.py index 9ea0d15..1ed378b 100644 --- a/modules/ycc.py +++ b/modules/ycc.py @@ -66,9 +66,8 @@ def parselisten(msg): LAST_URLS[msg.channel].append(url) except: pass - return False + return msg @hook("all_post") def parseresponse(res): - parselisten(res) - return True + return parselisten(res) diff --git a/response.py b/response.py index 781c4d8..5529e92 100644 --- a/response.py +++ b/response.py @@ -69,6 +69,14 @@ class Response: self.sender = sender def append_message(self, message, title=None, shown_first_count=-1): + if type(message) is str: + message = message.split('\n') + if len(message) > 1: + for m in message: + self.append_message(m) + return + else: + message = message[0] if message is not None and len(message) > 0: if shown_first_count >= 0: self.messages.append(message[:shown_first_count]) diff --git a/server/DCC.py b/server/DCC.py index c5f8798..6f85b0b 100644 --- a/server/DCC.py +++ b/server/DCC.py @@ -31,7 +31,7 @@ import server #Store all used ports PORTS = list() -class DCC(server.Server): +class DCC(server.AbstractServer): def __init__(self, srv, dest, socket=None): server.Server.__init__(self) diff --git a/server/IRC.py b/server/IRC.py index f05c2c8..61c8172 100644 --- a/server/IRC.py +++ b/server/IRC.py @@ -42,6 +42,17 @@ class IRCServer(SocketServer): return True return False + def send_response(self, res): + if type(res.channel) != list: + res.channel = [ res.channel ] + for channel in res.channel: + if channel is not None and channel != self.nick: + self.write("%s %s :%s" % ("PRIVMSG", channel, res.get_message())) + else: + channel = res.sender.split("!")[0] + self.write("%s %s :%s" % ("NOTICE" if res.is_ctcp else "PRIVMSG", channel, res.get_message())) + + def _close(self): self.write("QUIT") SocketServer._close(self) diff --git a/server/__init__.py b/server/__init__.py index 3162c3f..67f1b2e 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -63,6 +63,10 @@ class AbstractServer(io.IOBase): _xlist.remove(self) + def send_response(self, res): + return NotImplemented + + def write(self, message): """Send a message to the server using send_callback""" self._send_callback(message)