|
|
@@ -0,0 +1,648 @@
|
|
|
+
|
|
|
+const irc = require("irc"),
|
|
|
+ fs = require("fs"),
|
|
|
+ readline = require("readline"),
|
|
|
+ cache = new (require("data-store"))({ path: "persistentDb.json" }),
|
|
|
+ arrayPad = require('./strpad.js').arrayPad;
|
|
|
+
|
|
|
+Object.assign(global, require("./config.js"));
|
|
|
+
|
|
|
+const MySQL = USE_MYSQL ? require("mysql2").createConnection({host: MySQL_HOST, user: MySQL_USER, database: MySQL_DB, password: MySQL_PASS}) : null;
|
|
|
+
|
|
|
+const HOSTNAME = require('os').hostname(); // For Mysql bot identification
|
|
|
+
|
|
|
+function Cache() {}
|
|
|
+Cache.SetScores = function(scores) {
|
|
|
+ var dataToSet = {};
|
|
|
+ for (var i in scores)
|
|
|
+ if (scores[i].score)
|
|
|
+ dataToSet[scores[i].name] = scores[i].score;
|
|
|
+ cache.set("scores", dataToSet);
|
|
|
+ cache.set("saveTs", Date.now());
|
|
|
+}
|
|
|
+
|
|
|
+Cache.getReportedQuestions = function(questionId, username) {
|
|
|
+ return cache.get("report") || {};
|
|
|
+}
|
|
|
+
|
|
|
+Cache.reportQuestion = function(questionId, username) {
|
|
|
+ var reported = cache.get("report") || {};
|
|
|
+ var questionReported = reported[questionId] || {};
|
|
|
+ reported[questionId] = questionReported;
|
|
|
+ if (questionReported[username])
|
|
|
+ return;
|
|
|
+ questionReported[username] = Date.now();
|
|
|
+ cache.set("report", reported);
|
|
|
+}
|
|
|
+
|
|
|
+Cache.unreportQuestion = function(questionId) {
|
|
|
+ var reported = cache.get("report") || {};
|
|
|
+ if (!reported[questionId])
|
|
|
+ return;
|
|
|
+ delete reported[questionId];
|
|
|
+ cache.set("report", reported);
|
|
|
+}
|
|
|
+
|
|
|
+Cache.setExportTs = function() {
|
|
|
+ cache.set("mysqlTs", Date.now());
|
|
|
+}
|
|
|
+
|
|
|
+Cache.getLastMysqlSave = function() {
|
|
|
+ return cache.get("mysqlTs") || 0;
|
|
|
+}
|
|
|
+
|
|
|
+Cache.GetData = function() {
|
|
|
+ return {
|
|
|
+ scores: cache.get("scores"),
|
|
|
+ lastSave: cache.get("saveTs") || 0,
|
|
|
+ lastMysqlExport: cache.get("mysqlTs") || 0
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+function Question(id, obj) {
|
|
|
+ this.id = id;
|
|
|
+ this.question = obj.question;
|
|
|
+ this.response = obj.response;
|
|
|
+ this.normalizedResponse = "";
|
|
|
+ if (Array.isArray(obj.response))
|
|
|
+ this.normalizedResponse = this.response.map(i => Question.normalize(i));
|
|
|
+ else
|
|
|
+ this.normalizedResponse = Question.normalize(this.response);
|
|
|
+}
|
|
|
+
|
|
|
+Question.normalize = function(str) {
|
|
|
+ return str.normalize('NFD').replace(/[\u0300-\u036f]/g, "").toLowerCase().trim();
|
|
|
+}
|
|
|
+
|
|
|
+const QuestionType = { bool: {}, number: {}, string: {} };
|
|
|
+
|
|
|
+Question.prototype.booleanValue = function(str) {
|
|
|
+ str = str || (Array.isArray(this.normalizedResponse) ? this.normalizedResponse[0] : this.normalizedResponse);
|
|
|
+ var index = ["non", "oui", "faux", "vrai"].indexOf(str);
|
|
|
+ if (index >= 0)
|
|
|
+ return index % 2 === 1;
|
|
|
+ return undefined;
|
|
|
+}
|
|
|
+
|
|
|
+Question.prototype.isBoolean = function(str) {
|
|
|
+ return this.booleanValue(str) !== undefined;
|
|
|
+}
|
|
|
+
|
|
|
+String.prototype.isWord = function() {
|
|
|
+ return (/\W/).exec(this) === null;
|
|
|
+}
|
|
|
+
|
|
|
+Question.prototype.getQuestionType = function() {
|
|
|
+ if (this.isBoolean())
|
|
|
+ return QuestionType.bool;
|
|
|
+ if (!isNaN(Number(this.response)))
|
|
|
+ return QuestionType.number;
|
|
|
+ return QuestionType.string;
|
|
|
+}
|
|
|
+
|
|
|
+Question.toHint = function(response, normalizedResponse, boundaries, responseIndex, questionType, hintLevel) {
|
|
|
+ if (questionType === QuestionType.string) {
|
|
|
+ if (hintLevel == 0)
|
|
|
+ return response.replace(/[\w]/g, '*');
|
|
|
+ else if (hintLevel == 1)
|
|
|
+ return response.replace(/[\w]/g, (a, b) => b ? '*' : response.charAt(b));
|
|
|
+ else if (normalizedResponse.isWord() && !responseIndex) {
|
|
|
+ var displayed = [];
|
|
|
+ const revealPercent = 0.1;
|
|
|
+ displayed[normalizedResponse.length -2] = 1;
|
|
|
+ displayed.fill(1, 0, normalizedResponse.length-1).fill(0, Math.ceil(normalizedResponse.length * revealPercent), normalizedResponse.length);
|
|
|
+ displayed.sort(()=>Math.random() > 0.5?-1:1)
|
|
|
+ return normalizedResponse.replace(/./g, (a, b) => b && !displayed[b -1] ? '*':response.charAt(b));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ return normalizedResponse.replace(/[\w]+/g, (a, wordIndex) => a.replace(/./g, (a, b) => b ? '*':response.charAt(wordIndex)));
|
|
|
+ }
|
|
|
+ else if (questionType === QuestionType.number) {
|
|
|
+ const responseInt = Number(normalizedResponse),
|
|
|
+ randomMin = ([ 30, 10, 3 ])[hintLevel],
|
|
|
+ randomSpread = 5 * (5 -hintLevel);
|
|
|
+ boundaries[0] = Math.max(
|
|
|
+ Math.floor(responseInt -(Math.random() *randomSpread) -randomMin),
|
|
|
+ boundaries[0]);
|
|
|
+ boundaries[1] = Math.min(
|
|
|
+ Math.ceil(responseInt +(Math.random() *randomSpread) +randomMin),
|
|
|
+ boundaries[1]);
|
|
|
+ return "Un nombre entre " +boundaries[0] +" et " +boundaries[1];
|
|
|
+ }
|
|
|
+ else
|
|
|
+ console.error("Unknown response type !");
|
|
|
+};
|
|
|
+
|
|
|
+Question.prototype.getHint = function(hintLevel) {
|
|
|
+ var type = this.getQuestionType();
|
|
|
+ if (type === QuestionType.bool)
|
|
|
+ return "Vrai / Faux ?";
|
|
|
+ else if (type === QuestionType.number)
|
|
|
+ this.boundaries = this.boundaries || [ -Infinity, Infinity];
|
|
|
+ if (!Array.isArray(this.response))
|
|
|
+ return Question.toHint(this.response, this.normalizedResponse, this.boundaries, 0, type, hintLevel);
|
|
|
+ var hints = [];
|
|
|
+ for (var i =0, len = this.response.length; i < len; ++i)
|
|
|
+ hints.push(Question.toHint(this.response[i], this.normalizedResponse[i], this.boundaries, i, type, hintLevel));
|
|
|
+ return hints.join (" ou ");
|
|
|
+}
|
|
|
+
|
|
|
+Question.prototype.check = function(response) {
|
|
|
+ response = Question.normalize(response);
|
|
|
+ var boolValue = this.booleanValue();
|
|
|
+ if (boolValue !== undefined)
|
|
|
+ return boolValue === this.booleanValue(response);
|
|
|
+ if (Array.isArray(this.normalizedResponse))
|
|
|
+ return this.normalizedResponse.indexOf(response) >= 0;
|
|
|
+ return response === this.normalizedResponse;
|
|
|
+}
|
|
|
+
|
|
|
+Question.prototype.end = function() {
|
|
|
+ if (this.boundaries)
|
|
|
+ delete this.boundaries;
|
|
|
+}
|
|
|
+
|
|
|
+function initQuestionList(filename) {
|
|
|
+ return new Promise((ok, ko) => {
|
|
|
+ var stream = fs.createReadStream(filename),
|
|
|
+ reader = readline.createInterface({input: stream}),
|
|
|
+ questions = [],
|
|
|
+ lineNo = 0,
|
|
|
+ borken = false;
|
|
|
+ reader.on("line", line => {
|
|
|
+ if (borken) return;
|
|
|
+ try {
|
|
|
+ var question = new Question(++lineNo, JSON.parse(line));
|
|
|
+ if (question.question && question.response)
|
|
|
+ questions.push(question);
|
|
|
+ } catch (e) {
|
|
|
+ console.error("Failed to load Database: ", e, "on line", lineNo);
|
|
|
+ borken = true;
|
|
|
+ reader.close();
|
|
|
+ stream.destroy();
|
|
|
+ ko("Failed to load database: syntax error on question #" +lineNo);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ reader.on("close", () => {
|
|
|
+ if (!borken)
|
|
|
+ ok(questions);
|
|
|
+ });
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function User(nick) {
|
|
|
+ this.admin = false;
|
|
|
+ this.name = nick;
|
|
|
+ this.score = 0;
|
|
|
+}
|
|
|
+User.prototype.setModeChar = function(mode) {
|
|
|
+ this.admin = !!(mode && (mode.indexOf('~') >= 0 || mode.indexOf('@') >= 0 || mode.indexOf('%') >= 0));
|
|
|
+}
|
|
|
+User.prototype.setMode = function(mode) {
|
|
|
+ if (mode == 'h' || mode == 'o' || mode == 'q' || mode == 'a')
|
|
|
+ this.admin = true;
|
|
|
+};
|
|
|
+User.prototype.unsetMode = function(mode) {
|
|
|
+ if (mode == 'h' || mode == 'o' || mode == 'q' || mode == 'a')
|
|
|
+ this.admin = false;
|
|
|
+};
|
|
|
+
|
|
|
+function KnackiBot(previousData) {
|
|
|
+ var _this = this;
|
|
|
+ this.name = "Knackizz";
|
|
|
+ this.room = "#quizz";
|
|
|
+ this.init = false;
|
|
|
+ this.password = NS_PASSWORD;
|
|
|
+ this.users = {};
|
|
|
+ this.activeUsers = {};
|
|
|
+
|
|
|
+ if (previousData) {
|
|
|
+ for (var i in previousData.scores) {
|
|
|
+ this.users[i.toLowerCase()] = new User(i);
|
|
|
+ this.users[i.toLowerCase()].score = previousData.scores[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.mySQLExportWrapper();
|
|
|
+
|
|
|
+ this.bot = new irc.Client("irc.knacki.info", this.name, {
|
|
|
+ channels: [ this.room ],
|
|
|
+ userName: this.name,
|
|
|
+ realName: this.name,
|
|
|
+ stripColors: true
|
|
|
+ });
|
|
|
+ this.bot.addListener("error", console.error);
|
|
|
+ this.bot.addListener("join" +this.room, (nick) => {
|
|
|
+ if (nick == _this.name) {
|
|
|
+ if (USE_NS) {
|
|
|
+ _this.bot.say("NickServ", "recover " +_this.name +" " +_this.password);
|
|
|
+ _this.bot.say("NickServ", "identify " +_this.password);
|
|
|
+ }
|
|
|
+ _this.init = true;
|
|
|
+ _this.reloadDb();
|
|
|
+ } else {
|
|
|
+ _this.users[nick.toLowerCase()] = _this.users[nick.toLowerCase()] || new User(nick);
|
|
|
+ _this.activeUsers[nick.toLowerCase()] = true;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.bot.addListener("names"+this.room, (nicks) => {
|
|
|
+ var oldusers = _this.users;
|
|
|
+ _this.activeUsers = {};
|
|
|
+ for (var i in nicks) {
|
|
|
+ var u = _this.users[i.toLowerCase()] = (_this.users[i.toLowerCase()] || new User(i));
|
|
|
+ u.setModeChar(nicks[i]);
|
|
|
+ _this.activeUsers[i.toLowerCase()] = true;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.bot.addListener("part"+this.room, (user) => delete _this.activeUsers[user.toLowerCase()]);
|
|
|
+ this.bot.addListener("kick"+this.room, (user) => delete _this.activeUsers[user.toLowerCase()]);
|
|
|
+ this.bot.addListener("+mode", (channel, by, mode, user) => {
|
|
|
+ if (user === _this.name) {
|
|
|
+ usersToVoice = [];
|
|
|
+ for (var i in _this.users) {
|
|
|
+ if (_this.users[i].score && _this.activeUsers[i])
|
|
|
+ usersToVoice.push(i);
|
|
|
+ }
|
|
|
+ _this.voice(usersToVoice);
|
|
|
+ } else {
|
|
|
+ user && _this.users[user.toLowerCase()] && _this.users[user.toLowerCase()].setMode(mode);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.bot.addListener("nick", (oldNick, newNick) => {
|
|
|
+ _this.users[newNick.toLowerCase()] = _this.users[newNick.toLowerCase()] || new User(newNick);
|
|
|
+ _this.users[newNick.toLowerCase()].isAdmin = _this.users[oldNick.toLowerCase()] && _this.users[oldNick.toLowerCase()].isAdmin;
|
|
|
+ _this.activeUsers[newNick.toLowerCase()] = true;
|
|
|
+ delete _this.activeUsers[oldNick.toLowerCase()];
|
|
|
+ });
|
|
|
+ this.bot.addListener("-mode", (channel, by, mode, user) => {
|
|
|
+ user && _this.users[user.toLowerCase()] && _this.users[user.toLowerCase()].unsetMode(mode);
|
|
|
+ });
|
|
|
+ this.bot.addListener("message"+this.room, (user, text) => {
|
|
|
+ _this.users[user.toLowerCase()] && _this.onMessage(user, _this.users[user.toLowerCase()], text.trimEnd());
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.mySQLExportWrapper = function() {
|
|
|
+ if (!USE_MYSQL)
|
|
|
+ return;
|
|
|
+ var msRemaining = 0,
|
|
|
+ lastMySQLExport = Cache.getLastMysqlSave();
|
|
|
+ if (lastMySQLExport)
|
|
|
+ msRemaining = GAME_DURATION -(Date.now() -lastMySQLExport);
|
|
|
+ if (msRemaining <= 0)
|
|
|
+ this.mySQLExport();
|
|
|
+ else
|
|
|
+ this.exportScoresTimer = setTimeout(this.mySQLExportWrapper.bind(this), Math.min(2147483000, msRemaining));
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.mySQLExport = function() {
|
|
|
+ this.exportScores().then(() => {
|
|
|
+ Cache.setExportTs();
|
|
|
+ this.resetScores();
|
|
|
+ console.log("Successfully exported scores to MySQL");
|
|
|
+ this.mySQLExportWrapper();
|
|
|
+ }).catch((errString) => {
|
|
|
+ console.error("mySQL Export error saving to database: ", errString);
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.exportScores = function() {
|
|
|
+ return new Promise((ok, ko) => {
|
|
|
+ var ts = Cache.getLastMysqlSave() || START_TIME;
|
|
|
+ ts = Math.floor(ts / 1000) *1000;
|
|
|
+ ts = MySQL.escape(new Date(ts));
|
|
|
+ ts = ts.substr(1, ts.length -2);
|
|
|
+ var sep = ts.lastIndexOf('.');
|
|
|
+ if (sep > 12) ts = ts.substr(0, sep);
|
|
|
+
|
|
|
+ var toSave = [];
|
|
|
+ for (var i in this.users)
|
|
|
+ if (this.users[i].score) {
|
|
|
+ toSave.push(this.users[i].name);
|
|
|
+ toSave.push(this.users[i].score);
|
|
|
+ }
|
|
|
+ if (toSave.length == 0)
|
|
|
+ return ok();
|
|
|
+ MySQL.execute("INSERT INTO " +MySQL_PERIOD_TABLE +" (start, host) VALUES(?, ?)", [ts, HOSTNAME], (err, result) => {
|
|
|
+ if (err || !result.insertId)
|
|
|
+ return ko(err || "Cannot get last inserted id");
|
|
|
+ MySQL.execute("INSERT INTO " +MySQL_SCORES_TABLE +"(period_id, pseudo, score) VALUES " +(",("+result.insertId+",?,?)").repeat(toSave.length /2).substr(1), toSave, (err) => {
|
|
|
+ if (err)
|
|
|
+ ko(err);
|
|
|
+ else
|
|
|
+ ok();
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.resetScores = function() {
|
|
|
+ if (this.sumScores(false) > 0) {
|
|
|
+ this.sendMsg("Fin de la manche ! Voici les scores finaux:");
|
|
|
+ this.sendScore(false);
|
|
|
+ }
|
|
|
+ for (var i in this.users)
|
|
|
+ this.users[i].score = 0;
|
|
|
+ Cache.SetScores({});
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.voice = function(username) {
|
|
|
+ var usernames = Array.isArray(username) ? username : [ username ];
|
|
|
+ if (usernames.length > 10)
|
|
|
+ {
|
|
|
+ this.voice(usernames.slice(0, 10));
|
|
|
+ this.voice(usernames.slice(10));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ usernames.splice(0, 0, "MODE", this.room, "+" +"v".repeat(usernames.length));
|
|
|
+ this.bot.send.apply(this.bot, usernames);
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.sendNotice = function(username, msg) {
|
|
|
+ this.bot.notice(username, msg);
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.sendMsg = function(msg) {
|
|
|
+ this.bot.say(this.room, msg);
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.stop = function() {
|
|
|
+ this.timer && clearTimeout(this.timer);
|
|
|
+ this.timer = null;
|
|
|
+ this.currentQuestion = null;
|
|
|
+ this.currentHint = 0;
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.onTick = function() {
|
|
|
+ if (!this.currentQuestion)
|
|
|
+ return;
|
|
|
+ if (this.currentHint < 3)
|
|
|
+ this.sendNextHint(false);
|
|
|
+ else {
|
|
|
+ var response = Array.isArray(this.currentQuestion.response) ?
|
|
|
+ this.currentQuestion.response.map(i => "\""+i+"\"").join(" ou ") :
|
|
|
+ this.currentQuestion.response;
|
|
|
+ this.sendMsg("Perdu, la réponse était: " +response);
|
|
|
+ this.nextQuestionWrapper();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.resetTimer = function() {
|
|
|
+ this.timer && clearTimeout(this.timer);
|
|
|
+ this.timer = setInterval(this.onTick.bind(this), AUTO_HINT_DELAY);
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.sendNextHint = function(resetTimer) {
|
|
|
+ this.sendMsg(this.currentQuestion.getHint(this.currentHint));
|
|
|
+ ++this.currentHint;
|
|
|
+ this.lastHint = Date.now();
|
|
|
+ resetTimer !== false && this.resetTimer();
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.nextQuestionWrapper = function() {
|
|
|
+ this.currentQuestion.end();
|
|
|
+ this.currentQuestion = null;
|
|
|
+ setTimeout(this.nextQuestion.bind(this), NEXT_QUESTION_DELAY);
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.nextQuestion = function() {
|
|
|
+ this.currentQuestion = this.questions[Math.floor(Math.random() * this.questions.length)];
|
|
|
+ console.log(this.currentQuestion);
|
|
|
+ this.currentHint = 0;
|
|
|
+ this.questionDate = Date.now();
|
|
|
+ this.sendMsg("#" +this.currentQuestion.id +" " +this.currentQuestion.question);
|
|
|
+ this.sendNextHint();
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.start = function() {
|
|
|
+ if (this.reloading) {
|
|
|
+ this.sendMsg("Error: database still reloading");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!this.currentQuestion) {
|
|
|
+ this.nextQuestion();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.reloadDb = function() {
|
|
|
+ var _this = this;
|
|
|
+ this.stop();
|
|
|
+ this.reloading = true;
|
|
|
+ initQuestionList(QUESTIONS_PATH).then(questions => {
|
|
|
+ _this.reloading = false;
|
|
|
+ _this.questions = questions;
|
|
|
+ _this.sendMsg(questions.length +" questions loaded from database");
|
|
|
+ _this.start();
|
|
|
+ }).catch(err => {
|
|
|
+ console.error(err);
|
|
|
+ _this.sendMsg(err);
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.delScore = function(user) {
|
|
|
+ var data = this.users[user.toLowerCase()];
|
|
|
+ if (!data) {
|
|
|
+ this.sendMsg("User not found...");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!data.score) {
|
|
|
+ this.sendMsg("Score for user already null");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ data.score = 0;
|
|
|
+ this.sendMsg("Removed score");
|
|
|
+ Cache.SetScores(this.users);
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.sumScores = function(onlyPresent) {
|
|
|
+ var score = 0;
|
|
|
+ for (var i in this.users)
|
|
|
+ score += (this.users[i].score && (this.activeUsers[i] || !onlyPresent)) ? this.users[i].score : 0;
|
|
|
+ return score;
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.sendScore = function(onlyPresent) {
|
|
|
+ var scores = [];
|
|
|
+ for (var i in this.users)
|
|
|
+ this.users[i].score && (this.activeUsers[i] || !onlyPresent) && scores.push({name: this.users[i].name, score: this.users[i].score});
|
|
|
+ if (scores.length == 0) {
|
|
|
+ this.sendMsg("Pas de points pour le moment");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ scores = scores.sort((a, b) => b.score - a.score).slice(0, 10);
|
|
|
+ var index = 0;
|
|
|
+ var scoreLines = arrayPad(scores.map(i => [ ((++index) +"."), i.name, (i.score +" points") ]));
|
|
|
+ if (scoreLines[0].length < 30) {
|
|
|
+ // merge score lines 2 by 2
|
|
|
+ var tmp = [];
|
|
|
+ for (var i =0, len = scoreLines.length; i < len; i += 2)
|
|
|
+ tmp.push((scoreLines[i] || "") +" - " +(scoreLines[i +1] || ""));
|
|
|
+ scoreLines = tmp;
|
|
|
+ }
|
|
|
+ scoreLines.forEach(i => this.sendMsg(i));
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.computeScore = function(username) {
|
|
|
+ var rep = Array.isArray(this.currentQuestion.response) ? this.currentQuestion.response.map(i => '"'+i+'"').join(" ou ") : this.currentQuestion.response,
|
|
|
+ responseMsg = "Réponse `" +rep +"` trouvée en " +Math.floor((Date.now() -this.questionDate) / 1000) +" secondes ";
|
|
|
+ if (this.currentHint <= 1)
|
|
|
+ responseMsg += "sans indice";
|
|
|
+ else if (this.currentHint === 2)
|
|
|
+ responseMsg += "avec 1 seul indice";
|
|
|
+ else
|
|
|
+ responseMsg += "avec " +this.currentHint +" indices";
|
|
|
+ var score = 4 - this.currentHint;
|
|
|
+ this.sendMsg(responseMsg);
|
|
|
+ this.sendMsg(score +" points pour " +username);
|
|
|
+ return score;
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.findQuestionById = function(qId) {
|
|
|
+ for (var i =0, len = this.questions.length; i < len; ++i) {
|
|
|
+ if (this.questions[i].id === qId)
|
|
|
+ return this.questions[i];
|
|
|
+ if (this.questions[i].id > qId)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+KnackiBot.prototype.onMessage = function(username, user, msg) {
|
|
|
+ if (msg.startsWith("!reload")) {
|
|
|
+ if (user.admin)
|
|
|
+ this.reloadDb();
|
|
|
+ else
|
|
|
+ this.sendMsg("Must be channel operator");
|
|
|
+ }
|
|
|
+ else if (msg.startsWith("!aide")) {
|
|
|
+ this.sendMsg("Usage: !aide | !indice | !reload | !next | !rename | !score [del pseudo] || !top");
|
|
|
+ }
|
|
|
+ else if (msg === "!indice" || msg === "!conseil") {
|
|
|
+ if (this.currentQuestion) {
|
|
|
+ if (this.currentHint < 3) {
|
|
|
+ if (Date.now() -this.lastHint > MIN_HINT_DELAY)
|
|
|
+ this.sendNextHint();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ this.sendMsg("Pas plus d'indice...");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (msg === "!next") {
|
|
|
+ if (user.admin) {
|
|
|
+ if (!this.currentQuestion)
|
|
|
+ return;
|
|
|
+ var response = Array.isArray(this.currentQuestion.response) ?
|
|
|
+ this.currentQuestion.response.map(i => "\""+i+"\"").join(" ou ") :
|
|
|
+ this.currentQuestion.response;
|
|
|
+ this.sendMsg("La réponse était: " +response);
|
|
|
+ this.nextQuestionWrapper();
|
|
|
+ } else {
|
|
|
+ this.sendMsg("Must be channel operator");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (msg.startsWith("!report list")) {
|
|
|
+ if (user.admin) {
|
|
|
+ var questions = Cache.getReportedQuestions();
|
|
|
+ for (var i in questions)
|
|
|
+ for (var j in questions[i])
|
|
|
+ questions[i][j] = (new Date(questions[i][j])).toLocaleString();
|
|
|
+ this.sendNotice(username, JSON.stringify(questions));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ this.sendMsg("Must be channel operator");
|
|
|
+ }
|
|
|
+ else if (msg.startsWith("!report del ")) {
|
|
|
+ if (user.admin) {
|
|
|
+ var questionId = msg.substr("!report del ".length).trim();
|
|
|
+ if (questionId.startsWith('#'))
|
|
|
+ questionId = questionId.substr(1);
|
|
|
+ questionId = Number(questionId);
|
|
|
+ if (isNaN(questionId)) {
|
|
|
+ this.sendMsg("Erreur: Usage: !report del #1234");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Cache.unreportQuestion(questionId);
|
|
|
+ this.sendMsg("Question #" +questionId +" marquée comme restaurée");
|
|
|
+ }
|
|
|
+ else
|
|
|
+ this.sendMsg("Must be channel operator");
|
|
|
+ }
|
|
|
+ else if (msg.startsWith("!report ")) {
|
|
|
+ var questionId = msg.substr("!report ".length).trim();
|
|
|
+ if (questionId.startsWith('#'))
|
|
|
+ questionId = questionId.substr(1);
|
|
|
+ questionId = Number(questionId);
|
|
|
+ if (isNaN(questionId)) {
|
|
|
+ this.sendMsg("Erreur: Usage: !report #1234");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!this.findQuestionById(questionId))
|
|
|
+ this.sendMsg("Erreur: question non trouvée");
|
|
|
+ else {
|
|
|
+ Cache.reportQuestion(questionId, username);
|
|
|
+ this.sendMsg("Question #" +questionId +" marquée comme déffectueuse par " +username);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (msg.startsWith("!score del ")) {
|
|
|
+ if (user.admin)
|
|
|
+ this.delScore(msg.substr("!score del ".length).trim());
|
|
|
+ else
|
|
|
+ this.sendMsg("Must be channel operator");
|
|
|
+ }
|
|
|
+ else if (msg.startsWith("!rename")) {
|
|
|
+ if (!user.admin) {
|
|
|
+ this.sendMsg("Must be channel operator");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ var args = (msg.split(/\s+/)).splice(1);
|
|
|
+ if (args.length < 2) {
|
|
|
+ this.sendNotice(username, "Usage: !rename nouveau_pseudo ancien_pseudo [ancien_pseudo...]");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ var sum = 0,
|
|
|
+ users = [],
|
|
|
+ target = args[0];
|
|
|
+
|
|
|
+ args = args.map(username => username.toLowerCase());
|
|
|
+ var userToMod = null;
|
|
|
+ for (var i in this.users) {
|
|
|
+ var userIndex = args.indexOf(i.toLowerCase());
|
|
|
+ if (userIndex > 0) {
|
|
|
+ sum += this.users[i].score;
|
|
|
+ this.users[i].score = 0;
|
|
|
+ if (!this.activeUsers[i])
|
|
|
+ delete this.users[i];
|
|
|
+ }
|
|
|
+ else if (userIndex == 0)
|
|
|
+ userToMod = this.users[i];
|
|
|
+ }
|
|
|
+ if (!userToMod) {
|
|
|
+ userToMod = this.users[target] = new User(target);
|
|
|
+ this.sendMsg("Created user " +target);
|
|
|
+ }
|
|
|
+ userToMod.score += sum;
|
|
|
+ Cache.SetScores(this.users);
|
|
|
+ }
|
|
|
+ else if (msg.startsWith("!score")) {
|
|
|
+ this.sendScore(true);
|
|
|
+ }
|
|
|
+ else if (msg.startsWith("!top")) {
|
|
|
+ this.sendScore(false);
|
|
|
+ }
|
|
|
+ else if (this.currentQuestion) {
|
|
|
+ var dieOnFailure = this.currentQuestion.isBoolean() && this.currentQuestion.isBoolean(Question.normalize(msg));
|
|
|
+ if (this.currentQuestion.check(msg)) {
|
|
|
+ user.score += this.computeScore(username);
|
|
|
+ Cache.SetScores(this.users);
|
|
|
+ this.voice(username);
|
|
|
+ this.nextQuestionWrapper();
|
|
|
+ }
|
|
|
+ else if (dieOnFailure) {
|
|
|
+ var rep = Array.isArray(this.currentQuestion.response) ? this.currentQuestion.response.map(i => '"'+i+'"').join(" ou ") : this.currentQuestion.response;
|
|
|
+ this.sendMsg("Perdu, la réponse était: " +rep);
|
|
|
+ this.nextQuestionWrapper();
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+new KnackiBot(Cache.GetData());
|
|
|
+
|