diff --git a/modules/qcm.py b/modules/qcm.py index 803f681..0cb9397 100644 --- a/modules/qcm.py +++ b/modules/qcm.py @@ -1,8 +1,14 @@ # 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 module_state import ModuleState import module_states_file as xmlparser @@ -11,10 +17,10 @@ nemubotversion = 3.0 def help_tiny (): """Line inserted in the response to the command !help""" - return "MCQ module" + return "MCQ module, working with http://bot.nemunai.re/" def help_full (): - return "todo" + return "!qcm [/nbQuest/] [/theme/]" class QuestionFile: def __init__(self, filename): @@ -27,6 +33,60 @@ class QuestionFile: 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 @@ -45,7 +105,7 @@ class Question: @property def course(self): - return self.node["course"] + return Course(self.node["course"]) @property def answers(self): @@ -53,7 +113,11 @@ class Question: @property def validator(self): - return self.node["validator"] + return User(self.node["validator"]) + + @property + def writer(self): + return User(self.node["writer"]) @property def validated(self): @@ -61,15 +125,26 @@ class Question: @property def addedtime(self): - return datetime.fromtimestamp(time.mktime(self.node["addedtime"])) + return datetime.fromtimestamp(float(self.node["addedtime"])) @property def author(self): - return "N/A" + return User(self.node["writer"]) + + def report(self): + conn = http.client.HTTPConnection(CONF.getNode("server")["url"]) + try: + conn.request("GET", "report.php?id=" + hashlib.md5(self.id.encode()).hexdigest()) + 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, self.validator, self.addedtime) + return (self.author.username, self.validator.username, self.addedtime) @property def bestAnswer(self): @@ -80,25 +155,31 @@ class Question: return best["answer"] def isCorrect(self, msg): + msg = msg.lower().replace(" ", "") for answer in self.answers: - if msg == answer["answer"]: + 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.lower() == answer["answer"].lower(): + if msg == answer["answer"].lower().replace(" ", ""): return answer.getInt("score") return 0 class Session: - def __init__(self): + 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: @@ -119,77 +200,197 @@ class Session: else: return None + def askNext(self, bfr = ""): + global SESSIONS + self.timer = None + nextQ = self.next_question() + if nextQ is not None: + if self.sender != self.channel: + self.server.send_msg(self.channel, "%s: %s%s" % (self.sender, bfr, nextQ.question)) + else: + self.server.send_msg(self.channel, "%s%s" % (bfr, nextQ.question)) + else: + if self.good > 1: + goodS = "s" + else: + goodS = "" + if self.sender != self.channel: + self.server.send_msg(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(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 buildSession(user, categ = None, nbQuest = 5): - global QUESTIONS +def load(): + CONF.setIndex("name", "file") + +def buildSession(msg, categ = None, nbQuest = 5, channel = False): + global QUESTIONS, COURSES, USERS if QUESTIONS is None: - QUESTIONS = xmlparser.parse_file(CONF.getNode("file")["url"]) + 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": + if QUESTIONS.index[ques]["validated"] != "1" or QUESTIONS.index[ques]["reported"] == "1": del QUESTIONS.index[ques] - nbQuest = min(nbQuest, len(QUESTIONS.index)) + #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"]) - sess = Session() - maxQuest = len(QUESTIONS.childs) - 1 + #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 = QUESTIONS.childs[random.randint(0, maxQuest)] - if q["xml:id"] is not None and q["validated"] == "1" and sess.addQuestion(q["xml:id"]): + q = QS[random.randint(0, maxQuest)] + if sess.addQuestion(q): break - SESSIONS[user] = sess + if channel: + SESSIONS[msg.channel] = sess + else: + SESSIONS[msg.sender] = sess def askQuestion(msg, bfr = ""): - nextQ = SESSIONS[msg.sender].next_question() - if nextQ is not None: - msg.send_chn("%s: %s%s" % (msg.sender, bfr, nextQ.question)) - else: - sess = SESSIONS[msg.sender] - if sess.good > 1: - goodS = "s" - else: - goodS = "" - msg.send_chn("%s: %sFini, tu as donné %d bonne%s réponse%s sur %d questions." % (msg.sender, bfr, sess.good, goodS, goodS, len(sess.questions))) - del SESSIONS[msg.sender] - + SESSIONS[msg.sender].askNext(bfr) def parseanswer(msg): - global DATAS - if msg.cmd[0] == "qcm": + global DATAS, SESSIONS + 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: - buildSession(msg.sender) - askQuestion(msg) + nbQuest = 5 + 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": - msg.send_chn("%s: fonction non implémentée" % msg.sender) - return True + if SESSIONS[msg.sender].question.report(): + msg.send_chn("Cette question vient vient d'etre signalée.") + askQuestion(msg) + 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 SESSIONS[msg.channel].question.report(): + msg.send_chn("Cette question vient vient d'etre signalée.") + askQuestion(msg) + else: + msg.send_chn("Une erreur s'est produite lors du signalement de la question, veuillez recommencer plus tard.") + return True return False def parseask(msg): if msg.sender in SESSIONS: - if SESSIONS[msg.sender].question.isCorrect(msg.content): - SESSIONS[msg.sender].good += 1 - SESSIONS[msg.sender].score += SESSIONS[msg.sender].question.getScore(msg.content) + 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[msg.sender].bad += 1 - if SESSIONS[msg.sender].trys == 0: - SESSIONS[msg.sender].trys = 1 + 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[msg.sender].question.bestAnswer) + 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.xml b/modules/qcm.xml index 1f9d21c..05a7076 100644 --- a/modules/qcm.xml +++ b/modules/qcm.xml @@ -1,4 +1,7 @@ - + + + + \ No newline at end of file diff --git a/modules/qd.py b/modules/qd.py index b053a67..bc64dab 100644 --- a/modules/qd.py +++ b/modules/qd.py @@ -398,7 +398,6 @@ class GameUpdater(threading.Thread): if rnd != 0: QUESTIONS = CONF.getNodes("question") - print (QUESTIONS) if self.msg.channel == "#nemutest": quest = 9