/**
* Wi-Fi chat-plugin. Only works in a room with id 'wifi'
* Handles giveaways in the formats: question, lottery, gts
* Written by bumbadadabum, based on the original plugin as written by Codelegend, SilverTactic, DanielCranham
*/
import {FS} from '../../lib/fs';
import {Utils} from '../../lib/utils';
Punishments.roomPunishmentTypes.set('GIVEAWAYBAN', 'banned from giveaways');
const BAN_DURATION = 7 * 24 * 60 * 60 * 1000;
const RECENT_THRESHOLD = 30 * 24 * 60 * 60 * 1000;
const STATS_FILE = 'config/chat-plugins/wifi.json';
const stats: {[k: string]: number[]} = JSON.parse(FS(STATS_FILE).readIfExistsSync() || "{}");
function saveStats() {
FS(STATS_FILE).writeUpdate(() => JSON.stringify(stats));
}
function toPokemonId(str: string) {
return str.toLowerCase().replace(/é/g, 'e').replace(/[^a-z0-9 -/]/g, '');
}
class Giveaway {
gaNumber: number;
host: User;
giver: User;
room: Room;
ot: string;
tid: string;
prize: string;
phase: string;
joined: {[k: string]: ID};
timer: NodeJS.Timer | null;
monIDs: Set It's giveaway time! Giveaway started by ${Utils.escapeHTML(this.host.name)} Giver: ${this.giver} Note: You must have a Switch, Pokémon Sword/Shield, and Nintendo Switch Online to receive the prize. Do not join if you are currently unable to trade. Do not enter if you have already won this exact Pokémon, unless it is explicitly allowed. Giveaway Question: ${this.question} use /ga to guess. The giveaway has started! Scroll down to see the question. The giveaway was forcibly ended. The giveaway was forcibly ended. The giveaway has ended! Scroll down to see the answer. ${Utils.escapeHTML(this.winner.name)} won the giveaway! Congratulations! ${this.question}
`;
}
}
}
return [monIDs, output];
}
static updateStats(monIDs: Set${this.sprite} ` +
`
OT: ${Utils.escapeHTML(this.ot)}, TID: ${this.tid}${rightSide}
Correct answer${Chat.plural(this.answers)}: ${this.answers.join(', ')}
${button}
The giveaway was forcibly ended.
'); delete this.room.giveaway; return this.room.send("The giveaway has been forcibly ended as there are no participants."); } while (this.winners.length < this.maxWinners && userlist.length > 0) { const winner = Users.get(userlist.splice(Math.floor(Math.random() * userlist.length), 1)[0]); if (!winner) continue; this.winners.push(winner); } this.end(); } end(force = false) { 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${Chat.plural(this.winners)}.
`); this.phase = 'ended'; const winnerNames = this.winners.map(winner => winner.name).join(', '); this.room.modlog({ action: 'GIVEAWAY WIN', note: `${winnerNames} won ${this.giver.name}'s giveaway for "${this.prize}" (OT: ${this.ot} TID: ${this.tid})`, }); this.send(this.generateWindow( `Lottery Draw
` + `${Object.keys(this.joined).length} users joined the giveaway.
` +
`Our lucky winner${Chat.plural(this.winners)}: ${Utils.escapeHTML(winnerNames)}!
Congratulations!
There is a GTS giveaway going on!
` + `Hosted by: ${Utils.escapeHTML(this.giver.name)} | Left: ${this.left}
` + `| Last winners: ${this.sent.join(' ')} | ` :
'') +
`${this.sprite} | ${Chat.formatText(this.summary, true)} | ` + `${rightSide} |
More Pokémon have been deposited than there are prizes in this giveaway and new deposits will not be accepted. If you have already deposited a Pokémon, please be patient, and do not withdraw your Pokémon.
`); this.changeUhtml(this.generateWindow()); } end(force = false) { if (force) { this.clearTimer(); this.changeUhtml('The GTS giveaway was forcibly ended.
'); this.room.send("The GTS giveaway was forcibly ended."); } else { this.clearTimer(); this.changeUhtml(`The GTS giveaway has finished.
`); this.room.modlog({ action: 'GTS FINISHED', userid: this.giver.id, note: `their GTS giveaway for "${this.summary}"`, }); this.send(`The GTS giveaway for a "${Utils.escapeHTML(this.lookfor)}" has finished.
`); Giveaway.updateStats(this.monIDs); } this.room.gtsga = undefined; return this.left; } // This currently doesn't match some of the edge cases the other pokemon matching function does account for // (such as Type: Null). However, this should never be used as a fodder mon anyway, // so I don't see a huge need to implement it. static linkify(text: string) { const parsed = text.toLowerCase().replace(/é/g, 'e'); for (const i in Dex.data.Pokedex) { let id = i; if (!Dex.data.Pokedex[i].baseSpecies && (Dex.data.Pokedex[i].name.includes(' '))) { id = toPokemonId(Dex.data.Pokedex[i].name); } const regexp = new RegExp(`\\b${id}\\b`, 'ig'); const res = regexp.exec(parsed); if (res) { const num = Dex.data.Pokedex[i].num < 100 ? (Dex.data.Pokedex[i].num < 10 ? `00${Dex.data.Pokedex[i].num}` : `0${Dex.data.Pokedex[i].num}`) : Dex.data.Pokedex[i].num; return `${text.slice(0, res.index)}${text.slice(res.index, res.index + res[0].length)}${text.slice(res.index + res[0].length)}`; } } return text; } } const cmds: ChatCommands = { // question giveaway. quiz: 'question', qg: 'question', question(target, room, user) { room = this.requireRoom('wifi' as RoomID); if (!target) return false; if (room.giveaway) return this.errorReply("There is already a giveaway going on!"); let [giver, ot, tid, prize, question, ...answers] = target.split(target.includes('|') ? '|' : ',').map( param => param.trim() ); if (!(giver && ot && tid && prize && question && answers.length)) { return this.errorReply("Invalid arguments specified - /qg giver | ot | tid | prize | question | answer(s)"); } tid = toID(tid); if (isNaN(parseInt(tid)) || tid.length < 5 || tid.length > 6) return this.errorReply("Invalid TID"); const targetUser = Users.get(giver); if (!targetUser || !targetUser.connected) return this.errorReply(`User '${giver}' is not online.`); if (!user.can('warn', null, room) && !(user.can('show', null, room) && user === targetUser)) { return this.errorReply("/qg - Access 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, ot, tid, prize, question, answers); this.privateModAction(`${user.name} started a question giveaway for ${targetUser.name}`); this.modlog('QUESTION GIVEAWAY', null, `for ${targetUser.getLastId()}`); }, changeanswer: 'changequestion', changequestion(target, room, user, conn, cmd) { room = this.requireRoom('wifi' as RoomID); 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 as QuestionGiveaway).change(cmd.substr(6), target, user); }, showanswer: 'viewanswer', viewanswer(target, room, user) { room = this.requireRoom('wifi' as RoomID); const giveaway = room.giveaway as QuestionGiveaway; 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.id !== giveaway.host.id && user.id !== giveaway.giver.id) return; this.sendReply(`The giveaway question is ${giveaway.question}.\n` + `The answer${Chat.plural(giveaway.answers, 's are', ' is')} ${giveaway.answers.join(', ')}.`); }, guessanswer: 'guess', guess(target, room, user) { room = this.requireRoom('wifi' as RoomID); this.checkChat(); 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 as QuestionGiveaway).guessAnswer(user, target); }, // lottery giveaway. lg: 'lottery', lotto: 'lottery', lottery(target, room, user) { room = this.requireRoom('wifi' as RoomID); if (!target) return false; if (room.giveaway) return this.errorReply("There is already a giveaway going on!"); let [giver, ot, tid, prize, winners] = target.split(target.includes('|') ? '|' : ',').map(param => param.trim()); if (!(giver && ot && tid && prize)) { return this.errorReply("Invalid arguments specified - /lottery giver | ot | tid | prize | winners"); } tid = toID(tid); if (isNaN(parseInt(tid)) || tid.length < 5 || tid.length > 6) return this.errorReply("Invalid TID"); const targetUser = Users.get(giver); if (!targetUser || !targetUser.connected) return this.errorReply(`User '${giver}' is not online.`); if (!user.can('warn', null, room) && !(user.can('show', null, room) && user === targetUser)) { return this.errorReply("/lg - Access 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 (winners) { numWinners = parseInt(winners); if (isNaN(numWinners) || numWinners < 1 || numWinners > 5) { return this.errorReply("The lottery giveaway can have a minimum of 1 and a maximum of 5 winners."); } } room.giveaway = new LotteryGiveaway(user, targetUser, room, ot, tid, prize, numWinners); this.privateModAction(`${user.name} started a lottery giveaway for ${targetUser.name}`); this.modlog('LOTTERY GIVEAWAY', null, `for ${targetUser.getLastId()}`); }, leavelotto: 'join', leavelottery: 'join', leave: 'join', joinlotto: 'join', joinlottery: 'join', join(target, room, user, conn, cmd) { room = this.requireRoom('wifi' as RoomID); this.checkChat(); if (user.semilocked) return; const giveaway = room.giveaway as LotteryGiveaway; 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; } }, // gts commands gts: { new: 'start', start(target, room, user) { room = this.requireRoom('wifi' as RoomID); if (!target) return false; if (room.gtsga) return this.errorReply("There is already a GTS giveaway going on!"); const [giver, amountStr, summary, deposit, lookfor] = target.split(target.includes('|') ? '|' : ',').map( param => param.trim() ); if (!(giver && amountStr && summary && deposit && lookfor)) { return this.errorReply("Invalid arguments specified - /gts start giver | amount | summary | deposit | lookfor"); } const amount = parseInt(amountStr); if (!amount || amount < 20 || amount > 100) { return this.errorReply("Please enter a valid amount. For a GTS giveaway, you need to give away at least 20 mons, and no more than 100."); } const targetUser = Users.get(giver); if (!targetUser || !targetUser.connected) return this.errorReply(`User '${giver}' is not online.`); this.checkCan('warn', null, room); if (!targetUser.autoconfirmed) { return this.errorReply(`User '${targetUser.name}' needs to be autoconfirmed to host a giveaway.`); } if (Giveaway.checkBanned(room, targetUser)) return this.errorReply(`User '${targetUser.name}' is giveaway banned.`); room.gtsga = new GTSGiveaway(room, targetUser, amount, summary, deposit, lookfor); this.privateModAction(`${user.name} started a GTS giveaway for ${targetUser.name} with ${amount} Pokémon`); this.modlog('GTS GIVEAWAY', null, `for ${targetUser.getLastId()} with ${amount} Pokémon`); }, left(target, room, user) { room = this.requireRoom('wifi' as RoomID); if (!room.gtsga) return this.errorReply("There is no GTS giveaway going on!"); if (!user.can('warn', null, room) && user !== room.gtsga.giver) { return this.errorReply("Only the host or a staff member can update GTS giveaways."); } if (!target) { if (!this.runBroadcast()) return; let output = `The GTS giveaway from ${room.gtsga.giver} has ${room.gtsga.left} Pokémon remaining!`; if (room.gtsga.sent.length) output += `Last winners: ${room.gtsga.sent.join(', ')}`; return this.sendReply(output); } const newamount = parseInt(target); if (isNaN(newamount)) return this.errorReply("Please enter a valid amount."); if (newamount > room.gtsga.left) return this.errorReply("The new amount must be lower than the old amount."); if (newamount < room.gtsga.left - 1) { this.modlog(`GTS GIVEAWAY`, null, `set from ${room.gtsga.left} to ${newamount} left`); } room.gtsga.updateLeft(newamount); }, sent(target, room, user) { room = this.requireRoom('wifi' as RoomID); if (!room.gtsga) return this.errorReply("There is no GTS giveaway going on!"); if (!user.can('warn', null, room) && user !== room.gtsga.giver) { return this.errorReply("Only the host or a staff member can update GTS giveaways."); } if (!target || target.length > 12) return this.errorReply("Please enter a valid IGN."); room.gtsga.updateSent(target); }, full(target, room, user) { room = this.requireRoom('wifi' as RoomID); if (!room.gtsga) return this.errorReply("There is no GTS giveaway going on!"); if (!user.can('warn', null, room) && user !== room.gtsga.giver) { return this.errorReply("Only the host or a staff member can update GTS giveaways."); } if (room.gtsga.noDeposits) return this.errorReply("The GTS giveaway was already set to not accept deposits."); room.gtsga.stopDeposits(); }, end(target, room, user) { room = this.requireRoom('wifi' as RoomID); if (!room.gtsga) return this.errorReply("There is no GTS giveaway going on at the moment."); this.checkCan('warn', null, room); if (target && target.length > 300) { return this.errorReply("The reason is too long. It cannot exceed 300 characters."); } const amount = room.gtsga.end(true); if (target) target = `: ${target}`; this.modlog('GTS END', null, `with ${amount} left${target}`); this.privateModAction(`The giveaway was forcibly ended by ${user.name} with ${amount} left${target}`); }, }, // general. ban(target, room, user) { if (!target) return false; room = this.requireRoom('wifi' as RoomID); this.checkCan('warn', null, room); target = this.splitTarget(target, false); const 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 (Punishments.getRoomPunishType(room, this.targetUsername)) { return this.errorReply(`User '${this.targetUsername}' is already punished in this room.`); } Giveaway.ban(room, targetUser, target); if (room.giveaway) room.giveaway.kickUser(targetUser); this.modlog('GIVEAWAYBAN', targetUser, target); if (target) target = ` (${target})`; this.privateModAction(`${targetUser.name} was banned from entering giveaways by ${user.name}.${target}`); }, unban(target, room, user) { if (!target) return false; room = this.requireRoom('wifi' as RoomID); this.checkCan('warn', null, room); this.splitTarget(target, false); const 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.privateModAction(`${targetUser.name} was unbanned from entering giveaways by ${user.name}.`); this.modlog('GIVEAWAYUNBAN', targetUser, null, {noip: 1, noalts: 1}); }, stop: 'end', end(target, room, user) { room = this.requireRoom('wifi' as RoomID); if (!room.giveaway) return this.errorReply("There is no giveaway going on at the moment."); if (user.id !== room.giveaway.host.id) this.checkCan('warn', null, room); if (target && target.length > 300) { return this.errorReply("The reason is too long. It cannot exceed 300 characters."); } room.giveaway.end(true); this.modlog('GIVEAWAY END', null, target); if (target) target = `: ${target}`; this.privateModAction(`The giveaway was forcibly ended by ${user.name}${target}`); }, rm: 'remind', remind(target, room, user) { room = this.requireRoom('wifi' as RoomID); const 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."); giveaway.send((giveaway as QuestionGiveaway).generateQuestion()); } else { (giveaway as LotteryGiveaway).display(); } }, count(target, room, user) { room = this.requireRoom('wifi' as RoomID); target = [...Giveaway.getSprite(target)[0]][0]; if (!target) return this.errorReply("No mon entered - /giveaway count pokemon."); if (!this.runBroadcast()) return; const count = stats[target]; if (!count) return this.sendReplyBox("This Pokémon has never been given away."); const recent = count.filter(val => val + RECENT_THRESHOLD > Date.now()).length; this.sendReplyBox(`This Pokémon has been given away ${Chat.count(count, "times")}, a total of ${Chat.count(recent, "times")} in the past month.`); }, '': 'help', help(target, room, user) { room = this.requireRoom('wifi' as RoomID); let reply = ''; switch (target) { case 'staff': this.checkCan('show', null, room); reply = 'Staff commands:!giveaway count to broadcast this to the entire room