SHARE
TWEET

Poll

a guest Apr 18th, 2019 94 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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. });
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top