"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var poll_exports = {}; __export(poll_exports, { Poll: () => Poll, commands: () => commands, pages: () => pages }); module.exports = __toCommonJS(poll_exports); var import_lib = require("../../lib"); const MINUTES = 6e4; const MAX_QUESTIONS = 10; class Poll extends Rooms.MinorActivity { constructor(room, options) { super(room); this.activityid = "poll"; this.name = "Poll"; this.activityNumber = options.activityNumber || room.nextGameNumber(); this.question = options.question; this.supportHTML = options.supportHTML; this.multiPoll = options.multiPoll; this.pendingVotes = options.pendingVotes || {}; this.voters = options.voters || {}; this.voterIps = options.voterIps || {}; this.totalVotes = options.totalVotes || 0; this.maxVotes = options.maxVotes || 0; if (!options.answers) options.answers = options.questions; this.answers = Poll.getAnswers(options.answers); this.isQuiz = options.isQuiz ?? [...this.answers.values()].some((answer) => answer.correct); this.setTimer(options); } select(user, option) { const userid = user.id; if (!this.multiPoll) { this.pendingVotes[userid] = [option]; this.submit(user); return; } if (!this.pendingVotes[userid]) { this.pendingVotes[userid] = []; } if (this.pendingVotes[userid].includes(option)) { throw new Chat.ErrorMessage(this.room.tr`That option is already selected.`); } this.pendingVotes[userid].push(option); this.updateFor(user); this.save(); } deselect(user, option) { const userid = user.id; const pendingVote = this.pendingVotes[userid]; if (!pendingVote?.includes(option)) { throw new Chat.ErrorMessage(this.room.tr`That option is not selected.`); } pendingVote.splice(pendingVote.indexOf(option), 1); this.updateFor(user); this.save(); } submit(user) { const ip = user.latestIp; const userid = user.id; if (userid in this.voters || !Config.noipchecks && ip in this.voterIps) { delete this.pendingVotes[userid]; throw new Chat.ErrorMessage(this.room.tr`You have already voted for this poll.`); } const selected = this.pendingVotes[userid]; if (!selected) throw new Chat.ErrorMessage(this.room.tr`No options selected.`); this.voters[userid] = selected; this.voterIps[ip] = selected; for (const option of selected) { this.answers.get(option).votes++; } delete this.pendingVotes[userid]; this.totalVotes++; if (this.maxVotes && this.totalVotes >= this.maxVotes) { this.end(this.room); return this.room.add(`|c|~|/log The poll hit the max vote cap of ${this.maxVotes}, and has ended.`).update(); } this.update(); this.save(); } blankvote(user) { const ip = user.latestIp; const userid = user.id; if (!(userid in this.voters) || !(ip in this.voterIps)) { this.voters[userid] = []; this.voterIps[ip] = []; } this.updateTo(user); this.save(); } generateVotes(user) { const iconText = this.isQuiz ? ` ${this.room.tr`Quiz`}` : ` ${this.room.tr`Poll`}`; let output = `

${iconText}`; output += ` ${Poll.getQuestionMarkup(this.question, this.supportHTML)}

`; if (this.multiPoll) { const empty = ``; const chosen = ``; const pendingVotes = user && this.pendingVotes[user.id] || []; for (const [num, answer] of this.answers) { const selected = pendingVotes.includes(num); output += `
`; } const submitButton = pendingVotes.length ? `` : ``; output += `
${submitButton}
`; output += `
`; } else { for (const [num, answer] of this.answers) { output += `
`; } output += `
`; output += ``; } return output; } static generateResults(options, room, ended = false, choice = null) { const iconText = options.isQuiz ? ` ${room.tr`Quiz`}` : ` ${room.tr`Poll`}`; const icon = `${iconText}${ended ? " " + room.tr`ended` : ""} ${options.totalVotes || 0} ${room.tr`votes`}`; let output = `

${icon} ${this.getQuestionMarkup(options.question, options.supportHTML)}

`; const answers = Poll.getAnswers(options.answers); const colors = ["#88B", "#79A", "#8A8"]; for (const [num, answer] of answers) { const chosen = choice?.includes(num); const percentage = Math.round(answer.votes * 100 / (options.totalVotes || 1)); const answerMarkup = options.isQuiz ? `${answer.correct ? "" : ""}${this.getAnswerMarkup(answer, options.supportHTML)}${answer.correct ? "" : ""}` : this.getAnswerMarkup(answer, options.supportHTML); output += `
${num}. ${chosen ? "" : ""}${answerMarkup}${chosen ? "" : ""} (${answer.votes} vote${answer.votes === 1 ? "" : "s"})
 ${percentage}%
`; } if (!choice && !ended) { output += `
(${room.tr`You can't vote after viewing results`})
`; } output += "
"; return output; } static getQuestionMarkup(question, supportHTML = false) { if (supportHTML) return question; return Chat.formatText(question); } static getAnswerMarkup(answer, supportHTML = false) { if (supportHTML) return answer.name; return Chat.formatText(answer.name); } update() { const state = this.toJSON(); const blankvote = Poll.generateResults(state, this.room, false); for (const id in this.room.users) { const user = this.room.users[id]; const selection = this.voters[user.id] || this.voterIps[user.latestIp]; if (selection) { if (selection.length) { user.sendTo( this.room, `|uhtmlchange|poll${this.activityNumber}|${Poll.generateResults(state, this.room, false, selection)}` ); } else { user.sendTo(this.room, `|uhtmlchange|poll${this.activityNumber}|${blankvote}`); } } } } updateTo(user, connection = null) { const state = this.toJSON(); const recipient = connection || user; const selection = this.voters[user.id] || this.voterIps[user.latestIp]; if (selection) { recipient.sendTo( this.room, `|uhtmlchange|poll${this.activityNumber}|${Poll.generateResults(state, this.room, false, selection)}` ); } else { recipient.sendTo(this.room, `|uhtmlchange|poll${this.activityNumber}|${this.generateVotes(user)}`); } } updateFor(user) { const state = this.toJSON(); if (user.id in this.voters) { user.sendTo( this.room, `|uhtmlchange|poll${this.activityNumber}|${Poll.generateResults(state, this.room, false, this.voters[user.id])}` ); } else { user.sendTo(this.room, `|uhtmlchange|poll${this.activityNumber}|${this.generateVotes(user)}`); } } display() { const state = this.toJSON(); const blankvote = Poll.generateResults(state, this.room, false); const blankquestions = this.generateVotes(null); for (const id in this.room.users) { const thisUser = this.room.users[id]; const selection = this.voters[thisUser.id] || this.voterIps[thisUser.latestIp]; if (selection) { if (selection.length) { thisUser.sendTo( this.room, `|uhtml|poll${this.activityNumber}|${Poll.generateResults(state, this.room, false, selection)}` ); } else { thisUser.sendTo(this.room, `|uhtml|poll${this.activityNumber}|${blankvote}`); } } else { if (this.multiPoll && thisUser.id in this.pendingVotes) { thisUser.sendTo(this.room, `|uhtml|poll${this.activityNumber}|${this.generateVotes(thisUser)}`); } else { thisUser.sendTo(this.room, `|uhtml|poll${this.activityNumber}|${blankquestions}`); } } } } displayTo(user, connection = null) { const state = this.toJSON(); const recipient = connection || user; if (user.id in this.voters) { recipient.sendTo( this.room, `|uhtml|poll${this.activityNumber}|${Poll.generateResults(state, this.room, false, this.voters[user.id])}` ); } else if (user.latestIp in this.voterIps && !Config.noipchecks) { recipient.sendTo(this.room, `|uhtml|poll${this.activityNumber}|${Poll.generateResults( state, this.room, false, this.voterIps[user.latestIp] )}`); } else { recipient.sendTo(this.room, `|uhtml|poll${this.activityNumber}|${this.generateVotes(user)}`); } } onConnect(user, connection = null) { this.displayTo(user, connection); } onRename(user, oldid, joining) { if (user.id in this.voters) { this.updateFor(user); } } destroy() { const results = Poll.generateResults(this.toJSON(), this.room, true); this.room.send(`|uhtmlchange|poll${this.activityNumber}|
(${this.room.tr`The poll has ended – scroll down to see the results`})
`); this.room.add(`|html|${results}`).update(); this.room.setMinorActivity(null); } toJSON() { return { activityid: "poll", activityNumber: this.activityNumber, question: this.question, supportHTML: this.supportHTML, multiPoll: this.multiPoll, pendingVotes: this.pendingVotes, voters: this.voters, voterIps: this.voterIps, totalVotes: this.totalVotes, timeoutMins: this.timeoutMins, timerEnd: this.timerEnd, isQuiz: this.isQuiz, answers: [...this.answers.values()] }; } save() { this.room.settings.minorActivity = this.toJSON(); this.room.saveSettings(); } static getAnswers(answers) { const out = /* @__PURE__ */ new Map(); if (answers.length && typeof answers[0] === "string") { for (const [i, answer] of answers.entries()) { out.set(i + 1, { name: answer.startsWith("+") ? answer.slice(1) : answer, votes: 0, correct: answer.startsWith("+") }); } } else { for (const [i, answer] of answers.entries()) { out.set(i + 1, answer); } } return out; } } const commands = { poll: { htmlcreate: "new", create: "new", createmulti: "new", htmlcreatemulti: "new", queue: "new", queuehtml: "new", htmlqueue: "new", queuemulti: "new", htmlqueuemulti: "new", new(target, room, user, connection, cmd, message) { room = this.requireRoom(); if (!target) return this.parse("/help poll new"); target = target.trim(); if (target.length > 1024) return this.errorReply(this.tr`Poll too long.`); if (room.battle) return this.errorReply(this.tr`Battles do not support polls.`); const text = this.filter(target); if (target !== text) return this.errorReply(this.tr`You are not allowed to use filtered words in polls.`); const supportHTML = cmd.includes("html"); const multiPoll = cmd.includes("multi"); const queue = cmd.includes("queue"); let params = []; let separator = ""; if (text.includes("\n")) { separator = "\n"; } else if (text.includes("|")) { separator = "|"; } else if (text.includes(",")) { separator = ","; } else { return this.errorReply(this.tr`Not enough arguments for /poll new.`); } let currentParam = ""; for (let i = 0; i < text.length; ++i) { const currentCharacter = text[i]; const nextCharacter = text[i + 1]; const isEscapeCharacter = currentCharacter === "\\"; if (isEscapeCharacter) { if (nextCharacter) { currentParam += nextCharacter; i += 1; } else { return this.errorReply(this.tr`Extra escape character. To end a poll with '\\', enter it as '\\\\'`); } continue; } const isSeparator = currentCharacter === separator; if (isSeparator) { params.push(currentParam); currentParam = ""; continue; } currentParam += currentCharacter; } params.push(currentParam); params = params.map((param) => param.trim()); this.checkCan("minigame", null, room); if (supportHTML) this.checkCan("declare", null, room); this.checkChat(); if (room.minorActivity && !queue) { return this.errorReply(this.tr`There is already a poll or announcement in progress in this room.`); } if (params.length < 3) return this.errorReply(this.tr`Not enough arguments for /poll new.`); if (supportHTML) params = params.map((parameter) => this.checkHTML(parameter)); const questions = params.splice(1); if (questions.length > MAX_QUESTIONS) { return this.errorReply(this.tr`Too many options for poll (maximum is ${MAX_QUESTIONS}).`); } if (new Set(questions).size !== questions.length) { return this.errorReply(this.tr`There are duplicate options in the poll.`); } if (room.minorActivity) { room.queueMinorActivity({ question: params[0], answers: questions, multiPoll, supportHTML, activityid: "poll" }); this.modlog("QUEUEPOLL"); return this.privateModAction(room.tr`${user.name} queued a poll.`); } room.setMinorActivity(new Poll(room, { question: params[0], supportHTML, answers: questions, multiPoll })); this.roomlog(`${user.name} used ${message}`); this.modlog("POLL"); this.addModAction(room.tr`A poll was started by ${user.name}.`); }, newhelp: [ `/poll create [question], [option1], [option2], [...] - Creates a poll. Requires: % @ # ~`, `/poll createmulti [question], [option1], [option2], [...] - Creates a poll, allowing for multiple answers to be selected. Requires: % @ # ~`, `To queue a poll, use [queue], [queuemulti], [queuehtml], or [htmlqueuemulti].`, `Polls can be used as quiz questions. To do this, prepend all correct answers with a +.` ], viewqueue(target, room, user) { room = this.requireRoom(); this.checkCan("mute", null, room); this.parse(`/join view-pollqueue-${room.roomid}`); }, viewqueuehelp: [`/viewqueue - view the queue of polls in the room. Requires: % @ # ~`], deletequeue(target, room, user) { room = this.requireRoom(); if (!target) return this.parse("/help deletequeue"); this.checkCan("mute", null, room); const queue = room.getMinorActivityQueue(); if (!queue) { return this.errorReply(this.tr`The queue is already empty.`); } const slot = parseInt(target); if (isNaN(slot)) { return this.errorReply(this.tr`Can't delete poll at slot ${target} - "${target}" is not a number.`); } if (!queue[slot - 1]) return this.errorReply(this.tr`There is no poll in queue at slot ${slot}.`); room.clearMinorActivityQueue(slot - 1); room.modlog({ action: "DELETEQUEUE", loggedBy: user.id, note: slot.toString() }); room.sendMods(this.tr`(${user.name} deleted the queued poll in slot ${slot}.)`); room.update(); this.refreshPage(`pollqueue-${room.roomid}`); }, deletequeuehelp: [ `/poll deletequeue [number] - deletes poll at the corresponding queue slot (1 = next, 2 = the one after that, etc). Requires: % @ # ~` ], clearqueue(target, room, user, connection, cmd) { room = this.requireRoom(); this.checkCan("mute", null, room); const queue = room.getMinorActivityQueue(); if (!queue) { return this.errorReply(this.tr`The queue is already empty.`); } room.clearMinorActivityQueue(); this.modlog("CLEARQUEUE"); this.sendReply(this.tr`Cleared poll queue.`); }, clearqueuehelp: [ `/poll clearqueue - deletes the queue of polls. Requires: % @ # ~` ], deselect: "select", vote: "select", select(target, room, user, connection, cmd) { room = this.requireRoom(); const poll = this.requireMinorActivity(Poll); if (!target) return this.parse("/help poll vote"); const parsed = parseInt(target); if (isNaN(parsed)) return this.errorReply(this.tr`To vote, specify the number of the option.`); if (!poll.answers.has(parsed)) return this.sendReply(this.tr`Option not in poll.`); if (cmd === "deselect") { poll.deselect(user, parsed); } else { poll.select(user, parsed); } }, selecthelp: [ `/poll select [number] - Select option [number].`, `/poll deselect [number] - Deselects option [number].` ], submit(target, room, user) { room = this.requireRoom(); const poll = this.requireMinorActivity(Poll); poll.submit(user); }, submithelp: [`/poll submit - Submits your vote.`], timer(target, room, user) { room = this.requireRoom(); const poll = this.requireMinorActivity(Poll); if (target) { this.checkCan("minigame", null, room); if (target === "clear") { if (!poll.endTimer()) return this.errorReply(this.tr("There is no timer to clear.")); return this.add(this.tr`The poll timer was turned off.`); } const timeoutMins = parseFloat(target); if (isNaN(timeoutMins) || timeoutMins <= 0 || timeoutMins > 7 * 24 * 60) { return this.errorReply(this.tr`Time should be a number of minutes less than one week.`); } poll.setTimer({ timeoutMins }); room.add(this.tr`The poll timer was turned on: the poll will end in ${Chat.toDurationString(timeoutMins * MINUTES)}.`); this.modlog("POLL TIMER", null, `${timeoutMins} minutes`); return this.privateModAction(room.tr`The poll timer was set to ${timeoutMins} minute(s) by ${user.name}.`); } else { if (!this.runBroadcast()) return; if (poll.timeout) { return this.sendReply(this.tr`The poll timer is on and will end in ${Chat.toDurationString(poll.timeoutMins * MINUTES)}.`); } else { return this.sendReply(this.tr`The poll timer is off.`); } } }, timerhelp: [ `/poll timer [minutes] - Sets the poll to automatically end after [minutes] minutes. Requires: % @ # ~`, `/poll timer clear - Clears the poll's timer. Requires: % @ # ~` ], results(target, room, user) { room = this.requireRoom(); const poll = this.requireMinorActivity(Poll); poll.blankvote(user); }, resultshelp: [ `/poll results - Shows the results of the poll without voting. NOTE: you can't go back and vote after using this.` ], close: "end", stop: "end", end(target, room, user) { room = this.requireRoom(); this.checkCan("minigame", null, room); this.checkChat(); const poll = this.requireMinorActivity(Poll); this.modlog("POLL END"); this.privateModAction(room.tr`The poll was ended by ${user.name}.`); poll.end(room, Poll); }, endhelp: [`/poll end - Ends a poll and displays the results. Requires: % @ # ~`], show: "", display: "", ""(target, room, user, connection) { room = this.requireRoom(); const poll = this.requireMinorActivity(Poll); if (!this.runBroadcast()) return; room.update(); if (this.broadcasting) { poll.display(); } else { poll.displayTo(user, connection); } }, displayhelp: [`/poll display - Displays the poll`], mv: "maxvotes", maxvotes(target, room, user) { room = this.requireRoom(); this.checkCan("mute", null, room); const poll = this.requireMinorActivity(Poll); let num = parseInt(target); if (this.meansNo(target)) { num = 0; } if (isNaN(num)) { return this.errorReply(`Invalid max vote cap: '${target}'`); } if (poll.maxVotes === num) { return this.errorReply(`The poll's vote cap is already set to ${num}.`); } poll.maxVotes = num; this.addModAction(`${user.name} set the poll's vote cap to ${num}.`); let ended = false; if (poll.totalVotes > poll.maxVotes) { poll.end(room); this.addModAction(`The poll has more votes than the maximum vote cap, and has ended.`); ended = true; } if (!ended) poll.save(); this.modlog("POLL MAXVOTES", null, `${poll.maxVotes}${ended ? ` (ended poll)` : ""}`); } }, pollhelp() { this.sendReply( `|html|
/poll allows rooms to run their own polls (limit 1 at a time).
Polls can be used as quiz questions, by putting + before correct answers.
/poll create [question], [option1], [option2], [...] - Creates a poll. Requires: % @ # ~
/poll createmulti [question], [option1], [option2], [...] - Creates a poll, allowing for multiple answers to be selected. Requires: % @ # ~
/poll htmlcreate(multi) [question], [option1], [option2], [...] - Creates a poll, with HTML allowed in the question and options. Requires: # ~
/poll vote [number] - Votes for option [number].
/poll timer [minutes] - Sets the poll to automatically end after [minutes]. Requires: % @ # ~.
/poll results - Shows the results of the poll without voting. NOTE: you can't go back and vote after using this.
/poll display - Displays the poll.
/poll end - Ends a poll and displays the results. Requires: % @ # ~.
/poll queue [question], [option1], [option2], [...] - Add a poll in queue. Requires: % @ # ~
/poll deletequeue [number] - Deletes poll at the corresponding queue slot (1 = next, 2 = the one after that, etc).
/poll clearqueue - Deletes the queue of polls. Requires: % @ # ~.
/poll viewqueue - View the queue of polls in the room. Requires: % @ # ~
/poll maxvotes [number] - Set the max poll votes to the given [number]. Requires: % @ # ~
` ); } }; const pages = { pollqueue(args, user) { const room = this.requireRoom(); let buf = `
${this.tr`Queued polls:`}`; buf += `
`; const queue = room.getMinorActivityQueue()?.filter((activity) => activity.activityid === "poll"); if (!queue) { buf += `
${this.tr`No polls queued.`}
`; return buf; } for (const [i, poll] of queue.entries()) { const number = i + 1; const button = `${this.tr`#${number} in queue`} `; buf += `
`; buf += `${button}
${Poll.generateResults(poll, room, false)}`; } buf += `
`; return buf; } }; process.nextTick(() => { Chat.multiLinePattern.register("/poll (new|create|createmulti|htmlcreate|htmlcreatemulti|queue|queuemulti|htmlqueuemulti) "); }); for (const room of Rooms.rooms.values()) { if (room.getMinorActivityQueue(true)) { for (const poll of room.getMinorActivityQueue(true)) { if (!poll.activityid) { poll.activityid = poll.activityId; delete poll.activityId; } if (!poll.activityNumber) { poll.activityNumber = poll.pollNumber; delete poll.pollNumber; } room.saveSettings(); } } if (room.settings.minorActivity) { if (!room.settings.minorActivity.activityid) { room.settings.minorActivity.activityid = room.settings.minorActivity.activityId; delete room.settings.minorActivity.activityId; } if (typeof room.settings.minorActivity.activityNumber !== "number") { room.settings.minorActivity.activityNumber = room.settings.minorActivity.pollNumber || // @ts-expect-error old format room.settings.minorActivity.announcementNumber; } room.saveSettings(); } if (room.settings.minorActivity?.activityid === "poll") { room.setMinorActivity(new Poll(room, room.settings.minorActivity), true); } } //# sourceMappingURL=poll.js.map