Introducing new hooks manager
Currently, the manager use a naive implementation, this is mainly for architectural testing purpose.
This commit is contained in:
parent
29819c7874
commit
3bc53bb4ef
219
bot.py
219
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)
|
||||
|
||||
|
|
191
consumer.py
191
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"""
|
||||
|
|
157
hooks.py
157
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
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
29
importer.py
29
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 = []
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue