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 = `