From 5e52c4dbbbcd5aa6c89ee5c6435f112ccfadf227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9munaire?= Date: Thu, 30 Aug 2012 15:29:11 +0200 Subject: [PATCH] Introduce Response class and a new way to send message to user Remove old send_* in Message class !more is now a built-in command --- consumer.py | 11 +++- message.py | 170 ++++++++++++++++++++++++---------------------------- response.py | 99 ++++++++++++++++++++++++++++++ server.py | 28 +++++---- 4 files changed, 204 insertions(+), 104 deletions(-) create mode 100644 response.py diff --git a/consumer.py b/consumer.py index aa3e7f2..9533ea0 100644 --- a/consumer.py +++ b/consumer.py @@ -37,13 +37,22 @@ class Consumer(threading.Thread): # Create, parse and treat the message try: msg = Message(srv, raw, time, prvt) - msg.treat(self.context.hooks) + res = msg.treat(self.context.hooks) except: print ("\033[1;31mERROR:\033[0m occurred during the " "processing of the message: %s" % raw) exc_type, exc_value, exc_traceback = sys.exc_info() traceback.print_exception(exc_type, exc_value, exc_traceback) + return + + # Send message + if res is not None: + if isinstance(res, list): + for r in res: + srv.send_response(r) + else: + srv.send_response(res) except queue.Empty: pass diff --git a/message.py b/message.py index 87f635f..8382d39 100644 --- a/message.py +++ b/message.py @@ -24,6 +24,7 @@ import time import credits from credits import Credits from DCC import DCC +from response import Response import xmlparser CREDITS = {} @@ -121,97 +122,73 @@ class Message: @property def is_owner(self): - return self.nick == self.srv.owner - - - def send_msg(self, channel, msg, cmd = "PRIVMSG", endl = "\r\n"): - if CREDITS[self.realname].speak(): - self.srv.send_msg_verified (self.sender, channel, msg, cmd, endl) - - def send_global(self, msg, cmd = "PRIVMSG", endl = "\r\n"): - if CREDITS[self.realname].speak(): - self.srv.send_global (msg, cmd, endl) - - def send_chn(self, msg): - """Send msg on the same channel as receive message""" - if (self.srv.isDCC() and self.channel == self.srv.nick) or CREDITS[self.realname].speak(): - if self.channel == self.srv.nick: - self.send_snd (msg) - else: - self.srv.send_msg (self.channel, msg) - - def send_snd(self, msg): - """Send msg to the person who send the original message""" - if self.srv.isDCC(self.sender) or CREDITS[self.realname].speak(): - self.srv.send_msg_usr (self.sender, msg) - + return self.nick == self.srv.owner def authorize(self): - if self.srv.isDCC(self.sender): - return True - elif self.realname not in CREDITS: - CREDITS[self.realname] = Credits(self.realname) - elif self.content[0] == '`': - return True - elif not CREDITS[self.realname].ask(): - return False - return self.srv.accepted_channel(self.channel) + """Is nemubot listening for the sender on this channel?""" + if self.srv.isDCC(self.sender): + return True + elif self.realname not in CREDITS: + CREDITS[self.realname] = Credits(self.realname) + elif self.content[0] == '`': + return True + elif not CREDITS[self.realname].ask(): + return False + return self.srv.accepted_channel(self.channel) def treat(self, hooks): - if self.cmd == "PING": - self.pong () - elif self.cmd == "PRIVMSG" and self.ctcp: - self.parsectcp () - elif self.cmd == "PRIVMSG" and self.authorize(): - self.parsemsg (hooks) - elif 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) - - - def pong (self): - self.srv.s.send(("PONG %s\r\n" % self.content).encode ()) - + """Parse and treat the message""" + if self.cmd == "PING": + self.srv.send_pong(self.content) + elif self.cmd == "PRIVMSG" and self.ctcp: + self.parsectcp() + elif self.cmd == "PRIVMSG" and self.authorize(): + return self.parsemsg (hooks) + elif 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 def parsectcp(self): - if self.content == '\x01CLIENTINFO\x01': - self.srv.send_ctcp(self.sender, "CLIENTINFO TIME USERINFO VERSION CLIENTINFO") - elif self.content == '\x01TIME\x01': - self.srv.send_ctcp(self.sender, "TIME %s" % (datetime.now())) - elif self.content == '\x01USERINFO\x01': - self.srv.send_ctcp(self.sender, "USERINFO %s" % (self.srv.realname)) - elif self.content == '\x01VERSION\x01': - self.srv.send_ctcp(self.sender, "VERSION nemubot v%s" % self.srv.context.version_txt) - elif self.content[:9] == '\x01DCC CHAT': - words = self.content[1:len(self.content) - 1].split(' ') - ip = self.srv.toIP(int(words[3])) - conn = DCC(self.srv, self.sender) - if conn.accept_user(ip, int(words[4])): - self.srv.dcc_clients[conn.sender] = conn - conn.send_dcc("Hello %s!" % conn.nick) - else: - print ("DCC: unable to connect to %s:%s" % (ip, words[4])) - elif self.content == '\x01NEMUBOT\x01': - self.srv.send_ctcp(self.sender, "NEMUBOT %f" % self.srv.context.version) - elif self.content[:7] != '\x01ACTION': - print (self.content) - self.srv.send_ctcp(self.sender, "ERRMSG Unknown or unimplemented CTCP request") + """Parse CTCP requests""" + if self.content == '\x01CLIENTINFO\x01': + self.srv.send_ctcp(self.sender, "CLIENTINFO TIME USERINFO VERSION CLIENTINFO") + elif self.content == '\x01TIME\x01': + self.srv.send_ctcp(self.sender, "TIME %s" % (datetime.now())) + elif self.content == '\x01USERINFO\x01': + self.srv.send_ctcp(self.sender, "USERINFO %s" % (self.srv.realname)) + elif self.content == '\x01VERSION\x01': + self.srv.send_ctcp(self.sender, "VERSION nemubot v%s" % self.srv.context.version_txt) + elif self.content[:9] == '\x01DCC CHAT': + words = self.content[1:len(self.content) - 1].split(' ') + ip = self.srv.toIP(int(words[3])) + conn = DCC(self.srv, self.sender) + if conn.accept_user(ip, int(words[4])): + self.srv.dcc_clients[conn.sender] = conn + conn.send_dcc("Hello %s!" % conn.nick) + else: + print ("DCC: unable to connect to %s:%s" % (ip, words[4])) + elif self.content == '\x01NEMUBOT\x01': + self.srv.send_ctcp(self.sender, "NEMUBOT %f" % self.srv.context.version) + elif self.content[:7] != '\x01ACTION': + print (self.content) + self.srv.send_ctcp(self.sender, "ERRMSG Unknown or unimplemented CTCP request") def reparsemsg(self): if self.hooks is not None: @@ -230,11 +207,11 @@ class Message: # Treat ping if re.match(".*(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)", messagel) is not None: - self.send_chn ("%s: pong"%(self.nick)) + return Response(message="pong", channel=self.channel, nick=self.nick) # Ask hooks else: - hooks.treat_ask(self) + return hooks.treat_ask(self) #Owner commands elif self.content[0] == '`' and self.sender == self.srv.owner: @@ -281,24 +258,33 @@ class Message: except AttributeError: continue + elif self.cmd[0] == "more": + if self.channel == self.srv.nick: + if self.nick in self.srv.moremessages: + return self.srv.moremessages[self.nick] + else: + if self.channel in self.srv.moremessages: + return self.srv.moremessages[self.channel] + elif self.cmd[0] == "dcctest": print("dcctest for", self.sender) self.srv.send_dcc("Test DCC", self.sender) elif self.cmd[0] == "pvdcctest": print("dcctest") - self.send_snd("Test DCC") + return Response(message="Test DCC", nick=self.nick) elif self.cmd[0] == "dccsendtest": print("dccsendtest") conn = DCC(self.srv, self.sender) conn.send_file("bot_sample.xml") else: - hooks.treat_cmd(self) + return hooks.treat_cmd(self) else: - hooks.treat_answer(self) + res = hooks.treat_answer(self) # Assume the message starts with nemubot: - if self.private: - hooks.treat_ask(self) + if res is None and self.private: + return hooks.treat_ask(self) + return res # def parseOwnerCmd(self, cmd): diff --git a/response.py b/response.py new file mode 100644 index 0000000..3ef275f --- /dev/null +++ b/response.py @@ -0,0 +1,99 @@ +# -*- 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 Response: + def __init__(self, message=None, channel=None, nick=None, server=None, + nomore="No more message", title=None, more="(suite) "): + self.nomore = nomore + self.more = more + self.title = title + self.messages = list() + if message is not None: + self.messages.append(message) + self.elt = 0 # Next element to display + + self.channel = channel + self.nick = nick + self.alone = True + + def append_message(self, message): + self.alone = False + self.messages.append(message) + + @property + def empty(self): + return len(self.messages) <= 0 + + def get_message(self): + if self.alone and len(self.messages) > 1: + self.alone = False + + if self.empty: + return self.nomore + + 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: + msg += "... " + + elts = self.messages[0][self.elt:] + if isinstance(elts, list): + for e in elts: + if len(msg) + len(e) > 430: + msg += "..." + self.alone = False + return msg + else: + msg += e + ", " + self.elt += 1 + self.messages.pop(0) + self.elt = 0 + return msg[:len(msg)-2] + + else: + if len(elts) <= 432: + self.messages.pop(0) + self.elt = 0 + return msg + elts + + else: + words = elts.split(' ') + + if len(words[0]) > 432 - len(msg): + self.elt += 432 - len(msg) + return msg + elts[:self.elt] + "..." + + for w in words: + if len(msg) + len(w) > 431: + msg += "..." + self.alone = False + return msg + else: + msg += w + " " + self.elt += len(w) + 1 + self.messages.pop(0) + self.elt = 0 + return msg diff --git a/server.py b/server.py index f01ff74..b4e1c3e 100644 --- a/server.py +++ b/server.py @@ -46,6 +46,8 @@ class Server(threading.Thread): chan = channel.Channel(chn, self) self.channels[chan.name] = chan + self.moremessages = dict() + threading.Thread.__init__(self) def isDCC(self, to=None): @@ -72,13 +74,6 @@ class Server(threading.Thread): else: return None - @property - def partner(self): - if self.node.hasAttribute("partner"): - return self.node["partner"] - else: - return None - @property def ip(self): """Convert common IP representation to little-endian integer representation""" @@ -112,6 +107,21 @@ class Server(threading.Thread): def id(self): return self.host + ":" + str(self.port) + def send_pong(self, cnt): + self.s.send(("PONG %s\r\n" % cnt).encode ()) + + def send_response(self, res): + if res.channel is not None: + self.send_msg(res.channel, res.get_message()) + + if not res.alone: + self.moremessages[res.channel] = res + elif res.nick is not None: + self.send_msg_usr(res.nick, res.get_message()) + + if not res.alone: + self.moremessages[res.nick] = res + def send_ctcp(self, to, msg, cmd = "NOTICE", endl = "\r\n"): """Send a message as CTCP response""" if msg is not None and to is not None: @@ -143,10 +153,6 @@ class Server(threading.Thread): else: self.s.send (("%s %s :%s%s" % (cmd, channel, line[0:442]+"...", endl)).encode ()) - def send_msg_prtn(self, msg): - """Send a message to partner bot""" - self.send_msg_final(self.partner, msg) - def send_msg_usr(self, user, msg): """Send a message to a user instead of a channel""" if user is not None and user[0] != "#":