diff --git a/modules/events.py b/modules/events/__init__.py similarity index 80% rename from modules/events.py rename to modules/events/__init__.py index 853d571..e07819f 100644 --- a/modules/events.py +++ b/modules/events/__init__.py @@ -3,7 +3,6 @@ import re import sys from datetime import timedelta -from datetime import datetime from datetime import date import time import threading @@ -12,6 +11,8 @@ from module_state import ModuleState nemubotversion = 3.0 +from . import Manager + def help_tiny (): """Line inserted in the response to the command !help""" return "events manager" @@ -19,63 +20,23 @@ def help_tiny (): def help_full (): return "This module store a lot of events: ny, we, vacs, " + (", ".join(DATAS.index.keys())) + "\n!eventslist: gets list of timer\n!start /something/: launch a timer" -class Manager(threading.Thread): - def __init__(self): - self.stop = False - threading.Thread.__init__(self) - - def alertEnd(self, evt): - global newStrendEvt, SRVS, DATAS - #Send the message on each matched servers - for server in SRVS.keys(): - if not evt.hasAttribute("server") or server == evt["server"]: - if evt["channel"] == SRVS[server].nick: - SRVS[server].send_msg_usr(evt["proprio"], "%s: %s arrivé à échéance." % (evt["proprio"], evt["name"])) - else: - SRVS[server].send_msg(evt["channel"], "%s: %s arrivé à échéance." % (evt["proprio"], evt["name"])) - DATAS.delChild(DATAS.index[evt["name"]]) - save() - newStrendEvt.set() - - def run(self): - global DATAS - while not self.stop: - newStrendEvt.clear() - closer = None - #Gets the closer event - for evt in DATAS.index.keys(): - if DATAS.index[evt].hasAttribute("end") and (closer is None or DATAS.index[evt].getDate("end") < closer.getDate("end")) and DATAS.index[evt].getDate("end") > datetime.now(): - closer = DATAS.index[evt] - if closer is not None and closer.hasAttribute("end"): - #print ("Closer: %s à %s"%(closer.name, closer["end"])) - timeleft = (closer.getDate("end") - datetime.now()).seconds - timer = threading.Timer(timeleft, self.alertEnd, (closer,)) - timer.start() - #print ("Start timer (%ds)"%timeleft) - - newStrendEvt.wait() - - if closer is not None and closer.hasAttribute("end") and closer.getDate("end") > datetime.now(): - timer.cancel() - self.threadManager = None - threadManager = None -newStrendEvt = threading.Event() def load(): - global DATAS, threadManager + global DATAS, SRVS, threadManager #Define the index DATAS.setIndex("name") #Load the manager - threadManager = Manager() + Manager.save = save + threadManager = Manager(DATAS, SRVS) threadManager.start() def close(): global threadManager if threadManager is not None: threadManager.stop = True - newStrendEvt.set() + Manager.newStrendEvt.set() def parseanswer(msg): @@ -122,7 +83,7 @@ def parseanswer(msg): strnd["end"] = datetime.now() + timedelta(days=int(result.group(1))) else: strnd["end"] = datetime.now() + timedelta(seconds=int(result.group(1))) - newStrendEvt.set() + Manager.newStrendEvt.set() msg.send_snd ("%s commencé le %s et se terminera le %s."% (msg.cmd[1], datetime.now(), strnd.getDate("end"))) except: msg.send_snd ("Impossible de définir la fin de %s."% (msg.cmd[1])) @@ -139,7 +100,7 @@ def parseanswer(msg): msg.send_chn ("%s a duré %s." % (msg.cmd[1], msg.just_countdown(datetime.now () - DATAS.index[msg.cmd[1]].getDate("start")))) if DATAS.index[msg.cmd[1]]["proprio"] == msg.sender or (msg.cmd[0] == "forceend" and msg.sender == msg.srv.owner): DATAS.delChild(DATAS.index[msg.cmd[1]]) - newStrendEvt.set() + Manager.newStrendEvt.set() save() else: msg.send_snd ("Vous ne pouvez pas terminer le compteur %s, créé par %s."% (msg.cmd[1], DATAS.index[msg.cmd[1]]["proprio"])) diff --git a/modules/qcm.py b/modules/qcm.py deleted file mode 100644 index 26ae3e5..0000000 --- a/modules/qcm.py +++ /dev/null @@ -1,418 +0,0 @@ -# coding=utf-8 - -from datetime import datetime -import http.client -import hashlib -import re -import random -import socket -import sys -import threading -import time -from urllib.parse import quote - -from module_state import ModuleState -import module_states_file as xmlparser - -nemubotversion = 3.0 - -def help_tiny (): - """Line inserted in the response to the command !help""" - return "MCQ module, working with http://bot.nemunai.re/" - -def help_full (): - return "!qcm [/nbQuest/] [/theme/]" - -class QuestionFile: - def __init__(self, filename): - self.questions = xmlparser.parse_file(filename) - self.questions.setIndex("xml:id") - - def getQuestion(self, ident): - if ident in self.questions.index: - return Question(self.questions.index[ident]) - else: - return None - -class Course: - def __init__(self, iden): - global COURSES - if iden in COURSES.index: - self.node = COURSES.index[iden] - else: - self.node = { "code":"N/A", "name":"N/A", "branch":"N/A" } - - @property - def id(self): - return self.node["xml:id"] - - @property - def code(self): - return self.node["code"] - - @property - def name(self): - return self.node["name"] - - @property - def branch(self): - return self.node["branch"] - - @property - def validated(self): - return int(self.node["validated"]) > 0 - - -class User: - def __init__(self, iden): - global USERS - if iden in USERS.index: - self.node = USERS.index[iden] - else: - self.node = { "username":"N/A", "email":"N/A" } - - @property - def id(self): - return self.node["xml:id"] - - @property - def username(self): - return self.node["username"] - - @property - def email(self): - return self.node["email"] - - @property - def validated(self): - return int(self.node["validated"]) > 0 - - -class Question: - def __init__(self, node): - self.node = node - - @property - def ident(self): - return self.node["xml:id"] - - @property - def id(self): - return self.node["xml:id"] - - @property - def question(self): - return self.node["question"] - - @property - def course(self): - return Course(self.node["course"]) - - @property - def answers(self): - return self.node.getNodes("answer") - - @property - def validator(self): - return User(self.node["validator"]) - - @property - def writer(self): - return User(self.node["writer"]) - - @property - def validated(self): - return self.node["validated"] - - @property - def addedtime(self): - return datetime.fromtimestamp(float(self.node["addedtime"])) - - @property - def author(self): - return User(self.node["writer"]) - - def report(self, raison="Sans raison"): - conn = http.client.HTTPConnection(CONF.getNode("server")["url"]) - try: - conn.request("GET", "report.php?id=" + hashlib.md5(self.id.encode()).hexdigest() + "&raison=" + quote(raison)) - except socket.gaierror: - print ("[%s] impossible de récupérer la page %s."%(s, p)) - return False - res = conn.getresponse() - conn.close() - return (res.status == http.client.OK) - - @property - def tupleInfo(self): - return (self.author.username, self.validator.username, self.addedtime) - - @property - def bestAnswer(self): - best = self.answers[0] - for answer in self.answers: - if best.getInt("score") < answer.getInt("score"): - best = answer - return best["answer"] - - def isCorrect(self, msg): - msg = msg.lower().replace(" ", "") - for answer in self.answers: - if msg == answer["answer"].lower().replace(" ", ""): - return True - return False - - def getScore(self, msg): - msg = msg.lower().replace(" ", "") - for answer in self.answers: - if msg == answer["answer"].lower().replace(" ", ""): - return answer.getInt("score") - return 0 - -class Session: - def __init__(self, srv, chan, sender): - self.questions = list() - self.current = -1 - self.score = 0 - self.good = 0 - self.bad = 0 - self.trys = 0 - self.timer = None - self.server = srv - self.channel = chan - self.sender = sender - - def addQuestion(self, ident): - if ident not in self.questions: - self.questions.append(ident) - return True - return False - - def next_question(self): - self.trys = 0 - self.current += 1 - return self.question - - @property - def question(self): - if self.current >= 0 and self.current < len(self.questions): - global QUESTIONS - return Question(QUESTIONS.index[self.questions[self.current]]) - else: - return None - - def askNext(self, bfr = ""): - global SESSIONS - self.timer = None - nextQ = self.next_question() - if nextQ is not None: - if self.channel == "nemubot": - self.server.send_msg_final(self.sender, "%s%s" % (bfr, nextQ.question)) - elif self.sender != self.channel: - self.server.send_msg_final(self.channel, "%s: %s%s" % (self.sender, bfr, nextQ.question)) - else: - self.server.send_msg_final(self.channel, "%s%s" % (bfr, nextQ.question)) - else: - if self.good > 1: - goodS = "s" - else: - goodS = "" - if self.channel == "nemubot": - self.server.send_msg_final(self.sender, "%sFini, tu as donné %d bonne%s réponse%s sur %d questions." % (self.sender, bfr, self.good, goodS, goodS, len(self.questions))) - elif self.sender != self.channel: - self.server.send_msg_final(self.channel, "%s: %sFini, tu as donné %d bonne%s réponse%s sur %d questions." % (self.sender, bfr, self.good, goodS, goodS, len(self.questions))) - else: - self.server.send_msg_final(self.channel, "%sFini, vous avez donné %d bonne%s réponse%s sur %d questions." % (bfr, self.good, goodS, goodS, len(self.questions))) - del SESSIONS[self.sender] - - def prepareNext(self, lag = 3): - if self.timer is None: - self.timer = threading.Timer(lag, self.askNext) - self.timer.start() - -QUESTIONS = None -COURSES = None -USERS = None -SESSIONS = dict() - -def load(): - CONF.setIndex("name", "file") - -def buildSession(msg, categ = None, nbQuest = 10, channel = False): - global QUESTIONS, COURSES, USERS - if QUESTIONS is None: - QUESTIONS = xmlparser.parse_file(CONF.index["main"]["url"]) - QUESTIONS.setIndex("xml:id") - COURSES = xmlparser.parse_file(CONF.index["courses"]["url"]) - COURSES.setIndex("xml:id") - USERS = xmlparser.parse_file(CONF.index["users"]["url"]) - USERS.setIndex("xml:id") - #Remove no validated questions - keys = list() - for k in QUESTIONS.index.keys(): - keys.append(k) - for ques in keys: - if QUESTIONS.index[ques]["validated"] != "1" or QUESTIONS.index[ques]["reported"] == "1": - del QUESTIONS.index[ques] - - #Apply filter - QS = list() - if categ is not None and len(categ) > 0: - #Find course id corresponding to categ - courses = list() - for c in COURSES.childs: - if c["code"] in categ: - courses.append(c["xml:id"]) - - #Keep only questions matching course or branch - for q in QUESTIONS.index.keys(): - if (QUESTIONS.index[q]["branch"] is not None and QUESTIONS.index[q]["branch"].find(categ)) or QUESTIONS.index[q]["course"] in courses: - QS.append(q) - else: - for q in QUESTIONS.index.keys(): - QS.append(q) - - nbQuest = min(nbQuest, len(QS)) - - if channel: - sess = Session(msg.srv, msg.channel, msg.channel) - else: - sess = Session(msg.srv, msg.channel, msg.sender) - maxQuest = len(QS) - 1 - for i in range(0, nbQuest): - while True: - q = QS[random.randint(0, maxQuest)] - if sess.addQuestion(q): - break - if channel: - SESSIONS[msg.channel] = sess - else: - SESSIONS[msg.sender] = sess - - -def askQuestion(msg, bfr = ""): - SESSIONS[msg.sender].askNext(bfr) - -def parseanswer(msg): - global DATAS, SESSIONS, COURSES, QUESTIONS, USERS - if msg.cmd[0] == "qcm" or msg.cmd[0] == "qcmchan" or msg.cmd[0] == "simulateqcm": - if msg.sender in SESSIONS: - if len(msg.cmd) > 1: - if msg.cmd[1] == "stop" or msg.cmd[1] == "end": - sess = SESSIONS[msg.sender] - if sess.good > 1: goodS = "s" - else: goodS = "" - msg.send_chn("%s: Fini, tu as donné %d bonne%s réponse%s sur %d questions." % (msg.sender, sess.good, goodS, goodS, sess.current)) - del SESSIONS[msg.sender] - return True - elif msg.cmd[1] == "next" or msg.cmd[1] == "suivant" or msg.cmd[1] == "suivante": - askQuestion(msg) - return True - msg.send_chn("%s: tu as déjà une session de QCM en cours, finis-la avant d'en commencer une nouvelle." % msg.sender) - elif msg.channel in SESSIONS: - if len(msg.cmd) > 1: - if msg.cmd[1] == "stop" or msg.cmd[1] == "end": - sess = SESSIONS[msg.channel] - if sess.good > 1: goodS = "s" - else: goodS = "" - msg.send_chn("Fini, vous avez donné %d bonne%s réponse%s sur %d questions." % (sess.good, goodS, goodS, sess.current)) - del SESSIONS[msg.channel] - return True - elif msg.cmd[1] == "next" or msg.cmd[1] == "suivant" or msg.cmd[1] == "suivante": - SESSIONS[msg.channel].prepareNext(1) - return True - else: - nbQuest = 10 - filtre = list() - if len(msg.cmd) > 1: - for cmd in msg.cmd[1:]: - try: - tmp = int(cmd) - nbQuest = tmp - except ValueError: - filtre.append(cmd.upper()) - if len(filtre) == 0: - filtre = None - if msg.channel in SESSIONS: - msg.send_snd("Il y a deja une session de QCM sur ce chan.") - else: - buildSession(msg, filtre, nbQuest, msg.cmd[0] == "qcmchan") - if msg.cmd[0] == "qcm": - askQuestion(msg) - elif msg.cmd[0] == "qcmchan": - SESSIONS[msg.channel].askNext() - else: - msg.send_chn("QCM de %d questions" % len(SESSIONS[msg.sender].questions)) - del SESSIONS[msg.sender] - return True - elif msg.sender in SESSIONS: - if msg.cmd[0] == "info" or msg.cmd[0] == "infoquestion": - msg.send_chn("Cette question a été écrite par %s et validée par %s, le %s" % SESSIONS[msg.sender].question.tupleInfo) - return True - elif msg.cmd[0] == "report" or msg.cmd[0] == "reportquestion": - if len(msg.cmd) == 1: - msg.send_chn("Veuillez indiquer une raison de report") - elif SESSIONS[msg.sender].question.report(' '.join(msg.cmd[1:])): - msg.send_chn("Cette question vient d'être signalée.") - SESSIONS[msg.sender].askNext() - else: - msg.send_chn("Une erreur s'est produite lors du signalement de la question, veuillez recommencer plus tard.") - return True - elif msg.channel in SESSIONS: - if msg.cmd[0] == "info" or msg.cmd[0] == "infoquestion": - msg.send_chn("Cette question a été écrite par %s et validée par %s, le %s" % SESSIONS[msg.channel].question.tupleInfo) - return True - elif msg.cmd[0] == "report" or msg.cmd[0] == "reportquestion": - if len(msg.cmd) == 1: - msg.send_chn("Veuillez indiquer une raison de report") - elif SESSIONS[msg.channel].question.report(' '.join(msg.cmd[1:])): - msg.send_chn("Cette question vient d'être signalée.") - SESSIONS[msg.channel].prepareNext() - else: - msg.send_chn("Une erreur s'est produite lors du signalement de la question, veuillez recommencer plus tard.") - return True - else: - if msg.cmd[0] == "listecours": - if COURSES is None: - msg.send_chn("La liste de cours n'est pas encore construite, lancez un QCM pour la construire.") - else: - lst = "" - for cours in COURSES.getNodes("course"): - lst += cours["code"] + " (" + cours["name"] + "), " - msg.send_chn("Liste des cours existants : " + lst[:len(lst)-2]) - elif msg.cmd[0] == "refreshqcm": - QUESTIONS = None - COURSES = None - USERS = None - return False - -def parseask(msg): - if msg.sender in SESSIONS: - dest = msg.sender - - if SESSIONS[dest].question.isCorrect(msg.content): - SESSIONS[dest].good += 1 - SESSIONS[dest].score += SESSIONS[dest].question.getScore(msg.content) - askQuestion(msg, "correct ; ") - else: - SESSIONS[dest].bad += 1 - if SESSIONS[dest].trys == 0: - SESSIONS[dest].trys = 1 - msg.send_chn("%s: non, essaie encore :p" % msg.sender) - else: - askQuestion(msg, "non, la bonne reponse était : %s ; " % SESSIONS[dest].question.bestAnswer) - return True - - elif msg.channel in SESSIONS: - dest = msg.channel - - if SESSIONS[dest].question.isCorrect(msg.content): - SESSIONS[dest].good += 1 - SESSIONS[dest].score += SESSIONS[dest].question.getScore(msg.content) - msg.send_chn("%s: correct :)" % msg.sender) - SESSIONS[dest].prepareNext() - else: - SESSIONS[dest].bad += 1 - msg.send_chn("%s: non, essaie encore :p" % msg.sender) - return True - return False diff --git a/modules/qcm/__init__.py b/modules/qcm/__init__.py new file mode 100644 index 0000000..d598cb3 --- /dev/null +++ b/modules/qcm/__init__.py @@ -0,0 +1,202 @@ +# coding=utf-8 + +from datetime import datetime +import http.client +import re +import random +import sys +import time + +nemubotversion = 3.0 + +def help_tiny (): + """Line inserted in the response to the command !help""" + return "MCQ module, working with http://bot.nemunai.re/" + +def help_full (): + return "!qcm [] []" + +from . import Question +from . import Course +from . import Session + +def load(): + CONF.setIndex("name", "file") + +def buildSession(msg, categ = None, nbQuest = 10, channel = False): + if Question.QUESTIONS is None: + Question.QUESTIONS = xmlparser.parse_file(CONF.index["main"]["url"]) + Question.QUESTIONS.setIndex("xml:id") + Course.COURSES = xmlparser.parse_file(CONF.index["courses"]["url"]) + Course.COURSES.setIndex("xml:id") + User.USERS = xmlparser.parse_file(CONF.index["users"]["url"]) + User.USERS.setIndex("xml:id") + #Remove no validated questions + keys = list() + for k in Question.QUESTIONS.index.keys(): + keys.append(k) + for ques in keys: + if Question.QUESTIONS.index[ques]["validated"] != "1" or Question.QUESTIONS.index[ques]["reported"] == "1": + del Question.QUESTIONS.index[ques] + + #Apply filter + QS = list() + if categ is not None and len(categ) > 0: + #Find course id corresponding to categ + courses = list() + for c in Course.COURSES.childs: + if c["code"] in categ: + courses.append(c["xml:id"]) + + #Keep only questions matching course or branch + for q in Question.QUESTIONS.index.keys(): + if (Question.QUESTIONS.index[q]["branch"] is not None and Question.QUESTIONS.index[q]["branch"].find(categ)) or Question.QUESTIONS.index[q]["course"] in courses: + QS.append(q) + else: + for q in Question.QUESTIONS.index.keys(): + QS.append(q) + + nbQuest = min(nbQuest, len(QS)) + + if channel: + sess = Session.Session(msg.srv, msg.channel, msg.channel) + else: + sess = Session.Session(msg.srv, msg.channel, msg.sender) + maxQuest = len(QS) - 1 + for i in range(0, nbQuest): + while True: + q = QS[random.randint(0, maxQuest)] + if sess.addQuestion(q): + break + if channel: + Session.SESSIONS[msg.channel] = sess + else: + Session.SESSIONS[msg.sender] = sess + + +def askQuestion(msg, bfr = ""): + Session.SESSIONS[msg.sender].askNext(bfr) + +def parseanswer(msg): + global DATAS + if msg.cmd[0] == "qcm" or msg.cmd[0] == "qcmchan" or msg.cmd[0] == "simulateqcm": + if msg.sender in Session.SESSIONS: + if len(msg.cmd) > 1: + if msg.cmd[1] == "stop" or msg.cmd[1] == "end": + sess = Session.SESSIONS[msg.sender] + if sess.good > 1: goodS = "s" + else: goodS = "" + msg.send_chn("%s: Fini, tu as donné %d bonne%s réponse%s sur %d questions." % (msg.sender, sess.good, goodS, goodS, sess.current)) + del Session.SESSIONS[msg.sender] + return True + elif msg.cmd[1] == "next" or msg.cmd[1] == "suivant" or msg.cmd[1] == "suivante": + askQuestion(msg) + return True + msg.send_chn("%s: tu as déjà une session de QCM en cours, finis-la avant d'en commencer une nouvelle." % msg.sender) + elif msg.channel in Session.SESSIONS: + if len(msg.cmd) > 1: + if msg.cmd[1] == "stop" or msg.cmd[1] == "end": + sess = Session.SESSIONS[msg.channel] + if sess.good > 1: goodS = "s" + else: goodS = "" + msg.send_chn("Fini, vous avez donné %d bonne%s réponse%s sur %d questions." % (sess.good, goodS, goodS, sess.current)) + del Session.SESSIONS[msg.channel] + return True + elif msg.cmd[1] == "next" or msg.cmd[1] == "suivant" or msg.cmd[1] == "suivante": + Session.SESSIONS[msg.channel].prepareNext(1) + return True + else: + nbQuest = 10 + filtre = list() + if len(msg.cmd) > 1: + for cmd in msg.cmd[1:]: + try: + tmp = int(cmd) + nbQuest = tmp + except ValueError: + filtre.append(cmd.upper()) + if len(filtre) == 0: + filtre = None + if msg.channel in Session.SESSIONS: + msg.send_snd("Il y a deja une session de QCM sur ce chan.") + else: + buildSession(msg, filtre, nbQuest, msg.cmd[0] == "qcmchan") + if msg.cmd[0] == "qcm": + askQuestion(msg) + elif msg.cmd[0] == "qcmchan": + Session.SESSIONS[msg.channel].askNext() + else: + msg.send_chn("QCM de %d questions" % len(Session.SESSIONS[msg.sender].questions)) + del Session.SESSIONS[msg.sender] + return True + elif msg.sender in Session.SESSIONS: + if msg.cmd[0] == "info" or msg.cmd[0] == "infoquestion": + msg.send_chn("Cette question a été écrite par %s et validée par %s, le %s" % Session.SESSIONS[msg.sender].question.tupleInfo) + return True + elif msg.cmd[0] == "report" or msg.cmd[0] == "reportquestion": + if len(msg.cmd) == 1: + msg.send_chn("Veuillez indiquer une raison de report") + elif Session.SESSIONS[msg.sender].question.report(' '.join(msg.cmd[1:])): + msg.send_chn("Cette question vient d'être signalée.") + Session.SESSIONS[msg.sender].askNext() + else: + msg.send_chn("Une erreur s'est produite lors du signalement de la question, veuillez recommencer plus tard.") + return True + elif msg.channel in Session.SESSIONS: + if msg.cmd[0] == "info" or msg.cmd[0] == "infoquestion": + msg.send_chn("Cette question a été écrite par %s et validée par %s, le %s" % Session.SESSIONS[msg.channel].question.tupleInfo) + return True + elif msg.cmd[0] == "report" or msg.cmd[0] == "reportquestion": + if len(msg.cmd) == 1: + msg.send_chn("Veuillez indiquer une raison de report") + elif Session.SESSIONS[msg.channel].question.report(' '.join(msg.cmd[1:])): + msg.send_chn("Cette question vient d'être signalée.") + Session.SESSIONS[msg.channel].prepareNext() + else: + msg.send_chn("Une erreur s'est produite lors du signalement de la question, veuillez recommencer plus tard.") + return True + else: + if msg.cmd[0] == "listecours": + if Course.COURSES is None: + msg.send_chn("La liste de cours n'est pas encore construite, lancez un QCM pour la construire.") + else: + lst = "" + for cours in Course.COURSES.getNodes("course"): + lst += cours["code"] + " (" + cours["name"] + "), " + msg.send_chn("Liste des cours existants : " + lst[:len(lst)-2]) + elif msg.cmd[0] == "refreshqcm": + Question.QUESTIONS = None + Course.COURSES = None + User.USERS = None + return False + +def parseask(msg): + if msg.sender in Session.SESSIONS: + dest = msg.sender + + if Session.SESSIONS[dest].question.isCorrect(msg.content): + Session.SESSIONS[dest].good += 1 + Session.SESSIONS[dest].score += Session.SESSIONS[dest].question.getScore(msg.content) + askQuestion(msg, "correct ; ") + else: + Session.SESSIONS[dest].bad += 1 + if Session.SESSIONS[dest].trys == 0: + Session.SESSIONS[dest].trys = 1 + msg.send_chn("%s: non, essaie encore :p" % msg.sender) + else: + askQuestion(msg, "non, la bonne reponse était : %s ; " % Session.SESSIONS[dest].question.bestAnswer) + return True + + elif msg.channel in Session.SESSIONS: + dest = msg.channel + + if Session.SESSIONS[dest].question.isCorrect(msg.content): + Session.SESSIONS[dest].good += 1 + Session.SESSIONS[dest].score += Session.SESSIONS[dest].question.getScore(msg.content) + msg.send_chn("%s: correct :)" % msg.sender) + Session.SESSIONS[dest].prepareNext() + else: + Session.SESSIONS[dest].bad += 1 + msg.send_chn("%s: non, essaie encore :p" % msg.sender) + return True + return False diff --git a/modules/qd.py b/modules/qd/__init__.py similarity index 50% rename from modules/qd.py rename to modules/qd/__init__.py index bc64dab..7550013 100644 --- a/modules/qd.py +++ b/modules/qd/__init__.py @@ -1,19 +1,12 @@ # coding=utf-8 import re -import time -import random -import sys -import threading -from datetime import timedelta from datetime import datetime -from datetime import date - -from module_state import ModuleState -from wrapper import Wrapper nemubotversion = 3.0 +from . import GameUpdater + channels = "#nemutest #42sh #ykar #epitagueule" LASTSEEN = dict () temps = dict () @@ -21,9 +14,14 @@ temps = dict () SCORES = None def load(): - global DATAS, SCORES + global DATAS, SCORES, CONF DATAS.setIndex("name", "player") - SCORES = QDWrapper() + SCORES = QDWrapper(DATAS) + GameUpdater.SCORES = SCORES + GameUpdater.CONF = CONF + GameUpdater.save = save + GameUpdater.getUser = getUser + def help_tiny (): """Line inserted in the response to the command !help""" @@ -33,150 +31,6 @@ def help_full (): return "!42: display scores\n!42 help: display the performed calculate\n!42 manche: display information about current round\n!42 /who/: show the /who/'s scores" -class QDWrapper(Wrapper): - def __init__(self): - global DATAS - Wrapper.__init__(self) - self.DATAS = DATAS - self.stateName = "player" - self.attName = "name" - - def __getitem__(self, i): - if i in self.cache: - return self.cache[i] - else: - sc = Score() - sc.parse(Wrapper.__getitem__(self, i)) - self.cache[i] = sc - return sc - -class Score: - """Manage the user's scores""" - def __init__(self): - #FourtyTwo - self.ftt = 0 - #TwentyThree - self.twt = 0 - self.pi = 0 - self.notfound = 0 - self.tententen = 0 - self.leet = 0 - self.great = 0 - self.bad = 0 - self.triche = 0 - self.last = None - self.changed = False - - def parse(self, item): - self.ftt = item.getInt("fourtytwo") - self.twt = item.getInt("twentythree") - self.pi = item.getInt("pi") - self.notfound = item.getInt("notfound") - self.tententen = item.getInt("tententen") - self.leet = item.getInt("leet") - self.great = item.getInt("great") - self.bad = item.getInt("bad") - self.triche = item.getInt("triche") - - def save(self, state): - state.setAttribute("fourtytwo", self.ftt) - state.setAttribute("twentythree", self.twt) - state.setAttribute("pi", self.pi) - state.setAttribute("notfound", self.notfound) - state.setAttribute("tententen", self.tententen) - state.setAttribute("leet", self.leet) - state.setAttribute("great", self.great) - state.setAttribute("bad", self.bad) - state.setAttribute("triche", self.triche) - - def merge(self, other): - self.ftt += other.ftt - self.twt += other.twt - self.pi += other.pi - self.notfound += other.notfound - self.tententen += other.tententen - self.leet += other.leet - self.great += other.great - self.bad += other.bad - self.triche += other.triche - - def newWinner(self): - self.ftt = 0 - self.twt = 0 - self.pi = 1 - self.notfound = 1 - self.tententen = 0 - self.leet = 1 - self.great = -1 - self.bad = -4 - self.triche = 0 - - def isWinner(self): - return self.great >= 42 - - def playFtt(self): - if self.canPlay(): - self.ftt += 1 - def playTwt(self): - if self.canPlay(): - self.twt += 1 - def playSuite(self): - self.canPlay() - self.twt += 1 - self.great += 1 - def playPi(self): - if self.canPlay(): - self.pi += 1 - def playNotfound(self): - if self.canPlay(): - self.notfound += 1 - def playTen(self): - if self.canPlay(): - self.tententen += 1 - def playLeet(self): - if self.canPlay(): - self.leet += 1 - def playGreat(self): - if self.canPlay(): - self.great += 1 - def playBad(self): - if self.canPlay(): - self.bad += 1 - self.great += 1 - def playTriche(self): - self.triche += 1 - def oupsTriche(self): - self.triche -= 1 - def bonusQuestion(self): - return - - def toTuple(self): - return (self.ftt, self.twt, self.pi, self.notfound, self.tententen, self.leet, self.great, self.bad, self.triche) - - def canPlay(self): - ret = self.last == None or self.last.minute != datetime.now().minute or self.last.hour != datetime.now().hour or self.last.day != datetime.now().day - self.changed = self.changed or ret - return ret - - def hasChanged(self): - if self.changed: - self.changed = False - self.last = datetime.now() - return True - else: - return False - - def score(self): - return (self.ftt * 2 + self.great * 5 + self.leet * 13.37 + (self.pi + 1) * 3.1415 * (self.notfound + 1) + self.tententen * 10 + self.twt - (self.bad + 1) * 10 * (self.triche * 5 + 1) + 7) - - def details(self): - return "42: %d, 23: %d, leet: %d, pi: %d, 404: %d, 10: %d, great: %d, bad: %d, triche: %d = %d."%(self.ftt, self.twt, self.leet, self.pi, self.notfound, self.tententen, self.great, self.bad, self.triche, self.score()) - - -def rev (tupl): - (k, v) = tupl - return (v.score(), k) - def parseanswer (msg): if msg.cmd[0] == "42" or msg.cmd[0] == "score" or msg.cmd[0] == "scores": global SCORES @@ -261,14 +115,20 @@ def win(msg): def parseask (msg): - if len(DELAYED) > 0: - if msg.sender in DELAYED: - DELAYED[msg.sender].msg = msg.content - DELAYED[msg.sender].delayEvnt.set() + if len(GameUpdater.DELAYED) > 0: + if msg.sender in GameUpdater.DELAYED: + GameUpdater.DELAYED[msg.sender].msg = msg.content + GameUpdater.DELAYED[msg.sender].delayEvnt.set() return True return False + +def rev (tupl): + (k, v) = tupl + return (v.score(), k) + + def getUser(name): global SCORES if name not in SCORES: @@ -277,7 +137,7 @@ def getUser(name): def parselisten (msg): - if len(DELAYED) > 0 and msg.sender in DELAYED and DELAYED[msg.sender].good(msg.content): + if len(GameUpdater.DELAYED) > 0 and msg.sender in GameUpdater.DELAYED and GameUpdater.DELAYED[msg.sender].good(msg.content): msg.send_chn("%s: n'oublie pas le nemubot: devant ta réponse pour qu'elle soit prise en compte !" % msg.sender) bfrseen = None @@ -285,8 +145,8 @@ def parselisten (msg): bfrseen = LASTSEEN[msg.realname] LASTSEEN[msg.realname] = datetime.now() -# if msg.channel == "#nemutest" and msg.sender not in DELAYED: - if msg.channel != "#nemutest" and msg.sender not in DELAYED: +# if msg.channel == "#nemutest" and msg.sender not in GameUpdater.DELAYED: + if msg.channel != "#nemutest" and msg.sender not in GameUpdater.DELAYED: if re.match("^(42|quarante[- ]?deux).{,2}$", msg.content.strip().lower()): if msg.time.minute == 10 and msg.time.second == 10 and msg.time.hour == 10: @@ -355,77 +215,3 @@ def parselisten (msg): return True return False -DELAYED = dict() - -class DelayedTuple: - def __init__(self, regexp, great): - self.delayEvnt = threading.Event() - self.msg = None - self.regexp = regexp - self.great = great - - def triche(self, res): - if res is not None: - return re.match(".*" + self.regexp + ".*", res.lower() + " ") is None - else: - return True - - def perfect(self, res): - if res is not None: - return re.match(".*" + self.great + ".*", res.lower() + " ") is not None - else: - return False - - def wait(self, timeout): - self.delayEvnt.wait(timeout) - -LASTQUESTION = 99999 - -class GameUpdater(threading.Thread): - def __init__(self, msg, bfrseen): - self.msg = msg - self.bfrseen = bfrseen - threading.Thread.__init__(self) - - def run(self): - global DELAYED, LASTQUESTION - - if self.bfrseen is not None: - seen = datetime.now() - self.bfrseen - rnd = random.randint(0, int(seen.seconds/90)) - else: - rnd = 1 - - if rnd != 0: - QUESTIONS = CONF.getNodes("question") - - if self.msg.channel == "#nemutest": - quest = 9 - else: - if LASTQUESTION >= len(QUESTIONS): - random.shuffle(QUESTIONS) - LASTQUESTION = 0 - quest = LASTQUESTION - LASTQUESTION += 1 - - question = QUESTIONS[quest]["question"] - regexp = QUESTIONS[quest]["regexp"] - great = QUESTIONS[quest]["great"] - self.msg.send_chn("%s: %s" % (self.msg.sender, question)) - - DELAYED[self.msg.sender] = DelayedTuple(regexp, great) - - DELAYED[self.msg.sender].wait(20) - - if DELAYED[self.msg.sender].triche(DELAYED[self.msg.sender].msg): - getUser(self.msg.sender).playTriche() - self.msg.send_chn("%s: Tricheur !" % self.msg.sender) - elif DELAYED[self.msg.sender].perfect(DELAYED[self.msg.sender].msg): - if random.randint(0, 10) == 1: - getUser(self.msg.sender).bonusQuestion() - self.msg.send_chn("%s: Correct !" % self.msg.sender) - else: - self.msg.send_chn("%s: J'accepte" % self.msg.sender) - del DELAYED[self.msg.sender] - SCORES.save(self.msg.sender) - save() diff --git a/modules/soutenance.py b/modules/soutenance/__init__.py similarity index 58% rename from modules/soutenance.py rename to modules/soutenance/__init__.py index 5ce1e4a..688773d 100644 --- a/modules/soutenance.py +++ b/modules/soutenance/__init__.py @@ -7,137 +7,28 @@ import _thread import threading from datetime import date from datetime import datetime -from datetime import timedelta from module_state import ModuleState +from .SiteSoutenances import SiteSoutenances +from .Delayed import Delayed + nemubotversion = 3.0 -def getPage (): - conn = http.client.HTTPSConnection(CONF.getNode("server")["ip"]) - conn.request("GET", CONF.getNode("server")["url"]) - - res = conn.getresponse() - data = res.read() - - conn.close() - return data - -class Soutenance: - def __init__(self): - self.hour = None - self.rank = 0 - self.login = None - self.state = None - self.assistant = None - self.start = None - self.end = None - DELAYED = dict() -class Delayed: - def __init__(self, name): - self.name = name - self.res = None - self.evt = threading.Event() - - def wait(self, timeout): - self.evt.clear() - self.evt.wait(timeout) - -class SiteSoutenances: - def __init__(self, page): - save = False - self.souts = list() - self.updated = datetime.now () - last = None - for line in page.split("\n"): - if re.match("", line) is not None: - save = False - elif re.match("", line) is not None: - save = True - last = Soutenance() - self.souts.append(last) - elif save: - result = re.match("]+>(.*)", line) - if last.hour is None: - try: - last.hour = datetime.fromtimestamp (time.mktime (time.strptime (result.group(1), "%Y-%m-%d %H:%M"))) - except ValueError: - continue - elif last.rank == 0: - last.rank = int (result.group(1)) - elif last.login == None: - last.login = result.group(1) - elif last.state == None: - last.state = result.group(1) - elif last.assistant == None: - last.assistant = result.group(1) - elif last.start == None: - try: - last.start = datetime.fromtimestamp (time.mktime (time.strptime (result.group(1), "%Y-%m-%d %H:%M"))) - except ValueError: - last.start = None - elif last.end == None: - try: - last.end = datetime.fromtimestamp (time.mktime (time.strptime (result.group(1), "%Y-%m-%d %H:%M"))) - except ValueError: - last.end = None - - def update(self): - if self.findLast() is not None and datetime.now () - self.updated > timedelta(minutes=2): - return None - elif datetime.now () - self.updated < timedelta(hours=1): - return self - else: - return None - - def findAssistants(self): - h = {} - for s in self.souts: - if s.assistant is not None and s.assistant != "": - h[s.assistant] = (s.start, s.end) - return h - - - def findLast(self): - close = None - for s in self.souts: - if (s.state != "En attente" and s.start is not None and (close is None or close.rank < s.rank or close.hour.day > s.hour.day)) and (close is None or s.hour - close.hour < timedelta(seconds=2499)): - close = s - return close - - def findAll(self, login): - ss = list() - for s in self.souts: - if s.login == login: - ss.append(s) - return ss - - def findClose(self, login): - ss = self.findAll(login) - close = None - for s in ss: - if close is not None: - print (close.hour) - print (s.hour) - if close is None or (close.hour < s.hour and close.hour.day >= datetime.datetime().day): - close = s - return close - - -def help_tiny (): +def help_tiny(): """Line inserted in the response to the command !help""" return "EPITA ING1 defenses module" -def help_full (): - return "!soutenance: gives information about current defenses state\n!soutenance /who/: gives the date of the next defense of /who/.\n!soutenances /who/: gives all defense dates of /who/" +def help_full(): + return "!soutenance: gives information about current defenses state\n!soutenance : gives the date of the next defense of /who/.\n!soutenances : gives all defense dates of /who/" datas = None THREAD = None wait = list() -def parseanswer (msg): +def parseanswer(msg): global THREAD, wait if msg.cmd[0] == "soutenance" or msg.cmd[0] == "soutenances": if THREAD is None: @@ -148,6 +39,22 @@ def parseanswer (msg): return False +def parseask(msg): + if len(DELAYED) > 0 and msg.sender == msg.srv.partner: + treat = False + for part in msg.content.split(';'): + if part is None: + continue + for d in DELAYED.keys(): + if DELAYED[d].res is None and part.find(DELAYED[d].name) >= 0: + result = re.match(".* est (.*[^.])\.?", part) + if result is not None: + DELAYED[d].res = result.group(1) + DELAYED[d].evt.set() + return treat + return False + + def startSoutenance (msg): global datas, THREAD, wait @@ -224,17 +131,16 @@ def startSoutenance (msg): startSoutenance(wait.pop()) -def parseask (msg): - if len(DELAYED) > 0 and msg.sender == msg.srv.partner: - treat = False - for part in msg.content.split(';'): - if part is None: - continue - for d in DELAYED.keys(): - if DELAYED[d].res is None and part.find(DELAYED[d].name) >= 0: - result = re.match(".* est (.*[^.])\.?", part) - if result is not None: - DELAYED[d].res = result.group(1) - DELAYED[d].evt.set() - return treat - return False +def getPage(): + conn = http.client.HTTPConnection(CONF.getNode("server")["ip"]) + try: + conn.request("GET", CONF.getNode("server")["url"]) + + res = conn.getresponse() + data = res.read() + except: + print ("[%s] impossible de récupérer la page %s."%(s, p)) + return "" + + conn.close() + return data diff --git a/modules/watchWebsite.py b/modules/watchWebsite.py deleted file mode 100644 index e776093..0000000 --- a/modules/watchWebsite.py +++ /dev/null @@ -1,206 +0,0 @@ -# coding=utf-8 - -from datetime import timedelta -from datetime import datetime -from datetime import date -import http.client -import hashlib -import sys -import traceback -import socket -import time -import base64 -import threading -from urllib.parse import unquote - -from module_state import ModuleState - -nemubotversion = 3.0 - -import atom - -def help_tiny (): - """Line inserted in the response to the command !help""" - return "Alert on changes on websites" - -def help_full (): - return "This module is autonomous you can't interract with it." - -WATCHER = None - -def load(): - global WATCHER, DATAS - #Load the watcher - WATCHER = Watcher() - for site in DATAS.getNodes("watch"): - s = Site(site) - WATCHER.addServer(s) - WATCHER.start() - -def close(): - global WATCHER - if WATCHER is not None: - WATCHER.stop = True - WATCHER.newSrv.set() - - -class Watcher(threading.Thread): - def __init__(self): - self.servers = list() - self.stop = False - self.newSrv = threading.Event() - threading.Thread.__init__(self) - - def addServer(self, server): - self.servers.append(server) - self.newSrv.set() - - def check(self, closer): - closer.check() - self.newSrv.set() - - def run(self): - while not self.stop: - self.newSrv.clear() - closer = None - #Gets the closer server update - for server in self.servers: - if server.update < datetime.now(): - #print ("Closer now: %s à %s"%(server.url, server.update)) - self.check(server) - elif closer is None or server.update < closer.update: - closer = server - if closer is not None: - #print ("Closer: %s à %s"%(closer.url, closer.update)) - timeleft = (closer.update - datetime.now()).seconds - timer = threading.Timer(timeleft, self.check, (closer,)) - timer.start() - #print ("Start timer (%ds)"%timeleft) - - self.newSrv.wait() - - if closer is not None and closer.update is not None and closer.update > datetime.now(): - timer.cancel() - - def stop(self): - self.stop = True - self.newSrv.set() - - -class Site: - def __init__(self, item): - self.server = item.getAttribute("server") - self.page = item.getAttribute("page") - if len(self.page) <= 0 or self.page[0] != "/": - self.page = "/" + self.page - if item.hasAttribute("type"): - self.type = item.getAttribute("type") - else: - self.type = "hash" - self.message = item.getAttribute("message") - - if item.hasAttribute("time"): - self.updateTime = item.getInt("time") - else: - self.updateTime = 60 - self.lastChange = datetime.now() - self.lastpage = None - - self.channels = list() - for channel in item.getNodes('channel'): - self.channels.append(channel.getAttribute("name")) - - self.categories = dict() - for category in item.getNodes('category'): - self.categories[category.getAttribute("term")] = category.getAttribute("part") - - @property - def update(self): - if self.lastpage is None: - return self.lastChange - else: - return self.lastChange + timedelta(seconds=self.updateTime) - - @property - def url(self): - return self.server + self.page - - def send_message (self, msg): - global SRVS - if len(self.channels) > 0: - for server in SRVS.keys(): - for chan in self.channels: - SRVS[server].send_msg (chan, msg) - else: - for server in SRVS.keys(): - SRVS[server].send_global (msg) - - def treat_atom (self, content): - change=False - f = atom.Atom (content) - if self.lastpage is not None: - diff = self.lastpage.diff (f) - if len(diff) > 0: - print ("[%s] Page differ!"%self.server) - diff.reverse() - for d in diff: - if self.message.count("%s") == 2 and len(self.categories) > 0: - if d.category is None or d.category not in self.categories: - messageI = self.message % (self.categories[""], "%s") - else: - messageI = self.message % (self.categories[d.category], "%s") - self.send_message (messageI % unquote (d.link)) - elif self.message.count("%s") == 2: - if f.id == youtube.idAtom: - youtube.send_global (d.link2, self.message % (d.title, unquote (d.link))) - else: - self.send_message (self.message % (d.title, unquote (d.link))) - elif self.message.count("%s") == 1: - self.send_message(self.message % unquote (d.title)) - else: - self.send_message(self.message) - change=True - return (f, change) - - def check (self): - try: - #print ("Check %s"%(self.url)) - (status, content) = getPage(self.server, self.page) - if content is None: - return - - if self.type == "atom": - (self.lastpage, change) = self.treat_atom (content) - else: - hash = hashlib.sha224(content).hexdigest() - if hash != self.lastpage: - if self.lastpage is not None: - self.send_message (self.message) - self.lastpage = hash - - self.lastChange = datetime.now() - -# if self.updateTime < 10: -# self.updateTime = 10 -# if self.updateTime > 400: -# self.updateTime = 400 - except: - print ("Une erreur est survenue lors de la récupération de la page " + self.server + "/" + self.page) - exc_type, exc_value, exc_traceback = sys.exc_info() - traceback.print_exception(exc_type, exc_value, exc_traceback) - self.updateTime *= 2 - - -def getPage(s, p): - conn = http.client.HTTPConnection(s) - try: - conn.request("GET", p) - - res = conn.getresponse() - data = res.read() - except socket.gaierror: - print ("[%s] impossible de récupérer la page %s."%(s, p)) - return (None, None) - - conn.close() - return (res.status, data) diff --git a/modules/watchWebsite/__init__.py b/modules/watchWebsite/__init__.py new file mode 100644 index 0000000..25683d0 --- /dev/null +++ b/modules/watchWebsite/__init__.py @@ -0,0 +1,32 @@ +# coding=utf-8 + +nemubotversion = 3.0 + +from .Watcher import Watcher +from .Site import Site + +def help_tiny (): + """Line inserted in the response to the command !help""" + return "Alert on changes on websites" + +def help_full (): + return "This module is autonomous you can't interract with it." + + +WATCHER = None + + +def load(): + global WATCHER, DATAS + #Load the watcher + WATCHER = Watcher() + for site in DATAS.getNodes("watch"): + s = Site(site) + WATCHER.addServer(s) + WATCHER.start() + +def close(): + global WATCHER + if WATCHER is not None: + WATCHER.stop = True + WATCHER.newSrv.set() diff --git a/atom.py b/modules/watchWebsite/atom.py similarity index 100% rename from atom.py rename to modules/watchWebsite/atom.py diff --git a/modules/whereis.py b/modules/whereis/__init__.py similarity index 71% rename from modules/whereis.py rename to modules/whereis/__init__.py index 3eef210..466f76d 100644 --- a/modules/whereis.py +++ b/modules/whereis/__init__.py @@ -13,102 +13,25 @@ from urllib.parse import unquote from module_state import ModuleState +from . import User +from .UpdatedStorage import UpdatedStorage +from .Delayed import Delayed + nemubotversion = 3.0 THREAD = None search = list() -class UpdatedStorage: - def __init__(self): - sock = connect_to_ns(CONF.getNode("server")["url"], - CONF.getNode("server").getInt("port")) - self.users = dict() - if sock != None: - users = list_users(sock) - if users is not None: - for l in users: - u = User(l) - if u.login not in self.users: - self.users[u.login] = list() - self.users[u.login].append(u) - self.lastUpdate = datetime.now () - else: - self.users = None - sock.close() - else: - self.users = None - - def update(self): - if datetime.now () - self.lastUpdate < timedelta(minutes=10): - return self - else: - return None - - -class User(object): - def __init__(self, line): - fields = line.split() - self.login = fields[1] - self.ip = fields[2] - self.location = fields[8] - self.promo = fields[9] - - @property - def sm(self): - for sm in CONF.getNodes("sm"): - if self.ip.startswith(sm["ip"]): - return sm["name"] - return None - - @property - def poste(self): - if self.sm is None: - if self.ip.startswith('10.'): - return 'quelque part sur le PIE (%s)'%self.ip - else: - return "chez lui" - else: - if self.ip.startswith('10.247') or self.ip.startswith('10.248') or self.ip.startswith('10.249') or self.ip.startswith('10.250'): - return "en " + self.sm + " rangée " + self.ip.split('.')[2] + " poste " + self.ip.split('.')[3] - else: - return "en " + self.sm - - def __cmp__(self, other): - return cmp(self.login, other.login) - - def __hash__(self): - return hash(self.login) - -def connect_to_ns(server, port): - try: - s = socket.socket() - s.settimeout(3) - s.connect((server, port)) - except socket.error: - return None - s.recv(8192) - return s - -def list_users(sock): - try: - sock.send('list_users\n'.encode()) - buf = '' - while True: - tmp = sock.recv(8192).decode() - buf += tmp - if '\nrep 002' in tmp or tmp == '': - break - return buf.split('\n')[:-2] - except socket.error: - return None - - def help_tiny (): """Line inserted in the response to the command !help""" return "Find a user on the PIE" def help_full (): - return "!whereis /who/: gives the position of /who/.\n!whereare /who/ [/other who/ ...]: gives the position of /who/." + return "!whereis : gives the position of /who/.\n!whereare [ ...]: gives the position of these .\n!peoplein : gives the number of people in this /sm/.\n!ip : gets the IP adress of /who/.\n!whoison : gives the name or the number (if > 15) of people at this /location/.\n!whoisin : gives the name or the number of people in this /sm/" + +def load(): + global CONF + User.CONF = CONF datas = None @@ -117,7 +40,7 @@ def startWhereis(msg): if datas is not None: datas = datas.update () if datas is None: - datas = UpdatedStorage() + datas = UpdatedStorage(CONF.getNode("server")["url"], CONF.getNode("server").getInt("port")) if datas is None or datas.users is None: msg.send_chn("Hmm c'est embarassant, serait-ce la fin du monde ou juste netsoul qui est mort ?") return @@ -178,10 +101,6 @@ def whoison(msg): DELAYED = dict() delayEvnt = threading.Event() -class Delayed: - def __init__(self): - self.names = dict() - def whereis_msg(msg): names = list() for name in msg.cmd: diff --git a/modules/ycc.xml b/modules/ycc.xml deleted file mode 100644 index 0811dd6..0000000 --- a/modules/ycc.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/modules/ycc.py b/modules/ycc/__init__.py similarity index 51% rename from modules/ycc.py rename to modules/ycc/__init__.py index 264c474..46f74b2 100644 --- a/modules/ycc.py +++ b/modules/ycc/__init__.py @@ -3,7 +3,8 @@ import http.client import re import sys -import threading + +from .Tinyfier import Tinyfier nemubotversion = 3.0 @@ -15,24 +16,6 @@ def help_full (): return "TODO" -class Tinyfier(threading.Thread): - def __init__(self, url, msg): - self.url = url - self.msg = msg - threading.Thread.__init__(self) - - def run(self): - (status, page) = getPage("ycc.fr", "/redirection/create/" + self.url) - if status == http.client.OK and len(page) < 100: - srv = re.match(".*((ht|f)tps?://|www.)([^/ ]+).*", self.url) - if srv is None: - self.msg.send_chn("Mauvaise URL : %s" % (self.url)) - else: - self.msg.send_chn("URL pour %s : %s" % (srv.group(3), page.decode())) - else: - print ("ERROR: ycc.fr seem down?") - self.msg.send_chn("La situation est embarassante, il semblerait que YCC soit down :(") - def parseanswer(msg): global LAST_URLS if msg.cmd[0] == "ycc": @@ -42,7 +25,7 @@ def parseanswer(msg): t = Tinyfier(url, msg) t.start() else: - msg.send_chn("%s: je n'ai pas d'autre URL a reduire" % msg.sender) + msg.send_chn("%s: je n'ai pas d'autre URL reduire" % msg.sender) else: if len(msg.cmd) < 6: for url in msg.cmd[1:]: @@ -66,17 +49,3 @@ def parselisten (msg): LAST_URLS[msg.channel] = list(res.group(1)) return True return False - -def getPage(s, p): - conn = http.client.HTTPConnection(s) - try: - conn.request("GET", p) - except socket.gaierror: - print ("[%s] impossible de récupérer la page %s."%(s, p)) - return None - - res = conn.getresponse() - data = res.read() - - conn.close() - return (res.status, data) diff --git a/nemubot.py b/nemubot.py index 4e89d1c..753dd12 100755 --- a/nemubot.py +++ b/nemubot.py @@ -10,9 +10,19 @@ servers = dict() prompt = __import__ ("prompt") +#Add modules dir path +if os.path.isdir("./modules/"): + modules_path = os.path.realpath(os.path.abspath("./modules/")) + if modules_path not in sys.path: + sys.path.insert(0, modules_path) + +#Load given files if len(sys.argv) >= 2: for arg in sys.argv[1:]: - prompt.load_file(arg, servers) + if os.path.isfile(arg): + prompt.load_file(arg, servers) + elif os.path.isdir(arg): + sys.path.insert(1, arg) print ("Nemubot ready, my PID is %i!" % (os.getpid())) while prompt.launch(servers): diff --git a/prompt.py b/prompt.py index dad9144..0af6196 100644 --- a/prompt.py +++ b/prompt.py @@ -1,4 +1,5 @@ import imp +import os import shlex import sys import traceback @@ -92,12 +93,12 @@ def launch(servers): # # ########################## -def mod_save(mod, datas_path, config): - mod.DATAS.save(datas_path + "/" + config["name"] + ".xml") +def mod_save(mod, datas_path): + mod.DATAS.save(datas_path + "/" + mod.name + ".xml") mod.print ("Saving!") def mod_has_access(mod, config, msg): - if config.hasNode("channel"): + if config is not None and config.hasNode("channel"): for chan in config.getChilds("channel"): if (chan["server"] is None or chan["server"] == msg.srv.id) and (chan["channel"] is None or chan["channel"] == msg.channel): return True @@ -111,93 +112,106 @@ def mod_has_access(mod, config, msg): # # ########################## +def load_module_from_name(name, servers, config=None): + try: + #Import the module code + mod = __import__(name) + try: + if mod.nemubotversion < 3.0: + print (" Module `%s' is not compatible with this version." % name) + return + + #Set module common functions and datas + mod.name = name + mod.print = lambda msg: print("[%s] %s"%(mod.name, msg)) + mod.DATAS = xmlparser.parse_file(datas_path + "/" + name + ".xml") + mod.CONF = config + mod.SRVS = servers + mod.has_access = lambda msg: mod_has_access(mod, config, msg) + mod.save = lambda: mod_save(mod, datas_path) + + #Load dependancies + if mod.CONF is not None and mod.CONF.hasNode("dependson"): + mod.MODS = dict() + for depend in mod.CONF.getNodes("dependson"): + for md in MODS: + if md.name == depend["name"]: + mod.MODS[md.name] = md + break + if depend["name"] not in mod.MODS: + print ("\033[1;31mERROR:\033[0m in module `%s', module `%s' require by this module but is not loaded." % (mod.name,depend["name"])) + return + + try: + test = mod.parseask + except AttributeError: + print ("\033[1;35mWarning:\033[0m in module `%s', no function parseask defined." % mod.name) + mod.parseask = lambda x: False + + try: + test = mod.parseanswer + except AttributeError: + print ("\033[1;35mWarning:\033[0m in module `%s', no function parseanswer defined." % mod.name) + mod.parseanswer = lambda x: False + + try: + test = mod.parselisten + except AttributeError: + print ("\033[1;35mWarning:\033[0m in module `%s', no function parselisten defined." % mod.name) + mod.parselisten = lambda x: False + + try: + mod.load() + print (" Module `%s' successfully loaded." % name) + except AttributeError: + print (" Module `%s' successfully added." % name) + #TODO: don't append already running modules + MODS.append(mod) + except AttributeError: + print (" Module `%s' is not a nemubot module." % name) + for srv in servers: + servers[srv].update_mods(MODS) + except IOError: + print (" Module `%s' not loaded: unable to find module implementation." % name) + except ImportError: + print ("\033[1;31mERROR:\033[0m Module attached to the file `%s' not loaded. Is this file existing?" % name) + exc_type, exc_value, exc_traceback = sys.exc_info() + traceback.print_exception(exc_type, exc_value, exc_traceback) + def load_module(config, servers): global MODS if config.hasAttribute("name"): - try: - #Import the module code - mod = imp.load_source(config["name"], modules_path + "/" + config["name"] + ".py") - try: - if mod.nemubotversion < 3.0: - print (" Module `%s' is not compatible with this version." % config["name"]) - return - - #Set module common functions and datas - mod.name = config["name"] - mod.print = lambda msg: print("[%s] %s"%(mod.name, msg)) - mod.DATAS = xmlparser.parse_file(datas_path + "/" + config["name"] + ".xml") - mod.CONF = config - mod.SRVS = servers - mod.has_access = lambda msg: mod_has_access(mod, config, msg) - mod.save = lambda: mod_save(mod, datas_path, config) - - #Load dependancies - if mod.CONF.hasNode("dependson"): - mod.MODS = dict() - for depend in mod.CONF.getNodes("dependson"): - for md in MODS: - if md.name == depend["name"]: - mod.MODS[md.name] = md - break - if depend["name"] not in mod.MODS: - print ("\033[1;35mERROR:\033[0m in module `%s', module `%s' require by this module but is not loaded." % (mod.name,depend["name"])) - return - - try: - test = mod.parseask - except AttributeError: - print ("\033[1;35mWarning:\033[0m in module `%s', no function parseask defined." % mod.name) - mod.parseask = lambda x: False - - try: - test = mod.parseanswer - except AttributeError: - print ("\033[1;35mWarning:\033[0m in module `%s', no function parseanswer defined." % mod.name) - mod.parseanswer = lambda x: False - - try: - test = mod.parselisten - except AttributeError: - print ("\033[1;35mWarning:\033[0m in module `%s', no function parselisten defined." % mod.name) - mod.parselisten = lambda x: False - - try: - mod.load() - print (" Module `%s' successfully loaded." % config["name"]) - except AttributeError: - print (" Module `%s' successfully added." % config["name"]) - #TODO: don't append already running modules - MODS.append(mod) - except AttributeError: - print (" Module `%s' is not a nemubot module." % config["name"]) - for srv in servers: - servers[srv].update_mods(MODS) - except IOError: - print (" Module `%s' not loaded: unable to find module implementation." % config["name"]) - + load_module_from_name(config["name"], servers, config) def load_file(filename, servers): """Realy load a file""" global MODS - config = xmlparser.parse_file(filename) - if config.getName() == "nemubotconfig" or config.getName() == "config": - #Preset each server in this file - for serveur in config.getNodes("server"): - srv = server.Server(serveur, config["nick"], config["owner"], config["realname"]) - if srv.id not in servers: - servers[srv.id] = srv - print (" Server `%s' successfully added." % srv.id) - else: - print (" Server `%s' already added, skiped." % srv.id) - if srv.autoconnect: - srv.launch(MODS) - #Load files asked by the configuration file - for load in config.getNodes("load"): - load_file(load["path"], servers) - elif config.getName() == "nemubotmodule": - load_module(config, servers) + if os.path.isfile(filename): + config = xmlparser.parse_file(filename) + if config.getName() == "nemubotconfig" or config.getName() == "config": + #Preset each server in this file + for serveur in config.getNodes("server"): + srv = server.Server(serveur, config["nick"], config["owner"], config["realname"]) + if srv.id not in servers: + servers[srv.id] = srv + print (" Server `%s' successfully added." % srv.id) + else: + print (" Server `%s' already added, skiped." % srv.id) + if srv.autoconnect: + srv.launch(MODS) + #Load files asked by the configuration file + for load in config.getNodes("load"): + load_file(load["path"], servers) + elif config.getName() == "nemubotmodule": + load_module(config, servers) + else: + print (" Can't load `%s'; this is not a valid nemubot configuration file." % filename) + elif os.path.isfile(filename + ".xml"): + load_file(filename + ".xml", servers) + elif os.path.isfile("./modules/" + filename + ".xml"): + load_file("./modules/" + filename + ".xml", servers) else: - print (" Can't load `%s'; this is not a valid nemubot configuration file." % filename) + load_module_from_name(filename, servers) def load(cmds, servers):