From 9ff1398c691fa158f2c8b3490e931f5334cec3c4 Mon Sep 17 00:00:00 2001 From: Kris Johnson <11083252+KrisXV@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:15:27 -0600 Subject: [PATCH] Use Chat.ErrorMessage instead of errorReply in more places (#11017) --- server/chat-commands/admin.ts | 125 +++---- server/chat-commands/avatars.tsx | 5 +- server/chat-commands/core.ts | 217 ++++++------ server/chat-commands/info.ts | 121 +++---- server/chat-commands/moderation.ts | 308 +++++++++--------- server/chat-commands/room-settings.ts | 237 +++++++------- server/chat-plugins/abuse-monitor.ts | 165 +++++----- server/chat-plugins/announcements.ts | 12 +- server/chat-plugins/auction.ts | 20 +- server/chat-plugins/battlesearch.ts | 12 +- server/chat-plugins/calculator.ts | 2 +- server/chat-plugins/cg-teams-leveling.ts | 4 +- server/chat-plugins/chat-monitor.ts | 18 +- server/chat-plugins/chatlog.ts | 44 +-- server/chat-plugins/daily-spotlight.ts | 38 +-- server/chat-plugins/datasearch.ts | 2 +- server/chat-plugins/friends.ts | 38 +-- server/chat-plugins/github.ts | 16 +- server/chat-plugins/hangman.ts | 30 +- server/chat-plugins/helptickets-auto.ts | 32 +- server/chat-plugins/helptickets.ts | 68 ++-- server/chat-plugins/hosts.ts | 69 ++-- server/chat-plugins/mafia.ts | 270 +++++++-------- server/chat-plugins/modlog-viewer.ts | 12 +- server/chat-plugins/permalocks.ts | 8 +- server/chat-plugins/poll.ts | 36 +- server/chat-plugins/quotes.ts | 28 +- server/chat-plugins/randombattles/index.ts | 15 +- server/chat-plugins/randombattles/winrates.ts | 14 +- server/chat-plugins/repeats.ts | 12 +- server/chat-plugins/responder.ts | 26 +- server/chat-plugins/room-events.ts | 130 ++++---- server/chat-plugins/room-faqs.ts | 30 +- server/chat-plugins/scavengers.ts | 238 +++++++------- server/chat-plugins/seasons.ts | 2 +- server/chat-plugins/smogtours.ts | 12 +- server/chat-plugins/suspect-tests.ts | 10 +- server/chat-plugins/teams.ts | 18 +- server/chat-plugins/the-studio.ts | 6 +- server/chat-plugins/thing-of-the-day.ts | 100 +++--- server/chat-plugins/trivia/trivia.ts | 65 ++-- server/chat-plugins/usersearch.tsx | 8 +- server/chat-plugins/wifi.tsx | 38 ++- server/chat-plugins/youtube.ts | 42 +-- server/chat.ts | 54 ++- server/tournaments/index.ts | 131 ++++---- 46 files changed, 1443 insertions(+), 1445 deletions(-) diff --git a/server/chat-commands/admin.ts b/server/chat-commands/admin.ts index a903765c5e..4de49f5296 100644 --- a/server/chat-commands/admin.ts +++ b/server/chat-commands/admin.ts @@ -126,11 +126,11 @@ export const commands: Chat.ChatCommands = { this.canUseConsole(); const species = Dex.species.get(target); if (species.id === Config.potd) { - return this.errorReply(`The PotD is already set to ${species.name}`); + throw new Chat.ErrorMessage(`The PotD is already set to ${species.name}`); } - if (!species.exists) return this.errorReply(`Pokemon "${target}" not found.`); + if (!species.exists) throw new Chat.ErrorMessage(`Pokemon "${target}" not found.`); if (!Dex.species.getFullLearnset(species.id).length) { - return this.errorReply(`That Pokemon has no learnset and cannot be used as the PotD.`); + throw new Chat.ErrorMessage(`That Pokemon has no learnset and cannot be used as the PotD.`); } Config.potd = species.id; for (const process of Rooms.PM.processes) { @@ -188,7 +188,7 @@ export const commands: Chat.ChatCommands = { if (!target) return this.parse('/help ' + cmd); this.checkChat(); let [rank, html] = this.splitOne(target); - if (!(rank in Config.groups)) return this.errorReply(`Group '${rank}' does not exist.`); + if (!(rank in Config.groups)) throw new Chat.ErrorMessage(`Group '${rank}' does not exist.`); html = this.checkHTML(html); this.checkCan('addhtml', null, room); html = Chat.collapseLineBreaksHTML(html); @@ -235,7 +235,7 @@ export const commands: Chat.ChatCommands = { this.checkChat(); const [rank, uhtml] = this.splitOne(target); - if (!(rank in Config.groups)) return this.errorReply(`Group '${rank}' does not exist.`); + if (!(rank in Config.groups)) throw new Chat.ErrorMessage(`Group '${rank}' does not exist.`); let [name, html] = this.splitOne(uhtml); name = toID(name); html = this.checkHTML(html); @@ -267,12 +267,12 @@ export const commands: Chat.ChatCommands = { if (cmd.startsWith('d')) { source = ''; } else if (!source || source.length > 18) { - return this.errorReply( + throw new Chat.ErrorMessage( `Specify a source username to take the color from. Name must be <19 characters.` ); } if (!userid || userid.length > 18) { - return this.errorReply(`Specify a valid name to set a new color for. Names must be <19 characters.`); + throw new Chat.ErrorMessage(`Specify a valid name to set a new color for. Names must be <19 characters.`); } const [res, error] = await LoginServer.request('updatenamecolor', { userid, @@ -280,10 +280,10 @@ export const commands: Chat.ChatCommands = { by: user.id, }); if (error) { - return this.errorReply(error.message); + throw new Chat.ErrorMessage(error.message); } if (!res || res.actionerror) { - return this.errorReply(res?.actionerror || "The loginserver is currently disabled."); + throw new Chat.ErrorMessage(res?.actionerror || "The loginserver is currently disabled."); } if (source) { return this.sendReply( @@ -602,7 +602,7 @@ export const commands: Chat.ChatCommands = { this.canUseConsole(); if (Monitor.updateServerLock) { - return this.errorReply("Wait for /updateserver to finish before hotpatching."); + throw new Chat.ErrorMessage("Wait for /updateserver to finish before hotpatching."); } await this.parse(`/rebuild`); @@ -617,10 +617,10 @@ export const commands: Chat.ChatCommands = { Utils.clearRequireCache({ exclude: ['/lib/process-manager'] }); if (target === 'all') { if (lock['all']) { - return this.errorReply(`Hot-patching all has been disabled by ${lock['all'].by} (${lock['all'].reason})`); + throw new Chat.ErrorMessage(`Hot-patching all has been disabled by ${lock['all'].by} (${lock['all'].reason})`); } if (Config.disablehotpatchall) { - return this.errorReply("This server does not allow for the use of /hotpatch all"); + throw new Chat.ErrorMessage("This server does not allow for the use of /hotpatch all"); } for (const hotpatch of hotpatches) { @@ -628,19 +628,21 @@ export const commands: Chat.ChatCommands = { } } else if (target === 'chat' || target === 'commands') { if (lock['tournaments']) { - return this.errorReply(`Hot-patching tournaments has been disabled by ${lock['tournaments'].by} (${lock['tournaments'].reason})`); + throw new Chat.ErrorMessage(`Hot-patching tournaments has been disabled by ${lock['tournaments'].by} (${lock['tournaments'].reason})`); } if (lock['chat']) { - return this.errorReply(`Hot-patching chat has been disabled by ${lock['chat'].by} (${lock['chat'].reason})`); + throw new Chat.ErrorMessage(`Hot-patching chat has been disabled by ${lock['chat'].by} (${lock['chat'].reason})`); } this.sendReply("Hotpatching chat commands..."); const disabledCommands = Chat.allCommands().filter(c => c.disabled).map(c => `/${c.fullCmd}`); if (cmd !== 'forcehotpatch' && disabledCommands.length) { - this.errorReply(`${Chat.count(disabledCommands.length, "commands")} are disabled right now.`); - this.errorReply(`Hotpatching will enable them. Use /forcehotpatch chat if you're sure.`); - return this.errorReply(`Currently disabled: ${disabledCommands.join(', ')}`); + throw new Chat.ErrorMessage([ + `${Chat.count(disabledCommands.length, "commands")} are disabled right now.`, + `Hotpatching will enable them. Use /forcehotpatch chat if you're sure.`, + `Currently disabled: ${disabledCommands.join(', ')}`, + ]); } const oldPlugins = Chat.plugins; @@ -662,7 +664,7 @@ export const commands: Chat.ChatCommands = { this.sendReply("DONE"); } else if (target === 'processmanager') { if (lock['processmanager']) { - return this.errorReply( + throw new Chat.ErrorMessage( `Hot-patching formats has been disabled by ${lock['processmanager'].by} ` + `(${lock['processmanager'].reason})` ); @@ -700,7 +702,7 @@ export const commands: Chat.ChatCommands = { this.sendReply('DONE'); } else if (target === 'usersp' || target === 'roomsp') { if (lock[target]) { - return this.errorReply(`Hot-patching ${target} has been disabled by ${lock[target].by} (${lock[target].reason})`); + throw new Chat.ErrorMessage(`Hot-patching ${target} has been disabled by ${lock[target].by} (${lock[target].reason})`); } let newProto: any, oldProto: any, message: string; switch (target) { @@ -752,7 +754,7 @@ export const commands: Chat.ChatCommands = { ); } else if (target === 'tournaments') { if (lock['tournaments']) { - return this.errorReply(`Hot-patching tournaments has been disabled by ${lock['tournaments'].by} (${lock['tournaments'].reason})`); + throw new Chat.ErrorMessage(`Hot-patching tournaments has been disabled by ${lock['tournaments'].by} (${lock['tournaments'].reason})`); } this.sendReply("Hotpatching tournaments..."); @@ -761,13 +763,13 @@ export const commands: Chat.ChatCommands = { this.sendReply("DONE"); } else if (target === 'formats' || target === 'battles') { if (lock['formats']) { - return this.errorReply(`Hot-patching formats has been disabled by ${lock['formats'].by} (${lock['formats'].reason})`); + throw new Chat.ErrorMessage(`Hot-patching formats has been disabled by ${lock['formats'].by} (${lock['formats'].reason})`); } if (lock['battles']) { - return this.errorReply(`Hot-patching battles has been disabled by ${lock['battles'].by} (${lock['battles'].reason})`); + throw new Chat.ErrorMessage(`Hot-patching battles has been disabled by ${lock['battles'].by} (${lock['battles'].reason})`); } if (lock['validator']) { - return this.errorReply(`Hot-patching the validator has been disabled by ${lock['validator'].by} (${lock['validator'].reason})`); + throw new Chat.ErrorMessage(`Hot-patching the validator has been disabled by ${lock['validator'].by} (${lock['validator'].reason})`); } this.sendReply("Hotpatching formats..."); @@ -793,10 +795,10 @@ export const commands: Chat.ChatCommands = { this.sendReply("DONE. New login server requests will use the new code."); } else if (target === 'learnsets' || target === 'validator') { if (lock['validator']) { - return this.errorReply(`Hot-patching the validator has been disabled by ${lock['validator'].by} (${lock['validator'].reason})`); + throw new Chat.ErrorMessage(`Hot-patching the validator has been disabled by ${lock['validator'].by} (${lock['validator'].reason})`); } if (lock['formats']) { - return this.errorReply(`Hot-patching formats has been disabled by ${lock['formats'].by} (${lock['formats'].reason})`); + throw new Chat.ErrorMessage(`Hot-patching formats has been disabled by ${lock['formats'].by} (${lock['formats'].reason})`); } this.sendReply("Hotpatching validator..."); @@ -806,7 +808,7 @@ export const commands: Chat.ChatCommands = { this.sendReply("DONE. Any battles started after now will have teams be validated according to the new code."); } else if (target === 'punishments') { if (lock['punishments']) { - return this.errorReply(`Hot-patching punishments has been disabled by ${lock['punishments'].by} (${lock['punishments'].reason})`); + throw new Chat.ErrorMessage(`Hot-patching punishments has been disabled by ${lock['punishments'].by} (${lock['punishments'].reason})`); } this.sendReply("Hotpatching punishments..."); @@ -820,7 +822,7 @@ export const commands: Chat.ChatCommands = { this.sendReply("DONE"); } else if (target === 'modlog') { if (lock['modlog']) { - return this.errorReply(`Hot-patching modlogs has been disabled by ${lock['modlog'].by} (${lock['modlog'].reason})`); + throw new Chat.ErrorMessage(`Hot-patching modlogs has been disabled by ${lock['modlog'].by} (${lock['modlog'].reason})`); } this.sendReply("Hotpatching modlog..."); @@ -840,14 +842,14 @@ export const commands: Chat.ChatCommands = { this.sendReply("Disabling hot-patch has been moved to its own command:"); return this.parse('/help nohotpatch'); } else { - return this.errorReply("Your hot-patch command was unrecognized."); + throw new Chat.ErrorMessage("Your hot-patch command was unrecognized."); } } catch (e: any) { Rooms.global.notifyRooms( ['development', 'staff'] as RoomID[], `|c|${user.getIdentity()}|/log ${user.name} used /hotpatch ${target} - but something failed while trying to hot-patch.` ); - return this.errorReply(`Something failed while trying to hot-patch ${target}: \n${e.stack}`); + throw new Chat.ErrorMessage([`Something failed while trying to hot-patch ${target}:`, e.stack]); } Rooms.global.notifyRooms( ['development', 'staff'] as RoomID[], @@ -890,18 +892,18 @@ export const commands: Chat.ChatCommands = { ]; if (!validDisable.includes(hotpatch)) { - return this.errorReply(`Disabling hotpatching "${hotpatch}" is not supported.`); + throw new Chat.ErrorMessage(`Disabling hotpatching "${hotpatch}" is not supported.`); } const enable = ['allowhotpatch', 'yeshotpatch'].includes(cmd); if (enable) { - if (!lock[hotpatch]) return this.errorReply(`Hot-patching ${hotpatch} is not disabled.`); + if (!lock[hotpatch]) throw new Chat.ErrorMessage(`Hot-patching ${hotpatch} is not disabled.`); delete lock[hotpatch]; this.sendReply(`You have enabled hot-patching ${hotpatch}.`); } else { if (lock[hotpatch]) { - return this.errorReply(`Hot-patching ${hotpatch} has already been disabled by ${lock[hotpatch].by} (${lock[hotpatch].reason})`); + throw new Chat.ErrorMessage(`Hot-patching ${hotpatch} has already been disabled by ${lock[hotpatch].by} (${lock[hotpatch].reason})`); } lock[hotpatch] = { by: user.name, @@ -1051,14 +1053,14 @@ export const commands: Chat.ChatCommands = { if (['!', '/'].some(c => target.startsWith(c))) target = target.slice(1); const parsed = Chat.parseCommand(`/${target}`); if (!parsed) { - return this.errorReply(`Command "/${target}" is in an invalid format.`); + throw new Chat.ErrorMessage(`Command "/${target}" is in an invalid format.`); } const { handler, fullCmd } = parsed; if (!handler) { - return this.errorReply(`Command "/${target}" not found.`); + throw new Chat.ErrorMessage(`Command "/${target}" not found.`); } if (handler.disabled) { - return this.errorReply(`Command "/${target}" is already disabled`); + throw new Chat.ErrorMessage(`Command "/${target}" is already disabled`); } handler.disabled = true; this.addGlobalModAction(`${user.name} disabled the command /${fullCmd}.`); @@ -1075,7 +1077,7 @@ export const commands: Chat.ChatCommands = { disableladder(target, room, user) { this.checkCan('disableladder'); if (Ladders.disabled) { - return this.errorReply(`/disableladder - Ladder is already disabled.`); + throw new Chat.ErrorMessage(`/disableladder - Ladder is already disabled.`); } Ladders.disabled = true; @@ -1101,7 +1103,7 @@ export const commands: Chat.ChatCommands = { enableladder(target, room, user) { this.checkCan('disableladder'); if (!Ladders.disabled) { - return this.errorReply(`/enable - Ladder is already enabled.`); + throw new Chat.ErrorMessage(`/enable - Ladder is already enabled.`); } Ladders.disabled = false; @@ -1145,13 +1147,13 @@ export const commands: Chat.ChatCommands = { if (Config.autolockdown === undefined) Config.autolockdown = true; if (this.meansYes(target)) { if (Config.autolockdown) { - return this.errorReply("The server is already set to automatically kill itself upon the final battle finishing."); + throw new Chat.ErrorMessage("The server is already set to automatically kill itself upon the final battle finishing."); } Config.autolockdown = true; this.privateGlobalModAction(`${user.name} used /autolockdownkill on (autokill on final battle finishing)`); } else if (this.meansNo(target)) { if (!Config.autolockdown) { - return this.errorReply("The server is already set to not automatically kill itself upon the final battle finishing."); + throw new Chat.ErrorMessage("The server is already set to not automatically kill itself upon the final battle finishing."); } Config.autolockdown = false; this.privateGlobalModAction(`${user.name} used /autolockdownkill off (no autokill on final battle finishing)`); @@ -1189,10 +1191,10 @@ export const commands: Chat.ChatCommands = { this.checkCan('lockdown'); if (!Rooms.global.lockdown) { - return this.errorReply("We're not under lockdown right now."); + throw new Chat.ErrorMessage("We're not under lockdown right now."); } if (Rooms.global.lockdown !== true && cmd === 'crashfixed') { - return this.errorReply('/crashfixed - There is no active crash.'); + throw new Chat.ErrorMessage('/crashfixed - There is no active crash.'); } const message = cmd === 'crashfixed' ? @@ -1221,7 +1223,7 @@ export const commands: Chat.ChatCommands = { this.checkCan('lockdown'); if (Config.emergency) { - return this.errorReply("We're already in emergency mode."); + throw new Chat.ErrorMessage("We're already in emergency mode."); } Config.emergency = true; for (const curRoom of Rooms.rooms.values()) { @@ -1238,7 +1240,7 @@ export const commands: Chat.ChatCommands = { this.checkCan('lockdown'); if (!Config.emergency) { - return this.errorReply("We're not in emergency mode."); + throw new Chat.ErrorMessage("We're not in emergency mode."); } Config.emergency = false; for (const curRoom of Rooms.rooms.values()) { @@ -1255,7 +1257,7 @@ export const commands: Chat.ChatCommands = { this.checkCan('lockdown'); if (!Rooms.global.lockdown) { - return this.errorReply("The server is not under lockdown right now."); + throw new Chat.ErrorMessage("The server is not under lockdown right now."); } const battleRooms = [...Rooms.rooms.values()].filter(x => x.battle?.rated && !x.battle?.ended); @@ -1288,11 +1290,11 @@ export const commands: Chat.ChatCommands = { if (!Config.usepostgres) noSave = true; if (Rooms.global.lockdown !== true && noSave) { - return this.errorReply("For safety reasons, using /kill without saving battles can only be done during lockdown."); + throw new Chat.ErrorMessage("For safety reasons, using /kill without saving battles can only be done during lockdown."); } if (Monitor.updateServerLock) { - return this.errorReply("Wait for /updateserver to finish before using /kill."); + throw new Chat.ErrorMessage("Wait for /updateserver to finish before using /kill."); } if (!noSave) { @@ -1347,8 +1349,8 @@ export const commands: Chat.ChatCommands = { this.checkCan('lockdown'); if (user.lastCommand !== 'refreshpage') { user.lastCommand = 'refreshpage'; - this.errorReply(`Are you sure you wish to refresh the page for every user online?`); - return this.errorReply(`If you are sure, please type /refreshpage again to confirm.`); + throw new Chat.ErrorMessage([`Are you sure you wish to refresh the page for every user online?`, + `If you are sure, please type /refreshpage again to confirm.`]); } Rooms.global.sendAll('|refresh|'); this.stafflog(`${user.name} used /refreshpage`); @@ -1360,7 +1362,7 @@ export const commands: Chat.ChatCommands = { async updateserver(target, room, user, connection) { this.canUseConsole(); if (Monitor.updateServerLock) { - return this.errorReply(`/updateserver - Another update is already in progress (or a previous update crashed).`); + throw new Chat.ErrorMessage(`/updateserver - Another update is already in progress (or a previous update crashed).`); } const validPrivateCodePath = Config.privatecodepath && path.isAbsolute(Config.privatecodepath); @@ -1401,12 +1403,12 @@ export const commands: Chat.ChatCommands = { ['staff', 'development'], `|c|${user.getIdentity()}|/log ${user.name} used /updateloginserver - but something failed while updating.` ); - return this.errorReply(`${err.message}\n${err.stack}`); + throw new Chat.ErrorMessage([err.message, err.stack || '']); } - if (!result) return this.errorReply('No result received.'); + if (!result) throw new Chat.ErrorMessage('No result received.'); this.stafflog(`[o] ${result.success || ""} [e] ${result.actionerror || ""}`); if (result.actionerror) { - return this.errorReply(result.actionerror); + throw new Chat.ErrorMessage(result.actionerror); } let message = `${user.name} used /updateloginserver`; if (result.updated) { @@ -1434,12 +1436,12 @@ export const commands: Chat.ChatCommands = { ['staff', 'development'], `|c|${user.getIdentity()}|/log ${user.name} used /updateclient - but something failed while updating.` ); - return this.errorReply(`${err.message}\n${err.stack}`); + throw new Chat.ErrorMessage([err.message, err.stack || '']); } - if (!result) return this.errorReply('No result received.'); + if (!result) throw new Chat.ErrorMessage('No result received.'); this.stafflog(`[o] ${result.success || ""} [e] ${result.actionerror || ""}`); if (result.actionerror) { - return this.errorReply(result.actionerror); + throw new Chat.ErrorMessage(result.actionerror); } let message = `${user.name} used /updateclient`; if (result.updated) { @@ -1529,12 +1531,12 @@ export const commands: Chat.ChatCommands = { async evalsql(target, room) { this.canUseConsole(); this.runBroadcast(true); - if (!Config.usesqlite) return this.errorReply(`SQLite is disabled.`); + if (!Config.usesqlite) throw new Chat.ErrorMessage(`SQLite is disabled.`); const logRoom = Rooms.get('upperstaff') || Rooms.get('staff'); - if (!target) return this.errorReply(`Specify a database to access and a query.`); + if (!target) throw new Chat.ErrorMessage(`Specify a database to access and a query.`); const [db, query] = Utils.splitFirst(target, ',').map(item => item.trim()); if (!FS('./databases').readdirSync().includes(`${db}.db`)) { - return this.errorReply(`The database file ${db}.db was not found.`); + throw new Chat.ErrorMessage(`The database file ${db}.db was not found.`); } if (room && this.message.startsWith('>>sql')) { this.broadcasting = true; @@ -1613,7 +1615,7 @@ export const commands: Chat.ChatCommands = { this.canUseConsole(); if (!this.runBroadcast(true)) return; if (!room.battle) { - return this.errorReply("/evalbattle - This isn't a battle room."); + throw new Chat.ErrorMessage("/evalbattle - This isn't a battle room."); } void room.battle.stream.write(`>eval ${target.replace(/\n/g, '\f')}`); @@ -1628,8 +1630,7 @@ export const commands: Chat.ChatCommands = { this.checkCan('forcewin'); if (!target) return this.parse('/help editbattle'); if (!room.battle) { - this.errorReply("/editbattle - This is not a battle room."); - return false; + throw new Chat.ErrorMessage("/editbattle - This is not a battle room."); } const battle = room.battle; let cmd; @@ -1774,7 +1775,7 @@ export const pages: Chat.PageTable = { const [botid, ...pageArgs] = args; const pageid = pageArgs.join('-'); if (pageid.length > 300) { - return this.errorReply(`The page ID specified is too long.`); + throw new Chat.ErrorMessage(`The page ID specified is too long.`); } const bot = Users.get(botid); if (!bot) { diff --git a/server/chat-commands/avatars.tsx b/server/chat-commands/avatars.tsx index ca236a481e..c793c88131 100644 --- a/server/chat-commands/avatars.tsx +++ b/server/chat-commands/avatars.tsx @@ -682,8 +682,7 @@ export const commands: Chat.ChatCommands = { if (!avatar) { if (silent) return false; - this.errorReply("Unrecognized avatar - make sure you're on the right account?"); - return false; + throw new Chat.ErrorMessage("Unrecognized avatar - make sure you're on the right account?"); } this.runBroadcast(); @@ -929,7 +928,7 @@ export const commands: Chat.ChatCommands = { return this.parse(`/help moveavatars`); } if (!customAvatars[from]?.allowed.length) { - return this.errorReply(`That user has no avatars.`); + throw new Chat.ErrorMessage(`That user has no avatars.`); } const existing = customAvatars[to]?.allowed.filter(Boolean); customAvatars[to] = { ...customAvatars[from] }; diff --git a/server/chat-commands/core.ts b/server/chat-commands/core.ts index 1849433cc7..9a9a530a5e 100644 --- a/server/chat-commands/core.ts +++ b/server/chat-commands/core.ts @@ -207,7 +207,7 @@ export const commands: Chat.ChatCommands = { mee: 'me', me(target, room, user) { if (this.cmd === 'mee' && /[A-Z-a-z0-9/]/.test(target.charAt(0))) { - return this.errorReply(this.tr`/mee - must not start with a letter or number`); + throw new Chat.ErrorMessage(this.tr`/mee - must not start with a letter or number`); } target = this.checkChat(`/${this.cmd} ${target || ''}`); @@ -303,9 +303,9 @@ export const commands: Chat.ChatCommands = { this.checkRecursion(); const targetRoom = Rooms.search(targetId.trim()); - if (!targetRoom) return this.errorReply(`Room not found.`); + if (!targetRoom) throw new Chat.ErrorMessage(`Room not found.`); if (message.trim().startsWith('/msgroom ')) { - return this.errorReply(`Please do not nest /msgroom inside itself.`); + throw new Chat.ErrorMessage(`Please do not nest /msgroom inside itself.`); } const subcontext = new Chat.CommandContext({ room: targetRoom, message, user, connection }); await subcontext.parse(); @@ -316,7 +316,7 @@ export const commands: Chat.ChatCommands = { reply(target, room, user) { if (!target) return this.parse('/help reply'); if (!user.lastPM) { - return this.errorReply(this.tr`No one has PMed you yet.`); + throw new Chat.ErrorMessage(this.tr`No one has PMed you yet.`); } return this.parse(`/msg ${user.lastPM || ''}, ${target}`); }, @@ -344,7 +344,7 @@ export const commands: Chat.ChatCommands = { return this.parse(`/offlinemsg ${targetUsername},${message}`); } user.lastCommand = 'pm'; - return this.errorReply( + throw new Chat.ErrorMessage( this.tr`User ${targetUsername} is offline. Send the message again to confirm. If you are using /msg, use /offlinemsg instead.` ); } @@ -364,11 +364,11 @@ export const commands: Chat.ChatCommands = { return this.parse(`/offlinemsg ${targetUser.getLastId()},${message}`); } user.lastCommand = 'pm'; - return this.errorReply( + throw new Chat.ErrorMessage( this.tr`User ${targetUsername} is offline. Send the message again to confirm. If you are using /msg, use /offlinemsg instead.` ); } - return this.errorReply(`${targetUsername} is offline.`); + throw new Chat.ErrorMessage(`${targetUsername} is offline.`); } return this.parse(message); @@ -383,7 +383,7 @@ export const commands: Chat.ChatCommands = { target = target.trim(); if (!target) return this.parse('/help offlinemsg'); if (!Chat.PrivateMessages.offlineIsEnabled) { - return this.errorReply(`Offline private messages have been disabled.`); + throw new Chat.ErrorMessage(`Offline private messages have been disabled.`); } let [username, message] = Utils.splitFirst(target, ',').map(i => i.trim()); const userid = toID(username); @@ -392,12 +392,12 @@ export const commands: Chat.ChatCommands = { return this.parse('/help offlinemsg'); } if (Chat.parseCommand(message)) { - return this.errorReply(`You cannot send commands in offline PMs.`); + throw new Chat.ErrorMessage(`You cannot send commands in offline PMs.`); } if (userid === user.id) { - return this.errorReply(`You cannot send offline PMs to yourself.`); + throw new Chat.ErrorMessage(`You cannot send offline PMs to yourself.`); } else if (userid.startsWith('guest')) { - return this.errorReply('You cannot send offline PMs to guests.'); + throw new Chat.ErrorMessage('You cannot send offline PMs to guests.'); } if (Users.get(userid)?.connected) { this.sendReply(`That user is online, so a normal PM is being sent.`); @@ -436,10 +436,10 @@ export const commands: Chat.ChatCommands = { targetRoom = room; } if (users.length > 1 && !user.trusted) { - return this.errorReply("You do not have permission to mass-invite users."); + throw new Chat.ErrorMessage("You do not have permission to mass-invite users."); } if (users.length > 10) { - return this.errorReply("You cannot invite more than 10 users at once."); + throw new Chat.ErrorMessage("You cannot invite more than 10 users at once."); } for (const toInvite of users) { this.parse(`/pm ${toInvite}, /invite ${targetRoom}`); @@ -448,24 +448,24 @@ export const commands: Chat.ChatCommands = { } const targetRoom = Rooms.search(target); - if (!targetRoom) return this.errorReply(this.tr`The room "${target}" was not found.`); + if (!targetRoom) throw new Chat.ErrorMessage(this.tr`The room "${target}" was not found.`); const invitesBlocked = pmTarget.settings.blockInvites; if (invitesBlocked) { if (invitesBlocked === true ? !user.can('lock') : !Users.globalAuth.atLeast(user, invitesBlocked as GroupSymbol)) { Chat.maybeNotifyBlocked('invite', pmTarget, user); - return this.errorReply(`This user is currently blocking room invites.`); + throw new Chat.ErrorMessage(`This user is currently blocking room invites.`); } } if (!targetRoom.checkModjoin(pmTarget)) { this.room = targetRoom; this.parse(`/roomvoice ${pmTarget.name}`); if (!targetRoom.checkModjoin(pmTarget)) { - return this.errorReply(this.tr`You do not have permission to invite people into this room.`); + throw new Chat.ErrorMessage(this.tr`You do not have permission to invite people into this room.`); } } if (pmTarget.id in targetRoom.users) { - return this.errorReply(this.tr`This user is already in "${targetRoom.title}".`); + throw new Chat.ErrorMessage(this.tr`This user is already in "${targetRoom.title}".`); } return this.checkChat(`/invite ${targetRoom.roomid}`); }, @@ -487,7 +487,7 @@ export const commands: Chat.ChatCommands = { const isOffline = cmd.includes('offline'); const msg = isOffline ? `offline ` : ``; if (!isOffline && user.settings.blockPMs === (target || true)) { - return this.errorReply(this.tr`You are already blocking ${msg}private messages! To unblock, use /unblockpms`); + throw new Chat.ErrorMessage(this.tr`You are already blocking ${msg}private messages! To unblock, use /unblockpms`); } if (Users.Auth.isAuthLevel(target)) { if (!isOffline) user.settings.blockPMs = target; @@ -528,7 +528,7 @@ export const commands: Chat.ChatCommands = { const isOffline = cmd.includes('offline'); const msg = isOffline ? 'offline ' : ''; if (isOffline ? !(await Chat.PrivateMessages.getSettings(user.id)) : !user.settings.blockPMs) { - return this.errorReply(this.tr`You are not blocking ${msg}private messages! To block, use /blockpms`); + throw new Chat.ErrorMessage(this.tr`You are not blocking ${msg}private messages! To block, use /blockpms`); } if (isOffline) { await Chat.PrivateMessages.deleteSettings(user.id); @@ -549,14 +549,14 @@ export const commands: Chat.ChatCommands = { const unblock = cmd.includes('unblock'); if (unblock) { if (!user.settings.blockInvites) { - return this.errorReply(`You are not blocking room invites! To block, use /blockinvites.`); + throw new Chat.ErrorMessage(`You are not blocking room invites! To block, use /blockinvites.`); } user.settings.blockInvites = false; this.sendReply(`You are no longer blocking room invites.`); } else { if (toID(target) === 'ac') target = 'autoconfirmed'; if (user.settings.blockInvites === (target || true)) { - return this.errorReply("You are already blocking room invites - to unblock, use /unblockinvites"); + throw new Chat.ErrorMessage("You are already blocking room invites - to unblock, use /unblockinvites"); } if (target in Config.groups) { user.settings.blockInvites = target as GroupSymbol; @@ -579,16 +579,16 @@ export const commands: Chat.ChatCommands = { status(target, room, user, connection, cmd) { if (user.locked || user.semilocked) { - return this.errorReply(this.tr`Your status cannot be updated while you are locked or semilocked.`); + throw new Chat.ErrorMessage(this.tr`Your status cannot be updated while you are locked or semilocked.`); } if (!target) return this.parse('/help status'); const maxLength = 70; if (target.length > maxLength) { - return this.errorReply(this.tr`Your status is too long; it must be under ${maxLength} characters.`); + throw new Chat.ErrorMessage(this.tr`Your status is too long; it must be under ${maxLength} characters.`); } target = this.statusfilter(target); - if (!target) return this.errorReply(this.tr`Your status contains a banned word.`); + if (!target) throw new Chat.ErrorMessage(this.tr`Your status contains a banned word.`); user.setUserMessage(target); this.sendReply(this.tr`Your status has been set to: ${target}.`); @@ -648,7 +648,7 @@ export const commands: Chat.ChatCommands = { unaway: 'back', unafk: 'back', back(target, room, user) { - if (user.statusType === 'online') return this.errorReply(this.tr`You are already marked as back.`); + if (user.statusType === 'online') throw new Chat.ErrorMessage(this.tr`You are already marked as back.`); const statusType = user.statusType; user.setStatusType('online'); @@ -693,19 +693,19 @@ export const commands: Chat.ChatCommands = { hiderank(target, room, user, connection, cmd) { const userGroup = Users.Auth.getGroup(Users.globalAuth.get(user.id)); if (!userGroup['hiderank'] || !user.registered) { - return this.errorReply(`/hiderank - Access denied.`); + throw new Chat.ErrorMessage(`/hiderank - Access denied.`); } const isShow = cmd === 'showrank'; const group = (isShow ? Users.globalAuth.get(user.id) : (target.trim() || Users.Auth.defaultSymbol()) as GroupSymbol); if (user.tempGroup === group) { - return this.errorReply(this.tr`You already have the temporary symbol '${group}'.`); + throw new Chat.ErrorMessage(this.tr`You already have the temporary symbol '${group}'.`); } if (!Users.Auth.isValidSymbol(group) || !(group in Config.groups)) { - return this.errorReply(this.tr`You must specify a valid group symbol.`); + throw new Chat.ErrorMessage(this.tr`You must specify a valid group symbol.`); } if (!isShow && Config.groups[group].rank > Config.groups[user.tempGroup].rank) { - return this.errorReply(this.tr`You may only set a temporary symbol below your current rank.`); + throw new Chat.ErrorMessage(this.tr`You may only set a temporary symbol below your current rank.`); } user.tempGroup = group; user.updateIdentity(); @@ -725,7 +725,7 @@ export const commands: Chat.ChatCommands = { const languageID = toID(target); if (!Chat.languages.has(languageID)) { const languages = [...Chat.languages.values()].join(', '); - return this.errorReply(this.tr`Valid languages are: ${languages}`); + throw new Chat.ErrorMessage(this.tr`Valid languages are: ${languages}`); } user.language = languageID; user.update(); @@ -781,24 +781,24 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); const battle = room.battle; if (!battle) { - return this.errorReply(this.tr`Must be in a battle.`); + throw new Chat.ErrorMessage(this.tr`Must be in a battle.`); } const targetUser = Users.getExact(target); if (!targetUser) { - return this.errorReply(this.tr`User ${target} not found.`); + throw new Chat.ErrorMessage(this.tr`User ${target} not found.`); } if (!battle.playerTable[user.id]) { - return this.errorReply(this.tr`Must be a player in this battle.`); + throw new Chat.ErrorMessage(this.tr`Must be a player in this battle.`); } if (!battle.allowExtraction[targetUser.id]) { - return this.errorReply(this.tr`${targetUser.name} has not requested extraction.`); + throw new Chat.ErrorMessage(this.tr`${targetUser.name} has not requested extraction.`); } if (battle.allowExtraction[targetUser.id].has(user.id)) { - return this.errorReply(this.tr`You have already consented to extraction with ${targetUser.name}.`); + throw new Chat.ErrorMessage(this.tr`You have already consented to extraction with ${targetUser.name}.`); } battle.allowExtraction[targetUser.id].add(user.id); this.addModAction(room.tr`${user.name} consents to sharing battle team and choices with ${targetUser.name}.`); - if (!battle.inputLog) return this.errorReply(this.tr`No input log found.`); + if (!battle.inputLog) throw new Chat.ErrorMessage(this.tr`No input log found.`); if (Object.keys(battle.playerTable).length === battle.allowExtraction[targetUser.id].size) { this.addModAction(room.tr`${targetUser.name} has extracted the battle input log.`); const inputLog = battle.inputLog.map(Utils.escapeHTML).join(`
`); @@ -817,7 +817,7 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); const battle = room.battle; if (!battle) { - return this.errorReply(this.tr`This command only works in battle rooms.`); + throw new Chat.ErrorMessage(this.tr`This command only works in battle rooms.`); } if (!battle.inputLog) { this.errorReply(this.tr`This command only works when the battle has ended - if the battle has stalled, use /offertie.`); @@ -826,7 +826,7 @@ export const commands: Chat.ChatCommands = { } this.checkCan('exportinputlog', null, room); if (user.can('forcewin') || Dex.formats.get(battle.format).team) { - if (!battle.inputLog) return this.errorReply(this.tr`No input log found.`); + if (!battle.inputLog) throw new Chat.ErrorMessage(this.tr`No input log found.`); this.addModAction(room.tr`${user.name} has extracted the battle input log.`); const inputLog = battle.inputLog.map(Utils.escapeHTML).join(`
`); user.sendTo( @@ -860,7 +860,7 @@ export const commands: Chat.ChatCommands = { Utils.html`|html|${user.name} wants to extract the battle input log. ` ); } - if (logExported) return this.errorReply(this.tr`You already extracted the battle input log.`); + if (logExported) throw new Chat.ErrorMessage(this.tr`You already extracted the battle input log.`); this.sendReply(this.tr`Battle input log re-requested.`); } }, @@ -870,10 +870,10 @@ export const commands: Chat.ChatCommands = { this.checkCan('importinputlog'); const formatIndex = target.indexOf(`"formatid":"`); const nextQuoteIndex = target.indexOf(`"`, formatIndex + 12); - if (formatIndex < 0 || nextQuoteIndex < 0) return this.errorReply(this.tr`Invalid input log.`); + if (formatIndex < 0 || nextQuoteIndex < 0) throw new Chat.ErrorMessage(this.tr`Invalid input log.`); target = target.replace(/\r/g, ''); if ((`\n` + target).includes(`\n>eval `) && !user.hasConsoleAccess(connection)) { - return this.errorReply(this.tr`Your input log contains untrusted code - you must have console access to use it.`); + throw new Chat.ErrorMessage(this.tr`Your input log contains untrusted code - you must have console access to use it.`); } const formatid = target.slice(formatIndex + 12, nextQuoteIndex); @@ -899,22 +899,22 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); const battle = room.battle; if (!showAll && !target) return this.parse(`/help showset`); - if (!battle) return this.errorReply(this.tr`This command can only be used in a battle.`); + if (!battle) throw new Chat.ErrorMessage(this.tr`This command can only be used in a battle.`); let team = await battle.getTeam(user); - if (!team) return this.errorReply(this.tr`You are not a player and don't have a team.`); + if (!team) throw new Chat.ErrorMessage(this.tr`You are not a player and don't have a team.`); if (!showAll) { const parsed = parseInt(target); if (isNaN(parsed)) { const id = toID(target); const matchedSet = team.find(set => toID(set.name) === id || toID(set.species) === id); - if (!matchedSet) return this.errorReply(this.tr`You don't have a Pokémon matching "${target}" in your team.`); + if (!matchedSet) throw new Chat.ErrorMessage(this.tr`You don't have a Pokémon matching "${target}" in your team.`); team = [matchedSet]; } else { const setIndex = parsed - 1; const indexedSet = team[setIndex]; if (!indexedSet) { - return this.errorReply(this.tr`You don't have a Pokémon #${parsed} on your team - your team only has ${team.length} Pokémon.`); + throw new Chat.ErrorMessage(this.tr`You don't have a Pokémon #${parsed} on your team - your team only has ${team.length} Pokémon.`); } team = [indexedSet]; } @@ -941,23 +941,23 @@ export const commands: Chat.ChatCommands = { acceptopenteamsheets(target, room, user, connection, cmd) { room = this.requireRoom(); const battle = room.battle; - if (!battle) return this.errorReply(this.tr`Must be in a battle room.`); + if (!battle) throw new Chat.ErrorMessage(this.tr`Must be in a battle room.`); const player = battle.playerTable[user.id]; if (!player) { - return this.errorReply(this.tr`Must be a player to agree to open team sheets.`); + throw new Chat.ErrorMessage(this.tr`Must be a player to agree to open team sheets.`); } const format = Dex.formats.get(battle.options.format); if (!Dex.formats.getRuleTable(format).has('openteamsheets')) { - return this.errorReply(this.tr`This format does not allow requesting open team sheets. You can both manually agree to it by using !showteam hidestats.`); + throw new Chat.ErrorMessage(this.tr`This format does not allow requesting open team sheets. You can both manually agree to it by using !showteam hidestats.`); } if (battle.turn > 0) { - return this.errorReply(this.tr`You cannot agree to open team sheets after Team Preview. Each player can still show their own sheet by using this command: !showteam hidestats`); + throw new Chat.ErrorMessage(this.tr`You cannot agree to open team sheets after Team Preview. Each player can still show their own sheet by using this command: !showteam hidestats`); } if (battle.players.some(curPlayer => curPlayer.wantsOpenTeamSheets === false)) { - return this.errorReply(this.tr`An opponent has already rejected open team sheets.`); + throw new Chat.ErrorMessage(this.tr`An opponent has already rejected open team sheets.`); } if (player.wantsOpenTeamSheets !== null) { - return this.errorReply(this.tr`You have already made your decision about agreeing to open team sheets.`); + throw new Chat.ErrorMessage(this.tr`You have already made your decision about agreeing to open team sheets.`); } player.wantsOpenTeamSheets = true; player.sendRoom(Utils.html`|uhtmlchange|otsrequest|`); @@ -972,20 +972,20 @@ export const commands: Chat.ChatCommands = { rejectopenteamsheets(target, room, user) { room = this.requireRoom(); const battle = room.battle; - if (!battle) return this.errorReply(this.tr`Must be in a battle room.`); + if (!battle) throw new Chat.ErrorMessage(this.tr`Must be in a battle room.`); const player = battle.playerTable[user.id]; if (!player) { - return this.errorReply(this.tr`Must be a player to reject open team sheets.`); + throw new Chat.ErrorMessage(this.tr`Must be a player to reject open team sheets.`); } const format = Dex.formats.get(battle.options.format); if (!Dex.formats.getRuleTable(format).has('openteamsheets')) { - return this.errorReply(this.tr`This format does not allow requesting open team sheets.`); + throw new Chat.ErrorMessage(this.tr`This format does not allow requesting open team sheets.`); } if (battle.turn > 0) { - return this.errorReply(this.tr`You cannot reject open team sheets after Team Preview.`); + throw new Chat.ErrorMessage(this.tr`You cannot reject open team sheets after Team Preview.`); } if (player.wantsOpenTeamSheets !== null) { - return this.errorReply(this.tr`You have already made your decision about agreeing to open team sheets.`); + throw new Chat.ErrorMessage(this.tr`You have already made your decision about agreeing to open team sheets.`); } player.wantsOpenTeamSheets = false; for (const otherPlayer of battle.players) { @@ -1002,19 +1002,19 @@ export const commands: Chat.ChatCommands = { offertie(target, room, user, connection, cmd) { room = this.requireRoom(); const battle = room.battle; - if (!battle) return this.errorReply(this.tr`Must be in a battle room.`); + if (!battle) throw new Chat.ErrorMessage(this.tr`Must be in a battle room.`); if (!Config.allowrequestingties) { - return this.errorReply(this.tr`This server does not allow offering ties.`); + throw new Chat.ErrorMessage(this.tr`This server does not allow offering ties.`); } if (room.tour) { - return this.errorReply(this.tr`You can't offer ties in tournaments.`); + throw new Chat.ErrorMessage(this.tr`You can't offer ties in tournaments.`); } if (battle.turn < 100) { - return this.errorReply(this.tr`It's too early to tie, please play until turn 100.`); + throw new Chat.ErrorMessage(this.tr`It's too early to tie, please play until turn 100.`); } this.checkCan('roomvoice', null, room); if (cmd === 'accepttie' && !battle.players.some(player => player.wantsTie)) { - return this.errorReply(this.tr`No other player is requesting a tie right now. It was probably canceled.`); + throw new Chat.ErrorMessage(this.tr`No other player is requesting a tie right now. It was probably canceled.`); } const player = battle.playerTable[user.id]; if (!battle.players.some(curPlayer => curPlayer.wantsTie)) { @@ -1031,12 +1031,12 @@ export const commands: Chat.ChatCommands = { } } else { if (!player) { - return this.errorReply(this.tr`Must be a player to accept ties.`); + throw new Chat.ErrorMessage(this.tr`Must be a player to accept ties.`); } if (!player.wantsTie) { player.wantsTie = true; } else { - return this.errorReply(this.tr`You have already agreed to a tie.`); + throw new Chat.ErrorMessage(this.tr`You have already agreed to a tie.`); } player.sendRoom(Utils.html`|uhtmlchange|offertie|`); this.add(this.tr`${user.name} accepted the tie.`); @@ -1054,13 +1054,13 @@ export const commands: Chat.ChatCommands = { rejecttie(target, room, user) { room = this.requireRoom(); const battle = room.battle; - if (!battle) return this.errorReply(this.tr`Must be in a battle room.`); + if (!battle) throw new Chat.ErrorMessage(this.tr`Must be in a battle room.`); const player = battle.playerTable[user.id]; if (!player) { - return this.errorReply(this.tr`Must be a player to reject ties.`); + throw new Chat.ErrorMessage(this.tr`Must be a player to reject ties.`); } if (!battle.players.some(curPlayer => curPlayer.wantsTie)) { - return this.errorReply(this.tr`No other player is requesting a tie right now. It was probably canceled.`); + throw new Chat.ErrorMessage(this.tr`No other player is requesting a tie right now. It was probably canceled.`); } if (player.wantsTie) player.wantsTie = false; for (const otherPlayer of battle.players) { @@ -1081,9 +1081,9 @@ export const commands: Chat.ChatCommands = { forfeit(target, room, user) { room = this.requireRoom(); - if (!room.game) return this.errorReply(this.tr`This room doesn't have an active game.`); + if (!room.game) throw new Chat.ErrorMessage(this.tr`This room doesn't have an active game.`); if (!room.game.forfeit) { - return this.errorReply(this.tr`This kind of game can't be forfeited.`); + throw new Chat.ErrorMessage(this.tr`This kind of game can't be forfeited.`); } room.game.forfeit(user); }, @@ -1094,8 +1094,8 @@ export const commands: Chat.ChatCommands = { guess: 'choose', choose(target, room, user) { room = this.requireRoom(); - if (!room.game) return this.errorReply(this.tr`This room doesn't have an active game.`); - if (!room.game.choose) return this.errorReply(this.tr`This game doesn't support /choose`); + if (!room.game) throw new Chat.ErrorMessage(this.tr`This room doesn't have an active game.`); + if (!room.game.choose) throw new Chat.ErrorMessage(this.tr`This game doesn't support /choose`); if (room.game.checkChat) this.checkChat(); room.game.choose(user, target); }, @@ -1129,8 +1129,8 @@ export const commands: Chat.ChatCommands = { undo(target, room, user) { room = this.requireRoom(); - if (!room.game) return this.errorReply(this.tr`This room doesn't have an active game.`); - if (!room.game.undo) return this.errorReply(this.tr`This game doesn't support /undo`); + if (!room.game) throw new Chat.ErrorMessage(this.tr`This room doesn't have an active game.`); + if (!room.game.undo) throw new Chat.ErrorMessage(this.tr`This game doesn't support /undo`); room.game.undo(user, target); }, @@ -1141,7 +1141,7 @@ export const commands: Chat.ChatCommands = { uploadreplay: 'savereplay', async savereplay(target, room, user, connection) { if (!room?.battle) { - return this.errorReply(this.tr`You can only save replays for battles.`); + throw new Chat.ErrorMessage(this.tr`You can only save replays for battles.`); } const options = (target === 'forpunishment' || target === 'silent') ? target : undefined; @@ -1150,12 +1150,12 @@ export const commands: Chat.ChatCommands = { savereplayhelp: [`/savereplay - Saves the replay for the current battle.`], hidereplay(target, room, user, connection) { - if (!room?.battle) return this.errorReply(`Must be used in a battle.`); + if (!room?.battle) throw new Chat.ErrorMessage(`Must be used in a battle.`); this.checkCan('joinbattle', null, room); if (room.tour?.forcePublic) { - return this.errorReply(this.tr`This battle can't have hidden replays, because the tournament is set to be forced public.`); + throw new Chat.ErrorMessage(this.tr`This battle can't have hidden replays, because the tournament is set to be forced public.`); } - if (room.hideReplay) return this.errorReply(this.tr`The replay for this battle is already set to hidden.`); + if (room.hideReplay) throw new Chat.ErrorMessage(this.tr`The replay for this battle is already set to hidden.`); room.hideReplay = true; // If a replay has already been saved, /savereplay again to update the uploaded replay's hidden status if (room.battle.replaySaved) this.parse('/savereplay'); @@ -1166,8 +1166,8 @@ export const commands: Chat.ChatCommands = { addplayer: 'invitebattle', invitebattle(target, room, user, connection) { room = this.requireRoom(); - if (!room.battle) return this.errorReply(this.tr`You can only do this in battle rooms.`); - if (room.rated) return this.errorReply(this.tr`You can only add a Player to unrated battles.`); + if (!room.battle) throw new Chat.ErrorMessage(this.tr`You can only do this in battle rooms.`); + if (room.rated) throw new Chat.ErrorMessage(this.tr`You can only add a Player to unrated battles.`); this.checkCan('joinbattle', null, room); @@ -1181,29 +1181,29 @@ export const commands: Chat.ChatCommands = { const player = battle[slot]; if (!player) { - return this.errorReply(`This battle does not support having players in ${slot}`); + throw new Chat.ErrorMessage(`This battle does not support having players in ${slot}`); } if (!targetUser) { battle.sendInviteForm(connection); - return this.errorReply(this.tr`User ${name} not found.`); + throw new Chat.ErrorMessage(this.tr`User ${name} not found.`); } if (player.id) { battle.sendInviteForm(connection); - return this.errorReply(this.tr`This room already has a player in slot ${slot}.`); + throw new Chat.ErrorMessage(this.tr`This room already has a player in slot ${slot}.`); } if (player.invite) { battle.sendInviteForm(connection); - return this.errorReply(`Someone else (${player.invite}) has already been invited to be ${slot}!`); + throw new Chat.ErrorMessage(`Someone else (${player.invite}) has already been invited to be ${slot}!`); } if (targetUser.id in battle.playerTable) { battle.sendInviteForm(connection); - return this.errorReply(this.tr`${targetUser.name} is already a player in this battle.`); + throw new Chat.ErrorMessage(this.tr`${targetUser.name} is already a player in this battle.`); } if (targetUser.settings.blockChallenges && !user.can('bypassblocks', targetUser)) { battle.sendInviteForm(connection); Chat.maybeNotifyBlocked('challenge', targetUser, user); - return this.errorReply(this.tr`The user '${targetUser.name}' is not accepting challenges right now.`); + throw new Chat.ErrorMessage(this.tr`The user '${targetUser.name}' is not accepting challenges right now.`); } // INVITE @@ -1239,11 +1239,11 @@ export const commands: Chat.ChatCommands = { const chall = Ladders.challenges.resolveAcceptCommand(this); const targetRoom = Rooms.get(chall.roomid); - if (!targetRoom) return this.errorReply(`Room ${chall.roomid} not found`); + if (!targetRoom) throw new Chat.ErrorMessage(`Room ${chall.roomid} not found`); const battle = targetRoom.battle!; const player = battle.players.find(maybe => maybe.invite === user.id); if (!player) { - return this.errorReply(`You haven't been invited to that battle.`); + throw new Chat.ErrorMessage(`You haven't been invited to that battle.`); } const slot = player.slot; if (player.id) { @@ -1271,7 +1271,7 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); this.checkCan('joinbattle', null, room); - if (!room.battle) return this.errorReply(this.tr`You can only do this in battle rooms.`); + if (!room.battle) throw new Chat.ErrorMessage(this.tr`You can only do this in battle rooms.`); const invitesFull = room.battle.invitesFull(); const challenges = Ladders.challenges.get(target as ID); @@ -1292,8 +1292,8 @@ export const commands: Chat.ChatCommands = { restoreplayers(target, room, user) { room = this.requireRoom(); - if (!room.battle) return this.errorReply(this.tr`You can only do this in battle rooms.`); - if (room.rated) return this.errorReply(this.tr`You can only add a Player to unrated battles.`); + if (!room.battle) throw new Chat.ErrorMessage(this.tr`You can only do this in battle rooms.`); + if (room.rated) throw new Chat.ErrorMessage(this.tr`You can only add a Player to unrated battles.`); let didSomething = false; for (const player of room.battle.players) { @@ -1304,7 +1304,7 @@ export const commands: Chat.ChatCommands = { } if (!didSomething) { - return this.errorReply(this.tr`Players could not be restored (maybe this battle already has two players?).`); + throw new Chat.ErrorMessage(this.tr`Players could not be restored (maybe this battle already has two players?).`); } }, restoreplayershelp: [ @@ -1314,8 +1314,8 @@ export const commands: Chat.ChatCommands = { joinbattle: 'joingame', joingame(target, room, user) { room = this.requireRoom(); - if (!room.game) return this.errorReply(this.tr`This room doesn't have an active game.`); - if (!room.game.joinGame) return this.errorReply(this.tr`This game doesn't support /joingame`); + if (!room.game) throw new Chat.ErrorMessage(this.tr`This room doesn't have an active game.`); + if (!room.game.joinGame) throw new Chat.ErrorMessage(this.tr`This game doesn't support /joingame`); room.game.joinGame(user, target); }, @@ -1325,8 +1325,8 @@ export const commands: Chat.ChatCommands = { partbattle: 'leavegame', leavegame(target, room, user) { room = this.requireRoom(); - if (!room.game) return this.errorReply(this.tr`This room doesn't have an active game.`); - if (!room.game.leaveGame) return this.errorReply(this.tr`This game doesn't support /leavegame`); + if (!room.game) throw new Chat.ErrorMessage(this.tr`This room doesn't have an active game.`); + if (!room.game.leaveGame) throw new Chat.ErrorMessage(this.tr`This game doesn't support /leavegame`); room.game.leaveGame(user); }, @@ -1335,9 +1335,9 @@ export const commands: Chat.ChatCommands = { kickbattle: 'kickgame', kickgame(target, room, user) { room = this.requireRoom(); - if (!room.battle) return this.errorReply(this.tr`You can only do this in battle rooms.`); + if (!room.battle) throw new Chat.ErrorMessage(this.tr`You can only do this in battle rooms.`); if (room.battle.challengeType === 'tour' || room.battle.rated) { - return this.errorReply(this.tr`You can only do this in unrated non-tour battles.`); + throw new Chat.ErrorMessage(this.tr`You can only do this in unrated non-tour battles.`); } const { targetUser, rest: reason } = this.requireUser(target, { allowOffline: true }); this.checkCan('kick', targetUser, room); @@ -1346,7 +1346,7 @@ export const commands: Chat.ChatCommands = { this.addModAction(room.tr`${targetUser.name} was kicked from a battle by ${user.name}.${displayReason}`); this.modlog('KICKBATTLE', targetUser, reason, { noip: 1, noalts: 1 }); } else { - this.errorReply("/kickbattle - User isn't in battle."); + throw new Chat.ErrorMessage("/kickbattle - User isn't in battle."); } }, kickbattlehelp: [`/kickbattle [username], [reason] - Kicks a user from a battle with reason. Requires: % @ ~`], @@ -1362,7 +1362,7 @@ export const commands: Chat.ChatCommands = { target = toID(target); room = this.requireRoom(); if (!room.game?.timer) { - return this.errorReply(this.tr`You can only set the timer from inside a battle room.`); + throw new Chat.ErrorMessage(this.tr`You can only set the timer from inside a battle room.`); } const timer = room.game.timer as any; if (!timer.timerRequesters) { @@ -1377,7 +1377,7 @@ export const commands: Chat.ChatCommands = { } const force = user.can('timer', null, room); if (!force && !room.game.playerTable[user.id]) { - return this.errorReply(this.tr`Access denied.`); + throw new Chat.ErrorMessage(this.tr`Access denied.`); } if (this.meansNo(target) || target === 'stop') { if (timer.timerRequesters.size) { @@ -1386,12 +1386,12 @@ export const commands: Chat.ChatCommands = { room.send(`|inactiveoff|${room.tr`Timer was turned off by staff. Please do not turn it back on until our staff say it's okay.`}`); } } else { - this.errorReply(this.tr`The timer is already off.`); + throw new Chat.ErrorMessage(this.tr`The timer is already off.`); } } else if (this.meansYes(target) || target === 'start') { timer.start(user); } else { - this.errorReply(this.tr`"${target}" is not a recognized timer state.`); + throw new Chat.ErrorMessage(this.tr`"${target}" is not a recognized timer state.`); } }, timerhelp: [ @@ -1410,7 +1410,7 @@ export const commands: Chat.ChatCommands = { Config.forcetimer = true; this.addModAction(room.tr`Forcetimer is now ON: All battles will be timed. (set by ${user.name})`); } else { - this.errorReply(this.tr`'${target}' is not a recognized forcetimer setting.`); + throw new Chat.ErrorMessage(this.tr`'${target}' is not a recognized forcetimer setting.`); } }, forcetimerhelp: [ @@ -1425,8 +1425,7 @@ export const commands: Chat.ChatCommands = { !room.battle && !(room.game && typeof (room.game as any).tie === 'function' && typeof (room.game as any).win === 'function') ) { - this.errorReply("/forcewin - This is not a battle room."); - return false; + throw new Chat.ErrorMessage("/forcewin - This is not a battle room."); } if (room.battle) room.battle.endType = 'forced'; @@ -1436,7 +1435,7 @@ export const commands: Chat.ChatCommands = { return false; } const targetUser = Users.getExact(target); - if (!targetUser) return this.errorReply(this.tr`User '${target}' not found.`); + if (!targetUser) throw new Chat.ErrorMessage(this.tr`User '${target}' not found.`); (room.game as any).win(targetUser); this.modlog('FORCEWIN', targetUser.id); @@ -1517,7 +1516,7 @@ export const commands: Chat.ChatCommands = { blockchallenges(target, room, user) { if (toID(target) === 'ac') target = 'autoconfirmed'; if (user.settings.blockChallenges === (target || true)) { - return this.errorReply(this.tr`You are already blocking challenges!`); + throw new Chat.ErrorMessage(this.tr`You are already blocking challenges!`); } if (Users.Auth.isAuthLevel(target)) { user.settings.blockChallenges = target; @@ -1542,7 +1541,7 @@ export const commands: Chat.ChatCommands = { unblockchalls: 'allowchallenges', unblockchallenges: 'allowchallenges', allowchallenges(target, room, user) { - if (!user.settings.blockChallenges) return this.errorReply(this.tr`You are already available for challenges!`); + if (!user.settings.blockChallenges) throw new Chat.ErrorMessage(this.tr`You are already available for challenges!`); user.settings.blockChallenges = false; user.update(); this.sendReply(this.tr`You are available for challenges from now on.`); @@ -1620,7 +1619,7 @@ export const commands: Chat.ChatCommands = { if (Monitor.countPrepBattle(connection.ip, connection)) { return; } - if (!target) return this.errorReply(this.tr`Provide a valid format.`); + if (!target) throw new Chat.ErrorMessage(this.tr`Provide a valid format.`); const originalFormat = Dex.formats.get(target); // Note: The default here of Anything Goes isn't normally hit; since the web client will send a default format const format = originalFormat.effectType === 'Format' ? originalFormat : Dex.formats.get('Anything Goes'); @@ -1807,7 +1806,7 @@ export const pages: Chat.PageTable = { receivedpms(query, user) { this.title = '[Received PMs]'; if (!Chat.PrivateMessages.offlineIsEnabled) { - return this.errorReply(`Offline PMs are presently disabled.`); + throw new Chat.ErrorMessage(`Offline PMs are presently disabled.`); } return Chat.PrivateMessages.renderReceived(user); }, diff --git a/server/chat-commands/info.ts b/server/chat-commands/info.ts index bd3f9ccc00..ae39953682 100644 --- a/server/chat-commands/info.ts +++ b/server/chat-commands/info.ts @@ -111,10 +111,10 @@ export const commands: Chat.ChatCommands = { const showRecursiveAlts = showAll && (cmd !== 'altsnorecurse'); if (!targetUser) { if (showAll) return this.parse('/offlinewhois ' + target); - return this.errorReply(`User ${target} not found.`); + throw new Chat.ErrorMessage(`User ${target} not found.`); } if (showAll && !user.trusted && targetUser !== user) { - return this.errorReply(`/${cmd} - Access denied.`); + throw new Chat.ErrorMessage(`/${cmd} - Access denied.`); } let buf = Utils.html`${targetUser.tempGroup}${targetUser.name} `; @@ -354,10 +354,10 @@ export const commands: Chat.ChatCommands = { checkpunishment: 'offlinewhois', offlinewhois(target, room, user) { if (!user.trusted) { - return this.errorReply("/offlinewhois - Access denied."); + throw new Chat.ErrorMessage("/offlinewhois - Access denied."); } const userid = toID(target); - if (!userid) return this.errorReply("Please enter a valid username."); + if (!userid) throw new Chat.ErrorMessage("Please enter a valid username."); const targetUser = Users.get(userid); let buf = Utils.html`${target}`; if (!targetUser?.connected) buf += ` (offline)`; @@ -395,7 +395,7 @@ export const commands: Chat.ChatCommands = { if (!user.can('alts') && !atLeastOne) { const hasJurisdiction = room && user.can('mute', null, room) && Punishments.roomUserids.nestedHas(room.roomid, userid); if (!hasJurisdiction) { - return this.errorReply("/checkpunishment - User not found."); + throw new Chat.ErrorMessage("/checkpunishment - User not found."); } } @@ -453,7 +453,7 @@ export const commands: Chat.ChatCommands = { showpunishments(target, room, user) { room = this.requireRoom(); if (!room.persist) { - return this.errorReply("This command is unavailable in temporary rooms."); + throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); } return this.parse(`/join view-punishments-${room}`); }, @@ -470,7 +470,7 @@ export const commands: Chat.ChatCommands = { if (!target) return this.parse('/help host'); this.checkCan('alts'); target = target.trim(); - if (!net.isIPv4(target)) return this.errorReply('You must pass a valid IPv4 IP to /host.'); + if (!net.isIPv4(target)) throw new Chat.ErrorMessage('You must pass a valid IPv4 IP to /host.'); const { dnsbl, host, hostType } = await IPTools.lookup(target); const dnsblMessage = dnsbl ? ` [${dnsbl}]` : ``; this.sendReply(`IP ${target}: ${host || "ERROR"} [${hostType}]${dnsblMessage}`); @@ -487,7 +487,7 @@ export const commands: Chat.ChatCommands = { const [ipOrHost, roomid] = this.splitOne(target); const targetRoom = roomid ? Rooms.get(roomid) : null; if (typeof targetRoom === 'undefined') { - return this.errorReply(`The room "${roomid}" does not exist.`); + throw new Chat.ErrorMessage(`The room "${roomid}" does not exist.`); } const results: string[] = []; const isAll = (cmd === 'ipsearchall'); @@ -520,7 +520,7 @@ export const commands: Chat.ChatCommands = { results.push(`${curUser.connected ? ONLINE_SYMBOL : OFFLINE_SYMBOL} ${curUser.name}`); } } else { - return this.errorReply(`${ipOrHost} is not a valid IP, IP range, or host.`); + throw new Chat.ErrorMessage(`${ipOrHost} is not a valid IP, IP range, or host.`); } if (!results.length) { @@ -546,7 +546,7 @@ export const commands: Chat.ChatCommands = { const { targetUser: user2, rest: rest2 } = this.requireUser(rest); if (user1 === user2 || rest2) return this.parse(`/help checkchallenges`); if (!(user1.id in room.users) || !(user2.id in room.users)) { - return this.errorReply(`Both users must be in this room.`); + throw new Chat.ErrorMessage(`Both users must be in this room.`); } const chall = Ladders.challenges.search(user1.id, user2.id); @@ -603,7 +603,7 @@ export const commands: Chat.ChatCommands = { const newTargets = dex.dataSearch(target); const showDetails = (cmd.startsWith('dt') || cmd === 'details'); if (!newTargets?.length) { - return this.errorReply(`No Pok\u00e9mon, item, move, ability or nature named '${target}' was found${Dex.gen > dex.gen ? ` in Gen ${dex.gen}` : ""}. (Check your spelling?)`); + throw new Chat.ErrorMessage(`No Pok\u00e9mon, item, move, ability or nature named '${target}' was found${Dex.gen > dex.gen ? ` in Gen ${dex.gen}` : ""}. (Check your spelling?)`); } for (const [i, newTarget] of newTargets.entries()) { @@ -1063,7 +1063,7 @@ export const commands: Chat.ChatCommands = { matchup: 'effectiveness', effectiveness(target, room, user) { const { dex, targets } = this.splitFormat(target.split(/[,/]/)); - if (targets.length !== 2) return this.errorReply("Attacker and defender must be separated with a comma."); + if (targets.length !== 2) throw new Chat.ErrorMessage("Attacker and defender must be separated with a comma."); let searchMethods = ['types', 'moves', 'species']; const sourceMethods = ['types', 'moves']; @@ -1176,9 +1176,9 @@ export const commands: Chat.ChatCommands = { // arg is a move? const move = dex.moves.get(arg); if (!move.exists) { - return this.errorReply(`Type or move '${arg}' not found.`); + throw new Chat.ErrorMessage(`Type or move '${arg}' not found.`); } else if (move.gen > dex.gen) { - return this.errorReply(`Move '${arg}' is not available in Gen ${dex.gen}.`); + throw new Chat.ErrorMessage(`Move '${arg}' is not available in Gen ${dex.gen}.`); } if (!move.basePower && !move.basePowerCallback) continue; @@ -1196,8 +1196,10 @@ export const commands: Chat.ChatCommands = { if (eff > bestCoverage[type]) bestCoverage[type] = eff; } } - if (sources.length === 0) return this.errorReply("No moves using a type table for determining damage were specified."); - if (sources.length > 4) return this.errorReply("Specify a maximum of 4 moves or types."); + if (sources.length === 0) { + throw new Chat.ErrorMessage("No moves using a type table for determining damage were specified."); + } + if (sources.length > 4) throw new Chat.ErrorMessage("Specify a maximum of 4 moves or types."); // converts to fractional effectiveness, 0 for immune for (const type in bestCoverage) { @@ -1901,7 +1903,7 @@ export const commands: Chat.ChatCommands = { if (targetId === 'all') targetId = ''; const { totalMatches, sections } = findFormats(targetId, isOMSearch); - if (!totalMatches) return this.errorReply("No matched formats found."); + if (!totalMatches) throw new Chat.ErrorMessage("No matched formats found."); const format = totalMatches === 1 ? Dex.formats.get(Object.values(sections)[0].formats[0]) : null; @@ -2070,15 +2072,16 @@ export const commands: Chat.ChatCommands = { return; } if (!room) { - return this.errorReply(`This is not a room you can set the rules of.`); + throw new Chat.ErrorMessage(`This is not a room you can set the rules of.`); } const possibleRoom = Rooms.search(toID(target)); const { totalMatches: formatMatches } = findFormats(toID(target)); if (formatMatches && possibleRoom && cmd !== 'roomrules') { - this.errorReply(`'${target}' is both a room and a tier. `); - this.errorReply(`If you were looking for rules of that room, use /roomrules [room].`); - this.errorReply(`Otherwise, use /tier [tiername].`); - return; + throw new Chat.ErrorMessage([ + `'${target}' is both a room and a tier.`, + `If you were looking for rules of that room, use /roomrules [room].`, + `Otherwise, use /tier [tiername].`, + ]); } if (possibleRoom) { @@ -2094,13 +2097,13 @@ export const commands: Chat.ChatCommands = { } this.checkCan('editroom', null, room); if (target.length > 150) { - return this.errorReply(`Error: Room rules link is too long (must be under 150 characters). You can use a URL shortener to shorten the link.`); + throw new Chat.ErrorMessage(`Error: Room rules link is too long (must be under 150 characters). You can use a URL shortener to shorten the link.`); } target = target.trim(); if (target === 'delete' || target === 'remove') { - if (!room.settings.rulesLink) return this.errorReply(`This room does not have rules set to remove.`); + if (!room.settings.rulesLink) throw new Chat.ErrorMessage(`This room does not have rules set to remove.`); delete room.settings.rulesLink; this.privateModAction(`${user.name} has removed the room rules link.`); this.modlog('RULES', null, `removed room rules link`); @@ -2373,7 +2376,7 @@ export const commands: Chat.ChatCommands = { if (pokemon.exists) { atLeastOne = true; if (pokemon.isNonstandard && pokemon.isNonstandard !== 'Past') { - return this.errorReply(`${pokemon.name} is not a real Pok\u00e9mon.`); + throw new Chat.ErrorMessage(`${pokemon.name} is not a real Pok\u00e9mon.`); } let baseSpecies = pokemon.baseSpecies; if (pokemon.id.startsWith('flabebe')) baseSpecies = 'Flabébé'; @@ -2386,7 +2389,7 @@ export const commands: Chat.ChatCommands = { if (item.exists) { atLeastOne = true; if (item.isNonstandard && item.isNonstandard !== 'Past') { - return this.errorReply(`${item.name} is not a real item.`); + throw new Chat.ErrorMessage(`${item.name} is not a real item.`); } let link = `${baseLink}${encodeURIComponent(item.name)}`; if (Dex.moves.get(item.name).exists) link += '_(item)'; @@ -2397,7 +2400,7 @@ export const commands: Chat.ChatCommands = { if (ability.exists) { atLeastOne = true; if (ability.isNonstandard && ability.isNonstandard !== 'Past') { - return this.errorReply(`${ability.name} is not a real ability.`); + throw new Chat.ErrorMessage(`${ability.name} is not a real ability.`); } const link = `${baseLink}${encodeURIComponent(ability.name)}_(Ability)`; this.sendReplyBox(`${ability.name} ability description, provided by Bulbapedia`); @@ -2407,7 +2410,7 @@ export const commands: Chat.ChatCommands = { if (move.exists) { atLeastOne = true; if (move.isNonstandard && move.isNonstandard !== 'Past') { - return this.errorReply(`${move.name} is not a real move.`); + throw new Chat.ErrorMessage(`${move.name} is not a real move.`); } const link = `${baseLink}${encodeURIComponent(move.name)}_(move)`; this.sendReplyBox(`${move.name} move description, provided by Bulbapedia`); @@ -2473,11 +2476,11 @@ export const commands: Chat.ChatCommands = { offset = Number(target.slice(modifierData.index)); if (isNaN(offset)) return this.parse('/help dice'); if (!Number.isSafeInteger(offset)) { - return this.errorReply(`The specified offset must be an integer up to ${Number.MAX_SAFE_INTEGER}.`); + throw new Chat.ErrorMessage(`The specified offset must be an integer up to ${Number.MAX_SAFE_INTEGER}.`); } } if (removeOutlier && diceQuantity <= 1) { - return this.errorReply(`More than one dice should be rolled before removing outliers.`); + throw new Chat.ErrorMessage(`More than one dice should be rolled before removing outliers.`); } target = target.slice(0, modifierData.index); } @@ -2486,14 +2489,14 @@ export const commands: Chat.ChatCommands = { if (target.length) { diceFaces = Number(target); if (!Number.isSafeInteger(diceFaces) || diceFaces <= 0) { - return this.errorReply(`Rolled dice must have a natural amount of faces up to ${Number.MAX_SAFE_INTEGER}.`); + throw new Chat.ErrorMessage(`Rolled dice must have a natural amount of faces up to ${Number.MAX_SAFE_INTEGER}.`); } } if (diceQuantity > 1) { // Make sure that we can deal with high rolls if (!Number.isSafeInteger(offset < 0 ? diceQuantity * diceFaces : diceQuantity * diceFaces + offset)) { - return this.errorReply(`The maximum sum of rolled dice must be lower or equal than ${Number.MAX_SAFE_INTEGER}.`); + throw new Chat.ErrorMessage(`The maximum sum of rolled dice must be lower or equal than ${Number.MAX_SAFE_INTEGER}.`); } } @@ -2568,17 +2571,17 @@ export const commands: Chat.ChatCommands = { ], showimage(target, room, user) { - return this.errorReply(`/showimage has been deprecated - use /show instead.`); + throw new Chat.ErrorMessage(`/showimage has been deprecated - use /show instead.`); }, async requestshow(target, room, user) { room = this.requireRoom(); this.checkChat(); if (!room.settings.requestShowEnabled) { - return this.errorReply(`Media approvals are disabled in this room.`); + throw new Chat.ErrorMessage(`Media approvals are disabled in this room.`); } - if (user.can('showmedia', null, room, 'show')) return this.errorReply(`Use !show instead.`); - if (room.pendingApprovals?.has(user.id)) return this.errorReply('You have a request pending already.'); + if (user.can('showmedia', null, room, 'show')) throw new Chat.ErrorMessage(`Use !show instead.`); + if (room.pendingApprovals?.has(user.id)) throw new Chat.ErrorMessage('You have a request pending already.'); if (!toID(target)) return this.parse(`/help requestshow`); let [link, comment] = this.splitOne(target); @@ -2596,7 +2599,7 @@ export const commands: Chat.ChatCommands = { } } if (comment && this.checkChat(comment) !== comment) { - return this.errorReply(`You cannot use filtered words in comments.`); + throw new Chat.ErrorMessage(`You cannot use filtered words in comments.`); } if (!room.pendingApprovals) room.pendingApprovals = new Map(); room.pendingApprovals.set(user.id, { @@ -2622,14 +2625,14 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); this.checkCan('mute', null, room); if (!room.settings.requestShowEnabled) { - return this.errorReply(`Media approvals are disabled in this room.`); + throw new Chat.ErrorMessage(`Media approvals are disabled in this room.`); } const userid = toID(target); if (!userid) return this.parse(`/help approveshow`); const request = room.pendingApprovals?.get(userid); - if (!request) return this.errorReply(`${userid} has no pending request.`); + if (!request) throw new Chat.ErrorMessage(`${userid} has no pending request.`); if (userid === user.id) { - return this.errorReply(`You can't approve your own /show request.`); + throw new Chat.ErrorMessage(`You can't approve your own /show request.`); } room.pendingApprovals!.delete(userid); room.sendMods(`|uhtmlchange|request-${target}|`); @@ -2642,7 +2645,7 @@ export const commands: Chat.ChatCommands = { if (resized) buf += Utils.html`
full-size image`; } else { buf = await YouTube.generateVideoDisplay(request.link, false); - if (!buf) return this.errorReply('Could not get YouTube video'); + if (!buf) throw new Chat.ErrorMessage('Could not get YouTube video'); } buf += Utils.html`
(Requested by ${request.name})`; if (request.comment) { @@ -2659,13 +2662,13 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); this.checkCan('mute', null, room); if (!room.settings.requestShowEnabled) { - return this.errorReply(`Media approvals are disabled in this room.`); + throw new Chat.ErrorMessage(`Media approvals are disabled in this room.`); } target = toID(target); if (!target) return this.parse(`/help denyshow`); const entry = room.pendingApprovals?.get(target); - if (!entry) return this.errorReply(`${target} has no pending request.`); + if (!entry) throw new Chat.ErrorMessage(`${target} has no pending request.`); room.pendingApprovals!.delete(target); room.sendMods(`|uhtmlchange|request-${target}|`); @@ -2696,11 +2699,11 @@ export const commands: Chat.ChatCommands = { async show(target, room, user, connection) { if (!room?.persist && !this.pmTarget && !room?.roomid.startsWith('help-')) { - return this.errorReply(`/show cannot be used in temporary rooms.`); + throw new Chat.ErrorMessage(`/show cannot be used in temporary rooms.`); } if (!toID(target).trim()) return this.parse(`/help show`); if (Monitor.countNetRequests(connection.ip)) { - return this.errorReply(`You are using this command too quickly. Wait a bit and try again.`); + throw new Chat.ErrorMessage(`You are using this command too quickly. Wait a bit and try again.`); } const [link, comment] = Utils.splitFirst(target, ',').map(f => f.trim()); @@ -2720,7 +2723,7 @@ export const commands: Chat.ChatCommands = { this.message = this.message.replace(/&ab_channel=(.*)(&|)/ig, '').replace(/https:\/\/www\./ig, ''); } else if (Twitch.linkRegex.test(link)) { const channelId = Twitch.linkRegex.exec(link)?.[2]?.trim(); - if (!channelId) return this.errorReply(`Specify a Twitch channel.`); + if (!channelId) throw new Chat.ErrorMessage(`Specify a Twitch channel.`); buf = Utils.html`Watching ${channelId}...
`; buf += ``; } else { @@ -2741,13 +2744,13 @@ export const commands: Chat.ChatCommands = { buf = Utils.html``; if (resized) buf += Utils.html`
full-size image`; } catch { - return this.errorReply('Invalid image, audio, or video URL.'); + throw new Chat.ErrorMessage('Invalid image, audio, or video URL.'); } } } if (comment) { if (this.checkChat(comment) !== comment) { - return this.errorReply(`You cannot use filtered words in comments.`); + throw new Chat.ErrorMessage(`You cannot use filtered words in comments.`); } buf += Utils.html`
(${comment})
`; } @@ -2776,9 +2779,9 @@ export const commands: Chat.ChatCommands = { async registertime(target, room, user, connection) { this.runBroadcast(); if (Monitor.countNetRequests(connection.ip)) { - return this.errorReply(`You are using this command too quickly. Wait a bit and try again.`); + throw new Chat.ErrorMessage(`You are using this command too quickly. Wait a bit and try again.`); } - if (!user.autoconfirmed) return this.errorReply(`Only autoconfirmed users can use this command.`); + if (!user.autoconfirmed) throw new Chat.ErrorMessage(`Only autoconfirmed users can use this command.`); target = toID(target); if (!target) target = user.id; let rawResult; @@ -2817,14 +2820,14 @@ export const commands: Chat.ChatCommands = { // important to code block indentation. target = this.message.substr(this.cmdToken.length + this.cmd.length + (this.message.includes(' ') ? 1 : 0)).trimRight(); if (!target) return this.parse('/help code'); - if (target.length >= 8192) return this.errorReply("Your code must be under 8192 characters long!"); + if (target.length >= 8192) throw new Chat.ErrorMessage("Your code must be under 8192 characters long!"); if (target.length < 80 && !target.includes('\n') && !target.includes('```') && this.shouldBroadcast()) { return this.checkChat(`\`\`\`${target}\`\`\``); } if (this.room?.settings.isPersonal !== false && this.shouldBroadcast()) { target = this.filter(target)!; - if (!target) return this.errorReply(`Invalid code.`); + if (!target) throw new Chat.ErrorMessage(`Invalid code.`); } this.checkBroadcast(true, '!code'); @@ -3001,7 +3004,7 @@ export const commands: Chat.ChatCommands = { randtopic(target, room, user) { room = this.requireRoom(); if (!room.settings.topics?.length) { - return this.errorReply(`This room has no random topics to select from.`); + throw new Chat.ErrorMessage(`This room has no random topics to select from.`); } this.runBroadcast(); this.sendReply(Utils.html`|html|
${Utils.randomElement(room.settings.topics)}
`); @@ -3036,10 +3039,10 @@ export const commands: Chat.ChatCommands = { } const index = Number(toID(target)) - 1; if (isNaN(index)) { - return this.errorReply(`Invalid topic index: ${target}. Must be a number.`); + throw new Chat.ErrorMessage(`Invalid topic index: ${target}. Must be a number.`); } if (!room.settings.topics?.[index]) { - return this.errorReply(`Topic ${index + 1} not found.`); + throw new Chat.ErrorMessage(`Topic ${index + 1} not found.`); } const topic = room.settings.topics.splice(index, 1)[0]; room.saveSettings(); @@ -3072,19 +3075,19 @@ export const pages: Chat.PageTable = { this.title = '[Alts Log]'; const target = toID(query.shift()); if (!target) { - return this.errorReply(`Please specify a user to find alternate accounts for.`); + throw new Chat.ErrorMessage(`Please specify a user to find alternate accounts for.`); } this.title += ` ${target}`; if (!Config.usesqlite) { - return this.errorReply(`The alternate account log is currently disabled.`); + throw new Chat.ErrorMessage(`The alternate account log is currently disabled.`); } const rawLimit = query.shift() || "100"; const num = parseInt(rawLimit); if (num > 3000) { - return this.errorReply(`3000 is the maximum number of results from the alternate account log.`); + throw new Chat.ErrorMessage(`3000 is the maximum number of results from the alternate account log.`); } if (isNaN(num) || num < 1) { - return this.errorReply(`The max results must be a real number that is at least one (received "${rawLimit}")`); + throw new Chat.ErrorMessage(`The max results must be a real number that is at least one (received "${rawLimit}")`); } const showIPs = user.can('globalban'); const results = await Chat.database.all( diff --git a/server/chat-commands/moderation.ts b/server/chat-commands/moderation.ts index 79b993bc36..60da091671 100644 --- a/server/chat-commands/moderation.ts +++ b/server/chat-commands/moderation.ts @@ -143,15 +143,15 @@ export const commands: Chat.ChatCommands = { } if (!target) return this.parse('/help roomowner'); const { targetUser, targetUsername, rest } = this.splitUser(target, { exactName: true }); - if (rest) return this.errorReply(`This command does not support specifying a reason.`); + if (rest) throw new Chat.ErrorMessage(`This command does not support specifying a reason.`); const userid = toID(targetUsername); if (!Users.isUsernameKnown(userid)) { - return this.errorReply(`User '${targetUsername}' is offline and unrecognized, and so can't be promoted.`); + throw new Chat.ErrorMessage(`User '${targetUsername}' is offline and unrecognized, and so can't be promoted.`); } this.checkCan('makeroom'); - if (room.auth.getDirect(userid) === '#') return this.errorReply(`${targetUsername} is already a room owner.`); + if (room.auth.getDirect(userid) === '#') throw new Chat.ErrorMessage(`${targetUsername} is already a room owner.`); room.auth.set(userid, '#'); const message = `${targetUsername} was appointed Room Owner by ${user.name}.`; @@ -181,7 +181,7 @@ export const commands: Chat.ChatCommands = { roompromote(target, room, user, connection, cmd) { if (!room) { // this command isn't marked as room-only because it's usable in PMs through /invite - return this.errorReply("This command is only available in rooms"); + throw new Chat.ErrorMessage("This command is only available in rooms"); } this.checkChat(); if (!target) return this.parse('/help roompromote'); @@ -193,23 +193,19 @@ export const commands: Chat.ChatCommands = { const nextGroup = Users.Auth.getGroup(nextSymbol); if (!nextSymbol) { - return this.errorReply("Please specify a group such as /roomvoice or /roomdeauth"); + throw new Chat.ErrorMessage("Please specify a group such as /roomvoice or /roomdeauth"); } if (!Config.groups[nextSymbol]) { if (!force || !user.can('bypassall')) { - this.errorReply(`Group '${nextSymbol}' does not exist.`); - if (user.can('bypassall')) { - this.errorReply(`If you want to promote to a nonexistent group, use /forceroompromote`); - } - return; + throw new Chat.ErrorMessage(`Group '${nextSymbol}' does not exist.${user.can('bypassall') ? ` If you want to promote to a nonexistent group, use /forceroompromote.` : ''}`); } else if (!Users.Auth.isValidSymbol(nextSymbol)) { // yes I know this excludes astral-plane characters and includes combining characters - return this.errorReply(`Admins can forcepromote to nonexistent groups only if they are one character long`); + throw new Chat.ErrorMessage(`Admins can forcepromote to nonexistent groups only if they are one character long`); } } if (!force && (nextGroup.globalonly || (nextGroup.battleonly && !room.battle))) { - return this.errorReply(`Group 'room${nextGroup.id || nextSymbol}' does not exist as a room rank.`); + throw new Chat.ErrorMessage(`Group 'room${nextGroup.id || nextSymbol}' does not exist as a room rank.`); } const nextGroupName = nextGroup.name || "regular user"; @@ -329,7 +325,7 @@ export const commands: Chat.ChatCommands = { let targetRoom = room; if (target) targetRoom = Rooms.search(target)!; if (!targetRoom?.checkModjoin(user)) { - return this.errorReply(`The room "${target}" does not exist.`); + throw new Chat.ErrorMessage(`The room "${target}" does not exist.`); } const showAll = user.can('mute', null, targetRoom); @@ -521,7 +517,7 @@ export const commands: Chat.ChatCommands = { Chat.handleRoomClose(target as RoomID, user, connection); return; } - return this.errorReply(`The room '${target}' does not exist.`); + throw new Chat.ErrorMessage(`The room '${target}' does not exist.`); } Chat.handleRoomClose(targetRoom.roomid, user, connection); user.leaveRoom(targetRoom, connection); @@ -538,7 +534,7 @@ export const commands: Chat.ChatCommands = { if (!target) return this.parse('/help warn'); this.checkChat(); if (room?.settings.isPersonal && !user.can('warn' as any)) { - return this.errorReply("Warning is unavailable in group chats."); + throw new Chat.ErrorMessage("Warning is unavailable in group chats."); } // If used in pms, staff, help tickets or battles, log the warn to the global modlog. const globalWarn = ( @@ -552,7 +548,7 @@ export const commands: Chat.ChatCommands = { const saveReplay = globalWarn && room?.battle; if (!targetUser?.connected) { - if (!globalWarn) return this.errorReply(`User '${targetUsername}' not found.`); + if (!globalWarn) throw new Chat.ErrorMessage(`User '${targetUsername}' not found.`); if (room) { this.checkCan('warn', null, room); } else { @@ -568,23 +564,23 @@ export const commands: Chat.ChatCommands = { return; } if (!globalWarn && !(targetUser.id in room.users)) { - return this.errorReply(`User ${targetUsername} is not in the room ${room.roomid}.`); + throw new Chat.ErrorMessage(`User ${targetUsername} is not in the room ${room.roomid}.`); } if (publicReason.length > MAX_REASON_LENGTH) { - return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); + throw new Chat.ErrorMessage(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } if (room) { this.checkCan('warn', targetUser, room); } else { this.checkCan('lock', targetUser); } - if (targetUser.can('makeroom')) return this.errorReply("You are not allowed to warn upper staff members."); + if (targetUser.can('makeroom')) throw new Chat.ErrorMessage("You are not allowed to warn upper staff members."); const now = Date.now(); const timeout = now - targetUser.lastWarnedAt; if (timeout < 15 * 1000) { const remainder = (15 - (timeout / 1000)).toFixed(2); - return this.errorReply(`You must wait ${remainder} more seconds before you can warn ${targetUser.name} again.`); + throw new Chat.ErrorMessage(`You must wait ${remainder} more seconds before you can warn ${targetUser.name} again.`); } const logMessage = `${targetUser.name} was warned by ${user.name}.${(publicReason ? ` (${publicReason})` : ``)}`; @@ -621,33 +617,32 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); if (!target) return this.parse('/help redirect'); if (room.settings.isPrivate || room.settings.isPersonal) { - return this.errorReply("Users cannot be redirected from private or personal rooms."); + throw new Chat.ErrorMessage("Users cannot be redirected from private or personal rooms."); } const { targetUser, targetUsername, rest: targetRoomid } = this.splitUser(target); const targetRoom = Rooms.search(targetRoomid); if (!targetRoom || targetRoom.settings.modjoin || targetRoom.settings.staffRoom) { - return this.errorReply(`The room "${targetRoomid}" does not exist.`); + throw new Chat.ErrorMessage(`The room "${targetRoomid}" does not exist.`); } this.checkCan('warn', targetUser, room); this.checkCan('warn', targetUser, targetRoom); if (!user.can('rangeban', targetUser)) { - this.errorReply(`Redirects have been deprecated. Instead of /redirect, use <> or /invite to guide users to the correct room, and punish if users don't cooperate.`); - return; + throw new Chat.ErrorMessage(`Redirects have been deprecated. Instead of /redirect, use <> or /invite to guide users to the correct room, and punish if users don't cooperate.`); } if (!targetUser?.connected) { - return this.errorReply(`User ${targetUsername} not found.`); + throw new Chat.ErrorMessage(`User ${targetUsername} not found.`); } - if (targetRoom.roomid === "global") return this.errorReply(`Users cannot be redirected to the global room.`); + if (targetRoom.roomid === "global") throw new Chat.ErrorMessage(`Users cannot be redirected to the global room.`); if (targetRoom.settings.isPrivate || targetRoom.settings.isPersonal) { - return this.errorReply(`The room "${targetRoom.title}" is not public.`); + throw new Chat.ErrorMessage(`The room "${targetRoom.title}" is not public.`); } if (targetUser.inRooms.has(targetRoom.roomid)) { - return this.errorReply(`User ${targetUser.name} is already in the room ${targetRoom.title}!`); + throw new Chat.ErrorMessage(`User ${targetUser.name} is already in the room ${targetRoom.title}!`); } if (!targetUser.inRooms.has(room.roomid)) { - return this.errorReply(`User ${targetUsername} is not in the room ${room.roomid}.`); + throw new Chat.ErrorMessage(`User ${targetUsername} is not in the room ${room.roomid}.`); } targetUser.leaveRoom(room.roomid); targetUser.popup(`You are in the wrong room; please go to <<${targetRoom.roomid}>> instead`); @@ -667,15 +662,15 @@ export const commands: Chat.ChatCommands = { this.checkChat(); const { targetUser, inputUsername, targetUsername, rest: reason } = this.splitUser(target); - if (!targetUser) return this.errorReply(`User '${targetUsername}' not found.`); + if (!targetUser) throw new Chat.ErrorMessage(`User '${targetUsername}' not found.`); if (reason.length > MAX_REASON_LENGTH) { - return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); + throw new Chat.ErrorMessage(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } const { publicReason, privateReason } = this.parseSpoiler(reason); const muteDuration = ((cmd === 'hm' || cmd === 'hourmute') ? HOURMUTE_LENGTH : MUTE_LENGTH); this.checkCan('mute', targetUser, room); - if (targetUser.can('makeroom')) return this.errorReply("You are not allowed to mute upper staff members."); + if (targetUser.can('makeroom')) throw new Chat.ErrorMessage("You are not allowed to mute upper staff members."); const canBeMutedFurther = ((room.getMuteTime(targetUser) || 0) <= (muteDuration * 5 / 6)); if (targetUser.locked || (room.isMuted(targetUser) && !canBeMutedFurther) || @@ -723,7 +718,7 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); if (!target) return this.parse('/help unmute'); const { targetUser, targetUsername, rest } = this.splitUser(target); - if (rest) return this.errorReply(`This command does not support specifying a reason.`); + if (rest) throw new Chat.ErrorMessage(`This command does not support specifying a reason.`); this.checkChat(); this.checkCan('mute', null, room); @@ -735,7 +730,7 @@ export const commands: Chat.ChatCommands = { this.addModAction(`${(targetUser ? targetUser.name : successfullyUnmuted)} was unmuted by ${user.name}.`); this.modlog('UNMUTE', (targetUser || successfullyUnmuted), null, { noip: 1, noalts: 1 }); } else { - this.errorReply(`${(targetUser ? targetUser.name : targetUsername)} is not muted.`); + throw new Chat.ErrorMessage(`${(targetUser ? targetUser.name : targetUsername)} is not muted.`); } }, unmutehelp: [`/unmute [username] - Removes mute from user. Requires: % @ # ~`], @@ -758,14 +753,14 @@ export const commands: Chat.ChatCommands = { const { targetUser, inputUsername, targetUsername, rest: reason } = this.splitUser(target); const { publicReason, privateReason } = this.parseSpoiler(reason); - if (!targetUser) return this.errorReply(`User '${targetUsername}' not found.`); + if (!targetUser) throw new Chat.ErrorMessage(`User '${targetUsername}' not found.`); if (reason.length > MAX_REASON_LENGTH) { - return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); + throw new Chat.ErrorMessage(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } this.checkCan('ban', targetUser, room); - if (targetUser.can('makeroom')) return this.errorReply("You are not allowed to ban upper staff members."); + if (targetUser.can('makeroom')) throw new Chat.ErrorMessage("You are not allowed to ban upper staff members."); if (Punishments.hasRoomPunishType(room, toID(targetUsername), 'BLACKLIST')) { - return this.errorReply(`This user is already blacklisted from ${room.roomid}.`); + throw new Chat.ErrorMessage(`This user is already blacklisted from ${room.roomid}.`); } const name = targetUser.getLastName(); const userid = targetUser.getLastId(); @@ -777,7 +772,7 @@ export const commands: Chat.ChatCommands = { ); } } else if (force) { - return this.errorReply(`Use /${week ? 'week' : 'room'}ban; ${name} is not a trusted user.`); + throw new Chat.ErrorMessage(`Use /${week ? 'week' : 'room'}ban; ${name} is not a trusted user.`); } if (!reason && !week && Punishments.isRoomBanned(targetUser, room.roomid)) { const problem = " but was already banned"; @@ -847,7 +842,7 @@ export const commands: Chat.ChatCommands = { this.globalModlog("UNROOMBAN", name); } } else { - this.errorReply(`User '${target}' is not banned from this room.`); + throw new Chat.ErrorMessage(`User '${target}' is not banned from this room.`); } }, unbanhelp: [`/unban [username] - Unbans the user from the room you are in. Requires: @ # ~`], @@ -874,12 +869,12 @@ export const commands: Chat.ChatCommands = { let userid: ID = toID(targetUsername); if (!targetUser && !Punishments.search(userid).length && !force) { - return this.errorReply( + throw new Chat.ErrorMessage( `User '${targetUsername}' not found. Use \`\`/force${month ? 'month' : (week ? 'week' : '')}lock\`\` if you need to to lock them anyway.` ); } if (reason.length > MAX_REASON_LENGTH) { - return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); + throw new Chat.ErrorMessage(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } this.checkCan('lock', userid); if (month) this.checkCan('rangeban'); @@ -907,7 +902,7 @@ export const commands: Chat.ChatCommands = { return this.sendReply(`${name} is a trusted user. If you are sure you would like to lock them use /force${month ? 'month' : (week ? 'week' : '')}lock.`); } } else if (force && targetUser) { - return this.errorReply(`Use /lock; ${name} is not a trusted user and is online.`); + throw new Chat.ErrorMessage(`Use /lock; ${name} is not a trusted user and is online.`); } const { privateReason, publicReason } = this.parseSpoiler(reason); @@ -985,7 +980,7 @@ export const commands: Chat.ChatCommands = { const targetUser = Users.get(target); if (targetUser?.namelocked) { - return this.errorReply(`User ${targetUser.name} is namelocked, not locked. Use /unnamelock to unnamelock them.`); + throw new Chat.ErrorMessage(`User ${targetUser.name} is namelocked, not locked. Use /unnamelock to unnamelock them.`); } let reason = ''; if (targetUser?.locked && targetUser.locked.startsWith('#')) { @@ -999,7 +994,7 @@ export const commands: Chat.ChatCommands = { if (!reason) this.globalModlog("UNLOCK", toID(target)); if (targetUser) targetUser.popup(`${user.name} has unlocked you.`); } else { - this.errorReply(`User '${target}' is not locked.`); + throw new Chat.ErrorMessage(`User '${target}' is not locked.`); } }, unlockname(target, room, user) { @@ -1008,12 +1003,12 @@ export const commands: Chat.ChatCommands = { const userid = toID(target); if (userid.startsWith('guest')) { - return this.errorReply(`You cannot unlock the guest userid - provide their original username instead.`); + throw new Chat.ErrorMessage(`You cannot unlock the guest userid - provide their original username instead.`); } const punishment = Punishments.userids.getByType(userid, 'LOCK') || Punishments.userids.getByType(userid, 'NAMELOCK'); - if (!punishment) return this.errorReply("This name isn't locked."); + if (!punishment) throw new Chat.ErrorMessage("This name isn't locked."); if (punishment.id === userid || Users.get(userid)?.previousIDs.includes(punishment.id as ID)) { - return this.errorReply(`"${userid}" was specifically locked by a staff member (check the global modlog). Use /unlock if you really want to unlock this name.`); + throw new Chat.ErrorMessage(`"${userid}" was specifically locked by a staff member (check the global modlog). Use /unlock if you really want to unlock this name.`); } Punishments.userids.delete(userid); Punishments.savePunishments(); @@ -1041,11 +1036,11 @@ export const commands: Chat.ChatCommands = { if (range) this.checkCan('rangeban'); if (!(range ? IPTools.ipRangeRegex : IPTools.ipRegex).test(target)) { - return this.errorReply("Please enter a valid IP address."); + throw new Chat.ErrorMessage("Please enter a valid IP address."); } const punishment = Punishments.ips.get(target); - if (!punishment) return this.errorReply(`${target} is not a locked/banned IP or IP range.`); + if (!punishment) throw new Chat.ErrorMessage(`${target} is not a locked/banned IP or IP range.`); Punishments.ips.delete(target); Punishments.savePunishments(); @@ -1086,13 +1081,13 @@ export const commands: Chat.ChatCommands = { let userid: ID = toID(targetUsername); if (!targetUser && !force) { - return this.errorReply(`User '${targetUsername}' not found. Use /forceglobalban to ban them anyway.`); + throw new Chat.ErrorMessage(`User '${targetUsername}' not found. Use /forceglobalban to ban them anyway.`); } if (reason.length > MAX_REASON_LENGTH) { - return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); + throw new Chat.ErrorMessage(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } if (!reason && REQUIRE_REASONS) { - return this.errorReply("Global bans require a reason."); + throw new Chat.ErrorMessage("Global bans require a reason."); } this.checkCan('globalban', targetUser); let name; @@ -1168,7 +1163,7 @@ export const commands: Chat.ChatCommands = { const name = Punishments.unban(target); if (!name) { - return this.errorReply(`User '${target}' is not globally banned.`); + throw new Chat.ErrorMessage(`User '${target}' is not globally banned.`); } this.addGlobalModAction(`${name} was globally unbanned by ${user.name}.`); @@ -1179,12 +1174,11 @@ export const commands: Chat.ChatCommands = { deroomvoiceall(target, room, user) { room = this.requireRoom(); this.checkCan('editroom', null, room); - if (!room.auth.size) return this.errorReply("Room does not have roomauth."); + if (!room.auth.size) throw new Chat.ErrorMessage("Room does not have roomauth."); if (!target) { user.lastCommand = '/deroomvoiceall'; - this.errorReply("THIS WILL DEROOMVOICE ALL ROOMVOICED USERS."); - this.errorReply("To confirm, use: /deroomvoiceall confirm"); - return; + throw new Chat.ErrorMessage(["THIS WILL DEROOMVOICE ALL ROOMVOICED USERS.", + "To confirm, use: /deroomvoiceall confirm"]); } if (user.lastCommand !== '/deroomvoiceall' || target !== 'confirm') { return this.parse('/help deroomvoiceall'); @@ -1263,7 +1257,7 @@ export const commands: Chat.ChatCommands = { banip(target, room, user, connection, cmd) { const [ip, reason] = this.splitOne(target); if (!ip || !/^[0-9.]+(?:\.\*)?$/.test(ip)) return this.parse('/help banip'); - if (!reason) return this.errorReply("/banip requires a ban reason"); + if (!reason) throw new Chat.ErrorMessage("/banip requires a ban reason"); this.checkCan('rangeban'); const ipDesc = `IP ${(ip.endsWith('*') ? `range ` : ``)}${ip}`; @@ -1273,7 +1267,7 @@ export const commands: Chat.ChatCommands = { const curPunishment = Punishments.ipSearch(ip, 'BAN'); if (curPunishment?.type === 'BAN' && !time) { - return this.errorReply(`The ${ipDesc} is already temporarily banned.`); + throw new Chat.ErrorMessage(`The ${ipDesc} is already temporarily banned.`); } Punishments.punishRange(ip, reason, time, 'BAN'); @@ -1304,7 +1298,7 @@ export const commands: Chat.ChatCommands = { } this.checkCan('rangeban'); if (!Punishments.ips.has(target)) { - return this.errorReply(`${target} is not a locked/banned IP or IP range.`); + throw new Chat.ErrorMessage(`${target} is not a locked/banned IP or IP range.`); } Punishments.ips.delete(target); @@ -1324,11 +1318,11 @@ export const commands: Chat.ChatCommands = { const targetUser = Users.get(targetUsername); const targetUserid = toID(targetUsername); if (!targetUserid || targetUserid.length > 18) { - return this.errorReply(`Invalid userid.`); + throw new Chat.ErrorMessage(`Invalid userid.`); } const force = this.cmd.includes('force'); if (targetUser?.registered && !force) { - return this.errorReply(`That user is registered. Either permalock them normally or use /forceyearlockname.`); + throw new Chat.ErrorMessage(`That user is registered. Either permalock them normally or use /forceyearlockname.`); } const punishment = { type: 'YEARLOCK', @@ -1351,7 +1345,7 @@ export const commands: Chat.ChatCommands = { lockip(target, room, user, connection, cmd) { const [ip, reason] = this.splitOne(target); if (!ip || !/^[0-9.]+(?:\.\*)?$/.test(ip)) return this.parse('/help lockip'); - if (!reason) return this.errorReply("/lockip requires a lock reason"); + if (!reason) throw new Chat.ErrorMessage("/lockip requires a lock reason"); this.checkCan('rangeban'); const ipDesc = ip.endsWith('*') ? `IP range ${ip}` : `IP ${ip}`; @@ -1360,7 +1354,7 @@ export const commands: Chat.ChatCommands = { const curPunishment = Punishments.byWeight(Punishments.ipSearch(ip) || [])[0]; if (!year && curPunishment && (curPunishment.type === 'BAN' || curPunishment.type === 'LOCK')) { const punishDesc = curPunishment.type === 'BAN' ? `temporarily banned` : `temporarily locked`; - return this.errorReply(`The ${ipDesc} is already ${punishDesc}.`); + throw new Chat.ErrorMessage(`The ${ipDesc} is already ${punishDesc}.`); } const time = year ? Date.now() + 365 * 24 * 60 * 60 * 1000 : null; @@ -1393,7 +1387,7 @@ export const commands: Chat.ChatCommands = { this.checkChat(); if (target.length > MAX_REASON_LENGTH) { - return this.errorReply(`The note is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); + throw new Chat.ErrorMessage(`The note is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } this.checkCan('receiveauthmessages', null, room); target = target.replace(/\n/g, "; "); @@ -1446,42 +1440,40 @@ export const commands: Chat.ChatCommands = { let nextGroup = nextGroupName as GroupSymbol; if (nextGroupName === 'deauth') nextGroup = Users.Auth.defaultSymbol(); if (!nextGroup) { - return this.errorReply("Please specify a group such as /globalvoice or /globaldeauth"); + throw new Chat.ErrorMessage("Please specify a group such as /globalvoice or /globaldeauth"); } if (!Config.groups[nextGroup]) { - return this.errorReply(`Group '${nextGroup}' does not exist.`); + throw new Chat.ErrorMessage(`Group '${nextGroup}' does not exist.`); } if (!cmd.startsWith('global')) { let groupid = Config.groups[nextGroup].id; if (!groupid && nextGroup === Users.Auth.defaultSymbol()) groupid = 'deauth' as ID; - if (Config.groups[nextGroup].globalonly) return this.errorReply(`Did you mean "/global${groupid}"?`); - if (Config.groups[nextGroup].roomonly) return this.errorReply(`Did you mean "/room${groupid}"?`); - return this.errorReply(`Did you mean "/room${groupid}" or "/global${groupid}"?`); + if (Config.groups[nextGroup].globalonly) throw new Chat.ErrorMessage(`Did you mean "/global${groupid}"?`); + if (Config.groups[nextGroup].roomonly) throw new Chat.ErrorMessage(`Did you mean "/room${groupid}"?`); + throw new Chat.ErrorMessage(`Did you mean "/room${groupid}" or "/global${groupid}"?`); } if (Config.groups[nextGroup].roomonly || Config.groups[nextGroup].battleonly) { - return this.errorReply(`Group '${nextGroup}' does not exist as a global rank.`); + throw new Chat.ErrorMessage(`Group '${nextGroup}' does not exist as a global rank.`); } const groupName = Config.groups[nextGroup].name || "regular user"; if (currentGroup === nextGroup) { - return this.errorReply(`User '${name}' is already a ${groupName}`); + throw new Chat.ErrorMessage(`User '${name}' is already a ${groupName}`); } if (!Users.Auth.hasPermission(user, 'promote', currentGroup)) { - this.errorReply(`/${cmd} - Access denied for promoting from ${currentGroup}`); - this.errorReply(`You can only promote to/from: ${Users.Auth.listJurisdiction(user, 'promote')}`); - return; + throw new Chat.ErrorMessage([`/${cmd} - Access denied for promoting from ${currentGroup}`, + `You can only promote to/from: ${Users.Auth.listJurisdiction(user, 'promote')}`]); } if (!Users.Auth.hasPermission(user, 'promote', nextGroup)) { - this.errorReply(`/${cmd} - Access denied for promoting to ${groupName}`); - this.errorReply(`You can only promote to/from: ${Users.Auth.listJurisdiction(user, 'promote')}`); - return; + throw new Chat.ErrorMessage([`/${cmd} - Access denied for promoting to ${groupName}`, + `You can only promote to/from: ${Users.Auth.listJurisdiction(user, 'promote')}`]); } if (!Users.isUsernameKnown(userid)) { - return this.errorReply(`/globalpromote - WARNING: '${name}' is offline and unrecognized. The username might be misspelled (either by you or the person who told you) or unregistered. Use /forcepromote if you're sure you want to risk it.`); + throw new Chat.ErrorMessage(`/globalpromote - WARNING: '${name}' is offline and unrecognized. The username might be misspelled (either by you or the person who told you) or unregistered. Use /forcepromote if you're sure you want to risk it.`); } if (targetUser && !targetUser.registered) { - return this.errorReply(`User '${name}' is unregistered, and so can't be promoted.`); + throw new Chat.ErrorMessage(`User '${name}' is unregistered, and so can't be promoted.`); } if (nextGroup === Users.Auth.defaultSymbol()) { Users.globalAuth.delete(targetUser ? targetUser.id : userid); @@ -1520,7 +1512,7 @@ export const commands: Chat.ChatCommands = { const force = cmd.includes('force'); const untrust = cmd.includes('un'); const { targetUser, targetUsername, rest } = this.splitUser(target, { exactName: true }); - if (rest) return this.errorReply(`This command does not support specifying a reason.`); + if (rest) throw new Chat.ErrorMessage(`This command does not support specifying a reason.`); const userid = toID(targetUsername); const name = targetUser?.name || targetUsername; @@ -1528,15 +1520,15 @@ export const commands: Chat.ChatCommands = { if (untrust) { if (currentGroup !== Users.Auth.defaultSymbol()) { - return this.errorReply(`User '${name}' is trusted indirectly through global rank ${currentGroup}. Demote them from that rank to remove trusted status.`); + throw new Chat.ErrorMessage(`User '${name}' is trusted indirectly through global rank ${currentGroup}. Demote them from that rank to remove trusted status.`); } const trustedSourceRooms = Rooms.global.chatRooms .filter(authRoom => authRoom.persist && authRoom.settings.isPrivate !== true && authRoom.auth.isStaff(userid)) .map(authRoom => authRoom.auth.get(userid) + authRoom.roomid).join(' '); if (trustedSourceRooms.length && !Users.globalAuth.has(userid)) { - return this.errorReply(`User '${name}' is trusted indirectly through room ranks ${trustedSourceRooms}. Demote them from those ranks to remove trusted status.`); + throw new Chat.ErrorMessage(`User '${name}' is trusted indirectly through room ranks ${trustedSourceRooms}. Demote them from those ranks to remove trusted status.`); } - if (!Users.globalAuth.has(userid)) return this.errorReply(`User '${name}' is not trusted.`); + if (!Users.globalAuth.has(userid)) throw new Chat.ErrorMessage(`User '${name}' is not trusted.`); if (targetUser) { targetUser.setGroup(Users.Auth.defaultSymbol()); @@ -1547,11 +1539,11 @@ export const commands: Chat.ChatCommands = { this.privateGlobalModAction(`${name} was set to no longer be a trusted user by ${user.name}.`); this.globalModlog('UNTRUSTUSER', userid); } else { - if (!targetUser && !force) return this.errorReply(`User '${name}' is offline. Use /force${cmd} if you're sure.`); + if (!targetUser && !force) throw new Chat.ErrorMessage(`User '${name}' is offline. Use /force${cmd} if you're sure.`); if (currentGroup) { if (Users.globalAuth.has(userid)) { - if (currentGroup === Users.Auth.defaultSymbol()) return this.errorReply(`User '${name}' is already trusted.`); - return this.errorReply(`User '${name}' has a global rank higher than trusted.`); + if (currentGroup === Users.Auth.defaultSymbol()) throw new Chat.ErrorMessage(`User '${name}' is already trusted.`); + throw new Chat.ErrorMessage(`User '${name}' has a global rank higher than trusted.`); } } if (targetUser) { @@ -1630,13 +1622,13 @@ export const commands: Chat.ChatCommands = { if (!name) return; name = name.slice(0, 18); const nextGroup = nextGroupName as GroupSymbol; - if (!Config.groups[nextGroup]) return this.errorReply(`Group '${nextGroup}' does not exist.`); + if (!Config.groups[nextGroup]) throw new Chat.ErrorMessage(`Group '${nextGroup}' does not exist.`); if (Config.groups[nextGroup].roomonly || Config.groups[nextGroup].battleonly) { - return this.errorReply(`Group '${nextGroup}' does not exist as a global rank.`); + throw new Chat.ErrorMessage(`Group '${nextGroup}' does not exist as a global rank.`); } if (Users.isUsernameKnown(name)) { - return this.errorReply("/forcepromote - Don't forcepromote unless you have to."); + throw new Chat.ErrorMessage("/forcepromote - Don't forcepromote unless you have to."); } Users.globalAuth.set(name as ID, nextGroup); @@ -1669,7 +1661,7 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); this.checkCan('declare', null, room); this.checkChat(); - if (target.length > 2000) return this.errorReply("Declares should not exceed 2000 characters."); + if (target.length > 2000) throw new Chat.ErrorMessage("Declares should not exceed 2000 characters."); for (const id in room.users) { room.users[id].sendTo(room, `|notify|${room.title} announcement!|${target}`); @@ -1745,7 +1737,7 @@ export const commands: Chat.ChatCommands = { this.checkChat(); let [rank, titleNotification] = this.splitOne(target); if (rank === 'all') rank = ` `; - if (!(rank in Config.groups)) return this.errorReply(`Group '${rank}' does not exist.`); + if (!(rank in Config.groups)) throw new Chat.ErrorMessage(`Group '${rank}' does not exist.`); const id = `${room.roomid}-rank-${(Config.groups[rank].id || `all`)}`; if (cmd === 'notifyoffrank') { if (rank === ' ') { @@ -1760,7 +1752,7 @@ export const commands: Chat.ChatCommands = { title += ` (notification from ${user.name})`; } const [notification, highlight] = this.splitOne(notificationHighlight); - if (notification.length > 300) return this.errorReply(`Notifications should not exceed 300 characters.`); + if (notification.length > 300) throw new Chat.ErrorMessage(`Notifications should not exceed 300 characters.`); const message = `|tempnotify|${id}|${title}|${notification}${(highlight ? `|${highlight}` : ``)}`; if (rank === ' ') { room.send(message); @@ -1782,7 +1774,7 @@ export const commands: Chat.ChatCommands = { this.checkCan('addhtml', null, room); this.checkChat(); const { targetUser, targetUsername, rest: titleNotification } = this.splitUser(target); - if (!targetUser?.connected) return this.errorReply(`User '${targetUsername}' not found.`); + if (!targetUser?.connected) throw new Chat.ErrorMessage(`User '${targetUsername}' not found.`); const id = `${room.roomid}-user-${toID(targetUsername)}`; if (cmd === 'notifyoffuser') { room.sendUser(targetUser, `|tempnotifyoff|${id}`); @@ -1793,7 +1785,7 @@ export const commands: Chat.ChatCommands = { if (!user.can('addhtml')) { title += ` (notification from ${user.name})`; } - if (notification.length > 300) return this.errorReply(`Notifications should not exceed 300 characters.`); + if (notification.length > 300) throw new Chat.ErrorMessage(`Notifications should not exceed 300 characters.`); const message = `|tempnotify|${id}|${title}|${notification}`; room.sendUser(targetUser, message); this.sendReply(`Sent a notification to ${targetUser.name}.`); @@ -1817,16 +1809,16 @@ export const commands: Chat.ChatCommands = { if (!targetUser && !offline) { const { targetUser: targetUserInexact, inputUsername } = this.splitUser(target); if (targetUserInexact) { - return this.errorReply(`User has already changed their name to '${targetUserInexact.name}'.`); + throw new Chat.ErrorMessage(`User has already changed their name to '${targetUserInexact.name}'.`); } - return this.errorReply(`User '${inputUsername}' not found. (use /offlineforcerename to rename anyway.)`); + throw new Chat.ErrorMessage(`User '${inputUsername}' not found. (use /offlineforcerename to rename anyway.)`); } if (Punishments.namefilterwhitelist.has(targetID)) { - this.errorReply(`That name is blocked from being forcerenamed.`); + const errorMessage = [`That name is blocked from being forcerenamed.`]; if (user.can('bypassall')) { - this.errorReply(`Use /noforcerename remove to remove it from the list if you wish to rename it.`); + errorMessage.push(`Use /noforcerename remove to remove it from the list if you wish to rename it.`); } - return false; + throw new Chat.ErrorMessage(errorMessage); } this.checkCan('forcerename', targetID); const { publicReason, privateReason } = this.parseSpoiler(reason); @@ -1877,7 +1869,7 @@ export const commands: Chat.ChatCommands = { if (!targetId) return this.parse('/help noforcerename'); this.checkCan('bypassall'); if (!Punishments.whitelistName(targetId, user.name)) { - return this.errorReply(`${targetUsername} is already on the noforcerename list.`); + throw new Chat.ErrorMessage(`${targetUsername} is already on the noforcerename list.`); } this.addGlobalModAction(`${user.name} added the name ${targetId} to the no forcerename list.${rest ? ` (${rest})` : ''}`); this.globalModlog('NOFORCERENAME', targetId, rest); @@ -1888,7 +1880,7 @@ export const commands: Chat.ChatCommands = { if (!targetId) return this.parse('/help noforcerename'); this.checkCan('bypassall'); if (!Punishments.namefilterwhitelist.has(targetId)) { - return this.errorReply(`${targetUsername} is not on the noforcerename list.`); + throw new Chat.ErrorMessage(`${targetUsername} is not on the noforcerename list.`); } Punishments.unwhitelistName(targetId); this.addGlobalModAction(`${user.name} removed ${targetId} from the no forcerename list.${rest ? ` (${rest})` : ''}`); @@ -1904,7 +1896,7 @@ export const commands: Chat.ChatCommands = { const { targetUser, rest: reason } = this.requireUser(target, { allowOffline: true }); this.checkCan('forcerename', targetUser); - if (!targetUser.userMessage) return this.errorReply(this.tr`${targetUser.name} does not have a status set.`); + if (!targetUser.userMessage) throw new Chat.ErrorMessage(this.tr`${targetUser.name} does not have a status set.`); const displayReason = reason ? `: ${reason}` : ``; this.privateGlobalModAction(this.tr`${targetUser.name}'s status "${targetUser.userMessage}" was cleared by ${user.name}${displayReason}.`); @@ -1928,24 +1920,26 @@ export const commands: Chat.ChatCommands = { const userid = toID(targetUsername); if (!targetUser && !force) { - return this.errorReply( + throw new Chat.ErrorMessage( `User '${targetUsername}' not found. Use \`\`/force${week ? 'week' : ''}namelock\`\` if you need to namelock them anyway.` ); } if (targetUser && targetUser.id !== toID(inputUsername) && !force) { - return this.errorReply(`${inputUsername} has already changed their name to ${targetUser.name}. To namelock anyway, use /force${week ? 'week' : ''}namelock.`); + throw new Chat.ErrorMessage(`${inputUsername} has already changed their name to ${targetUser.name}. To namelock anyway, use /force${week ? 'week' : ''}namelock.`); } this.checkCan('forcerename', userid); if (targetUser?.namelocked && !week) { - return this.errorReply(`User '${targetUser.name}' is already namelocked.`); + throw new Chat.ErrorMessage(`User '${targetUser.name}' is already namelocked.`); } if (!force && !week) { const existingPunishments = Punishments.search(userid); for (const [,, punishment] of existingPunishments) { if (punishment.type === 'LOCK' && (punishment.expireTime - Date.now()) > (2 * DAY)) { - this.errorReply(`User '${userid}' is already normally locked for more than 2 days.`); - this.errorReply(`Use /weeknamelock to namelock them instead, so you don't decrease the existing punishment.`); - return this.errorReply(`If you really need to override this, use /forcenamelock.`); + throw new Chat.ErrorMessage([ + `User '${userid}' is already normally locked for more than 2 days.`, + `Use /weeknamelock to namelock them instead, so you don't decrease the existing punishment.`, + `If you really need to override this, use /forcenamelock.`, + ]); } } } @@ -1997,7 +1991,7 @@ export const commands: Chat.ChatCommands = { const unlocked = Punishments.unnamelock(target); if (!unlocked) { - return this.errorReply(`User '${target}' is not namelocked.`); + throw new Chat.ErrorMessage(`User '${target}' is not namelocked.`); } this.addGlobalModAction(`${unlocked} was unnamelocked by ${user.name}.${reason}`); @@ -2032,22 +2026,22 @@ export const commands: Chat.ChatCommands = { [lineCountString, reason] = Utils.splitFirst(reason, ',').map(p => p.trim()); lineCount = parseInt(lineCountString); } else if (!cmd.includes('force')) { - return this.errorReply(`Your reason was a number; use /hidelines if you wanted to clear a specific number of lines, or /forcehidetext if you really wanted your reason to be a number.`); + throw new Chat.ErrorMessage(`Your reason was a number; use /hidelines if you wanted to clear a specific number of lines, or /forcehidetext if you really wanted your reason to be a number.`); } } const showAlts = cmd.includes('alt'); if (!lineCount && hasLineCount) { - return this.errorReply(`You must specify a number of messages to clear. To clear all messages, use /hidetext.`); + throw new Chat.ErrorMessage(`You must specify a number of messages to clear. To clear all messages, use /hidetext.`); } if (reason.length > MAX_REASON_LENGTH) { - return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); + throw new Chat.ErrorMessage(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } if (!targetUser && !room.log.hasUsername(name)) { - return this.errorReply(`User ${name} not found or has no roomlogs.`); + throw new Chat.ErrorMessage(`User ${name} not found or has no roomlogs.`); } if (lineCount && showAlts) { - return this.errorReply(`You can't specify a line count when using /hidealtstext.`); + throw new Chat.ErrorMessage(`You can't specify a line count when using /hidealtstext.`); } const userid = toID(inputUsername); @@ -2099,20 +2093,20 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); if (!target) return this.parse('/help blacklist'); this.checkChat(); - if (toID(target) === 'show') return this.errorReply(`You're looking for /showbl`); + if (toID(target) === 'show') throw new Chat.ErrorMessage(`You're looking for /showbl`); const { targetUser, targetUsername, rest: reason } = this.splitUser(target); if (!targetUser) { - this.errorReply(`User ${targetUsername} not found.`); - return this.errorReply(`If you want to blacklist an offline account by name (not IP), consider /blacklistname`); + throw new Chat.ErrorMessage([`User ${targetUsername} not found.`, + `If you want to blacklist an offline account by name (not IP), consider /blacklistname`]); } this.checkCan('editroom', targetUser, room); if (!room.persist) { - return this.errorReply(`This room is not going to last long enough for a blacklist to matter - just ban the user`); + throw new Chat.ErrorMessage(`This room is not going to last long enough for a blacklist to matter - just ban the user`); } const punishment = Punishments.isRoomBanned(targetUser, room.roomid); if (punishment && punishment.type === 'BLACKLIST') { - return this.errorReply(`This user is already blacklisted from this room.`); + throw new Chat.ErrorMessage(`This user is already blacklisted from this room.`); } const force = cmd === 'forceblacklist' || cmd === 'forcebl'; if (targetUser.trusted) { @@ -2122,13 +2116,13 @@ export const commands: Chat.ChatCommands = { ); } } else if (force) { - return this.errorReply(`Use /blacklist; ${targetUser.name} is not a trusted user.`); + throw new Chat.ErrorMessage(`Use /blacklist; ${targetUser.name} is not a trusted user.`); } if (!reason && REQUIRE_REASONS) { - return this.errorReply(`Blacklists require a reason.`); + throw new Chat.ErrorMessage(`Blacklists require a reason.`); } if (reason.length > MAX_REASON_LENGTH) { - return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); + throw new Chat.ErrorMessage(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } const name = targetUser.getLastName(); const userid = targetUser.getLastId(); @@ -2189,29 +2183,30 @@ export const commands: Chat.ChatCommands = { if (!target) return this.parse(`/help battleban`); const { targetUser, targetUsername, rest: reason } = this.splitUser(target); - if (!targetUser) return this.errorReply(`User ${targetUsername} not found.`); + if (!targetUser) throw new Chat.ErrorMessage(`User ${targetUsername} not found.`); if (target.length > MAX_REASON_LENGTH) { - return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); + throw new Chat.ErrorMessage(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } if (!reason) { - return this.errorReply(`Battle bans require a reason.`); + throw new Chat.ErrorMessage(`Battle bans require a reason.`); } const includesUrl = reason.includes(`.${Config.routes.root}/`); // lgtm [js/incomplete-url-substring-sanitization] if (!room.battle && !includesUrl && cmd !== 'forcebattleban') { - return this.errorReply(`Battle bans require a battle replay if used outside of a battle; if the battle has expired, use /forcebattleban.`); + throw new Chat.ErrorMessage(`Battle bans require a battle replay if used outside of a battle; if the battle has expired, use /forcebattleban.`); } if (!user.can('rangeban', targetUser)) { - this.errorReply(`Battlebans have been deprecated. Alternatives:`); - this.errorReply(`- timerstalling and bragging about it: lock`); - this.errorReply(`- other timerstalling: they're not timerstalling, leave them alone`); - this.errorReply(`- bad nicknames: lock, locks prevent nicknames from appearing; you should always have been locking for this`); - this.errorReply(`- ladder cheating: gban, get a moderator if necessary`); - this.errorReply(`- serious ladder cheating: permaban, get an administrator`); - this.errorReply(`- other: get an administrator`); - return; + throw new Chat.ErrorMessage([ + `Battlebans have been deprecated. Alternatives:`, + `- timerstalling and bragging about it: lock`, + `- other timerstalling: they're not timerstalling, leave them alone`, + `- bad nicknames: lock, locks prevent nicknames from appearing; you should always have been locking for this`, + `- ladder cheating: gban, get a moderator if necessary`, + `- serious ladder cheating: permaban, get an administrator`, + `- other: get an administrator`, + ]); } if (Punishments.isBattleBanned(targetUser)) { - return this.errorReply(`User '${targetUser.name}' is already banned from battling.`); + throw new Chat.ErrorMessage(`User '${targetUser.name}' is already banned from battling.`); } this.privateGlobalModAction(`${targetUser.name} was banned from starting new battles by ${user.name} (${reason})`); @@ -2245,7 +2240,7 @@ export const commands: Chat.ChatCommands = { this.globalModlog("UNBATTLEBAN", toID(target)); if (targetUser) targetUser.popup(`${user.name} has allowed you to battle again.`); } else { - this.errorReply(`User ${target} is not banned from battling.`); + throw new Chat.ErrorMessage(`User ${target} is not banned from battling.`); } }, unbattlebanhelp: [`/unbattleban [username] - [DEPRECATED] Allows a user to battle again. Requires: % @ ~`], @@ -2257,22 +2252,20 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); if (!target) return this.parse(`/help groupchatban`); if (!user.can('rangeban')) { - return this.errorReply( - `/groupchatban has been deprecated.\n` + - `For future groupchat misuse, lock the creator, it will take away their trusted status and their ability to make groupchats.` - ); + throw new Chat.ErrorMessage([`/groupchatban has been deprecated.`, + `For future groupchat misuse, lock the creator, it will take away their trusted status and their ability to make groupchats.`]); } const { targetUser, targetUsername, rest: reason } = this.splitUser(target); - if (!targetUser) return this.errorReply(`User ${targetUsername} not found.`); + if (!targetUser) throw new Chat.ErrorMessage(`User ${targetUsername} not found.`); if (target.length > MAX_REASON_LENGTH) { - return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); + throw new Chat.ErrorMessage(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } const isMonth = cmd.startsWith('month'); if (!isMonth && Punishments.isGroupchatBanned(targetUser)) { - return this.errorReply(`User '${targetUser.name}' is already banned from using groupchats.`); + throw new Chat.ErrorMessage(`User '${targetUser.name}' is already banned from using groupchats.`); } const reasonText = reason ? `: ${reason}` : ``; @@ -2330,7 +2323,7 @@ export const commands: Chat.ChatCommands = { this.globalModlog("UNGROUPCHATBAN", toID(target), ` by ${user.id}`); if (targetUser) targetUser.popup(`${user.name} has allowed you to use groupchats again.`); } else { - this.errorReply(`User ${target} is not banned from using groupchats.`); + throw new Chat.ErrorMessage(`User ${target} is not banned from using groupchats.`); } }, ungroupchatbanhelp: [`/ungroupchatban [user] - Allows a groupchatbanned user to use groupchats again. Requires: % @ ~`], @@ -2343,12 +2336,12 @@ export const commands: Chat.ChatCommands = { this.checkChat(); this.checkCan('editroom', null, room); if (!room.persist) { - return this.errorReply("This room is not going to last long enough for a blacklist to matter - just ban the user"); + throw new Chat.ErrorMessage("This room is not going to last long enough for a blacklist to matter - just ban the user"); } const [targetStr, reason] = target.split('|').map(val => val.trim()); if (!targetStr || (!reason && REQUIRE_REASONS)) { - return this.errorReply("Usage: /blacklistname name1, name2, ... | reason"); + throw new Chat.ErrorMessage("Usage: /blacklistname name1, name2, ... | reason"); } const targets = targetStr.split(',').map(s => toID(s)); @@ -2358,15 +2351,15 @@ export const commands: Chat.ChatCommands = { Punishments.roomUserids.nestedGetByType(room.roomid, userid, 'BLACKLIST') )); if (duplicates.length) { - return this.errorReply(`[${duplicates.join(', ')}] ${Chat.plural(duplicates, "are", "is")} already blacklisted.`); + throw new Chat.ErrorMessage(`[${duplicates.join(', ')}] ${Chat.plural(duplicates, "are", "is")} already blacklisted.`); } const expireTime = this.cmd.includes('perma') ? Date.now() + (10 * 365 * 24 * 60 * 60 * 1000) : null; const action = expireTime ? 'PERMANAMEBLACKLIST' : 'NAMEBLACKLIST'; for (const userid of targets) { - if (!userid) return this.errorReply(`User '${userid}' is not a valid userid.`); + if (!userid) throw new Chat.ErrorMessage(`User '${userid}' is not a valid userid.`); if (!Users.Auth.hasPermission(user, 'ban', room.auth.get(userid), room)) { - return this.errorReply(`/blacklistname - Access denied: ${userid} is of equal or higher authority than you.`); + throw new Chat.ErrorMessage(`/blacklistname - Access denied: ${userid} is of equal or higher authority than you.`); } Punishments.roomBlacklist(room, userid, expireTime, null, reason); @@ -2405,7 +2398,7 @@ export const commands: Chat.ChatCommands = { this.globalModlog("UNBLACKLIST", name); } } else { - this.errorReply(`User '${target}' is not blacklisted.`); + throw new Chat.ErrorMessage(`User '${target}' is not blacklisted.`); } }, unblacklisthelp: [`/unblacklist [username] - Unblacklists the user from the room you are in. Requires: # ~`], @@ -2416,16 +2409,15 @@ export const commands: Chat.ChatCommands = { if (!target) { user.lastCommand = '/unblacklistall'; - this.errorReply("THIS WILL UNBLACKLIST ALL BLACKLISTED USERS IN THIS ROOM."); - this.errorReply("To confirm, use: /unblacklistall confirm"); - return; + throw new Chat.ErrorMessage(["THIS WILL UNBLACKLIST ALL BLACKLISTED USERS IN THIS ROOM.", + "To confirm, use: /unblacklistall confirm"]); } if (user.lastCommand !== '/unblacklistall' || target !== 'confirm') { return this.parse('/help unblacklistall'); } user.lastCommand = ''; const unblacklisted = Punishments.roomUnblacklistAll(room); - if (!unblacklisted) return this.errorReply("No users are currently blacklisted in this room to unblacklist."); + if (!unblacklisted) throw new Chat.ErrorMessage("No users are currently blacklisted in this room to unblacklist."); this.addModAction(`All blacklists in this room have been lifted by ${user.name}.`); this.modlog('UNBLACKLISTALL'); this.roomlog(`Unblacklisted users: ${unblacklisted.join(', ')}`); @@ -2438,11 +2430,11 @@ export const commands: Chat.ChatCommands = { showbl: 'showblacklist', showblacklist(target, room, user, connection, cmd) { if (target) room = Rooms.search(target)!; - if (!room) return this.errorReply(`The room "${target}" was not found.`); + if (!room) throw new Chat.ErrorMessage(`The room "${target}" was not found.`); this.checkCan('mute', null, room); const SOON_EXPIRING_TIME = 3 * 30 * 24 * 60 * 60 * 1000; // 3 months - if (!room.persist) return this.errorReply("This room does not support blacklists."); + if (!room.persist) throw new Chat.ErrorMessage("This room does not support blacklists."); const roomUserids = Punishments.roomUserids.get(room.roomid); if (!roomUserids || roomUserids.size === 0) { diff --git a/server/chat-commands/room-settings.ts b/server/chat-commands/room-settings.ts index bd59e4c097..ca3ab47d35 100644 --- a/server/chat-commands/room-settings.ts +++ b/server/chat-commands/room-settings.ts @@ -38,7 +38,7 @@ export const commands: Chat.ChatCommands = { roomsetting: 'roomsettings', roomsettings(target, room, user, connection) { room = this.requireRoom(); - if (room.battle) return this.errorReply("This command cannot be used in battle rooms."); + if (room.battle) throw new Chat.ErrorMessage("This command cannot be used in battle rooms."); let uhtml = 'uhtml'; if (!target) { @@ -81,7 +81,7 @@ export const commands: Chat.ChatCommands = { return this.sendReply(`Moderated chat is currently set to: ${modchatSetting}`); } if (user.locked) { // would put this below but it behaves weird if there's no modchat set - return this.errorReply(`/modchat - Access denied.`); + throw new Chat.ErrorMessage(`/modchat - Access denied.`); } else { this.checkCan('modchat', null, room); } @@ -92,16 +92,16 @@ export const commands: Chat.ChatCommands = { // Upper Staff should probably be able to set /modchat ~ in secret rooms !user.can('bypassall') ) { - return this.errorReply(`/modchat - Access denied for changing a setting currently at ${room.settings.modchat}.`); + throw new Chat.ErrorMessage(`/modchat - Access denied for changing a setting currently at ${room.settings.modchat}.`); } if ((room as any).requestModchat) { const error = (room as GameRoom).requestModchat(user); - if (error) return this.errorReply(error); + if (error) throw new Chat.ErrorMessage(error); } // only admins can force modchat on a forced public battle if (room.battle?.forcedSettings.modchat && !user.can('rangeban')) { - return this.errorReply( + throw new Chat.ErrorMessage( `This battle is required to have modchat on due to one of the players having a username that starts with ` + `${room.battle.forcedSettings.modchat}.` ); @@ -134,13 +134,13 @@ export const commands: Chat.ChatCommands = { // Users shouldn't be able to set modchat above their own rank (except for ROs who are also Upper Staff) const modchatLevelHigherThanUserRank = !room.auth.atLeast(user, target) && !user.can('bypassall'); if (modchatLevelHigherThanUserRank || !Users.Auth.hasPermission(user, 'modchat', target as GroupSymbol, room)) { - return this.errorReply(`/modchat - Access denied for setting to ${target}.`); + throw new Chat.ErrorMessage(`/modchat - Access denied for setting to ${target}.`); } room.settings.modchat = target; break; } if (currentModchat === room.settings.modchat) { - return this.errorReply(`Modchat is already set to ${currentModchat || 'off'}.`); + throw new Chat.ErrorMessage(`Modchat is already set to ${currentModchat || 'off'}.`); } if (!room.settings.modchat) { this.add("|raw|
Moderated chat was disabled!
Anyone may talk now.
"); @@ -167,7 +167,7 @@ export const commands: Chat.ChatCommands = { } this.checkCan('declare', null, room); if (this.meansNo(toID(target))) { - if (!room.settings.autoModchat) return this.errorReply(`Auto modchat is not set.`); + if (!room.settings.autoModchat) throw new Chat.ErrorMessage(`Auto modchat is not set.`); delete room.settings.autoModchat; room.saveSettings(); if (room.modchatTimer) clearTimeout(room.modchatTimer); // fallback just in case (should never happen) @@ -187,11 +187,11 @@ export const commands: Chat.ChatCommands = { } const validGroups = [...Config.groupsranking as string[], 'trusted', 'autoconfirmed']; if (!validGroups.includes(rank)) { - return this.errorReply(`Invalid rank.`); + throw new Chat.ErrorMessage(`Invalid rank.`); } const time = parseInt(rawTime); if (isNaN(time) || time > 480 || time < 5) { - return this.errorReply("Invalid duration. Choose a number under 480 (in minutes) and over 5 minutes."); + throw new Chat.ErrorMessage("Invalid duration. Choose a number under 480 (in minutes) and over 5 minutes."); } room.settings.autoModchat = { rank, time, active: false, @@ -207,17 +207,21 @@ export const commands: Chat.ChatCommands = { ], ionext() { - this.errorReply(`"ionext" is an outdated feature. Hidden battles now have password-protected URLs, making them fully secure against eavesdroppers.`); - this.errorReply(`You probably want to switch from /ionext to /hidenext, and from /ioo to /hideroom`); + throw new Chat.ErrorMessage([ + `"ionext" is an outdated feature. Hidden battles now have password-protected URLs, making them fully secure against eavesdroppers.`, + `You probably want to switch from /ionext to /hidenext, and from /ioo to /hideroom`, + ]); }, ioo() { - this.errorReply(`"ioo" is an outdated feature. Hidden battles now have password-protected URLs, making them fully secure against eavesdroppers.`); - this.errorReply(`You probably want to switch from /ioo to /hideroom`); + throw new Chat.ErrorMessage([ + `"ioo" is an outdated feature. Hidden battles now have password-protected URLs, making them fully secure against eavesdroppers.`, + `You probably want to switch from /ioo to /hideroom`, + ]); }, inviteonlynext(target, room, user) { const groupConfig = Config.groups[Users.PLAYER_SYMBOL]; - if (!groupConfig?.editprivacy) return this.errorReply(`/ionext - Access denied.`); + if (!groupConfig?.editprivacy) throw new Chat.ErrorMessage(`/ionext - Access denied.`); if (this.meansNo(target)) { user.battleSettings.inviteOnly = false; user.update(); @@ -258,12 +262,12 @@ export const commands: Chat.ChatCommands = { if (room.battle) { this.checkCan('editprivacy', null, room); if (room.battle.forcedSettings.privacy) { - return this.errorReply( + throw new Chat.ErrorMessage( `This battle is required to be public due to a player having a name prefixed by '${room.battle.forcedSettings.privacy}'.` ); } if (room.battle.inviteOnlySetter && !user.can('mute', null, room) && room.battle.inviteOnlySetter !== user.id) { - return this.errorReply(`Only the person who set this battle to be invite-only can turn it off.`); + throw new Chat.ErrorMessage(`Only the person who set this battle to be invite-only can turn it off.`); } room.battle.inviteOnlySetter = user.id; } else if (room.settings.isPersonal) { @@ -272,11 +276,11 @@ export const commands: Chat.ChatCommands = { this.checkCan('makeroom'); } if (room.tour && !room.tour.allowModjoin) { - return this.errorReply(`You can't do this in tournaments where modjoin is prohibited.`); + throw new Chat.ErrorMessage(`You can't do this in tournaments where modjoin is prohibited.`); } if (target === 'player') target = Users.PLAYER_SYMBOL; if (this.meansNo(target)) { - if (!room.settings.modjoin) return this.errorReply(`Modjoin is already turned off in this room.`); + if (!room.settings.modjoin) throw new Chat.ErrorMessage(`Modjoin is already turned off in this room.`); room.settings.modjoin = null; this.add(`|raw|
This room is no longer invite only!
Anyone may now join.
`); this.addModAction(`${user.name} turned off modjoin.`); @@ -285,25 +289,25 @@ export const commands: Chat.ChatCommands = { room.saveSettings(); return; } else if (target === 'sync') { - if (room.settings.modjoin === true) return this.errorReply(`Modjoin is already set to sync modchat in this room.`); + if (room.settings.modjoin === true) throw new Chat.ErrorMessage(`Modjoin is already set to sync modchat in this room.`); room.settings.modjoin = true; this.add(`|raw|
Moderated join is set to sync with modchat!
Only users who can speak in modchat can join.
`); this.addModAction(`${user.name} set modjoin to sync with modchat.`); this.modlog('MODJOIN SYNC'); } else if (target === 'ac' || target === 'autoconfirmed') { - if (room.settings.modjoin === 'autoconfirmed') return this.errorReply(`Modjoin is already set to autoconfirmed.`); + if (room.settings.modjoin === 'autoconfirmed') throw new Chat.ErrorMessage(`Modjoin is already set to autoconfirmed.`); room.settings.modjoin = 'autoconfirmed'; this.add(`|raw|
Moderated join is set to autoconfirmed!
Users must be rank autoconfirmed or invited with /invite to join
`); this.addModAction(`${user.name} set modjoin to autoconfirmed.`); this.modlog('MODJOIN', null, 'autoconfirmed'); } else if (Users.Auth.isAuthLevel(target) && !['‽', '!'].includes(target)) { if (room.battle && !user.can('makeroom') && !'+%'.includes(target)) { - return this.errorReply(`/modjoin - Access denied from setting modjoin past % in battles.`); + throw new Chat.ErrorMessage(`/modjoin - Access denied from setting modjoin past % in battles.`); } if (room.settings.isPersonal && !user.can('makeroom') && !'+%'.includes(target)) { - return this.errorReply(`/modjoin - Access denied from setting modjoin past % in group chats.`); + throw new Chat.ErrorMessage(`/modjoin - Access denied from setting modjoin past % in group chats.`); } - if (room.settings.modjoin === target) return this.errorReply(`Modjoin is already set to ${target} in this room.`); + if (room.settings.modjoin === target) throw new Chat.ErrorMessage(`Modjoin is already set to ${target} in this room.`); room.settings.modjoin = target; this.add(`|raw|
This room is now invite only!
Users must be rank ${target} or invited with /invite to join
`); this.addModAction(`${user.name} set modjoin to ${target}.`); @@ -340,7 +344,7 @@ export const commands: Chat.ChatCommands = { this.checkCan('editroom', null, room); const targetLanguage = toID(target); - if (!Chat.languages.has(targetLanguage)) return this.errorReply(`"${target}" is not a supported language.`); + if (!Chat.languages.has(targetLanguage)) throw new Chat.ErrorMessage(`"${target}" is not a supported language.`); room.settings.language = targetLanguage === 'english' ? false : targetLanguage; @@ -364,14 +368,14 @@ export const commands: Chat.ChatCommands = { let targetInt = parseInt(target); if (this.meansNo(target)) { - if (!room.settings.slowchat) return this.errorReply(`Slow chat is already disabled in this room.`); + if (!room.settings.slowchat) throw new Chat.ErrorMessage(`Slow chat is already disabled in this room.`); room.settings.slowchat = false; } else if (targetInt) { if (!user.can('bypassall') && room.userCount < SLOWCHAT_USER_REQUIREMENT) { - return this.errorReply(`This room must have at least ${SLOWCHAT_USER_REQUIREMENT} users to set slowchat; it only has ${room.userCount} right now.`); + throw new Chat.ErrorMessage(`This room must have at least ${SLOWCHAT_USER_REQUIREMENT} users to set slowchat; it only has ${room.userCount} right now.`); } if (room.settings.slowchat === targetInt) { - return this.errorReply(`Slow chat is already set to ${room.settings.slowchat} seconds in this room.`); + throw new Chat.ErrorMessage(`Slow chat is already set to ${room.settings.slowchat} seconds in this room.`); } if (targetInt < SLOWCHAT_MINIMUM) targetInt = SLOWCHAT_MINIMUM; if (targetInt > SLOWCHAT_MAXIMUM) targetInt = SLOWCHAT_MAXIMUM; @@ -397,10 +401,10 @@ export const commands: Chat.ChatCommands = { let rank = displayRank; if (rank === 'default') rank = ''; if (rank === 'all users') rank = Users.Auth.defaultSymbol(); - if (!room.persist) return this.errorReply(`This room does not allow customizing permissions.`); + if (!room.persist) throw new Chat.ErrorMessage(`This room does not allow customizing permissions.`); if (!target || !perm) return this.parse(`/permissions help`); if (rank && rank !== 'whitelist' && !Config.groupsranking.includes(rank as EffectiveGroupSymbol)) { - return this.errorReply(`${rank} is not a valid rank.`); + throw new Chat.ErrorMessage(`${rank} is not a valid rank.`); } const validPerms = Users.Auth.supportedRoomPermissions(room); const sanitizedPerm = perm.replace('!', '/'); // handles ! commands so we don't have to add commands to the array twice @@ -409,21 +413,21 @@ export const commands: Chat.ChatCommands = { p === sanitizedPerm || p === perm || p.startsWith(`${sanitizedPerm} `) || p.startsWith(`${perm} `) ))) { - return this.errorReply(`${perm} is not a valid room permission.`); + throw new Chat.ErrorMessage(`${perm} is not a valid room permission.`); } if (!room.auth.atLeast(user, '#')) { - return this.errorReply(`/permissions set - You must be at least a Room Owner to set permissions.`); + throw new Chat.ErrorMessage(`/permissions set - You must be at least a Room Owner to set permissions.`); } if ( Users.Auth.ROOM_PERMISSIONS.includes(perm as RoomPermission) && !Users.Auth.hasPermission(user, perm, null, room) ) { - return this.errorReply(`/permissions set - You can't set the permission "${perm}" because you don't have it.`); + throw new Chat.ErrorMessage(`/permissions set - You can't set the permission "${perm}" because you don't have it.`); } const currentPermissions = room.settings.permissions || {}; if (currentPermissions[perm] === (rank || undefined)) { - return this.errorReply(`${perm} is already set to ${displayRank || 'default'}.`); + throw new Chat.ErrorMessage(`${perm} is already set to ${displayRank || 'default'}.`); } if (rank) { @@ -503,10 +507,10 @@ export const commands: Chat.ChatCommands = { this.checkCan('editroom', null, room); if (this.meansYes(target)) { - if (room.settings.filterStretching) return this.errorReply(`This room's stretch filter is already ON`); + if (room.settings.filterStretching) throw new Chat.ErrorMessage(`This room's stretch filter is already ON`); room.settings.filterStretching = true; } else if (this.meansNo(target)) { - if (!room.settings.filterStretching) return this.errorReply(`This room's stretch filter is already OFF`); + if (!room.settings.filterStretching) throw new Chat.ErrorMessage(`This room's stretch filter is already OFF`); room.settings.filterStretching = false; } else { return this.parse("/help stretchfilter"); @@ -532,10 +536,10 @@ export const commands: Chat.ChatCommands = { this.checkCan('editroom', null, room); if (this.meansYes(target)) { - if (room.settings.filterCaps) return this.errorReply(`This room's caps filter is already ON`); + if (room.settings.filterCaps) throw new Chat.ErrorMessage(`This room's caps filter is already ON`); room.settings.filterCaps = true; } else if (this.meansNo(target)) { - if (!room.settings.filterCaps) return this.errorReply(`This room's caps filter is already OFF`); + if (!room.settings.filterCaps) throw new Chat.ErrorMessage(`This room's caps filter is already OFF`); room.settings.filterCaps = false; } else { return this.parse("/help capsfilter"); @@ -560,10 +564,10 @@ export const commands: Chat.ChatCommands = { this.checkCan('editroom', null, room); if (this.meansYes(target)) { - if (room.settings.filterEmojis) return this.errorReply(`This room's emoji filter is already ON`); + if (room.settings.filterEmojis) throw new Chat.ErrorMessage(`This room's emoji filter is already ON`); room.settings.filterEmojis = true; } else if (this.meansNo(target)) { - if (!room.settings.filterEmojis) return this.errorReply(`This room's emoji filter is already OFF`); + if (!room.settings.filterEmojis) throw new Chat.ErrorMessage(`This room's emoji filter is already OFF`); room.settings.filterEmojis = false; } else { return this.parse("/help emojifilter"); @@ -587,10 +591,10 @@ export const commands: Chat.ChatCommands = { this.checkCan('editroom', null, room); if (this.meansYes(target)) { - if (room.settings.filterLinks) return this.errorReply(`This room's link filter is already ON`); + if (room.settings.filterLinks) throw new Chat.ErrorMessage(`This room's link filter is already ON`); room.settings.filterLinks = true; } else if (this.meansNo(target)) { - if (!room.settings.filterLinks) return this.errorReply(`This room's link filter is already OFF`); + if (!room.settings.filterLinks) throw new Chat.ErrorMessage(`This room's link filter is already OFF`); room.settings.filterLinks = false; } else { return this.parse("/help linkfilter"); @@ -613,7 +617,7 @@ export const commands: Chat.ChatCommands = { this.checkCan('declare', null, room); const regex = cmd.includes('regex'); - if (regex && !user.can('makeroom')) return this.errorReply("Regex banwords are only allowed for administrators."); + if (regex && !user.can('makeroom')) throw new Chat.ErrorMessage("Regex banwords are only allowed for administrators."); if (!room.settings.banwords) room.settings.banwords = []; // Most of the regex code is copied from the client. TODO: unify them? // Regex banwords can have commas in the {1,5} pattern @@ -634,7 +638,7 @@ export const commands: Chat.ChatCommands = { let banwordRegexLen = (room.banwordRegex instanceof RegExp) ? room.banwordRegex.source.length : 32; for (const word of words) { Chat.validateRegex(word); - if (room.settings.banwords.includes(word)) return this.errorReply(`${word} is already a banned phrase.`); + if (room.settings.banwords.includes(word)) throw new Chat.ErrorMessage(`${word} is already a banned phrase.`); // Banword strings are joined, so account for the first string not having the prefix banwordRegexLen += (banwordRegexLen === 32) ? word.length : `|${word}`.length; @@ -643,7 +647,7 @@ export const commands: Chat.ChatCommands = { // the server on compile. In this case, that would happen each // time a chat message gets tested for any banned phrases. if (banwordRegexLen >= (1 << 16 - 1)) { - return this.errorReply("This room has too many banned phrases to add the ones given."); + throw new Chat.ErrorMessage("This room has too many banned phrases to add the ones given."); } } @@ -669,7 +673,7 @@ export const commands: Chat.ChatCommands = { if (!target) return this.parse('/help banword'); this.checkCan('declare', null, room); - if (!room.settings.banwords) return this.errorReply("This room has no banned phrases."); + if (!room.settings.banwords) throw new Chat.ErrorMessage("This room has no banned phrases."); const regexMatch = target.match(/[^,]+(,\d*}[^,]*)?/g); if (!regexMatch) return this.parse('/help banword'); @@ -677,7 +681,7 @@ export const commands: Chat.ChatCommands = { const words = regexMatch.map(word => word.replace(/\n/g, '').trim()).filter(word => word.length > 0); for (const word of words) { - if (!room.settings.banwords.includes(word)) return this.errorReply(`${word} is not a banned phrase in this room.`); + if (!room.settings.banwords.includes(word)) throw new Chat.ErrorMessage(`${word} is not a banned phrase in this room.`); } room.settings.banwords = room.settings.banwords.filter(w => !words.includes(w)); @@ -729,11 +733,11 @@ export const commands: Chat.ChatCommands = { return this.sendReply(`Approvals are currently ${room.settings.requestShowEnabled ? `ENABLED` : `DISABLED`} for ${room}.`); } if (this.meansNo(target)) { - if (!room.settings.requestShowEnabled) return this.errorReply(`Approvals are already disabled.`); + if (!room.settings.requestShowEnabled) throw new Chat.ErrorMessage(`Approvals are already disabled.`); room.settings.requestShowEnabled = undefined; this.privateModAction(`${user.name} disabled approvals in this room.`); } else if (this.meansYes(target)) { - if (room.settings.requestShowEnabled) return this.errorReply(`Approvals are already enabled.`); + if (room.settings.requestShowEnabled) throw new Chat.ErrorMessage(`Approvals are already enabled.`); room.settings.requestShowEnabled = true; this.privateModAction(`${user.name} enabled the use of media approvals in this room.`); if (!room.settings.permissions || room.settings.permissions['/show'] === '@') { @@ -743,7 +747,7 @@ export const commands: Chat.ChatCommands = { ); } } else { - return this.errorReply(`Unrecognized setting for approvals. Use 'on' or 'off'.`); + throw new Chat.ErrorMessage(`Unrecognized setting for approvals. Use 'on' or 'off'.`); } room.saveSettings(); return this.modlog(`SHOWAPPROVALS`, null, `${this.meansYes(target) ? `ON` : `OFF`}`); @@ -793,7 +797,7 @@ export const commands: Chat.ChatCommands = { const id = toID(target); if (!id || this.cmd === 'makechatroom') return this.parse('/help makechatroom'); if (!Rooms.global.addChatRoom(target)) { - return this.errorReply(`The room '${target}' already exists or it is using an invalid title.`); + throw new Chat.ErrorMessage(`The room '${target}' already exists or it is using an invalid title.`); } const targetRoom = Rooms.search(target); @@ -830,21 +834,21 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); this.checkChat(); if (!user.trusted) { - return this.errorReply("You must be trusted (public room driver or global voice) to make a groupchat."); + throw new Chat.ErrorMessage("You must be trusted (public room driver or global voice) to make a groupchat."); } const groupchatbanned = Punishments.isGroupchatBanned(user); if (groupchatbanned) { const expireText = Punishments.checkPunishmentExpiration(groupchatbanned); - return this.errorReply(`You are banned from using groupchats ${expireText}.`); + throw new Chat.ErrorMessage(`You are banned from using groupchats ${expireText}.`); } if (cmd === 'subroomgroupchat' || cmd === 'srgc') { if (!user.can('mute', null, room)) { - return this.errorReply("You can only create subroom groupchats for rooms you're staff in."); + throw new Chat.ErrorMessage("You can only create subroom groupchats for rooms you're staff in."); } - if (room.battle) return this.errorReply("You cannot create a subroom of a battle."); - if (room.settings.isPersonal) return this.errorReply("You cannot create a subroom of a groupchat."); + if (room.battle) throw new Chat.ErrorMessage("You cannot create a subroom of a battle."); + if (room.settings.isPersonal) throw new Chat.ErrorMessage("You cannot create a subroom of a groupchat."); } const parent = cmd === 'subroomgroupchat' || cmd === 'srgc' ? room.roomid : null; // this.checkCan('makegroupchat'); @@ -852,24 +856,24 @@ export const commands: Chat.ChatCommands = { // Title defaults to a random 8-digit number. let title = target.trim(); if (title.length >= 32) { - return this.errorReply("Title must be under 32 characters long."); + throw new Chat.ErrorMessage("Title must be under 32 characters long."); } else if (!title) { title = (`${Math.floor(Math.random() * 100000000)}`); } else if (this.filter(title) !== title) { - return this.errorReply("Invalid title."); + throw new Chat.ErrorMessage("Invalid title."); } // `,` is a delimiter used by a lot of /commands // `|` and `[` are delimiters used by the protocol // `-` has special meaning in roomids if (title.includes(',') || title.includes('|') || title.includes('[') || title.includes('-')) { - return this.errorReply("Room titles can't contain any of: ,|[-"); + throw new Chat.ErrorMessage("Room titles can't contain any of: ,|[-"); } // Even though they're different namespaces, to cut down on confusion, you // can't share names with registered chatrooms. const existingRoom = Rooms.search(toID(title)); if (existingRoom && !existingRoom.settings.modjoin) { - return this.errorReply(`Your group chat name is too similar to existing chat room '${title}'.`); + throw new Chat.ErrorMessage(`Your group chat name is too similar to existing chat room '${title}'.`); } // Room IDs for groupchats are groupchat-TITLEID let titleid = toID(title); @@ -878,13 +882,12 @@ export const commands: Chat.ChatCommands = { } const roomid = `groupchat-${parent || user.id}-${titleid}` as RoomID; // Titles must be unique. - if (Rooms.search(roomid)) return this.errorReply(`A group chat named '${title}' already exists.`); + if (Rooms.search(roomid)) throw new Chat.ErrorMessage(`A group chat named '${title}' already exists.`); // Tab title is prefixed with '[G]' to distinguish groupchats from // registered chatrooms if (Monitor.countGroupChat(connection.ip)) { - this.errorReply("Due to high load, you are limited to creating 4 group chats every hour."); - return; + throw new Chat.ErrorMessage("Due to high load, you are limited to creating 4 group chats every hour."); } const titleMsg = Utils.html`Welcome to ${parent ? room.title : user.name}'s` + @@ -902,7 +905,7 @@ export const commands: Chat.ChatCommands = { `

Groupchats are temporary rooms, and will expire if there hasn't been any activity in 40 minutes.

You can invite new users using /invite. Be careful with who you invite!

Commands: | |

As creator of this groupchat, you are entirely responsible for what occurs in this chatroom. Global rules apply at all times.

If this room is used to break global rules or disrupt other areas of the server, you as the creator will be held accountable and punished.

`, }); if (!targetRoom) { - return this.errorReply(`An unknown error occurred while trying to create the room '${title}'.`); + throw new Chat.ErrorMessage(`An unknown error occurred while trying to create the room '${title}'.`); } // The creator is a Room Owner in subroom groupchats and a Host otherwise.. targetRoom.auth.set(user.id, parent ? '#' : Users.HOST_SYMBOL); @@ -919,7 +922,7 @@ export const commands: Chat.ChatCommands = { groupchatuptime: 'roomuptime', roomuptime(target, room, user, connection, cmd) { if (!this.runBroadcast()) return; - if (!room) return this.errorReply(`Can only be used in a room.`); + if (!room) throw new Chat.ErrorMessage(`Can only be used in a room.`); // for hotpatching if (!room.settings.creationTime) room.settings.creationTime = Date.now(); @@ -934,7 +937,7 @@ export const commands: Chat.ChatCommands = { const id = toID(target); if (!id) return this.parse('/help deregisterchatroom'); const targetRoom = Rooms.search(id); - if (!targetRoom) return this.errorReply(`The room '${target}' doesn't exist.`); + if (!targetRoom) throw new Chat.ErrorMessage(`The room '${target}' doesn't exist.`); target = targetRoom.title || targetRoom.roomid; const isPrivate = targetRoom.settings.isPrivate; const staffRoom = Rooms.get('staff'); @@ -957,7 +960,7 @@ export const commands: Chat.ChatCommands = { } return; } - return this.errorReply(`The room "${target}" isn't registered.`); + throw new Chat.ErrorMessage(`The room "${target}" isn't registered.`); }, deregisterchatroomhelp: [ `/deregisterchatroom [roomname] - Deletes room [roomname] after the next server restart. Requires: ~`, @@ -1024,36 +1027,36 @@ export const commands: Chat.ChatCommands = { ], rename() { - this.errorReply("Did you mean /renameroom?"); + throw new Chat.ErrorMessage("Did you mean /renameroom?"); }, renamegroupchat: 'renameroom', renameroom(target, room, user, connection, cmd) { room = this.requireRoom(); if (room.game || room.minorActivity || room.tour) { - return this.errorReply("Cannot rename room while a tour/poll/game is running."); + throw new Chat.ErrorMessage("Cannot rename room while a tour/poll/game is running."); } if (room.battle) { - return this.errorReply("Cannot rename battle rooms."); + throw new Chat.ErrorMessage("Cannot rename battle rooms."); } const oldTitle = room.title; const isGroupchat = cmd === 'renamegroupchat'; if (!toID(target)) return this.parse("/help renameroom"); - if (room.persist && isGroupchat) return this.errorReply(`This isn't a groupchat.`); - if (!room.persist && !isGroupchat) return this.errorReply(`Use /renamegroupchat instead.`); + if (room.persist && isGroupchat) throw new Chat.ErrorMessage(`This isn't a groupchat.`); + if (!room.persist && !isGroupchat) throw new Chat.ErrorMessage(`Use /renamegroupchat instead.`); if (isGroupchat) { if (!user.can('lock')) this.checkCan('declare', null, room); const existingRoom = Rooms.search(toID(target)); if (existingRoom && !existingRoom.settings.modjoin) { - return this.errorReply(`Your groupchat name is too similar to existing chat room '${existingRoom.title}'.`); + throw new Chat.ErrorMessage(`Your groupchat name is too similar to existing chat room '${existingRoom.title}'.`); } if (this.filter(target) !== target) { - return this.errorReply("Invalid title."); + throw new Chat.ErrorMessage("Invalid title."); } // `,` is a delimiter used by a lot of /commands // `|` and `[` are delimiters used by the protocol // `-` has special meaning in roomids if (target.includes(',') || target.includes('|') || target.includes('[') || target.includes('-')) { - return this.errorReply("Room titles can't contain any of: ,|[-"); + throw new Chat.ErrorMessage("Room titles can't contain any of: ,|[-"); } } else { this.checkCan('makeroom'); @@ -1092,10 +1095,10 @@ export const commands: Chat.ChatCommands = { if (battle) { this.checkCan('editprivacy', null, room); if (battle.forcedSettings.privacy) { - return this.errorReply(`This battle is required to be public because a player has a name prefixed by '${battle.forcedSettings.privacy}'.`); + throw new Chat.ErrorMessage(`This battle is required to be public because a player has a name prefixed by '${battle.forcedSettings.privacy}'.`); } if (room.tour?.forcePublic) { - return this.errorReply(`This battle can't be hidden, because the tournament is set to be forced public.`); + throw new Chat.ErrorMessage(`This battle can't be hidden, because the tournament is set to be forced public.`); } } else if (room.settings.isPersonal) { this.checkCan('editroom', null, room); @@ -1127,23 +1130,23 @@ export const commands: Chat.ChatCommands = { break; } if (this.meansNo(target)) { - return this.errorReply(`Please specify what privacy setting you want for this room: /hiddenroom, /secretroom, or /publicroom`); + throw new Chat.ErrorMessage(`Please specify what privacy setting you want for this room: /hiddenroom, /secretroom, or /publicroom`); } if (!setting) { if (!room.settings.isPrivate) { - return this.errorReply(`This room is already public.`); + throw new Chat.ErrorMessage(`This room is already public.`); } if (room.parent?.settings.isPrivate) { - return this.errorReply(`This room's parent ${room.parent.title} must be public for this room to be public.`); + throw new Chat.ErrorMessage(`This room's parent ${room.parent.title} must be public for this room to be public.`); } if (room.settings.isPersonal && !battle) { - return this.errorReply(`This room can't be made public.`); + throw new Chat.ErrorMessage(`This room can't be made public.`); } if (room.privacySetter && user.can('nooverride', null, room) && !user.can('makeroom')) { if (!room.privacySetter.has(user.id)) { const privacySetters = [...room.privacySetter].join(', '); - return this.errorReply(`You can't make the room public since you didn't make it private - only ${privacySetters} can.`); + throw new Chat.ErrorMessage(`You can't make the room public since you didn't make it private - only ${privacySetters} can.`); } room.privacySetter.delete(user.id); if (room.privacySetter.size) { @@ -1158,10 +1161,10 @@ export const commands: Chat.ChatCommands = { } else { const settingName = (setting === true ? 'secret' : setting); if (room.subRooms && !room.bestOf) { - if (settingName === 'secret') return this.errorReply("Secret rooms cannot have subrooms."); + if (settingName === 'secret') throw new Chat.ErrorMessage("Secret rooms cannot have subrooms."); for (const subRoom of room.subRooms.values()) { if (!subRoom.settings.isPrivate) { - return this.errorReply(`Subroom ${subRoom.title} must be private to make this room private.`); + throw new Chat.ErrorMessage(`Subroom ${subRoom.title} must be private to make this room private.`); } } } @@ -1170,7 +1173,7 @@ export const commands: Chat.ChatCommands = { room.privacySetter.add(user.id); return this.sendReply(`This room is already ${settingName}, but is now forced to stay that way until you use /publicroom.`); } - return this.errorReply(`This room is already ${settingName}.`); + throw new Chat.ErrorMessage(`This room is already ${settingName}.`); } this.addModAction(`${user.name} made this room ${settingName}.`); this.modlog(`${settingName.toUpperCase()}ROOM`); @@ -1187,7 +1190,7 @@ export const commands: Chat.ChatCommands = { hidenext(target, room, user) { const groupConfig = Config.groups[Users.PLAYER_SYMBOL]; - if (!groupConfig?.editprivacy) return this.errorReply(`/hidenext - Access denied.`); + if (!groupConfig?.editprivacy) throw new Chat.ErrorMessage(`/hidenext - Access denied.`); if (this.meansNo(target)) { user.battleSettings.hidden = false; user.update(); @@ -1213,16 +1216,16 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); if (!target) return this.parse(`/help roomspotlight`); if (!room.persist) { - return this.errorReply(`/roomspotlight - You can't spotlight this room.`); + throw new Chat.ErrorMessage(`/roomspotlight - You can't spotlight this room.`); } if (this.meansNo(target)) { - if (!room.settings.spotlight) return this.errorReply(`This chatroom is not being spotlighted.`); + if (!room.settings.spotlight) throw new Chat.ErrorMessage(`This chatroom is not being spotlighted.`); this.addModAction(`${user.name} removed this chatroom from the spotlight.`); this.globalModlog('UNSPOTLIGHT'); delete room.settings.spotlight; room.saveSettings(); } else { - if (room.settings.spotlight === target) return this.errorReply("This chat room is already spotlighted."); + if (room.settings.spotlight === target) throw new Chat.ErrorMessage("This chat room is already spotlighted."); this.addModAction(`${user.name} spotlighted this room with the message "${target}".`); this.globalModlog('SPOTLIGHT'); room.settings.spotlight = target; @@ -1237,28 +1240,28 @@ export const commands: Chat.ChatCommands = { setsubroom: 'subroom', subroom(target, room, user) { room = this.requireRoom(); - if (!user.can('makeroom')) return this.errorReply(`/subroom - Access denied. Did you mean /subrooms?`); + if (!user.can('makeroom')) throw new Chat.ErrorMessage(`/subroom - Access denied. Did you mean /subrooms?`); if (!target) return this.parse('/help subroom'); - if (!room.persist) return this.errorReply(`Temporary rooms cannot be subrooms.`); + if (!room.persist) throw new Chat.ErrorMessage(`Temporary rooms cannot be subrooms.`); if (room.parent) { - return this.errorReply(`This room is already a subroom. To change which room this subroom belongs to, remove the subroom first.`); + throw new Chat.ErrorMessage(`This room is already a subroom. To change which room this subroom belongs to, remove the subroom first.`); } if (room.subRooms) { - return this.errorReply(`This room is already a parent room, and a parent room cannot be made as a subroom.`); + throw new Chat.ErrorMessage(`This room is already a parent room, and a parent room cannot be made as a subroom.`); } const parent = Rooms.search(target); - if (!parent) return this.errorReply(`The room '${target}' does not exist.`); - if (parent.type !== 'chat') return this.errorReply(`Parent room '${target}' must be a chat room.`); - if (parent.parent) return this.errorReply(`Subrooms cannot have subrooms.`); - if (parent.settings.isPrivate === true) return this.errorReply(`Only public and hidden rooms can have subrooms.`); + if (!parent) throw new Chat.ErrorMessage(`The room '${target}' does not exist.`); + if (parent.type !== 'chat') throw new Chat.ErrorMessage(`Parent room '${target}' must be a chat room.`); + if (parent.parent) throw new Chat.ErrorMessage(`Subrooms cannot have subrooms.`); + if (parent.settings.isPrivate === true) throw new Chat.ErrorMessage(`Only public and hidden rooms can have subrooms.`); if (parent.settings.isPrivate && !room.settings.isPrivate) { - return this.errorReply(`Private rooms cannot have public subrooms.`); + throw new Chat.ErrorMessage(`Private rooms cannot have public subrooms.`); } - if (!parent.persist) return this.errorReply(`Temporary rooms cannot be parent rooms.`); - if (room === parent) return this.errorReply(`You cannot set a room to be a subroom of itself.`); + if (!parent.persist) throw new Chat.ErrorMessage(`Temporary rooms cannot be parent rooms.`); + if (room === parent) throw new Chat.ErrorMessage(`You cannot set a room to be a subroom of itself.`); const settingsList = Rooms.global.settingsList; @@ -1282,7 +1285,7 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); this.checkCan('makeroom'); if (!room.parent || !room.persist) { - return this.errorReply(`This room is not currently a subroom of a public room.`); + throw new Chat.ErrorMessage(`This room is not currently a subroom of a public room.`); } room.setParent(null); @@ -1296,10 +1299,10 @@ export const commands: Chat.ChatCommands = { subrooms(target, room, user, connection, cmd) { room = this.requireRoom(); if (cmd === 'parentroom') { - if (!room.parent) return this.errorReply(`This room is not a subroom.`); + if (!room.parent) throw new Chat.ErrorMessage(`This room is not a subroom.`); return this.sendReply(`This is a subroom of ${room.parent.title}.`); } - if (!room.persist) return this.errorReply(`Temporary rooms cannot have subrooms.`); + if (!room.persist) throw new Chat.ErrorMessage(`Temporary rooms cannot have subrooms.`); if (!this.runBroadcast()) return; @@ -1334,18 +1337,18 @@ export const commands: Chat.ChatCommands = { } this.checkCan('makeroom'); if (target.length > 80) { - return this.errorReply(`Error: Room description is too long (must be at most 80 characters).`); + throw new Chat.ErrorMessage(`Error: Room description is too long (must be at most 80 characters).`); } const normalizedTarget = ' ' + target.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim() + ' '; if (normalizedTarget.includes(' welcome ')) { - return this.errorReply(`Error: Room description must not contain the word "welcome".`); + throw new Chat.ErrorMessage(`Error: Room description must not contain the word "welcome".`); } if (normalizedTarget.startsWith(' discuss ')) { - return this.errorReply(`Error: Room description must not start with the word "discuss".`); + throw new Chat.ErrorMessage(`Error: Room description must not start with the word "discuss".`); } if (normalizedTarget.startsWith(' talk about ') || normalizedTarget.startsWith(' talk here about ')) { - return this.errorReply(`Error: Room description must not start with the phrase "talk about".`); + throw new Chat.ErrorMessage(`Error: Room description must not start with the phrase "talk about".`); } room.settings.desc = target; @@ -1371,7 +1374,7 @@ export const commands: Chat.ChatCommands = { return; } this.checkCan('editroom', null, room); - if (this.meansNo(target) || target === 'delete') return this.errorReply('Did you mean "/deleteroomintro"?'); + if (this.meansNo(target) || target === 'delete') throw new Chat.ErrorMessage('Did you mean "/deleteroomintro"?'); this.checkHTML(target); if (!target.includes("<")) { // not HTML, do some simple URL linking @@ -1399,7 +1402,7 @@ export const commands: Chat.ChatCommands = { deleteroomintro(target, room, user) { room = this.requireRoom(); this.checkCan('declare', null, room); - if (!room.settings.introMessage) return this.errorReply("This room does not have a introduction set."); + if (!room.settings.introMessage) throw new Chat.ErrorMessage("This room does not have a introduction set."); this.privateModAction(`${user.name} deleted the roomintro.`); this.modlog('DELETEROOMINTRO'); @@ -1424,7 +1427,7 @@ export const commands: Chat.ChatCommands = { } this.checkCan('ban', null, room); this.checkChat(); - if (this.meansNo(target) || target === 'delete') return this.errorReply('Did you mean "/deletestaffintro"?'); + if (this.meansNo(target) || target === 'delete') throw new Chat.ErrorMessage('Did you mean "/deletestaffintro"?'); this.checkHTML(target); if (!target.includes("<")) { // not HTML, do some simple URL linking @@ -1448,7 +1451,7 @@ export const commands: Chat.ChatCommands = { deletestaffintro(target, room, user) { room = this.requireRoom(); this.checkCan('ban', null, room); - if (!room.settings.staffMessage) return this.errorReply("This room does not have a staff introduction set."); + if (!room.settings.staffMessage) throw new Chat.ErrorMessage("This room does not have a staff introduction set."); this.privateModAction(`${user.name} deleted the staffintro.`); this.modlog('DELETESTAFFINTRO'); @@ -1472,11 +1475,11 @@ export const commands: Chat.ChatCommands = { } const alias = toID(target); - if (!alias.length) return this.errorReply("Only alphanumeric characters are valid in an alias."); + if (!alias.length) throw new Chat.ErrorMessage("Only alphanumeric characters are valid in an alias."); if (Rooms.get(alias) || Rooms.aliases.has(alias)) { - return this.errorReply("You cannot set an alias to an existing room or alias."); + throw new Chat.ErrorMessage("You cannot set an alias to an existing room or alias."); } - if (room.settings.isPersonal) return this.errorReply("Personal rooms can't have aliases."); + if (room.settings.isPersonal) throw new Chat.ErrorMessage("Personal rooms can't have aliases."); Rooms.aliases.set(alias, room.roomid); this.privateModAction(`${user.name} added the room alias '${alias}'.`); @@ -1497,7 +1500,7 @@ export const commands: Chat.ChatCommands = { unroomalias: 'removeroomalias', removeroomalias(target, room, user) { room = this.requireRoom(); - if (!room.settings.aliases) return this.errorReply("This room does not have any aliases."); + if (!room.settings.aliases) throw new Chat.ErrorMessage("This room does not have any aliases."); this.checkCan('makeroom'); if (target.includes(',')) { this.errorReply(`Invalid room alias: ${target.trim()}`); @@ -1505,9 +1508,9 @@ export const commands: Chat.ChatCommands = { } const alias = toID(target); - if (!alias || !Rooms.aliases.has(alias)) return this.errorReply("Please specify an existing alias."); + if (!alias || !Rooms.aliases.has(alias)) throw new Chat.ErrorMessage("Please specify an existing alias."); if (Rooms.aliases.get(alias) !== room.roomid) { - return this.errorReply("You may only remove an alias from the current room."); + throw new Chat.ErrorMessage("You may only remove an alias from the current room."); } this.privateModAction(`${user.name} removed the room alias '${alias}'.`); diff --git a/server/chat-plugins/abuse-monitor.ts b/server/chat-plugins/abuse-monitor.ts index 4131602f0f..fd60beff3a 100644 --- a/server/chat-plugins/abuse-monitor.ts +++ b/server/chat-plugins/abuse-monitor.ts @@ -836,15 +836,15 @@ export const commands: Chat.ChatCommands = { for (let k in args) { const vals = args[k]; if (vals.length > 1) { - return this.errorReply(`Too many values for ${k}`); + throw new Chat.ErrorMessage(`Too many values for ${k}`); } k = k.toUpperCase().replace(/\s/g, '_'); if (!(k in Artemis.RemoteClassifier.ATTRIBUTES)) { - return this.errorReply(`Invalid attribute: ${k}`); + throw new Chat.ErrorMessage(`Invalid attribute: ${k}`); } const val = parseFloat(vals[0]); if (isNaN(val)) { - return this.errorReply(`Invalid value for ${k}: ${vals[0]}`); + throw new Chat.ErrorMessage(`Invalid value for ${k}: ${vals[0]}`); } scores[k] = val; } @@ -859,13 +859,13 @@ export const commands: Chat.ChatCommands = { toggle(target) { checkAccess(this); if (this.meansYes(target)) { - if (!settings.disabled) return this.errorReply(`The abuse monitor is already enabled.`); + if (!settings.disabled) throw new Chat.ErrorMessage(`The abuse monitor is already enabled.`); settings.disabled = false; } else if (this.meansNo(target)) { - if (settings.disabled) return this.errorReply(`The abuse monitor is already disabled.`); + if (settings.disabled) throw new Chat.ErrorMessage(`The abuse monitor is already disabled.`); settings.disabled = true; } else { - return this.errorReply(`Invalid setting. Must be 'on' or 'off'.`); + throw new Chat.ErrorMessage(`Invalid setting. Must be 'on' or 'off'.`); } saveSettings(); this.refreshPage('abusemonitor-settings'); @@ -883,7 +883,7 @@ export const commands: Chat.ChatCommands = { return this.parse(`/help abusemonitor`); } if (settings.threshold === num) { - return this.errorReply(`The abuse monitor threshold is already ${num}.`); + throw new Chat.ErrorMessage(`The abuse monitor threshold is already ${num}.`); } settings.threshold = num; saveSettings(); @@ -939,14 +939,14 @@ export const commands: Chat.ChatCommands = { } const roomMutes = muted.get(room); if (!roomMutes) { - return this.errorReply(`No users have Artemis mutes in this room.`); + throw new Chat.ErrorMessage(`No users have Artemis mutes in this room.`); } const targetUser = Users.get(target); if (!targetUser) { - return this.errorReply(`User '${target}' not found.`); + throw new Chat.ErrorMessage(`User '${target}' not found.`); } if (!roomMutes.has(targetUser)) { - return this.errorReply(`That user does not have an Artemis mute in this room.`); + throw new Chat.ErrorMessage(`That user does not have an Artemis mute in this room.`); } roomMutes.delete(targetUser); this.modlog(`ABUSEMONITOR UNMUTE`, targetUser); @@ -959,7 +959,7 @@ export const commands: Chat.ChatCommands = { if (!tarRoom) return this.popupReply(`The room "${roomid}" does not exist.`); const cmd = NOJOIN_COMMAND_WHITELIST[toID(type)]; if (!cmd) { - return this.errorReply( + throw new Chat.ErrorMessage( `Invalid punishment given. ` + `Must be one of ${Object.keys(NOJOIN_COMMAND_WHITELIST).join(', ')}.` ); @@ -1007,25 +1007,25 @@ export const commands: Chat.ChatCommands = { const examples = target.split(',').filter(Boolean); const type = examples.shift()?.toUpperCase().replace(/\s/g, '_') || ""; if (!(type in Artemis.RemoteClassifier.ATTRIBUTES)) { - return this.errorReply(`Invalid type: ${type}`); + throw new Chat.ErrorMessage(`Invalid type: ${type}`); } if (examples.length < 3) { - return this.errorReply(`At least 3 examples are needed.`); + throw new Chat.ErrorMessage(`At least 3 examples are needed.`); } const scales = []; const oldScales = []; for (const chunk of examples) { const [message, rawNum] = chunk.split('|'); if (!(message && rawNum)) { - return this.errorReply(`Invalid example: "${chunk}". Must be in \`\`message|num\`\` format.`); + throw new Chat.ErrorMessage(`Invalid example: "${chunk}". Must be in \`\`message|num\`\` format.`); } const num = parseFloat(rawNum); if (isNaN(num)) { - return this.errorReply(`Invalid number in example '${chunk}'.`); + throw new Chat.ErrorMessage(`Invalid number in example '${chunk}'.`); } const data = await classifier.classify(message); if (!data) { - return this.errorReply(`No results found. Try again in a minute?`); + throw new Chat.ErrorMessage(`No results found. Try again in a minute?`); } oldScales.push(num); scales.push(data[type]); @@ -1070,16 +1070,15 @@ export const commands: Chat.ChatCommands = { if (!targetId) return this.parse(`/help abusemonitor`); if (user.lastCommand !== `am userclear ${targetId}`) { user.lastCommand = `am userclear ${targetId}`; - this.errorReply(`Are you sure you want to clear abuse monitor database records for ${targetId}?`); - this.errorReply(`Retype the command if you're sure.`); - return; + throw new Chat.ErrorMessage([`Are you sure you want to clear abuse monitor database records for ${targetId}?`, + `Retype the command if you're sure.`]); } user.lastCommand = ''; const results = await Chat.database.run( 'DELETE FROM perspective_logs WHERE userid = ?', [targetId] ); if (!results.changes) { - return this.errorReply(`No logs for ${targetUsername} found.`); + throw new Chat.ErrorMessage(`No logs for ${targetUsername} found.`); } this.sendReply(`${results.changes} log(s) cleared for ${targetId}.`); this.privateGlobalModAction(`${user.name} cleared abuse monitor logs for ${targetUsername}${rest ? ` (${rest})` : ""}.`); @@ -1091,13 +1090,13 @@ export const commands: Chat.ChatCommands = { if (!target) return this.parse(`/help abusemonitor`); const num = parseInt(target); if (isNaN(num)) { - return this.errorReply(`Invalid log number: ${target}`); + throw new Chat.ErrorMessage(`Invalid log number: ${target}`); } const row = await Chat.database.get( 'SELECT * FROM perspective_logs WHERE rowid = ?', [num] ); if (!row) { - return this.errorReply(`No log with ID ${num} found.`); + throw new Chat.ErrorMessage(`No log with ID ${num} found.`); } await Chat.database.run( // my kingdom for RETURNING * in sqlite :( 'DELETE FROM perspective_logs WHERE rowid = ?', [num] @@ -1119,27 +1118,27 @@ export const commands: Chat.ChatCommands = { rawScore = toID(rawScore); const types = { ...Artemis.RemoteClassifier.ATTRIBUTES, "ALL": {} }; if (!(type in types)) { - return this.errorReply(`Invalid type: ${type}. Valid types: ${Object.keys(types).join(', ')}.`); + throw new Chat.ErrorMessage(`Invalid type: ${type}. Valid types: ${Object.keys(types).join(', ')}.`); } const percent = parseFloat(rawPercent); if (isNaN(percent) || percent > 1 || percent < 0) { - return this.errorReply(`Invalid percent: ${percent}. Must be between 0 and 1.`); + throw new Chat.ErrorMessage(`Invalid percent: ${percent}. Must be between 0 and 1.`); } const score = parseInt(rawScore) || toID(rawScore).toUpperCase() as 'MAXIMUM'; switch (typeof score) { case 'string': if (score !== 'MAXIMUM') { - return this.errorReply(`Invalid score. Must be a number or "MAXIMUM".`); + throw new Chat.ErrorMessage(`Invalid score. Must be a number or "MAXIMUM".`); } break; case 'number': if (isNaN(score) || score < 0) { - return this.errorReply(`Invalid score. Must be a number or "MAXIMUM".`); + throw new Chat.ErrorMessage(`Invalid score. Must be a number or "MAXIMUM".`); } break; } if (settings.specials[type]?.[percent] && !this.cmd.includes('f')) { - return this.errorReply(`That special case already exists. Use /am forceeditspecial to change it.`); + throw new Chat.ErrorMessage(`That special case already exists. Use /am forceeditspecial to change it.`); } if (!settings.specials[type]) settings.specials[type] = {}; // checked above to ensure it's a valid number or MAXIMUM @@ -1157,14 +1156,14 @@ export const commands: Chat.ChatCommands = { const type = rawType.toUpperCase().replace(/\s/g, '_'); const types = { ...Artemis.RemoteClassifier.ATTRIBUTES, "ALL": {} }; if (!(type in types)) { - return this.errorReply(`Invalid type: ${type}. Valid types: ${Object.keys(types).join(', ')}.`); + throw new Chat.ErrorMessage(`Invalid type: ${type}. Valid types: ${Object.keys(types).join(', ')}.`); } const percent = parseFloat(rawPercent); if (isNaN(percent) || percent > 1 || percent < 0) { - return this.errorReply(`Invalid percent: ${percent}. Must be between 0 and 1.`); + throw new Chat.ErrorMessage(`Invalid percent: ${percent}. Must be between 0 and 1.`); } if (!settings.specials[type]?.[percent]) { - return this.errorReply(`That special case does not exist.`); + throw new Chat.ErrorMessage(`That special case does not exist.`); } delete settings.specials[type][percent]; if (!Object.keys(settings.specials[type]).length) { @@ -1181,7 +1180,7 @@ export const commands: Chat.ChatCommands = { checkAccess(this); const num = parseFloat(target); if (isNaN(num) || num < 0 || num > 1) { - return this.errorReply(`Invalid minimum score: ${num}. Must be a positive integer.`); + throw new Chat.ErrorMessage(`Invalid minimum score: ${num}. Must be a positive integer.`); } settings.minScore = num; saveSettings(); @@ -1195,11 +1194,11 @@ export const commands: Chat.ChatCommands = { checkAccess(this); const num = parseInt(target) - 1; if (isNaN(num)) { - return this.errorReply(`Invalid punishment number: ${num + 1}.`); + throw new Chat.ErrorMessage(`Invalid punishment number: ${num + 1}.`); } const punishment = settings.punishments[num]; if (!punishment) { - return this.errorReply(`Punishment ${num + 1} does not exist.`); + throw new Chat.ErrorMessage(`Punishment ${num + 1} does not exist.`); } this.sendReply( `|html|Punishment ${num + 1}: ` + @@ -1210,10 +1209,10 @@ export const commands: Chat.ChatCommands = { checkAccess(this); const [to, from] = target.split(',').map(f => toID(f)); if (!(to && from)) { - return this.errorReply(`Specify a type to change and a type to change to.`); + throw new Chat.ErrorMessage(`Specify a type to change and a type to change to.`); } if (![to, from].every(f => punishmentHandlers[f])) { - return this.errorReply( + throw new Chat.ErrorMessage( `Invalid types given. Valid types: ${Object.keys(punishmentHandlers).join(', ')}.` ); } @@ -1225,7 +1224,7 @@ export const commands: Chat.ChatCommands = { } } if (!changed.length) { - return this.errorReply(`No punishments of type '${to}' found.`); + throw new Chat.ErrorMessage(`No punishments of type '${to}' found.`); } this.sendReply(`Updated punishment(s) ${changed.join(', ')}`); this.privateGlobalModAction(`${user.name} updated all abuse-monitor punishments of type ${to} to type ${from}`); @@ -1268,31 +1267,31 @@ export const commands: Chat.ChatCommands = { switch (key) { case 'punishment': case 'p': if (punishment.punishment) { - return this.errorReply(`Duplicate punishment values.`); + throw new Chat.ErrorMessage(`Duplicate punishment values.`); } value = toID(value).toUpperCase(); if (!PUNISHMENTS.includes(value)) { - return this.errorReply(`Invalid punishment: ${value}. Valid punishments: ${PUNISHMENTS.join(', ')}.`); + throw new Chat.ErrorMessage(`Invalid punishment: ${value}. Valid punishments: ${PUNISHMENTS.join(', ')}.`); } punishment.punishment = value; break; case 'count': case 'num': case 'c': if (punishment.count) { - return this.errorReply(`Duplicate count values.`); + throw new Chat.ErrorMessage(`Duplicate count values.`); } const num = parseInt(value); if (isNaN(num)) { - return this.errorReply(`Invalid count '${value}'. Must be a number.`); + throw new Chat.ErrorMessage(`Invalid count '${value}'. Must be a number.`); } punishment.count = num; break; case 'type': case 't': if (punishment.type) { - return this.errorReply(`Duplicate type values.`); + throw new Chat.ErrorMessage(`Duplicate type values.`); } value = value.replace(/\s/g, '_').toUpperCase(); if (!Artemis.RemoteClassifier.ATTRIBUTES[value as keyof typeof Artemis.RemoteClassifier.ATTRIBUTES]) { - return this.errorReply( + throw new Chat.ErrorMessage( `Invalid attribute: ${value}. ` + `Valid attributes: ${Object.keys(Artemis.RemoteClassifier.ATTRIBUTES).join(', ')}.` ); @@ -1301,11 +1300,11 @@ export const commands: Chat.ChatCommands = { break; case 'certainty': case 'ct': if (punishment.certainty) { - return this.errorReply(`Duplicate certainty values.`); + throw new Chat.ErrorMessage(`Duplicate certainty values.`); } const certainty = parseFloat(value); if (isNaN(certainty) || certainty > 1 || certainty < 0) { - return this.errorReply(`Invalid certainty '${value}'. Must be a number above 0 and below 1.`); + throw new Chat.ErrorMessage(`Invalid certainty '${value}'. Must be a number above 0 and below 1.`); } punishment.certainty = certainty; break; @@ -1315,41 +1314,41 @@ export const commands: Chat.ChatCommands = { punishment.modlogActions = []; } if (punishment.modlogActions.includes(value)) { - return this.errorReply(`Duplicate modlog action values - '${value}'.`); + throw new Chat.ErrorMessage(`Duplicate modlog action values - '${value}'.`); } punishment.modlogActions.push(value); break; case 'mlc': case 'modlogcount': if (punishment.modlogCount) { - return this.errorReply(`Duplicate modlog count values.`); + throw new Chat.ErrorMessage(`Duplicate modlog count values.`); } const count = parseInt(value); if (isNaN(count)) { - return this.errorReply(`Invalid modlog count.`); + throw new Chat.ErrorMessage(`Invalid modlog count.`); } punishment.modlogCount = count; break; case 'st': case 's': case 'secondary': let [sType, sValue] = Utils.splitFirst(value, '|').map(f => f.trim()); if (!sType || !sValue) { - return this.errorReply(`Invalid secondary type/certainty.`); + throw new Chat.ErrorMessage(`Invalid secondary type/certainty.`); } sType = sType.replace(/\s/g, '_').toUpperCase(); if (!Artemis.RemoteClassifier.ATTRIBUTES[sType as keyof typeof Artemis.RemoteClassifier.ATTRIBUTES]) { - return this.errorReply( + throw new Chat.ErrorMessage( `Invalid secondary attribute: ${sType}. ` + `Valid attributes: ${Object.keys(Artemis.RemoteClassifier.ATTRIBUTES).join(', ')}.` ); } const sCertainty = parseFloat(sValue); if (isNaN(sCertainty) || sCertainty > 1 || sCertainty < 0) { - return this.errorReply(`Invalid secondary certainty '${sValue}'. Must be a number above 0 and below 1.`); + throw new Chat.ErrorMessage(`Invalid secondary certainty '${sValue}'. Must be a number above 0 and below 1.`); } if (!punishment.secondaryTypes) { punishment.secondaryTypes = {}; } if (punishment.secondaryTypes[sType]) { - return this.errorReply(`Duplicate secondary type.`); + throw new Chat.ErrorMessage(`Duplicate secondary type.`); } punishment.secondaryTypes[sType] = sCertainty; break; @@ -1362,7 +1361,7 @@ export const commands: Chat.ChatCommands = { } } if (!punishment.punishment) { - return this.errorReply(`A punishment type must be specified.`); + throw new Chat.ErrorMessage(`A punishment type must be specified.`); } for (const [i, p] of settings.punishments.entries()) { let matches = 0; @@ -1372,7 +1371,7 @@ export const commands: Chat.ChatCommands = { if (val && val === visualizePunishmentKey(p, key)) matches++; } if (matches === Object.keys(p).length) { - return this.errorReply(`This punishment is already stored at ${i + 1}.`); + throw new Chat.ErrorMessage(`This punishment is already stored at ${i + 1}.`); } } settings.punishments.push(punishment as PunishmentSettings); @@ -1387,10 +1386,10 @@ export const commands: Chat.ChatCommands = { deletepunishment(target, room, user) { checkAccess(this); const idx = parseInt(target) - 1; - if (isNaN(idx)) return this.errorReply(`Invalid number.`); + if (isNaN(idx)) throw new Chat.ErrorMessage(`Invalid number.`); const punishment = settings.punishments[idx]; if (!punishment) { - return this.errorReply(`No punishments exist at index ${idx + 1}.`); + throw new Chat.ErrorMessage(`No punishments exist at index ${idx + 1}.`); } settings.punishments.splice(idx, 1); saveSettings(); @@ -1418,15 +1417,15 @@ export const commands: Chat.ChatCommands = { const [rawTurns, rawIncrement, rawMin] = Utils.splitFirst(target, ',', 2).map(toID); const turns = parseInt(rawTurns); if (isNaN(turns) || turns < 0) { - return this.errorReply(`Turns must be a number above 0.`); + throw new Chat.ErrorMessage(`Turns must be a number above 0.`); } const increment = parseInt(rawIncrement); if (isNaN(increment) || increment < 0) { - return this.errorReply(`The increment must be a number above 0.`); + throw new Chat.ErrorMessage(`The increment must be a number above 0.`); } const min = parseInt(rawMin); if (rawMin && isNaN(min)) { - return this.errorReply(`Invalid minimum (must be a number).`); + throw new Chat.ErrorMessage(`Invalid minimum (must be a number).`); } settings.thresholdIncrement = { amount: increment, turns }; if (min) { @@ -1445,7 +1444,7 @@ export const commands: Chat.ChatCommands = { di: 'deleteincrement', deleteincrement(target, room, user) { checkAccess(this); - if (!settings.thresholdIncrement) return this.errorReply(`The threshold increment is already disabled.`); + if (!settings.thresholdIncrement) throw new Chat.ErrorMessage(`The threshold increment is already disabled.`); settings.thresholdIncrement = null; saveSettings(); this.refreshPage('abusemonitor-settings'); @@ -1459,7 +1458,7 @@ export const commands: Chat.ChatCommands = { } const timeNum = new Date(target).getTime(); if (isNaN(timeNum)) { - return this.errorReply(`Invalid date.`); + throw new Chat.ErrorMessage(`Invalid date.`); } let logs = await Chat.database.all( 'SELECT * FROM perspective_stats WHERE result = 0 AND timestamp > ? AND timestamp < ?', @@ -1469,7 +1468,7 @@ export const commands: Chat.ChatCommands = { Chat.toTimestamp(new Date(log.timestamp)).split(' ')[0] === target )); if (!logs.length) { - return this.errorReply(`No logs found for that date.`); + throw new Chat.ErrorMessage(`No logs found for that date.`); } this.sendReplyBox( `${Chat.count(logs, 'logs')} found on the date ${target}:
` + @@ -1507,7 +1506,7 @@ export const commands: Chat.ChatCommands = { path = `artemis/${target.toLowerCase().replace(/\//g, '-')}`; } const backup = await FS(`config/chat-plugins/${path}.json`).readIfExists(); - if (!backup) return this.errorReply(`No backup settings saved.`); + if (!backup) throw new Chat.ErrorMessage(`No backup settings saved.`); const backupSettings = JSON.parse(backup); Object.assign(settings, backupSettings); saveSettings(); @@ -1518,10 +1517,10 @@ export const commands: Chat.ChatCommands = { async deletebackup(target, room, user) { checkAccess(this); target = target.toLowerCase().replace(/\//g, '-'); - if (!target) return this.errorReply(`Specify a backup file.`); + if (!target) throw new Chat.ErrorMessage(`Specify a backup file.`); const path = FS(`config/chat-plugins/artemis/${target}.json`); if (!(await path.exists())) { - return this.errorReply(`Backup '${target}' not found.`); + throw new Chat.ErrorMessage(`Backup '${target}' not found.`); } await path.unlinkIfExists(); this.globalModlog(`ABUSEMONITOR DELETEBACKUP`, null, target); @@ -1550,13 +1549,13 @@ export const commands: Chat.ChatCommands = { let message; if (this.meansYes(target)) { if (!settings.recommendOnly) { - return this.errorReply(`Automatic punishments are already enabled.`); + throw new Chat.ErrorMessage(`Automatic punishments are already enabled.`); } settings.recommendOnly = false; message = `${user.name} enabled automatic punishments for the Artemis battle monitor`; } else if (this.meansNo(target)) { if (settings.recommendOnly) { - return this.errorReply(`Automatic punishments are already disabled.`); + throw new Chat.ErrorMessage(`Automatic punishments are already disabled.`); } settings.recommendOnly = true; message = `${user.name} disabled automatic punishments for the Artemis battle monitor`; @@ -1609,18 +1608,18 @@ export const commands: Chat.ChatCommands = { return this.parse(`/help abusemonitor resolvereview`); } if (!reviews[userid]) { - return this.errorReply(`No reviews found by that user.`); + throw new Chat.ErrorMessage(`No reviews found by that user.`); } const review = reviews[userid].find(f => getBattleLinks(f.room).includes(roomid)); if (!review) { - return this.errorReply(`No reviews found by that user for that room.`); + throw new Chat.ErrorMessage(`No reviews found by that user for that room.`); } const isAccurate = Number(accurate); if (isNaN(isAccurate) || isAccurate < 0 || isAccurate > 1) { return this.popupReply(`Invalid accuracy. Must be a number between 0 and 1.`); } if (review.resolved) { - return this.errorReply(`That review has already been resolved.`); + throw new Chat.ErrorMessage(`That review has already been resolved.`); } review.resolved = { by: user.id, @@ -1636,10 +1635,10 @@ export const commands: Chat.ChatCommands = { checkAccess(this); if (!target) return this.parse(`/help am`); const [old, newWord] = target.split(','); - if (!old || !newWord) return this.errorReply(`Invalid arguments - must be [oldWord], [newWord].`); - if (toID(old) === toID(newWord)) return this.errorReply(`The old word and the new word are the same.`); + if (!old || !newWord) throw new Chat.ErrorMessage(`Invalid arguments - must be [oldWord], [newWord].`); + if (toID(old) === toID(newWord)) throw new Chat.ErrorMessage(`The old word and the new word are the same.`); if (settings.replacements[old]) { - return this.errorReply(`The old word '${old}' is already in use (for '${settings.replacements[old]}').`); + throw new Chat.ErrorMessage(`The old word '${old}' is already in use (for '${settings.replacements[old]}').`); } Chat.validateRegex(target); settings.replacements[old] = newWord; @@ -1653,7 +1652,7 @@ export const commands: Chat.ChatCommands = { if (!target) return this.parse(`/help am`); const replaceTo = settings.replacements[target]; if (!replaceTo) { - return this.errorReply(`${target} is not a currently set replacement.`); + throw new Chat.ErrorMessage(`${target} is not a currently set replacement.`); } delete settings.replacements[target]; saveSettings(); @@ -1684,7 +1683,7 @@ export const commands: Chat.ChatCommands = { const num = Number(target); if (isNaN(num)) { if (!/[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(target)) { - return this.errorReply(`Invalid date provided. Must be in YYYY-MM-DD format.`); + throw new Chat.ErrorMessage(`Invalid date provided. Must be in YYYY-MM-DD format.`); } metadata.modlogIgnores[targetUser] = target; target = 'before and including ' + target; @@ -1694,7 +1693,7 @@ export const commands: Chat.ChatCommands = { metadata.modlogIgnores[targetUser] = ignores = []; } if (ignores.includes(num)) { - return this.errorReply(`That modlog entry is already ignored.`); + throw new Chat.ErrorMessage(`That modlog entry is already ignored.`); } ignores.push(num); target = `entry #${target}`; @@ -1715,18 +1714,18 @@ export const commands: Chat.ChatCommands = { } const entry = metadata.modlogIgnores?.[targetUser]; if (!entry) { - return this.errorReply(`That user has no ignored modlog entries registered.`); + throw new Chat.ErrorMessage(`That user has no ignored modlog entries registered.`); } if (typeof entry === 'string') { rawNum = entry; delete metadata.modlogIgnores![targetUser]; } else { if (isNaN(num)) { - return this.errorReply(`Invalid modlog entry number: ${num}`); + throw new Chat.ErrorMessage(`Invalid modlog entry number: ${num}`); } const idx = entry.indexOf(num); if (idx === -1) { - return this.errorReply(`That modlog entry is not ignored for the user ${targetUser}.`); + throw new Chat.ErrorMessage(`That modlog entry is not ignored for the user ${targetUser}.`); } entry.splice(idx, 1); if (!entry.length) { @@ -1807,7 +1806,7 @@ export const pages: Chat.PageTable = { checkAccess(this, 'lock'); const roomid = query.join('-') as RoomID; if (!toID(roomid)) { - return this.errorReply(`You must specify a roomid to view abuse monitor data for.`); + throw new Chat.ErrorMessage(`You must specify a roomid to view abuse monitor data for.`); } let buf = `
`; buf += ``; diff --git a/server/chat-plugins/randombattles/index.ts b/server/chat-plugins/randombattles/index.ts index 8ac43f0c72..7574170d58 100644 --- a/server/chat-plugins/randombattles/index.ts +++ b/server/chat-plugins/randombattles/index.ts @@ -493,8 +493,7 @@ export const commands: Chat.ChatCommands = { const searchResults = dex.dataSearch(args[0], ['Pokedex']); if (!searchResults?.length) { - this.errorReply(`No Pok\u00e9mon named '${args[0]}' was found${Dex.gen > dex.gen ? ` in Gen ${dex.gen}` : ""}. (Check your spelling?)`); - return; + throw new Chat.ErrorMessage(`No Pok\u00e9mon named '${args[0]}' was found${Dex.gen > dex.gen ? ` in Gen ${dex.gen}` : ""}. (Check your spelling?)`); } let inexactMsg = ''; @@ -515,7 +514,7 @@ export const commands: Chat.ChatCommands = { const rbyMoves = getRBYMoves(species); if (!rbyMoves) { this.sendReply(inexactMsg); - return this.errorReply(`Error: ${species.name} has no Random Battle data in ${GEN_NAMES[toID(args[1])]}`); + throw new Chat.ErrorMessage(`Error: ${species.name} has no Random Battle data in ${GEN_NAMES[toID(args[1])]}`); } movesets.push(`Moves for ${species.name} in ${format.name}:${rbyMoves}`); setCount = 1; @@ -523,7 +522,7 @@ export const commands: Chat.ChatCommands = { const lgpeMoves = getLetsGoMoves(species); if (!lgpeMoves) { this.sendReply(inexactMsg); - return this.errorReply(`Error: ${species.name} has no Random Battle data in [Gen 7 Let's Go]`); + throw new Chat.ErrorMessage(`Error: ${species.name} has no Random Battle data in [Gen 7 Let's Go]`); } movesets.push(`Moves for ${species.name} in ${format.name}:
${lgpeMoves}`); setCount = 1; @@ -582,7 +581,7 @@ export const commands: Chat.ChatCommands = { if (!movesets.length) { this.sendReply(inexactMsg); - return this.errorReply(`Error: ${species.name} has no Random Battle data in ${format.name}`); + throw new Chat.ErrorMessage(`Error: ${species.name} has no Random Battle data in ${format.name}`); } let buf = movesets.join('
'); if (setCount <= 2) { @@ -605,7 +604,7 @@ export const commands: Chat.ChatCommands = { if (!args[0]) return this.parse(`/help battlefactory`); const species = Dex.species.get(args[0]); if (!species.exists) { - return this.errorReply(`Error: Pok\u00e9mon '${args[0].trim()}' not found.`); + throw new Chat.ErrorMessage(`Error: Pok\u00e9mon '${args[0].trim()}' not found.`); } let mod = 'gen9'; if (args[1] && toID(args[1]) in Dex.dexes && Dex.dexes[toID(args[1])].gen >= 7) mod = toID(args[1]); @@ -617,7 +616,7 @@ export const commands: Chat.ChatCommands = { if (!args[0]) return this.parse(`/help battlefactory`); const species = Dex.species.get(args[0]); if (!species.exists) { - return this.errorReply(`Error: Pok\u00e9mon '${args[0].trim()}' not found.`); + throw new Chat.ErrorMessage(`Error: Pok\u00e9mon '${args[0].trim()}' not found.`); } let tier = ''; if (args[1] && toID(args[1]) in TIERS) { @@ -654,7 +653,7 @@ export const commands: Chat.ChatCommands = { if (!this.runBroadcast()) return; if (!target) return this.parse(`/help cap1v1`); const species = Dex.species.get(target); - if (!species.exists) return this.errorReply(`Error: Pok\u00e9mon '${target.trim()}' not found.`); + if (!species.exists) throw new Chat.ErrorMessage(`Error: Pok\u00e9mon '${target.trim()}' not found.`); const cap1v1Set = CAP1v1Sets(species); if (!cap1v1Set) return this.parse(`/help cap1v1`); if (typeof cap1v1Set !== 'string') { diff --git a/server/chat-plugins/randombattles/winrates.ts b/server/chat-plugins/randombattles/winrates.ts index cced97f676..ef940c0701 100644 --- a/server/chat-plugins/randombattles/winrates.ts +++ b/server/chat-plugins/randombattles/winrates.ts @@ -216,11 +216,11 @@ export const commands: Chat.ChatCommands = { async removewinrates(target, room, user) { this.checkCan('rangeban'); if (!/^[0-9]{4}-[0-9]{2}$/.test(target) || target === getMonth()) { - return this.errorReply(`Invalid month: ${target}`); + throw new Chat.ErrorMessage(`Invalid month: ${target}`); } const path = STATS_PATH.replace('{{MON}}', target); if (!(await FS(path).exists())) { - return this.errorReply(`No stats for the month ${target}.`); + throw new Chat.ErrorMessage(`No stats for the month ${target}.`); } await FS(path).unlinkIfExists(); this.globalModlog('REMOVEWINRATES', null, target); @@ -233,22 +233,22 @@ export const pages: Chat.PageTable = { if (!user.named) return Rooms.RETRY_AFTER_LOGIN; query = query.join('-').split('--'); const format = toID(query.shift()); - if (!format) return this.errorReply(`Specify a format to view winrates for.`); + if (!format) throw new Chat.ErrorMessage(`Specify a format to view winrates for.`); if (!stats.formats[format]) { - return this.errorReply(`That format does not have winrates tracked.`); + throw new Chat.ErrorMessage(`That format does not have winrates tracked.`); } checkRollover(); const sorter = toID(query.shift() || 'zscore'); if (!['zscore', 'raw'].includes(sorter)) { - return this.errorReply(`Invalid sorting method. Must be either 'zscore' or 'raw'.`); + throw new Chat.ErrorMessage(`Invalid sorting method. Must be either 'zscore' or 'raw'.`); } const month = query.shift() || getMonth(); if (!/^[0-9]{4}-[0-9]{2}$/.test(month)) { - return this.errorReply(`Invalid month: ${month}`); + throw new Chat.ErrorMessage(`Invalid month: ${month}`); } const isOldMonth = month !== getMonth(); if (isOldMonth && !(await FS(STATS_PATH.replace('{{MONTH}}', month)).exists())) { - return this.errorReply(`There are no winrates for that month.`); + throw new Chat.ErrorMessage(`There are no winrates for that month.`); } const formatTitle = Dex.formats.get(format).name; let buf = `

Winrates for ${formatTitle} (${month})

`; diff --git a/server/chat-plugins/repeats.ts b/server/chat-plugins/repeats.ts index be7a1a3c82..bab191157d 100644 --- a/server/chat-plugins/repeats.ts +++ b/server/chat-plugins/repeats.ts @@ -160,7 +160,7 @@ export const commands: Chat.ChatCommands = { const isHTML = cmd.includes('html'); const isByMessages = cmd.includes('bymessages'); room = this.requireRoom(); - if (room.settings.isPersonal) return this.errorReply(`Personal rooms do not support repeated messages.`); + if (room.settings.isPersonal) throw new Chat.ErrorMessage(`Personal rooms do not support repeated messages.`); this.checkCan(isHTML ? 'addhtml' : 'mute', null, room); const [intervalString, name, ...messageArray] = target.split(','); const id = toID(name); @@ -211,7 +211,7 @@ export const commands: Chat.ChatCommands = { repeatfaq(target, room, user, connection, cmd) { room = this.requireRoom(); this.checkCan('mute', null, room); - if (room.settings.isPersonal) return this.errorReply(`Personal rooms do not support repeated messages.`); + if (room.settings.isPersonal) throw new Chat.ErrorMessage(`Personal rooms do not support repeated messages.`); const isByMessages = cmd.includes('bymessages'); let [intervalString, topic] = target.split(','); @@ -257,11 +257,11 @@ export const commands: Chat.ChatCommands = { } this.checkCan('mute', null, room); if (!room.settings.repeats?.length) { - return this.errorReply(this.tr`There are no repeated phrases in this room.`); + throw new Chat.ErrorMessage(this.tr`There are no repeated phrases in this room.`); } if (!Repeats.hasRepeat(room, id)) { - return this.errorReply(this.tr`The phrase labeled with "${id}" is not being repeated in this room.`); + throw new Chat.ErrorMessage(this.tr`The phrase labeled with "${id}" is not being repeated in this room.`); } Repeats.removeRepeat(room, id); @@ -275,7 +275,7 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); this.checkCan('declare', null, room); if (!room.settings.repeats?.length) { - return this.errorReply(this.tr`There are no repeated phrases in this room.`); + throw new Chat.ErrorMessage(this.tr`There are no repeated phrases in this room.`); } for (const { id } of room.settings.repeats) { @@ -289,7 +289,7 @@ export const commands: Chat.ChatCommands = { repeats: 'viewrepeats', viewrepeats(target, room, user) { const roomid = toID(target) || room?.roomid; - if (!roomid) return this.errorReply(this.tr`You must specify a room when using this command in PMs.`); + if (!roomid) throw new Chat.ErrorMessage(this.tr`You must specify a room when using this command in PMs.`); this.parse(`/j view-repeats-${roomid}`); }, }; diff --git a/server/chat-plugins/responder.ts b/server/chat-plugins/responder.ts index b37200ab70..3e98a59f16 100644 --- a/server/chat-plugins/responder.ts +++ b/server/chat-plugins/responder.ts @@ -302,7 +302,7 @@ export const commands: Chat.ChatCommands = { question(target, room, user) { room = this.requireRoom(); const responder = room.responder; - if (!responder) return this.errorReply(`This room does not have an autoresponder configured.`); + if (!responder) throw new Chat.ErrorMessage(`This room does not have an autoresponder configured.`); if (!target) return this.parse("/help question"); const reply = responder.visualize(target, true); if (!reply) return this.sendReplyBox(`No answer found.`); @@ -317,7 +317,7 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); const responder = room.responder; if (!responder) { - return this.errorReply(`This room has not configured an autoresponder.`); + throw new Chat.ErrorMessage(`This room has not configured an autoresponder.`); } if (!target) { return this.parse('/help autoresponder'); @@ -337,15 +337,15 @@ export const commands: Chat.ChatCommands = { } this.checkCan('ban', null, room); if (room.settings.isPrivate === true) { - return this.errorReply(`Secret rooms cannot enable an autoresponder.`); + throw new Chat.ErrorMessage(`Secret rooms cannot enable an autoresponder.`); } if (this.meansYes(target)) { - if (room.responder) return this.errorReply(`The Autoresponder for this room is already enabled.`); + if (room.responder) throw new Chat.ErrorMessage(`The Autoresponder for this room is already enabled.`); room.responder = new AutoResponder(room, answererData[room.roomid]); room.responder.writeState(); } if (this.meansNo(target)) { - if (!room.responder) return this.errorReply(`The Autoresponder for this room is already disabled.`); + if (!room.responder) throw new Chat.ErrorMessage(`The Autoresponder for this room is already disabled.`); room.responder.destroy(); } this.privateModAction(`${user.name} ${!room.responder ? 'disabled' : 'enabled'} the auto-response filter.`); @@ -355,11 +355,11 @@ export const commands: Chat.ChatCommands = { add(target, room, user, connection, cmd) { room = this.requireRoom(); if (!room.responder) { - return this.errorReply(`This room has not configured an auto-response filter.`); + throw new Chat.ErrorMessage(`This room has not configured an auto-response filter.`); } const force = cmd === 'forceadd'; if (force && !AutoResponder.canOverride(user, room)) { - return this.errorReply(`You cannot use raw regex - use /autoresponder add instead.`); + throw new Chat.ErrorMessage(`You cannot use raw regex - use /autoresponder add instead.`); } this.checkCan('ban', null, room); room.responder.tryAddRegex(target, force); @@ -370,11 +370,11 @@ export const commands: Chat.ChatCommands = { const [faq, index] = target.split(','); room = this.requireRoom(); if (!room.responder) { - return this.errorReply(`${room.title} has not configured an auto-response filter.`); + throw new Chat.ErrorMessage(`${room.title} has not configured an auto-response filter.`); } this.checkCan('ban', null, room); const num = parseInt(index); - if (isNaN(num)) return this.errorReply("Invalid index."); + if (isNaN(num)) throw new Chat.ErrorMessage("Invalid index."); room.responder.tryRemoveRegex(faq, num - 1); this.privateModAction(`${user.name} removed regex ${num} from the usable regexes for ${faq}.`); this.modlog('AUTOFILTER REMOVE', null, `removed regex ${index} for FAQ ${faq}`); @@ -386,7 +386,7 @@ export const commands: Chat.ChatCommands = { ignore(target, room, user) { room = this.requireRoom(); if (!room.responder) { - return this.errorReply(`This room has not configured an auto-response filter.`); + throw new Chat.ErrorMessage(`This room has not configured an auto-response filter.`); } this.checkCan('ban', null, room); if (!toID(target)) { @@ -402,7 +402,7 @@ export const commands: Chat.ChatCommands = { unignore(target, room, user) { room = this.requireRoom(); if (!room.responder) { - return this.errorReply(`${room.title} has not configured an auto-response filter.`); + throw new Chat.ErrorMessage(`${room.title} has not configured an auto-response filter.`); } this.checkCan('ban', null, room); if (!toID(target)) { @@ -434,7 +434,7 @@ export const pages: Chat.PageTable = { async autoresponder(args, user) { const room = this.requireRoom(); if (!room.responder) { - return this.errorReply(`${room.title} does not have a configured autoresponder.`); + throw new Chat.ErrorMessage(`${room.title} does not have a configured autoresponder.`); } args.shift(); const roomData = answererData[room.roomid]; @@ -508,7 +508,7 @@ export const pages: Chat.PageTable = { this.title = `[${room.title} Autoresponder ignore list]`; buf = `

${room.title} responder terms to ignore:

${back}${refresh('ignore')}
`; if (!roomData.ignore) { - return this.errorReply(`No terms on ignore list.`); + throw new Chat.ErrorMessage(`No terms on ignore list.`); } for (const term of roomData.ignore) { buf += `- ${term}
`; diff --git a/server/chat-plugins/room-events.ts b/server/chat-plugins/room-events.ts index f823c2b29e..267e961136 100644 --- a/server/chat-plugins/room-events.ts +++ b/server/chat-plugins/room-events.ts @@ -104,9 +104,9 @@ export const commands: Chat.ChatCommands = { roomevents: { ''(target, room, user) { room = this.requireRoom(); - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); if (!room.settings.events || !Object.keys(room.settings.events).length) { - return this.errorReply("There are currently no planned upcoming events for this room."); + throw new Chat.ErrorMessage("There are currently no planned upcoming events for this room."); } if (!this.runBroadcast()) return; convertAliasFormat(room); @@ -131,7 +131,7 @@ export const commands: Chat.ChatCommands = { edit: 'add', add(target, room, user) { room = this.requireRoom(); - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); this.checkCan('ban', null, room); if (!room.settings.events) room.settings.events = Object.create(null); convertAliasFormat(room); @@ -139,21 +139,21 @@ export const commands: Chat.ChatCommands = { const [eventName, date, ...desc] = target.split(target.includes('|') ? '|' : ','); if (!(eventName && date && desc)) { - return this.errorReply("You're missing a command parameter - to see this command's syntax, use /help roomevents."); + throw new Chat.ErrorMessage("You're missing a command parameter - to see this command's syntax, use /help roomevents."); } const dateActual = date.trim(); const descString = desc.join(target.includes('|') ? '|' : ',').trim(); - if (eventName.trim().length > 50) return this.errorReply("Event names should not exceed 50 characters."); - if (dateActual.length > 150) return this.errorReply("Event dates should not exceed 150 characters."); - if (descString.length > 1000) return this.errorReply("Event descriptions should not exceed 1000 characters."); + if (eventName.trim().length > 50) throw new Chat.ErrorMessage("Event names should not exceed 50 characters."); + if (dateActual.length > 150) throw new Chat.ErrorMessage("Event dates should not exceed 150 characters."); + if (descString.length > 1000) throw new Chat.ErrorMessage("Event descriptions should not exceed 1000 characters."); const eventId = getEventID(eventName, room); - if (!eventId) return this.errorReply("Event names must contain at least one alphanumerical character."); + if (!eventId) throw new Chat.ErrorMessage("Event names must contain at least one alphanumerical character."); const oldEvent = room.settings.events?.[eventId] as RoomEvent; - if (oldEvent && 'events' in oldEvent) return this.errorReply(`"${eventId}" is already the name of a category.`); + if (oldEvent && 'events' in oldEvent) throw new Chat.ErrorMessage(`"${eventId}" is already the name of a category.`); const eventNameActual = (oldEvent ? oldEvent.eventName : eventName.trim()); this.privateModAction(`${user.name} ${oldEvent ? "edited the" : "added a"} roomevent titled "${eventNameActual}".`); @@ -169,24 +169,24 @@ export const commands: Chat.ChatCommands = { rename(target, room, user) { room = this.requireRoom(); - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); this.checkCan('ban', null, room); let [oldName, newName] = target.split(target.includes('|') ? '|' : ','); - if (!(oldName && newName)) return this.errorReply("Usage: /roomevents rename [old name], [new name]"); + if (!(oldName && newName)) throw new Chat.ErrorMessage("Usage: /roomevents rename [old name], [new name]"); convertAliasFormat(room); newName = newName.trim(); const newID = toID(newName); const oldID = (getAliases(room).includes(toID(oldName)) ? getEventID(oldName, room) : toID(oldName)); - if (newID === oldID) return this.errorReply("The new name must be different from the old one."); - if (!newID) return this.errorReply("Event names must contain at least one alphanumeric character."); - if (newName.length > 50) return this.errorReply("Event names should not exceed 50 characters."); + if (newID === oldID) throw new Chat.ErrorMessage("The new name must be different from the old one."); + if (!newID) throw new Chat.ErrorMessage("Event names must contain at least one alphanumeric character."); + if (newName.length > 50) throw new Chat.ErrorMessage("Event names should not exceed 50 characters."); const events = room.settings.events!; const eventData = events?.[oldID]; - if (!(eventData && 'eventName' in eventData)) return this.errorReply(`There is no event titled "${oldName}".`); + if (!(eventData && 'eventName' in eventData)) throw new Chat.ErrorMessage(`There is no event titled "${oldName}".`); if (events?.[newID]) { - return this.errorReply(`"${newName}" is already an event, alias, or category.`); + throw new Chat.ErrorMessage(`"${newName}" is already an event, alias, or category.`); } const originalName = eventData.eventName; @@ -202,19 +202,21 @@ export const commands: Chat.ChatCommands = { begin: 'start', start(target, room, user) { room = this.requireRoom(); - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); this.checkCan('ban', null, room); if (!room.settings.events || !Object.keys(room.settings.events).length) { - return this.errorReply("There are currently no planned upcoming events for this room to start."); + throw new Chat.ErrorMessage("There are currently no planned upcoming events for this room to start."); } - if (!target) return this.errorReply("Usage: /roomevents start [event name]"); + if (!target) throw new Chat.ErrorMessage("Usage: /roomevents start [event name]"); convertAliasFormat(room); target = toID(target); const event = room.settings.events[getEventID(target, room)]; - if (!(event && 'eventName' in event)) return this.errorReply(`There is no event titled '${target}'. Check spelling?`); + if (!(event && 'eventName' in event)) { + throw new Chat.ErrorMessage(`There is no event titled '${target}'. Check spelling?`); + } if (event.started) { - return this.errorReply(`The event ${event.eventName} has already started.`); + throw new Chat.ErrorMessage(`The event ${event.eventName} has already started.`); } for (const u in room.users) { const activeUser = Users.get(u); @@ -237,17 +239,17 @@ export const commands: Chat.ChatCommands = { delete: 'remove', remove(target, room, user) { room = this.requireRoom(); - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); this.checkCan('ban', null, room); if (!room.settings.events || Object.keys(room.settings.events).length === 0) { - return this.errorReply("There are currently no planned upcoming events for this room to remove."); + throw new Chat.ErrorMessage("There are currently no planned upcoming events for this room to remove."); } - if (!target) return this.errorReply("Usage: /roomevents remove [event name]"); + if (!target) throw new Chat.ErrorMessage("Usage: /roomevents remove [event name]"); const eventID = toID(target); convertAliasFormat(room); - if (getAliases(room).includes(eventID)) return this.errorReply("To delete aliases, use /roomevents removealias."); + if (getAliases(room).includes(eventID)) throw new Chat.ErrorMessage("To delete aliases, use /roomevents removealias."); if (!(room.settings.events[eventID] && 'eventName' in room.settings.events[eventID])) { - return this.errorReply(`There is no event titled '${target}'. Check spelling?`); + throw new Chat.ErrorMessage(`There is no event titled '${target}'. Check spelling?`); } delete room.settings.events[eventID]; @@ -265,12 +267,12 @@ export const commands: Chat.ChatCommands = { view(target, room, user) { room = this.requireRoom(); - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); if (!room.settings.events || !Object.keys(room.settings.events).length) { - return this.errorReply("There are currently no planned upcoming events for this room."); + throw new Chat.ErrorMessage("There are currently no planned upcoming events for this room."); } - if (!target) return this.errorReply("Usage: /roomevents view [event name, alias, or category]"); + if (!target) throw new Chat.ErrorMessage("Usage: /roomevents view [event name, alias, or category]"); convertAliasFormat(room); target = getEventID(target, room); @@ -288,7 +290,7 @@ export const commands: Chat.ChatCommands = { } else if (room.settings.events[target] && 'eventName' in room.settings.events[target]) { events.push(room.settings.events[target] as RoomEvent); } else { - return this.errorReply(`There is no event or category titled '${target}'. Check spelling?`); + throw new Chat.ErrorMessage(`There is no event or category titled '${target}'. Check spelling?`); } if (!this.runBroadcast()) return; let hasAliases = false; @@ -325,19 +327,19 @@ export const commands: Chat.ChatCommands = { alias: 'addalias', addalias(target, room, user) { room = this.requireRoom(); - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); this.checkCan('ban', null, room); const [alias, eventId] = target.split(target.includes('|') ? '|' : ',').map(argument => toID(argument)); if (!(alias && eventId)) { - return this.errorReply("Usage: /roomevents addalias [alias], [event name]. Aliases must contain at least one alphanumeric character."); + throw new Chat.ErrorMessage("Usage: /roomevents addalias [alias], [event name]. Aliases must contain at least one alphanumeric character."); } if (!room.settings.events || Object.keys(room.settings.events).length === 0) { - return this.errorReply(`There are currently no scheduled events.`); + throw new Chat.ErrorMessage(`There are currently no scheduled events.`); } convertAliasFormat(room); const event = room.settings.events[eventId]; - if (!(event && 'eventName' in event)) return this.errorReply(`There is no event titled "${eventId}".`); - if (room.settings.events[alias]) return this.errorReply(`"${alias}" is already an event, alias, or category.`); + if (!(event && 'eventName' in event)) throw new Chat.ErrorMessage(`There is no event titled "${eventId}".`); + if (room.settings.events[alias]) throw new Chat.ErrorMessage(`"${alias}" is already an event, alias, or category.`); room.settings.events[alias] = { eventID: eventId }; this.privateModAction(`${user.name} added an alias "${alias}" for the roomevent "${eventId}".`); @@ -348,16 +350,16 @@ export const commands: Chat.ChatCommands = { deletealias: 'removealias', removealias(target, room, user) { room = this.requireRoom(); - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); this.checkCan('ban', null, room); target = toID(target); - if (!target) return this.errorReply("Usage: /roomevents removealias "); + if (!target) throw new Chat.ErrorMessage("Usage: /roomevents removealias "); if (!room.settings.events || Object.keys(room.settings.events).length === 0) { - return this.errorReply(`There are currently no scheduled events.`); + throw new Chat.ErrorMessage(`There are currently no scheduled events.`); } convertAliasFormat(room); if (!(room.settings.events[target] && 'eventID' in room.settings.events[target])) { - return this.errorReply(`${target} isn't an alias.`); + throw new Chat.ErrorMessage(`${target} isn't an alias.`); } delete room.settings.events[target]; @@ -368,25 +370,25 @@ export const commands: Chat.ChatCommands = { addtocategory(target, room, user) { room = this.requireRoom(); - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); this.checkCan('ban', null, room); const [eventId, categoryId] = target.split(target.includes('|') ? '|' : ',').map(argument => toID(argument)); - if (!(eventId && categoryId)) return this.errorReply("Usage: /roomevents addtocategory [event name], [category]."); + if (!(eventId && categoryId)) throw new Chat.ErrorMessage("Usage: /roomevents addtocategory [event name], [category]."); if (!room.settings.events || Object.keys(room.settings.events).length === 0) { - return this.errorReply(`There are currently no scheduled events.`); + throw new Chat.ErrorMessage(`There are currently no scheduled events.`); } convertAliasFormat(room); const event = room.settings.events[getEventID(eventId, room)]; - if (!(event && 'eventName' in event)) return this.errorReply(`There is no event or alias titled "${eventId}".`); + if (!(event && 'eventName' in event)) throw new Chat.ErrorMessage(`There is no event or alias titled "${eventId}".`); const category = room.settings.events[categoryId]; if (category && !('events' in category)) { - return this.errorReply(`There is already an event or alias titled "${categoryId}".`); + throw new Chat.ErrorMessage(`There is already an event or alias titled "${categoryId}".`); } if (!category) { - return this.errorReply(`There is no category titled "${categoryId}". To create it, use /roomevents addcategory ${categoryId}.`); + throw new Chat.ErrorMessage(`There is no category titled "${categoryId}". To create it, use /roomevents addcategory ${categoryId}.`); } if (category.events.includes(toID(event.eventName))) { - return this.errorReply(`The event "${eventId}" is already in the "${categoryId}" category.`); + throw new Chat.ErrorMessage(`The event "${eventId}" is already in the "${categoryId}" category.`); } category.events.push(toID(event.eventName)); room.settings.events[categoryId] = category; @@ -399,27 +401,27 @@ export const commands: Chat.ChatCommands = { removefromcategory(target, room, user) { room = this.requireRoom(); - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); this.checkCan('ban', null, room); const [eventId, categoryId] = target.split(target.includes('|') ? '|' : ',').map(argument => toID(argument)); if (!(eventId && categoryId)) { - return this.errorReply("Usage: /roomevents removefromcategory [event name], [category]."); + throw new Chat.ErrorMessage("Usage: /roomevents removefromcategory [event name], [category]."); } if (!room.settings.events || Object.keys(room.settings.events).length === 0) { - return this.errorReply(`There are currently no scheduled events.`); + throw new Chat.ErrorMessage(`There are currently no scheduled events.`); } convertAliasFormat(room); const event = room.settings.events[getEventID(eventId, room)]; - if (!(event && 'eventName' in event)) return this.errorReply(`There is no event or alias titled "${eventId}".`); + if (!(event && 'eventName' in event)) throw new Chat.ErrorMessage(`There is no event or alias titled "${eventId}".`); const category = room.settings.events[categoryId]; if (category && !('events' in category)) { - return this.errorReply(`There is already an event or alias titled "${categoryId}".`); + throw new Chat.ErrorMessage(`There is already an event or alias titled "${categoryId}".`); } - if (!category) return this.errorReply(`There is no category titled "${categoryId}".`); + if (!category) throw new Chat.ErrorMessage(`There is no category titled "${categoryId}".`); if (!category.events.includes(toID(event.eventName))) { - return this.errorReply(`The event "${eventId}" isn't in the "${categoryId}" category.`); + throw new Chat.ErrorMessage(`The event "${eventId}" isn't in the "${categoryId}" category.`); } category.events = category.events.filter(e => e !== eventId); room.settings.events[categoryId] = category; @@ -433,15 +435,15 @@ export const commands: Chat.ChatCommands = { addcat: 'addcategory', addcategory(target, room, user) { room = this.requireRoom(); - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); this.checkCan('ban', null, room); const categoryId = toID(target); if (!target) { - return this.errorReply("Usage: /roomevents addcategory [category name]. Categories must contain at least one alphanumeric character."); + throw new Chat.ErrorMessage("Usage: /roomevents addcategory [category name]. Categories must contain at least one alphanumeric character."); } convertAliasFormat(room); if (!room.settings.events) room.settings.events = Object.create(null); - if (room.settings.events?.[categoryId]) return this.errorReply(`The category "${target}" already exists.`); + if (room.settings.events?.[categoryId]) throw new Chat.ErrorMessage(`The category "${target}" already exists.`); room.settings.events![categoryId] = { events: [] }; @@ -457,13 +459,13 @@ export const commands: Chat.ChatCommands = { rmcat: 'removecategory', removecategory(target, room, user) { room = this.requireRoom(); - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); this.checkCan('ban', null, room); const categoryId = toID(target); - if (!target) return this.errorReply("Usage: /roomevents removecategory [category name]."); + if (!target) throw new Chat.ErrorMessage("Usage: /roomevents removecategory [category name]."); convertAliasFormat(room); if (!room.settings.events) room.settings.events = Object.create(null); - if (!room.settings.events?.[categoryId]) return this.errorReply(`The category "${target}" doesn't exist.`); + if (!room.settings.events?.[categoryId]) throw new Chat.ErrorMessage(`The category "${target}" doesn't exist.`); delete room.settings.events?.[categoryId]; @@ -476,13 +478,13 @@ export const commands: Chat.ChatCommands = { viewcategories: 'categories', categories(target, room, user) { room = this.requireRoom(); - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); this.runBroadcast(); const categoryButtons = getAllCategories(room).map( category => `` ); - if (!categoryButtons.length) return this.errorReply(`There are no roomevent categories in ${room.title}.`); + if (!categoryButtons.length) throw new Chat.ErrorMessage(`There are no roomevent categories in ${room.title}.`); this.sendReplyBox(`Roomevent categories in ${room.title}: ${categoryButtons.join(' ')}`); }, @@ -493,9 +495,9 @@ export const commands: Chat.ChatCommands = { sortby(target, room, user) { room = this.requireRoom(); // preconditions - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); if (!room.settings.events || !Object.keys(room.settings.events).length) { - return this.errorReply("There are currently no planned upcoming events for this room."); + throw new Chat.ErrorMessage("There are currently no planned upcoming events for this room."); } this.checkCan('ban', null, room); @@ -545,7 +547,7 @@ export const commands: Chat.ChatCommands = { ); break; default: - return this.errorReply(`Invalid column name "${columnName}". Please use one of: date, desc, name.`); + throw new Chat.ErrorMessage(`Invalid column name "${columnName}". Please use one of: date, desc, name.`); } // rebuild the room.settings.events object diff --git a/server/chat-plugins/room-faqs.ts b/server/chat-plugins/room-faqs.ts index ef67191e55..4f3f35691b 100644 --- a/server/chat-plugins/room-faqs.ts +++ b/server/chat-plugins/room-faqs.ts @@ -61,26 +61,26 @@ export const commands: Chat.ChatCommands = { const useHTML = this.cmd.includes('html'); this.checkCan('ban', null, room); if (useHTML && !user.can('addhtml', null, room, this.fullCmd)) { - return this.errorReply(`You are not allowed to use raw HTML in roomfaqs.`); + throw new Chat.ErrorMessage(`You are not allowed to use raw HTML in roomfaqs.`); } - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); if (!target) return this.parse('/help roomfaq'); target = target.trim(); const input = this.filter(target); - if (target !== input) return this.errorReply("You are not allowed to use fitered words in roomfaq entries."); + if (target !== input) throw new Chat.ErrorMessage("You are not allowed to use fitered words in roomfaq entries."); let [topic, ...rest] = input.split(','); topic = toID(topic); if (!(topic && rest.length)) return this.parse('/help roomfaq'); let text = rest.join(',').trim(); - if (topic.length > 25) return this.errorReply("FAQ topics should not exceed 25 characters."); + if (topic.length > 25) throw new Chat.ErrorMessage("FAQ topics should not exceed 25 characters."); const lengthWithoutFormatting = Chat.stripFormatting(text).length; if (lengthWithoutFormatting > MAX_ROOMFAQ_LENGTH) { - return this.errorReply(`FAQ entries must not exceed ${MAX_ROOMFAQ_LENGTH} characters.`); + throw new Chat.ErrorMessage(`FAQ entries must not exceed ${MAX_ROOMFAQ_LENGTH} characters.`); } else if (lengthWithoutFormatting < 1) { - return this.errorReply(`FAQ entries must include at least one character.`); + throw new Chat.ErrorMessage(`FAQ entries must include at least one character.`); } if (!useHTML) { @@ -108,7 +108,7 @@ export const commands: Chat.ChatCommands = { const topic = toID(target); if (!topic) return this.parse('/help roomfaq'); - if (!roomFaqs[room.roomid]?.[topic]) return this.errorReply("Invalid topic."); + if (!roomFaqs[room.roomid]?.[topic]) throw new Chat.ErrorMessage("Invalid topic."); if ( room.settings.repeats?.length && room.settings.repeats.filter(x => x.faq && x.id === topic).length @@ -131,21 +131,21 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom(); this.checkChat(); this.checkCan('ban', null, room); - if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms."); const [alias, topic] = target.split(',').map(val => toID(val)); if (!(alias && topic)) return this.parse('/help roomfaq'); - if (alias.length > 25) return this.errorReply("FAQ topics should not exceed 25 characters."); - if (alias === topic) return this.errorReply("You cannot make the alias have the same name as the topic."); + if (alias.length > 25) throw new Chat.ErrorMessage("FAQ topics should not exceed 25 characters."); + if (alias === topic) throw new Chat.ErrorMessage("You cannot make the alias have the same name as the topic."); if (roomFaqs[room.roomid][alias] && !roomFaqs[room.roomid][alias].alias) { - return this.errorReply("You cannot overwrite an existing topic with an alias; please delete the topic first."); + throw new Chat.ErrorMessage("You cannot overwrite an existing topic with an alias; please delete the topic first."); } if (!(roomFaqs[room.roomid] && topic in roomFaqs[room.roomid])) { - return this.errorReply(`The topic ${topic} was not found in this room's faq list.`); + throw new Chat.ErrorMessage(`The topic ${topic} was not found in this room's faq list.`); } if (getAlias(room.roomid, topic)) { - return this.errorReply(`You cannot make an alias of an alias. Use /addalias ${alias}, ${getAlias(room.roomid, topic)} instead.`); + throw new Chat.ErrorMessage(`You cannot make an alias of an alias. Use /addalias ${alias}, ${getAlias(room.roomid, topic)} instead.`); } roomFaqs[room.roomid][alias] = { alias: true, @@ -159,13 +159,13 @@ export const commands: Chat.ChatCommands = { rfaq: 'roomfaq', roomfaq(target, room, user, connection, cmd) { room = this.requireRoom(); - if (!roomFaqs[room.roomid]) return this.errorReply("This room has no FAQ topics."); + if (!roomFaqs[room.roomid]) throw new Chat.ErrorMessage("This room has no FAQ topics."); let topic: string = toID(target); if (topic === 'constructor') return false; if (!topic) { return this.parse(`/join view-roomfaqs-${room.roomid}`); } - if (!roomFaqs[room.roomid][topic]) return this.errorReply("Invalid topic."); + if (!roomFaqs[room.roomid][topic]) throw new Chat.ErrorMessage("Invalid topic."); topic = getAlias(room.roomid, topic) || topic; if (!this.runBroadcast()) return; diff --git a/server/chat-plugins/scavengers.ts b/server/chat-plugins/scavengers.ts index 6ec9756ff6..1622a1d598 100644 --- a/server/chat-plugins/scavengers.ts +++ b/server/chat-plugins/scavengers.ts @@ -1064,7 +1064,7 @@ const ScavengerCommands: Chat.ChatCommands = { join(target, room, user) { room = this.requireRoom(); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply("There is no scavenger hunt currently running."); + if (!game) throw new Chat.ErrorMessage("There is no scavenger hunt currently running."); this.checkChat(); game.joinGame(user); @@ -1073,7 +1073,7 @@ const ScavengerCommands: Chat.ChatCommands = { leave(target, room, user) { room = this.requireRoom(); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply("There is no scavenger hunt currently running."); + if (!game) throw new Chat.ErrorMessage("There is no scavenger hunt currently running."); game.leaveGame(user); }, @@ -1092,15 +1092,15 @@ const ScavengerCommands: Chat.ChatCommands = { start(target, room, user) { room = this.requireRoom(); this.checkCan('mute', null, room); - if (room.scavgame) return this.errorReply('There is already a scavenger game running.'); + if (room.scavgame) throw new Chat.ErrorMessage('There is already a scavenger game running.'); if (room.getGame(ScavengerHunt)) { - return this.errorReply('You cannot start a scavenger game where there is already a scavenger hunt in the room.'); + throw new Chat.ErrorMessage('You cannot start a scavenger game where there is already a scavenger hunt in the room.'); } target = toID(target); const game = ScavMods.LoadGame(room, target); - if (!game) return this.errorReply('Invalid game mode.'); + if (!game) throw new Chat.ErrorMessage('Invalid game mode.'); room.scavgame = game; @@ -1113,7 +1113,7 @@ const ScavengerCommands: Chat.ChatCommands = { end(target, room, user) { room = this.requireRoom(); this.checkCan('mute', null, room); - if (!room.scavgame) return this.errorReply(`There is no scavenger game currently running.`); + if (!room.scavgame) throw new Chat.ErrorMessage(`There is no scavenger game currently running.`); this.privateModAction(`The ${room.scavgame.name} has been forcibly ended by ${user.name}.`); this.modlog('SCAVENGER', null, 'ended the scavenger game'); @@ -1124,10 +1124,10 @@ const ScavengerCommands: Chat.ChatCommands = { kick(target, room, user) { room = this.requireRoom(); this.checkCan('mute', null, room); - if (!room.scavgame) return this.errorReply(`There is no scavenger game currently running.`); + if (!room.scavgame) throw new Chat.ErrorMessage(`There is no scavenger game currently running.`); const targetId = toID(target); - if (targetId === 'constructor' || !targetId) return this.errorReply("Invalid player."); + if (targetId === 'constructor' || !targetId) throw new Chat.ErrorMessage("Invalid player."); const success = room.scavgame.eliminate(targetId); if (success) { @@ -1138,7 +1138,7 @@ const ScavengerCommands: Chat.ChatCommands = { game.eliminate(targetId); // remove player from current hunt as well. } } else { - this.errorReply(`Unable to kick user '${targetId}'.`); + throw new Chat.ErrorMessage(`Unable to kick user '${targetId}'.`); } }, @@ -1147,8 +1147,8 @@ const ScavengerCommands: Chat.ChatCommands = { scoreboard: 'leaderboard', async leaderboard(target, room, user) { room = this.requireRoom(); - if (!room.scavgame) return this.errorReply(`There is no scavenger game currently running.`); - if (!room.scavgame.leaderboard) return this.errorReply("This scavenger game does not have a leaderboard."); + if (!room.scavgame) throw new Chat.ErrorMessage(`There is no scavenger game currently running.`); + if (!room.scavgame.leaderboard) throw new Chat.ErrorMessage("This scavenger game does not have a leaderboard."); if (!this.runBroadcast()) return false; const html = await room.scavgame.leaderboard.htmlLadder(); @@ -1157,8 +1157,8 @@ const ScavengerCommands: Chat.ChatCommands = { async rank(target, room, user) { room = this.requireRoom(); - if (!room.scavgame) return this.errorReply(`There is no scavenger game currently running.`); - if (!room.scavgame.leaderboard) return this.errorReply("This scavenger game does not have a leaderboard."); + if (!room.scavgame) throw new Chat.ErrorMessage(`There is no scavenger game currently running.`); + if (!room.scavgame.leaderboard) throw new Chat.ErrorMessage("This scavenger game does not have a leaderboard."); if (!this.runBroadcast()) return false; const targetId = toID(target) || user.id; @@ -1177,18 +1177,18 @@ const ScavengerCommands: Chat.ChatCommands = { createteam(target, room, user) { room = this.requireRoom(); this.checkCan('mute', null, room); - // if (room.getGame(ScavengerHunt)) return this.errorReply('Teams cannot be modified after the hunt starts.'); + // if (room.getGame(ScavengerHunt)) throw new Chat.ErrorMessage('Teams cannot be modified after the hunt starts.'); const game = room.scavgame; - if (!game || game.id !== 'teamscavs') return this.errorReply('There is currently no game of Team Scavs going on.'); + if (!game || game.id !== 'teamscavs') throw new Chat.ErrorMessage('There is currently no game of Team Scavs going on.'); let [teamName, leader] = target.split(','); teamName = teamName.trim(); - if (game.teams[teamName]) return this.errorReply(`The team ${teamName} already exists.`); + if (game.teams[teamName]) throw new Chat.ErrorMessage(`The team ${teamName} already exists.`); const leaderUser = Users.get(leader); - if (!leaderUser) return this.errorReply('The user you specified is currently not online'); - if (game.getPlayerTeam(leaderUser)) return this.errorReply('The user is already a member of another team.'); + if (!leaderUser) throw new Chat.ErrorMessage('The user you specified is currently not online'); + if (game.getPlayerTeam(leaderUser)) throw new Chat.ErrorMessage('The user is already a member of another team.'); game.teams[teamName] = { name: teamName, answers: [], players: [leaderUser.id], question: 1, completed: false }; game.announce(Utils.html`A new team "${teamName}" has been created with ${leaderUser.name} as the leader.`); @@ -1198,12 +1198,12 @@ const ScavengerCommands: Chat.ChatCommands = { removeteam(target, room, user) { room = this.requireRoom(); this.checkCan('mute', null, room); - // if (room.getGame(ScavengerHunt)) return this.errorReply('Teams cannot be modified after the hunt starts.'); + // if (room.getGame(ScavengerHunt)) throw new Chat.ErrorMessage('Teams cannot be modified after the hunt starts.'); const game = room.scavgame; - if (!game || game.id !== 'teamscavs') return this.errorReply('There is currently no game of Team Scavs going on.'); + if (!game || game.id !== 'teamscavs') throw new Chat.ErrorMessage('There is currently no game of Team Scavs going on.'); - if (!game.teams[target]) return this.errorReply(`The team ${target} does not exist.`); + if (!game.teams[target]) throw new Chat.ErrorMessage(`The team ${target} does not exist.`); delete game.teams[target]; game.announce(Utils.html`The team "${target}" has been removed.`); @@ -1212,8 +1212,8 @@ const ScavengerCommands: Chat.ChatCommands = { addplayer(target, room, user) { room = this.requireRoom(); const game = room.scavgame; - if (!game || game.id !== 'teamscavs') return this.errorReply('There is currently no game of Team Scavs going on.'); - // if (room.getGame(ScavengerHunt)) return this.errorReply('Teams cannot be modified after the hunt starts.'); + if (!game || game.id !== 'teamscavs') throw new Chat.ErrorMessage('There is currently no game of Team Scavs going on.'); + // if (room.getGame(ScavengerHunt)) throw new Chat.ErrorMessage('Teams cannot be modified after the hunt starts.'); let userTeam; @@ -1224,10 +1224,10 @@ const ScavengerCommands: Chat.ChatCommands = { break; } } - if (!userTeam) return this.errorReply('You must be the leader of a team to add people into the team.'); + if (!userTeam) throw new Chat.ErrorMessage('You must be the leader of a team to add people into the team.'); const targetUsers = target.split(',').map(id => Users.getExact(id)).filter(u => u?.connected) as User[]; - if (!targetUsers.length) return this.errorReply('Please select a user that is currently online.'); + if (!targetUsers.length) throw new Chat.ErrorMessage('Please select a user that is currently online.'); const errors = []; for (const targetUser of targetUsers) { @@ -1247,9 +1247,9 @@ const ScavengerCommands: Chat.ChatCommands = { editplayers(target, room, user) { room = this.requireRoom(); const game = room.scavgame; - if (!game || game.id !== 'teamscavs') return this.errorReply('There is currently no game of Team Scavs going on.'); + if (!game || game.id !== 'teamscavs') throw new Chat.ErrorMessage('There is currently no game of Team Scavs going on.'); this.checkCan('mute', null, room); - // if (room.getGame(ScavengerHunt)) return this.errorReply('Teams cannot be modified after the hunt starts.'); + // if (room.getGame(ScavengerHunt)) throw new Chat.ErrorMessage('Teams cannot be modified after the hunt starts.'); const parts = target.split(','); const teamName = parts[0].trim(); @@ -1257,7 +1257,7 @@ const ScavengerCommands: Chat.ChatCommands = { const team = game.teams[teamName]; - if (!team) return this.errorReply('Invalid team.'); + if (!team) throw new Chat.ErrorMessage('Invalid team.'); for (const entry of playerchanges) { const userid = toID(entry); @@ -1298,7 +1298,7 @@ const ScavengerCommands: Chat.ChatCommands = { room = this.requireRoom(); const game = room.scavgame; - if (!game || game.id !== 'teamscavs') return this.errorReply('There is currently no game of Team Scavs going on.'); + if (!game || game.id !== 'teamscavs') throw new Chat.ErrorMessage('There is currently no game of Team Scavs going on.'); const display = []; for (const teamID in game.teams) { @@ -1312,10 +1312,10 @@ const ScavengerCommands: Chat.ChatCommands = { guesses(target, room, user) { room = this.requireRoom(); const game = room.scavgame; - if (!game || game.id !== 'teamscavs') return this.errorReply('There is currently no game of Team Scavs going on.'); + if (!game || game.id !== 'teamscavs') throw new Chat.ErrorMessage('There is currently no game of Team Scavs going on.'); const team = game.getPlayerTeam(user); - if (!team) return this.errorReply('You are not currently part of this Team Scavs game.'); + if (!team) throw new Chat.ErrorMessage('You are not currently part of this Team Scavs game.'); this.sendReplyBox(Utils.html`Question #${team.question} guesses: ${team.answers.sort().join(', ')}`); }, @@ -1324,12 +1324,12 @@ const ScavengerCommands: Chat.ChatCommands = { note(target, room, user) { room = this.requireRoom(); const game = room.scavgame; - if (!game || game.id !== 'teamscavs') return this.errorReply('There is currently no game of Team Scavs going on.'); + if (!game || game.id !== 'teamscavs') throw new Chat.ErrorMessage('There is currently no game of Team Scavs going on.'); const team = game.getPlayerTeam(user); - if (!team) return this.errorReply('You are not currently part of this Team Scavs game.'); + if (!team) throw new Chat.ErrorMessage('You are not currently part of this Team Scavs game.'); - if (!target) return this.errorReply('Please include a message as the note.'); + if (!target) throw new Chat.ErrorMessage('Please include a message as the note.'); game.teamAnnounce(user, Utils.html` Note from ${user.name}: ${target}`); }, @@ -1376,10 +1376,10 @@ const ScavengerCommands: Chat.ChatCommands = { create(target, room, user, connection, cmd) { room = this.requireRoom(); if (!getScavsRoom(room)) { - return this.errorReply("Scavenger hunts can only be created in the scavengers room."); + throw new Chat.ErrorMessage("Scavenger hunts can only be created in the scavengers room."); } this.checkCan('mute', null, room); - if (room.game) return this.errorReply(`There is already a game in this room - ${room.game.title}.`); + if (room.game) throw new Chat.ErrorMessage(`There is already a game in this room - ${room.game.title}.`); let gameType = 'regular' as GameTypes; if (cmd.includes('practice')) { gameType = 'practice'; @@ -1409,18 +1409,18 @@ const ScavengerCommands: Chat.ChatCommands = { !cmd.includes('force') && ['regular', 'unrated', 'recycled'].includes(gameType) && !mod && room.settings.scavQueue?.length && !room.scavgame ) { - return this.errorReply(`There are currently hunts in the queue! If you would like to start the hunt anyways, use /forcestart${gameType === 'regular' ? 'hunt' : gameType}.`); + throw new Chat.ErrorMessage(`There are currently hunts in the queue! If you would like to start the hunt anyways, use /forcestart${gameType === 'regular' ? 'hunt' : gameType}.`); } if (gameType === 'recycled') { if (ScavengerHuntDatabase.isEmpty()) { - return this.errorReply("There are no hunts in the database."); + throw new Chat.ErrorMessage("There are no hunts in the database."); } let hunt; if (questions) { const huntNumber = parseInt(questions); - if (!ScavengerHuntDatabase.hasHunt(huntNumber)) return this.errorReply("You specified an invalid hunt number."); + if (!ScavengerHuntDatabase.hasHunt(huntNumber)) throw new Chat.ErrorMessage("You specified an invalid hunt number."); hunt = scavengersData.recycledHunts[huntNumber - 1]; } else { hunt = ScavengerHuntDatabase.getRecycledHuntFromDatabase(); @@ -1440,11 +1440,11 @@ const ScavengerCommands: Chat.ChatCommands = { gameType === 'official' || gameType === 'recycled' ); if (!hosts.length) { - return this.errorReply("The user(s) you specified as the host is not online, or is not in the room."); + throw new Chat.ErrorMessage("The user(s) you specified as the host is not online, or is not in the room."); } const res = ScavengerHunt.parseQuestions(params); - if (res.err) return this.errorReply(res.err); + if (res.err) throw new Chat.ErrorMessage(res.err); room.game = new ScavengerHunt({ room, @@ -1463,7 +1463,7 @@ const ScavengerCommands: Chat.ChatCommands = { status(target, room, user) { room = this.requireRoom(); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply(`There is no scavenger hunt currently running.`); + if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`); const elapsedMsg = Chat.toDurationString(Date.now() - game.startTime, { hhmmss: true }); const gameTypeMsg = game.gameType ? `${game.gameType} ` : ''; @@ -1508,15 +1508,15 @@ const ScavengerCommands: Chat.ChatCommands = { hint(target, room, user) { room = this.requireRoom(); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply(`There is no scavenger hunt currently running.`); - if (!game.onSendQuestion(user, true)) this.errorReply("You are not currently participating in the hunt."); + if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`); + if (!game.onSendQuestion(user, true)) throw new Chat.ErrorMessage("You are not currently participating in the hunt."); }, timer(target, room, user) { room = this.requireRoom(); this.checkCan('mute', null, room); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply(`There is no scavenger hunt currently running.`); + if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`); const minutes = (toID(target) === 'off' ? 0 : parseFloat(target)); if (isNaN(minutes) || minutes < 0 || (minutes * 60 * 1000) > Chat.MAX_TIMEOUT_DURATION) { @@ -1535,9 +1535,9 @@ const ScavengerCommands: Chat.ChatCommands = { room = this.requireRoom(); this.checkCan('mute', null, room); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply(`There is no scavenger hunt currently running.`); + if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`); - if (game.staffHostId === user.id) return this.errorReply('You already have staff permissions for this hunt.'); + if (game.staffHostId === user.id) throw new Chat.ErrorMessage('You already have staff permissions for this hunt.'); game.staffHostId = `${user.id}`; game.staffHostName = `${user.name}`; @@ -1554,7 +1554,7 @@ const ScavengerCommands: Chat.ChatCommands = { room = this.requireRoom(); this.checkCan('mute', null, room); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply(`There is no scavenger hunt currently running.`); + if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`); game.onEnd(true, user); this.privateModAction(`${user.name} has reset the scavenger hunt.`); @@ -1565,7 +1565,7 @@ const ScavengerCommands: Chat.ChatCommands = { room = this.requireRoom(); this.checkCan('mute', null, room); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply(`There is no scavenger hunt currently running.`); + if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`); const hunt: QueuedHunt = { hosts: game.hosts, @@ -1592,16 +1592,16 @@ const ScavengerCommands: Chat.ChatCommands = { this.checkCan('mute', null, room); if (!room.game && room.scavgame) return this.parse('/scav games end'); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply(`There is no scavenger hunt currently running.`); + if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`); const completed = game.preCompleted ? game.preCompleted : game.completed; if (!this.cmd.includes('force')) { if (!completed.length) { - return this.errorReply('No one has finished the hunt yet. Use /forceendhunt if you want to end the hunt and reveal the answers.'); + throw new Chat.ErrorMessage('No one has finished the hunt yet. Use /forceendhunt if you want to end the hunt and reveal the answers.'); } } else if (completed.length) { - return this.errorReply(`This hunt has ${Chat.count(completed, "finishers")}; use /endhunt`); + throw new Chat.ErrorMessage(`This hunt has ${Chat.count(completed, "finishers")}; use /endhunt`); } game.onEnd(false, user); @@ -1612,9 +1612,9 @@ const ScavengerCommands: Chat.ChatCommands = { viewhunt(target, room, user) { room = this.requireRoom(); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply(`There is no scavenger hunt currently running.`); + if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`); - if (!('onViewHunt' in game)) return this.errorReply('There is currently no hunt to be viewed.'); + if (!('onViewHunt' in game)) throw new Chat.ErrorMessage('There is currently no hunt to be viewed.'); game.onViewHunt(user); }, @@ -1622,12 +1622,12 @@ const ScavengerCommands: Chat.ChatCommands = { edithunt(target, room, user) { room = this.requireRoom(); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply(`There is no scavenger hunt currently running.`); + if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`); if ( (!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) && game.staffHostId !== user.id ) { - return this.errorReply("You cannot edit the hints and answers if you are not the host."); + throw new Chat.ErrorMessage("You cannot edit the hints and answers if you are not the host."); } const [question, type, ...value] = target.split(','); @@ -1640,19 +1640,19 @@ const ScavengerCommands: Chat.ChatCommands = { spoiler(target, room, user) { room = this.requireRoom(); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply(`There is no scavenger hunt currently running.`); + if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`); if ( (!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) && game.staffHostId !== user.id ) { - return this.errorReply("You cannot add more hints if you are not the host."); + throw new Chat.ErrorMessage("You cannot add more hints if you are not the host."); } const parts = target.split(','); const question = parseInt(parts[0]) - 1; const hint = parts.slice(1).join(','); - if (!game.questions[question]) return this.errorReply(`Invalid question number.`); - if (!hint) return this.errorReply('The hint cannot be left empty.'); + if (!game.questions[question]) throw new Chat.ErrorMessage(`Invalid question number.`); + if (!hint) throw new Chat.ErrorMessage('The hint cannot be left empty.'); game.questions[question].spoilers.push(hint); room.addByUser(user, `Question #${question + 1} hint - spoiler: ${hint}`); @@ -1669,20 +1669,20 @@ const ScavengerCommands: Chat.ChatCommands = { removehint(target, room, user) { room = this.requireRoom(); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply(`There is no scavenger hunt currently running.`); + if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`); if ( (!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) && game.staffHostId !== user.id ) { - return this.errorReply("You cannot remove hints if you are not the host."); + throw new Chat.ErrorMessage("You cannot remove hints if you are not the host."); } const parts = target.split(','); const question = parseInt(parts[0]) - 1; const hint = parseInt(parts[1]) - 1; - if (!game.questions[question]) return this.errorReply(`Invalid question number.`); - if (!game.questions[question].spoilers[hint]) return this.errorReply('Invalid hint number.'); + if (!game.questions[question]) throw new Chat.ErrorMessage(`Invalid question number.`); + if (!game.questions[question].spoilers[hint]) throw new Chat.ErrorMessage('Invalid hint number.'); game.questions[question].spoilers.splice(hint, 1); return this.sendReply("Hint has been removed."); @@ -1692,12 +1692,12 @@ const ScavengerCommands: Chat.ChatCommands = { edithint(target, room, user) { room = this.requireRoom(); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply(`There is no scavenger hunt currently running.`); + if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`); if ( (!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) && game.staffHostId !== user.id ) { - return this.errorReply("You cannot edit hints if you are not the host."); + throw new Chat.ErrorMessage("You cannot edit hints if you are not the host."); } const parts = target.split(','); @@ -1705,9 +1705,9 @@ const ScavengerCommands: Chat.ChatCommands = { const hint = parseInt(parts[1]) - 1; const value = parts.slice(2).join(','); - if (!game.questions[question]) return this.errorReply(`Invalid question number.`); - if (!game.questions[question].spoilers[hint]) return this.errorReply('Invalid hint number.'); - if (!value) return this.errorReply('The hint cannot be left empty.'); + if (!game.questions[question]) throw new Chat.ErrorMessage(`Invalid question number.`); + if (!game.questions[question].spoilers[hint]) throw new Chat.ErrorMessage('Invalid hint number.'); + if (!value) throw new Chat.ErrorMessage('The hint cannot be left empty.'); game.questions[question].spoilers[hint] = value; room.addByUser(user, `Question #${question + 1} hint - spoiler: ${value}`); @@ -1724,17 +1724,17 @@ const ScavengerCommands: Chat.ChatCommands = { kick(target, room, user) { room = this.requireRoom(); const game = room.getGame(ScavengerHunt); - if (!game) return this.errorReply(`There is no scavenger hunt currently running.`); + if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`); const targetId = toID(target); - if (targetId === 'constructor' || !targetId) return this.errorReply("Invalid player."); + if (targetId === 'constructor' || !targetId) throw new Chat.ErrorMessage("Invalid player."); const success = game.eliminate(targetId); if (success) { this.modlog('SCAV KICK', targetId); return this.privateModAction(`${user.name} has kicked '${targetId}' from the scavenger hunt.`); } - this.errorReply(`Unable to kick '${targetId}' from the scavenger hunt.`); + throw new Chat.ErrorMessage(`Unable to kick '${targetId}' from the scavenger hunt.`); }, /** @@ -1758,7 +1758,7 @@ const ScavengerCommands: Chat.ChatCommands = { queue(target, room, user) { room = this.requireRoom(); if (!getScavsRoom(room)) { - return this.errorReply("This command can only be used in the scavengers room."); + throw new Chat.ErrorMessage("This command can only be used in the scavengers room."); } if (!target && this.cmd !== 'queuerecycled') { if (this.cmd === 'queue') { @@ -1776,7 +1776,7 @@ const ScavengerCommands: Chat.ChatCommands = { if (this.cmd === 'queuerecycled') { if (ScavengerHuntDatabase.isEmpty()) { - return this.errorReply(`There are no hunts in the database.`); + throw new Chat.ErrorMessage(`There are no hunts in the database.`); } if (!room.settings.scavQueue) { room.settings.scavQueue = []; @@ -1785,7 +1785,7 @@ const ScavengerCommands: Chat.ChatCommands = { let next; if (target) { const huntNumber = parseInt(target); - if (!ScavengerHuntDatabase.hasHunt(huntNumber)) return this.errorReply("You specified an invalid hunt number."); + if (!ScavengerHuntDatabase.hasHunt(huntNumber)) throw new Chat.ErrorMessage("You specified an invalid hunt number."); next = scavengersData.recycledHunts[huntNumber - 1]; } else { next = ScavengerHuntDatabase.getRecycledHuntFromDatabase(); @@ -1803,11 +1803,11 @@ const ScavengerCommands: Chat.ChatCommands = { const [hostsArray, ...params] = target.split('|'); const hosts = ScavengerHunt.parseHosts(hostsArray.split(/[,;]/), room); if (!hosts.length) { - return this.errorReply("The user(s) you specified as the host is not online, or is not in the room."); + throw new Chat.ErrorMessage("The user(s) you specified as the host is not online, or is not in the room."); } const results = ScavengerHunt.parseQuestions(params, this.cmd.includes('force')); - if (results.err) return this.errorReply(results.err); + if (results.err) throw new Chat.ErrorMessage(results.err); if (!room.settings.scavQueue) room.settings.scavQueue = []; @@ -1828,7 +1828,7 @@ const ScavengerCommands: Chat.ChatCommands = { dequeue(target, room, user) { room = this.requireRoom(); if (!getScavsRoom(room)) { - return this.errorReply("This command can only be used in the scavengers room."); + throw new Chat.ErrorMessage("This command can only be used in the scavengers room."); } this.checkCan('mute', null, room); const id = parseInt(target); @@ -1846,7 +1846,7 @@ const ScavengerCommands: Chat.ChatCommands = { viewqueue(target, room, user) { room = this.requireRoom(); if (!getScavsRoom(room)) { - return this.errorReply("This command can only be used in the scavengers room."); + throw new Chat.ErrorMessage("This command can only be used in the scavengers room."); } if (!this.runBroadcast()) return false; @@ -1856,14 +1856,14 @@ const ScavengerCommands: Chat.ChatCommands = { next(target, room, user) { room = this.requireRoom(); if (!getScavsRoom(room)) { - return this.errorReply("This command can only be used in the scavengers room."); + throw new Chat.ErrorMessage("This command can only be used in the scavengers room."); } this.checkCan('mute', null, room); if (!room.settings.scavQueue?.length) { - return this.errorReply("The scavenger hunt queue is currently empty."); + throw new Chat.ErrorMessage("The scavenger hunt queue is currently empty."); } - if (room.game) return this.errorReply(`There is already a game in this room - ${room.game.title}.`); + if (room.game) throw new Chat.ErrorMessage(`There is already a game in this room - ${room.game.title}.`); const huntId = parseInt(target) || 0; @@ -1892,14 +1892,14 @@ const ScavengerCommands: Chat.ChatCommands = { disablequeue(target, room, user) { room = this.requireRoom(); if (!getScavsRoom(room)) { - return this.errorReply("This command can only be used in the scavengers room."); + throw new Chat.ErrorMessage("This command can only be used in the scavengers room."); } this.checkCan('mute', null, room); if (!room.settings.scavSettings) room.settings.scavSettings = {}; const state = this.cmd === 'disablequeue'; if ((room.settings.scavSettings.scavQueueDisabled || false) === state) { - return this.errorReply(`The queue is already ${state ? 'disabled' : 'enabled'}.`); + throw new Chat.ErrorMessage(`The queue is already ${state ? 'disabled' : 'enabled'}.`); } room.settings.scavSettings.scavQueueDisabled = state; @@ -1913,7 +1913,7 @@ const ScavengerCommands: Chat.ChatCommands = { defaulttimer(target, room, user) { room = this.requireRoom(); if (!getScavsRoom(room)) { - return this.errorReply("This command can only be used in the scavengers room."); + throw new Chat.ErrorMessage("This command can only be used in the scavengers room."); } this.checkCan('declare', null, room); @@ -1925,7 +1925,7 @@ const ScavengerCommands: Chat.ChatCommands = { const duration = parseInt(target); if (!duration || duration < 0) { - return this.errorReply('The default timer must be an integer greater than zero, in minutes.'); + throw new Chat.ErrorMessage('The default timer must be an integer greater than zero, in minutes.'); } room.settings.scavSettings.defaultScavTimer = duration; @@ -1945,8 +1945,10 @@ const ScavengerCommands: Chat.ChatCommands = { const targetId = toID(parts[0]); const points = parseInt(parts[1]); - if (!targetId || targetId === 'constructor' || targetId.length > 18) return this.errorReply("Invalid username."); - if (!points || points < 0 || points > 1000) return this.errorReply("Points must be an integer between 1 and 1000."); + if (!targetId || targetId === 'constructor' || targetId.length > 18) throw new Chat.ErrorMessage("Invalid username."); + if (!points || points < 0 || points > 1000) { + throw new Chat.ErrorMessage("Points must be an integer between 1 and 1000."); + } Leaderboard.addPoints(targetId, 'points', points, true).write(); @@ -1962,8 +1964,10 @@ const ScavengerCommands: Chat.ChatCommands = { const targetId = toID(parts[0]); const points = parseInt(parts[1]); - if (!targetId || targetId === 'constructor' || targetId.length > 18) return this.errorReply("Invalid username."); - if (!points || points < 0 || points > 1000) return this.errorReply("Points must be an integer between 1 and 1000."); + if (!targetId || targetId === 'constructor' || targetId.length > 18) throw new Chat.ErrorMessage("Invalid username."); + if (!points || points < 0 || points > 1000) { + throw new Chat.ErrorMessage("Points must be an integer between 1 and 1000."); + } Leaderboard.addPoints(targetId, 'points', -points, true).write(); @@ -1984,7 +1988,7 @@ const ScavengerCommands: Chat.ChatCommands = { async ladder(target, room, user) { room = this.requireRoom(); if (!getScavsRoom(room)) { - return this.errorReply("This command can only be used in the scavengers room."); + throw new Chat.ErrorMessage("This command can only be used in the scavengers room."); } if (!this.runBroadcast()) return false; @@ -2010,7 +2014,7 @@ const ScavengerCommands: Chat.ChatCommands = { async rank(target, room, user) { room = this.requireRoom(); if (!getScavsRoom(room)) { - return this.errorReply("This command can only be used in the scavengers room."); + throw new Chat.ErrorMessage("This command can only be used in the scavengers room."); } if (!this.runBroadcast()) return false; @@ -2031,7 +2035,7 @@ const ScavengerCommands: Chat.ChatCommands = { room = this.requireRoom(); const scavsRoom = getScavsRoom(room); if (!scavsRoom) { - return this.errorReply("This command can only be used in the scavengers room."); + throw new Chat.ErrorMessage("This command can only be used in the scavengers room."); } this.checkCan('mute', null, room); // perms for viewing only @@ -2050,10 +2054,10 @@ const ScavengerCommands: Chat.ChatCommands = { const parts = target.split(','); const blitzPoints = parseInt(parts[1]); const gameType = toID(parts[0]) as GameTypes; - if (!RATED_TYPES.includes(gameType)) return this.errorReply(`You cannot set blitz points for ${gameType} hunts.`); + if (!RATED_TYPES.includes(gameType)) throw new Chat.ErrorMessage(`You cannot set blitz points for ${gameType} hunts.`); if (isNaN(blitzPoints) || blitzPoints < 0 || blitzPoints > 1000) { - return this.errorReply("The points value awarded for blitz must be an integer bewteen 0 and 1000."); + throw new Chat.ErrorMessage("The points value awarded for blitz must be an integer bewteen 0 and 1000."); } if (!room.settings.scavSettings.blitzPoints) room.settings.scavSettings.blitzPoints = {}; room.settings.scavSettings.blitzPoints[gameType] = blitzPoints; @@ -2078,7 +2082,7 @@ const ScavengerCommands: Chat.ChatCommands = { room = this.requireRoom(); const scavsRoom = getScavsRoom(room); if (!scavsRoom) { - return this.errorReply("This command can only be used in the scavengers room."); + throw new Chat.ErrorMessage("This command can only be used in the scavengers room."); } this.checkCan('mute', null, room); // perms for viewing only if (!room.settings.scavSettings) room.settings.scavSettings = {}; @@ -2090,7 +2094,7 @@ const ScavengerCommands: Chat.ChatCommands = { this.checkCan('declare', null, room); // perms for editting const points = parseInt(target); - if (isNaN(points)) return this.errorReply(`${target} is not a valid number of points.`); + if (isNaN(points)) throw new Chat.ErrorMessage(`${target} is not a valid number of points.`); room.settings.scavSettings.hostPoints = points; room.saveSettings(); @@ -2112,7 +2116,7 @@ const ScavengerCommands: Chat.ChatCommands = { room = this.requireRoom(); const scavsRoom = getScavsRoom(room); if (!scavsRoom) { - return this.errorReply("This command can only be used in the scavengers room."); + throw new Chat.ErrorMessage("This command can only be used in the scavengers room."); } this.checkCan('mute', null, room); // perms for viewing only if (!room.settings.scavSettings) room.settings.scavSettings = {}; @@ -2133,11 +2137,11 @@ const ScavengerCommands: Chat.ChatCommands = { let [type, ...pointsSet] = target.split(','); type = toID(type) as GameTypes; - if (!RATED_TYPES.includes(type)) return this.errorReply(`You cannot set win points for ${type} hunts.`); + if (!RATED_TYPES.includes(type)) throw new Chat.ErrorMessage(`You cannot set win points for ${type} hunts.`); const winPoints = pointsSet.map(p => parseInt(p)); if (winPoints.some(p => isNaN(p) || p < 0 || p > 1000) || !winPoints.length) { - return this.errorReply("The points value awarded for winning a scavenger hunt must be an integer between 0 and 1000."); + throw new Chat.ErrorMessage("The points value awarded for winning a scavenger hunt must be an integer between 0 and 1000."); } if (!room.settings.scavSettings.winPoints) room.settings.scavSettings.winPoints = {}; @@ -2165,7 +2169,7 @@ const ScavengerCommands: Chat.ChatCommands = { room = this.requireRoom(); const scavsRoom = getScavsRoom(room); if (!scavsRoom) { - return this.errorReply("This command can only be used in the scavengers room."); + throw new Chat.ErrorMessage("This command can only be used in the scavengers room."); } if (this.cmd.includes('reset')) target = 'RESET'; @@ -2179,7 +2183,7 @@ const ScavengerCommands: Chat.ChatCommands = { room.settings.scavSettings.officialtwist = null; } else { const twist = toID(target); - if (!ScavMods.twists[twist] || twist === 'constructor') return this.errorReply('Invalid twist.'); + if (!ScavMods.twists[twist] || twist === 'constructor') throw new Chat.ErrorMessage('Invalid twist.'); room.settings.scavSettings.officialtwist = twist; room.saveSettings(); @@ -2212,7 +2216,7 @@ const ScavengerCommands: Chat.ChatCommands = { twists(target, room, user) { room = this.requireRoom(); if (!getScavsRoom(room)) { - return this.errorReply("This command can only be used in the scavengers room."); + throw new Chat.ErrorMessage("This command can only be used in the scavengers room."); } this.checkCan('mute', null, room); if (!this.runBroadcast()) return false; @@ -2335,7 +2339,7 @@ const ScavengerCommands: Chat.ChatCommands = { this.checkCan('mute', null, room); const targetId = toID(target); - if (!targetId) return this.errorReply(`Please include the name of the user to ${this.cmd}.`); + if (!targetId) throw new Chat.ErrorMessage(`Please include the name of the user to ${this.cmd}.`); const change = this.cmd === 'infract' ? 1 : -1; PlayerLeaderboard.addPoints(targetId, 'infraction', change, true).write(); @@ -2393,7 +2397,7 @@ const ScavengerCommands: Chat.ChatCommands = { room = this.requireRoom(); this.checkCan('mute', null, room); if (!getScavsRoom(room)) { - return this.errorReply("Scavenger Hunts can only be added to the database in the scavengers room."); + throw new Chat.ErrorMessage("Scavenger Hunts can only be added to the database in the scavengers room."); } let cmd; @@ -2405,23 +2409,23 @@ const ScavengerCommands: Chat.ChatCommands = { } if (cmd === 'addhunt') { - if (!target) return this.errorReply(`Usage: ${cmd} Hunt Text`); + if (!target) throw new Chat.ErrorMessage(`Usage: ${cmd} Hunt Text`); const [hostsArray, ...questions] = target.split('|'); const hosts = ScavengerHunt.parseHosts(hostsArray.split(/[,;]/), room, true); if (!hosts.length) { - return this.errorReply("You need to specify a host."); + throw new Chat.ErrorMessage("You need to specify a host."); } const result = ScavengerHunt.parseQuestions(questions); - if (result.err) return this.errorReply(result.err); + if (result.err) throw new Chat.ErrorMessage(result.err); ScavengerHuntDatabase.addRecycledHuntToDatabase(hosts, result.result); return this.privateModAction(`A recycled hunt has been added to the database.`); } // The rest of the commands depend on there already being hunts in the database. - if (ScavengerHuntDatabase.isEmpty()) return this.errorReply("There are no hunts in the database."); + if (ScavengerHuntDatabase.isEmpty()) throw new Chat.ErrorMessage("There are no hunts in the database."); if (cmd === 'list') { return this.parse(`/join view-recycledHunts-${room}`); @@ -2435,7 +2439,7 @@ const ScavengerCommands: Chat.ChatCommands = { 'removehint': 'Usage: removehint hunt number, question number, hint text', 'autostart': 'Usage: autostart on/off', }; - if (!params) return this.errorReply(usageMessages[cmd]); + if (!params) throw new Chat.ErrorMessage(usageMessages[cmd]); const numberOfRequiredParameters: { [k: string]: number } = { 'removehunt': 1, @@ -2443,12 +2447,12 @@ const ScavengerCommands: Chat.ChatCommands = { 'removehint': 3, 'autostart': 1, }; - if (params.length < numberOfRequiredParameters[cmd]) return this.errorReply(usageMessages[cmd]); + if (params.length < numberOfRequiredParameters[cmd]) throw new Chat.ErrorMessage(usageMessages[cmd]); const [huntNumber, questionNumber, hintNumber] = params.map(param => parseInt(param)); const cmdsNeedingHuntNumber = ['removehunt', 'removehint', 'addhint']; if (cmdsNeedingHuntNumber.includes(cmd)) { - if (!ScavengerHuntDatabase.hasHunt(huntNumber)) return this.errorReply("You specified an invalid hunt number."); + if (!ScavengerHuntDatabase.hasHunt(huntNumber)) throw new Chat.ErrorMessage("You specified an invalid hunt number."); } const cmdsNeedingQuestionNumber = ['addhint', 'removehint']; @@ -2458,7 +2462,7 @@ const ScavengerCommands: Chat.ChatCommands = { questionNumber <= 0 || questionNumber > scavengersData.recycledHunts[huntNumber - 1].questions.length ) { - return this.errorReply("You specified an invalid question number."); + throw new Chat.ErrorMessage("You specified an invalid question number."); } } @@ -2466,7 +2470,7 @@ const ScavengerCommands: Chat.ChatCommands = { if (cmdsNeedingHintNumber.includes(cmd)) { const numQuestions = scavengersData.recycledHunts[huntNumber - 1].questions.length; if (isNaN(questionNumber) || questionNumber <= 0 || questionNumber > numQuestions) { - return this.errorReply("You specified an invalid hint number."); + throw new Chat.ErrorMessage("You specified an invalid hint number."); } } @@ -2482,9 +2486,9 @@ const ScavengerCommands: Chat.ChatCommands = { return this.privateModAction(`Hint #${hintNumber} was removed from Recycled hunt #${huntNumber} question #${questionNumber}.`); } else if (cmd === 'autostart') { if (!room.settings.scavSettings) room.settings.scavSettings = {}; - if (params[0] !== 'on' && params[0] !== 'off') return this.errorReply(usageMessages[cmd]); + if (params[0] !== 'on' && params[0] !== 'off') throw new Chat.ErrorMessage(usageMessages[cmd]); if ((params[0] === 'on') === !!room.settings.scavSettings.addRecycledHuntsToQueueAutomatically) { - return this.errorReply(`Autostarting recycled hunts is already ${room.settings.scavSettings.addRecycledHuntsToQueueAutomatically ? 'on' : 'off'}.`); + throw new Chat.ErrorMessage(`Autostarting recycled hunts is already ${room.settings.scavSettings.addRecycledHuntsToQueueAutomatically ? 'on' : 'off'}.`); } room.settings.scavSettings.addRecycledHuntsToQueueAutomatically = !room.settings.scavSettings.addRecycledHuntsToQueueAutomatically; @@ -2632,7 +2636,7 @@ export const commands: Chat.ChatCommands = { scavhelp: 'scavengershelp', scavengershelp(target, room, user) { if (!room || !getScavsRoom(room)) { - return this.errorReply("This command can only be used in the scavengers room."); + throw new Chat.ErrorMessage("This command can only be used in the scavengers room."); } if (!this.runBroadcast()) return false; diff --git a/server/chat-plugins/seasons.ts b/server/chat-plugins/seasons.ts index 664c615dee..1f73b90407 100644 --- a/server/chat-plugins/seasons.ts +++ b/server/chat-plugins/seasons.ts @@ -285,7 +285,7 @@ export const pages: Chat.PageTable = { const format = toID(query.shift()); const season = toID(query.shift()) || `${data.current.season}`; if (!data.badgeholders[season]) { - return this.errorReply(`Season ${season} not found.`); + throw new Chat.ErrorMessage(`Season ${season} not found.`); } this.title = `[Seasons]`; let buf = '
'; diff --git a/server/chat-plugins/smogtours.ts b/server/chat-plugins/smogtours.ts index 64fc073f68..453a3567d8 100644 --- a/server/chat-plugins/smogtours.ts +++ b/server/chat-plugins/smogtours.ts @@ -118,7 +118,7 @@ export const commands: Chat.ChatCommands = { } const section = tours[sectionID]; if (!section) { - return this.errorReply(`Invalid section ID: "${sectionID}"`); + return this.popupReply(`Invalid section ID: "${sectionID}"`); } if (!isEdit && section.tours.find(f => toID(title) === f.id)) { return this.popupReply(`A tour with that ID already exists. Please choose another.`); @@ -152,7 +152,7 @@ export const commands: Chat.ChatCommands = { if (rawCredit || rawArtistName) { // if one exists, both should, as verified above const artistUrl = (Chat.linkRegex.exec(rawCredit))?.[0]; if (!artistUrl) { - return this.errorReply(`Invalid artist credit URL.`); + return this.popupReply(`Invalid artist credit URL.`); } artistCredit = { url: artistUrl, name: rawArtistName.trim() }; } @@ -205,10 +205,10 @@ export const commands: Chat.ChatCommands = { } const section = tours[sectionID]; if (!section) { - return this.errorReply(`Invalid section ID: "${sectionID}". Valid IDs: ${Object.keys(tours).join(', ')}`); + throw new Chat.ErrorMessage(`Invalid section ID: "${sectionID}". Valid IDs: ${Object.keys(tours).join(', ')}`); } if (section.whitelist?.includes(targetID)) { - return this.errorReply(`That user is already whitelisted on that section.`); + throw new Chat.ErrorMessage(`That user is already whitelisted on that section.`); } if (!section.whitelist) section.whitelist = []; section.whitelist.push(targetID); @@ -226,11 +226,11 @@ export const commands: Chat.ChatCommands = { } const section = tours[sectionID]; if (!section) { - return this.errorReply(`Invalid section ID: "${sectionID}". Valid IDs: ${Object.keys(tours).join(', ')}`); + throw new Chat.ErrorMessage(`Invalid section ID: "${sectionID}". Valid IDs: ${Object.keys(tours).join(', ')}`); } const idx = section.whitelist?.indexOf(targetID) ?? -1; if (!section.whitelist || idx < 0) { - return this.errorReply(`${targetID} is not whitelisted in that section.`); + throw new Chat.ErrorMessage(`${targetID} is not whitelisted in that section.`); } section.whitelist.splice(idx, 1); if (!section.whitelist.length) { diff --git a/server/chat-plugins/suspect-tests.ts b/server/chat-plugins/suspect-tests.ts index 8e3537a835..f46d7fca6d 100644 --- a/server/chat-plugins/suspect-tests.ts +++ b/server/chat-plugins/suspect-tests.ts @@ -78,7 +78,7 @@ export const commands: Chat.ChatCommands = { const reqData: Record = {}; if (!reqs.length) { - return this.errorReply("At least one requirement for qualifying must be provided."); + throw new Chat.ErrorMessage("At least one requirement for qualifying must be provided."); } for (const req of reqs) { let [k, v] = req.split('='); @@ -88,17 +88,17 @@ export const commands: Chat.ChatCommands = { continue; } if (!['elo', 'gxe', 'coil'].includes(k)) { - return this.errorReply(`Invalid requirement type: ${k}. Must be 'coil', 'gxe', or 'elo'.`); + throw new Chat.ErrorMessage(`Invalid requirement type: ${k}. Must be 'coil', 'gxe', or 'elo'.`); } if (k === 'coil' && !reqs.some(x => toID(x).startsWith('b'))) { throw new Chat.ErrorMessage("COIL reqs are specified, but you have not provided a B value (with the argument `b=num`)"); } const val = Number(v); if (isNaN(val) || val < 0) { - return this.errorReply(`Invalid value: ${v}`); + throw new Chat.ErrorMessage(`Invalid value: ${v}`); } if (reqData[k]) { - return this.errorReply(`Requirement type ${k} specified twice.`); + throw new Chat.ErrorMessage(`Requirement type ${k} specified twice.`); } reqData[k] = val; } @@ -131,7 +131,7 @@ export const commands: Chat.ChatCommands = { const format = toID(target); const test = suspectTests.suspects[format]; - if (!test) return this.errorReply(`There is no suspect test for '${target}'. Check spelling?`); + if (!test) throw new Chat.ErrorMessage(`There is no suspect test for '${target}'. Check spelling?`); const [out, error] = await LoginServer.request('suspects/end', { format, diff --git a/server/chat-plugins/teams.ts b/server/chat-plugins/teams.ts index 1a7589064e..fe8252d773 100644 --- a/server/chat-plugins/teams.ts +++ b/server/chat-plugins/teams.ts @@ -458,7 +458,7 @@ export const commands: Chat.ChatCommands = { const teamData = await TeamsHandler.get(teamid); if (!teamData) return this.popupReply(`Team not found.`); if (teamData.ownerid !== user.id && !user.can('rangeban')) { - return this.errorReply("You cannot delete teams you do not own."); + throw new Chat.ErrorMessage("You cannot delete teams you do not own."); } await TeamsHandler.delete(teamid); this.popupReply(`Team ${teamid} deleted.`); @@ -598,11 +598,11 @@ export const pages: Chat.PageTable = { const team = await TeamsHandler.get(teamid); if (!team) { this.title = `[Invalid Team]`; - return this.errorReply(`No team with the ID ${teamid} was found.`); + throw new Chat.ErrorMessage(`No team with the ID ${teamid} was found.`); } if (team?.private && user.id !== team.ownerid && password !== team.private) { this.title = `[Private Team]`; - return this.errorReply(`That team is private.`); + throw new Chat.ErrorMessage(`That team is private.`); } this.title = `[Team] ${team.teamid}`; if (user.id !== team.ownerid && team.views >= 0) { @@ -639,12 +639,12 @@ export const pages: Chat.PageTable = { TeamsHandler.validateAccess(connection); const teamID = toID(query.shift() || ""); if (!teamID.length) { - return this.errorReply(`Invalid team ID.`); + throw new Chat.ErrorMessage(`Invalid team ID.`); } this.title = `[Edit Team] ${teamID}`; const data = await TeamsHandler.get(teamID); if (!data) { - return this.errorReply(`Team ${teamID} not found.`); + throw new Chat.ErrorMessage(`Team ${teamID} not found.`); } let buf = `

Edit team ${teamID}

${refresh(this)}
`; // let [teamName, formatid, rawPrivacy, rawTeam] = Utils.splitFirst(target, ',', 4); @@ -702,15 +702,15 @@ export const pages: Chat.PageTable = { const [rawOwner, rawFormat, rawPokemon, rawMoves, rawAbilities, rawGen] = query; const owner = toID(rawOwner); if (owner.length > 18) { - return this.errorReply(`Invalid owner name. Names must be under 18 characters long.`); + throw new Chat.ErrorMessage(`Invalid owner name. Names must be under 18 characters long.`); } const format = toID(rawFormat); if (format && !Dex.formats.get(format).exists) { - return this.errorReply(`Format ${format} not found.`); + throw new Chat.ErrorMessage(`Format ${format} not found.`); } const gen = Number(rawGen); if (rawGen && (isNaN(gen) || (gen < 1 || gen > Dex.gen))) { - return this.errorReply(`Invalid generation: '${rawGen}'`); + throw new Chat.ErrorMessage(`Invalid generation: '${rawGen}'`); } const pokemon = rawPokemon?.split(',').map(toID).filter(Boolean); @@ -764,7 +764,7 @@ export const pages: Chat.PageTable = { queryStr += ` ORDER BY date DESC`; break; default: - return this.errorReply(`Invalid sort term '${sorter}'. Must be either 'views' or 'latest'.`); + throw new Chat.ErrorMessage(`Invalid sort term '${sorter}'. Must be either 'views' or 'latest'.`); } queryStr += ` LIMIT ${count}`; let buf = `

Browse ${name} teams

`; diff --git a/server/chat-plugins/the-studio.ts b/server/chat-plugins/the-studio.ts index 911db3ccf4..1580746c8b 100644 --- a/server/chat-plugins/the-studio.ts +++ b/server/chat-plugins/the-studio.ts @@ -470,7 +470,7 @@ export const commands: Chat.ChatCommands = { async lastfm(target, room, user) { this.checkChat(); - if (!user.autoconfirmed) return this.errorReply(`You cannot use this command while not autoconfirmed.`); + if (!user.autoconfirmed) throw new Chat.ErrorMessage(`You cannot use this command while not autoconfirmed.`); this.runBroadcast(true); const targetUsername = this.splitUser(target).targetUsername || (user.named ? user.name : ''); const username = LastFM.getAccountName(targetUsername); @@ -484,7 +484,7 @@ export const commands: Chat.ChatCommands = { async track(target, room, user) { if (!target) return this.parse('/help track'); this.checkChat(); - if (!user.autoconfirmed) return this.errorReply(`You cannot use this command while not autoconfirmed.`); + if (!user.autoconfirmed) throw new Chat.ErrorMessage(`You cannot use this command while not autoconfirmed.`); const [track, artist] = this.splitOne(target); if (!track) return this.parse('/help track'); this.runBroadcast(true); @@ -541,7 +541,7 @@ export const commands: Chat.ChatCommands = { return this.parse('/help suggestrecommendation'); } this.checkChat(target); - if (!user.autoconfirmed) return this.errorReply(`You cannot use this command while not autoconfirmed.`); + if (!user.autoconfirmed) throw new Chat.ErrorMessage(`You cannot use this command while not autoconfirmed.`); const [artist, title, url, description, ...tags] = target.split('|').map(x => x.trim()); if (!(artist && title && url && description && tags?.length)) { return this.parse(`/help suggestrecommendation`); diff --git a/server/chat-plugins/thing-of-the-day.ts b/server/chat-plugins/thing-of-the-day.ts index 0c6f1d62c3..2b32154176 100644 --- a/server/chat-plugins/thing-of-the-day.ts +++ b/server/chat-plugins/thing-of-the-day.ts @@ -502,14 +502,12 @@ export const otdCommands: Chat.ChatCommands = { const handler = selectHandler(this.message); - if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`); - if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`); + if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`); + if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`); this.checkCan('mute', null, room); if (handler.voting) { - return this.errorReply( - `The nomination for the ${handler.name} of the ${handler.timeLabel} nomination is already in progress.` - ); + throw new Chat.ErrorMessage(`The nomination for the ${handler.name} of the ${handler.timeLabel} nomination is already in progress.`); } handler.startVote(); @@ -523,15 +521,15 @@ export const otdCommands: Chat.ChatCommands = { const handler = selectHandler(this.message); - if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`); - if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`); + if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`); + if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`); this.checkCan('mute', null, room); if (!handler.voting) { - return this.errorReply(`There is no ${handler.name} of the ${handler.timeLabel} nomination in progress.`); + throw new Chat.ErrorMessage(`There is no ${handler.name} of the ${handler.timeLabel} nomination in progress.`); } if (!handler.nominations.size) { - return this.errorReply(`Can't select the ${handler.name} of the ${handler.timeLabel} without nominations.`); + throw new Chat.ErrorMessage(`Can't select the ${handler.name} of the ${handler.timeLabel} without nominations.`); } handler.rollWinner(); @@ -548,8 +546,8 @@ export const otdCommands: Chat.ChatCommands = { const handler = selectHandler(this.message); - if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`); - if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`); + if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`); + if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`); if (!toNominationId(target).length || target.length > 75) { return this.sendReply(`'${target}' is not a valid ${handler.name.toLowerCase()} name.`); @@ -564,8 +562,8 @@ export const otdCommands: Chat.ChatCommands = { const handler = selectHandler(this.message); - if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`); - if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`); + if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`); + if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`); if (this.broadcasting) { selectHandler(this.message).display(); @@ -580,12 +578,12 @@ export const otdCommands: Chat.ChatCommands = { const handler = selectHandler(this.message); - if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`); - if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`); + if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`); + if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`); this.checkCan('mute', null, room); const userid = toID(target); - if (!userid) return this.errorReply(`'${target}' is not a valid username.`); + if (!userid) throw new Chat.ErrorMessage(`'${target}' is not a valid username.`); if (handler.removeNomination(userid)) { this.privateModAction(`${user.name} removed ${target}'s nomination for the ${handler.name} of the ${handler.timeLabel}.`); @@ -622,8 +620,8 @@ export const otdCommands: Chat.ChatCommands = { const handler = selectHandler(this.message); - if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`); - if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`); + if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`); + if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`); this.checkCan('declare', null, room); if (!toNominationId(target).length || target.length > 50) { @@ -643,12 +641,12 @@ export const otdCommands: Chat.ChatCommands = { const handler = selectHandler(this.message); - if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`); - if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`); + if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`); + if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`); this.checkCan('mute', null, room); if (!(handler.voting && handler.timer)) { - return this.errorReply(`There is no ${handler.name} of the ${handler.timeLabel} nomination to disable the timer for.`); + throw new Chat.ErrorMessage(`There is no ${handler.name} of the ${handler.timeLabel} nomination to disable the timer for.`); } clearTimeout(handler.timer); @@ -663,8 +661,8 @@ export const otdCommands: Chat.ChatCommands = { const handler = selectHandler(this.message); - if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`); - if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`); + if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`); + if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`); this.checkCan('mute', null, room); const params = target.split(target.includes('|') ? '|' : ',').map(param => param.trim()); @@ -673,13 +671,13 @@ export const otdCommands: Chat.ChatCommands = { for (const param of params) { let [key, ...values] = param.split(':'); - if (!key || !values.length) return this.errorReply(`Syntax error in '${param}'`); + if (!key || !values.length) throw new Chat.ErrorMessage(`Syntax error in '${param}'`); key = key.trim(); const value = values.join(':').trim(); if (!handler.keys.includes(key)) { - return this.errorReply(`Invalid key: '${key}'. Valid keys: ${handler.keys.join(', ')}`); + throw new Chat.ErrorMessage(`Invalid key: '${key}'. Valid keys: ${handler.keys.join(', ')}`); } switch (key) { @@ -690,38 +688,38 @@ export const otdCommands: Chat.ChatCommands = { case 'book': case 'author': case 'athlete': - if (!toNominationId(value) || value.length > 50) return this.errorReply(`Please enter a valid ${key} name.`); + if (!toNominationId(value) || value.length > 50) throw new Chat.ErrorMessage(`Please enter a valid ${key} name.`); break; case 'quote': case 'tagline': case 'match': case 'event': case 'videogame': - if (!value.length || value.length > 150) return this.errorReply(`Please enter a valid ${key}.`); + if (!value.length || value.length > 150) throw new Chat.ErrorMessage(`Please enter a valid ${key}.`); break; case 'sport': case 'team': case 'song': case 'country': - if (!value.length || value.length > 50) return this.errorReply(`Please enter a valid ${key} name.`); + if (!value.length || value.length > 50) throw new Chat.ErrorMessage(`Please enter a valid ${key} name.`); break; case 'link': case 'image': if (!/https?:\/\//.test(value)) { - return this.errorReply(`Please enter a valid URL for the ${key} (starting with http:// or https://)`); + throw new Chat.ErrorMessage(`Please enter a valid URL for the ${key} (starting with http:// or https://)`); } - if (value.length > 200) return this.errorReply("URL too long."); + if (value.length > 200) throw new Chat.ErrorMessage("URL too long."); break; case 'age': const num = parseInt(value); // let's assume someone isn't over 100 years old? Maybe we should for the memes // but i doubt there's any legit athlete over 100. - if (isNaN(num) || num < 1 || num > 100) return this.errorReply('Please enter a valid number as an age'); + if (isNaN(num) || num < 1 || num > 100) throw new Chat.ErrorMessage('Please enter a valid number as an age'); break; default: // another custom key w/o validation if (!toNominationId(value)) { - return this.errorReply(`No value provided for key ${key}.`); + throw new Chat.ErrorMessage(`No value provided for key ${key}.`); } break; } @@ -772,8 +770,8 @@ export const otdCommands: Chat.ChatCommands = { const handler = selectHandler(this.message); - if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`); - if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`); + if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`); + if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`); return this.parse(`/join view-${handler.id}`); }, @@ -784,12 +782,12 @@ export const otdCommands: Chat.ChatCommands = { if (!this.runBroadcast()) return false; const handler = selectHandler(this.message); - if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`); + if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`); - if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`); + if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`); const text = await handler.generateWinnerDisplay(); - if (!text) return this.errorReply("There is no winner yet."); + if (!text) throw new Chat.ErrorMessage("There is no winner yet."); this.sendReplyBox(text); }, }; @@ -800,11 +798,11 @@ export const commands: Chat.ChatCommands = { create(target, room, user) { room = this.requireRoom(); if (room.settings.isPrivate) { - return this.errorReply(`This command is only available in public rooms`); + throw new Chat.ErrorMessage(`This command is only available in public rooms`); } const count = [...otds.values()].filter(otd => otd.room.roomid === room.roomid).length; if (count > 3) { - return this.errorReply(`This room already has 3+ -otd's.`); + throw new Chat.ErrorMessage(`This room already has 3+ -otd's.`); } this.checkCan('rangeban'); @@ -813,17 +811,19 @@ export const commands: Chat.ChatCommands = { } const [title, time, ...keyLabels] = target.split(',').map(i => i.trim()); if (!toID(title)) { - return this.errorReply(`Invalid title.`); + throw new Chat.ErrorMessage(`Invalid title.`); } const timeLabel = toID(time); if (!['week', 'day'].includes(timeLabel)) { - return this.errorReply("Invalid time label - use 'week' or 'month'"); + throw new Chat.ErrorMessage("Invalid time label - use 'week' or 'month'"); } const id = `${title.charAt(0)}ot${timeLabel.charAt(0)}`; const existing = otds.get(id); if (existing) { - this.errorReply(`That -OTD already exists (${existing.name} of the ${existing.timeLabel}, in ${existing.room.title})`); - return this.errorReply(`Try picking a new title.`); + throw new Chat.ErrorMessage([ + `That -OTD already exists (${existing.name} of the ${existing.timeLabel}, in ${existing.room.title})`, + `Try picking a new title.`, + ]); } const titleIdx = keyLabels.map(toID).indexOf(toID(title)); if (titleIdx > -1) { @@ -833,16 +833,16 @@ export const commands: Chat.ChatCommands = { const filteredKeys = keyLabels.map(toNominationId).filter(Boolean); if (!filteredKeys.length) { - return this.errorReply(`No valid key labels given.`); + throw new Chat.ErrorMessage(`No valid key labels given.`); } if (new Set(filteredKeys).size !== keyLabels.length) { - return this.errorReply(`Invalid keys in set - do not use duplicate key labels.`); + throw new Chat.ErrorMessage(`Invalid keys in set - do not use duplicate key labels.`); } if (filteredKeys.length < 3) { - return this.errorReply(`Specify at least 3 key labels.`); + throw new Chat.ErrorMessage(`Specify at least 3 key labels.`); } if (filteredKeys.some(k => k.length < 3 || k.length > 50)) { - return this.errorReply(`All labels must be more than 3 characters and less than 50 characters long.`); + throw new Chat.ErrorMessage(`All labels must be more than 3 characters and less than 50 characters long.`); } const otd = OtdHandler.create(room, { keyLabels, keys: filteredKeys, title, timeLabel, roomid: room.roomid, @@ -860,11 +860,11 @@ export const commands: Chat.ChatCommands = { } const otd = otds.get(otdId); if (!otd) { - return this.errorReply(`OTD ${otd} not found.`); + throw new Chat.ErrorMessage(`OTD ${otd} not found.`); } const targetRoom = Rooms.get(roomid); if (!targetRoom) { - return this.errorReply(`Room ${roomid} not found.`); + throw new Chat.ErrorMessage(`Room ${roomid} not found.`); } const oldRoom = otd.settings.roomid.slice(); otd.settings.roomid = targetRoom.roomid; @@ -882,7 +882,7 @@ export const commands: Chat.ChatCommands = { return this.parse(`/help otd`); } const otd = otds.get(target); - if (!otd) return this.errorReply(`OTD ${target} not found.`); + if (!otd) throw new Chat.ErrorMessage(`OTD ${target} not found.`); otd.destroy(); this.globalModlog(`OTD DELETE`, null, target); this.privateGlobalModAction(`${user.name} deleted the OTD ${otd.name} of the ${otd.timeLabel}`); diff --git a/server/chat-plugins/trivia/trivia.ts b/server/chat-plugins/trivia/trivia.ts index 09c797fdce..4e5e149785 100644 --- a/server/chat-plugins/trivia/trivia.ts +++ b/server/chat-plugins/trivia/trivia.ts @@ -1520,11 +1520,11 @@ const triviaCommands: Chat.ChatCommands = { this.checkCan('show', null, room); this.checkChat(); if (room.game) { - return this.errorReply(this.tr`There is already a game of ${room.game.title} in progress.`); + throw new Chat.ErrorMessage(this.tr`There is already a game of ${room.game.title} in progress.`); } const targets = (target ? target.split(',') : []); - if (targets.length < 3) return this.errorReply("Usage: /trivia new [mode], [category], [length]"); + if (targets.length < 3) throw new Chat.ErrorMessage("Usage: /trivia new [mode], [category], [length]"); let mode: string = toID(targets[0]); if (['triforce', 'tri'].includes(mode)) mode = 'triumvirate'; @@ -1533,7 +1533,7 @@ const triviaCommands: Chat.ChatCommands = { const acceptableModes = Object.keys(MODES).filter(curMode => curMode !== 'first'); mode = Utils.shuffle(acceptableModes)[0]; } - if (!MODES[mode]) return this.errorReply(this.tr`"${mode}" is an invalid mode.`); + if (!MODES[mode]) throw new Chat.ErrorMessage(this.tr`"${mode}" is an invalid mode.`); let categories: ID[] | 'random' = targets[1] .split('+') @@ -1551,23 +1551,23 @@ const triviaCommands: Chat.ChatCommands = { let length: ID | number = toID(targets[2]); if (!LENGTHS[length]) { length = parseInt(length); - if (isNaN(length) || length < 1) return this.errorReply(this.tr`"${length}" is an invalid game length.`); + if (isNaN(length) || length < 1) throw new Chat.ErrorMessage(this.tr`"${length}" is an invalid game length.`); } // Assume that infinite mode will last for at least 75 points const questionsNecessary = typeof length === 'string' ? (LENGTHS[length].cap || 75) / 5 : length; if (questions.length < questionsNecessary) { if (categories === 'random') { - return this.errorReply( + throw new Chat.ErrorMessage( this.tr`There are not enough questions in the randomly chosen category to finish a trivia game.` ); } if (categories.length === 1 && categories[0] === 'all') { - return this.errorReply( + throw new Chat.ErrorMessage( this.tr`There are not enough questions in the trivia database to finish a trivia game.` ); } - return this.errorReply( + throw new Chat.ErrorMessage( this.tr`There are not enough questions under the specified categories to finish a trivia game.` ); } @@ -1642,7 +1642,7 @@ const triviaCommands: Chat.ChatCommands = { } const answer = toID(target); - if (!answer) return this.errorReply(this.tr`No valid answer was entered.`); + if (!answer) throw new Chat.ErrorMessage(this.tr`No valid answer was entered.`); if (room.game?.gameid === 'trivia' && !Object.keys(game.playerTable).includes(user.id)) { game.addTriviaPlayer(user); @@ -1683,7 +1683,7 @@ const triviaCommands: Chat.ChatCommands = { const game = getTriviaGame(room); if (game.game.length !== 'infinite' && !user.can('editroom', null, room)) { - return this.errorReply( + throw new Chat.ErrorMessage( this.tr`Only Room Owners and higher can force a Trivia game to end with winners in a non-infinite length.` ); } @@ -1699,7 +1699,7 @@ const triviaCommands: Chat.ChatCommands = { const game = getTriviaGame(room); const targetUser = this.getUserOrSelf(target); - if (!targetUser) return this.errorReply(this.tr`User ${target} does not exist.`); + if (!targetUser) throw new Chat.ErrorMessage(this.tr`User ${target} does not exist.`); let buffer = `${game.isPaused ? this.tr`There is a paused trivia game` : this.tr`There is a trivia game in progress`}, ` + this.tr`and it is in its ${game.phase} phase.` + `
` + this.tr`Mode: ${game.game.mode} | Category: ${game.game.category} | Cap: ${game.getDisplayableCap()}`; @@ -1710,7 +1710,7 @@ const triviaCommands: Chat.ChatCommands = { buffer += `
${this.tr`Current score: ${player.points} | Correct Answers: ${player.correctAnswers}`}`; } } else if (targetUser.id !== user.id) { - return this.errorReply(this.tr`User ${targetUser.name} is not a player in the current trivia game.`); + throw new Chat.ErrorMessage(this.tr`User ${targetUser.name} is not a player in the current trivia game.`); } buffer += `
${this.tr`Players: ${game.formatPlayerList({ max: null, requirePoints: false })}`}`; @@ -1881,7 +1881,7 @@ const triviaCommands: Chat.ChatCommands = { const indicesLen = indices.length; if (!indicesLen) { - return this.errorReply( + throw new Chat.ErrorMessage( this.tr`'${target}' is not a valid set of submission index numbers.\n` + this.tr`View /trivia review and /trivia help for more information.` ); @@ -1899,7 +1899,7 @@ const triviaCommands: Chat.ChatCommands = { return this.privateModAction(`${user.name} ${message} from the submission database.`); } - this.errorReply(this.tr`'${target}' is an invalid argument. View /trivia help questions for more information.`); + throw new Chat.ErrorMessage(this.tr`'${target}' is an invalid argument. View /trivia help questions for more information.`); }, accepthelp: [`/trivia accept [index1], [index2], ... [indexn] OR all - Add questions from the submission database to the question database using their index numbers or ranges of them. Requires: @ # ~`], rejecthelp: [`/trivia reject [index1], [index2], ... [indexn] OR all - Remove questions from the submission database using their index numbers or ranges of them. Requires: @ # ~`], @@ -1914,7 +1914,7 @@ const triviaCommands: Chat.ChatCommands = { const question = Utils.escapeHTML(target).trim(); if (!question) { - return this.errorReply(this.tr`'${target}' is not a valid argument. View /trivia help questions for more information.`); + throw new Chat.ErrorMessage(this.tr`'${target}' is not a valid argument. View /trivia help questions for more information.`); } const { category } = await database.ensureQuestionExists(question); @@ -2112,7 +2112,7 @@ const triviaCommands: Chat.ChatCommands = { const category = CATEGORY_ALIASES[target] || target; if (category === 'random') return false; if (!ALL_CATEGORIES[category]) { - return this.errorReply(this.tr`'${target}' is not a valid category. View /help trivia for more information.`); + throw new Chat.ErrorMessage(this.tr`'${target}' is not a valid category. View /help trivia for more information.`); } const list = await database.getQuestions([category], Number.MAX_SAFE_INTEGER, { order: 'oldestfirst' }); @@ -2158,7 +2158,7 @@ const triviaCommands: Chat.ChatCommands = { type = target; } else { [type, ...query] = target.split(','); - if (!target.includes(',')) return this.errorReply(this.tr`No valid search arguments entered.`); + if (!target.includes(',')) throw new Chat.ErrorMessage(this.tr`No valid search arguments entered.`); } type = toID(type); @@ -2176,7 +2176,7 @@ const triviaCommands: Chat.ChatCommands = { let queryString = query.join(','); if (cmd !== 'doublespacesearch') queryString = queryString.trim(); - if (!queryString) return this.errorReply(this.tr`No valid search query was entered.`); + if (!queryString) throw new Chat.ErrorMessage(this.tr`No valid search query was entered.`); const results = await database.searchQuestions(queryString, options); if (!results.length) return this.sendReply(this.tr`No results found under the ${type} list.`); @@ -2305,7 +2305,7 @@ const triviaCommands: Chat.ChatCommands = { if (cmd.includes('score')) leaderboard = 'nonAlltime'; const ladder = (await cachedLadder.get(leaderboard))?.ladder; - if (!ladder?.length) return this.errorReply(this.tr`No Trivia games have been played yet.`); + if (!ladder?.length) throw new Chat.ErrorMessage(this.tr`No Trivia games have been played yet.`); let buffer = "|raw|
" + ``; @@ -2340,9 +2340,10 @@ const triviaCommands: Chat.ChatCommands = { if (user.lastCommand !== '/trivia resetcycleleaderboard') { user.lastCommand = '/trivia resetcycleleaderboard'; - this.errorReply(`Are you sure you want to reset the Trivia cycle-specific leaderboard? This action is IRREVERSIBLE.`); - this.errorReply(`To confirm, retype the command.`); - return; + throw new Chat.ErrorMessage([ + `Are you sure you want to reset the Trivia cycle-specific leaderboard? This action is IRREVERSIBLE.`, + `To confirm, retype the command.`, + ]); } user.lastCommand = ''; @@ -2364,10 +2365,10 @@ const triviaCommands: Chat.ChatCommands = { this.modlog(`TRIVIA CATEGORY CLEAR`, null, SPECIAL_CATEGORIES[category]); return this.privateModAction(room.tr`${user.name} removed all questions of category '${category}'.`); } else { - return this.errorReply(this.tr`You cannot clear the category '${ALL_CATEGORIES[category]}'.`); + throw new Chat.ErrorMessage(this.tr`You cannot clear the category '${ALL_CATEGORIES[category]}'.`); } } else { - return this.errorReply(this.tr`'${category}' is an invalid category.`); + throw new Chat.ErrorMessage(this.tr`'${category}' is an invalid category.`); } }, clearqshelp: [`/trivia clearqs [category] - Remove all questions of the given category. Requires: # ~`], @@ -2416,7 +2417,7 @@ const triviaCommands: Chat.ChatCommands = { const [userid, pointString] = this.splitOne(target).map(toID); const points = parseInt(pointString); - if (isNaN(points)) return this.errorReply(`You must specify a number of points to add/remove.`); + if (isNaN(points)) throw new Chat.ErrorMessage(`You must specify a number of points to add/remove.`); const isRemoval = cmd === 'removepoints'; const change = { score: isRemoval ? -points : points, totalPoints: 0, totalCorrectAnswers: 0 }; @@ -2447,7 +2448,7 @@ const triviaCommands: Chat.ChatCommands = { const userid = toID(target); if (!userid) return this.parse('/help trivia removeleaderboardentry'); if (!(await database.getLeaderboardEntry(userid, 'alltime'))) { - return this.errorReply(`The user '${userid}' has no Trivia leaderboard entry.`); + throw new Chat.ErrorMessage(`The user '${userid}' has no Trivia leaderboard entry.`); } const command = `/trivia removeleaderboardentry ${userid}`; @@ -2581,7 +2582,7 @@ const mastermindCommands: Chat.ChatCommands = { const finalists = parseInt(target); if (isNaN(finalists) || finalists < 2) { - return this.errorReply(this.tr`You must specify a number that is at least 2 for finalists.`); + throw new Chat.ErrorMessage(this.tr`You must specify a number that is at least 2 for finalists.`); } room.game = new Mastermind(room, finalists); @@ -2601,17 +2602,17 @@ const mastermindCommands: Chat.ChatCommands = { category = CATEGORY_ALIASES[category] || category; if (!(category in ALL_CATEGORIES)) { - return this.errorReply(this.tr`${category} is not a valid category.`); + throw new Chat.ErrorMessage(this.tr`${category} is not a valid category.`); } const categoryName = ALL_CATEGORIES[category]; const timeout = parseInt(timeoutString); if (isNaN(timeout) || timeout < 1 || (timeout * 1000) > Chat.MAX_TIMEOUT_DURATION) { - return this.errorReply(this.tr`You must specify a round length of at least 1 second.`); + throw new Chat.ErrorMessage(this.tr`You must specify a round length of at least 1 second.`); } const questions = await getQuestions([category], 'random'); if (!questions.length) { - return this.errorReply(this.tr`There are no questions in the ${categoryName} category.`); + throw new Chat.ErrorMessage(this.tr`There are no questions in the ${categoryName} category.`); } game.startRound(player, category, questions, timeout); @@ -2629,7 +2630,7 @@ const mastermindCommands: Chat.ChatCommands = { const timeout = parseInt(target); if (isNaN(timeout) || timeout < 1 || (timeout * 1000) > Chat.MAX_TIMEOUT_DURATION) { - return this.errorReply(this.tr`You must specify a length of at least 1 second.`); + throw new Chat.ErrorMessage(this.tr`You must specify a length of at least 1 second.`); } await game.startFinals(timeout); @@ -2652,9 +2653,9 @@ const mastermindCommands: Chat.ChatCommands = { pass(target, room, user) { room = this.requireRoom(); const round = getMastermindGame(room).currentRound; - if (!round) return this.errorReply(this.tr`No round of Mastermind is currently being played.`); + if (!round) throw new Chat.ErrorMessage(this.tr`No round of Mastermind is currently being played.`); if (!(user.id in round.playerTable)) { - return this.errorReply(this.tr`You are not a player in the current round of Mastermind.`); + throw new Chat.ErrorMessage(this.tr`You are not a player in the current round of Mastermind.`); } round.pass(); }, diff --git a/server/chat-plugins/usersearch.tsx b/server/chat-plugins/usersearch.tsx index b7aeddf116..0b1685cc42 100644 --- a/server/chat-plugins/usersearch.tsx +++ b/server/chat-plugins/usersearch.tsx @@ -147,7 +147,7 @@ export const commands: Chat.ChatCommands = { this.checkCan('lock'); const targets = target.split(',').map(toID).filter(Boolean); if (!targets.length) { - return this.errorReply(`Specify at least one term.`); + throw new Chat.ErrorMessage(`Specify at least one term.`); } for (const [i, arg] of targets.entries()) { if (nameList.has(arg)) { @@ -164,7 +164,7 @@ export const commands: Chat.ChatCommands = { } if (!targets.length) { // fuck you too, "mia added 0 term to the usersearch name list" - return this.errorReply(`No terms could be added.`); + throw new Chat.ErrorMessage(`No terms could be added.`); } const count = Chat.count(targets, 'terms'); Rooms.get('staff')?.addByUser( @@ -180,7 +180,7 @@ export const commands: Chat.ChatCommands = { this.checkCan('lock'); const targets = target.split(',').map(toID).filter(Boolean); if (!targets.length) { - return this.errorReply(`Specify at least one term.`); + throw new Chat.ErrorMessage(`Specify at least one term.`); } for (const [i, arg] of targets.entries()) { if (!nameList.has(arg)) { @@ -191,7 +191,7 @@ export const commands: Chat.ChatCommands = { nameList.delete(arg); } if (!targets.length) { - return this.errorReply(`No terms could be removed.`); + throw new Chat.ErrorMessage(`No terms could be removed.`); } const count = Chat.count(targets, 'terms'); Rooms.get('staff')?.addByUser( diff --git a/server/chat-plugins/wifi.tsx b/server/chat-plugins/wifi.tsx index 72607088d4..f7688e1a08 100644 --- a/server/chat-plugins/wifi.tsx +++ b/server/chat-plugins/wifi.tsx @@ -948,19 +948,21 @@ export const commands: Chat.ChatCommands = { param => param.trim() ); if (!(giver && amountStr && summary && deposit && lookfor)) { - return this.errorReply("Invalid arguments specified - /gts start giver | amount | summary | deposit | lookfor"); + throw new Chat.ErrorMessage("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."); + throw new Chat.ErrorMessage("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?.connected) return this.errorReply(`User '${giver}' is not online.`); + if (!targetUser?.connected) throw new Chat.ErrorMessage(`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.`); + throw new Chat.ErrorMessage(`User '${targetUser.name}' needs to be autoconfirmed to host a giveaway.`); + } + if (Giveaway.checkBanned(room, targetUser)) { + throw new Chat.ErrorMessage(`User '${targetUser.name}' is giveaway banned.`); } - if (Giveaway.checkBanned(room, targetUser)) return this.errorReply(`User '${targetUser.name}' is giveaway banned.`); room.subGame = new GTS(room, targetUser, amount, summary, deposit, lookfor); @@ -981,8 +983,8 @@ export const commands: Chat.ChatCommands = { return this.sendReply(output); } const newamount = parseInt(target); - if (isNaN(newamount)) return this.errorReply("Please enter a valid amount."); - if (newamount > game.left) return this.errorReply("The new amount must be lower than the old amount."); + if (isNaN(newamount)) throw new Chat.ErrorMessage("Please enter a valid amount."); + if (newamount > game.left) throw new Chat.ErrorMessage("The new amount must be lower than the old amount."); if (newamount < game.left - 1) { this.modlog(`GTS GIVEAWAY`, null, `set from ${game.left} to ${newamount} left`); } @@ -993,10 +995,10 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom('wifi' as RoomID); const game = this.requireGame(GTS, true); if (!user.can('warn', null, room) && user !== game.giver) { - return this.errorReply("Only the host or a staff member can update GTS giveaways."); + throw new Chat.ErrorMessage("Only the host or a staff member can update GTS giveaways."); } - if (!target || target.length > 12) return this.errorReply("Please enter a valid IGN."); + if (!target || target.length > 12) throw new Chat.ErrorMessage("Please enter a valid IGN."); game.updateSent(target); }, @@ -1004,9 +1006,9 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom('wifi' as RoomID); const game = this.requireGame(GTS, true); if (!user.can('warn', null, room) && user !== game.giver) { - return this.errorReply("Only the host or a staff member can update GTS giveaways."); + throw new Chat.ErrorMessage("Only the host or a staff member can update GTS giveaways."); } - if (game.noDeposits) return this.errorReply("The GTS giveaway was already set to not accept deposits."); + if (game.noDeposits) throw new Chat.ErrorMessage("The GTS giveaway was already set to not accept deposits."); game.stopDeposits(); }, @@ -1016,7 +1018,7 @@ export const commands: Chat.ChatCommands = { this.checkCan('warn', null, room); if (target && target.length > 300) { - return this.errorReply("The reason is too long. It cannot exceed 300 characters."); + throw new Chat.ErrorMessage("The reason is too long. It cannot exceed 300 characters."); } const amount = game.end(true); if (target) target = `: ${target}`; @@ -1095,10 +1097,10 @@ export const commands: Chat.ChatCommands = { const { targetUser, rest: reason } = this.requireUser(target, { allowOffline: true }); if (reason.length > 300) { - return this.errorReply("The reason is too long. It cannot exceed 300 characters."); + throw new Chat.ErrorMessage("The reason is too long. It cannot exceed 300 characters."); } if (Punishments.hasRoomPunishType(room, targetUser.name, 'GIVEAWAYBAN')) { - return this.errorReply(`User '${targetUser.name}' is already giveawaybanned.`); + throw new Chat.ErrorMessage(`User '${targetUser.name}' is already giveawaybanned.`); } const duration = cmd === 'monthban' ? 30 * DAY : cmd === 'permaban' ? 3650 * DAY : 7 * DAY; @@ -1119,7 +1121,7 @@ export const commands: Chat.ChatCommands = { const { targetUser } = this.requireUser(target, { allowOffline: true }); if (!Giveaway.checkBanned(room, targetUser)) { - return this.errorReply(`User '${targetUser.name}' isn't banned from entering giveaways.`); + throw new Chat.ErrorMessage(`User '${targetUser.name}' isn't banned from entering giveaways.`); } Giveaway.unban(room, targetUser); @@ -1183,7 +1185,7 @@ export const commands: Chat.ChatCommands = { if (user.id !== game.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."); + throw new Chat.ErrorMessage("The reason is too long. It cannot exceed 300 characters."); } game.end(true); this.modlog('GIVEAWAY END', null, target); @@ -1396,7 +1398,7 @@ export const commands: Chat.ChatCommands = { if (cmd.includes('un')) { const idx = wifiData.whitelist.indexOf(targetId); if (idx < 0) { - return this.errorReply(`'${targetId}' is not whitelisted.`); + throw new Chat.ErrorMessage(`'${targetId}' is not whitelisted.`); } wifiData.whitelist.splice(idx, 1); this.privateModAction(`${user.name} removed '${targetId}' from the giveaway whitelist.`); @@ -1404,7 +1406,7 @@ export const commands: Chat.ChatCommands = { saveData(); } else { if (wifiData.whitelist.includes(targetId)) { - return this.errorReply(`'${targetId}' is already whitelisted.`); + throw new Chat.ErrorMessage(`'${targetId}' is already whitelisted.`); } wifiData.whitelist.push(targetId); this.privateModAction(`${user.name} added ${targetId} to the giveaway whitelist.`); diff --git a/server/chat-plugins/youtube.ts b/server/chat-plugins/youtube.ts index d3d58fb3c5..a74af39681 100644 --- a/server/chat-plugins/youtube.ts +++ b/server/chat-plugins/youtube.ts @@ -494,7 +494,7 @@ export function destroy() { export const commands: Chat.ChatCommands = { async randchannel(target, room, user) { room = this.requireRoom('internetexplorers' as RoomID); - if (Object.keys(YouTube.data.channels).length < 1) return this.errorReply(`No channels in the database.`); + if (Object.keys(YouTube.data.channels).length < 1) throw new Chat.ErrorMessage(`No channels in the database.`); target = toID(target); this.runBroadcast(); const data = await YouTube.randChannel(target); @@ -508,7 +508,7 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom('internetexplorers' as RoomID); this.checkCan('mute', null, room); const [id, name] = target.split(',').map(t => t.trim()); - if (!id) return this.errorReply('Specify a channel ID.'); + if (!id) throw new Chat.ErrorMessage('Specify a channel ID.'); await YouTube.getChannelData(id, name); this.modlog('ADDCHANNEL', null, `${id} ${name ? `username: ${name}` : ''}`); return this.privateModAction( @@ -521,7 +521,7 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom('internetexplorers' as RoomID); this.checkCan('mute', null, room); const id = YouTube.channelSearch(target); - if (!id) return this.errorReply(`Channel with ID or name ${target} not found.`); + if (!id) throw new Chat.ErrorMessage(`Channel with ID or name ${target} not found.`); delete YouTube.data.channels[id]; YouTube.save(); this.privateModAction(`${user.name} deleted channel with ID or name ${target}.`); @@ -532,7 +532,7 @@ export const commands: Chat.ChatCommands = { async channel(target, room, user) { room = this.requireRoom('internetexplorers' as RoomID); const channel = YouTube.channelSearch(target); - if (!channel) return this.errorReply(`No channels with ID or name ${target} found.`); + if (!channel) throw new Chat.ErrorMessage(`No channels with ID or name ${target} found.`); const data = await YouTube.generateChannelDisplay(channel); this.runBroadcast(); return this.sendReply(`|html|${data}`); @@ -565,7 +565,7 @@ export const commands: Chat.ChatCommands = { this.checkCan('mute', null, room); const [channel, name] = target.split(','); const id = YouTube.channelSearch(channel); - if (!id) return this.errorReply(`Channel ${channel} is not in the database.`); + if (!id) throw new Chat.ErrorMessage(`Channel ${channel} is not in the database.`); YouTube.data.channels[id].username = name; this.modlog(`UPDATECHANNEL`, null, name); this.privateModAction(`${user.name} updated channel ${id}'s username to ${name}.`); @@ -576,19 +576,19 @@ export const commands: Chat.ChatCommands = { room = this.requireRoom('internetexplorers' as RoomID); this.checkCan('declare', null, room); if (!target) { - if (!YouTube.interval) return this.errorReply(`The YouTube plugin is not currently running an interval.`); + if (!YouTube.interval) throw new Chat.ErrorMessage(`The YouTube plugin is not currently running an interval.`); return this.sendReply(`Interval is currently set to ${Chat.toDurationString(YouTube.intervalTime * 60 * 1000)}.`); } if (this.meansNo(target)) { - if (!YouTube.interval) return this.errorReply(`The interval is not currently running`); + if (!YouTube.interval) throw new Chat.ErrorMessage(`The interval is not currently running`); clearInterval(YouTube.interval); delete YouTube.data.intervalTime; YouTube.save(); this.privateModAction(`${user.name} turned off the YouTube interval`); return this.modlog(`YOUTUBE INTERVAL`, null, 'OFF'); } - if (Object.keys(channelData).length < 1) return this.errorReply(`No channels in the database.`); - if (isNaN(parseInt(target))) return this.errorReply(`Specify a number (in minutes) for the interval.`); + if (Object.keys(channelData).length < 1) throw new Chat.ErrorMessage(`No channels in the database.`); + if (isNaN(parseInt(target))) throw new Chat.ErrorMessage(`Specify a number (in minutes) for the interval.`); YouTube.runInterval(target); YouTube.save(); this.privateModAction(`${user.name} set a randchannel interval to ${target} minutes`); @@ -600,7 +600,7 @@ export const commands: Chat.ChatCommands = { const categoryID = toID(target); if (!categoryID) return this.parse(`/help youtube`); if (YouTube.data.categories.map(toID).includes(categoryID)) { - return this.errorReply(`This category is already added. To change it, remove it and re-add it.`); + throw new Chat.ErrorMessage(`This category is already added. To change it, remove it and re-add it.`); } YouTube.data.categories.push(target); this.modlog(`YOUTUBE ADDCATEGORY`, null, target); @@ -614,7 +614,7 @@ export const commands: Chat.ChatCommands = { if (!categoryID) return this.parse(`/help youtube`); const index = YouTube.data.categories.indexOf(target); if (index < 0) { - return this.errorReply(`${target} is not a valid category.`); + throw new Chat.ErrorMessage(`${target} is not a valid category.`); } for (const id in YouTube.data.channels) { const channel = YouTube.data.channels[id]; @@ -633,10 +633,10 @@ export const commands: Chat.ChatCommands = { return this.parse('/help youtube'); } if (!YouTube.data.categories.includes(category)) { - return this.errorReply(`Invalid category.`); + throw new Chat.ErrorMessage(`Invalid category.`); } const name = YouTube.channelSearch(id); - if (!name) return this.errorReply(`Invalid channel.`); + if (!name) throw new Chat.ErrorMessage(`Invalid channel.`); const channel = YouTube.data.channels[name]; YouTube.data.channels[name].category = category; YouTube.save(); @@ -651,10 +651,10 @@ export const commands: Chat.ChatCommands = { return this.parse('/help youtube'); } const name = YouTube.channelSearch(target); - if (!name) return this.errorReply(`Invalid channel.`); + if (!name) throw new Chat.ErrorMessage(`Invalid channel.`); const channel = YouTube.data.channels[name]; const category = channel.category; - if (!category) return this.errorReply(`That channel does not have a category.`); + if (!category) throw new Chat.ErrorMessage(`That channel does not have a category.`); delete channel.category; YouTube.save(); this.modlog(`YOUTUBE DECATEGORIZE`, null, target); @@ -681,11 +681,11 @@ export const commands: Chat.ChatCommands = { async create(target, room, user) { room = this.requireRoom(); if (!GROUPWATCH_ROOMS.includes(room.roomid)) { - return this.errorReply(`This room is not allowed to use the groupwatch function.`); + throw new Chat.ErrorMessage(`This room is not allowed to use the groupwatch function.`); } this.checkCan('mute', null, room); const [url, title] = Utils.splitFirst(target, ',').map(p => p.trim()); - if (!url || !title) return this.errorReply(`You must specify a video to watch and a title for the group watch.`); + if (!url || !title) throw new Chat.ErrorMessage(`You must specify a video to watch and a title for the group watch.`); const game = await YouTube.createGroupWatch(url, room, title); this.modlog(`YOUTUBE GROUPWATCH`, null, `${url} (${title})`); room.add( @@ -735,9 +735,9 @@ export const commands: Chat.ChatCommands = { twitch: { async channel(target, room, user) { room = this.requireRoom('internetexplorers' as RoomID); - if (!Config.twitchKey) return this.errorReply(`Twitch is not configured`); + if (!Config.twitchKey) throw new Chat.ErrorMessage(`Twitch is not configured`); const data = await Twitch.getChannel(target); - if (!data) return this.errorReply(`Channel not found`); + if (!data) throw new Chat.ErrorMessage(`Channel not found`); const html = Twitch.visualizeChannel(data); this.runBroadcast(); return this.sendReplyBox(html); @@ -761,7 +761,7 @@ export const pages: Chat.PageTable = { switch (toID(type)) { case 'categories': if (!YouTube.data.categories.length) { - return this.errorReply(`There are currently no categories in the Youtube channel database.`); + throw new Chat.ErrorMessage(`There are currently no categories in the Youtube channel database.`); } const sorted: { [k: string]: string[] } = {}; const channels = YouTube.data.channels; @@ -802,7 +802,7 @@ export const pages: Chat.PageTable = { if (!user.named) return Rooms.RETRY_AFTER_LOGIN; const [roomid, num] = query; const watch = GroupWatch.groupwatches.get(`${roomid}-${num}`); - if (!watch) return this.errorReply(`Groupwatch ${roomid}-${num} not found.`); + if (!watch) throw new Chat.ErrorMessage(`Groupwatch ${roomid}-${num} not found.`); this.title = `[Groupwatch] ${watch.title}`; return watch.display(); }, diff --git a/server/chat.ts b/server/chat.ts index ee76e2ac4e..c5744d2937 100644 --- a/server/chat.ts +++ b/server/chat.ts @@ -235,7 +235,8 @@ class PatternTester { * Outside of a command/page context, it would still cause a crash. */ export class ErrorMessage extends Error { - constructor(message: string) { + constructor(message: string | string[]) { + if (Array.isArray(message)) message = message.join('\n'); super(message); this.name = 'ErrorMessage'; Error.captureStackTrace(this, ErrorMessage); @@ -471,7 +472,7 @@ export class PageContext extends MessageContext { res = await handler.call(this, parts, this.user, this.connection); } catch (err: any) { if (err.name?.endsWith('ErrorMessage')) { - if (err.message) this.errorReply(err.message); + if (err.message) this.errorReply(err.message.replace(/\n/g, '
')); return; } if (err.name.endsWith('Interruption')) { @@ -693,7 +694,7 @@ export class CommandContext extends MessageContext { !Users.globalAuth.atLeast(this.user, blockInvites as GroupSymbol) ) { Chat.maybeNotifyBlocked(`invite`, this.pmTarget, this.user); - return this.errorReply(`${this.pmTarget.name} is blocking room invites.`); + throw new Chat.ErrorMessage(`${this.pmTarget.name} is blocking room invites.`); } } Chat.PrivateMessages.send(message, this.user, this.pmTarget); @@ -707,9 +708,8 @@ export class CommandContext extends MessageContext { run(handler: string | AnnotatedChatHandler) { if (typeof handler === 'string') handler = Chat.commands[handler] as AnnotatedChatHandler; if (!handler.broadcastable && this.cmdToken === '!') { - this.errorReply(`The command "${this.fullCmd}" can't be broadcast.`); - this.errorReply(`Use /${this.fullCmd} instead.`); - return false; + throw new Chat.ErrorMessage([`The command "${this.fullCmd}" can't be broadcast.`, + `Use /${this.fullCmd} instead.`]); } let result: any = handler.call(this, this.target, this.room, this.user, this.connection, this.cmd, this.message); if (result === undefined) result = false; @@ -1024,9 +1024,7 @@ export class CommandContext extends MessageContext { } canUseConsole() { if (!this.user.hasConsoleAccess(this.connection)) { - throw new Chat.ErrorMessage( - (this.cmdToken + this.fullCmd).trim() + " - Requires console access, please set up `Config.consoleips`." - ); + throw new Chat.ErrorMessage(`${(this.cmdToken + this.fullCmd).trim()} - Requires console access, please set up \`Config.consoleips\`.`); } return true; } @@ -1039,20 +1037,20 @@ export class CommandContext extends MessageContext { } if (this.user.locked && !(this.room?.roomid.startsWith('help-') || this.pmTarget?.can('lock'))) { - this.errorReply(`You cannot broadcast this command's information while locked.`); - throw new Chat.ErrorMessage(`To see it for yourself, use: /${this.message.slice(1)}`); + throw new Chat.ErrorMessage([`You cannot broadcast this command's information while locked.`, + `To see it for yourself, use: /${this.message.slice(1)}`]); } if (this.room && !this.user.can('show', null, this.room, this.cmd, this.cmdToken)) { const perm = this.room.settings.permissions?.[`!${this.cmd}`]; const atLeast = perm ? `at least rank ${perm}` : 'voiced'; - this.errorReply(`You need to be ${atLeast} to broadcast this command's information.`); - throw new Chat.ErrorMessage(`To see it for yourself, use: /${this.message.slice(1)}`); + throw new Chat.ErrorMessage([`You need to be ${atLeast} to broadcast this command's information.`, + `To see it for yourself, use: /${this.message.slice(1)}`]); } if (!this.room && !this.pmTarget) { - this.errorReply(`Broadcasting a command with "!" in a PM or chatroom will show it that user or room.`); - throw new Chat.ErrorMessage(`To see it for yourself, use: /${this.message.slice(1)}`); + throw new Chat.ErrorMessage([`Broadcasting a command with "!" in a PM or chatroom will show it that user or room.`, + `To see it for yourself, use: /${this.message.slice(1)}`]); } // broadcast cooldown @@ -1388,24 +1386,22 @@ export class CommandContext extends MessageContext { if (tagName === 'img') { if (!this.room || (this.room.settings.isPersonal && !this.user.can('lock'))) { - throw new Chat.ErrorMessage( - `This tag is not allowed: <${tagContent}>. Images are not allowed outside of chatrooms.` - ); + throw new Chat.ErrorMessage(`This tag is not allowed: <${tagContent}>. Images are not allowed outside of chatrooms.`); } if (!/width ?= ?(?:[0-9]+|"[0-9]+")/i.test(tagContent) || !/height ?= ?(?:[0-9]+|"[0-9]+")/i.test(tagContent)) { // Width and height are required because most browsers insert the // element before width and height are known, and when the // image is loaded, this changes the height of the chat area, which // messes up autoscrolling. - this.errorReply(`This image is missing a width/height attribute: <${tagContent}>`); - throw new Chat.ErrorMessage(`Images without predefined width/height cause problems with scrolling because loading them changes their height.`); + throw new Chat.ErrorMessage([`This image is missing a width/height attribute: <${tagContent}>`, + `Images without predefined width/height cause problems with scrolling because loading them changes their height.`]); } const srcMatch = / src ?= ?(?:"|')?([^ "']+)(?: ?(?:"|'))?/i.exec(tagContent); if (srcMatch) { this.checkEmbedURI(srcMatch[1]); } else { - this.errorReply(`This image has a broken src attribute: <${tagContent}>`); - throw new Chat.ErrorMessage(`The src attribute must exist and have no spaces in the URL`); + throw new Chat.ErrorMessage([`This image has a broken src attribute: <${tagContent}>`, + `The src attribute must exist and have no spaces in the URL`]); } } if (tagName === 'button') { @@ -1418,16 +1414,18 @@ export class CommandContext extends MessageContext { const [pmTarget] = buttonValue.replace(msgCommandRegex, '').split(','); const auth = this.room ? this.room.auth : Users.globalAuth; if (auth.get(toID(pmTarget)) !== '*' && toID(pmTarget) !== this.user.id) { - this.errorReply(`This button is not allowed: <${tagContent}>`); - throw new Chat.ErrorMessage(`Your scripted button can't send PMs to ${pmTarget}, because that user is not a Room Bot.`); + throw new Chat.ErrorMessage([`This button is not allowed: <${tagContent}>`, + `Your scripted button can't send PMs to ${pmTarget}, because that user is not a Room Bot.`]); } } else if (buttonName === 'send' && buttonValue && botmsgCommandRegex.test(buttonValue)) { // no need to validate the bot being an actual bot; `/botmsg` will do it for us and is not abusable } else if (buttonName) { - this.errorReply(`This button is not allowed: <${tagContent}>`); - this.errorReply(`You do not have permission to use most buttons. Here are the two types you're allowed to use:`); - this.errorReply(`1. Linking to a room: `); - throw new Chat.ErrorMessage(`2. Sending a message to a Bot: `); + throw new Chat.ErrorMessage([ + `This button is not allowed: <${tagContent}>`, + `You do not have permission to use most buttons. Here are the two types you're allowed to use:`, + `1. Linking to a room: `, + `2. Sending a message to a Bot: `, + ]); } } } diff --git a/server/tournaments/index.ts b/server/tournaments/index.ts index 16b73e1cb4..6318bc738d 100644 --- a/server/tournaments/index.ts +++ b/server/tournaments/index.ts @@ -424,8 +424,7 @@ export class Tournament extends Rooms.RoomGame { const gameCount = user.games.size; if (gameCount > 4) { - output.errorReply("Due to high load, you are limited to 4 games at the same time."); - return; + throw new Chat.ErrorMessage("Due to high load, you are limited to 4 games at the same time."); } if (!Config.noipchecks) { @@ -486,21 +485,17 @@ export class Tournament extends Rooms.RoomGame { return; } if (!(user.id in this.playerTable)) { - output.errorReply(`${user.name} isn't in the tournament.`); - return; + throw new Chat.ErrorMessage(`${user.name} isn't in the tournament.`); } if (!replacementUser.named) { - output.errorReply(`${replacementUser.name} must be named to join the tournament.`); - return; + throw new Chat.ErrorMessage(`${replacementUser.name} must be named to join the tournament.`); } if (replacementUser.id in this.playerTable) { - output.errorReply(`${replacementUser.name} is already in the tournament.`); - return; + throw new Chat.ErrorMessage(`${replacementUser.name} is already in the tournament.`); } if (Tournament.checkBanned(this.room, replacementUser) || Punishments.isBattleBanned(replacementUser) || replacementUser.namelocked) { - output.errorReply(`${replacementUser.name} is banned from joining tournaments.`); - return; + throw new Chat.ErrorMessage(`${replacementUser.name} is banned from joining tournaments.`); } if ((this.room.settings.tournaments?.autoconfirmedOnly || this.autoconfirmedOnly) && !user.autoconfirmed) { user.popup("Signups for tournaments are only available for autoconfirmed users in this room."); @@ -511,17 +506,14 @@ export class Tournament extends Rooms.RoomGame { for (const otherPlayer of this.players) { if (!otherPlayer) continue; const otherUser = Users.get(otherPlayer.id); - if (otherUser && - otherUser.latestIp === replacementUser.latestIp && + if (otherUser && otherUser.latestIp === replacementUser.latestIp && replacementUser.latestIp !== user.latestIp) { - output.errorReply(`${replacementUser.name} already has an alt in the tournament.`); - return; + throw new Chat.ErrorMessage(`${replacementUser.name} already has an alt in the tournament.`); } } } if (!(replacementUser.id in this.room.users)) { - output.errorReply(`${replacementUser.name} is not in this room (${this.room.title}).`); - return; + throw new Chat.ErrorMessage(`${replacementUser.name} is not in this room (${this.room.title}).`); } const player = this.playerTable[user.id]; if (player.pendingChallenge) { @@ -1210,14 +1202,12 @@ function getGenerator(generator: string | undefined) { } function createTournamentGenerator( - generatorName: string | undefined, modifier: string | undefined, output: Chat.CommandContext + generatorName: string | undefined, modifier: string | undefined ) { const TourGenerator = getGenerator(generatorName); if (!TourGenerator) { - output.errorReply(`${generatorName} is not a valid type.`); const generatorNames = Object.keys(TournamentGenerators).join(', '); - output.errorReply(`Valid types: ${generatorNames}`); - return; + throw new Chat.ErrorMessage([`${generatorName} is not a valid type.`, `Valid types: ${generatorNames}`]); } return new TourGenerator(modifier || ''); } @@ -1226,16 +1216,13 @@ function createTournament( isRated: boolean, generatorMod: string | undefined, name: string | undefined, output: Chat.CommandContext ) { if (room.type !== 'chat') { - output.errorReply("Tournaments can only be created in chat rooms."); - return; + throw new Chat.ErrorMessage("Tournaments can only be created in chat rooms."); } if (room.game) { - output.errorReply(`You cannot have a tournament until the current room activity is over: ${room.game.title}`); - return; + throw new Chat.ErrorMessage(`You cannot have a tournament until the current room activity is over: ${room.game.title}`); } if (Rooms.global.lockdown) { - output.errorReply("The server is restarting soon, so a tournament cannot be created."); - return; + throw new Chat.ErrorMessage("The server is restarting soon, so a tournament cannot be created."); } const format = Dex.formats.get(formatId); if (format.effectType !== 'Format' || !format.tournamentShow) { @@ -1247,19 +1234,15 @@ function createTournament( if (settings?.blockRecents && settings.recentTours && settings.recentToursLength) { const recentTours = settings.recentTours.map(x => x.baseFormat); if (recentTours.includes(format.id)) { - output.errorReply(`A ${format.name} tournament was made too recently.`); - return; + throw new Chat.ErrorMessage(`A ${format.name} tournament was made too recently.`); } } if (!getGenerator(generator)) { - output.errorReply(`${generator} is not a valid type.`); const generators = Object.keys(TournamentGenerators).join(', '); - output.errorReply(`Valid types: ${generators}`); - return; + throw new Chat.ErrorMessage([`${generator} is not a valid type.`, `Valid types: ${generators}`]); } if (playerCap && parseInt(playerCap) < 2) { - output.errorReply("You cannot have a player cap that is less than 2."); - return; + throw new Chat.ErrorMessage("You cannot have a player cap that is less than 2."); } if (name?.trim().length) { if (output.checkChat(name) !== name) { @@ -1272,7 +1255,7 @@ function createTournament( if (name.includes('|')) throw new Chat.ErrorMessage("The tournament's name cannot include the | symbol."); } const tour = room.game = new Tournament( - room, format, createTournamentGenerator(generator, generatorMod, output)!, playerCap, isRated, name + room, format, createTournamentGenerator(generator, generatorMod), playerCap, isRated, name ); if (settings) { if (typeof settings.autostart === 'number') tour.setAutoStartTimeout(settings.autostart, output); @@ -1353,14 +1336,18 @@ const commands: Chat.ChatCommands = { const option = target.toLowerCase(); if (this.meansYes(option)) { - if (room.settings.tournaments?.announcements) return this.errorReply("Tournament announcements are already enabled."); + if (room.settings.tournaments?.announcements) { + throw new Chat.ErrorMessage("Tournament announcements are already enabled."); + } if (!room.settings.tournaments) room.settings.tournaments = {}; room.settings.tournaments.announcements = true; room.saveSettings(); this.privateModAction(`Tournament announcements were enabled by ${user.name}`); this.modlog('TOUR ANNOUNCEMENTS', null, 'ON'); } else if (this.meansNo(option)) { - if (!room.settings.tournaments?.announcements) return this.errorReply("Tournament announcements are already disabled."); + if (!room.settings.tournaments?.announcements) { + throw new Chat.ErrorMessage("Tournament announcements are already disabled."); + } if (!room.settings.tournaments) room.settings.tournaments = {}; room.settings.tournaments.announcements = false; room.saveSettings(); @@ -1426,10 +1413,12 @@ const commands: Chat.ChatCommands = { const targetUserid = targetUser ? targetUser.id : toID(userid); if (!targetUser) return false; if (reason?.length > MAX_REASON_LENGTH) { - return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); + throw new Chat.ErrorMessage(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } - if (Tournament.checkBanned(room, targetUser)) return this.errorReply("This user is already banned from tournaments."); + if (Tournament.checkBanned(room, targetUser)) { + throw new Chat.ErrorMessage("This user is already banned from tournaments."); + } const punishment = { type: 'TOURBAN', @@ -1460,7 +1449,9 @@ const commands: Chat.ChatCommands = { const targetUserid = toID(targetUser || toID(target)); - if (!Tournament.checkBanned(room, targetUserid)) return this.errorReply("This user isn't banned from tournaments."); + if (!Tournament.checkBanned(room, targetUserid)) { + throw new Chat.ErrorMessage("This user isn't banned from tournaments."); + } if (targetUser) { Punishments.roomUnpunish(room, targetUserid, 'TOURBAN', false); @@ -1484,7 +1475,7 @@ const commands: Chat.ChatCommands = { if (tournament.getRemainingPlayers().some(player => player.id === user.id)) { tournament.disqualifyUser(user.id, this, null, true); } else { - this.errorReply("You have already been eliminated from this tournament."); + throw new Chat.ErrorMessage("You have already been eliminated from this tournament."); } } else { tournament.removeUser(user.id, this); @@ -1548,7 +1539,7 @@ const commands: Chat.ChatCommands = { const tournament = this.requireGame(Tournament); if (!this.runBroadcast()) return; if (tournament.customRules.length < 1) { - return this.errorReply("The tournament does not have any custom rules."); + throw new Chat.ErrorMessage("The tournament does not have any custom rules."); } this.sendReply(`|html|
This tournament includes:
${tournament.getCustomRules()}
`); }, @@ -1561,7 +1552,7 @@ const commands: Chat.ChatCommands = { } const [generatorType, cap, modifier] = target.split(',').map(item => item.trim()); const playerCap = parseInt(cap); - const generator = createTournamentGenerator(generatorType, modifier, this); + const generator = createTournamentGenerator(generatorType, modifier); if (generator && tournament.setGenerator(generator, this)) { if (playerCap && playerCap >= 2) { tournament.playerCap = playerCap; @@ -1595,11 +1586,11 @@ const commands: Chat.ChatCommands = { } } if (tournament.isTournamentStarted) { - return this.errorReply("The player cap cannot be changed once the tournament has started."); + throw new Chat.ErrorMessage("The player cap cannot be changed once the tournament has started."); } const option = target.toLowerCase(); if (['0', 'infinity', 'off', 'false', 'stop', 'remove'].includes(option)) { - if (!tournament.playerCap) return this.errorReply("The tournament does not have a player cap."); + if (!tournament.playerCap) throw new Chat.ErrorMessage("The tournament does not have a player cap."); target = '0'; } const playerCap = parseInt(target); @@ -1610,10 +1601,10 @@ const commands: Chat.ChatCommands = { this.sendReply("Tournament cap removed."); } else { if (isNaN(playerCap) || playerCap < 2) { - return this.errorReply("The tournament cannot have a player cap less than 2."); + throw new Chat.ErrorMessage("The tournament cannot have a player cap less than 2."); } if (playerCap === tournament.playerCap) { - return this.errorReply(`The tournament's player cap is already ${playerCap}.`); + throw new Chat.ErrorMessage(`The tournament's player cap is already ${playerCap}.`); } tournament.playerCap = playerCap; if (Config.tourdefaultplayercap && tournament.playerCap > Config.tourdefaultplayercap) { @@ -1642,7 +1633,7 @@ const commands: Chat.ChatCommands = { room = this.requireRoom(); const tournament = this.requireGame(Tournament); if (cmd === 'banlist') { - return this.errorReply('The new syntax is: /tour rules -bannedthing, +un[banned|restricted]thing, *restrictedthing, !removedrule, addedrule'); + throw new Chat.ErrorMessage('The new syntax is: /tour rules -bannedthing, +un[banned|restricted]thing, *restrictedthing, !removedrule, addedrule'); } if (!target) { this.sendReply("Usage: /tour rules "); @@ -1655,7 +1646,7 @@ const commands: Chat.ChatCommands = { } this.checkCan('tournaments', null, room); if (tournament.isTournamentStarted) { - return this.errorReply("The custom rules cannot be changed once the tournament has started."); + throw new Chat.ErrorMessage("The custom rules cannot be changed once the tournament has started."); } if (tournament.setCustomRules(target)) { room.addRaw( @@ -1674,10 +1665,10 @@ const commands: Chat.ChatCommands = { this.checkCan('tournaments', null, room); const tournament = this.requireGame(Tournament); if (tournament.isTournamentStarted) { - return this.errorReply("The custom rules cannot be changed once the tournament has started."); + throw new Chat.ErrorMessage("The custom rules cannot be changed once the tournament has started."); } if (tournament.customRules.length < 1) { - return this.errorReply("The tournament does not have any custom rules."); + throw new Chat.ErrorMessage("The tournament does not have any custom rules."); } tournament.customRules = []; tournament.fullFormat = tournament.baseFormat; @@ -1704,9 +1695,9 @@ const commands: Chat.ChatCommands = { if (!name || typeof name !== 'string') return; if (name.length > MAX_CUSTOM_NAME_LENGTH) { - return this.errorReply(`The tournament's name cannot exceed ${MAX_CUSTOM_NAME_LENGTH} characters.`); + throw new Chat.ErrorMessage(`The tournament's name cannot exceed ${MAX_CUSTOM_NAME_LENGTH} characters.`); } - if (name.includes('|')) return this.errorReply("The tournament's name cannot include the | symbol."); + if (name.includes('|')) throw new Chat.ErrorMessage("The tournament's name cannot include the | symbol."); tournament.name = name; room.send(`|tournament|update|${JSON.stringify({ format: tournament.name })}`); this.privateModAction(`${user.name} set the tournament's name to ${tournament.name}.`); @@ -1718,7 +1709,7 @@ const commands: Chat.ChatCommands = { room = this.requireRoom(); this.checkCan('tournaments', null, room); const tournament = this.requireGame(Tournament); - if (tournament.name === tournament.baseFormat) return this.errorReply("The tournament does not have a name."); + if (tournament.name === tournament.baseFormat) throw new Chat.ErrorMessage("The tournament does not have a name."); tournament.name = tournament.baseFormat; room.send(`|tournament|update|${JSON.stringify({ format: tournament.name })}`); this.privateModAction(`${user.name} cleared the tournament's name.`); @@ -1746,7 +1737,7 @@ const commands: Chat.ChatCommands = { const targetUser = Users.get(userid); const targetUserid = toID(targetUser || userid); if (reason?.length > MAX_REASON_LENGTH) { - return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); + throw new Chat.ErrorMessage(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`); } if (tournament.disqualifyUser(targetUserid, this, reason)) { this.privateModAction(`${(targetUser ? targetUser.name : targetUserid)} was disqualified from the tournament by ${user.name}${(reason ? ' (' + reason + ')' : '')}`); @@ -1759,8 +1750,8 @@ const commands: Chat.ChatCommands = { this.checkCan('tournaments', null, room); const tournament = this.requireGame(Tournament); const [oldUser, newUser] = target.split(',').map(item => Users.get(item.trim())); - if (!oldUser) return this.errorReply(`User ${oldUser} not found.`); - if (!newUser) return this.errorReply(`User ${newUser} not found.`); + if (!oldUser) throw new Chat.ErrorMessage(`User ${oldUser} not found.`); + if (!newUser) throw new Chat.ErrorMessage(`User ${newUser} not found.`); tournament.replaceUser(oldUser, newUser, this); }, @@ -1776,12 +1767,12 @@ const commands: Chat.ChatCommands = { const option = target.toLowerCase(); if ((this.meansYes(option) && option !== '1') || option === 'start') { if (tournament.isTournamentStarted) { - return this.errorReply("The tournament has already started."); + throw new Chat.ErrorMessage("The tournament has already started."); } else if (!tournament.playerCap) { - return this.errorReply("The tournament does not have a player cap set."); + throw new Chat.ErrorMessage("The tournament does not have a player cap set."); } else { if (tournament.autostartcap) { - return this.errorReply("The tournament is already set to autostart when the player cap is reached."); + throw new Chat.ErrorMessage("The tournament is already set to autostart when the player cap is reached."); } tournament.setAutostartAtCap(true); this.privateModAction(`The tournament was set to autostart when the player cap is reached by ${user.name}`); @@ -1790,14 +1781,14 @@ const commands: Chat.ChatCommands = { } else { if (option === '0' || option === 'infinity' || this.meansNo(option) || option === 'stop' || option === 'remove') { if (!tournament.autostartcap && tournament.autoStartTimeout === Infinity) { - return this.errorReply("The automatic tournament start timer is already off."); + throw new Chat.ErrorMessage("The automatic tournament start timer is already off."); } target = 'off'; tournament.autostartcap = false; } const timeout = target.toLowerCase() === 'off' ? Infinity : Number(target) * 60 * 1000; if (timeout <= 0 || (timeout !== Infinity && timeout > Chat.MAX_TIMEOUT_DURATION)) { - return this.errorReply(`The automatic tournament start timer must be set to a positive number.`); + throw new Chat.ErrorMessage(`The automatic tournament start timer must be set to a positive number.`); } if (tournament.setAutoStartTimeout(timeout, this)) { this.privateModAction(`The tournament auto start timer was set to ${target} by ${user.name}`); @@ -1821,10 +1812,10 @@ const commands: Chat.ChatCommands = { if (target.toLowerCase() === 'infinity' || target === '0') target = 'off'; const timeout = target.toLowerCase() === 'off' ? Infinity : Number(target) * 60 * 1000; if (timeout <= 0 || (timeout !== Infinity && timeout > Chat.MAX_TIMEOUT_DURATION)) { - return this.errorReply(`The automatic disqualification timer must be set to a positive number.`); + throw new Chat.ErrorMessage(`The automatic disqualification timer must be set to a positive number.`); } if (timeout === tournament.autoDisqualifyTimeout) { - return this.errorReply(`The automatic tournament disqualify timer is already set to ${target} minute(s).`); + throw new Chat.ErrorMessage(`The automatic tournament disqualify timer is already set to ${target} minute(s).`); } if (tournament.setAutoDisqualifyTimeout(timeout, this)) { this.privateModAction(`The tournament auto disqualify timer was set to ${target} by ${user.name}`); @@ -1836,7 +1827,7 @@ const commands: Chat.ChatCommands = { this.checkCan('tournaments', null, room); const tournament = this.requireGame(Tournament); if (tournament.autoDisqualifyTimeout === Infinity) { - return this.errorReply("The automatic tournament disqualify timer is not set."); + throw new Chat.ErrorMessage("The automatic tournament disqualify timer is not set."); } tournament.runAutoDisqualify(this); this.roomlog(`${user.name} used /tour runautodq`); @@ -1859,12 +1850,12 @@ const commands: Chat.ChatCommands = { const option = target.toLowerCase(); if (this.meansYes(option) || option === 'allow' || option === 'allowed') { - if (tournament.allowScouting) return this.errorReply("Scouting for this tournament is already set to allowed."); + if (tournament.allowScouting) throw new Chat.ErrorMessage("Scouting for this tournament is already set to allowed."); tournament.setScouting(true); this.privateModAction(`The tournament was set to allow scouting by ${user.name}`); this.modlog('TOUR SCOUT', null, 'allow'); } else if (this.meansNo(option) || option === 'disallow' || option === 'disallowed') { - if (!tournament.allowScouting) return this.errorReply("Scouting for this tournament is already disabled."); + if (!tournament.allowScouting) throw new Chat.ErrorMessage("Scouting for this tournament is already disabled."); tournament.setScouting(false); this.privateModAction(`The tournament was set to disallow scouting by ${user.name}`); this.modlog('TOUR SCOUT', null, 'disallow'); @@ -1888,12 +1879,12 @@ const commands: Chat.ChatCommands = { const option = target.toLowerCase(); if (this.meansYes(option) || option === 'allowed') { - if (tournament.allowModjoin) return this.errorReply("Modjoining is already allowed for this tournament."); + if (tournament.allowModjoin) throw new Chat.ErrorMessage("Modjoining is already allowed for this tournament."); tournament.setModjoin(true); this.privateModAction(`The tournament was set to allow modjoin by ${user.name}`); this.modlog('TOUR MODJOIN', null, option); } else if (this.meansNo(option) || option === 'disallowed') { - if (!tournament.allowModjoin) return this.errorReply("Modjoining is already not allowed for this tournament."); + if (!tournament.allowModjoin) throw new Chat.ErrorMessage("Modjoining is already not allowed for this tournament."); tournament.setModjoin(false); this.privateModAction(`The tournament was set to disallow modjoin by ${user.name}`); this.modlog('TOUR MODJOIN', null, option); @@ -1920,7 +1911,7 @@ const commands: Chat.ChatCommands = { return this.parse(`/help tour`); } if (tournament.autoconfirmedOnly === value) { - return this.errorReply(`This tournament is already set to ${value ? 'disallow' : 'allow'} non-autoconfirmed users.`); + throw new Chat.ErrorMessage(`This tournament is already set to ${value ? 'disallow' : 'allow'} non-autoconfirmed users.`); } tournament.setAutoconfirmedOnly(value); this.privateModAction(`${user.name} set this tournament to ${value ? 'disallow' : 'allow'} non-autoconfirmed users.`); @@ -2037,7 +2028,7 @@ const commands: Chat.ChatCommands = { const value = this.meansYes(target) ? true : this.meansNo(target) ? false : null; if (!target || value === null) return this.parse(`/help tour settings`); if (room.settings.tournaments.autoconfirmedOnly === value) { - return this.errorReply(`All tournaments are already set to ${value ? 'disallow' : 'allow'} non-autoconfimed users.`); + throw new Chat.ErrorMessage(`All tournaments are already set to ${value ? 'disallow' : 'allow'} non-autoconfimed users.`); } room.settings.tournaments.autoconfirmedOnly = value; room.saveSettings();
${this.tr`Rank`}${this.tr`User`}${this.tr`Leaderboard score`}${this.tr`Total game points`}${this.tr`Total correct answers`}