mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-04-26 10:48:53 -05:00
Repeats: Support non-time-based repeats (#7518)
This commit is contained in:
parent
2cca8bd48e
commit
6d92ac8547
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import {roomFaqs, getAlias} from './room-faqs';
|
||||
import type {MessageHandler} from '../rooms';
|
||||
|
||||
export interface RepeatedPhrase {
|
||||
/** Identifier for deleting */
|
||||
|
|
@ -13,13 +14,14 @@ export interface RepeatedPhrase {
|
|||
/** interval in milliseconds */
|
||||
interval: number;
|
||||
faq?: boolean;
|
||||
isByMessages?: boolean;
|
||||
isHTML?: boolean;
|
||||
}
|
||||
|
||||
export const Repeats = new class {
|
||||
// keying to Room rather than RoomID will help us correctly handle room renames
|
||||
/** room:identifier:phrase:timeout map */
|
||||
repeats = new Map<BasicRoom, Map<ID, Map<string, NodeJS.Timeout>>>();
|
||||
repeats = new Map<BasicRoom, Map<ID, Map<string, NodeJS.Timeout | MessageHandler>>>();
|
||||
|
||||
constructor() {
|
||||
for (const room of Rooms.rooms.values()) {
|
||||
|
|
@ -30,6 +32,14 @@ export const Repeats = new class {
|
|||
}
|
||||
}
|
||||
|
||||
removeRepeatHandler(room: BasicRoom, handler?: NodeJS.Timeout | MessageHandler) {
|
||||
if (typeof handler === 'function') {
|
||||
room.nthMessageHandlers.delete(handler);
|
||||
} else if (typeof handler === 'object') {
|
||||
clearInterval(handler);
|
||||
}
|
||||
}
|
||||
|
||||
hasRepeat(room: BasicRoom, id: ID) {
|
||||
return !!this.repeats.get(room)?.get(id);
|
||||
}
|
||||
|
|
@ -51,7 +61,7 @@ export const Repeats = new class {
|
|||
const roomRepeats = this.repeats.get(room);
|
||||
if (!roomRepeats) return;
|
||||
const oldInterval = roomRepeats.get(id)?.get(phrase!);
|
||||
if (oldInterval) clearInterval(oldInterval);
|
||||
this.removeRepeatHandler(room, oldInterval);
|
||||
roomRepeats.delete(id);
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +70,7 @@ export const Repeats = new class {
|
|||
if (!roomRepeats) return;
|
||||
for (const ids of roomRepeats.values()) {
|
||||
for (const interval of ids.values()) {
|
||||
if (interval) clearInterval(interval);
|
||||
this.removeRepeatHandler(room, interval);
|
||||
}
|
||||
}
|
||||
this.repeats.delete(room);
|
||||
|
|
@ -77,25 +87,31 @@ export const Repeats = new class {
|
|||
if (roomRepeats.has(id)) {
|
||||
throw new Error(`Repeat already exists`);
|
||||
}
|
||||
|
||||
roomRepeats.set(id, new Map().set(phrase, setInterval(() => {
|
||||
if (room !== Rooms.get(room.roomid)) {
|
||||
const repeater = (targetRoom: BasicRoom) => {
|
||||
if (targetRoom !== Rooms.get(targetRoom.roomid)) {
|
||||
// room was deleted
|
||||
this.clearRepeats(room);
|
||||
this.clearRepeats(targetRoom);
|
||||
return;
|
||||
}
|
||||
const formattedText = repeat.faq ? Chat.formatText(roomFaqs[room.roomid][repeat.id], true) :
|
||||
const formattedText = repeat.faq ? Chat.formatText(roomFaqs[targetRoom.roomid][repeat.id], true) :
|
||||
repeat.isHTML ? repeat.phrase : Chat.formatText(repeat.phrase, false, true);
|
||||
room.add(`|html|<div class="infobox">${formattedText}</div>`);
|
||||
room.update();
|
||||
}, interval)));
|
||||
targetRoom.add(`|html|<div class="infobox">${formattedText}</div>`);
|
||||
targetRoom.update();
|
||||
};
|
||||
|
||||
if (repeat.isByMessages) {
|
||||
room.nthMessageHandlers.set(repeater, interval);
|
||||
roomRepeats.set(id, new Map().set(phrase, repeater));
|
||||
} else {
|
||||
roomRepeats.set(id, new Map().set(phrase, setInterval(repeater, interval, room)));
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (const roomRepeats of this.repeats.values()) {
|
||||
for (const [room, roomRepeats] of this.repeats) {
|
||||
for (const ids of roomRepeats.values()) {
|
||||
for (const interval of ids.values()) {
|
||||
if (interval) clearInterval(interval);
|
||||
this.removeRepeatHandler(room, interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -119,14 +135,14 @@ export const pages: PageTable = {
|
|||
html += `<h2>${this.tr`Repeated phrases in ${room.title}`}</h2>`;
|
||||
html += `<table><tr><th>${this.tr`Identifier`}</th><th>${this.tr`Phrase`}</th><th>${this.tr`Raw text`}</th><th>${this.tr`Interval`}</th><th>${this.tr`Action`}</th>`;
|
||||
for (const repeat of room.settings.repeats) {
|
||||
const minutes = repeat.interval / (60 * 1000);
|
||||
const minutes = repeat.interval / (repeat.isByMessages ? 1 : 60 * 1000);
|
||||
if (!repeat.faq) {
|
||||
const phrase = repeat.isHTML ? repeat.phrase : Chat.formatText(repeat.phrase, false, true);
|
||||
html += `<tr><td>${repeat.id}</td><td>${phrase}</td><td>${Chat.getReadmoreCodeBlock(repeat.phrase)}</td><td>${this.tr`every ${minutes} minute(s)`}</td>`;
|
||||
html += `<tr><td>${repeat.id}</td><td>${phrase}</td><td>${Chat.getReadmoreCodeBlock(repeat.phrase)}</td><td>${repeat.isHTML ? this.tr`every ${minutes} chat message(s)` : this.tr`every ${minutes} minute(s)`}</td>`;
|
||||
html += `<td><button class="button" name="send" value="/removerepeat ${repeat.id},${room.roomid}">${this.tr`Remove`}</button></td>`;
|
||||
} else {
|
||||
const phrase = Chat.formatText(roomFaqs[room.roomid][repeat.id], true);
|
||||
html += `<tr><td>${repeat.id}</td><td>${phrase}</td><td>${Chat.getReadmoreCodeBlock(roomFaqs[room.roomid][repeat.id])}</td><td>${this.tr`every ${minutes} minute(s)`}</td>`;
|
||||
html += `<tr><td>${repeat.id}</td><td>${phrase}</td><td>${Chat.getReadmoreCodeBlock(roomFaqs[room.roomid][repeat.id])}</td><td>${repeat.isHTML ? this.tr`every ${minutes} chat message(s)` : this.tr`every ${minutes} minute(s)`}</td>`;
|
||||
html += `<td><button class="button" name="send" value="/removerepeat ${repeat.id},${room.roomid}">${this.tr`Remove`}</button></td>`;
|
||||
}
|
||||
}
|
||||
|
|
@ -140,9 +156,12 @@ export const pages: PageTable = {
|
|||
};
|
||||
|
||||
export const commands: ChatCommands = {
|
||||
repeatbymessages: 'repeat',
|
||||
repeathtmlbymessages: 'repeat',
|
||||
repeathtml: 'repeat',
|
||||
repeat(target, room, user, connection, cmd) {
|
||||
const isHTML = cmd === 'repeathtml';
|
||||
const isHTML = cmd.includes('html');
|
||||
const isByMessages = cmd.includes('bymessages');
|
||||
room = this.requireRoom();
|
||||
this.checkCan(isHTML ? 'addhtml' : 'mute', null, room);
|
||||
const [intervalString, name, ...messageArray] = target.split(',');
|
||||
|
|
@ -150,7 +169,7 @@ export const commands: ChatCommands = {
|
|||
const phrase = messageArray.join(',').trim();
|
||||
const interval = parseInt(intervalString);
|
||||
if (isNaN(interval) || !/[0-9]{1,}/.test(intervalString) || interval < 1 || interval > 24 * 60) {
|
||||
throw new Chat.ErrorMessage(this.tr`You must specify an interval as a number of minutes between 1 and 1440.`);
|
||||
throw new Chat.ErrorMessage(this.tr`You must specify an interval as a number of minutes or chat messages between 1 and 1440.`);
|
||||
}
|
||||
|
||||
if (Repeats.hasRepeat(room, id)) {
|
||||
|
|
@ -162,34 +181,42 @@ export const commands: ChatCommands = {
|
|||
Repeats.addRepeat(room, {
|
||||
id,
|
||||
phrase,
|
||||
interval: interval * 60 * 1000, // convert to milliseconds
|
||||
// convert to milliseconds for time-based repeats
|
||||
interval: interval * (isByMessages ? 1 : 60 * 1000),
|
||||
isHTML,
|
||||
isByMessages,
|
||||
});
|
||||
|
||||
this.modlog('REPEATPHRASE', null, `every ${interval} minute${Chat.plural(interval)}: "${phrase.replace(/\n/g, ' ')}"`);
|
||||
this.modlog('REPEATPHRASE', null, `every ${interval} ${isByMessages ? `chat messages` : `minute`}${Chat.plural(interval)}: "${phrase.replace(/\n/g, ' ')}"`);
|
||||
this.privateModAction(
|
||||
room.tr`${user.name} set the phrase labeled with "${id}" to be repeated every ${interval} minute(s).`
|
||||
isByMessages ?
|
||||
room.tr`${user.name} set the phrase labeled with "${id}" to be repeated every ${interval} chat message(s).` :
|
||||
room.tr`${user.name} set the phrase labeled with "${id}" to be repeated every ${interval} minute(s).`
|
||||
);
|
||||
},
|
||||
repeathelp() {
|
||||
this.runBroadcast();
|
||||
this.sendReplyBox(
|
||||
`<code>/repeat [minutes], [id], [phrase]</code>: repeats a given phrase every [minutes] minutes.<br />` +
|
||||
`<code>/repeat [minutes], [id], [phrase]</code>: repeats a given phrase every [minutes] minutes. Requires: % @ # &<br />` +
|
||||
`<code>/repeathtml [minutes], [id], [phrase]</code>: repeats a given phrase containing HTML every [minutes] minutes. Requires: # &<br />` +
|
||||
`<code>/repeatfaq [minutes], [FAQ name/alias]</code>: repeats a given Room FAQ every [minutes] minutes.<br />` +
|
||||
`<code>/removerepeat [id]</code>: removes a repeated phrase.<br />` +
|
||||
`<code>/viewrepeats [optional room]</code>: Displays all repeated phrases in a room.<br />` +
|
||||
`Phrases for <code>/repeat</code> can include normal chat formatting, but not commands. Requires: % @ # &`
|
||||
`<code>/repeatfaq [minutes], [FAQ name/alias]</code>: repeats a given Room FAQ every [minutes] minutes. Requires: % @ # &<br />` +
|
||||
`<code>/removerepeat [id]</code>: removes a repeated phrase. Requires: % @ # &<br />` +
|
||||
`<code>/viewrepeats [optional room]</code>: Displays all repeated phrases in a room. Requires: % @ # &<br />` +
|
||||
`You can append <code>bymessages</code> to a <code>/repeat</code> command to repeat a phrase based on how many messages have been sent in chat. For example, <code>/repeatfaqbymessages ...</code><br />` +
|
||||
`Phrases for <code>/repeat</code> can include normal chat formatting, but not commands.`
|
||||
);
|
||||
},
|
||||
|
||||
repeatfaq(target, room, user) {
|
||||
repeatfaqbymessages: 'repeatfaq',
|
||||
repeatfaq(target, room, user, connection, cmd) {
|
||||
room = this.requireRoom();
|
||||
this.checkCan('mute', null, room);
|
||||
const isByMessages = cmd.includes('bymessages');
|
||||
|
||||
let [intervalString, topic] = target.split(',');
|
||||
const interval = parseInt(intervalString);
|
||||
if (isNaN(interval) || !/[0-9]{1,}/.test(intervalString) || interval < 1 || interval > 24 * 60) {
|
||||
throw new Chat.ErrorMessage(this.tr`You must specify an interval as a number of minutes between 1 and 1440.`);
|
||||
throw new Chat.ErrorMessage(this.tr`You must specify an interval as a number of minutes or chat messages between 1 and 1440.`);
|
||||
}
|
||||
if (!roomFaqs[room.roomid]) {
|
||||
throw new Chat.ErrorMessage(`This room has no FAQs.`);
|
||||
|
|
@ -206,13 +233,16 @@ export const commands: ChatCommands = {
|
|||
Repeats.addRepeat(room, {
|
||||
id: topic as ID,
|
||||
phrase: roomFaqs[room.roomid][topic],
|
||||
interval: interval * 60 * 1000,
|
||||
interval: interval * (isByMessages ? 1 : 60 * 1000),
|
||||
faq: true,
|
||||
isByMessages,
|
||||
});
|
||||
|
||||
this.modlog('REPEATPHRASE', null, `every ${interval} minute${Chat.plural(interval)}: the Room FAQ for "${topic}"`);
|
||||
this.modlog('REPEATPHRASE', null, `every ${interval} ${isByMessages ? 'chat message' : 'minute'}${Chat.plural(interval)}: the Room FAQ for "${topic}"`);
|
||||
this.privateModAction(
|
||||
room.tr`${user.name} set the Room FAQ "${topic}" to be repeated every ${interval} minute(s).`
|
||||
isByMessages ?
|
||||
room.tr`${user.name} set the Room FAQ "${topic}" to be repeated every ${interval} minute(s).` :
|
||||
room.tr`${user.name} set the Room FAQ "${topic}" to be repeated every ${interval} chat message(s).`
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1599,9 +1599,19 @@ export const Chat = new class {
|
|||
*/
|
||||
parse(message: string, room: Room | null | undefined, user: User, connection: Connection) {
|
||||
Chat.loadPlugins();
|
||||
const context = new CommandContext({message, room, user, connection});
|
||||
|
||||
return context.parse();
|
||||
const initialRoomlogLength = room?.log.log.length;
|
||||
const context = new CommandContext({message, room, user, connection});
|
||||
const result = context.parse();
|
||||
|
||||
if (room && room.log.log.length !== initialRoomlogLength) {
|
||||
room.messagesSent++;
|
||||
for (const [handler, numMessages] of room.nthMessageHandlers) {
|
||||
if (room.messagesSent % numMessages === 0) handler(room, message);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
sendPM(message: string, user: User, pmTarget: User, onlyRecipient: User | null = null) {
|
||||
const buf = `|pm|${user.getIdentity()}|${pmTarget.getIdentity()}|${message}`;
|
||||
|
|
|
|||
|
|
@ -128,7 +128,10 @@ export interface RoomSettings {
|
|||
noAutoTruncate?: boolean;
|
||||
isMultichannel?: boolean;
|
||||
}
|
||||
|
||||
export type MessageHandler = (room: BasicRoom, message: string) => void;
|
||||
export type Room = GameRoom | ChatRoom;
|
||||
|
||||
import type {Announcement, AnnouncementData} from './chat-plugins/announcements';
|
||||
import type {Poll, PollData} from './chat-plugins/poll';
|
||||
import type {AutoResponder} from './chat-plugins/responder';
|
||||
|
|
@ -205,6 +208,13 @@ export abstract class BasicRoom {
|
|||
userList: string;
|
||||
pendingApprovals: Map<string, ShowRequest> | null;
|
||||
|
||||
messagesSent: number;
|
||||
/**
|
||||
* These handlers will be invoked every n messages.
|
||||
* handler:number-of-messages map
|
||||
*/
|
||||
nthMessageHandlers: Map<MessageHandler, number>;
|
||||
|
||||
constructor(roomid: RoomID, title?: string, options: Partial<RoomSettings> = {}) {
|
||||
this.users = Object.create(null);
|
||||
this.type = 'chat';
|
||||
|
|
@ -289,6 +299,8 @@ export abstract class BasicRoom {
|
|||
this.userList = this.getUserList();
|
||||
}
|
||||
this.pendingApprovals = null;
|
||||
this.messagesSent = 0;
|
||||
this.nthMessageHandlers = new Map();
|
||||
this.tour = null;
|
||||
this.game = null;
|
||||
this.battle = null;
|
||||
|
|
|
|||
|
|
@ -12,10 +12,12 @@ export const translations: Translations = {
|
|||
"Raw text": "",
|
||||
"Remove": "",
|
||||
"Remove all repeats": "",
|
||||
"You must specify an interval as a number of minutes between 1 and 1440.": "",
|
||||
"You must specify an interval as a number of minutes or chat messages between 1 and 1440.": "",
|
||||
'The phrase labeled with "${id}" is already being repeated in this room.': "",
|
||||
'${user.name} set the phrase labeled with "${id}" to be repeated every ${interval} minute(s).': "",
|
||||
'${user.name} set the phrase labeled with "${id}" to be repeated every ${interval} chat message(s).': "",
|
||||
'${user.name} set the Room FAQ "${topic}" to be repeated every ${interval} minute(s).': "",
|
||||
'${user.name} set the Room FAQ "${topic}" to be repeated every ${interval} chat message(s).': "",
|
||||
'The phrase labeled with "${id}" is not being repeated in this room.': "",
|
||||
'The text for the Room FAQ "${topic}" is already being repeated.': "",
|
||||
'${user.name} removed the repeated phrase labeled with "${id}".': "",
|
||||
|
|
|
|||
|
|
@ -10,12 +10,15 @@ export const translations: Translations = {
|
|||
"Interval": "Zeitspanne",
|
||||
"every ${minutes} minute(s)": "jede ${minutes} Minute(n)",
|
||||
"Raw text": "Rohtext",
|
||||
"every ${messages} chat message(s)": "",
|
||||
"Remove": "Entfernen",
|
||||
"Remove all repeats": "Entferne alle Wiederholungen",
|
||||
"You must specify an interval as a number of minutes between 1 and 1440.": "Du musst eine Zeitspanne als eine Zahl zwischen 1 und 1440 angeben.",
|
||||
"You must specify an interval as a number of minutes or chat messages between 1 and 1440.": "Du musst eine Zeitspanne (oder Chat-Nachrichten) als eine Zahl zwischen 1 und 1440 angeben.",
|
||||
'The phrase labeled with "${id}" is already being repeated in this room.': 'Der Ausdruck "${id}" wird bereits im Raum wiederholt.',
|
||||
'${user.name} set the phrase labeled with "${id}" to be repeated every ${interval} minute(s).': '${user.name} hat eingestellt, dass der Ausdruck "${id}" jede ${interval} Minute(n) wiederholt wird.',
|
||||
'${user.name} set the phrase labeled with "${id}" to be repeated every ${interval} chat message(s).': '${user.name} hat eingestellt, dass der Ausdruck "${id}" jede ${interval} Chat-Nachrichte(n) wiederholt wird.',
|
||||
'${user.name} set the Room FAQ "${topic}" to be repeated every ${interval} minute(s).': '${user.name} hat eingestellt, dass der Raum-FAQ "${topic}" jede ${interval} Minute(n) wiederholt wird.',
|
||||
'${user.name} set the Room FAQ "${topic}" to be repeated every ${interval} chat message(s).': '${user.name} hat eingestellt, dass der Raum-FAQ "${topic}" jede ${interval} Chat-Nachrichte(n) wiederholt wird.',
|
||||
'The phrase labeled with "${id}" is not being repeated in this room.': 'Der Ausdruck "${id}" wird gerade nicht in diesem Raum wiederholt.',
|
||||
'The text for the Room FAQ "${topic}" is already being repeated.': 'Der Text für den Raum-FAQ "${topic}" wird bereits wiederholt.',
|
||||
'${user.name} removed the repeated phrase labeled with "${id}".': '${user.name} hat den sich wiederholenden Ausdruck "${id}" entfernt.',
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user