/** * Wi-Fi chat-plugin. Only works in a room with id 'wifi' * Handles giveaways in the formats: question, lottery * Credits: Codelegend, SilverTactic, DanielCranham, bumbadadabum **/ 'use strict'; Punishments.roomPunishmentTypes.set('GIVEAWAYBAN', 'banned from giveaways'); const BAN_DURATION = 7 * 24 * 60 * 60 * 1000; function checkPlural(variable, plural, singular) { if (!plural) plural = 's'; if (!singular) singular = ''; return ((variable.length || variable) > 1 ? plural : singular); } class Giveaway { constructor(host, giver, room, prize) { if (room.gaNumber) { room.gaNumber++; } else { room.gaNumber = 1; } this.host = host; this.giver = giver; this.room = room; this.prize = prize; this.phase = 'pending'; this.joined = {}; this.sprite = Giveaway.getSprite(prize); } send(content) { this.room.add(`|uhtml|giveaway${this.room.gaNumber}${this.phase}|
${content}
`); this.room.update(); } changeUhtml(content) { this.room.add(`|uhtmlchange|giveaway${this.room.gaNumber}${this.phase}|
${content}
`); this.room.update(); } clearTimer() { if (this.timer) { clearTimeout(this.timer); delete this.timer; } } checkJoined(user) { for (let ip in this.joined) { if (user.latestIp === ip) return ip; if (this.joined[ip] in user.prevNames) return this.joined[ip]; } return false; } kickUser(user) { for (let ip in this.joined) { if (user.latestIp === ip || this.joined[ip] in user.prevNames) { if (this.generateReminder) user.sendTo(this.room, `|uhtmlchange|giveaway${this.room.gaNumber}${this.phase}|
${this.generateReminder()}
`); delete this.joined[ip]; } } } checkExcluded(user) { if (Giveaway.checkBanned(this.room, user)) return true; if (user === this.giver || user.latestIp in this.giver.ips || toId(user) in this.giver.prevNames) return true; if (user === this.host || user.latestIp in this.host.ips || toId(user) in this.host.prevNames) return true; return false; } static checkBanned(room, user) { return Punishments.getRoomPunishType(room, toId(user)) === 'GIVEAWAYBAN'; } static ban(room, user, reason) { Punishments.roomPunish(room, user, ['GIVEAWAYBAN', toId(user), Date.now() + BAN_DURATION, reason]); } static unban(room, user) { Punishments.roomUnpunish(room, toId(user), 'GIVEAWAYBAN'); } static getSprite(text) { text = text.toLowerCase(); let mons = new Map(); let output = ''; for (let i in Tools.data.Pokedex) { let regexp = new RegExp(`\\b${i}\\b`); if (regexp.test(text)) { let mon = Tools.getTemplate(i); mons.set(mon.baseSpecies, mon); } } // the previous regex doesn't match "nidoran-m" or "nidoran male" if (/\bnidoran\W{0,1}m(ale){0,1}\b/.test(text)) { mons.set('nidoranm', Tools.getTemplate('nidoranm')); } if (/\bnidoran\W{0,1}f(emale){0,1}\b/.test(text)) { mons.set('nidoranf', Tools.getTemplate('nidoranf')); } text = toId(text); if (mons.size) { mons.forEach(function (value, key) { let spriteid = value.spriteid; if (value.otherForms) { for (let i = 0; i < value.otherForms.length; i++) { if (text.includes(value.otherForms[i])) { spriteid += '-' + value.otherForms[i].substr(key.length); break; // We don't want to end up with deerling-summer-spring } } } if (value.otherFormes) { for (let i = 0; i < value.otherFormes.length; i++) { if (text.includes(value.otherFormes[i])) { spriteid += '-' + value.otherFormes[i].substr(key.length); break; // We don't want to end up with landorus-therian-therian } } } if (mons.size > 1) { let top = Math.floor(value.num / 12) * 30; let left = (value.num % 12) * 40; output += `
`; } else { let shiny = (text.includes("shiny") && !text.includes("shinystone") ? '-shiny' : ''); output += ``; } }); } return output; } generateWindow(rightSide) { return `

It's giveaway time!

` + `

Giveaway started by ${Tools.escapeHTML(this.host.name)}

` + `` + `
${this.sprite}

Giver: ${this.giver}

${Tools.escapeHTML(this.prize)}
${rightSide}

Note: Please do not join if you don't have a 3DS and a copy of Pokémon XY or ORAS.

`; } } class QuestionGiveaway extends Giveaway { constructor(host, giver, room, prize, question, answers) { super(host, giver, room, prize); this.type = 'question'; this.question = question; this.answers = QuestionGiveaway.sanitizeAnswers(answers); this.answered = {}; // userid: number of guesses this.send(this.generateWindow('The question will be displayed in one minute! Use /ga to answer.')); this.timer = setTimeout(() => this.start(), 1000 * 60); } generateQuestion() { return this.generateWindow(`

Giveaway Question: ${this.question}

use /ga to guess.

`); } start() { this.changeUhtml('

The giveaway has started! Scroll down to see the question.

'); this.phase = 'started'; this.send(this.generateQuestion()); this.timer = setTimeout(() => this.end(), 1000 * 60 * 5); } guessAnswer(user, guess) { if (this.phase !== 'started') return user.sendTo(this.room, "The giveaway has not started yet."); if (this.checkJoined(user) && Object.values(this.joined).indexOf(user.userid) < 0) return user.sendTo(this.room, "You have already joined the giveaway."); if (this.checkExcluded(user)) return user.sendTo(this.room, "You are disallowed from entering the giveaway."); if (!this.answered[user.userid]) this.answered[user.userid] = 0; if (this.answered[user.userid] >= 3) return user.sendTo(this.room, "You have already guessed three times. You cannot guess anymore in this giveaway."); for (let i = 0; i < this.answers.length; i++) { if (toId(this.answers[i]) === toId(guess)) { this.winner = user; this.clearTimer(); return this.end(); } } this.joined[user.latestIp] = user.userid; this.answered[user.userid]++; if (this.answered[user.userid] >= 3) { user.sendTo(this.room, `Your guess '${guess}' is wrong. You have used up all of your guesses. Better luck next time!`); } else { user.sendTo(this.room, `Your guess '${guess}' is wrong. Try again!`); } } change(key, value, user) { if (user.userid !== this.host.userid) return user.sendTo(this.room, "Only the host can edit the giveaway."); if (this.phase !== 'pending') return user.sendTo(this.room, "You cannot change the question or answer once the giveaway has started."); if (key === 'question') { this.question = value; return user.sendTo(this.room, `The question has been changed to ${value}.`); } let ans = QuestionGiveaway.sanitizeAnswers(value); if (!ans.length) return user.sendTo(this.room, "You must specify at least one answer and it must not contain any special characters."); this.answers = ans; user.sendTo(this.room, `The answer${checkPlural(ans, "s have", "has")} been changed to ${ans.join(', ')}.`); } end(force) { if (force) { this.clearTimer(); this.changeUhtml('

The giveaway was forcibly ended.

'); this.room.send("The giveaway was forcibly ended."); } else { if (!this.winner) { this.changeUhtml('

The giveaway was forcibly ended.

'); this.room.send("The giveaway has been forcibly ended as no one has answered the question."); } else { this.changeUhtml('

The giveaway has ended! Scroll down to see the answer.

'); this.phase = 'ended'; this.clearTimer(); this.room.modlog(`${this.winner.name} won ${this.giver.name}'s giveaway for a "${this.prize}"`); this.send(this.generateWindow(`

${Tools.escapeHTML(this.winner.name)} won the giveaway! Congratulations!

` + `

${this.question}
Correct answer${checkPlural(this.answers)}: ${this.answers.join(', ')}

`)); if (this.winner.connected) this.winner.popup(`You have won the giveaway. PM **${Tools.escapeHTML(this.giver.name)}** to claim your prize!`); if (this.giver.connected) this.giver.popup(`${Tools.escapeHTML(this.winner.name)} has won your question giveaway!`); } } delete this.room.giveaway; } static sanitizeAnswers(target) { let ret = []; for (let ans of target.split(",")) { ans = ans.replace(/[^a-z0-9 .-]+/ig, "").trim(); if (!toId(ans)) continue; ret.push(ans); } return ret; } } class LotteryGiveaway extends Giveaway { constructor(host, giver, room, prize, winners) { super(host, giver, room, prize); this.type = 'lottery'; this.maxwinners = winners || 1; this.send(this.generateReminder(false)); this.timer = setTimeout(() => this.drawLottery(), 1000 * 60 * 2); } generateReminder(joined) { let cmd = (joined ? 'Leave' : 'Join'); let button = ``; return this.generateWindow(`The lottery drawing will occur in 2 minutes, and with ${this.maxwinners} winner${checkPlural(this.maxwinners)}!
${button}

`); } display() { let joined = this.generateReminder(true); let notJoined = this.generateReminder(); for (let i in this.room.users) { let thisUser = this.room.users[i]; if (this.checkJoined(thisUser)) { thisUser.sendTo(this.room, `|uhtmlchange|giveaway${this.room.gaNumber}${this.phase}|
${joined}
`); } else { thisUser.sendTo(this.room, `|uhtmlchange|giveaway${this.room.gaNumber}${this.phase}|
${notJoined}
`); } } } addUser(user) { if (this.phase !== 'pending') return user.sendTo(this.room, "The join phase of the lottery giveaway has ended."); if (!user.named) return user.sendTo(this.room, "You need to choose a name before joining a lottery giveaway."); if (this.checkJoined(user)) return user.sendTo(this.room, "You have already joined the giveaway."); if (this.checkExcluded(user)) return user.sendTo(this.room, "You are disallowed from entering the giveaway."); this.joined[user.latestIp] = user.userid; user.sendTo(this.room, `|uhtmlchange|giveaway${this.room.gaNumber}${this.phase}|
${this.generateReminder(true)}
`); user.sendTo(this.room, "You have successfully joined the lottery giveaway."); } removeUser(user) { if (this.phase !== 'pending') return user.sendTo(this.room, "The join phase of the lottery giveaway has ended."); if (!this.checkJoined(user)) return user.sendTo(this.room, "You have not joined the lottery giveaway."); for (let ip in this.joined) { if (ip === user.latestIp || this.joined[ip] === user.userid) { delete this.joined[ip]; } } user.sendTo(this.room, `|uhtmlchange|giveaway${this.room.gaNumber}${this.phase}|
${this.generateReminder(false)}
`); user.sendTo(this.room, "You have left the lottery giveaway."); } drawLottery() { this.clearTimer(); let userlist = Object.values(this.joined); if (userlist.length < this.maxwinners) { this.changeUhtml('

The giveaway was forcibly ended.

'); delete this.room.giveaway; return this.room.send("The giveaway has been forcibly ended as there are not enough participants."); } this.winners = []; while (this.winners.length < this.maxwinners) { let winner = Users(userlist.splice(Math.floor(Math.random() * userlist.length), 1)[0]); if (!winner) continue; this.winners.push(winner); } this.end(); } end(force) { if (force) { this.clearTimer(); this.changeUhtml('

The giveaway was forcibly ended.

'); this.room.send("The giveaway was forcibly ended."); } else { this.changeUhtml(`

The giveaway has ended! Scroll down to see the winner${checkPlural(this.winners)}.

`); this.phase = 'ended'; let winnerNames = this.winners.map(winner => winner.name).join(', '); this.room.modlog(`${winnerNames} won ${this.giver.name}'s giveaway for "${this.prize}"`); this.send(this.generateWindow(`

Lottery Draw

${Object.keys(this.joined).length} users joined the giveaway.
Our lucky winner${checkPlural(this.winners)}: ${Tools.escapeHTML(winnerNames)}! Congratulations!

`)); for (let i = 0; i < this.winners.length; i++) { if (this.winners[i].connected) this.winners[i].popup(`You have won the lottery giveaway! PM **${this.giver.name}** to claim your prize!`); } if (this.giver.connected) this.giver.popup(`The following users have won your lottery giveaway:\n${Tools.escapeHTML(winnerNames)}`); } delete this.room.giveaway; } } let commands = { // question giveaway. quiz: 'question', qg: 'question', question: function (target, room, user) { if (room.id !== 'wifi' || !target) return false; if (room.giveaway) return this.errorReply("There is already a giveaway going on!"); let params = target.split(target.includes('|') ? '|' : ',').map(param => param.trim()); if (params.length < 4) return this.errorReply("Invalid arguments specified - /question giver, prize, question, answer(s)"); let targetUser = Users(params[0]); if (!targetUser || !targetUser.connected) return this.errorReply(`User '${params[0]}' is not online.`); if (!this.can('warn', null, room) && !(this.can('broadcast', null, room) && user === targetUser)) return this.errorReply("Permission denied."); if (!targetUser.autoconfirmed) return this.errorReply(`User '${targetUser.name}' needs to be autoconfirmed to give something away.`); if (Giveaway.checkBanned(room, targetUser)) return this.errorReply(`User '${targetUser.name}' is giveaway banned.`); room.giveaway = new QuestionGiveaway(user, targetUser, room, params[1], params[2], params.slice(3).join(',')); this.privateModCommand(`(${user.name} started a question giveaway for ${targetUser.name})`); }, changeanswer: 'changequestion', changequestion: function (target, room, user, conn, cmd) { if (room.id !== 'wifi') return false; if (!room.giveaway) return this.errorReply("There is no giveaway going on at the moment."); if (room.giveaway.type !== 'question') return this.errorReply("This is not a question giveaway."); target = target.trim(); if (!target) return this.errorReply("You must include a question or an answer."); room.giveaway.change(cmd.substr(6), target, user); }, showanswer: 'viewanswer', viewanswer: function (target, room, user) { if (room.id !== 'wifi') return false; let giveaway = room.giveaway; if (!giveaway) return this.errorReply("There is no giveaway going on at the moment."); if (giveaway.type !== 'question') return this.errorReply("This is not a question giveaway."); if (user.userid !== giveaway.host.userid && user.userid !== giveaway.giver.userid) return; this.sendReply(`The giveaway question is ${giveaway.question}.\n` + `The answer${checkPlural(giveaway.answers, 's are', ' is')} ${giveaway.answers.join(', ')}.`); }, guessanswer: 'guess', guess: function (target, room, user) { if (room.id !== 'wifi') return this.errorReply("This command can only be used in the Wi-Fi room."); if (!this.canTalk()) return this.errorReply("You cannot do this while unable to talk."); if (!room.giveaway) return this.errorReply("There is no giveaway going on at the moment."); if (room.giveaway.type !== 'question') return this.errorReply("This is not a question giveaway."); room.giveaway.guessAnswer(user, target); }, // lottery giveaway. lg: 'lottery', lotto: 'lottery', lottery: function (target, room, user) { if (room.id !== 'wifi' || !target) return false; if (room.giveaway) return this.errorReply("There is already a giveaway going on!"); let params = target.split(target.includes('|') ? '|' : ',').map(param => param.trim()); if (params.length < 2) return this.errorReply("Invalid arguments specified - /lottery giver, prize [, maxwinners]"); let targetUser = Users(params[0]); if (!targetUser || !targetUser.connected) return this.errorReply(`User '${params[0]}' is not online.`); if (!this.can('warn', null, room) && !(this.can('broadcast', null, room) && user === targetUser)) return this.errorReply("Permission denied."); if (!targetUser.autoconfirmed) return this.errorReply(`User '${targetUser.name}' needs to be autoconfirmed to give something away.`); if (Giveaway.checkBanned(room, targetUser)) return this.errorReply(`User '${targetUser.name}' is giveaway banned.`); let numWinners = 1; if (params.length > 2) { numWinners = parseInt(params[2]); if (isNaN(numWinners) || numWinners < 1 || numWinners > 10) return this.errorReply("The lottery giveaway can have a minimum of 1 and a maximum of 10 winners."); } room.giveaway = new LotteryGiveaway(user, targetUser, room, params[1], numWinners); this.privateModCommand(`(${user.name} started a lottery giveaway for ${targetUser.name})`); }, leavelotto: 'join', leavelottery: 'join', leave: 'join', joinlotto: 'join', joinlottery: 'join', join: function (target, room, user, conn, cmd) { if (room.id !== 'wifi') return this.errorReply("This command can only be used in the Wi-Fi room."); if (!this.canTalk()) return this.errorReply("You cannot do this while unable to talk."); let giveaway = room.giveaway; if (!giveaway) return this.errorReply("There is no giveaway going on at the moment."); if (giveaway.type !== 'lottery') return this.errorReply("This is not a lottery giveaway."); switch (cmd) { case 'joinlottery': case 'join': case 'joinlotto': giveaway.addUser(user); break; case 'leavelottery': case 'leave': case 'leavelotto': giveaway.removeUser(user); break; } }, // general. ban: function (target, room, user) { if (!target) return false; if (room.id !== 'wifi') return this.errorReply("This command can only be used in the Wi-Fi room."); if (!this.can('warn', null, room)) return false; target = this.splitTarget(target); let targetUser = this.targetUser; if (!targetUser) return this.errorReply(`User '${this.targetUsername}' not found.`); if (target.length > 300) { return this.errorReply("The reason is too long. It cannot exceed 300 characters."); } if (Giveaway.checkBanned(room, targetUser)) return this.errorReply(`User '${this.targetUsername}' is already banned from entering giveaways.`); Giveaway.ban(room, targetUser, target); if (room.giveaway) room.giveaway.kickUser(targetUser); if (target) target = ` (${target})`; this.privateModCommand(`(${targetUser.name} was banned from entering giveaways by ${user.name}.${target})`); }, unban: function (target, room, user) { if (!target) return false; if (room.id !== 'wifi') return this.errorReply("This command can only be used in the Wi-Fi room."); if (!this.can('warn', null, room)) return false; this.splitTarget(target); let targetUser = this.targetUser; if (!targetUser) return this.errorReply(`User '${this.targetUsername}' not found.`); if (!Giveaway.checkBanned(room, targetUser)) return this.errorReply(`User '${this.targetUsername}' isn't banned from entering giveaways.`); Giveaway.unban(room, targetUser); this.privateModCommand(`${targetUser.name} was unbanned from entering giveaways by ${user.name}.`); }, stop: 'end', end: function (target, room, user) { if (room.id !== 'wifi') return this.errorReply("This command can only be used in the Wi-Fi room."); if (!room.giveaway) return this.errorReply("There is no giveaway going on at the moment."); if (!this.can('warn', null, room) && user.userid !== room.giveaway.host.userid) return false; if (target && target.length > 300) { return this.errorReply("The reason is too long. It cannot exceed 300 characters."); } room.giveaway.end(true); if (target) target = `: ${target}`; this.privateModCommand(`(The giveaway was forcibly ended by ${user.name}${target})`); }, rm: 'remind', remind: function (target, room, user) { if (room.id !== 'wifi') return this.errorReply("This command can only be used in the Wi-Fi room."); let giveaway = room.giveaway; if (!giveaway) return this.errorReply("There is no giveaway going on at the moment."); if (!this.runBroadcast()) return; if (giveaway.type === 'question') { if (giveaway.phase !== 'started') return this.errorReply("The giveaway has not started yet."); room.giveaway.send(room.giveaway.generateQuestion()); } else { room.giveaway.display(); } }, '': 'help', help: function (target, room, user) { if (room.id !== 'wifi') return this.errorReply("This command can only be used in the Wi-Fi room."); let reply = ''; switch (target) { case 'staff': if (!this.can('warn', null, room)) return; reply = 'Staff commands:
' + '- question or qg User | Prize | Question | Answer[,Answer2,Answer3] - Start a new question giveaway (voices can only host for themselves, staff can for all users) (Requires: + % @ * # & ~)
' + '- lottery or lg User | Prize[| Number of Winners] - Starts a lottery giveaway (voices can only host for themselves, staff can for all users) (Requires: + % @ * # & ~)
' + '- changequestion - Changes the question of a question giveaway (Requires: giveaway host)
' + '- changeanswer - Changes the answer of a question giveaway (Requires: giveaway host)
' + '- viewanswer - Shows the answer in a question giveaway (only to giveaway host/giver)
' + '- ban - Temporarily bans a user from entering giveaways (Requires: % @ * # & ~)
' + '- end - Forcibly ends the current giveaway (Requires: % @ * # & ~)
'; break; case 'game': case 'giveaway': case 'user': if (!this.runBroadcast()) return; reply = 'Giveaway participation commands: (start with /giveaway, except for /ga)
' + '- guess or /ga answer - Guesses the answer for a question giveaway
' + '- viewanswer - Shows the answer in a question giveaway (only to host/giver)
' + '- remind - Shows the details of the current giveaway (can be broadcast)
' + '- join or joinlottery - Joins a lottery giveaway
' + '- leave or leavelottery - Leaves a lottery giveaway
'; break; default: if (!this.runBroadcast()) return; reply = 'Wi-Fi room Giveaway help and info
' + '- help user - shows list of participation commands
' + '- help staff - shows giveaway staff commands (Requires: % @ * # & ~)'; } this.sendReplyBox(reply); }, }; exports.commands = { 'giveaway': commands, 'ga': commands.guess, 'gh': commands.help, 'qg': commands.question, 'lg': commands.lottery, };