index.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. const irc = require("irc"),
  2. fs = require("fs"),
  3. readline = require("readline"),
  4. cache = new (require("data-store"))({ path: "persistentDb.json" }),
  5. arrayPad = require('./strpad.js').arrayPad;
  6. Object.assign(global, require("./config.js"));
  7. const MySQL = USE_MYSQL ? require("mysql2").createConnection({host: MySQL_HOST, user: MySQL_USER, database: MySQL_DB, password: MySQL_PASS}) : null;
  8. const HOSTNAME = require('os').hostname(); // For Mysql bot identification
  9. function Cache() {}
  10. Cache.SetScores = function(scores) {
  11. var dataToSet = {};
  12. for (var i in scores)
  13. if (scores[i].score)
  14. dataToSet[scores[i].name] = scores[i].score;
  15. cache.set("scores", dataToSet);
  16. cache.set("saveTs", Date.now());
  17. }
  18. Cache.getReportedQuestions = function(questionId, username) {
  19. return cache.get("report") || {};
  20. }
  21. Cache.reportQuestion = function(questionId, username) {
  22. var reported = cache.get("report") || {};
  23. var questionReported = reported[questionId] || {};
  24. reported[questionId] = questionReported;
  25. if (questionReported[username])
  26. return;
  27. questionReported[username] = Date.now();
  28. cache.set("report", reported);
  29. }
  30. Cache.unreportQuestion = function(questionId) {
  31. var reported = cache.get("report") || {};
  32. if (!reported[questionId])
  33. return;
  34. delete reported[questionId];
  35. cache.set("report", reported);
  36. }
  37. Cache.setExportTs = function() {
  38. cache.set("mysqlTs", Date.now());
  39. }
  40. Cache.getLastMysqlSave = function() {
  41. return cache.get("mysqlTs") || 0;
  42. }
  43. Cache.GetData = function() {
  44. return {
  45. scores: cache.get("scores"),
  46. lastSave: cache.get("saveTs") || 0,
  47. lastMysqlExport: cache.get("mysqlTs") || 0
  48. };
  49. }
  50. function Question(id, obj) {
  51. this.id = id;
  52. this.question = obj.question;
  53. this.response = obj.response;
  54. this.normalizedResponse = "";
  55. if (Array.isArray(obj.response))
  56. this.normalizedResponse = this.response.map(i => Question.normalize(i));
  57. else
  58. this.normalizedResponse = Question.normalize(this.response);
  59. }
  60. Question.normalize = function(str) {
  61. return str.normalize('NFD').replace(/[\u0300-\u036f]/g, "").toLowerCase().trim();
  62. }
  63. const QuestionType = { bool: {}, number: {}, string: {} };
  64. Question.prototype.booleanValue = function(str) {
  65. str = str || (Array.isArray(this.normalizedResponse) ? this.normalizedResponse[0] : this.normalizedResponse);
  66. var index = ["non", "oui", "faux", "vrai"].indexOf(str);
  67. if (index >= 0)
  68. return index % 2 === 1;
  69. return undefined;
  70. }
  71. Question.prototype.isBoolean = function(str) {
  72. return this.booleanValue(str) !== undefined;
  73. }
  74. String.prototype.isWord = function() {
  75. return (/\W/).exec(this) === null;
  76. }
  77. Question.prototype.getQuestionType = function() {
  78. if (this.isBoolean())
  79. return QuestionType.bool;
  80. if (!isNaN(Number(this.response)))
  81. return QuestionType.number;
  82. return QuestionType.string;
  83. }
  84. Question.toHint = function(response, normalizedResponse, boundaries, responseIndex, questionType, hintLevel) {
  85. if (questionType === QuestionType.string) {
  86. if (hintLevel == 0)
  87. return response.replace(/[\w]/g, '*');
  88. else if (hintLevel == 1)
  89. return response.replace(/[\w]/g, (a, b) => b ? '*' : response.charAt(b));
  90. else if (normalizedResponse.isWord() && !responseIndex) {
  91. var displayed = [];
  92. const revealPercent = 0.1;
  93. displayed[normalizedResponse.length -2] = 1;
  94. displayed.fill(1, 0, normalizedResponse.length-1).fill(0, Math.ceil(normalizedResponse.length * revealPercent), normalizedResponse.length);
  95. displayed.sort(()=>Math.random() > 0.5?-1:1)
  96. return normalizedResponse.replace(/./g, (a, b) => b && !displayed[b -1] ? '*':response.charAt(b));
  97. }
  98. else
  99. return normalizedResponse.replace(/[\w]+/g, (a, wordIndex) => a.replace(/./g, (a, b) => b ? '*':response.charAt(wordIndex)));
  100. }
  101. else if (questionType === QuestionType.number) {
  102. const responseInt = Number(normalizedResponse),
  103. randomMin = ([ 30, 10, 3 ])[hintLevel],
  104. randomSpread = 5 * (5 -hintLevel);
  105. boundaries[0] = Math.max(
  106. Math.floor(responseInt -(Math.random() *randomSpread) -randomMin),
  107. boundaries[0]);
  108. boundaries[1] = Math.min(
  109. Math.ceil(responseInt +(Math.random() *randomSpread) +randomMin),
  110. boundaries[1]);
  111. return "Un nombre entre " +boundaries[0] +" et " +boundaries[1];
  112. }
  113. else
  114. console.error("Unknown response type !");
  115. };
  116. Question.prototype.getHint = function(hintLevel) {
  117. var type = this.getQuestionType();
  118. if (type === QuestionType.bool)
  119. return "Vrai / Faux ?";
  120. else if (type === QuestionType.number)
  121. this.boundaries = this.boundaries || [ -Infinity, Infinity];
  122. if (!Array.isArray(this.response))
  123. return Question.toHint(this.response, this.normalizedResponse, this.boundaries, 0, type, hintLevel);
  124. var hints = [];
  125. for (var i =0, len = this.response.length; i < len; ++i)
  126. hints.push(Question.toHint(this.response[i], this.normalizedResponse[i], this.boundaries, i, type, hintLevel));
  127. return hints.join (" ou ");
  128. }
  129. Question.prototype.check = function(response) {
  130. response = Question.normalize(response);
  131. var boolValue = this.booleanValue();
  132. if (boolValue !== undefined)
  133. return boolValue === this.booleanValue(response);
  134. if (Array.isArray(this.normalizedResponse))
  135. return this.normalizedResponse.indexOf(response) >= 0;
  136. return response === this.normalizedResponse;
  137. }
  138. Question.prototype.end = function() {
  139. if (this.boundaries)
  140. delete this.boundaries;
  141. }
  142. function initQuestionList(filename) {
  143. return new Promise((ok, ko) => {
  144. var stream = fs.createReadStream(filename),
  145. reader = readline.createInterface({input: stream}),
  146. questions = [],
  147. lineNo = 0,
  148. borken = false;
  149. reader.on("line", line => {
  150. if (borken) return;
  151. try {
  152. var question = new Question(++lineNo, JSON.parse(line));
  153. if (question.question && question.response)
  154. questions.push(question);
  155. } catch (e) {
  156. console.error("Failed to load Database: ", e, "on line", lineNo);
  157. borken = true;
  158. reader.close();
  159. stream.destroy();
  160. ko("Failed to load database: syntax error on question #" +lineNo);
  161. }
  162. });
  163. reader.on("close", () => {
  164. if (!borken)
  165. ok(questions);
  166. });
  167. });
  168. }
  169. function User(nick) {
  170. this.admin = false;
  171. this.name = nick;
  172. this.score = 0;
  173. }
  174. User.prototype.setModeChar = function(mode) {
  175. this.admin = !!(mode && (mode.indexOf('~') >= 0 || mode.indexOf('@') >= 0 || mode.indexOf('%') >= 0));
  176. }
  177. User.prototype.setMode = function(mode) {
  178. if (mode == 'h' || mode == 'o' || mode == 'q' || mode == 'a')
  179. this.admin = true;
  180. };
  181. User.prototype.unsetMode = function(mode) {
  182. if (mode == 'h' || mode == 'o' || mode == 'q' || mode == 'a')
  183. this.admin = false;
  184. };
  185. function KnackiBot(previousData) {
  186. var _this = this;
  187. this.name = "Knackizz";
  188. this.room = "#quizz";
  189. this.init = false;
  190. this.password = NS_PASSWORD;
  191. this.users = {};
  192. this.activeUsers = {};
  193. if (previousData) {
  194. for (var i in previousData.scores) {
  195. this.users[i.toLowerCase()] = new User(i);
  196. this.users[i.toLowerCase()].score = previousData.scores[i];
  197. }
  198. }
  199. this.mySQLExportWrapper();
  200. this.bot = new irc.Client("irc.knacki.info", this.name, {
  201. channels: [ this.room ],
  202. userName: this.name,
  203. realName: this.name,
  204. stripColors: true
  205. });
  206. this.bot.addListener("error", console.error);
  207. this.bot.addListener("join" +this.room, (nick) => {
  208. if (nick == _this.name) {
  209. if (USE_NS) {
  210. _this.bot.say("NickServ", "recover " +_this.name +" " +_this.password);
  211. _this.bot.say("NickServ", "identify " +_this.password);
  212. }
  213. _this.init = true;
  214. _this.reloadDb();
  215. } else {
  216. _this.users[nick.toLowerCase()] = _this.users[nick.toLowerCase()] || new User(nick);
  217. _this.activeUsers[nick.toLowerCase()] = true;
  218. }
  219. });
  220. this.bot.addListener("names"+this.room, (nicks) => {
  221. var oldusers = _this.users;
  222. _this.activeUsers = {};
  223. for (var i in nicks) {
  224. var u = _this.users[i.toLowerCase()] = (_this.users[i.toLowerCase()] || new User(i));
  225. u.setModeChar(nicks[i]);
  226. _this.activeUsers[i.toLowerCase()] = true;
  227. }
  228. });
  229. this.bot.addListener("part"+this.room, (user) => delete _this.activeUsers[user.toLowerCase()]);
  230. this.bot.addListener("kick"+this.room, (user) => delete _this.activeUsers[user.toLowerCase()]);
  231. this.bot.addListener("+mode", (channel, by, mode, user) => {
  232. if (user === _this.name) {
  233. usersToVoice = [];
  234. for (var i in _this.users) {
  235. if (_this.users[i].score && _this.activeUsers[i])
  236. usersToVoice.push(i);
  237. }
  238. _this.voice(usersToVoice);
  239. } else {
  240. user && _this.users[user.toLowerCase()] && _this.users[user.toLowerCase()].setMode(mode);
  241. }
  242. });
  243. this.bot.addListener("nick", (oldNick, newNick) => {
  244. _this.users[newNick.toLowerCase()] = _this.users[newNick.toLowerCase()] || new User(newNick);
  245. _this.users[newNick.toLowerCase()].isAdmin = _this.users[oldNick.toLowerCase()] && _this.users[oldNick.toLowerCase()].isAdmin;
  246. _this.activeUsers[newNick.toLowerCase()] = true;
  247. delete _this.activeUsers[oldNick.toLowerCase()];
  248. });
  249. this.bot.addListener("-mode", (channel, by, mode, user) => {
  250. user && _this.users[user.toLowerCase()] && _this.users[user.toLowerCase()].unsetMode(mode);
  251. });
  252. this.bot.addListener("message"+this.room, (user, text) => {
  253. _this.users[user.toLowerCase()] && _this.onMessage(user, _this.users[user.toLowerCase()], text.trimEnd());
  254. });
  255. }
  256. KnackiBot.prototype.mySQLExportWrapper = function() {
  257. if (!USE_MYSQL)
  258. return;
  259. var msRemaining = 0,
  260. lastMySQLExport = Cache.getLastMysqlSave();
  261. if (lastMySQLExport)
  262. msRemaining = GAME_DURATION -(Date.now() -lastMySQLExport);
  263. if (msRemaining <= 0)
  264. this.mySQLExport();
  265. else
  266. this.exportScoresTimer = setTimeout(this.mySQLExportWrapper.bind(this), Math.min(2147483000, msRemaining));
  267. }
  268. KnackiBot.prototype.mySQLExport = function() {
  269. this.exportScores().then(() => {
  270. Cache.setExportTs();
  271. this.resetScores();
  272. console.log("Successfully exported scores to MySQL");
  273. this.mySQLExportWrapper();
  274. }).catch((errString) => {
  275. console.error("mySQL Export error saving to database: ", errString);
  276. });
  277. }
  278. KnackiBot.prototype.exportScores = function() {
  279. return new Promise((ok, ko) => {
  280. var ts = Cache.getLastMysqlSave() || START_TIME;
  281. ts = Math.floor(ts / 1000) *1000;
  282. ts = MySQL.escape(new Date(ts));
  283. ts = ts.substr(1, ts.length -2);
  284. var sep = ts.lastIndexOf('.');
  285. if (sep > 12) ts = ts.substr(0, sep);
  286. var toSave = [];
  287. for (var i in this.users)
  288. if (this.users[i].score) {
  289. toSave.push(this.users[i].name);
  290. toSave.push(this.users[i].score);
  291. }
  292. if (toSave.length == 0)
  293. return ok();
  294. MySQL.execute("INSERT INTO " +MySQL_PERIOD_TABLE +" (start, host) VALUES(?, ?)", [ts, HOSTNAME], (err, result) => {
  295. if (err || !result.insertId)
  296. return ko(err || "Cannot get last inserted id");
  297. MySQL.execute("INSERT INTO " +MySQL_SCORES_TABLE +"(period_id, pseudo, score) VALUES " +(",("+result.insertId+",?,?)").repeat(toSave.length /2).substr(1), toSave, (err) => {
  298. if (err)
  299. ko(err);
  300. else
  301. ok();
  302. });
  303. });
  304. });
  305. }
  306. KnackiBot.prototype.resetScores = function() {
  307. if (this.sumScores(false) > 0) {
  308. this.sendMsg("Fin de la manche ! Voici les scores finaux:");
  309. this.sendScore(false);
  310. }
  311. for (var i in this.users)
  312. this.users[i].score = 0;
  313. Cache.SetScores({});
  314. }
  315. KnackiBot.prototype.voice = function(username) {
  316. var usernames = Array.isArray(username) ? username : [ username ];
  317. if (usernames.length > 10)
  318. {
  319. this.voice(usernames.slice(0, 10));
  320. this.voice(usernames.slice(10));
  321. return;
  322. }
  323. usernames.splice(0, 0, "MODE", this.room, "+" +"v".repeat(usernames.length));
  324. this.bot.send.apply(this.bot, usernames);
  325. }
  326. KnackiBot.prototype.sendNotice = function(username, msg) {
  327. this.bot.notice(username, msg);
  328. }
  329. KnackiBot.prototype.sendMsg = function(msg) {
  330. this.bot.say(this.room, msg);
  331. }
  332. KnackiBot.prototype.stop = function() {
  333. this.timer && clearTimeout(this.timer);
  334. this.timer = null;
  335. this.currentQuestion = null;
  336. this.currentHint = 0;
  337. }
  338. KnackiBot.prototype.onTick = function() {
  339. if (!this.currentQuestion)
  340. return;
  341. if (this.currentHint < 3)
  342. this.sendNextHint(false);
  343. else {
  344. var response = Array.isArray(this.currentQuestion.response) ?
  345. this.currentQuestion.response.map(i => "\""+i+"\"").join(" ou ") :
  346. this.currentQuestion.response;
  347. this.sendMsg("Perdu, la réponse était: " +response);
  348. this.nextQuestionWrapper();
  349. }
  350. }
  351. KnackiBot.prototype.resetTimer = function() {
  352. this.timer && clearTimeout(this.timer);
  353. this.timer = setInterval(this.onTick.bind(this), AUTO_HINT_DELAY);
  354. }
  355. KnackiBot.prototype.sendNextHint = function(resetTimer) {
  356. this.sendMsg(this.currentQuestion.getHint(this.currentHint));
  357. ++this.currentHint;
  358. this.lastHint = Date.now();
  359. resetTimer !== false && this.resetTimer();
  360. }
  361. KnackiBot.prototype.nextQuestionWrapper = function() {
  362. this.currentQuestion.end();
  363. this.currentQuestion = null;
  364. setTimeout(this.nextQuestion.bind(this), NEXT_QUESTION_DELAY);
  365. }
  366. KnackiBot.prototype.nextQuestion = function() {
  367. this.currentQuestion = this.questions[Math.floor(Math.random() * this.questions.length)];
  368. console.log(this.currentQuestion);
  369. this.currentHint = 0;
  370. this.questionDate = Date.now();
  371. this.sendMsg("#" +this.currentQuestion.id +" " +this.currentQuestion.question);
  372. this.sendNextHint();
  373. }
  374. KnackiBot.prototype.start = function() {
  375. if (this.reloading) {
  376. this.sendMsg("Error: database still reloading");
  377. return;
  378. }
  379. if (!this.currentQuestion) {
  380. this.nextQuestion();
  381. }
  382. }
  383. KnackiBot.prototype.reloadDb = function() {
  384. var _this = this;
  385. this.stop();
  386. this.reloading = true;
  387. initQuestionList(QUESTIONS_PATH).then(questions => {
  388. _this.reloading = false;
  389. _this.questions = questions;
  390. _this.sendMsg(questions.length +" questions loaded from database");
  391. _this.start();
  392. }).catch(err => {
  393. console.error(err);
  394. _this.sendMsg(err);
  395. });
  396. }
  397. KnackiBot.prototype.delScore = function(user) {
  398. var data = this.users[user.toLowerCase()];
  399. if (!data) {
  400. this.sendMsg("User not found...");
  401. return;
  402. }
  403. if (!data.score) {
  404. this.sendMsg("Score for user already null");
  405. return;
  406. }
  407. data.score = 0;
  408. this.sendMsg("Removed score");
  409. Cache.SetScores(this.users);
  410. }
  411. KnackiBot.prototype.sumScores = function(onlyPresent) {
  412. var score = 0;
  413. for (var i in this.users)
  414. score += (this.users[i].score && (this.activeUsers[i] || !onlyPresent)) ? this.users[i].score : 0;
  415. return score;
  416. }
  417. KnackiBot.prototype.sendScore = function(onlyPresent) {
  418. var scores = [];
  419. for (var i in this.users)
  420. this.users[i].score && (this.activeUsers[i] || !onlyPresent) && scores.push({name: this.users[i].name, score: this.users[i].score});
  421. if (scores.length == 0) {
  422. this.sendMsg("Pas de points pour le moment");
  423. return;
  424. }
  425. scores = scores.sort((a, b) => b.score - a.score).slice(0, 10);
  426. var index = 0;
  427. var scoreLines = arrayPad(scores.map(i => [ ((++index) +"."), i.name, (i.score +" points") ]));
  428. if (scoreLines[0].length < 30) {
  429. // merge score lines 2 by 2
  430. var tmp = [];
  431. for (var i =0, len = scoreLines.length; i < len; i += 2)
  432. tmp.push((scoreLines[i] || "") +" - " +(scoreLines[i +1] || ""));
  433. scoreLines = tmp;
  434. }
  435. scoreLines.forEach(i => this.sendMsg(i));
  436. }
  437. KnackiBot.prototype.computeScore = function(username) {
  438. var rep = Array.isArray(this.currentQuestion.response) ? this.currentQuestion.response.map(i => '"'+i+'"').join(" ou ") : this.currentQuestion.response,
  439. responseMsg = "Réponse `" +rep +"` trouvée en " +Math.floor((Date.now() -this.questionDate) / 1000) +" secondes ";
  440. if (this.currentHint <= 1)
  441. responseMsg += "sans indice";
  442. else if (this.currentHint === 2)
  443. responseMsg += "avec 1 seul indice";
  444. else
  445. responseMsg += "avec " +this.currentHint +" indices";
  446. var score = 4 - this.currentHint;
  447. this.sendMsg(responseMsg);
  448. this.sendMsg(score +" points pour " +username);
  449. return score;
  450. }
  451. KnackiBot.prototype.findQuestionById = function(qId) {
  452. for (var i =0, len = this.questions.length; i < len; ++i) {
  453. if (this.questions[i].id === qId)
  454. return this.questions[i];
  455. if (this.questions[i].id > qId)
  456. break;
  457. }
  458. }
  459. KnackiBot.prototype.onMessage = function(username, user, msg) {
  460. if (msg.startsWith("!reload")) {
  461. if (user.admin)
  462. this.reloadDb();
  463. else
  464. this.sendMsg("Must be channel operator");
  465. }
  466. else if (msg.startsWith("!aide")) {
  467. this.sendMsg("Usage: !aide | !indice | !reload | !next | !rename | !score [del pseudo] || !top");
  468. }
  469. else if (msg === "!indice" || msg === "!conseil") {
  470. if (this.currentQuestion) {
  471. if (this.currentHint < 3) {
  472. if (Date.now() -this.lastHint > MIN_HINT_DELAY)
  473. this.sendNextHint();
  474. }
  475. else
  476. this.sendMsg("Pas plus d'indice...");
  477. }
  478. }
  479. else if (msg === "!next") {
  480. if (user.admin) {
  481. if (!this.currentQuestion)
  482. return;
  483. var response = Array.isArray(this.currentQuestion.response) ?
  484. this.currentQuestion.response.map(i => "\""+i+"\"").join(" ou ") :
  485. this.currentQuestion.response;
  486. this.sendMsg("La réponse était: " +response);
  487. this.nextQuestionWrapper();
  488. } else {
  489. this.sendMsg("Must be channel operator");
  490. }
  491. }
  492. else if (msg.startsWith("!report list")) {
  493. if (user.admin) {
  494. var questions = Cache.getReportedQuestions();
  495. for (var i in questions)
  496. for (var j in questions[i])
  497. questions[i][j] = (new Date(questions[i][j])).toLocaleString();
  498. this.sendNotice(username, JSON.stringify(questions));
  499. }
  500. else
  501. this.sendMsg("Must be channel operator");
  502. }
  503. else if (msg.startsWith("!report del ")) {
  504. if (user.admin) {
  505. var questionId = msg.substr("!report del ".length).trim();
  506. if (questionId.startsWith('#'))
  507. questionId = questionId.substr(1);
  508. questionId = Number(questionId);
  509. if (isNaN(questionId)) {
  510. this.sendMsg("Erreur: Usage: !report del #1234");
  511. return;
  512. }
  513. Cache.unreportQuestion(questionId);
  514. this.sendMsg("Question #" +questionId +" marquée comme restaurée");
  515. }
  516. else
  517. this.sendMsg("Must be channel operator");
  518. }
  519. else if (msg.startsWith("!report ")) {
  520. var questionId = msg.substr("!report ".length).trim();
  521. if (questionId.startsWith('#'))
  522. questionId = questionId.substr(1);
  523. questionId = Number(questionId);
  524. if (isNaN(questionId)) {
  525. this.sendMsg("Erreur: Usage: !report #1234");
  526. return;
  527. }
  528. if (!this.findQuestionById(questionId))
  529. this.sendMsg("Erreur: question non trouvée");
  530. else {
  531. Cache.reportQuestion(questionId, username);
  532. this.sendMsg("Question #" +questionId +" marquée comme déffectueuse par " +username);
  533. }
  534. }
  535. else if (msg.startsWith("!score del ")) {
  536. if (user.admin)
  537. this.delScore(msg.substr("!score del ".length).trim());
  538. else
  539. this.sendMsg("Must be channel operator");
  540. }
  541. else if (msg.startsWith("!rename")) {
  542. if (!user.admin) {
  543. this.sendMsg("Must be channel operator");
  544. return;
  545. }
  546. var args = (msg.split(/\s+/)).splice(1);
  547. if (args.length < 2) {
  548. this.sendNotice(username, "Usage: !rename nouveau_pseudo ancien_pseudo [ancien_pseudo...]");
  549. return;
  550. }
  551. var sum = 0,
  552. users = [],
  553. target = args[0];
  554. args = args.map(username => username.toLowerCase());
  555. var userToMod = null;
  556. for (var i in this.users) {
  557. var userIndex = args.indexOf(i.toLowerCase());
  558. if (userIndex > 0) {
  559. sum += this.users[i].score;
  560. this.users[i].score = 0;
  561. if (!this.activeUsers[i])
  562. delete this.users[i];
  563. }
  564. else if (userIndex == 0)
  565. userToMod = this.users[i];
  566. }
  567. if (!userToMod) {
  568. userToMod = this.users[target] = new User(target);
  569. this.sendMsg("Created user " +target);
  570. }
  571. userToMod.score += sum;
  572. Cache.SetScores(this.users);
  573. }
  574. else if (msg.startsWith("!score")) {
  575. this.sendScore(true);
  576. }
  577. else if (msg.startsWith("!top")) {
  578. this.sendScore(false);
  579. }
  580. else if (this.currentQuestion) {
  581. var dieOnFailure = this.currentQuestion.isBoolean() && this.currentQuestion.isBoolean(Question.normalize(msg));
  582. if (this.currentQuestion.check(msg)) {
  583. user.score += this.computeScore(username);
  584. Cache.SetScores(this.users);
  585. this.voice(username);
  586. this.nextQuestionWrapper();
  587. }
  588. else if (dieOnFailure) {
  589. var rep = Array.isArray(this.currentQuestion.response) ? this.currentQuestion.response.map(i => '"'+i+'"').join(" ou ") : this.currentQuestion.response;
  590. this.sendMsg("Perdu, la réponse était: " +rep);
  591. this.nextQuestionWrapper();
  592. }
  593. }
  594. };
  595. new KnackiBot(Cache.GetData());