diff --git a/modules/speak.py b/modules/speak.py new file mode 100644 index 0000000..b7b9230 --- /dev/null +++ b/modules/speak.py @@ -0,0 +1,132 @@ +# coding=utf-8 + +from datetime import timedelta +from queue import Queue +import re +import subprocess +from threading import Thread + +from hooks import hook +from message import TextMessage +from message.visitor import AbstractVisitor + +nemubotversion = 3.4 + +queue = Queue() +spk_th = None +last = None + +SMILEY = list() +CORRECTIONS = list() + +def load(context): + for smiley in CONF.getNodes("smiley"): + if smiley.hasAttribute("txt") and smiley.hasAttribute("mood"): + SMILEY.append((smiley.getAttribute("txt"), smiley.getAttribute("mood"))) + print ("%d smileys loaded" % len(SMILEY)) + + for correct in CONF.getNodes("correction"): + if correct.hasAttribute("bad") and correct.hasAttribute("good"): + CORRECTIONS.append((" " + (correct.getAttribute("bad") + " "), (" " + correct.getAttribute("good") + " "))) + print ("%d corrections loaded" % len(CORRECTIONS)) + + +class Speaker(Thread): + + def run(self): + global queue, spk_th + while not queue.empty(): + sentence = queue.get_nowait() + lang = "fr" + print_debug(sentence) + subprocess.call(["espeak", "-v", lang, "--", sentence]) + queue.task_done() + + spk_th = None + + +class SpeakerVisitor(AbstractVisitor): + + def __init__(self, last): + self.pp = "" + self.last = last + + + def visit_TextMessage(self, msg): + force = (self.last is None) + + if force or msg.date - self.last.date > timedelta(0, 500): + self.pp += "A %d heure %d : " % (msg.date.hour, msg.date.minute) + force = True + + if force or msg.channel != self.last.channel: + if msg.to_response == msg.to: + self.pp += "sur %s. " % (", ".join(msg.to)) + else: + self.pp += "en message priver. " + + action = False + if msg.message.find("ACTION ") == 0: + self.pp += "%s " % msg.frm + msg.message = msg.message.replace("ACTION ", "") + action = True + for (txt, mood) in SMILEY: + if msg.message.find(txt) >= 0: + self.pp += "%s %s : " % (msg.frm, mood) + msg.message = msg.message.replace(txt, "") + action = True + break + + if not action and (force or msg.frm != self.last.frm): + self.pp += "%s dit : " % msg.frm + + if re.match(".*https?://.*", msg.message) is not None: + msg.message = re.sub(r'https?://([^/]+)[^ ]*', " U.R.L \\1", msg.message) + + self.pp += msg.message + + + def visit_DirectAsk(self, msg): + res = TextMessage("%s: %s" % (msg.designated, msg.message), + server=msg.server, date=msg.date, + to=msg.to, frm=msg.frm) + res.accept(self) + + + def visit_Command(self, msg): + res = TextMessage("Bang %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("Owner Bang %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) + + +@hook("in") +def treat_for_speak(msg): + if not msg.frm_owner: + append_message(msg) + +def append_message(msg): + global last, spk_th + + if msg.message.find("TYPING ") == 0: + return + + vprnt = SpeakerVisitor(last) + msg.accept(vprnt) + queue.put_nowait(vprnt.pp) + last = msg + + if spk_th is None: + spk_th = Speaker() + spk_th.start() diff --git a/nemuspeak.py b/nemuspeak.py deleted file mode 100755 index 48d338f..0000000 --- a/nemuspeak.py +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/python3 -# coding=utf-8 - -import sys -import signal -import os -import re -import subprocess -import traceback -from datetime import datetime -from datetime import timedelta -import _thread - -if len(sys.argv) <= 1: - print ("This script takes exactly 1 arg: a XML config file") - sys.exit(1) - -def onSignal(signum, frame): - print ("\nSIGINT receive, saving states and close") - sys.exit (0) -signal.signal(signal.SIGINT, onSignal) - -if len(sys.argv) == 3: - basedir = sys.argv[2] -else: - basedir = "./" - -import xmlparser as msf -import message -from server.IRC import IRCServer - -SMILEY = list() -CORRECTIONS = list() -g_queue = list() -talkEC = 0 -stopSpk = 0 -lastmsg = None - -def speak(endstate): - global lastmsg, g_queue, talkEC, stopSpk - talkEC = 1 - stopSpk = 0 - - if lastmsg is None: - lastmsg = message.Message(b":Quelqun!someone@p0m.fr PRIVMSG channel nothing", datetime.now()) - - while not stopSpk and len(g_queue) > 0: - srv, msg = g_queue.pop(0) - lang = "fr" - sentence = "" - force = 0 - - #Skip identic body - if msg.content == lastmsg.content: - continue - - if force or msg.time - lastmsg.time > timedelta(0, 500): - sentence += "A {0} heure {1} : ".format(msg.time.hour, msg.time.minute) - force = 1 - - if force or msg.channel != lastmsg.channel: - if msg.channel == srv.owner: - sentence += "En message priver. " #Just to avoid é :p - else: - sentence += "Sur " + msg.channel + ". " - force = 1 - - action = 0 - if msg.content.find("ACTION ") == 1: - sentence += msg.nick + " " - msg.content = msg.content.replace("ACTION ", "") - action = 1 - for (txt, mood) in SMILEY: - if msg.content.find(txt) >= 0: - sentence += msg.nick + (" %s : "%mood) - msg.content = msg.content.replace(txt, "") - action = 1 - break - - for (bad, good) in CORRECTIONS: - if msg.content.find(bad) >= 0: - msg.content = (" " + msg.content + " ").replace(bad, good) - - if action == 0 and (force or msg.sender != lastmsg.sender): - sentence += msg.nick + " dit : " - - if re.match(".*(https?://)?(www\\.)?ycc.fr/[a-z0-9A-Z]+.*", msg.content) is not None: - msg.content = re.sub("(https?://)?(www\\.)?ycc.fr/[a-z0-9A-Z]+", " U.R.L Y.C.C ", msg.content) - - if re.match(".*https?://.*", msg.content) is not None: - msg.content = re.sub(r'https?://[^ ]+', " U.R.L ", msg.content) - - if re.match("^ *[^a-zA-Z0-9 ][a-zA-Z]{2}[^a-zA-Z0-9 ]", msg.content) is not None: - if sentence != "": - intro = subprocess.call(["espeak", "-v", "fr", "--", sentence]) - #intro.wait() - - lang = msg.content[1:3].lower() - sentence = msg.content[4:] - else: - sentence += msg.content - - spk = subprocess.call(["espeak", "-v", lang, "--", sentence]) - #spk.wait() - - lastmsg = msg - - if not stopSpk: - talkEC = endstate - else: - talkEC = 1 - - -class Server(IRCServer): - def treat_msg(self, line, private = False): - global stopSpk, talkEC, g_queue - try: - msg = message.Message (line, datetime.now(), private) - if msg.cmd == 'PING': - self.send_pong(msg.content) - elif msg.cmd == 'PRIVMSG' and self.accepted_channel(msg.channel): - if msg.nick != self.owner: - g_queue.append((self, msg)) - if talkEC == 0: - _thread.start_new_thread(speak, (0,)) - elif msg.content[0] == "`" and len(msg.content) > 1: - msg.cmds = msg.cmds[1:] - if msg.cmds[0] == "speak": - _thread.start_new_thread(speak, (0,)) - elif msg.cmds[0] == "reset": - while len(g_queue) > 0: - g_queue.pop() - elif msg.cmds[0] == "save": - if talkEC == 0: - talkEC = 1 - stopSpk = 1 - elif msg.cmds[0] == "add": - self.channels.append(msg.cmds[1]) - print (cmd[1] + " added to listened channels") - elif msg.cmds[0] == "del": - if self.channels.count(msg.cmds[1]) > 0: - self.channels.remove(msg.cmds[1]) - print (msg.cmds[1] + " removed from listened channels") - else: - print (cmd[1] + " not in listened channels") - except: - print ("\033[1;31mERROR:\033[0m occurred during the processing of the message: %s" % line) - exc_type, exc_value, exc_traceback = sys.exc_info() - traceback.print_exception(exc_type, exc_value, exc_traceback) - - -config = msf.parse_file(sys.argv[1]) - -for smiley in config.getNodes("smiley"): - if smiley.hasAttribute("txt") and smiley.hasAttribute("mood"): - SMILEY.append((smiley.getAttribute("txt"), smiley.getAttribute("mood"))) -print ("%d smileys loaded"%len(SMILEY)) - -for correct in config.getNodes("correction"): - if correct.hasAttribute("bad") and correct.hasAttribute("good"): - CORRECTIONS.append((" " + (correct.getAttribute("bad") + " "), (" " + correct.getAttribute("good") + " "))) -print ("%d corrections loaded"%len(CORRECTIONS)) - -for serveur in config.getNodes("server"): - srv = Server(serveur, config["nick"], config["owner"], config["realname"], serveur.hasAttribute("ssl")) - srv.launch(None) - -def sighup_h(signum, frame): - global talkEC, stopSpk - sys.stdout.write ("Signal reçu ... ") - if os.path.exists("/tmp/isPresent"): - _thread.start_new_thread(speak, (0,)) - print ("Morning!") - else: - print ("Sleeping!") - if talkEC == 0: - talkEC = 1 - stopSpk = 1 -signal.signal(signal.SIGHUP, sighup_h) - -print ("Nemuspeak ready, waiting for new messages...") -prompt="" -while prompt != "quit": - prompt=sys.stdin.readlines () - -sys.exit(0) diff --git a/server/IRC.py b/server/IRC.py index 0ddb7f9..2693a48 100644 --- a/server/IRC.py +++ b/server/IRC.py @@ -105,6 +105,9 @@ class IRC(SocketServer): self.ctcp_capabilities["USERINFO"] = lambda msg, cmds: "USERINFO %s" % self.realname self.ctcp_capabilities["VERSION"] = lambda msg, cmds: "VERSION nemubot v%s" % bot.__version__ + # TODO: Temporary fix, waiting for hook based CTCP management + self.ctcp_capabilities["TYPING"] = lambda msg, cmds: None + self.logger.debug("CTCP capabilities setup: %s", ", ".join(self.ctcp_capabilities)) diff --git a/speak_sample.xml b/speak_sample.xml index b7c3ff5..ee403ac 100644 --- a/speak_sample.xml +++ b/speak_sample.xml @@ -1,33 +1,35 @@ - + - + - + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - +