From dc5259395335c590f1055a0320009622496a908f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9munaire?= Date: Sun, 4 Nov 2012 04:28:24 +0100 Subject: [PATCH] Add new hook that treat all messages, not only PRIVMSG ; can respond with a response.Hook to create a hook --- DCC.py | 11 ++-- IRCServer.py | 30 +++++++--- bot.py | 165 +++++++++++++++++++++++++++++++++++++++++---------- channel.py | 153 ++++++++++++++++++++++++++++------------------- consumer.py | 132 +++++++++++------------------------------ hooks.py | 70 +++++++++++++++++----- importer.py | 5 ++ message.py | 27 +-------- response.py | 10 ++++ server.py | 5 +- 10 files changed, 365 insertions(+), 243 deletions(-) diff --git a/DCC.py b/DCC.py index 85ca1ec..96f974c 100644 --- a/DCC.py +++ b/DCC.py @@ -35,7 +35,6 @@ class DCC(server.Server): def __init__(self, srv, dest, socket=None): server.Server.__init__(self) - self.DCC = False self.error = False # An error has occur, closing the connection? self.messages = list() # Message queued before connexion @@ -132,9 +131,10 @@ class DCC(server.Server): if self.error: self.srv.send_msg_final(self.nick, msg) elif not self.connected or self.s is None: - if not self.DCC: - self.DCC = True + try: self.start() + except RuntimeError: + pass self.messages.append(msg) else: for line in msg.split("\n"): @@ -146,9 +146,10 @@ class DCC(server.Server): """Send a file over DCC""" if os.path.isfile(filename): self.messages = filename - if not self.DCC: + try: self.start() - self.DCC = True + except RuntimeError: + pass else: print("File not found `%s'" % filename) diff --git a/IRCServer.py b/IRCServer.py index 3341f75..36b3292 100644 --- a/IRCServer.py +++ b/IRCServer.py @@ -22,8 +22,9 @@ import socket import threading import traceback -import channel +from channel import Channel from DCC import DCC +from hooks import Hook import message import server import xmlparser @@ -56,7 +57,7 @@ class IRCServer(server.Server): self.channels = dict() for chn in self.node.getNodes("channel"): - chan = channel.Channel(chn, self) + chan = Channel(chn["name"], chn["password"]) self.channels[chan.name] = chan @@ -104,6 +105,23 @@ class IRCServer(server.Server): """Gives the server identifiant""" return self.host + ":" + str(self.port) + def register_hooks(self): + self.add_hook(Hook(self.evt_channel, "JOIN")) + self.add_hook(Hook(self.evt_channel, "PART")) + self.add_hook(Hook(self.evt_server, "NICK")) + self.add_hook(Hook(self.evt_server, "QUIT")) + self.add_hook(Hook(self.evt_channel, "332")) + self.add_hook(Hook(self.evt_channel, "353")) + + def evt_server(self, msg, srv): + for chan in self.channels: + self.channels[chan].treat(msg.cmd, msg) + + def evt_channel(self, msg, srv): + if msg.channel is not None: + if msg.channel in self.channels: + self.channels[msg.channel].treat(msg.cmd, msg) + def accepted_channel(self, chan, sender = None): """Return True if the channel (or the user) is authorized""" if self.allow_all: @@ -120,11 +138,7 @@ class IRCServer(server.Server): """Join a channel""" if force or (chan is not None and self.connected and chan not in self.channels): - chn = xmlparser.module_state.ModuleState("channel") - chn["name"] = chan - chn["password"] = password - self.node.addChild(chn) - self.channels[chan] = channel.Channel(chn, self) + self.channels[chan] = Channel(chan, password) if password is not None: self.s.send(("JOIN %s %s\r\n" % (chan, password)).encode()) else: @@ -136,7 +150,7 @@ class IRCServer(server.Server): def leave(self, chan): """Leave a channel""" if chan is not None and self.connected and chan in self.channels: - if chan.instanceof(list): + if isinstance(chan, list): for c in chan: self.leave(c) else: diff --git a/bot.py b/bot.py index 14c5c8f..8b65698 100644 --- a/bot.py +++ b/bot.py @@ -21,6 +21,7 @@ from datetime import timedelta from queue import Queue import threading import time +import re import consumer import event @@ -67,6 +68,10 @@ class Bot: self.cnsr_thrd = list() self.cnsr_thrd_size = -1 + self.hooks.add_hook("irc_hook", + hooks.Hook(self.treat_prvmsg, "PRIVMSG"), + self) + def init_ctcp_capabilities(self): """Reset existing CTCP capabilities to default one""" @@ -98,7 +103,7 @@ class Bot: print ("DCC: unable to connect to %s:%s" % (ip, msg.cmds[4])) - def add_event(self, evt, eid=None): + def add_event(self, evt, eid=None, module_src=None): """Register an event and return its identifiant for futur update""" if eid is None: # Find an ID @@ -123,18 +128,26 @@ class Bot: if len(self.events) <= 0 or self.events[i+1] != evt: return None + if module_src is not None: + module_src.REGISTERED_EVENTS.append(evt.id) + return evt.id - def del_event(self, id): + def del_event(self, id, module_src=None): """Find and remove an event from list""" if len(self.events) > 0 and id == self.events[0].id: self.events.remove(self.events[0]) self.update_timer() + if module_src is not None: + module_src.REGISTERED_EVENTS.remove(evt.id) return True for evt in self.events: if evt.id == id: self.events.remove(evt) + + if module_src is not None: + module_src.REGISTERED_EVENTS.remove(evt.id) return True return False @@ -172,6 +185,8 @@ class Bot: def addServer(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.register_hooks() if srv.id not in self.servers: self.servers[srv.id] = srv if srv.autoconnect: @@ -326,24 +341,111 @@ class Bot: elif isinstance(store, list): store.remove(hook) - def treat_pre(self, msg): + def treat_pre(self, msg, srv): """Treat a message before all other treatment""" for h, lvl, store in self.create_cache("all_pre"): - h.run(self.create_cache, msg) - self.check_rest_times(store, h) + if h.is_matching(None, server=srv): + h.run(msg, self.create_cache) + self.check_rest_times(store, h) - def treat_post(self, msg): + def treat_post(self, res): """Treat a message before send""" for h, lvl, store in self.create_cache("all_post"): - c = h.run(msg) - self.check_rest_times(store, h) - if not c: - return False + if h.is_matching(None, channel=res.channel, server=res.server): + c = h.run(msg) + self.check_rest_times(store, h) + if not c: + return False return True - def treat_cmd(self, msg): + 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) = 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 + if re.match("^ *(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)", + msg.content, re.I) is not None: + return response.Response(msg.sender, message="pong", + channel=msg.channel, nick=msg.nick) + + # Ask hooks + else: + return self.treat_ask(msg, srv) + + def treat_prvmsg(self, msg, srv): + # First, treat CTCP + if msg.ctcp: + if msg.cmds[0] in self.ctcp_capabilities: + return self.ctcp_capabilities[msg.cmds[0]](srv, msg) + else: + return _ctcp_response(msg.sender, "ERRMSG Unknown or unimplemented CTCP request") + + # Treat all messages starting with 'nemubot:' as distinct commands + elif msg.content.find("%s:"%srv.nick) == 0: + # Remove the bot name + msg.content = msg.content[len(srv.nick)+1:].strip() + + return self.treat_prvmsg_ask(msg, srv) + + # Owner commands + elif msg.content[0] == '`' and msg.nick == srv.owner: + #TODO: owner commands + pass + + elif msg.content[0] == '!' and len(msg.content) > 1: + # Remove the ! + msg.cmds[0] = msg.cmds[0][1:] + + if msg.cmds[0] == "help": + return _help_msg(msg.sender) + + elif msg.cmds[0] == "more": + if msg.channel == srv.nick: + if msg.sender in srv.moremessages: + return srv.moremessages[msg.sender] + else: + if msg.channel in srv.moremessages: + return srv.moremessages[msg.channel] + + elif msg.cmds[0] == "dcc": + print("dcctest for", msg.sender) + srv.send_dcc("Hello %s!" % msg.nick, msg.sender) + elif msg.cmds[0] == "pvdcctest": + print("dcctest") + return Response(msg.sender, message="Test DCC") + elif msg.cmds[0] == "dccsendtest": + print("dccsendtest") + conn = DCC(srv, msg.sender) + conn.send_file("bot_sample.xml") + + else: + return self.treat_cmd(msg, srv) + + else: + res = self.treat_answer(msg, srv) + # Assume the message starts with nemubot: + if (res is None or len(res) <= 0) and msg.private: + return self.treat_prvmsg_ask(msg, srv) + return res + + + def treat_cmd(self, msg, srv): """Treat a command message""" treated = list() @@ -352,15 +454,16 @@ class Bot: if msg.cmds[0] in cmd_hook: (hks, lvl, store) = cmd_hook[msg.cmds[0]] for h in hks: - res = h.run(msg) - if res is not None and res != False: - treated.append(res) - self.check_rest_times(store, h) + if h.is_matching(msg.cmds[0], channel=msg.channel, server=srv): + 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 in cmd_rgxp: - if hook.is_matching(msg.cmds[0], msg.channel): + if hook.is_matching(msg.cmds[0], msg.channel, server=srv): res = hook.run(msg) if res is not None and res != False: treated.append(res) @@ -378,7 +481,7 @@ class Bot: return treated - def treat_ask(self, msg): + def treat_ask(self, msg, srv): """Treat an ask message""" treated = list() @@ -387,16 +490,17 @@ class Bot: if msg.content in ask_hook: hks, lvl, store = ask_hook[msg.content] for h in hks: - res = h.run(msg) - if res is not None and res != False: - treated.append(res) - self.check_rest_times(store, h) + if h.is_matching(msg.content, channel=msg.channel, server=srv): + res = h.run(msg, strcmp=msg.content) + 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 in ask_rgxp: - if hook.is_matching(msg.content, msg.channel): - res = hook.run(msg) + if hook.is_matching(msg.content, channel=msg.channel, server=srv): + res = hook.run(msg, strcmp=msg.content) if res is not None and res != False: treated.append(res) self.check_rest_times(store, hook) @@ -413,7 +517,7 @@ class Bot: return treated - def treat_answer(self, msg): + def treat_answer(self, msg, srv): """Treat a normal message""" treated = list() @@ -422,16 +526,17 @@ class Bot: if msg.content in msg_hook: hks, lvl, store = msg_hook[msg.content] for h in hks: - res = h.run(msg) - if res is not None and res != False: - treated.append(res) - self.check_rest_times(store, h) + if h.is_matching(msg.content, channel=msg.channel, server=srv): + res = h.run(msg, strcmp=msg.content) + 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 in msg_rgxp: - if hook.is_matching(msg.content, msg.channel): - res = hook.run(msg) + if hook.is_matching(msg.content, channel=msg.channel, server=srv): + res = hook.run(msg, strcmp=msg.content) if res is not None and res != False: treated.append(res) self.check_rest_times(store, hook) diff --git a/channel.py b/channel.py index 718baeb..6a67d76 100644 --- a/channel.py +++ b/channel.py @@ -1,71 +1,102 @@ # 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 . + class Channel: - def __init__(self, node, srv): - self.node = node - self.name = node["name"] - self.password = node["password"] - self.people = dict() - self.srv = srv - self.topic = "" + def __init__(self, name, password=None): + self.name = name + self.password = password + self.people = dict() + self.topic = "" - def join(self, nick, level = 0): - #print ("%s arrive sur %s" % (nick, self.name)) - self.people[nick] = level + def treat(self, cmd, msg): + if cmd == "353": + self.parse353(msg) + elif cmd == "332": + self.parse332(msg) + elif cmd == "MODE": + self.mode(msg) + elif cmd == "JOIN": + self.join(msg.nick) + elif cmd == "NICK": + self.nick(msg.nick, msg.content) + elif cmd == "PART" or cmd == "QUIT": + self.part(msg.nick) + elif cmd == "TOPIC": + self.topic = self.content - def chtopic(self, newtopic): - """Send command to change the topic""" - self.srv.send_msg(self.name, newtopic, "TOPIC") - self.topic = newtopic + def join(self, nick, level = 0): + """Someone join the channel""" + #print ("%s arrive sur %s" % (nick, self.name)) + self.people[nick] = level - def nick(self, oldnick, newnick): - #print ("%s change de nom pour %s" % (oldnick, newnick)) - if oldnick in self.people: - lvl = self.people[oldnick] - del self.people[oldnick] - else: - lvl = 0 - self.people[newnick] = lvl + def chtopic(self, newtopic): + """Send command to change the topic""" + self.srv.send_msg(self.name, newtopic, "TOPIC") + self.topic = newtopic - def part(self, nick): - #print ("%s vient de quitter %s" % (self.sender, self.channel)) - if nick in self.people: - del self.people[nick] + def nick(self, oldnick, newnick): + """Someone change his nick""" + if oldnick in self.people: + #print ("%s change de nom pour %s sur %s" % (oldnick, newnick, self.name)) + lvl = self.people[oldnick] + del self.people[oldnick] + self.people[newnick] = lvl - def mode(self, msg): - if msg.content[0] == "-k": - self.password = "" - elif msg.content[0] == "+k": - if len(msg.content) > 1: - self.password = ' '.join(msg.content[1:])[1:] - else: - self.password = msg.content[1] - elif msg.content[0] == "+o": - self.people[msg.nick] |= 4 - elif msg.content[0] == "-o": - self.people[msg.nick] &= ~4 - elif msg.content[0] == "+h": - self.people[msg.nick] |= 2 - elif msg.content[0] == "-h": - self.people[msg.nick] &= ~2 - elif msg.content[0] == "+v": - self.people[msg.nick] |= 1 - elif msg.content[0] == "-v": - self.people[msg.nick] &= ~1 + def part(self, nick): + """Someone leave the channel""" + if nick in self.people: + #print ("%s vient de quitter %s" % (nick, self.name)) + del self.people[nick] - def parse332(self, msg): - self.topic = msg.content + def mode(self, msg): + if msg.content[0] == "-k": + self.password = "" + elif msg.content[0] == "+k": + if len(msg.content) > 1: + self.password = ' '.join(msg.content[1:])[1:] + else: + self.password = msg.content[1] + elif msg.content[0] == "+o": + self.people[msg.nick] |= 4 + elif msg.content[0] == "-o": + self.people[msg.nick] &= ~4 + elif msg.content[0] == "+h": + self.people[msg.nick] |= 2 + elif msg.content[0] == "-h": + self.people[msg.nick] &= ~2 + elif msg.content[0] == "+v": + self.people[msg.nick] |= 1 + elif msg.content[0] == "-v": + self.people[msg.nick] &= ~1 - def parse353(self, msg): - for p in msg.content: - p = p.decode() - if p[0] == "@": - level = 4 - elif p[0] == "%": - level = 2 - elif p[0] == "+": - level = 1 - else: - self.people[p] = 0 - continue - self.people[p[1:]] = level + def parse332(self, msg): + self.topic = msg.content + + def parse353(self, msg): + for p in msg.content: + p = p.decode() + if p[0] == "@": + level = 4 + elif p[0] == "%": + level = 2 + elif p[0] == "+": + level = 1 + else: + self.join(p, 0) + continue + self.join(p[1:], level) diff --git a/consumer.py b/consumer.py index 264cdc5..10ba236 100644 --- a/consumer.py +++ b/consumer.py @@ -25,7 +25,7 @@ import sys import bot from DCC import DCC from message import Message -from response import Response +import response import server class MessageConsumer: @@ -43,104 +43,48 @@ class MessageConsumer: if msg.cmd == "PING": self.srv.send_pong(msg.content) else: - # TODO: Manage credits here - context.treat_pre(msg) - - if msg.cmd == "PRIVMSG" and msg.ctcp: - if msg.cmds[0] in context.ctcp_capabilities: - return context.ctcp_capabilities[msg.cmds[0]](self.srv, msg) - else: - return bot._ctcp_response(msg.sender, "ERRMSG Unknown or unimplemented CTCP request") - elif msg.cmd == "PRIVMSG" and self.srv.accepted_channel(msg.channel): - return self.treat_prvmsg(context, msg) - # TODO: continue here - pass - - def treat_prvmsg_ask(self, context, msg): - # Treat ping - if re.match("^ *(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)", - msg.content, re.I) is not None: - return Response(msg.sender, message="pong", - channel=msg.channel, nick=msg.nick) - - # Ask hooks - else: - return context.treat_ask(msg) - - def treat_prvmsg(self, context, msg): - # Treat all messages starting with 'nemubot:' as distinct commands - if msg.content.find("%s:"%self.srv.nick) == 0: - # Remove the bot name - msg.content = msg.content[len(self.srv.nick)+1:].strip() - - return self.treat_prvmsg_ask(context, msg) - - # Owner commands - elif msg.content[0] == '`' and msg.nick == self.srv.owner: - #TODO: owner commands - pass - - elif msg.content[0] == '!' and len(msg.content) > 1: - # Remove the ! - msg.cmds[0] = msg.cmds[0][1:] - - if msg.cmds[0] == "help": - return _help_msg(msg.sender) - - elif msg.cmds[0] == "more": - if msg.channel == self.srv.nick: - if msg.sender in self.srv.moremessages: - return self.srv.moremessages[msg.sender] - else: - if msg.channel in self.srv.moremessages: - return self.srv.moremessages[msg.channel] - - elif msg.cmds[0] == "dcc": - print("dcctest for", msg.sender) - self.srv.send_dcc("Hello %s!" % msg.nick, msg.sender) - elif msg.cmds[0] == "pvdcctest": - print("dcctest") - return Response(msg.sender, message="Test DCC") - elif msg.cmds[0] == "dccsendtest": - print("dccsendtest") - conn = DCC(self.srv, msg.sender) - conn.send_file("bot_sample.xml") - - else: - return context.treat_cmd(msg) - - else: - res = context.treat_answer(msg) - # Assume the message starts with nemubot: - if (res is None or len(res) <= 0) and self.prvt: - return self.treat_prvmsg_ask(context, msg) - return res + # All messages + context.treat_pre(msg, self.srv) + # TODO: Manage credits + return context.treat_irc(msg, self.srv) 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) + + elif isinstance(res, response.Response): # Define the destination server - if (res.server is not None and - res.server.instanceof(string) and res.server in context.servers): - res.server = context.servers[res.server] - if (res.server is not None and - not res.server.instanceof(server.Server)): - print ("\033[1;35mWarning:\033[0m the server defined in this " - "response doesn't exist: %s" % (res.server)) - res.server = None - if res.server is None: - res.server = self.srv + if (res.server is not None and + isinstance(res.server, str) and res.server in context.servers): + res.server = context.servers[res.server] + if (res.server is not None and + not isinstance(res.server, server.Server)): + print ("\033[1;35mWarning:\033[0m the server defined in this " + "response doesn't exist: %s" % (res.server)) + res.server = None + if res.server is None: + res.server = self.srv - # Sent the message only if treat_post authorize it - if context.treat_post(res): - res.server.send_response(res, self.data) + # Sent the message only if treat_post authorize it + if context.treat_post(res): + res.server.send_response(res, self.data) + elif isinstance(res, response.Hook): + context.hooks.add_hook(res.type, res.hook, res.src) + + elif res is not None: + print ("\033[1;35mWarning:\033[0m 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.is_owner = (msg.nick == self.srv.owner) + if msg.cmd == "PRIVMSG": + msg.is_owner = (msg.nick == self.srv.owner) res = self.treat_in(context, msg) except: print ("\033[1;31mERROR:\033[0m occurred during the " @@ -151,17 +95,7 @@ class MessageConsumer: return # Send message - if res is not None: - if isinstance(res, list): - for r in res: - if isinstance(r, Response): - self.treat_out(context, r) - elif isinstance(r, list): - for s in r: - if isinstance(s, Response): - self.treat_out(context, s) - elif isinstance(res, Response): - self.treat_out(context, res) + self.treat_out(context, res) # Inform that the message has been treated self.srv.msg_treated(self.data) @@ -210,7 +144,7 @@ class Consumer(threading.Thread): def _help_msg(modules, sndr, cmd): """Parse and response to help messages""" - res = Response(sndr) + res = response.Response(sndr) if len(cmd) > 1: if cmd[1] in modules: if len(cmd) > 2: diff --git a/hooks.py b/hooks.py index 34e5332..965a50f 100644 --- a/hooks.py +++ b/hooks.py @@ -22,16 +22,19 @@ class MessagesHook: def __init__(self, context): self.context = context - # Store specials hook + # Store specials hooks self.all_pre = list() # Treated before any parse self.all_post = list() # Treated before send message to user - # Store direct hook + # 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 hook + # Store regexp hooks self.cmd_rgxp = list() self.ask_rgxp = list() self.msg_rgxp = list() @@ -61,12 +64,16 @@ class MessagesHook: 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: print ("\033[1;32mWarning:\033[0m unrecognized hook store type") return - if module_src is not None: + 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): @@ -92,7 +99,7 @@ class MessagesHook: node["type"] == "all"): self.register_hook_attributes("answer", module, node) - def del_hook(self, store, hook): + 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] @@ -105,33 +112,68 @@ class MessagesHook: 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()): + def __init__(self, call, name=None, data=None, regexp=None, channels=list(), server=None, end=None, call_end=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 - def is_matching(self, strcmp, channel): + def is_matching(self, strcmp, channel=None, server=None): """Test if the current hook correspond to the message""" - return (len(self.channel) <= 0 or channel in self.channels) and ( + 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 not None and strcmp == self.name) or ( + self.end is not None and strcmp == self.end) or ( self.regexp is not None and re.match(self.regexp, strcmp))) - def run(self, msg): + def run(self, msg, data2=None, strcmp=None): """Run the hook""" if self.times != 0: self.times -= 1 - if self.data is None: - return self.call(msg) - elif isinstance(self.data, dict): - return self.call(msg, **self.data) + 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: - return self.call(msg, self.data) + call = self.call + + 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) diff --git a/importer.py b/importer.py index 6ddc7ee..bf4fd59 100644 --- a/importer.py +++ b/importer.py @@ -145,6 +145,7 @@ class ModuleLoader(SourceLoader): # Set module common functions and datas module.REGISTERED_HOOKS = list() + module.REGISTERED_EVENTS = list() module.DEBUG = False module.DIR = self.mpath module.name = fullname @@ -152,6 +153,10 @@ class ModuleLoader(SourceLoader): module.print_debug = lambda msg: mod_print_dbg(module, msg) module.send_response = lambda srv, res: mod_send_response(self.context, srv, res) module.add_hook = lambda store, hook: self.context.hooks.add_hook(store, hook, module) + module.del_hook = lambda store, hook: self.context.hooks.del_hook(store, hook) + module.add_event = lambda evt: self.context.add_event(evt, module_src=module) + module.add_event_eid = lambda evt, eid: self.context.add_event(evt, eid, module_src=module) + module.del_event = lambda evt: self.context.add_event(evt, module_src=module) if not hasattr(module, "NODATA"): module.DATAS = xmlparser.parse_file(self.context.datas_path diff --git a/message.py b/message.py index 990f3d3..d14a16d 100644 --- a/message.py +++ b/message.py @@ -105,8 +105,8 @@ class Message: def parse_content(self): """Parse or reparse the message content""" # If CTCP, remove 0x01 - #if self.ctcp: - # self.content = self.content[1:len(self.content)-1] + if self.ctcp: + self.content = self.content[1:len(self.content)-1] # Split content by words try: @@ -146,29 +146,6 @@ class Message: return False return self.srv.accepted_channel(self.channel) - def treat(self): - """Parse and treat the message""" - if self.channel in self.srv.channels: - if self.cmd == "353": - self.srv.channels[self.channel].parse353(self) - elif self.cmd == "332": - self.srv.channels[self.channel].parse332(self) - elif self.cmd == "MODE": - self.srv.channels[self.channel].mode(self) - elif self.cmd == "JOIN": - self.srv.channels[self.channel].join(self.nick) - elif self.cmd == "PART": - self.srv.channels[self.channel].part(self.nick) - elif self.cmd == "TOPIC": - self.srv.channels[self.channel].topic = self.content - elif self.cmd == "NICK": - for chn in self.srv.channels.keys(): - self.srv.channels[chn].nick(self.nick, self.content) - elif self.cmd == "QUIT": - for chn in self.srv.channels.keys(): - self.srv.channels[chn].part(self.nick) - return None - ############################## # # # Extraction/Format text # diff --git a/response.py b/response.py index 8d41a3b..17c6c41 100644 --- a/response.py +++ b/response.py @@ -154,3 +154,13 @@ class Response: self.pop() self.elt = 0 return msg + +import hooks +class Hook: + def __init__(self, TYPE, call, name=None, data=None, regexp=None, + channels=list(), server=None, end=None, call_end=None, + SRC=None): + self.hook = hooks.Hook(call, name, data, regexp, channels, + server, end, call_end) + self.type = TYPE + self.src = SRC diff --git a/server.py b/server.py index 67bf0bc..fd5fdff 100644 --- a/server.py +++ b/server.py @@ -144,7 +144,10 @@ class Server(threading.Thread): self._receive_action = receive_action if not self.connected: self.stop = False - self.start() + try: + self.start() + except RuntimeError: + pass elif verb: print (" Already connected.")