/** * Minor activities * Pokemon Showdown - http://pokemonshowdown.com/ * * Minor activities are representations of non-game activities that rooms * regularly use, such as polls and announcements. Rooms are limited to * one minor activity at a time. * * Minor activities keep track of users in the form of userids and IPs. * If a player votes for a poll under one IP, they cannot vote for the same * poll again. * * The user-tracking system is not implemented at the base level: Announcements * do not have a reason to keep track of users' IPs/IDs because they're just used * to broadcast a message to a room. * * @license MIT */ export interface MinorActivityData { readonly activityid: 'announcement' | 'poll'; activityNumber?: number; source?: string; timeoutMins?: number; timerEnd?: number; question: string; supportHTML: boolean; multiPoll: boolean; pendingVotes?: { [userid: string]: number[] }; voters?: { [k: string]: number[] }; voterIps?: { [k: string]: number[] }; totalVotes?: number; isQuiz?: boolean; answers: string[] | { name: string, votes: number, correct?: boolean }[]; } // globally Rooms.MinorActivity export abstract class MinorActivity { abstract activityid: ID; abstract name: string; timeout: NodeJS.Timeout | null; timeoutMins: number; timerEnd: number; roomid: RoomID; room: Room; supportHTML: boolean; constructor(room: Room) { this.timeout = null; this.timeoutMins = 0; this.timerEnd = 0; this.roomid = room.roomid; this.room = room; this.supportHTML = false; } setTimer(options: { timeoutMins?: number, timerEnd?: number }) { if (this.timeout) clearTimeout(this.timeout); this.timeoutMins = options.timeoutMins || 0; if (!this.timeoutMins) { this.timerEnd = 0; this.timeout = null; return; } const now = Date.now(); this.timerEnd = options.timerEnd || now + this.timeoutMins * 60000; this.timeout = setTimeout(() => { const room = this.room; if (!room) return; // someone forgot to `.destroy()` this.end(room); }, this.timerEnd - now); this.save(); } end(room: Room, MinorActivityClass?: new (...args: any[]) => any) { room.minorActivity?.destroy(); if (room.minorActivityQueue?.length) { const pollData = room.minorActivityQueue.shift()!; if (!room.minorActivityQueue.length) room.clearMinorActivityQueue(); if (!room.settings.minorActivityQueue?.length) { delete room.settings.minorActivityQueue; room.saveSettings(); } if (pollData.activityid !== 'poll') throw new Error(`Unexpected Minor Activity (${pollData.activityid}) in queue`); room.add(`|c|~|/log ${room.tr`The queued poll was started.`}`).update(); room.modlog({ action: 'POLL', note: '(queued)', }); if (!MinorActivityClass) { if (pollData.activityid === 'poll') { const { Poll } = require('./chat-plugins/poll'); room.setMinorActivity(new Poll(room, pollData)); } } else { room.setMinorActivity(new MinorActivityClass(room, pollData)); } } } endTimer() { if (!this.timeout) return false; clearTimeout(this.timeout); this.timeoutMins = 0; this.timerEnd = 0; return true; } abstract destroy(): void; abstract display(): void; onConnect?(user: User, connection: Connection | null): void; onRename?(user: User, oldid: ID, joining: boolean): void; abstract toJSON(): AnyObject; abstract save(): void; }