Advertisement
Guest User

Poll

a guest
Apr 18th, 2019
125
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.92 KB | None | 0 0
  1. /*
  2. * Poll chat plugin
  3. * By bumbadadabum and Zarel.
  4. * Redone by Volco and WGC for Collapsible Multi-Polls
  5. */
  6.  
  7. 'use strict';
  8.  
  9. /** @typedef {{source: string, supportHTML: boolean}} QuestionData */
  10. /** @typedef {{name: string, votes: number}} Option */
  11.  
  12. class Poll {
  13. constructor(room, questionData, options, name) {
  14. if (room.pollNumber) {
  15. room.pollNumber++;
  16. } else {
  17. room.pollNumber = 1;
  18. }
  19. this.room = room;
  20. this.pollArray = [{
  21. room: room,
  22. pollNum: room.pollNumber,
  23. question: questionData.source,
  24. supportHTML: questionData.supportHTML,
  25. voters: {},
  26. voterIps: {},
  27. totalVotes: 0,
  28. timeout: null,
  29. timeoutMins: 0,
  30. startTime: Date.now(),
  31. startedUser: WL.nameColor(name, true, true),
  32. options: new Map(),
  33. }];
  34. for (let i = 0; i < options.length; i++) {
  35. this.pollArray[0].options.set(i + 1, {name: options[i], votes: 0});
  36. }
  37. }
  38.  
  39. vote(user, option, number) {
  40. let ip = user.latestIp;
  41. let userid = user.userid;
  42. if (userid in this.pollArray[number].voters || ip in this.pollArray[number].voterIps) {
  43. return user.sendTo(this.room, "You have already voted for this poll.");
  44. }
  45. this.pollArray[number].voters[userid] = option;
  46. this.pollArray[number].voterIps[ip] = option;
  47. this.pollArray[number].options.get(option).votes++;
  48. this.pollArray[number].totalVotes++;
  49. this.update(number);
  50. }
  51.  
  52. blankvote(user, number) {
  53. let ip = user.latestIp;
  54. let userid = user.userid;
  55.  
  56. if (!(userid in this.pollArray[number].voters) || !(ip in this.pollArray[number].voterIps)) {
  57. this.pollArray[number].voters[userid] = 0;
  58. this.pollArray[number].voterIps[ip] = 0;
  59. }
  60. this.updateTo(user, number);
  61. }
  62.  
  63. generateVotes(num) {
  64. let output = `<div class="infobox"><details><summary style="margin: 2px 0 5px 0"><span style="border:1px solid #6A6;color:#484;border-radius:4px;padding:0 3px"><i class="fa fa-bar-chart"></i> Poll-${this.pollArray[num].pollNum}</span> <strong style="font-size:11pt">${this.getQuestionMarkup(num)}</strong><psicon pokemon="espeon"></summary>`;
  65. this.pollArray[num].options.forEach((option, number) => {
  66. output += `<div style="margin-top: 5px"><button class="button" style="text-align: left" value="/poll vote ${number}, ${this.pollArray[num].pollNum}" name="send" title="Vote for ${number}. ${Chat.escapeHTML(option.name)}">${number}. <strong>${this.getOptionMarkup(option, num)}</strong></button></div>`;
  67. });
  68. output += `<div style="margin-top: 7px; padding-left: 12px"><button value="/poll results ${this.pollArray[num].pollNum}" name="send" title="View results - you will not be able to vote after viewing results"><small>(View results)</small></button></div>`;
  69. output += `</details></div>`;
  70.  
  71. return output;
  72. }
  73.  
  74. generateResults(ended, option, num) {
  75. let icon = `<span style="border: 1px solid #${(ended ? '777;color:#555' : '6A6;color:#484')}; border-radius: 4px; padding: 3px"><i class="fa fa-bar-chart"></i> ${(ended ? `Poll-${this.pollArray[num].pollNum} ended` : `Poll-${this.pollArray[num].pollNum}`)}</span>`;
  76. let totalVotes = `<br /><span style="font-style: italic; font-size: 9pt; color: #79330A;">[Total Votes: ${this.pollArray[num].totalVotes}] (Started by ${this.pollArray[num].startedUser} Started on: ${new Date(this.pollArray[num].startTime)})</span>`;
  77. let output = `<div class="infobox"><details open><summary style="margin: 2px 0 5px 0">${icon} <strong style="font-size: 11pt">${this.getQuestionMarkup(num)}</strong><psicon pokemon="espeon"></summary>`;
  78. output += totalVotes;
  79. output += `<div style="padding: 8px 15px;"><font color="red"><small><center>(Options with 0 votes are not shown)</center></small></font>`;
  80. output += `<table cellspacing="0" style="width: 100%; margin-top: 3px;">`;
  81. let iter = this.pollArray[num].options.entries();
  82.  
  83. let i = iter.next();
  84. let c = 0;
  85. let colors = ['#00FFFF', '#66CCCC', '#388E8E'];
  86. while (!i.done) {
  87. if (i.value[1].votes && i.value[1].votes !== 0) {
  88. let percentage = Math.round((i.value[1].votes * 100) / (this.pollArray[num].totalVotes || 1));
  89. output += `<tr><td><strong>${(i.value[0] === option ? '<em>' : '')} ${Chat.escapeHTML(i.value[1].name)} ${(i.value[0] === option ? '</em>' : '')}</strong> <small>(${i.value[1].votes} vote${(i.value[1].votes === 1 ? '' : 's')})</small></td><td><span style="font-size: 7pt; background: ${colors[c % 3]}; padding-right: ${(percentage * 3)}px; border-radius: 20px;"></span><small>&nbsp;${percentage}%</small></td></tr>`;
  90. }
  91. i = iter.next();
  92. c++;
  93. }
  94. if (option === 0 && !ended) output += `<div style="text-align:center; color:red;"><small>(You can't vote after viewing results)</small></div>`;
  95. output += `</table></details></div>`;
  96.  
  97. return output;
  98. }
  99.  
  100. getQuestionMarkup(num) {
  101. if (this.pollArray[num].supportHTML) return this.pollArray[num].question;
  102. return Chat.escapeHTML(this.pollArray[num].question);
  103. }
  104.  
  105. getOptionMarkup(option, num) {
  106. if (this.pollArray[num].supportHTML) return option.name;
  107. return Chat.escapeHTML(option.name);
  108. }
  109.  
  110. update(num) {
  111. let results = [];
  112. for (let i = 0; i <= this.pollArray[num].options.size; i++) {
  113. results.push(this.generateResults(false, i, num));
  114. }
  115. // Update the poll results for everyone that has voted
  116. for (let i in this.room.users) {
  117. let user = this.room.users[i];
  118. if (user.userid in this.pollArray[num].voters) {
  119. user.sendTo(this.room, '|uhtmlchange|poll' + this.pollArray[num].pollNum + '|' + results[this.pollArray[num].voters[user.userid]]);
  120. } else if (user.latestIp in this.pollArray[num].voterIps) {
  121. user.sendTo(this.room, '|uhtmlchange|poll' + this.pollArray[num].pollNum + '|' + results[this.pollArray[num].voterIps[user.latestIp]]);
  122. }
  123. }
  124. }
  125.  
  126. updateTo(user, num, connection) {
  127. if (!connection) connection = user;
  128. if (user.userid in this.pollArray[num].voters) {
  129. connection.sendTo(this.room, '|uhtmlchange|poll' + this.pollArray[num].pollNum + '|' + this.generateResults(false, this.pollArray[num].voters[user.userid], num));
  130. } else if (user.latestIp in this.pollArray[num].voterIps) {
  131. connection.sendTo(this.room, '|uhtmlchange|poll' + this.pollArray[num].pollNum + '|' + this.generateResults(false, this.pollArray[num].voterIps[user.latestIp], num));
  132. } else {
  133. connection.sendTo(this.room, '|uhtmlchange|poll' + this.pollArray[num].pollNum + '|' + this.generateVotes(num));
  134. }
  135. }
  136.  
  137. /**
  138. * @param {User} user
  139. */
  140. updateFor(user) {
  141. for (let u in this.pollArray) {
  142. if (user.userid in this.pollArray[u].voters) user.sendTo(this.room, '|uhtmlchange|poll' + this.pollArray[u].pollNum + '|' + this.generateResults(false, this.pollArray[u].voters[user.userid], u));
  143. }
  144. }
  145.  
  146. display() {
  147. for (let u in this.pollArray) {
  148. let votes = this.generateVotes(u);
  149.  
  150. let results = [];
  151.  
  152. for (let i = 0; i <= this.pollArray[u].options.size; i++) {
  153. results.push(this.generateResults(false, i, u));
  154. }
  155.  
  156. for (let i in this.room.users) {
  157. let thisUser = this.room.users[i];
  158. if (thisUser.userid in this.pollArray[u].voters) {
  159. thisUser.sendTo(this.room, '|uhtml|poll' + this.pollArray[u].pollNum + '|' + results[this.pollArray[u].voters[thisUser.userid]]);
  160. } else if (thisUser.latestIp in this.pollArray[u].voterIps) {
  161. thisUser.sendTo(this.room, '|uhtml|poll' + this.pollArray[u].pollNum + '|' + results[this.pollArray[u].voterIps[thisUser.latestIp]]);
  162. } else {
  163. thisUser.sendTo(this.room, '|uhtml|poll' + this.pollArray[u].pollNum + '|' + votes);
  164. }
  165. }
  166. }
  167. }
  168.  
  169. displayTo(user, connection) {
  170. if (!connection) connection = user;
  171. for (let u in this.pollArray) {
  172. if (user.userid in this.pollArray[u].voters) {
  173. connection.sendTo(this.room, '|uhtml|poll' + this.pollArray[u].pollNum + '|' + this.generateResults(false, this.pollArray[u].voters[user.userid], u));
  174. } else if (user.latestIp in this.pollArray[u].voterIps) {
  175. connection.sendTo(this.room, '|uhtml|poll' + this.pollArray[u].pollNum + '|' + this.generateResults(false, this.pollArray[u].voterIps[user.latestIp], u));
  176. } else {
  177. connection.sendTo(this.room, '|uhtml|poll' + this.pollArray[u].pollNum + '|' + this.generateVotes(u));
  178. }
  179. }
  180. }
  181.  
  182. displaySpecific(num) {
  183. let votes = this.generateVotes(num);
  184.  
  185. let results = [];
  186.  
  187. for (let i = 0; i <= this.pollArray[num].options.size; i++) {
  188. results.push(this.generateResults(false, i, num));
  189. }
  190.  
  191. for (let i in this.room.users) {
  192. let thisUser = this.room.users[i];
  193. if (thisUser.userid in this.pollArray[num].voters) {
  194. thisUser.sendTo(this.room, '|uhtml|poll' + this.pollArray[num].pollNum + '|' + results[this.pollArray[num].voters[thisUser.userid]]);
  195. } else if (thisUser.latestIp in this.pollArray[num].voterIps) {
  196. thisUser.sendTo(this.room, '|uhtml|poll' + this.pollArray[num].pollNum + '|' + results[this.pollArray[num].voterIps[thisUser.latestIp]]);
  197. } else {
  198. thisUser.sendTo(this.room, '|uhtml|poll' + this.pollArray[num].pollNum + '|' + votes);
  199. }
  200. }
  201. }
  202.  
  203. displaySpecificTo(user, connection, num) {
  204. if (!connection) connection = user;
  205. if (user.userid in this.pollArray[num].voters) {
  206. connection.sendTo(this.room, '|uhtml|poll' + this.pollArray[num].pollNum + '|' + this.generateResults(false, this.pollArray[num].voters[user.userid], num));
  207. } else if (user.latestIp in this.pollArray[num].voterIps) {
  208. connection.sendTo(this.room, '|uhtml|poll' + this.pollArray[num].pollNum + '|' + this.generateResults(false, this.pollArray[num].voterIps[user.latestIp], num));
  209. } else {
  210. connection.sendTo(this.room, '|uhtml|poll' + this.pollArray[num].pollNum + '|' + this.generateVotes(num));
  211. }
  212. }
  213.  
  214. /**
  215. * @param {User} user
  216. * @param {Connection?} [connection]
  217. */
  218. onConnect(user, connection = null) {
  219. this.displayTo(user, connection);
  220. }
  221.  
  222. end(number) {
  223. let results = this.generateResults(true, null, number);
  224. this.room.send('|uhtmlchange|poll' + this.pollArray[number].pollNum + '|<div class="infobox">(The poll has ended &ndash; scroll down to see the results)</div>');
  225. this.room.add('|html|' + results);
  226. }
  227. obtain(number) {
  228. for (let u in this.pollArray) {
  229. if (this.pollArray[u].pollNum === number) return u;
  230. }
  231. }
  232. }
  233.  
  234. exports.Poll = Poll;
  235.  
  236. /** @type {ChatCommands} */
  237. const commands = {
  238. poll: {
  239. htmlcreate: 'new',
  240. create: 'new',
  241. new: function (target, room, user, connection, cmd, message) {
  242. if (!target) return this.parse('/help poll new');
  243. target = target.trim();
  244. if (target.length > 1024) return this.errorReply("Poll too long.");
  245. if (room.battle) return this.errorReply("Battles do not support polls.");
  246.  
  247. /** @type {string} */
  248. let text = Chat.filter.call(this, target, user, room, connection);
  249. if (target !== text) return this.errorReply("You are not allowed to use filtered words in polls.");
  250.  
  251. const supportHTML = cmd === 'htmlcreate';
  252. if (room.poll && room.poll.pollArray.length >= 5) return this.errorReply('There can only be up to 5 polls at a time.');
  253. let separator = '';
  254. if (text.includes('\n')) {
  255. separator = '\n';
  256. } else if (text.includes('|')) {
  257. separator = '|';
  258. } else if (text.includes(',')) {
  259. separator = ',';
  260. } else {
  261. return this.errorReply("Not enough arguments for /poll new.");
  262. }
  263.  
  264. let params = text.split(separator).map(param => param.trim());
  265.  
  266. if (!this.can('minigame', null, room)) return false;
  267. if (supportHTML && !this.can('declare', null, room)) return false;
  268. if (!this.canTalk()) return;
  269. if (room.poll && room.poll.pollArray[0] && room.poll.pollArray[1] && room.poll.pollArray[2] && room.poll.pollArray[3] && room.poll.pollArray[4]) return this.errorReply("Only 5 polls at a time!");
  270. if (params.length < 3) return this.errorReply("Not enough arguments for /poll new.");
  271.  
  272. // @ts-ignore In the case that any of these are null, the function is terminated, and the result never used.
  273. if (supportHTML) params = params.map(parameter => this.canHTML(parameter));
  274. if (params.some(parameter => !parameter)) return;
  275.  
  276. const options = params.splice(1);
  277. if (options.length > 8) {
  278. return this.errorReply("Too many options for poll (maximum is 8).");
  279. }
  280. if (room.poll && room.pollNumber) room.pollNumber++;
  281. if (room.poll) {
  282. room.poll.pollArray.push({
  283. room: room,
  284. pollNum: room.pollNumber,
  285. question: params[0],
  286. supportHTML: supportHTML,
  287. voters: {},
  288. voterIps: {},
  289. totalVotes: 0,
  290. timeout: null,
  291. timeoutMins: 0,
  292. startTime: Date.now(),
  293. startedUser: WL.nameColor(user.name, true, true),
  294. options: new Map(),
  295. });
  296. for (let i = 0; i < options.length; i++) {
  297. room.poll.pollArray[room.poll.pollArray.length - 1].options.set(i + 1, {name: options[i], votes: 0});
  298. }
  299. room.poll.displaySpecific(room.poll.pollArray.length - 1);
  300. } else {
  301. room.poll = new Poll(room, {source: params[0], supportHTML: supportHTML}, options, user.name);
  302. room.poll.display();
  303. }
  304.  
  305. this.roomlog(`${user.name} used ${message}`);
  306. this.modlog('POLL');
  307. return this.privateModAction(`(A poll was started by ${user.name}.)`);
  308. },
  309. newhelp: [`/poll create [question], [option1], [option2], [...] - Creates a poll. Allows up to 5 polls at once. Requires: % @ * # & ~`],
  310.  
  311. vote: function (target, room, user) {
  312. if (!room.poll) return this.errorReply("There is no poll running in this room.");
  313. let targets = target.split(',');
  314. if (!targets[1]) return this.parse('/help poll vote');
  315. for (let u = 0; u < targets.length; u++) targets[u] = targets[u].trim();
  316. let number = parseInt(targets[1]);
  317. let num = room.poll.obtain(number);
  318. if (!num) return this.errorReply("Not a poll number!");
  319. if (targets[0] === 'blank') {
  320. room.poll.blankvote(user, num);
  321. return;
  322. }
  323.  
  324. let parsed = parseInt(targets[0]);
  325. if (isNaN(parsed)) return this.errorReply("To vote, specify the number of the option.");
  326. if (!room.poll.pollArray[num].options.has(parsed)) return this.sendReply("Option not in poll.");
  327.  
  328. room.poll.vote(user, parsed, num);
  329. },
  330. votehelp: [`/poll vote [option number], [poll number] - Votes for option [number] on poll [poll number].`],
  331.  
  332. timer: function (target, room, user) {
  333. if (!room.poll) return this.errorReply("There is no poll running in this room.");
  334. let targets = target.split(",");
  335. for (let u = 0; u < targets.length; u++) targets[u] = targets[u].trim();
  336. if (!targets[1]) return this.errorReply("/poll timer (clear/ time amount), (poll number)");
  337. let num = room.poll.obtain(parseInt(targets[1]));
  338. if (!room.poll.pollArray[num]) return this.errorReply('That poll number is not currently a poll!');
  339. if (targets[0]) {
  340. if (!this.can('minigame', null, room)) return false;
  341. if (targets[0] === 'clear') {
  342. if (room.poll.pollArray[num] && !room.poll.pollArray[num].timeout) return this.errorReply("There is no timer to clear.");
  343. clearTimeout(room.poll.pollArray[num].timeout);
  344. room.poll.pollArray[num].timeout = null;
  345. room.poll.pollArray[num].timeoutMins = 0;
  346. return this.add("The poll timer was turned off.");
  347. }
  348. let timeout = parseFloat(target);
  349. if (isNaN(timeout) || timeout <= 0 || timeout > 0x7FFFFFFF) return this.errorReply("Invalid time given.");
  350. if (room.poll.pollArray[num] && room.poll.pollArray[num].timeout) clearTimeout(room.poll.pollArray[num].timeout);
  351. room.poll.pollArray[num].timeoutMins = timeout;
  352. room.poll.pollArray[num].timeout = setTimeout(() => {
  353. room.poll.end(num);
  354. room.poll.pollArray.splice(num, 1);
  355. }, (timeout * 60000));
  356. room.add("The poll timer was turned on: the poll " + room.poll.pollArray[num].pollNum + " will end in " + timeout + " minute(s).");
  357. this.modlog('POLL TIMER', null, `#${room.poll.pollArray[num].pollNum} ${timeout} minutes`);
  358. return this.privateModAction("(The poll timer for poll " + room.poll.pollArray[num].pollNum + " was set to " + timeout + " minute(s) by " + user.name + ".)");
  359. } else {
  360. if (!this.runBroadcast()) return;
  361. if (room.poll.pollArray[num].timeout) {
  362. return this.sendReply("The poll timer for " + room.poll.pollArray[num].pollNum + " is on and will end in " + room.poll.pollArray[num].timeoutMins + " minute(s).");
  363. } else {
  364. return this.sendReply("The poll timer for " + room.poll.pollArray[num].pollNum + " is off.");
  365. }
  366. }
  367. },
  368. timerhelp: [
  369. `/poll timer [minutes], [poll id number] - Sets the poll to automatically end after [minutes] minutes. Requires: % @ * # & ~`,
  370. `/poll timer clear - Clears the poll's timer. Requires: % @ * # & ~`,
  371. ],
  372.  
  373. results: function (target, room, user) {
  374. if (!room.poll) return this.errorReply("There is no poll running in this room.");
  375. let num = room.poll.obtain(parseInt(target));
  376. if (!num) return this.errorReply("Not a poll number!");
  377. if (room.poll.pollArray[num].pollNum === parseInt(target)) return room.poll.blankvote(user, num);
  378. },
  379. resultshelp: [`/poll results [poll id number] - Shows the results of the poll without voting. NOTE: you can't go back and vote after using this.`],
  380.  
  381. close: 'end',
  382. stop: 'end',
  383. end: function (target, room, user) {
  384. if (!this.can('minigame', null, room)) return false;
  385. if (!this.canTalk()) return;
  386. if (!room.poll) return this.errorReply("There is no poll running in this room.");
  387. let num = room.poll.obtain(parseInt(target));
  388. if (!num) return this.errorReply("Not a poll number!");
  389.  
  390. if (room.poll.pollArray[num].pollNum === parseInt(target) && room.poll.pollArray[num].timeout) clearTimeout(room.poll.pollArray[num].timeout);
  391. if (room.poll.pollArray[num].pollNum === parseInt(target)) room.poll.end(num);
  392. if (room.poll.pollArray[num].pollNum === parseInt(target)) room.poll.pollArray.splice(num, 1);
  393.  
  394. this.modlog('POLL END');
  395. return this.privateModAction(`(The poll was ended by ${user.name}.)`);
  396. },
  397. endhelp: [`/poll end [poll id number] - Ends a poll and displays the results. Requires: % @ * # & ~`],
  398.  
  399. show: 'display',
  400. display: function (target, room, user, connection) {
  401. if (!room.poll) return this.errorReply("There is no poll running in this room.");
  402. if (!this.runBroadcast()) return;
  403. room.update();
  404. let num = room.poll.obtain(parseInt(target));
  405. if (num) {
  406. if (this.broadcasting) {
  407. room.poll.displayTo(user, connection);
  408. } else {
  409. room.poll.displaySpecificTo(user, connection, num);
  410. }
  411. } else {
  412. if (!num && this.broadcasting) {
  413. room.poll.display();
  414. } else {
  415. room.poll.displayTo(user, connection);
  416. }
  417. }
  418. },
  419. displayhelp: [`/poll display [poll id number] - Displays the poll. Id number is optional and only displays the poll with the id number.`],
  420.  
  421. '': function (target, room, user) {
  422. this.parse('/help poll');
  423. },
  424. },
  425.  
  426. pollhelp: [
  427. `/poll allows rooms to run their own polls. These polls are limited to five polls at a time per room.`,
  428. `Accepts the following commands:`,
  429. `/poll create [question], [option1], [option2], [...] - Allows up to 5 polls at once per room. Creates a poll. Requires: % @ * # & ~`,
  430. `/poll htmlcreate [question], [option1], [option2], [...] - Allows up to 5 polls at once per room. Creates a poll, with HTML allowed in the question and options. Requires: # & ~`,
  431. `/poll vote [number], [poll id number] - Votes for option [number] in the poll [poll id number].`,
  432. `/poll timer [minutes], [poll id number] - Sets the poll to automatically end after [minutes]. Requires: % @ * # & ~`,
  433. `/poll results, [poll id number] - Shows the results of the poll without voting. NOTE: you can't go back and vote after using this.`,
  434. `/poll display [poll id number] - Displays the poll. The poll id number is optional for this command and displays only the poll with the matching id number.`,
  435. `/poll end [poll id number] - Ends a poll and displays the results. The poll id number is optional for this command and ends only the poll with the matching id number. and Requires: % @ * # & ~`,
  436. ],
  437. };
  438.  
  439. exports.commands = commands;
  440.  
  441. process.nextTick(() => {
  442. Chat.multiLinePattern.register('/poll (new|create|htmlcreate) ');
  443. });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement