New message processing
This commit is contained in:
parent
981025610e
commit
dfde4c5f49
43
bot.py
43
bot.py
@ -25,7 +25,7 @@ import threading
|
||||
import time
|
||||
import uuid
|
||||
|
||||
__version__ = '3.4.dev0'
|
||||
__version__ = '3.4.dev1'
|
||||
__author__ = 'nemunaire'
|
||||
|
||||
from consumer import Consumer, EventConsumer, MessageConsumer
|
||||
@ -33,7 +33,7 @@ from event import ModuleEvent
|
||||
from hooks.messagehook import MessageHook
|
||||
from hooks.manager import HooksManager
|
||||
from networkbot import NetworkBot
|
||||
from server.IRC import IRCServer
|
||||
from server.IRC import IRC as IRCServer
|
||||
from server.DCC import DCC
|
||||
|
||||
logger = logging.getLogger("nemubot.bot")
|
||||
@ -74,28 +74,27 @@ class Bot(threading.Thread):
|
||||
# Own hooks
|
||||
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 "PRIVMSG %s :%s: pong" % (",".join(msg.receivers), msg.nick)
|
||||
self.hooks.add_hook(MessageHook(in_ping), "in", "PRIVMSG", "ask")
|
||||
if re.match("^ *(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)", msg.message, re.I) is not None:
|
||||
return msg.respond("pong")
|
||||
self.hooks.add_hook(MessageHook(in_ping), "in", "DirectAsk")
|
||||
|
||||
def _help_msg(msg):
|
||||
"""Parse and response to help messages"""
|
||||
cmd = msg.cmds
|
||||
from more import Response
|
||||
res = Response()
|
||||
if len(cmd) > 1:
|
||||
if cmd[1] in self.modules:
|
||||
if len(cmd) > 2:
|
||||
if hasattr(self.modules[cmd[1]], "HELP_cmd"):
|
||||
res.append_message(self.modules[cmd[1]].HELP_cmd(cmd[2]))
|
||||
res = Response(channel=msg.frm)
|
||||
if len(msg.args) > 1:
|
||||
if msg.args[0] in self.modules:
|
||||
if len(msg.args) > 2:
|
||||
if hasattr(self.modules[msg.args[0]], "HELP_cmd"):
|
||||
res.append_message(self.modules[msg.args[0]].HELP_cmd(msg.args[1]))
|
||||
else:
|
||||
res.append_message("No help for command %s in module %s" % (cmd[2], cmd[1]))
|
||||
elif hasattr(self.modules[cmd[1]], "help_full"):
|
||||
res.append_message(self.modules[cmd[1]].help_full())
|
||||
res.append_message("No help for command %s in module %s" % (msg.args[1], msg.args[0]))
|
||||
elif hasattr(self.modules[msg.args[0]], "help_full"):
|
||||
res.append_message(self.modules[msg.args[0]].help_full())
|
||||
else:
|
||||
res.append_message("No help for module %s" % cmd[1])
|
||||
res.append_message("No help for module %s" % msg.args[0])
|
||||
else:
|
||||
res.append_message("No module named %s" % cmd[1])
|
||||
res.append_message("No module named %s" % msg.args[0])
|
||||
else:
|
||||
res.append_message("Pour me demander quelque chose, commencez "
|
||||
"votre message par mon nom ; je réagis "
|
||||
@ -113,7 +112,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(MessageHook(_help_msg, "help"), "in", "PRIVMSG", "cmd")
|
||||
self.hooks.add_hook(MessageHook(_help_msg, "help"), "in", "Command")
|
||||
|
||||
# Other known bots, making a bots network
|
||||
self.network = dict()
|
||||
@ -343,12 +342,10 @@ class Bot(threading.Thread):
|
||||
"""Add a module to the context, if already exists, unload the
|
||||
old one before"""
|
||||
# Check if the module already exists
|
||||
for mod in self.modules.keys():
|
||||
if self.modules[mod].name == module.name:
|
||||
self.unload_module(self.modules[mod].name)
|
||||
break
|
||||
if module.__name__ in self.modules:
|
||||
self.unload_module(module.__name__)
|
||||
|
||||
self.modules[module.name] = module
|
||||
self.modules[module.__name__] = module
|
||||
return True
|
||||
|
||||
|
||||
|
45
consumer.py
45
consumer.py
@ -20,13 +20,6 @@ import logging
|
||||
import queue
|
||||
import re
|
||||
import threading
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
import bot
|
||||
from server.DCC import DCC
|
||||
from message import Message
|
||||
import server
|
||||
|
||||
logger = logging.getLogger("nemubot.consumer")
|
||||
|
||||
@ -48,26 +41,10 @@ class MessageConsumer:
|
||||
msg -- The Message or Response to qualify
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -84,8 +61,8 @@ class MessageConsumer:
|
||||
|
||||
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):
|
||||
for h in hm.get_hooks("pre", type(msg).__name__):
|
||||
if h.match(msg, server=self.srv):
|
||||
res = h.run(msg)
|
||||
if isinstance(res, list):
|
||||
for i in range(len(res)):
|
||||
@ -113,17 +90,12 @@ class MessageConsumer:
|
||||
|
||||
self.responses = list()
|
||||
for msg in self.msgs:
|
||||
for h in hm.get_hooks("in", msg.cmd, msg.qual):
|
||||
if h.match(message=msg, server=self.srv):
|
||||
for h in hm.get_hooks("in", type(msg).__name__):
|
||||
if h.match(msg, server=self.srv):
|
||||
res = h.run(msg)
|
||||
if isinstance(res, list):
|
||||
for r in res:
|
||||
if hasattr(r, "set_sender"):
|
||||
r.set_sender(msg.sender)
|
||||
self.responses += res
|
||||
elif res is not None:
|
||||
if hasattr(res, "set_sender"):
|
||||
res.set_sender(msg.sender)
|
||||
self.responses.append(res)
|
||||
|
||||
|
||||
@ -145,7 +117,7 @@ class MessageConsumer:
|
||||
continue
|
||||
msg = self.first_treat(ff)
|
||||
for h in hm.get_hooks("post"):
|
||||
if h.match(message=msg, server=self.srv):
|
||||
if h.match(msg, server=self.srv):
|
||||
res = h.run(msg)
|
||||
if isinstance(res, list):
|
||||
for i in range(len(res)):
|
||||
@ -154,6 +126,7 @@ class MessageConsumer:
|
||||
break
|
||||
msg = None
|
||||
new_msg += res
|
||||
break
|
||||
elif res is not None and res != msg:
|
||||
new_msg.append(res)
|
||||
msg = None
|
||||
@ -161,6 +134,8 @@ class MessageConsumer:
|
||||
elif res is None or res == False:
|
||||
msg = None
|
||||
break
|
||||
else:
|
||||
msg = res
|
||||
if msg is not None:
|
||||
self.responses.append(msg)
|
||||
|
||||
@ -182,7 +157,7 @@ class MessageConsumer:
|
||||
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.msgs[0].raw)
|
||||
logger.exception("Error occurred during the processing of the %s: %s", type(self.msgs[0]).__name__, self.msgs[0])
|
||||
|
||||
for res in self.responses:
|
||||
to_server = None
|
||||
@ -200,7 +175,7 @@ class MessageConsumer:
|
||||
continue
|
||||
|
||||
# Sent the message only if treat_post authorize it
|
||||
to_server.write(res)
|
||||
to_server.send_response(res)
|
||||
|
||||
class EventConsumer:
|
||||
"""Store a event before treating"""
|
||||
|
@ -16,6 +16,8 @@
|
||||
# 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/>.
|
||||
|
||||
from message import TextMessage, DirectAsk
|
||||
|
||||
class IRCException(Exception):
|
||||
|
||||
def __init__(self, message, personnal=True):
|
||||
@ -24,6 +26,7 @@ class IRCException(Exception):
|
||||
self.personnal = personnal
|
||||
|
||||
def fill_response(self, msg):
|
||||
# TODO: no more Response usable here
|
||||
from more import Response
|
||||
return Response(self.message, channel=msg.receivers, nick=(msg.nick if self.personnal else None))
|
||||
if self.personnal:
|
||||
return DirectAsk(msg.frm, self.message, server=msg.server, to=msg.to_response)
|
||||
else:
|
||||
return TextMessage(self.message, server=msg.server, to=msg.to_response)
|
||||
|
@ -20,8 +20,7 @@ import re
|
||||
|
||||
from exception import IRCException
|
||||
import hooks
|
||||
|
||||
from message import Message
|
||||
import message
|
||||
|
||||
class MessageHook(hooks.AbstractHook):
|
||||
|
||||
@ -39,24 +38,28 @@ class MessageHook(hooks.AbstractHook):
|
||||
self.channels = channels
|
||||
|
||||
|
||||
def match(self, message, server=None):
|
||||
if not isinstance(message, Message):
|
||||
def match(self, msg, server=None):
|
||||
if not isinstance(msg, message.AbstractMessage):
|
||||
return True
|
||||
|
||||
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)
|
||||
elif isinstance(msg, message.Command):
|
||||
return self.is_matching(msg.cmd, msg.to, server)
|
||||
elif isinstance(msg, message.TextMessage):
|
||||
return self.is_matching(msg.message, msg.to, server)
|
||||
else:
|
||||
return self.is_matching(message.cmd, message.channel, server)
|
||||
return False
|
||||
|
||||
|
||||
def is_matching(self, strcmp, channel=None, server=None):
|
||||
def is_matching(self, strcmp, receivers=list(), 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)))
|
||||
if (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))):
|
||||
|
||||
if receivers and self.channels:
|
||||
for receiver in receivers:
|
||||
if receiver in self.channels:
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
39
importer.py
39
importer.py
@ -28,6 +28,7 @@ from bot import __version__
|
||||
import event
|
||||
import exception
|
||||
import hooks
|
||||
from message import TextMessage
|
||||
import xmlparser
|
||||
|
||||
logger = logging.getLogger("nemubot.importer")
|
||||
@ -124,13 +125,6 @@ class ModuleLoader(SourceLoader):
|
||||
def load_module(self, fullname):
|
||||
module = self._load_module(fullname, sourceless=True)
|
||||
|
||||
# Remove the module from sys list
|
||||
del sys.modules[fullname]
|
||||
|
||||
# If the module was already loaded, then reload it
|
||||
if hasattr(module, '__LOADED__'):
|
||||
reload(module)
|
||||
|
||||
# Check that is a valid nemubot module
|
||||
if not hasattr(module, "nemubotversion"):
|
||||
raise ImportError("Module `%s' is not a nemubot module."%self.name)
|
||||
@ -144,21 +138,25 @@ class ModuleLoader(SourceLoader):
|
||||
module.logger = logging.getLogger("nemubot.module." + fullname)
|
||||
|
||||
def prnt(*args):
|
||||
print("[%s]" % module.name, *args)
|
||||
print("[%s]" % module.__name__, *args)
|
||||
module.logger.info(*args)
|
||||
def prnt_dbg(*args):
|
||||
if module.DEBUG:
|
||||
print("{%s}" % module.name, *args)
|
||||
print("{%s}" % module.__name__, *args)
|
||||
module.logger.debug(*args)
|
||||
|
||||
def mod_save():
|
||||
fpath = self.context.data_path + "/" + module.name + ".xml"
|
||||
fpath = self.context.data_path + "/" + module.__name__ + ".xml"
|
||||
module.print_debug("Saving DATAS to " + fpath)
|
||||
module.DATAS.save(fpath)
|
||||
|
||||
def send_response(server, res):
|
||||
if server in self.context.servers:
|
||||
return self.context.servers[server].write("PRIVMSG %s :%s" % (",".join(res.receivers), res.get_message()))
|
||||
r = res.next_response()
|
||||
if r.server is not None:
|
||||
return self.context.servers[r.server].send_response(r)
|
||||
else:
|
||||
return self.context.servers[server].send_response(r)
|
||||
else:
|
||||
module.logger.error("Try to send a message to the unknown server: %s", server)
|
||||
return False
|
||||
@ -183,7 +181,6 @@ class ModuleLoader(SourceLoader):
|
||||
module.REGISTERED_EVENTS = list()
|
||||
module.DEBUG = False
|
||||
module.DIR = self.mpath
|
||||
module.name = fullname
|
||||
module.print = prnt
|
||||
module.print_debug = prnt_dbg
|
||||
module.send_response = send_response
|
||||
@ -195,7 +192,7 @@ class ModuleLoader(SourceLoader):
|
||||
|
||||
if not hasattr(module, "NODATA"):
|
||||
module.DATAS = xmlparser.parse_file(self.context.data_path
|
||||
+ module.name + ".xml")
|
||||
+ module.__name__ + ".xml")
|
||||
module.save = mod_save
|
||||
else:
|
||||
module.DATAS = None
|
||||
@ -216,7 +213,7 @@ class ModuleLoader(SourceLoader):
|
||||
break
|
||||
if depend["name"] not in module.MODS:
|
||||
logger.error("In module `%s', module `%s' require by this "
|
||||
"module but is not loaded.", module.name,
|
||||
"module but is not loaded.", module.__name__,
|
||||
depend["name"])
|
||||
return
|
||||
|
||||
@ -230,21 +227,21 @@ class ModuleLoader(SourceLoader):
|
||||
# Register hooks
|
||||
register_hooks(module, self.context, self.prompt)
|
||||
|
||||
logger.info("Module '%s' successfully loaded.", module.name)
|
||||
logger.info("Module '%s' successfully loaded.", module.__name__)
|
||||
else:
|
||||
logger.error("An error occurs while importing `%s'.", module.name)
|
||||
logger.error("An error occurs while importing `%s'.", module.__name__)
|
||||
raise ImportError("An error occurs while importing `%s'."
|
||||
% module.name)
|
||||
% module.__name__)
|
||||
return module
|
||||
|
||||
|
||||
def convert_legacy_store(old):
|
||||
if old == "cmd_hook" or old == "cmd_rgxp" or old == "cmd_default":
|
||||
return "in_PRIVMSG_cmd"
|
||||
return "in_Command"
|
||||
elif old == "ask_hook" or old == "ask_rgxp" or old == "ask_default":
|
||||
return "in_PRIVMSG_ask"
|
||||
return "in_DirectAsk"
|
||||
elif old == "msg_hook" or old == "msg_rgxp" or old == "msg_default":
|
||||
return "in_PRIVMSG_def"
|
||||
return "in_TextMessage"
|
||||
elif old == "all_post":
|
||||
return "post"
|
||||
elif old == "all_pre":
|
||||
@ -258,7 +255,7 @@ def add_cap_hook(prompt, module, cmd):
|
||||
prompt.add_cap_hook(cmd["name"], getattr(module, cmd["call"]))
|
||||
else:
|
||||
logger.warn("In module `%s', no function `%s' defined for `%s' "
|
||||
"command hook.", module.name, cmd["call"], cmd["name"])
|
||||
"command hook.", module.__name__, cmd["call"], cmd["name"])
|
||||
|
||||
def register_hooks(module, context, prompt):
|
||||
"""Register all available hooks"""
|
||||
|
71
message.py
71
message.py
@ -1,71 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime
|
||||
import shlex
|
||||
|
||||
class Message:
|
||||
def __init__ (self, orig, private=False):
|
||||
self.cmd = orig.cmd
|
||||
self.tags = orig.tags
|
||||
self.params = orig.params
|
||||
self.private = private
|
||||
self.prefix = orig.prefix
|
||||
self.nick = orig.nick
|
||||
|
||||
# Special commands
|
||||
if self.cmd == 'PRIVMSG' or self.cmd == 'NOTICE':
|
||||
self.receivers = orig.decode(self.params[0]).split(',')
|
||||
|
||||
# If CTCP, remove 0x01
|
||||
if len(self.params[1]) > 1 and (self.params[1][0] == 0x01 or self.params[1][1] == 0x01):
|
||||
self.is_ctcp = True
|
||||
self.text = orig.decode(self.params[1][1:len(self.params[1])-1])
|
||||
else:
|
||||
self.is_ctcp = False
|
||||
self.text = orig.decode(self.params[1])
|
||||
|
||||
# Split content by words
|
||||
self.parse_content()
|
||||
|
||||
else:
|
||||
for i in range(0, len(self.params)):
|
||||
self.params[i] = orig.decode(self.params[i])
|
||||
|
||||
|
||||
# TODO: here for legacy content
|
||||
@property
|
||||
def sender(self):
|
||||
return self.prefix
|
||||
@property
|
||||
def channel(self):
|
||||
return self.receivers[0]
|
||||
|
||||
|
||||
def parse_content(self):
|
||||
"""Parse or reparse the message content"""
|
||||
# Remove !
|
||||
if len(self.text) > 1 and self.text[0] == '!':
|
||||
self.qual = "cmd"
|
||||
self.text = self.text[1:].strip()
|
||||
|
||||
# Split content by words
|
||||
try:
|
||||
self.cmds = shlex.split(self.text)
|
||||
except ValueError:
|
||||
self.cmds = self.text.split(' ')
|
160
message/__init__.py
Normal file
160
message/__init__.py
Normal file
@ -0,0 +1,160 @@
|
||||
# -*- 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/>.
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
class AbstractMessage:
|
||||
|
||||
"""This class represents an abstract message"""
|
||||
|
||||
def __init__(self, server, date=None, to=None, to_response=None, frm=None):
|
||||
"""Initialize an abstract message
|
||||
|
||||
Arguments:
|
||||
server -- the servir identifier
|
||||
date -- time of the message reception, default: now
|
||||
to -- list of recipients
|
||||
to_response -- if channel(s) where send the response differ
|
||||
frm -- the sender
|
||||
"""
|
||||
|
||||
self.server = server
|
||||
self.date = datetime.now(timezone.utc) if date is None else date
|
||||
self.to = to if to is not None else list()
|
||||
self._to_response = to_response if to_response is None or isinstance(to_response, list) else [ to_response ]
|
||||
self.frm = frm # None allowed when it designate this bot
|
||||
|
||||
|
||||
@property
|
||||
def to_response(self):
|
||||
if self._to_response is not None:
|
||||
return self._to_response
|
||||
else:
|
||||
return self.to
|
||||
|
||||
|
||||
@property
|
||||
def receivers(self):
|
||||
# TODO: this is for legacy modules
|
||||
return self.to_response
|
||||
|
||||
@property
|
||||
def channel(self):
|
||||
# TODO: this is for legacy modules
|
||||
return self.to_response[0]
|
||||
|
||||
@property
|
||||
def nick(self):
|
||||
# TODO: this is for legacy modules
|
||||
return self.frm
|
||||
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit(self)
|
||||
|
||||
|
||||
def export_args(self, without=list()):
|
||||
if not isinstance(without, list):
|
||||
without = [ without ]
|
||||
|
||||
ret = {
|
||||
"server": self.server,
|
||||
"date": self.date,
|
||||
"to": self.to,
|
||||
"to_response": self._to_response,
|
||||
"frm": self.frm
|
||||
}
|
||||
|
||||
for w in without:
|
||||
if w in ret:
|
||||
del ret[w]
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
class TextMessage(AbstractMessage):
|
||||
|
||||
"""This class represent a simple message send to someone"""
|
||||
|
||||
def __init__(self, message, *args, **kargs):
|
||||
"""Initialize a message with no particular specificity
|
||||
|
||||
Argument:
|
||||
message -- the parsed message
|
||||
"""
|
||||
|
||||
AbstractMessage.__init__(self, *args, **kargs)
|
||||
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
# TODO: this is for legacy modules
|
||||
return self.message
|
||||
|
||||
|
||||
class DirectAsk(TextMessage):
|
||||
|
||||
"""This class represents a message to this bot"""
|
||||
|
||||
def __init__(self, designated, *args, **kargs):
|
||||
"""Initialize a message to a specific person
|
||||
|
||||
Argument:
|
||||
designated -- the user designated by the message
|
||||
"""
|
||||
|
||||
TextMessage.__init__(self, *args, **kargs)
|
||||
|
||||
self.designated = designated
|
||||
|
||||
|
||||
def respond(self, message):
|
||||
return DirectAsk(self.frm,
|
||||
message,
|
||||
server=self.server,
|
||||
to=self.to_response)
|
||||
|
||||
|
||||
class Command(AbstractMessage):
|
||||
|
||||
"""This class represents a specialized TextMessage"""
|
||||
|
||||
def __init__(self, cmd, args=None, *nargs, **kargs):
|
||||
AbstractMessage.__init__(self, *nargs, **kargs)
|
||||
|
||||
self.cmd = cmd
|
||||
self.args = args if args is not None else list()
|
||||
|
||||
def __str__(self):
|
||||
return self.cmd + " @" + ",@".join(self.args)
|
||||
|
||||
@property
|
||||
def cmds(self):
|
||||
# TODO: this is for legacy modules
|
||||
return [self.cmd] + self.args
|
||||
|
||||
|
||||
class OwnerCommand(Command):
|
||||
|
||||
"""This class represents a special command incomming from the owner"""
|
||||
|
||||
pass
|
69
message/printer/IRC.py
Normal file
69
message/printer/IRC.py
Normal file
@ -0,0 +1,69 @@
|
||||
# -*- 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/>.
|
||||
|
||||
from message import TextMessage
|
||||
from message.visitor import AbstractVisitor
|
||||
|
||||
class IRC(AbstractVisitor):
|
||||
|
||||
def __init__(self):
|
||||
self.pp = ""
|
||||
|
||||
|
||||
def visit_TextMessage(self, msg):
|
||||
self.pp += "PRIVMSG %s :" % ",".join(msg.to)
|
||||
if isinstance(msg.message, str):
|
||||
self.pp += msg.message
|
||||
else:
|
||||
msg.message.accept(self)
|
||||
self.pp += "\r\n"
|
||||
|
||||
|
||||
def visit_DirectAsk(self, msg):
|
||||
others = [to for to in msg.to if to != msg.designated]
|
||||
|
||||
# Avoid nick starting message when discussing on user channel
|
||||
if len(others) != len(msg.to):
|
||||
res = TextMessage(msg.message,
|
||||
server=msg.server, date=msg.date,
|
||||
to=msg.to, frm=msg.frm)
|
||||
res.accept(self)
|
||||
|
||||
if len(others):
|
||||
res = TextMessage("%s: %s" % (msg.designated, msg.message),
|
||||
server=msg.server, date=msg.date,
|
||||
to=others, frm=msg.frm)
|
||||
res.accept(self)
|
||||
|
||||
|
||||
def visit_Command(self, msg):
|
||||
res = TextMessage("!%s%s%s" % (msg.cmd,
|
||||
" " if len(msg.args) else "",
|
||||
" ".join(msg.args)),
|
||||
server=msg.server, date=msg.date,
|
||||
to=msg.to, frm=msg.frm)
|
||||
res.accept(self)
|
||||
|
||||
|
||||
def visit_OwnerCommand(self, msg):
|
||||
res = TextMessage("`%s%s%s" % (msg.cmd,
|
||||
" " if len(msg.args) else "",
|
||||
" ".join(msg.args)),
|
||||
server=msg.server, date=msg.date,
|
||||
to=msg.to, frm=msg.frm)
|
||||
res.accept(self)
|
0
message/printer/__init__.py
Normal file
0
message/printer/__init__.py
Normal file
25
message/visitor.py
Normal file
25
message/visitor.py
Normal file
@ -0,0 +1,25 @@
|
||||
# -*- 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 AbstractVisitor:
|
||||
|
||||
def visit(self, obj):
|
||||
"""Visit a node"""
|
||||
method_name = "visit_%s" % obj.__class__.__name__
|
||||
method = getattr(self, method_name)
|
||||
return method(obj)
|
@ -5,8 +5,10 @@
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
import shlex
|
||||
|
||||
from hooks import hook
|
||||
from message import TextMessage, Command
|
||||
|
||||
nemubotversion = 3.4
|
||||
|
||||
@ -34,10 +36,8 @@ def set_variable(name, value, creator):
|
||||
DATAS.getNode("variables").addChild(var)
|
||||
|
||||
def get_variable(name, msg=None):
|
||||
if name == "sender":
|
||||
return msg.sender
|
||||
elif name == "nick":
|
||||
return msg.nick
|
||||
if name == "sender" or name == "from" or name == "nick":
|
||||
return msg.frm
|
||||
elif name == "chan" or name == "channel":
|
||||
return msg.channel
|
||||
elif name == "date":
|
||||
@ -141,35 +141,25 @@ def replace_variables(cnt, msg=None):
|
||||
return " ".join(cnt)
|
||||
|
||||
|
||||
@hook("all_post")
|
||||
def treat_variables(res):
|
||||
for i in range(0, len(res.messages)):
|
||||
if isinstance(res.messages[i], list):
|
||||
res.messages[i] = replace_variables(", ".join(res.messages[i]), res)
|
||||
else:
|
||||
res.messages[i] = replace_variables(res.messages[i], res)
|
||||
return res
|
||||
|
||||
@hook("pre_PRIVMSG_cmd")
|
||||
@hook("pre_Command")
|
||||
def treat_alias(msg):
|
||||
if msg.cmds[0] in DATAS.getNode("aliases").index:
|
||||
oldcmd = msg.cmds[0]
|
||||
msg.text = msg.text.replace(msg.cmds[0],
|
||||
DATAS.getNode("aliases").index[msg.cmds[0]]["origin"], 1)
|
||||
|
||||
msg.qual = "def"
|
||||
msg.parse_content()
|
||||
if msg.cmd in DATAS.getNode("aliases").index:
|
||||
txt = DATAS.getNode("aliases").index[msg.cmd]["origin"]
|
||||
# TODO: for legacy compatibility
|
||||
if txt[0] == "!":
|
||||
txt = txt[1:]
|
||||
try:
|
||||
args = shlex.split(txt)
|
||||
except ValueError:
|
||||
args = txt.split(' ')
|
||||
nmsg = Command(args[0], args[1:] + msg.args, **msg.export_args())
|
||||
|
||||
# Avoid infinite recursion
|
||||
if oldcmd == msg.cmds[0]:
|
||||
return msg
|
||||
else:
|
||||
return treat_alias(msg)
|
||||
if msg.cmd != nmsg.cmd:
|
||||
return nmsg
|
||||
|
||||
return msg
|
||||
|
||||
else:
|
||||
msg.text = replace_variables(msg.text, msg)
|
||||
msg.parse_content()
|
||||
return msg
|
||||
|
||||
@hook("ask_default")
|
||||
def parseask(msg):
|
||||
|
@ -71,8 +71,7 @@ def start_countdown(msg):
|
||||
strnd["server"] = msg.server
|
||||
strnd["channel"] = msg.channel
|
||||
strnd["proprio"] = msg.nick
|
||||
strnd["sender"] = msg.sender
|
||||
strnd["start"] = msg.tags["time"]
|
||||
strnd["start"] = msg.date
|
||||
strnd["name"] = msg.cmds[1]
|
||||
DATAS.addChild(strnd)
|
||||
|
||||
@ -84,7 +83,7 @@ def start_countdown(msg):
|
||||
result3 = re.match("(.*[^0-9])?([0-2]?[0-9]):([0-5]?[0-9])(:([0-5]?[0-9]))?", msg.cmds[2])
|
||||
if result2 is not None or result3 is not None:
|
||||
try:
|
||||
now = msg.tags["time"]
|
||||
now = msg.date
|
||||
if result3 is None or result3.group(5) is None: sec = 0
|
||||
else: sec = int(result3.group(5))
|
||||
if result3 is None or result3.group(3) is None: minu = 0
|
||||
@ -109,7 +108,7 @@ def start_countdown(msg):
|
||||
raise IRCException("Mauvais format de date pour l'événement %s. Il n'a pas été créé." % msg.cmds[1])
|
||||
|
||||
elif result1 is not None and len(result1) > 0:
|
||||
strnd["end"] = msg.tags["time"]
|
||||
strnd["end"] = msg.date
|
||||
for (t, g) in result1:
|
||||
if g is None or g == "" or g == "m" or g == "M":
|
||||
strnd["end"] += timedelta(minutes=int(t))
|
||||
@ -131,11 +130,13 @@ def start_countdown(msg):
|
||||
save()
|
||||
if "end" in strnd:
|
||||
return Response("%s commencé le %s et se terminera le %s." %
|
||||
(msg.cmds[1], msg.tags["time"].strftime("%A %d %B %Y à %H:%M:%S"),
|
||||
strnd.getDate("end").strftime("%A %d %B %Y à %H:%M:%S")))
|
||||
(msg.cmds[1], msg.date.strftime("%A %d %B %Y à %H:%M:%S"),
|
||||
strnd.getDate("end").strftime("%A %d %B %Y à %H:%M:%S")),
|
||||
nick=msg.frm)
|
||||
else:
|
||||
return Response("%s commencé le %s"% (msg.cmds[1],
|
||||
msg.tags["time"].strftime("%A %d %B %Y à %H:%M:%S")))
|
||||
msg.date.strftime("%A %d %B %Y à %H:%M:%S")),
|
||||
nick=msg.frm)
|
||||
|
||||
@hook("cmd_hook", "end")
|
||||
@hook("cmd_hook", "forceend")
|
||||
@ -145,7 +146,7 @@ def end_countdown(msg):
|
||||
|
||||
if msg.cmds[1] in DATAS.index:
|
||||
if DATAS.index[msg.cmds[1]]["proprio"] == msg.nick or (msg.cmds[0] == "forceend" and msg.is_owner):
|
||||
duration = countdown(msg.tags["time"] - DATAS.index[msg.cmds[1]].getDate("start"))
|
||||
duration = countdown(msg.date - DATAS.index[msg.cmds[1]].getDate("start"))
|
||||
del_event(DATAS.index[msg.cmds[1]]["id"])
|
||||
DATAS.delChild(DATAS.index[msg.cmds[1]])
|
||||
save()
|
||||
@ -182,9 +183,9 @@ def parseanswer(msg):
|
||||
|
||||
if DATAS.index[msg.cmds[0]].name == "strend":
|
||||
if DATAS.index[msg.cmds[0]].hasAttribute("end"):
|
||||
res.append_message("%s commencé il y a %s et se terminera dans %s." % (msg.cmds[0], countdown(msg.tags["time"] - DATAS.index[msg.cmds[0]].getDate("start")), countdown(DATAS.index[msg.cmds[0]].getDate("end") - msg.tags["time"])))
|
||||
res.append_message("%s commencé il y a %s et se terminera dans %s." % (msg.cmds[0], countdown(msg.date - DATAS.index[msg.cmds[0]].getDate("start")), countdown(DATAS.index[msg.cmds[0]].getDate("end") - msg.date)))
|
||||
else:
|
||||
res.append_message("%s commencé il y a %s." % (msg.cmds[0], countdown(msg.tags["time"] - DATAS.index[msg.cmds[0]].getDate("start"))))
|
||||
res.append_message("%s commencé il y a %s." % (msg.cmds[0], countdown(msg.date - DATAS.index[msg.cmds[0]].getDate("start"))))
|
||||
else:
|
||||
res.append_message(countdown_format(DATAS.index[msg.cmds[0]].getDate("start"), DATAS.index[msg.cmds[0]]["msg_before"], DATAS.index[msg.cmds[0]]["msg_after"]))
|
||||
return res
|
||||
@ -222,7 +223,6 @@ def parseask(msg):
|
||||
evt["server"] = msg.server
|
||||
evt["channel"] = msg.channel
|
||||
evt["proprio"] = msg.nick
|
||||
evt["sender"] = msg.sender
|
||||
evt["name"] = name.group(1)
|
||||
evt["start"] = extDate
|
||||
evt["msg_after"] = msg_after
|
||||
@ -237,12 +237,12 @@ def parseask(msg):
|
||||
evt["server"] = msg.server
|
||||
evt["channel"] = msg.channel
|
||||
evt["proprio"] = msg.nick
|
||||
evt["sender"] = msg.sender
|
||||
evt["name"] = name.group(1)
|
||||
evt["msg_before"] = texts.group (2)
|
||||
DATAS.addChild(evt)
|
||||
save()
|
||||
return Response("Nouvelle commande !%s ajoutée avec succès." % name.group(1))
|
||||
return Response("Nouvelle commande !%s ajoutée avec succès." % name.group(1),
|
||||
channel=msg.channel)
|
||||
|
||||
else:
|
||||
raise IRCException("Veuillez indiquez les messages d'attente et d'après événement entre guillemets.")
|
||||
|
@ -107,7 +107,7 @@ def search(site, term, ssl=False):
|
||||
web.striphtml(itm["snippet"].replace("<span class='searchmatch'>", "\x03\x02").replace("</span>", "\x03\x02")))
|
||||
|
||||
|
||||
@hook("in_PRIVMSG_cmd", "mediawiki")
|
||||
@hook("cmd_hook", "mediawiki")
|
||||
def cmd_mediawiki(msg):
|
||||
"""Read an article on a MediaWiki"""
|
||||
if len(msg.cmds) < 3:
|
||||
@ -118,7 +118,7 @@ def cmd_mediawiki(msg):
|
||||
channel=msg.receivers)
|
||||
|
||||
|
||||
@hook("in_PRIVMSG_cmd", "search_mediawiki")
|
||||
@hook("cmd_hook", "search_mediawiki")
|
||||
def cmd_srchmediawiki(msg):
|
||||
"""Search an article on a MediaWiki"""
|
||||
if len(msg.cmds) < 3:
|
||||
@ -132,7 +132,7 @@ def cmd_srchmediawiki(msg):
|
||||
return res
|
||||
|
||||
|
||||
@hook("in_PRIVMSG_cmd", "wikipedia")
|
||||
@hook("cmd_hook", "wikipedia")
|
||||
def cmd_wikipedia(msg):
|
||||
if len(msg.cmds) < 3:
|
||||
raise IRCException("indicate a lang and a term to search")
|
||||
|
100
modules/more.py
100
modules/more.py
@ -20,6 +20,7 @@
|
||||
|
||||
import logging
|
||||
|
||||
from message import TextMessage, DirectAsk
|
||||
from hooks import hook
|
||||
|
||||
nemubotversion = 3.4
|
||||
@ -29,8 +30,7 @@ logger = logging.getLogger("nemubot.response")
|
||||
class Response:
|
||||
def __init__(self, message=None, channel=None, nick=None, server=None,
|
||||
nomore="No more message", title=None, more="(suite) ",
|
||||
count=None, ctcp=False, shown_first_count=-1,
|
||||
line_treat=None):
|
||||
count=None, shown_first_count=-1, line_treat=None):
|
||||
self.nomore = nomore
|
||||
self.more = more
|
||||
self.line_treat = line_treat
|
||||
@ -38,12 +38,10 @@ class Response:
|
||||
self.server = server
|
||||
self.messages = list()
|
||||
self.alone = True
|
||||
self.is_ctcp = ctcp
|
||||
if message is not None:
|
||||
self.append_message(message, shown_first_count=shown_first_count)
|
||||
self.elt = 0 # Next element to display
|
||||
|
||||
self.sender = None
|
||||
self.channel = channel
|
||||
self.nick = nick
|
||||
self.count = count
|
||||
@ -59,14 +57,6 @@ class Response:
|
||||
else:
|
||||
return [ self.channel ]
|
||||
|
||||
def set_sender(self, sender):
|
||||
if sender is None or sender.find("!") < 0:
|
||||
if sender is not None:
|
||||
logger.warn("Bad sender provided in Response, it will be ignored.", stack_info=True)
|
||||
self.sender = None
|
||||
else:
|
||||
self.sender = sender
|
||||
|
||||
def append_message(self, message, title=None, shown_first_count=-1):
|
||||
if type(message) is str:
|
||||
message = message.split('\n')
|
||||
@ -120,13 +110,19 @@ class Response:
|
||||
if len(self.rawtitle) <= 0:
|
||||
self.rawtitle = None
|
||||
|
||||
def treat_ctcp(self, content):
|
||||
if self.is_ctcp:
|
||||
return "\x01" + content + "\x01"
|
||||
else:
|
||||
return content
|
||||
|
||||
def get_message(self):
|
||||
def accept(self, visitor):
|
||||
visitor.visit(self.next_response())
|
||||
|
||||
|
||||
def next_response(self, maxlen=440):
|
||||
if self.nick:
|
||||
return DirectAsk(self.nick, self.get_message(maxlen - len(self.nick) - 2), server=None, to=self.receivers)
|
||||
else:
|
||||
return TextMessage(self.get_message(maxlen), server=None, to=self.receivers)
|
||||
|
||||
|
||||
def get_message(self, maxlen):
|
||||
if self.alone and len(self.messages) > 1:
|
||||
self.alone = False
|
||||
|
||||
@ -134,7 +130,7 @@ class Response:
|
||||
if hasattr(self.nomore, '__call__'):
|
||||
res = self.nomore(self)
|
||||
if res is None:
|
||||
return self.treat_ctcp("No more message")
|
||||
return "No more message"
|
||||
elif isinstance(res, Response):
|
||||
self.__dict__ = res.__dict__
|
||||
elif isinstance(res, list):
|
||||
@ -145,62 +141,59 @@ class Response:
|
||||
raise Exception("Type returned by nomore (%s) is not handled here." % type(res))
|
||||
return self.get_message()
|
||||
else:
|
||||
return self.treat_ctcp(self.nomore)
|
||||
return self.nomore
|
||||
|
||||
if self.line_treat is not None and self.elt == 0:
|
||||
self.messages[0] = self.line_treat(self.messages[0]).replace("\n", " ").strip()
|
||||
|
||||
msg = ""
|
||||
if self.channel is not None and self.nick is not None:
|
||||
msg += self.nick + ": "
|
||||
|
||||
if self.title is not None:
|
||||
if self.elt > 0:
|
||||
msg += self.title + " " + self.more + ": "
|
||||
else:
|
||||
msg += self.title + ": "
|
||||
|
||||
if self.elt > 0:
|
||||
elif self.elt > 0:
|
||||
msg += "[…] "
|
||||
|
||||
elts = self.messages[0][self.elt:]
|
||||
if isinstance(elts, list):
|
||||
for e in elts:
|
||||
if len(msg) + len(e) > 430:
|
||||
if len(msg) + len(e) > maxlen - 3:
|
||||
msg += "[…]"
|
||||
self.alone = False
|
||||
return self.treat_ctcp(msg)
|
||||
return msg
|
||||
else:
|
||||
msg += e + ", "
|
||||
self.elt += 1
|
||||
self.pop()
|
||||
return self.treat_ctcp(msg[:len(msg)-2])
|
||||
return msg[:len(msg)-2]
|
||||
|
||||
else:
|
||||
if len(elts.encode()) <= 432:
|
||||
if len(elts.encode()) <= maxlen:
|
||||
self.pop()
|
||||
if self.count is not None:
|
||||
return self.treat_ctcp(msg + elts + (self.count % len(self.messages)))
|
||||
return msg + elts + (self.count % len(self.messages))
|
||||
else:
|
||||
return self.treat_ctcp(msg + elts)
|
||||
return msg + elts
|
||||
|
||||
else:
|
||||
words = elts.split(' ')
|
||||
|
||||
if len(words[0].encode()) > 432 - len(msg.encode()):
|
||||
self.elt += 432 - len(msg.encode())
|
||||
return self.treat_ctcp(msg + elts[:self.elt] + "[…]")
|
||||
if len(words[0].encode()) > maxlen - len(msg.encode()):
|
||||
self.elt += maxlen - len(msg.encode())
|
||||
return msg + elts[:self.elt] + "[…]"
|
||||
|
||||
for w in words:
|
||||
if len(msg.encode()) + len(w.encode()) > 431:
|
||||
if len(msg.encode()) + len(w.encode()) >= maxlen:
|
||||
msg += "[…]"
|
||||
self.alone = False
|
||||
return self.treat_ctcp(msg)
|
||||
return msg
|
||||
else:
|
||||
msg += w + " "
|
||||
self.elt += len(w) + 1
|
||||
self.pop()
|
||||
return self.treat_ctcp(msg)
|
||||
return msg
|
||||
|
||||
|
||||
SERVERS = dict()
|
||||
@ -209,18 +202,17 @@ SERVERS = dict()
|
||||
def parseresponse(res):
|
||||
# TODO: handle inter-bot communication NOMORE
|
||||
# TODO: check that the response is not the one already saved
|
||||
rstr = res.get_message()
|
||||
|
||||
if not res.alone:
|
||||
if isinstance(res, Response):
|
||||
if res.server not in SERVERS:
|
||||
SERVERS[res.server] = dict()
|
||||
for receiver in res.receivers:
|
||||
SERVERS[res.server][receiver] = res
|
||||
|
||||
ret = list()
|
||||
for channel in res.receivers:
|
||||
ret.append("%s %s :%s" % ("NOTICE" if res.is_ctcp else "PRIVMSG", channel, rstr))
|
||||
return ret
|
||||
if receiver in SERVERS[res.server]:
|
||||
nw, bk = SERVERS[res.server][receiver]
|
||||
else:
|
||||
nw, bk = None, None
|
||||
if nw != res:
|
||||
SERVERS[res.server][receiver] = (res, bk)
|
||||
return res
|
||||
|
||||
|
||||
@hook("cmd_hook", "more")
|
||||
@ -228,9 +220,13 @@ def cmd_more(msg):
|
||||
"""Display next chunck of the message"""
|
||||
res = list()
|
||||
if msg.server in SERVERS:
|
||||
for receiver in msg.receivers:
|
||||
for receiver in msg.to_response:
|
||||
if receiver in SERVERS[msg.server]:
|
||||
res.append(SERVERS[msg.server][receiver])
|
||||
nw, bk = SERVERS[msg.server][receiver]
|
||||
if nw is not None and not nw.alone:
|
||||
bk = nw
|
||||
SERVERS[msg.server][receiver] = None, bk
|
||||
res.append(bk)
|
||||
return res
|
||||
|
||||
|
||||
@ -239,8 +235,12 @@ def cmd_next(msg):
|
||||
"""Display the next information include in the message"""
|
||||
res = list()
|
||||
if msg.server in SERVERS:
|
||||
for receiver in msg.receivers:
|
||||
for receiver in msg.to_response:
|
||||
if receiver in SERVERS[msg.server]:
|
||||
SERVERS[msg.server][receiver].pop()
|
||||
res.append(SERVERS[msg.server][receiver])
|
||||
nw, bk = SERVERS[msg.server][receiver]
|
||||
if nw is not None and not nw.alone:
|
||||
bk = nw
|
||||
SERVERS[msg.server][receiver] = None, bk
|
||||
bk.pop()
|
||||
res.append(bk)
|
||||
return res
|
||||
|
@ -56,7 +56,7 @@ def del_site(msg):
|
||||
site = DATAS.index[url]
|
||||
for a in site.getNodes("alert"):
|
||||
if a["channel"] == msg.channel:
|
||||
if not (msg.sender == a["sender"] or msg.is_owner):
|
||||
if not (msg.frm == a["nick"] or msg.is_owner):
|
||||
raise IRCException("vous ne pouvez pas supprimer cette URL.")
|
||||
site.delChild(a)
|
||||
if not site.hasNode("alert"):
|
||||
@ -82,7 +82,7 @@ def add_site(msg, diffType="diff"):
|
||||
raise IRCException("je ne peux pas surveiller cette URL")
|
||||
|
||||
alert = ModuleState("alert")
|
||||
alert["sender"] = msg.sender
|
||||
alert["nick"] = msg.nick
|
||||
alert["server"] = msg.server
|
||||
alert["channel"] = msg.channel
|
||||
alert["message"] = "{url} a changé !"
|
||||
|
@ -31,7 +31,6 @@ def start_watch(msg):
|
||||
w["server"] = msg.server
|
||||
w["channel"] = msg.channel
|
||||
w["proprio"] = msg.nick
|
||||
w["sender"] = msg.sender
|
||||
w["start"] = datetime.now(timezone.utc)
|
||||
DATAS.addChild(w)
|
||||
save()
|
||||
|
@ -8,11 +8,10 @@ from urllib.parse import quote
|
||||
from urllib.request import urlopen
|
||||
|
||||
from hooks import hook
|
||||
from message import TextMessage
|
||||
|
||||
nemubotversion = 3.4
|
||||
|
||||
from more import Response
|
||||
|
||||
def help_full():
|
||||
return "!ycc [<url>]: with an argument, reduce the given <url> thanks to ycc.fr; without argument, reduce the last URL said on the current channel."
|
||||
|
||||
@ -22,24 +21,28 @@ def gen_response(res, msg, srv):
|
||||
if res is None:
|
||||
raise IRCException("la situation est embarassante, il semblerait que YCC soit down :(")
|
||||
elif isinstance(res, str):
|
||||
return Response("URL pour %s : %s" % (srv, res), msg.channel)
|
||||
return TextMessage("URL pour %s : %s" % (srv, res), server=None, to=msg.to_response)
|
||||
else:
|
||||
raise IRCException("mauvaise URL : %s" % srv)
|
||||
|
||||
@hook("cmd_hook", "ycc")
|
||||
def cmd_ycc(msg):
|
||||
minify = list()
|
||||
|
||||
if len(msg.cmds) == 1:
|
||||
global LAST_URLS
|
||||
if msg.channel in LAST_URLS and len(LAST_URLS[msg.channel]) > 0:
|
||||
msg.cmds.append(LAST_URLS[msg.channel].pop())
|
||||
minify.append(LAST_URLS[msg.channel].pop())
|
||||
else:
|
||||
raise IRCException("je n'ai pas d'autre URL à réduire.")
|
||||
|
||||
if len(msg.cmds) > 5:
|
||||
raise IRCException("je ne peux pas réduire autant d'URL d'un seul coup.")
|
||||
else:
|
||||
minify += msg.cmds[1:]
|
||||
|
||||
res = list()
|
||||
for url in msg.cmds[1:]:
|
||||
for url in minify:
|
||||
o = urlparse(url, "http")
|
||||
if o.scheme != "":
|
||||
snd_url = "http://ycc.fr/redirection/create/" + quote(url, "/:%@&=?")
|
||||
|
@ -16,6 +16,7 @@
|
||||
# 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/>.
|
||||
|
||||
import imp
|
||||
import logging
|
||||
import os
|
||||
import xmlparser
|
||||
@ -94,7 +95,8 @@ def load_file(filename, context):
|
||||
|
||||
# Unexisting file, assume a name was passed, import the module!
|
||||
else:
|
||||
__import__(filename)
|
||||
tt = __import__(filename)
|
||||
imp.reload(tt)
|
||||
|
||||
|
||||
def load(toks, context, prompt):
|
||||
|
131
server/IRC.py
131
server/IRC.py
@ -23,14 +23,15 @@ import time
|
||||
import shlex
|
||||
|
||||
from channel import Channel
|
||||
from message import Message
|
||||
import server
|
||||
import message
|
||||
from message.printer.IRC import IRC as IRCPrinter
|
||||
from server.socket import SocketServer
|
||||
|
||||
class IRCServer(SocketServer):
|
||||
class IRC(SocketServer):
|
||||
|
||||
def __init__(self, node, nick, owner, realname):
|
||||
self.id = nick + "@" + node["host"] + ":" + node["port"]
|
||||
self.printer = IRCPrinter
|
||||
SocketServer.__init__(self,
|
||||
node["host"],
|
||||
node["port"],
|
||||
@ -60,18 +61,18 @@ class IRCServer(SocketServer):
|
||||
# Register CTCP capabilities
|
||||
self.ctcp_capabilities = dict()
|
||||
|
||||
def _ctcp_clientinfo(msg):
|
||||
def _ctcp_clientinfo(msg, cmds):
|
||||
"""Response to CLIENTINFO CTCP message"""
|
||||
return _ctcp_response(" ".join(self.ctcp_capabilities.keys()))
|
||||
return " ".join(self.ctcp_capabilities.keys())
|
||||
|
||||
def _ctcp_dcc(msg):
|
||||
def _ctcp_dcc(msg, cmds):
|
||||
"""Response to DCC CTCP message"""
|
||||
try:
|
||||
ip = srv.toIP(int(msg.cmds[3]))
|
||||
port = int(msg.cmds[4])
|
||||
ip = srv.toIP(int(cmds[3]))
|
||||
port = int(cmds[4])
|
||||
conn = DCC(srv, msg.sender)
|
||||
except:
|
||||
return _ctcp_response("ERRMSG invalid parameters provided as DCC CTCP request")
|
||||
return "ERRMSG invalid parameters provided as DCC CTCP request"
|
||||
|
||||
self.logger.info("Receive DCC connection request from %s to %s:%d", conn.sender, ip, port)
|
||||
|
||||
@ -80,27 +81,20 @@ class IRCServer(SocketServer):
|
||||
conn.send_dcc("Hello %s!" % conn.nick)
|
||||
else:
|
||||
self.logger.error("DCC: unable to connect to %s:%d", ip, port)
|
||||
return _ctcp_response("ERRMSG unable to connect to %s:%d" % (ip, port))
|
||||
return "ERRMSG unable to connect to %s:%d" % (ip, port)
|
||||
|
||||
import bot
|
||||
|
||||
self.ctcp_capabilities["ACTION"] = lambda msg: print ("ACTION receive: %s" % msg.text)
|
||||
self.ctcp_capabilities["ACTION"] = lambda msg, cmds: print ("ACTION receive: %s" % cmds)
|
||||
self.ctcp_capabilities["CLIENTINFO"] = _ctcp_clientinfo
|
||||
#self.ctcp_capabilities["DCC"] = _ctcp_dcc
|
||||
self.ctcp_capabilities["FINGER"] = lambda msg: _ctcp_response(
|
||||
"VERSION nemubot v%s" % bot.__version__)
|
||||
self.ctcp_capabilities["NEMUBOT"] = lambda msg: _ctcp_response(
|
||||
"NEMUBOT %s" % bot.__version__)
|
||||
self.ctcp_capabilities["PING"] = lambda msg: _ctcp_response(
|
||||
"PING %s" % " ".join(msg.cmds[1:]))
|
||||
self.ctcp_capabilities["SOURCE"] = lambda msg: _ctcp_response(
|
||||
"SOURCE https://github.com/nemunaire/nemubot")
|
||||
self.ctcp_capabilities["TIME"] = lambda msg: _ctcp_response(
|
||||
"TIME %s" % (datetime.now()))
|
||||
self.ctcp_capabilities["USERINFO"] = lambda msg: _ctcp_response(
|
||||
"USERINFO %s" % self.realname)
|
||||
self.ctcp_capabilities["VERSION"] = lambda msg: _ctcp_response(
|
||||
"VERSION nemubot v%s" % bot.__version__)
|
||||
self.ctcp_capabilities["FINGER"] = lambda msg, cmds: "VERSION nemubot v%s" % bot.__version__
|
||||
self.ctcp_capabilities["NEMUBOT"] = lambda msg, cmds: "NEMUBOT %s" % bot.__version__
|
||||
self.ctcp_capabilities["PING"] = lambda msg, cmds: "PING %s" % " ".join(cmds[1:])
|
||||
self.ctcp_capabilities["SOURCE"] = lambda msg, cmds: "SOURCE https://github.com/nemunaire/nemubot"
|
||||
self.ctcp_capabilities["TIME"] = lambda msg, cmds: "TIME %s" % (datetime.now())
|
||||
self.ctcp_capabilities["USERINFO"] = lambda msg, cmds: "USERINFO %s" % self.realname
|
||||
self.ctcp_capabilities["VERSION"] = lambda msg, cmds: "VERSION nemubot v%s" % bot.__version__
|
||||
|
||||
self.logger.debug("CTCP capabilities setup: %s", ", ".join(self.ctcp_capabilities))
|
||||
|
||||
@ -190,6 +184,18 @@ class IRCServer(SocketServer):
|
||||
self.write("JOIN " + msg.decode(msg.params[1]))
|
||||
self.hookscmd["INVITE"] = _on_invite
|
||||
|
||||
# Handle CTCP requests
|
||||
def _on_ctcp(msg):
|
||||
if len(msg.params) != 2 or not msg.is_ctcp: return
|
||||
cmds = msg.decode(msg.params[1][1:len(msg.params[1])-1]).split(' ')
|
||||
if cmds[0] in self.ctcp_capabilities:
|
||||
res = self.ctcp_capabilities[cmds[0]](msg, cmds)
|
||||
else:
|
||||
res = "ERRMSG Unknown or unimplemented CTCP request"
|
||||
if res is not None:
|
||||
self.write("NOTICE %s :\x01%s\x01" % (msg.nick, res))
|
||||
self.hookscmd["PRIVMSG"] = _on_ctcp
|
||||
|
||||
|
||||
def _open(self):
|
||||
if SocketServer._open(self):
|
||||
@ -215,31 +221,9 @@ class IRCServer(SocketServer):
|
||||
if msg.cmd in self.hookscmd:
|
||||
self.hookscmd[msg.cmd](msg)
|
||||
|
||||
else:
|
||||
mes = msg.to_message()
|
||||
mes.raw = msg.raw
|
||||
|
||||
if hasattr(mes, "receivers"):
|
||||
# Private message: prepare response
|
||||
for i in range(len(mes.receivers)):
|
||||
if mes.receivers[i] == self.nick:
|
||||
mes.receivers[i] = mes.nick
|
||||
|
||||
if (mes.cmd == "PRIVMSG" or mes.cmd == "NOTICE") and mes.is_ctcp:
|
||||
if mes.cmds[0] in self.ctcp_capabilities:
|
||||
res = self.ctcp_capabilities[mes.cmds[0]](mes)
|
||||
else:
|
||||
res = _ctcp_response("ERRMSG Unknown or unimplemented CTCP request")
|
||||
if res is not None:
|
||||
res = res % mes.nick
|
||||
self.write(res)
|
||||
|
||||
else:
|
||||
yield mes
|
||||
|
||||
|
||||
def _ctcp_response(msg):
|
||||
return "NOTICE %%s :\x01%s\x01" % msg
|
||||
mes = msg.to_message(self)
|
||||
if mes is not None:
|
||||
yield mes
|
||||
|
||||
|
||||
mgx = re.compile(b'''^(?:@(?P<tags>[^ ]+)\ )?
|
||||
@ -303,6 +287,11 @@ class IRCMessage:
|
||||
self.tags[key] = value
|
||||
|
||||
|
||||
@property
|
||||
def is_ctcp(self):
|
||||
return self.cmd == "PRIVMSG" and len(self.params) == 2 and len(self.params[1]) > 1 and (self.params[1][0] == 0x01 or self.params[1][1] == 0x01)
|
||||
|
||||
|
||||
def decode(self, s):
|
||||
"""Decode the content string usign a specific encoding"""
|
||||
if isinstance(s, bytes):
|
||||
@ -313,8 +302,6 @@ class IRCMessage:
|
||||
return s
|
||||
|
||||
|
||||
def to_message(self):
|
||||
return Message(self)
|
||||
|
||||
def to_irc_string(self, client=True):
|
||||
"""Pretty print the message to close to original input string
|
||||
@ -336,3 +323,43 @@ class IRCMessage:
|
||||
res += " :" + self.decode(self.params[-1])
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def to_message(self, srv):
|
||||
if self.cmd == "PRIVMSG" or self.cmd == "NOTICE":
|
||||
|
||||
receivers = self.decode(self.params[0]).split(',')
|
||||
|
||||
common_args = {
|
||||
"server": srv.id,
|
||||
"date": self.tags["time"],
|
||||
"to": receivers,
|
||||
"to_response": [r if r != srv.nick else self.nick for r in receivers],
|
||||
"frm": self.nick
|
||||
}
|
||||
|
||||
# If CTCP, remove 0x01
|
||||
if self.is_ctcp:
|
||||
text = self.decode(self.params[1][1:len(self.params[1])-1])
|
||||
else:
|
||||
text = self.decode(self.params[1])
|
||||
|
||||
if len(text) > 1 and text[0] == '!':
|
||||
text = text[1:].strip()
|
||||
|
||||
# Split content by words
|
||||
try:
|
||||
args = shlex.split(text)
|
||||
except ValueError:
|
||||
args = text.split(' ')
|
||||
|
||||
return message.Command(cmd=args[0], args=args[1:], **common_args)
|
||||
|
||||
elif text.find(srv.nick) == 0 and len(text) > len(srv.nick) + 2 and text[len(srv.nick)] == ":":
|
||||
text = text[len(srv.nick) + 1:].strip()
|
||||
return message.DirectAsk(designated=srv.nick, message=text, **common_args)
|
||||
|
||||
else:
|
||||
return message.TextMessage(message=text, **common_args)
|
||||
|
||||
return None
|
||||
|
@ -89,3 +89,18 @@ class AbstractServer(io.IOBase):
|
||||
def exception(self):
|
||||
"""Exception occurs in fd"""
|
||||
print("Unhandle file descriptor exception on server " + self.id)
|
||||
|
||||
|
||||
def send_response(self, response):
|
||||
"""Send a formated Message class"""
|
||||
if response is None:
|
||||
return
|
||||
|
||||
elif isinstance(response, list):
|
||||
for r in response:
|
||||
self.send_response(r)
|
||||
|
||||
else:
|
||||
vprnt = self.printer()
|
||||
response.accept(vprnt)
|
||||
self.write(vprnt.pp)
|
||||
|
Loading…
x
Reference in New Issue
Block a user