Use Chat.ErrorMessage instead of errorReply in more places (#11017)

This commit is contained in:
Kris Johnson 2025-04-07 21:15:27 -06:00 committed by GitHub
parent 40f4915925
commit 9ff1398c69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 1443 additions and 1445 deletions

View File

@ -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) {

View File

@ -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] };

View File

@ -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(`<br />`);
@ -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(`<br />`);
user.sendTo(
@ -860,7 +860,7 @@ export const commands: Chat.ChatCommands = {
Utils.html`|html|${user.name} wants to extract the battle input log. <button name="send" value="/allowexportinputlog ${user.id}">Share your team and choices with "${user.name}"</button>`
);
}
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);
},

View File

@ -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`<strong class="username"><small style="display:none">${targetUser.tempGroup}</small>${targetUser.name}</strong> `;
@ -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`<strong class="username">${target}</strong>`;
if (!targetUser?.connected) buf += ` <em style="color:gray">(offline)</em>`;
@ -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(`<a href="${link}">${ability.name} ability description</a>, 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(`<a href="${link}">${move.name} move description</a>, 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`<br /><a href="${request.link}" target="_blank">full-size image</a>`;
} 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`<br /><div class="infobox"><small>(Requested by ${request.name})</small>`;
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 <b><a class="subtle" href="https://twitch.tv/${toID(channelId)}">${channelId}</a></b>...<br />`;
buf += `<twitch src="${link}" />`;
} else {
@ -2741,13 +2744,13 @@ export const commands: Chat.ChatCommands = {
buf = Utils.html`<img src="${link}" width="${width}" height="${height}" />`;
if (resized) buf += Utils.html`<br /><a href="${link}" target="_blank">full-size image</a>`;
} 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`<br />(${comment})</div>`;
}
@ -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|<div class="broadcast-blue">${Utils.randomElement(room.settings.topics)}</div>`);
@ -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(

View File

@ -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 <<room links>> 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 <<room links>> 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) {

View File

@ -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|<div class=\"broadcast-blue\"><strong>Moderated chat was disabled!</strong><br />Anyone may talk now.</div>");
@ -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|<div class="broadcast-blue"><strong>This room is no longer invite only!</strong><br />Anyone may now join.</div>`);
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|<div class="broadcast-red"><strong>Moderated join is set to sync with modchat!</strong><br />Only users who can speak in modchat can join.</div>`);
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|<div class="broadcast-red"><strong>Moderated join is set to autoconfirmed!</strong><br />Users must be rank autoconfirmed or invited with <code>/invite</code> to join</div>`);
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|<div class="broadcast-red"><strong>This room is now invite only!</strong><br />Users must be rank ${target} or invited with <code>/invite</code> to join</div>`);
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 = {
`<p>Groupchats are temporary rooms, and will expire if there hasn't been any activity in 40 minutes.</p><p>You can invite new users using <code>/invite</code>. Be careful with who you invite!</p><p>Commands: <button class="button" name="send" value="/roomhelp">Room Management</button> | <button class="button" name="send" value="/roomsettings">Room Settings</button> | <button class="button" name="send" value="/tournaments help">Tournaments</button></p><p>As creator of this groupchat, <u>you are entirely responsible for what occurs in this chatroom</u>. Global rules apply at all times.</p><p>If this room is used to break global rules or disrupt other areas of the server, <strong>you as the creator will be held accountable and punished</strong>.</p>`,
});
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}'.`);

View File

@ -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}: <code>` +
@ -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(
`<strong>${Chat.count(logs, 'logs')}</strong> found on the date ${target}:<hr />` +
@ -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 = `<div class="pad">`;
buf += `<button style="float:right;" class="button" name="send" value="/join ${this.pageid}">`;
@ -1985,7 +1984,7 @@ export const pages: Chat.PageTable = {
checkAccess(this);
const dateString = (query.join('-') || Chat.toTimestamp(new Date())).slice(0, 7);
if (!/^[0-9]{4}-[0-9]{2}$/.test(dateString)) {
return this.errorReply(`Invalid date: ${dateString}`);
throw new Chat.ErrorMessage(`Invalid date: ${dateString}`);
}
let buf = `<div class="pad">`;
buf += `<button style="float:right;" class="button" name="send" value="/join ${this.pageid}">`;
@ -2298,7 +2297,7 @@ export const pages: Chat.PageTable = {
this.checkCan('lock');
const targetUser = toID(query[0]);
if (!targetUser) {
return this.errorReply(`Specify a user.`);
throw new Chat.ErrorMessage(`Specify a user.`);
}
this.title = `[Artemis History] ${targetUser}`;
let buf = `<div class="pad"><h2>Artemis modlog handling for ${targetUser}</h2><hr />`;
@ -2339,13 +2338,13 @@ export const pages: Chat.PageTable = {
const [format, num, pw] = query.map(toID);
this.checkCan('lock');
if (!format || !num) {
return this.errorReply(`Invalid battle link provided.`);
throw new Chat.ErrorMessage(`Invalid battle link provided.`);
}
this.title = `[Battle Logs] ${format}-${num}`;
const full = `battle-${format}-${num}${pw ? `-${pw}` : ""}`;
const logData = await getBattleLog(full);
if (!logData) {
return this.errorReply(`No logs found for the battle <code>${full}</code>.`);
throw new Chat.ErrorMessage(`No logs found for the battle <code>${full}</code>.`);
}
let log = logData.log;
log = log.filter(l => l.startsWith('|c|'));

View File

@ -76,10 +76,10 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
if (!target) return this.parse('/help announcement new');
target = target.trim();
if (room.battle) return this.errorReply(this.tr`Battles do not support announcements.`);
if (room.battle) throw new Chat.ErrorMessage(this.tr`Battles do not support announcements.`);
const text = this.filter(target);
if (target !== text) return this.errorReply(this.tr`You are not allowed to use filtered words in announcements.`);
if (target !== text) throw new Chat.ErrorMessage(this.tr`You are not allowed to use filtered words in announcements.`);
const supportHTML = cmd === 'htmlcreate';
@ -87,7 +87,7 @@ export const commands: Chat.ChatCommands = {
if (supportHTML) this.checkCan('declare', null, room);
this.checkChat();
if (room.minorActivity) {
return this.errorReply(this.tr`There is already a poll or announcement in progress in this room.`);
throw new Chat.ErrorMessage(this.tr`There is already a poll or announcement in progress in this room.`);
}
const source = supportHTML ? this.checkHTML(Chat.collapseLineBreaksHTML(target)) : Chat.formatText(target, true);
@ -108,7 +108,7 @@ export const commands: Chat.ChatCommands = {
if (!target) return this.parse('/help announcement edit');
target = target.trim();
const text = this.filter(target);
if (target !== text) return this.errorReply(this.tr`You are not allowed to use filtered words in announcements.`);
if (target !== text) throw new Chat.ErrorMessage(this.tr`You are not allowed to use filtered words in announcements.`);
const supportHTML = cmd === 'htmledit';
@ -134,12 +134,12 @@ export const commands: Chat.ChatCommands = {
if (target) {
this.checkCan('minigame', null, room);
if (target === 'clear') {
if (!announcement.endTimer()) return this.errorReply(this.tr`There is no timer to clear.`);
if (!announcement.endTimer()) throw new Chat.ErrorMessage(this.tr`There is no timer to clear.`);
return this.add(this.tr`The announcement timer was turned off.`);
}
const timeoutMins = parseFloat(target);
if (isNaN(timeoutMins) || timeoutMins <= 0 || timeoutMins > 7 * 24 * 60) {
return this.errorReply(this.tr`Time should be a number of minutes less than one week.`);
throw new Chat.ErrorMessage(this.tr`Time should be a number of minutes less than one week.`);
}
announcement.setTimer({ timeoutMins });
room.add(`The announcement timer was turned on: the announcement will end in ${timeoutMins} minute${Chat.plural(timeoutMins)}.`);

View File

@ -633,8 +633,8 @@ export class Auction extends Rooms.SimpleRoomGame {
this.room.update();
try {
this.bid(user, parseCredits(message));
} catch (e) {
if (e instanceof Chat.ErrorMessage) {
} catch (e: any) {
if (e.name?.endsWith('ErrorMessage')) {
user.sendTo(this.room, Utils.html`|raw|<span class="message-error">${e.message}</span>`);
} else {
user.sendTo(this.room, `|raw|<span class="message-error">An unexpected error occurred while placing your bid.</span>`);
@ -752,14 +752,14 @@ export const commands: Chat.ChatCommands = {
create(target, room, user) {
room = this.requireRoom();
this.checkCan('minigame', null, room);
if (room.game) return this.errorReply(`There is already a game of ${room.game.title} in progress in this room.`);
if (room.settings.auctionDisabled) return this.errorReply('Auctions are currently disabled in this room.');
if (room.game) throw new Chat.ErrorMessage(`There is already a game of ${room.game.title} in progress in this room.`);
if (room.settings.auctionDisabled) throw new Chat.ErrorMessage('Auctions are currently disabled in this room.');
let startingCredits;
if (target) {
startingCredits = parseCredits(target);
if (startingCredits < 10000 || startingCredits > 10000000) {
return this.errorReply(`Starting credits must be between 10,000 and 10,000,000.`);
throw new Chat.ErrorMessage(`Starting credits must be between 10,000 and 10,000,000.`);
}
}
const auction = new Auction(room, startingCredits);
@ -915,13 +915,13 @@ export const commands: Chat.ChatCommands = {
if (!target) return this.parse('/help auction importplayers');
if (!/^https?:\/\/pastebin\.com\/[a-zA-Z0-9]+$/.test(target)) {
return this.errorReply('Invalid pastebin URL.');
throw new Chat.ErrorMessage('Invalid pastebin URL.');
}
let data = '';
try {
data = await Net(`https://pastebin.com/raw/${target.split('/').pop()}`).get();
} catch {}
if (!data) return this.errorReply('Error fetching data from pastebin.');
if (!data) throw new Chat.ErrorMessage('Error fetching data from pastebin.');
auction.importPlayers(data);
this.addModAction(`${user.name} imported the player list from ${target}.`);
@ -1091,7 +1091,7 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
this.checkCan('gamemanagement', null, room);
if (room.settings.auctionDisabled) {
return this.errorReply('Auctions are already disabled.');
throw new Chat.ErrorMessage('Auctions are already disabled.');
}
room.settings.auctionDisabled = true;
room.saveSettings();
@ -1101,7 +1101,7 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
this.checkCan('gamemanagement', null, room);
if (!room.settings.auctionDisabled) {
return this.errorReply('Auctions are already enabled.');
throw new Chat.ErrorMessage('Auctions are already enabled.');
}
delete room.settings.auctionDisabled;
room.saveSettings();
@ -1161,7 +1161,7 @@ export const commands: Chat.ChatCommands = {
this.parse(`/auction nominate ${target}`);
},
bid() {
this.errorReply(`/bid is no longer supported. Send the amount by itself in the chat to place your bid.`);
throw new Chat.ErrorMessage(`/bid is no longer supported. Send the amount by itself in the chat to place your bid.`);
},
overpay() {
this.requireGame(Auction);

View File

@ -248,7 +248,7 @@ async function rustBattleSearch(
const { connection, user } = context;
const currentDayOfMonth = (new Date()).getDate();
if (days < 1 || days > 15) {
return context.errorReply(`Days must be between 1 and 15. To search longer ranges, use psbattletools manually on sim3.`);
throw new Chat.ErrorMessage(`Days must be between 1 and 15. To search longer ranges, use psbattletools manually on sim3.`);
}
try {
@ -256,9 +256,7 @@ async function rustBattleSearch(
env: { PATH: `${process.env.PATH}:${process.env.HOME}/.cargo/bin` },
});
} catch {
return context.errorReply(
`You must install <a href="https://crates.io/crates/psbattletools">psbattletools</a> to use the alternate battlesearch.`
);
throw new Chat.ErrorMessage(`You must install <a href="https://crates.io/crates/psbattletools">psbattletools</a> to use the alternate battlesearch.`);
}
if (user.lastCommand !== '/battlesearch' && [30, 31, 1].includes(currentDayOfMonth)) {
const buf = [`Warning: Usage stats may be running currently.`];
@ -266,7 +264,7 @@ async function rustBattleSearch(
buf.push(`Please exercise caution.`);
buf.push(`Type the command again to confirm.`);
user.lastCommand = '/battlesearch';
throw new Chat.ErrorMessage(buf.join('<br />'));
throw new Chat.ErrorMessage(buf);
}
user.lastCommand = '';
@ -322,7 +320,7 @@ export const pages: Chat.PageTable = {
if (!user.named) return Rooms.RETRY_AFTER_LOGIN;
this.checkCan('forcewin');
if (Config.nobattlesearch === true) {
return this.errorReply(`Battlesearch has been temporarily disabled due to load issues.`);
throw new Chat.ErrorMessage(`Battlesearch has been temporarily disabled due to load issues.`);
}
if (Config.nobattlesearch === 'psbattletools') {
return rustBattleSearch(this, args[0], args[1], toID(args[2]));
@ -423,7 +421,7 @@ export const commands: Chat.ChatCommands = {
if (part.startsWith('limit=')) {
const n = parseInt(part.slice('limit='.length).trim());
if (isNaN(n)) {
return this.errorReply(`Invalid limit: ${part.slice('limit='.length)}`);
throw new Chat.ErrorMessage(`Invalid limit: ${part.slice('limit='.length)}`);
}
turnLimit = n;
continue;

View File

@ -170,7 +170,7 @@ export const commands: Chat.ChatCommands = {
case 'octal': case 'oct': base = 8; break;
case 'binary': case 'bin': base = 2; break;
default:
return this.errorReply(`Unrecognized base "${baseMatchResult[1]}". Valid options are binary or bin, octal or oct, decimal or dec, and hexadecimal or hex.`);
throw new Chat.ErrorMessage(`Unrecognized base "${baseMatchResult[1]}". Valid options are binary or bin, octal or oct, decimal or dec, and hexadecimal or hex.`);
}
}
const expression = target.replace(/\b(in|to)\s+([a-zA-Z]+)\b/g, '').trim();

View File

@ -89,7 +89,7 @@ export const pages: Chat.PageTable = {
async cgtwinrates(query, user) {
if (!user.named) return Rooms.RETRY_AFTER_LOGIN;
if (!cgtDatabase) {
return this.errorReply(`CGT win rates are not being tracked due to the server's SQL settings.`);
throw new Chat.ErrorMessage(`CGT win rates are not being tracked due to the server's SQL settings.`);
}
query = query.join('-').split('--');
const mode = query.shift();
@ -97,7 +97,7 @@ export const pages: Chat.PageTable = {
let buf = `<div class="pad"><h2>Winrates for [Gen 9] Computer Generated Teams</h2>`;
const sorter = toID(query.shift() || 'alphabetical');
if (!['alphabetical', 'level'].includes(sorter)) {
return this.errorReply(`Invalid sorting method. Must be either 'alphabetical' or 'level'.`);
throw new Chat.ErrorMessage(`Invalid sorting method. Must be either 'alphabetical' or 'level'.`);
}
const otherSort = sorter === 'alphabetical' ? 'Level' : 'Alphabetical';
buf += `<a class="button" target="replace" href="/view-cgtwinrates-current--${toID(otherSort)}">`;

View File

@ -625,11 +625,11 @@ export const commands: Chat.ChatCommands = {
list = toID(list);
if (!list || !rest.length) {
return this.errorReply(`Syntax: /filter add list ${separator} word ${separator} reason [${separator} optional public reason]`);
throw new Chat.ErrorMessage(`Syntax: /filter add list ${separator} word ${separator} reason [${separator} optional public reason]`);
}
if (!(list in filterWords)) {
return this.errorReply(`Invalid list: ${list}. Possible options: ${Object.keys(filterWords).join(', ')}`);
throw new Chat.ErrorMessage(`Invalid list: ${list}. Possible options: ${Object.keys(filterWords).join(', ')}`);
}
const filterWord = { list, word: '' } as Partial<FilterWord> & { list: string, word: string };
@ -638,7 +638,7 @@ export const commands: Chat.ChatCommands = {
if (Chat.monitors[list].punishment === 'FILTERTO') {
[filterWord.word, filterWord.replacement, filterWord.reason, filterWord.publicReason] = rest;
if (!filterWord.replacement) {
return this.errorReply(
throw new Chat.ErrorMessage(
`Syntax for word filters: /filter add ${list} ${separator} regex ${separator} reason [${separator} optional public reason]`
);
}
@ -648,7 +648,7 @@ export const commands: Chat.ChatCommands = {
filterWord.word = filterWord.word.trim();
if (!filterWord.word) {
return this.errorReply(`Invalid word: '${filterWord.word}'.`);
throw new Chat.ErrorMessage(`Invalid word: '${filterWord.word}'.`);
}
Filters.add(filterWord);
const reason = filterWord.reason ? ` (${filterWord.reason})` : '';
@ -667,15 +667,15 @@ export const commands: Chat.ChatCommands = {
let [list, ...words] = target.split(target.includes('\n') ? '\n' : ',').map(param => param.trim());
list = toID(list);
if (!list || !words.length) return this.errorReply("Syntax: /filter remove list, words");
if (!list || !words.length) throw new Chat.ErrorMessage("Syntax: /filter remove list, words");
if (!(list in filterWords)) {
return this.errorReply(`Invalid list: ${list}. Possible options: ${Object.keys(filterWords).join(', ')}`);
throw new Chat.ErrorMessage(`Invalid list: ${list}. Possible options: ${Object.keys(filterWords).join(', ')}`);
}
const notFound = words.filter(val => !filterWords[list].filter(entry => entry.word === val).length);
if (notFound.length) {
return this.errorReply(`${notFound.join(', ')} ${Chat.plural(notFound, "are", "is")} not on the ${list} list.`);
throw new Chat.ErrorMessage(`${notFound.join(', ')} ${Chat.plural(notFound, "are", "is")} not on the ${list} list.`);
}
filterWords[list] = filterWords[list].filter(entry => !words.includes(entry.word));
@ -751,9 +751,9 @@ export const commands: Chat.ChatCommands = {
allowname(target, room, user) {
this.checkCan('forcerename');
target = toID(target);
if (!target) return this.errorReply(`Syntax: /allowname username`);
if (!target) throw new Chat.ErrorMessage(`Syntax: /allowname username`);
if (Punishments.namefilterwhitelist.has(target)) {
return this.errorReply(`${target} is already allowed as a username.`);
throw new Chat.ErrorMessage(`${target} is already allowed as a username.`);
}
const msg = `${target} was allowed as a username by ${user.name}.`;

View File

@ -875,25 +875,25 @@ export const pages: Chat.PageTable = {
if (room) {
this.checkCan('declare', null, room);
} else {
return this.errorReply(`Access denied.`);
throw new Chat.ErrorMessage(`Access denied.`);
}
}
if (!user.can('rangeban')) {
// Some chatlogs can only be viewed by upper staff
if (roomid.startsWith('spl') && roomid !== 'splatoon') {
return this.errorReply("SPL team discussions are super secret.");
throw new Chat.ErrorMessage("SPL team discussions are super secret.");
}
if (roomid.startsWith('wcop')) {
return this.errorReply("WCOP team discussions are super secret.");
throw new Chat.ErrorMessage("WCOP team discussions are super secret.");
}
if (UPPER_STAFF_ROOMS.includes(roomid) && !user.inRooms.has(roomid)) {
return this.errorReply("Upper staff rooms are super secret.");
throw new Chat.ErrorMessage("Upper staff rooms are super secret.");
}
}
if (room) {
if (!user.can('lock') || room.settings.isPrivate === 'hidden' && !room.checkModjoin(user)) {
if (!room.persist) return this.errorReply(`Access denied.`);
if (!room.persist) throw new Chat.ErrorMessage(`Access denied.`);
this.checkCan('mute', null, room);
}
} else {
@ -914,7 +914,7 @@ export const pages: Chat.PageTable = {
const validNonDateTerm = search ? validDateStrings.includes(date) : date === 'today';
// this is apparently the best way to tell if a date is invalid
if (isNaN(parsedDate.getTime()) && !validNonDateTerm) {
return this.errorReply(`Invalid date.`);
throw new Chat.ErrorMessage(`Invalid date.`);
}
const isTime = opts?.startsWith('time-');
@ -943,15 +943,15 @@ export const pages: Chat.PageTable = {
this.checkCan('mute', null, room);
} else {
if (!user.can('bypassall')) {
return this.errorReply(`You cannot view logs for rooms that no longer exist.`);
throw new Chat.ErrorMessage(`You cannot view logs for rooms that no longer exist.`);
}
}
const [, date, target] = Utils.splitFirst(args.join('-'), '--', 3).map(item => item.trim());
if (isNaN(new Date(date).getTime())) {
return this.errorReply(`Invalid date.`);
throw new Chat.ErrorMessage(`Invalid date.`);
}
if (!LogReader.isMonth(date)) {
return this.errorReply(`You must specify an exact month - both a year and a month.`);
throw new Chat.ErrorMessage(`You must specify an exact month - both a year and a month.`);
}
this.title = `[Log Stats] ${date}`;
return LogSearcher.runLinecountSearch(this, room ? room.roomid : args[0] as RoomID, date, toID(target));
@ -960,7 +960,7 @@ export const pages: Chat.PageTable = {
this.checkCan('rangeban');
const type = toID(query.shift());
if (type && !['chat', 'battle', 'all', 'battles'].includes(type)) {
return this.errorReply(`Invalid log type.`);
throw new Chat.ErrorMessage(`Invalid log type.`);
}
let title = '';
switch (type) {
@ -997,7 +997,7 @@ export const pages: Chat.PageTable = {
const args = Utils.splitFirst(query.join('-'), '--', 2);
const roomid = toID(args.shift()) as RoomID;
if (!roomid) {
return this.errorReply(`Specify a room.`);
throw new Chat.ErrorMessage(`Specify a room.`);
}
const date = args.shift() || LogReader.getMonth();
this.title = `[${roomid}] Activity Stats (${date})`;
@ -1110,7 +1110,7 @@ export const commands: Chat.ChatCommands = {
case 'room': case 'roomid':
const tarRoom = Rooms.search(val);
if (!tarRoom && !user.can('bypassall')) {
return this.errorReply(`Room '${val}' not found.`);
throw new Chat.ErrorMessage(`Room '${val}' not found.`);
}
search.roomid = (
tarRoom?.roomid || val.toLowerCase().replace(/[^a-z0-9-]/g, '') as RoomID
@ -1121,14 +1121,14 @@ export const commands: Chat.ChatCommands = {
break;
case 'date': case 'month': case 'time':
if (!LogReader.isMonth(val)) {
return this.errorReply(`Invalid date.`);
throw new Chat.ErrorMessage(`Invalid date.`);
}
search.date = val;
}
}
if (!search.roomid) {
if (!room) {
return this.errorReply(`If you're not specifying a room, you must use this command in a room.`);
throw new Chat.ErrorMessage(`If you're not specifying a room, you must use this command in a room.`);
}
search.roomid = room.roomid;
}
@ -1155,7 +1155,7 @@ export const commands: Chat.ChatCommands = {
battlelog(target, room, user) {
this.checkCan('lock');
target = target.trim();
if (!target) return this.errorReply(`Specify a battle.`);
if (!target) throw new Chat.ErrorMessage(`Specify a battle.`);
if (target.startsWith('http://')) target = target.slice(7);
if (target.startsWith('https://')) target = target.slice(8);
if (target.startsWith(`${Config.routes.client}/`)) target = target.slice(Config.routes.client.length + 1);
@ -1174,7 +1174,7 @@ export const commands: Chat.ChatCommands = {
let [roomName, userName] = Utils.splitFirst(target, ',').map(f => f.trim());
if (!roomName) {
if (!room) {
return this.errorReply(`If you are not specifying a room, use this command in a room.`);
throw new Chat.ErrorMessage(`If you are not specifying a room, use this command in a room.`);
}
roomName = room.roomid;
}
@ -1194,8 +1194,8 @@ export const commands: Chat.ChatCommands = {
const roomid = roomName.toLowerCase().replace(/[^a-z0-9-]+/g, '') as RoomID;
if (!roomid) return this.parse('/help getbattlechat');
const userid = toID(userName);
if (userName && !userid) return this.errorReply(`Invalid username.`);
if (!roomid.startsWith('battle-')) return this.errorReply(`You must specify a battle.`);
if (userName && !userid) throw new Chat.ErrorMessage(`Invalid username.`);
if (!roomid.startsWith('battle-')) throw new Chat.ErrorMessage(`You must specify a battle.`);
const tarRoom = Rooms.get(roomid);
let log: string[];
@ -1208,7 +1208,7 @@ export const commands: Chat.ChatCommands = {
}
const replayData = await Rooms.Replays.get(battleId);
if (!replayData) {
return this.errorReply(`No room or replay found for that battle.`);
throw new Chat.ErrorMessage(`No room or replay found for that battle.`);
}
log = replayData.log.split('\n');
} else {
@ -1217,7 +1217,7 @@ export const commands: Chat.ChatCommands = {
const data = JSON.parse(raw);
log = data.log ? data.log.split('\n') : [];
} catch {
return this.errorReply(`No room or replay found for that battle.`);
throw new Chat.ErrorMessage(`No room or replay found for that battle.`);
}
}
log = log.filter(l => l.startsWith('|c|'));
@ -1266,7 +1266,7 @@ export const commands: Chat.ChatCommands = {
target = target.toLowerCase().replace(/[^a-z0-9-]+/g, '');
if (!target) return this.parse(`/help groupchatsearch`);
if (target.length < 3) {
return this.errorReply(`Too short of a search term.`);
throw new Chat.ErrorMessage(`Too short of a search term.`);
}
const files = await Monitor.logPath(`chat`).readdir();
const buffer = [];
@ -1290,7 +1290,7 @@ export const commands: Chat.ChatCommands = {
this.checkCan('bypassall');
const [id, date] = target.split(',').map(i => i.trim());
if (id) room = Rooms.search(toID(id)) as Room | null;
if (!room) return this.errorReply(`Either use this command in the target room or specify a room.`);
if (!room) throw new Chat.ErrorMessage(`Either use this command in the target room or specify a room.`);
return this.parse(`/join view-roominfo-${room}${date ? `--${date}` : ''}`);
},
roomactivityhelp: [

View File

@ -84,7 +84,7 @@ export const pages: Chat.PageTable = {
query.shift(); // roomid
const sortType = toID(query.shift());
if (sortType && !['time', 'alphabet'].includes(sortType)) {
return this.errorReply(`Invalid sorting type '${sortType}' - must be either 'time', 'alphabet', or not provided.`);
throw new Chat.ErrorMessage(`Invalid sorting type '${sortType}' - must be either 'time', 'alphabet', or not provided.`);
}
let buf = `<div class="pad ladder">`;
@ -145,18 +145,18 @@ export const pages: Chat.PageTable = {
export const commands: Chat.ChatCommands = {
removedaily(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
let [key, rest] = target.split(',');
key = toID(key);
if (!key) return this.parse('/help daily');
if (!spotlights[room.roomid][key]) return this.errorReply(`Cannot find a daily spotlight with name '${key}'`);
if (!spotlights[room.roomid][key]) throw new Chat.ErrorMessage(`Cannot find a daily spotlight with name '${key}'`);
this.checkCan('announce', null, room);
if (rest) {
const queueNumber = parseInt(rest);
if (isNaN(queueNumber) || queueNumber < 1) return this.errorReply("Invalid queue number");
if (isNaN(queueNumber) || queueNumber < 1) throw new Chat.ErrorMessage("Invalid queue number");
if (queueNumber >= spotlights[room.roomid][key].length) {
return this.errorReply(`Queue number needs to be between 1 and ${spotlights[room.roomid][key].length - 1}`);
throw new Chat.ErrorMessage(`Queue number needs to be between 1 and ${spotlights[room.roomid][key].length - 1}`);
}
spotlights[room.roomid][key].splice(queueNumber, 1);
saveSpotlights();
@ -179,21 +179,21 @@ export const commands: Chat.ChatCommands = {
swapdailies: 'swapdaily',
swapdaily(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!spotlights[room.roomid]) return this.errorReply("There are no dailies for this room.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
if (!spotlights[room.roomid]) throw new Chat.ErrorMessage("There are no dailies for this room.");
this.checkCan('announce', null, room);
const [key, indexStringA, indexStringB] = target.split(',').map(index => toID(index));
if (!indexStringB) return this.parse('/help daily');
if (!spotlights[room.roomid][key]) return this.errorReply(`Cannot find a daily spotlight with name '${key}'`);
if (!spotlights[room.roomid][key]) throw new Chat.ErrorMessage(`Cannot find a daily spotlight with name '${key}'`);
if (!(NUMBER_REGEX.test(indexStringA) && NUMBER_REGEX.test(indexStringB))) {
return this.errorReply("Queue numbers must be numbers.");
throw new Chat.ErrorMessage("Queue numbers must be numbers.");
}
const indexA = parseInt(indexStringA);
const indexB = parseInt(indexStringB);
const queueLength = spotlights[room.roomid][key].length;
if (indexA < 1 || indexB < 1 || indexA >= queueLength || indexB >= queueLength) {
return this.errorReply(`Queue numbers must between 1 and the length of the queue (${queueLength}).`);
throw new Chat.ErrorMessage(`Queue numbers must between 1 and the length of the queue (${queueLength}).`);
}
const dailyA = spotlights[room.roomid][key][indexA];
@ -212,7 +212,7 @@ export const commands: Chat.ChatCommands = {
replacedaily: 'setdaily',
async setdaily(target, room, user, connection, cmd) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
let key, indexString, rest;
if (cmd.endsWith('at') || cmd === 'replacedaily') {
[key, indexString, ...rest] = target.split(',');
@ -221,16 +221,16 @@ export const commands: Chat.ChatCommands = {
}
key = toID(key);
if (!key) return this.parse('/help daily');
if (key.length > 20) return this.errorReply("Spotlight names can be a maximum of 20 characters long.");
if (key.length > 20) throw new Chat.ErrorMessage("Spotlight names can be a maximum of 20 characters long.");
if (key === 'constructor') return false;
if (!spotlights[room.roomid]) spotlights[room.roomid] = {};
const queueLength = spotlights[room.roomid][key]?.length || 0;
if (indexString && !NUMBER_REGEX.test(indexString)) return this.errorReply("The queue number must be a number.");
if (indexString && !NUMBER_REGEX.test(indexString)) throw new Chat.ErrorMessage("The queue number must be a number.");
const index = (indexString ? parseInt(indexString) : queueLength);
if (indexString && (index < 1 || index > queueLength)) {
return this.errorReply(`Queue numbers must be between 1 and the length of the queue (${queueLength}).`);
throw new Chat.ErrorMessage(`Queue numbers must be between 1 and the length of the queue (${queueLength}).`);
}
this.checkCan('announce', null, room);
@ -242,12 +242,12 @@ export const commands: Chat.ChatCommands = {
try {
[width, height] = await Chat.fitImage(img);
} catch {
return this.errorReply(`Invalid image url: ${img}`);
throw new Chat.ErrorMessage(`Invalid image url: ${img}`);
}
}
const desc = rest.join(',');
if (Chat.stripFormatting(desc).length > 500) {
return this.errorReply("Descriptions can be at most 500 characters long.");
throw new Chat.ErrorMessage("Descriptions can be at most 500 characters long.");
}
if (img) img = [img, width, height] as StoredImage;
const obj = { image: img, description: desc, time: Date.now() };
@ -277,12 +277,12 @@ export const commands: Chat.ChatCommands = {
},
async daily(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
const key = toID(target);
if (!key) return this.parse('/help daily');
if (!spotlights[room.roomid]?.[key]) {
return this.errorReply(`Cannot find a daily spotlight with name '${key}'`);
throw new Chat.ErrorMessage(`Cannot find a daily spotlight with name '${key}'`);
}
if (!this.runBroadcast()) return;
@ -301,7 +301,7 @@ export const commands: Chat.ChatCommands = {
dailies: 'viewspotlights',
viewspotlights(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
target = toID(target);
return this.parse(`/join view-spotlights-${room.roomid}${target ? `-${target}` : ''}`);
},

View File

@ -94,7 +94,7 @@ export const commands: Chat.ChatCommands = {
async dexsearch(target, room, user, connection, cmd, message) {
this.checkBroadcast();
if (!target) return this.parse('/help dexsearch');
if (target.length > 300) return this.errorReply('Dexsearch queries may not be longer than 300 characters.');
if (target.length > 300) throw new Chat.ErrorMessage('Dexsearch queries may not be longer than 300 characters.');
const targetGen = parseInt(cmd[cmd.length - 1]);
if (targetGen) target += `, mod=gen${targetGen}`;
const split = target.split(',').map(term => term.trim());

View File

@ -252,7 +252,7 @@ export const commands: Chat.ChatCommands = {
viewlist(target, room, user) {
Friends.checkCanUse(this);
target = toID(target);
if (!target) return this.errorReply(`Specify a user.`);
if (!target) throw new Chat.ErrorMessage(`Specify a user.`);
if (target === user.id) return this.parse(`/friends list`);
return this.parse(`/j view-friends-viewuser-${target}`);
},
@ -261,7 +261,7 @@ export const commands: Chat.ChatCommands = {
Friends.checkCanUse(this);
target = toID(target);
if (target.length > 18) {
return this.errorReply(this.tr`That name is too long - choose a valid name.`);
throw new Chat.ErrorMessage(this.tr`That name is too long - choose a valid name.`);
}
if (!target) return this.parse('/help friends');
await Friends.request(user, target as ID);
@ -292,7 +292,7 @@ export const commands: Chat.ChatCommands = {
Friends.checkCanUse(this);
target = toID(target);
if (user.settings.blockFriendRequests) {
return this.errorReply(this.tr`You are currently blocking friend requests, and so cannot accept your own.`);
throw new Chat.ErrorMessage(this.tr`You are currently blocking friend requests, and so cannot accept your own.`);
}
if (!target) return this.parse('/help friends');
await Friends.approveRequest(user.id, target as ID);
@ -314,7 +314,7 @@ export const commands: Chat.ChatCommands = {
if (!target) return this.parse('/help friends');
const res = await Friends.removeRequest(user.id, target as ID);
if (!res.changes) {
return this.errorReply(`You do not have a friend request pending from '${target}'.`);
throw new Chat.ErrorMessage(`You do not have a friend request pending from '${target}'.`);
}
this.refreshPage('friends-received');
return sendPM(`You denied a friend request from '${target}'.`, user.id);
@ -324,11 +324,11 @@ export const commands: Chat.ChatCommands = {
const setting = user.settings.blockFriendRequests;
target = target.trim();
if (this.meansYes(target)) {
if (!setting) return this.errorReply(this.tr`You already are allowing friend requests.`);
if (!setting) throw new Chat.ErrorMessage(this.tr`You already are allowing friend requests.`);
user.settings.blockFriendRequests = false;
this.sendReply(this.tr`You are now allowing friend requests.`);
} else if (this.meansNo(target)) {
if (setting) return this.errorReply(this.tr`You already are blocking incoming friend requests.`);
if (setting) throw new Chat.ErrorMessage(this.tr`You already are blocking incoming friend requests.`);
user.settings.blockFriendRequests = true;
this.sendReply(this.tr`You are now blocking incoming friend requests.`);
} else {
@ -355,11 +355,11 @@ export const commands: Chat.ChatCommands = {
const setting = user.settings.allowFriendNotifications;
target = target.trim();
if (!cmd.includes('hide') || target && this.meansYes(target)) {
if (setting) return this.errorReply(this.tr(`You are already allowing friend notifications.`));
if (setting) throw new Chat.ErrorMessage(this.tr(`You are already allowing friend notifications.`));
user.settings.allowFriendNotifications = true;
this.sendReply(this.tr(`You will now receive friend notifications.`));
} else if (cmd.includes('hide') || target && this.meansNo(target)) {
if (!setting) return this.errorReply(this.tr`You are already not receiving friend notifications.`);
if (!setting) throw new Chat.ErrorMessage(this.tr`You are already not receiving friend notifications.`);
user.settings.allowFriendNotifications = false;
this.sendReply(this.tr`You will not receive friend notifications.`);
} else {
@ -377,17 +377,17 @@ export const commands: Chat.ChatCommands = {
Friends.checkCanUse(this);
const setting = user.settings.hideLogins;
if (cmd.includes('hide')) {
if (setting) return this.errorReply(this.tr`You are already hiding your logins from friends.`);
if (setting) throw new Chat.ErrorMessage(this.tr`You are already hiding your logins from friends.`);
user.settings.hideLogins = true;
await Chat.Friends.hideLoginData(user.id);
this.sendReply(`You are now hiding your login times from your friends.`);
} else if (cmd.includes('show')) {
if (!setting) return this.errorReply(this.tr`You are already allowing friends to see your login times.`);
if (!setting) throw new Chat.ErrorMessage(this.tr`You are already allowing friends to see your login times.`);
user.settings.hideLogins = false;
await Chat.Friends.allowLoginData(user.id);
this.sendReply(`You are now allowing your friends to see your login times.`);
} else {
return this.errorReply(`Invalid setting.`);
throw new Chat.ErrorMessage(`Invalid setting.`);
}
this.refreshPage('friends-settings');
user.update();
@ -398,14 +398,14 @@ export const commands: Chat.ChatCommands = {
const { public_list: setting } = await Chat.Friends.getSettings(user.id);
if (this.meansYes(target)) {
if (setting) {
return this.errorReply(this.tr`You are already allowing other people to view your friends list.`);
throw new Chat.ErrorMessage(this.tr`You are already allowing other people to view your friends list.`);
}
await Chat.Friends.setHideList(user.id, true);
this.refreshPage('friends-settings');
return this.sendReply(this.tr`You are now allowing other people to view your friends list.`);
} else if (this.meansNo(target)) {
if (!setting) {
return this.errorReply(this.tr`You are already hiding your friends list.`);
throw new Chat.ErrorMessage(this.tr`You are already hiding your friends list.`);
}
await Chat.Friends.setHideList(user.id, false);
this.refreshPage('friends-settings');
@ -429,19 +429,19 @@ export const commands: Chat.ChatCommands = {
target = toID(target);
if (this.meansYes(target)) {
if (user.settings.displayBattlesToFriends) {
return this.errorReply(this.tr`You are already sharing your battles with friends.`);
throw new Chat.ErrorMessage(this.tr`You are already sharing your battles with friends.`);
}
user.settings.displayBattlesToFriends = true;
this.sendReply(`You are now allowing your friends to see your ongoing battles.`);
} else if (this.meansNo(target)) {
if (!user.settings.displayBattlesToFriends) {
return this.errorReply(this.tr`You are already not sharing your battles with friends.`);
throw new Chat.ErrorMessage(this.tr`You are already not sharing your battles with friends.`);
}
user.settings.displayBattlesToFriends = false;
this.sendReply(`You are now hiding your ongoing battles from your friends.`);
} else {
if (!target) return this.parse('/help friends sharebattles');
return this.errorReply(`Invalid setting '${target}'. Provide 'on' or 'off'.`);
throw new Chat.ErrorMessage(`Invalid setting '${target}'. Provide 'on' or 'off'.`);
}
user.update();
this.refreshPage('friends-settings');
@ -521,12 +521,12 @@ export const pages: Chat.PageTable = {
break;
case 'viewuser':
const target = toID(args.shift());
if (!target) return this.errorReply(`Specify a user.`);
if (!target) throw new Chat.ErrorMessage(`Specify a user.`);
if (target === user.id) {
return this.errorReply(`Use /friends list to view your own list.`);
throw new Chat.ErrorMessage(`Use /friends list to view your own list.`);
}
const { public_list: isAllowing } = await Chat.Friends.getSettings(target);
if (!isAllowing) return this.errorReply(`${target}'s friends list is not public or they do not have one.`);
if (!isAllowing) throw new Chat.ErrorMessage(`${target}'s friends list is not public or they do not have one.`);
this.title = `[Friends List] ${target}`;
buf += await Friends.visualizePublicList(target);
break;

View File

@ -177,13 +177,13 @@ export const commands: Chat.ChatCommands = {
this.checkCan('mute', null, room);
const [username, reason] = Utils.splitFirst(target, ',').map(u => u.trim());
if (!toID(target)) return this.parse(`/help github`);
if (!toID(username)) return this.errorReply("Provide a username.");
if (!toID(username)) throw new Chat.ErrorMessage("Provide a username.");
if (room.auth.has(toID(GitHub.getUsername(username)))) {
return this.errorReply("That user is Dev roomauth. If you need to do this, demote them and try again.");
throw new Chat.ErrorMessage("That user is Dev roomauth. If you need to do this, demote them and try again.");
}
if (!gitData.bans) gitData.bans = {};
if (gitData.bans[toID(username)]) {
return this.errorReply(`${username} is already gitbanned.`);
throw new Chat.ErrorMessage(`${username} is already gitbanned.`);
}
gitData.bans[toID(username)] = reason || " "; // to ensure it's truthy
GitHub.save();
@ -195,7 +195,7 @@ export const commands: Chat.ChatCommands = {
this.checkCan('mute', null, room);
target = toID(target);
if (!target) return this.parse('/help github');
if (!gitData.bans?.[target]) return this.errorReply("That user is not gitbanned.");
if (!gitData.bans?.[target]) throw new Chat.ErrorMessage("That user is not gitbanned.");
delete gitData.bans[target];
if (!Object.keys(gitData.bans).length) delete gitData.bans;
GitHub.save();
@ -226,7 +226,7 @@ export const commands: Chat.ChatCommands = {
target = toID(target);
if (!target) return this.parse(`/help github`);
const name = gitData.usernames?.[target];
if (!name) return this.errorReply(`${target} is not a GitHub username on our list.`);
if (!name) throw new Chat.ErrorMessage(`${target} is not a GitHub username on our list.`);
delete gitData.usernames?.[target];
if (!Object.keys(gitData.usernames || {}).length) delete gitData.usernames;
GitHub.save();
@ -251,9 +251,9 @@ export const pages: Chat.PageTable = {
github: {
bans(query, user) {
const room = Rooms.get('development');
if (!room) return this.errorReply("No Development room found.");
if (!room) throw new Chat.ErrorMessage("No Development room found.");
this.checkCan('mute', null, room);
if (!gitData.bans) return this.errorReply("There are no gitbans at this time.");
if (!gitData.bans) throw new Chat.ErrorMessage("There are no gitbans at this time.");
let buf = `<div class="pad"><h2>Current Gitbans:</h2><hr /><ol>`;
for (const [username, reason] of Object.entries(gitData.bans)) {
buf += `<li><strong>${username}</strong> - ${reason.trim() || '(No reason found)'}</li>`;
@ -262,7 +262,7 @@ export const pages: Chat.PageTable = {
return buf;
},
names() {
if (!gitData.usernames) return this.errorReply("There are no GitHub usernames in the list.");
if (!gitData.usernames) throw new Chat.ErrorMessage("There are no GitHub usernames in the list.");
let buf = `<div class="pad"><h2>Current GitHub username mappings:</h2><hr /><ol>`;
for (const [username, name] of Object.entries(gitData.usernames)) {
buf += `<li><strong>${username}</strong> - ${name}</li>`;

View File

@ -323,15 +323,15 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
target = target.trim();
const text = this.filter(target);
if (target !== text) return this.errorReply("You are not allowed to use filtered words in hangmans.");
if (target !== text) throw new Chat.ErrorMessage("You are not allowed to use filtered words in hangmans.");
const params = text.split(',');
this.checkCan('minigame', null, room);
if (room.settings.hangmanDisabled) return this.errorReply("Hangman is disabled for this room.");
if (room.settings.hangmanDisabled) throw new Chat.ErrorMessage("Hangman is disabled for this room.");
this.checkChat();
if (room.game) return this.errorReply(`There is already a game of ${room.game.title} in progress in this room.`);
if (room.game) throw new Chat.ErrorMessage(`There is already a game of ${room.game.title} in progress in this room.`);
if (!params) return this.errorReply("No word entered.");
if (!params) throw new Chat.ErrorMessage("No word entered.");
const { phrase, hint } = Hangman.validateParams(params);
const game = new Hangman(room, user, phrase, hint);
@ -345,7 +345,7 @@ export const commands: Chat.ChatCommands = {
guess(target, room, user) {
const word = this.filter(target);
if (word !== target) return this.errorReply(`You may not use filtered words in guesses.`);
if (word !== target) throw new Chat.ErrorMessage(`You may not use filtered words in guesses.`);
this.parse(`/choose ${target}`);
},
guesshelp: [
@ -369,7 +369,7 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
this.checkCan('gamemanagement', null, room);
if (room.settings.hangmanDisabled) {
return this.errorReply("Hangman is already disabled.");
throw new Chat.ErrorMessage("Hangman is already disabled.");
}
room.settings.hangmanDisabled = true;
room.saveSettings();
@ -380,7 +380,7 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
this.checkCan('gamemanagement', null, room);
if (!room.settings.hangmanDisabled) {
return this.errorReply("Hangman is already enabled.");
throw new Chat.ErrorMessage("Hangman is already enabled.");
}
delete room.settings.hangmanDisabled;
room.saveSettings();
@ -440,7 +440,7 @@ export const commands: Chat.ChatCommands = {
const newID = hint.slice(5);
const targetRoom = Rooms.search(newID);
if (!targetRoom) {
return this.errorReply(`Invalid room: ${newID}`);
throw new Chat.ErrorMessage(`Invalid room: ${newID}`);
}
this.room = targetRoom;
room = targetRoom;
@ -448,13 +448,13 @@ export const commands: Chat.ChatCommands = {
}
}
if (!hangmanData[room.roomid]) {
return this.errorReply("There are no hangman words for this room.");
throw new Chat.ErrorMessage("There are no hangman words for this room.");
}
const roomKeys = Object.keys(hangmanData[room.roomid]);
const roomKeyIDs = roomKeys.map(toID);
const index = roomKeyIDs.indexOf(toID(word));
if (index < 0) {
return this.errorReply(`That word is not a saved hangman.`);
throw new Chat.ErrorMessage(`That word is not a saved hangman.`);
}
word = roomKeys[index];
hints = hints.map(toID);
@ -488,7 +488,7 @@ export const commands: Chat.ChatCommands = {
hangmanData[room.roomid] = {};
}
if (!hangmanData[room.roomid][term]) {
return this.errorReply(`Term ${term} not found.`);
throw new Chat.ErrorMessage(`Term ${term} not found.`);
}
if (!hangmanData[room.roomid][term].tags) hangmanData[room.roomid][term].tags = [];
for (const [i, tag] of tags.entries()) {
@ -519,13 +519,13 @@ export const commands: Chat.ChatCommands = {
return this.parse('/help hangman');
}
if (!hangmanData[room.roomid]) {
return this.errorReply(`This room has no hangman terms.`);
throw new Chat.ErrorMessage(`This room has no hangman terms.`);
}
if (!hangmanData[room.roomid][term]) {
return this.errorReply(`That term was not found.`);
throw new Chat.ErrorMessage(`That term was not found.`);
}
if (!hangmanData[room.roomid][term].tags) {
return this.errorReply(`That term has no tags.`);
throw new Chat.ErrorMessage(`That term has no tags.`);
}
if (tags.length) {
this.privateModAction(`${user.name} removed ${Chat.count(tags, "tags")} from the hangman term ${term}`);
@ -579,7 +579,7 @@ export const pages: Chat.PageTable = {
buf += `<div class="pad"><h2>Hangman entries on ${room.title}</h2>`;
const roomTerms = hangmanData[room.roomid];
if (!roomTerms) {
return this.errorReply(`No hangman terms found for ${room.title}.`);
throw new Chat.ErrorMessage(`No hangman terms found for ${room.title}.`);
}
for (const t in roomTerms) {
buf += `<div class="infobox">`;

View File

@ -560,7 +560,7 @@ export const commands: Chat.ChatCommands = {
const types = list.map(f => f.toLowerCase().replace(/\s/g, '_'));
for (const type of types) {
if (!Artemis.LocalClassifier.ATTRIBUTES[type]) {
return this.errorReply(
throw new Chat.ErrorMessage(
`Invalid classifier type '${type}'. Valid types are ` +
Object.keys(Artemis.LocalClassifier.ATTRIBUTES).join(', ')
);
@ -574,7 +574,7 @@ export const commands: Chat.ChatCommands = {
case 'certainty': case 'c':
const num = parseFloat(val);
if (isNaN(num) || num < 0 || num > 1) {
return this.errorReply(`Certainty must be a number below 1 and above 0.`);
throw new Chat.ErrorMessage(`Certainty must be a number below 1 and above 0.`);
}
if (!punishment.severity) {
punishment.severity = { certainty: 0, type: [] };
@ -584,14 +584,14 @@ export const commands: Chat.ChatCommands = {
case 'modlog': case 'm':
const count = parseInt(val);
if (isNaN(count) || count < 0) {
return this.errorReply(`Modlog count must be a number above 0.`);
throw new Chat.ErrorMessage(`Modlog count must be a number above 0.`);
}
punishment.modlogCount = count;
break;
case 'ticket': case 'tt': case 'tickettype':
const type = toID(val);
if (!(type in checkers)) {
return this.errorReply(
throw new Chat.ErrorMessage(
`The ticket type '${type}' does not exist or is not supported. ` +
`Supported types are ${Object.keys(checkers).join(', ')}.`
);
@ -601,7 +601,7 @@ export const commands: Chat.ChatCommands = {
case 'p': case 'punishment':
const name = toID(val).toUpperCase();
if (!ORDERED_PUNISHMENTS.includes(name)) {
return this.errorReply(
throw new Chat.ErrorMessage(
`Punishment '${name}' not supported. ` +
`Supported punishments: ${ORDERED_PUNISHMENTS.join(', ')}`
);
@ -610,7 +610,7 @@ export const commands: Chat.ChatCommands = {
break;
case 'single': case 's':
if (!this.meansYes(toID(val))) {
return this.errorReply(
throw new Chat.ErrorMessage(
`The 'single' value must always be 'on'. ` +
`If you don't want it enabled, just do not use this argument type.`
);
@ -620,13 +620,13 @@ export const commands: Chat.ChatCommands = {
}
}
if (!punishment.ticketType) {
return this.errorReply(`Must specify a ticket type to handle.`);
throw new Chat.ErrorMessage(`Must specify a ticket type to handle.`);
}
if (!punishment.punishment) {
return this.errorReply(`Must specify a punishment to apply.`);
throw new Chat.ErrorMessage(`Must specify a punishment to apply.`);
}
if (!(punishment.severity?.certainty && punishment.severity?.type.length)) {
return this.errorReply(`A severity to monitor for must be specified (certainty).`);
throw new Chat.ErrorMessage(`A severity to monitor for must be specified (certainty).`);
}
for (const curP of settings.punishments) {
let matches = 0;
@ -636,7 +636,7 @@ export const commands: Chat.ChatCommands = {
}
}
if (matches === Object.keys(punishment).length) {
return this.errorReply(`That punishment is already added.`);
throw new Chat.ErrorMessage(`That punishment is already added.`);
}
}
settings.punishments.push(punishment as AutoPunishment);
@ -653,7 +653,7 @@ export const commands: Chat.ChatCommands = {
const num = parseInt(target) - 1;
if (isNaN(num)) return this.parse(`/h autohelpticket`);
const punishment = settings.punishments[num];
if (!punishment) return this.errorReply(`There is no punishment at index ${num + 1}.`);
if (!punishment) throw new Chat.ErrorMessage(`There is no punishment at index ${num + 1}.`);
settings.punishments.splice(num, 1);
this.privateGlobalModAction(
`${user.name} removed the Artemis helpticket ${punishment.punishment} punishment indexed at ${num + 1}`
@ -679,18 +679,18 @@ export const commands: Chat.ChatCommands = {
let message;
if (this.meansYes(target)) {
if (settings.applyPunishments) {
return this.errorReply(`Automatic punishments are already enabled.`);
throw new Chat.ErrorMessage(`Automatic punishments are already enabled.`);
}
settings.applyPunishments = true;
message = `${user.name} enabled automatic punishments for the Artemis ticket handler`;
} else if (this.meansNo(target)) {
if (!settings.applyPunishments) {
return this.errorReply(`Automatic punishments are already disabled.`);
throw new Chat.ErrorMessage(`Automatic punishments are already disabled.`);
}
settings.applyPunishments = false;
message = `${user.name} disabled automatic punishments for the Artemis ticket handler`;
} else {
return this.errorReply(`Invalid setting. Must be 'on' or 'off'.`);
throw new Chat.ErrorMessage(`Invalid setting. Must be 'on' or 'off'.`);
}
this.privateGlobalModAction(message);
this.globalModlog(`AUTOHELPTICKET TOGGLE`, null, settings.applyPunishments ? 'on' : 'off');
@ -739,7 +739,7 @@ export const pages: Chat.PageTable = {
month = Chat.toTimestamp(new Date()).split(' ')[0].slice(0, -3);
}
if (!month) {
return this.errorReply(`Invalid month. Must be in YYYY-MM format.`);
throw new Chat.ErrorMessage(`Invalid month. Must be in YYYY-MM format.`);
}
this.title = `[Artemis Ticket Stats] ${month}`;
@ -821,7 +821,7 @@ export const pages: Chat.PageTable = {
month = Chat.toTimestamp(new Date()).split(' ')[0].slice(0, -3);
}
if (!month) {
return this.errorReply(`Invalid month. Must be in YYYY-MM format.`);
throw new Chat.ErrorMessage(`Invalid month. Must be in YYYY-MM format.`);
}
this.title = `[Artemis Ticket Logs]`;
let buf = `<div class="pad"><h3>Artemis ticket logs</h3><hr />`;

View File

@ -1881,14 +1881,14 @@ export const pages: Chat.PageTable = {
this.checkCan('lock');
const userid = query.shift();
if (!userid) {
return this.errorReply(`Specify a userid to view the ticket for.`);
throw new Chat.ErrorMessage(`Specify a userid to view the ticket for.`);
}
const ticket = tickets[toID(userid)];
if (!ticket) {
return this.errorReply(`Ticket not found.`);
throw new Chat.ErrorMessage(`Ticket not found.`);
}
if (!ticket.text) {
return this.errorReply(`That is either not a text ticket, or it has not yet been submitted.`);
throw new Chat.ErrorMessage(`That is either not a text ticket, or it has not yet been submitted.`);
}
const ticketInfo = textTickets[HelpTicket.getTypeId(ticket.type)];
this.title = `[Text Ticket] ${ticket.userid}`;
@ -1968,12 +1968,12 @@ export const pages: Chat.PageTable = {
this.checkCan('lock');
const args = query.join('-').split('--');
const userid = toID(args.shift());
if (!userid) return this.errorReply(`Specify a userid to view ticket logs for.`);
if (!userid) throw new Chat.ErrorMessage(`Specify a userid to view ticket logs for.`);
const date = args.shift();
if (date) {
const parsed = new Date(date);
if (!/[0-9]{4}-[0-9]{2}/.test(date) || isNaN(parsed.getTime())) {
return this.errorReply(`Invalid date.`);
throw new Chat.ErrorMessage(`Invalid date.`);
}
}
const logs = await HelpTicket.getTextLogs(['userid', userid], date);
@ -2254,7 +2254,7 @@ export const commands: Chat.ChatCommands = {
if (user.can('lock')) {
return this.parse('/join view-help-request'); // Globals automatically get the form for reference.
}
if (!user.named) return this.errorReply(this.tr`You need to choose a username before doing this.`);
if (!user.named) throw new Chat.ErrorMessage(this.tr`You need to choose a username before doing this.`);
return this.parse(`/join view-help-request${meta}`);
},
createhelp: [`/helpticket create - Creates a new ticket requesting help from global staff.`],
@ -2573,11 +2573,11 @@ export const commands: Chat.ChatCommands = {
if (!target) return this.parse(`/help helpticket addnote`);
const [ticketName, note] = Utils.splitFirst(target, ',').map(i => i.trim());
const ticketId = toID(ticketName);
if (!ticketId) return this.errorReply(`Specify the userid that created the ticket you want to mark.`);
if (!ticketId) throw new Chat.ErrorMessage(`Specify the userid that created the ticket you want to mark.`);
const ticket = tickets[ticketId];
if (!ticket) return this.errorReply(`${ticketId} does not have an active ticket.`);
if (ticket.resolved) return this.errorReply(`${ticketId}'s ticket has already been resolved.`);
if (!note) return this.errorReply(`You must specify a note to add.`);
if (!ticket) throw new Chat.ErrorMessage(`${ticketId} does not have an active ticket.`);
if (ticket.resolved) throw new Chat.ErrorMessage(`${ticketId}'s ticket has already been resolved.`);
if (!note) throw new Chat.ErrorMessage(`You must specify a note to add.`);
if (!ticket.notes) ticket.notes = {};
ticket.notes[user.id] = note;
writeTickets();
@ -2596,14 +2596,16 @@ export const commands: Chat.ChatCommands = {
if (!target) return this.parse(`/help helpticket removenote`);
let [ticketName, staff] = Utils.splitFirst(target, ',').map(i => i.trim());
const targetId = toID(ticketName);
if (!targetId) return this.errorReply(`Specify the userid that created the ticket you want to remove a note from.`);
if (!targetId) {
throw new Chat.ErrorMessage(`Specify the userid that created the ticket you want to remove a note from.`);
}
const ticket = tickets[targetId];
if (!ticket || ticket.resolved) return this.errorReply(`${targetId} does not have a pending ticket.`);
if (!ticket || ticket.resolved) throw new Chat.ErrorMessage(`${targetId} does not have a pending ticket.`);
staff = toID(staff) || user.id;
if (!ticket.notes) return this.errorReply(`${targetId}'s ticket does not have any notes.`);
if (!ticket.notes) throw new Chat.ErrorMessage(`${targetId}'s ticket does not have any notes.`);
const note = ticket.notes[staff];
if (!note) {
return this.errorReply(`${staff === user.id ? 'you do' : `'${staff}' does`} not have a note on that ticket.`);
throw new Chat.ErrorMessage(`${staff === user.id ? 'you do' : `'${staff}' does`} not have a note on that ticket.`);
}
if (!room || room.roomid !== 'staff') {
this.sendReply(`You removed the note '${note}' (by ${staff}) on ${ticket.userid}'s ticket.`);
@ -2633,15 +2635,15 @@ export const commands: Chat.ChatCommands = {
}
const typeId = HelpTicket.getTypeId(type);
if (!(typeId in textTickets)) {
this.errorReply(`'${type}' is not a valid text ticket type.`);
return this.errorReply(`Valid types: ${Object.keys(textTickets).join(', ')}.`);
throw new Chat.ErrorMessage([`'${type}' is not a valid text ticket type.`,
`Valid types: ${Object.keys(textTickets).join(', ')}.`]);
}
if (!settings.responses[typeId]) {
settings.responses[typeId] = {};
}
if (settings.responses[typeId][name] && !this.cmd.includes('f')) {
this.errorReply(`That button already exists for that ticket type.`);
return this.errorReply(`Use /ht forceaddresponse to override it if you're sure.`);
throw new Chat.ErrorMessage([`That button already exists for that ticket type.`,
`Use /ht forceaddresponse to override it if you're sure.`]);
}
settings.responses[typeId][name] = response;
writeSettings();
@ -2660,10 +2662,10 @@ export const commands: Chat.ChatCommands = {
if (!toID(type) || !toID(name)) return this.parse(`/help helpticket removeresponse`);
const typeId = HelpTicket.getTypeId(type);
if (!(type in textTickets)) {
return this.errorReply(`'${type}' is not a valid text ticket type.`);
throw new Chat.ErrorMessage(`'${type}' is not a valid text ticket type.`);
}
if (!settings.responses[typeId]?.[name]) {
return this.errorReply(`'${name}' is not a response for the ${typeId} ticket type .`);
throw new Chat.ErrorMessage(`'${name}' is not a response for the ${typeId} ticket type .`);
}
delete settings.responses[typeId][name];
if (!Object.keys(settings.responses[typeId]).length) {
@ -2684,7 +2686,7 @@ export const commands: Chat.ChatCommands = {
let buf = `<strong>Help ticket response buttons `;
target = toID(target);
if (target && !(target in textTickets)) {
return this.errorReply(`Invalid ticket type: ${target}.`);
throw new Chat.ErrorMessage(`Invalid ticket type: ${target}.`);
}
buf += `${target ? `for the type ${target}:` : ""}</strong><hr />`;
const table = target ? { [target]: settings.responses[target] } : settings.responses;
@ -2716,7 +2718,7 @@ export const commands: Chat.ChatCommands = {
let result = rest !== 'false';
const ticket = tickets[toID(targetUsername)];
if (!ticket?.open || (ticket.userid !== user.id && !user.can('lock'))) {
return this.errorReply(this.tr`${targetUsername} does not have an open ticket.`);
throw new Chat.ErrorMessage(this.tr`${targetUsername} does not have an open ticket.`);
}
if (typeof ticket.text !== 'undefined') {
return this.parse(`/helpticket resolve ${target}`);
@ -2747,10 +2749,10 @@ export const commands: Chat.ChatCommands = {
const punishment = Punishments.roomUserids.nestedGet('staff', toID(targetUsername));
if (!targetUser && !Punishments.search(toID(targetUsername)).length) {
return this.errorReply(this.tr`User '${targetUsername}' not found.`);
throw new Chat.ErrorMessage(this.tr`User '${targetUsername}' not found.`);
}
if (reason.length > 300) {
return this.errorReply(this.tr`The reason is too long. It cannot exceed 300 characters.`);
throw new Chat.ErrorMessage(this.tr`The reason is too long. It cannot exceed 300 characters.`);
}
let username;
@ -2846,7 +2848,7 @@ export const commands: Chat.ChatCommands = {
const targetID: ID = Users.get(target)?.id || target as ID;
const banned = Punishments.isTicketBanned(targetID);
if (!banned) {
return this.errorReply(this.tr`${target} is not ticket banned.`);
throw new Chat.ErrorMessage(this.tr`${target} is not ticket banned.`);
}
const affected = HelpTicket.unban(targetID);
@ -2859,7 +2861,7 @@ export const commands: Chat.ChatCommands = {
ignore(target, room, user) {
this.checkCan('lock');
if (user.settings.ignoreTickets) {
return this.errorReply(this.tr`You are already ignoring help ticket notifications. Use /helpticket unignore to receive notifications again.`);
throw new Chat.ErrorMessage(this.tr`You are already ignoring help ticket notifications. Use /helpticket unignore to receive notifications again.`);
}
user.settings.ignoreTickets = true;
user.update();
@ -2870,7 +2872,7 @@ export const commands: Chat.ChatCommands = {
unignore(target, room, user) {
this.checkCan('lock');
if (!user.settings.ignoreTickets) {
return this.errorReply(this.tr`You are not ignoring help ticket notifications. Use /helpticket ignore to stop receiving notifications.`);
throw new Chat.ErrorMessage(this.tr`You are not ignoring help ticket notifications. Use /helpticket ignore to stop receiving notifications.`);
}
user.settings.ignoreTickets = false;
user.update();
@ -2883,7 +2885,7 @@ export const commands: Chat.ChatCommands = {
this.checkCan('makeroom');
if (!target) return this.parse(`/help helpticket delete`);
const ticket = tickets[toID(target)];
if (!ticket) return this.errorReply(this.tr`${target} does not have a ticket.`);
if (!ticket) throw new Chat.ErrorMessage(this.tr`${target} does not have a ticket.`);
const targetRoom = Rooms.get(`help-${ticket.userid}`);
if (targetRoom) {
targetRoom.getGame(HelpTicket)!.deleteTicket(user);
@ -2900,7 +2902,7 @@ export const commands: Chat.ChatCommands = {
this.checkCan('lock');
const [targetString, dateString] = Utils.splitFirst(target, ',').map(i => i.trim());
const id = toID(targetString);
if (!id) return this.errorReply(`Specify a userid.`);
if (!id) throw new Chat.ErrorMessage(`Specify a userid.`);
return this.parse(`/j view-help-logs-${id}${dateString ? `--${dateString}` : ''}`);
},
logshelp: [
@ -2916,11 +2918,11 @@ export const commands: Chat.ChatCommands = {
const userid = toID(username);
if (!userid) return this.parse(`/help helpticket`);
if (!/[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(date)) {
return this.errorReply(`Invalid date (must be YYYY-MM-DD format).`);
throw new Chat.ErrorMessage(`Invalid date (must be YYYY-MM-DD format).`);
}
const logPath = Monitor.logPath(`chat/help-${userid}/${date.slice(0, -3)}/${date}.txt`);
if (!(await logPath.exists())) {
return this.errorReply(`There are no logs for tickets from '${userid}' on the date '${date}'.`);
throw new Chat.ErrorMessage(`There are no logs for tickets from '${userid}' on the date '${date}'.`);
}
if (!(await Monitor.logPath(`private/${userid}`).exists())) {
await Monitor.logPath(`private/${userid}`).mkdirp();
@ -2940,11 +2942,11 @@ export const commands: Chat.ChatCommands = {
const userid = toID(username);
if (!userid) return this.parse(`/help helpticket`);
if (!/[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(date)) {
return this.errorReply(`Invalid date (must be YYYY-MM-DD format).`);
throw new Chat.ErrorMessage(`Invalid date (must be YYYY-MM-DD format).`);
}
const logPath = Monitor.logPath(`private/${userid}/${date}.txt`);
if (!(await logPath.exists())) {
return this.errorReply(`There are no logs for tickets from '${userid}' on the date '${date}'.`);
throw new Chat.ErrorMessage(`There are no logs for tickets from '${userid}' on the date '${date}'.`);
}
const monthPath = Monitor.logPath(`chat/help-${userid}/${date.slice(0, -3)}`);
if (!(await monthPath.exists())) {

View File

@ -226,7 +226,7 @@ export const commands: Chat.ChatCommands = {
const types = ['all', 'residential', 'res', 'mobile', 'proxy', 'openproxy'];
const type = target ? toID(target) : 'all';
if (!types.includes(type)) {
return this.errorReply(`'${type}' isn't a valid host type. Specify one of ${types.join(', ')}.`);
throw new Chat.ErrorMessage(`'${type}' isn't a valid host type. Specify one of ${types.join(', ')}.`);
}
return this.parse(`/join view-ranges-${type}`);
},
@ -245,11 +245,11 @@ export const commands: Chat.ChatCommands = {
const [typeString, stringRange, host] = target.split(',').map(part => part.trim());
if (!host || !IPTools.hostRegex.test(host)) {
return this.errorReply(`Invalid data: ${target}`);
throw new Chat.ErrorMessage(`Invalid data: ${target}`);
}
const type = getHostType(typeString);
const range = IPTools.stringToRange(stringRange);
if (!range) return this.errorReply(`Couldn't parse IP range '${stringRange}'.`);
if (!range) throw new Chat.ErrorMessage(`Couldn't parse IP range '${stringRange}'.`);
range.host = `${IPTools.urlToHost(host)}?/${type}`;
IPTools.sortRanges();
@ -257,7 +257,7 @@ export const commands: Chat.ChatCommands = {
try {
result = IPTools.checkRangeConflicts(range, IPTools.ranges, widen);
} catch (e: any) {
return this.errorReply(e.message);
throw new Chat.ErrorMessage(e.message);
}
if (typeof result === 'number') {
// Remove the range that is being widened
@ -280,8 +280,8 @@ export const commands: Chat.ChatCommands = {
if (!target) return this.parse('/help ipranges remove');
const range = IPTools.stringToRange(target);
if (!range) return this.errorReply(`Couldn't parse the IP range '${target}'.`);
if (!IPTools.getRange(range.minIP, range.maxIP)) return this.errorReply(`No IP range found at '${target}'.`);
if (!range) throw new Chat.ErrorMessage(`Couldn't parse the IP range '${target}'.`);
if (!IPTools.getRange(range.minIP, range.maxIP)) throw new Chat.ErrorMessage(`No IP range found at '${target}'.`);
void IPTools.removeRange(range.minIP, range.maxIP);
@ -301,9 +301,9 @@ export const commands: Chat.ChatCommands = {
return this.parse('/help ipranges rename');
}
const toRename = IPTools.stringToRange(rangeString);
if (!toRename) return this.errorReply(`Couldn't parse IP range '${rangeString}'.`);
if (!toRename) throw new Chat.ErrorMessage(`Couldn't parse IP range '${rangeString}'.`);
const exists = IPTools.getRange(toRename.minIP, toRename.maxIP);
if (!exists) return this.errorReply(`No IP range found at '${rangeString}'.`);
if (!exists) throw new Chat.ErrorMessage(`No IP range found at '${rangeString}'.`);
const range = {
minIP: toRename.minIP,
@ -338,7 +338,7 @@ export const commands: Chat.ChatCommands = {
const types = ['all', 'residential', 'mobile', 'ranges'];
const type = target ? toID(target) : 'all';
if (!types.includes(type)) {
return this.errorReply(`'${type}' isn't a valid host type. Specify one of ${types.join(', ')}.`);
throw new Chat.ErrorMessage(`'${type}' isn't a valid host type. Specify one of ${types.join(', ')}.`);
}
return this.parse(`/join view-hosts-${type}`);
},
@ -362,9 +362,9 @@ export const commands: Chat.ChatCommands = {
switch (type) {
case 'openproxy':
for (const host of hosts) {
if (!IPTools.ipRegex.test(host)) return this.errorReply(`'${host}' is not a valid IP address.`);
if (!IPTools.ipRegex.test(host)) throw new Chat.ErrorMessage(`'${host}' is not a valid IP address.`);
if (removing !== IPTools.singleIPOpenProxies.has(host)) {
return this.errorReply(`'${host}' is ${removing ? 'not' : 'already'} in the list of proxy IPs.`);
throw new Chat.ErrorMessage(`'${host}' is ${removing ? 'not' : 'already'} in the list of proxy IPs.`);
}
}
if (removing) {
@ -375,9 +375,9 @@ export const commands: Chat.ChatCommands = {
break;
case 'proxy':
for (const host of hosts) {
if (!IPTools.hostRegex.test(host)) return this.errorReply(`'${host}' is not a valid host.`);
if (!IPTools.hostRegex.test(host)) throw new Chat.ErrorMessage(`'${host}' is not a valid host.`);
if (removing !== IPTools.proxyHosts.has(host)) {
return this.errorReply(`'${host}' is ${removing ? 'not' : 'already'} in the list of proxy hosts.`);
throw new Chat.ErrorMessage(`'${host}' is ${removing ? 'not' : 'already'} in the list of proxy hosts.`);
}
}
if (removing) {
@ -388,9 +388,9 @@ export const commands: Chat.ChatCommands = {
break;
case 'residential':
for (const host of hosts) {
if (!IPTools.hostRegex.test(host)) return this.errorReply(`'${host}' is not a valid host.`);
if (!IPTools.hostRegex.test(host)) throw new Chat.ErrorMessage(`'${host}' is not a valid host.`);
if (removing !== IPTools.residentialHosts.has(host)) {
return this.errorReply(`'${host}' is ${removing ? 'not' : 'already'} in the list of residential hosts.`);
throw new Chat.ErrorMessage(`'${host}' is ${removing ? 'not' : 'already'} in the list of residential hosts.`);
}
}
if (removing) {
@ -401,9 +401,9 @@ export const commands: Chat.ChatCommands = {
break;
case 'mobile':
for (const host of hosts) {
if (!IPTools.hostRegex.test(host)) return this.errorReply(`'${host}' is not a valid host.`);
if (!IPTools.hostRegex.test(host)) throw new Chat.ErrorMessage(`'${host}' is not a valid host.`);
if (removing !== IPTools.mobileHosts.has(host)) {
return this.errorReply(`'${host}' is ${removing ? 'not' : 'already'} in the list of mobile hosts.`);
throw new Chat.ErrorMessage(`'${host}' is ${removing ? 'not' : 'already'} in the list of mobile hosts.`);
}
}
if (removing) {
@ -413,7 +413,7 @@ export const commands: Chat.ChatCommands = {
}
break;
default:
return this.errorReply(`'${type}' isn't one of 'openproxy', 'proxy', 'residential', or 'mobile'.`);
throw new Chat.ErrorMessage(`'${type}' isn't one of 'openproxy', 'proxy', 'residential', or 'mobile'.`);
}
this.privateGlobalModAction(
`${user.name} ${removing ? 'removed' : 'added'} ${hosts.length} hosts (${hosts.join(', ')}) ${removing ? 'from' : 'to'} the ${type} category`
@ -441,23 +441,23 @@ export const commands: Chat.ChatCommands = {
if (!IPTools.ipRegex.test(ip)) {
const pattern = IPTools.stringToRange(ip);
if (!pattern) {
return this.errorReply("Please enter a valid IP address.");
throw new Chat.ErrorMessage("Please enter a valid IP address.");
}
if (!user.can('rangeban')) {
return this.errorReply('Only upper staff can markshare ranges.');
throw new Chat.ErrorMessage('Only upper staff can markshare ranges.');
}
for (const range of Punishments.sharedRanges.keys()) {
if (IPTools.rangeIntersects(range, pattern)) {
return this.errorReply(
throw new Chat.ErrorMessage(
`Range ${IPTools.rangeToString(pattern)} intersects with shared range ${IPTools.rangeToString(range)}`
);
}
}
}
if (Punishments.isSharedIp(ip)) return this.errorReply("This IP is already marked as shared.");
if (Punishments.isSharedIp(ip)) throw new Chat.ErrorMessage("This IP is already marked as shared.");
if (Punishments.isBlacklistedSharedIp(ip)) {
return this.errorReply(`This IP is blacklisted from being marked as shared.`);
throw new Chat.ErrorMessage(`This IP is blacklisted from being marked as shared.`);
}
if (!note) {
this.errorReply(`You must specify who owns this shared IP.`);
@ -479,9 +479,9 @@ export const commands: Chat.ChatCommands = {
checkCanPerform(this, user, 'globalban');
target = target.trim();
const pattern = IPTools.stringToRange(target);
if (!pattern) return this.errorReply("Please enter a valid IP address.");
if (!pattern) throw new Chat.ErrorMessage("Please enter a valid IP address.");
if (pattern.minIP !== pattern.maxIP && !user.can('rangeban')) {
return this.errorReply(`Only administrators can unmarkshare ranges.`);
throw new Chat.ErrorMessage(`Only administrators can unmarkshare ranges.`);
}
let shared = false;
@ -494,7 +494,7 @@ export const commands: Chat.ChatCommands = {
shared = Punishments.sharedIps.has(target);
}
if (!shared) return this.errorReply(`That IP/range isn't marked as shared.`);
if (!shared) throw new Chat.ErrorMessage(`That IP/range isn't marked as shared.`);
Punishments.removeSharedIp(target);
@ -510,14 +510,14 @@ export const commands: Chat.ChatCommands = {
if (!target) return this.parse(`/help nomarkshared`);
checkCanPerform(this, user, 'globalban');
const [ip, ...reasonArr] = target.split(',');
if (!IPTools.ipRangeRegex.test(ip)) return this.errorReply(`Please enter a valid IP address or range.`);
if (!IPTools.ipRangeRegex.test(ip)) throw new Chat.ErrorMessage(`Please enter a valid IP address or range.`);
if (!reasonArr?.length) {
this.errorReply(`A reason is required.`);
this.parse(`/help nomarkshared`);
return;
}
if (Punishments.isBlacklistedSharedIp(ip)) {
return this.errorReply(`This IP is already blacklisted from being marked as shared.`);
throw new Chat.ErrorMessage(`This IP is already blacklisted from being marked as shared.`);
}
// this works because we test ipRangeRegex above, which works for both ranges AND single ips.
// so we know here this is one of the two.
@ -526,15 +526,14 @@ export const commands: Chat.ChatCommands = {
// if it doesn't end with *, it doesn't function as a range in IPTools#stringToRange, only as a single IP.
// that's valid behavior, but it's detrimental here.
if (!ip.endsWith('*')) {
this.errorReply(`That looks like a range, but it is invalid.`);
this.errorReply(`Append * to the end of the range and try again.`);
return;
throw new Chat.ErrorMessage([`That looks like a range, but it is invalid.`,
`Append * to the end of the range and try again.`]);
}
if (!user.can('bypassall')) {
return this.errorReply(`Only Administrators can add ranges.`);
throw new Chat.ErrorMessage(`Only Administrators can add ranges.`);
}
const range = IPTools.stringToRange(ip);
if (!range) return this.errorReply(`Invalid IP range.`);
if (!range) throw new Chat.ErrorMessage(`Invalid IP range.`);
for (const sharedIp of Punishments.sharedIps.keys()) {
const ipNum = IPTools.ipToNumber(sharedIp);
if (IPTools.checkPattern([range], ipNum)) {
@ -554,9 +553,9 @@ export const commands: Chat.ChatCommands = {
remove(target, room, user) {
if (!target) return this.parse(`/help nomarkshared`);
checkCanPerform(this, user);
if (!IPTools.ipRangeRegex.test(target)) return this.errorReply(`Please enter a valid IP address or range.`);
if (!IPTools.ipRangeRegex.test(target)) throw new Chat.ErrorMessage(`Please enter a valid IP address or range.`);
if (!Punishments.sharedIpBlacklist.has(target)) {
return this.errorReply(`This IP is not blacklisted from being marked as shared.`);
throw new Chat.ErrorMessage(`This IP is not blacklisted from being marked as shared.`);
}
Punishments.removeBlacklistedSharedIp(target);

View File

@ -2197,10 +2197,10 @@ export const commands: Chat.ChatCommands = {
nexthost: 'host',
host(target, room, user, connection, cmd) {
room = this.requireRoom();
if (room.settings.mafiaDisabled) return this.errorReply(`Mafia is disabled for this room.`);
if (room.settings.mafiaDisabled) throw new Chat.ErrorMessage(`Mafia is disabled for this room.`);
this.checkChat();
if (room.type !== 'chat') return this.errorReply(`This command is only meant to be used in chat rooms.`);
if (room.game) return this.errorReply(`There is already a game of ${room.game.title} in progress in this room.`);
if (room.type !== 'chat') throw new Chat.ErrorMessage(`This command is only meant to be used in chat rooms.`);
if (room.game) throw new Chat.ErrorMessage(`There is already a game of ${room.game.title} in progress in this room.`);
const nextHost = room.roomid === 'mafia' && cmd === 'nexthost';
if (nextHost || !room.auth.has(user.id)) this.checkCan('show', null, room);
@ -2208,7 +2208,7 @@ export const commands: Chat.ChatCommands = {
let targetUser!: User | null;
let targetUsername!: string;
if (nextHost) {
if (!hostQueue.length) return this.errorReply(`Nobody is on the host queue.`);
if (!hostQueue.length) throw new Chat.ErrorMessage(`Nobody is on the host queue.`);
const skipped = [];
let hostid;
while ((hostid = hostQueue.shift())) {
@ -2225,27 +2225,27 @@ export const commands: Chat.ChatCommands = {
if (skipped.length) {
this.sendReply(`${skipped.join(', ')} ${Chat.plural(skipped.length, 'were', 'was')} not online, not in the room, or are host banned and were removed from the host queue.`);
}
if (!targetUser) return this.errorReply(`Nobody on the host queue could be hosted.`);
if (!targetUser) throw new Chat.ErrorMessage(`Nobody on the host queue could be hosted.`);
} else {
({ targetUser, targetUsername } = this.splitUser(target, { exactName: true }));
if (room.roomid === 'mafia' && hostQueue.length && toID(targetUsername) !== hostQueue[0]) {
if (!cmd.includes('force')) {
return this.errorReply(`${targetUsername} isn't the next host on the queue. Use /mafia forcehost if you're sure.`);
throw new Chat.ErrorMessage(`${targetUsername} isn't the next host on the queue. Use /mafia forcehost if you're sure.`);
}
}
}
if (!targetUser?.connected) {
return this.errorReply(`The user "${targetUsername}" was not found.`);
throw new Chat.ErrorMessage(`The user "${targetUsername}" was not found.`);
}
if (!nextHost && targetUser.id !== user.id) this.checkCan('mute', null, room);
if (!room.users[targetUser.id]) {
return this.errorReply(`${targetUsername} is not in this room, and cannot be hosted.`);
throw new Chat.ErrorMessage(`${targetUsername} is not in this room, and cannot be hosted.`);
}
if (Mafia.isHostBanned(room, targetUser)) {
return this.errorReply(`${targetUsername} is banned from hosting mafia games.`);
throw new Chat.ErrorMessage(`${targetUsername} is banned from hosting mafia games.`);
}
room.game = new Mafia(room, targetUser);
@ -2268,7 +2268,7 @@ export const commands: Chat.ChatCommands = {
q: 'queue',
queue(target, room, user) {
room = this.requireRoom('mafia' as RoomID);
if (room.settings.mafiaDisabled) return this.errorReply(`Mafia is disabled for this room.`);
if (room.settings.mafiaDisabled) throw new Chat.ErrorMessage(`Mafia is disabled for this room.`);
const [command, targetUserID] = target.split(',').map(toID);
switch (command) {
@ -2284,11 +2284,11 @@ export const commands: Chat.ChatCommands = {
if (!targetUserID) return this.parse(`/help mafia queue`);
const targetUser = Users.get(targetUserID);
if ((!targetUser?.connected) && !command.includes('force')) {
return this.errorReply(`User ${targetUserID} not found. To forcefully add the user to the queue, use /mafia queue forceadd, ${targetUserID}`);
throw new Chat.ErrorMessage(`User ${targetUserID} not found. To forcefully add the user to the queue, use /mafia queue forceadd, ${targetUserID}`);
}
if (hostQueue.includes(targetUserID)) return this.errorReply(`User ${targetUserID} is already on the host queue.`);
if (hostQueue.includes(targetUserID)) throw new Chat.ErrorMessage(`User ${targetUserID} is already on the host queue.`);
if (targetUser && Mafia.isHostBanned(room, targetUser)) {
return this.errorReply(`User ${targetUserID} is banned from hosting mafia games.`);
throw new Chat.ErrorMessage(`User ${targetUserID} is banned from hosting mafia games.`);
}
hostQueue.push(targetUserID);
room.add(`User ${targetUserID} has been added to the host queue by ${user.name}.`).update();
@ -2299,7 +2299,7 @@ export const commands: Chat.ChatCommands = {
// anyone can self remove
if (targetUserID !== user.id) this.checkCan('mute', null, room);
const index = hostQueue.indexOf(targetUserID);
if (index === -1) return this.errorReply(`User ${targetUserID} is not on the host queue.`);
if (index === -1) throw new Chat.ErrorMessage(`User ${targetUserID} is not on the host queue.`);
hostQueue.splice(index, 1);
room.add(`User ${targetUserID} has been removed from the host queue by ${user.name}.`).update();
break;
@ -2352,13 +2352,13 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
const game = this.requireGame(Mafia);
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
if (game.phase !== 'signups') return this.errorReply(`Signups are already closed.`);
if (game.phase !== 'signups') throw new Chat.ErrorMessage(`Signups are already closed.`);
const num = parseInt(target);
if (isNaN(num) || num > 50 || num < 2) return this.parse('/help mafia playercap');
if (num < game.playerCount) {
return this.errorReply(`Player cap has to be equal or more than the amount of players in game.`);
throw new Chat.ErrorMessage(`Player cap has to be equal or more than the amount of players in game.`);
}
if (num === game.playerCap) return this.errorReply(`Player cap is already set at ${game.playerCap}.`);
if (num === game.playerCap) throw new Chat.ErrorMessage(`Player cap is already set at ${game.playerCap}.`);
game.playerCap = num;
game.sendDeclare(`Player cap has been set to ${game.playerCap}`);
game.logAction(user, `set playercap to ${num}`);
@ -2371,8 +2371,8 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
const game = this.requireGame(Mafia);
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
if (game.phase !== 'signups') return this.errorReply(`Signups are already closed.`);
if (game.playerCount < 2) return this.errorReply(`You need at least 2 players to start.`);
if (game.phase !== 'signups') throw new Chat.ErrorMessage(`Signups are already closed.`);
if (game.playerCount < 2) throw new Chat.ErrorMessage(`You need at least 2 players to start.`);
game.phase = 'locked';
game.sendHTML(game.roomWindow());
game.updatePlayers();
@ -2388,10 +2388,10 @@ export const commands: Chat.ChatCommands = {
const action = toID(target);
if (!['on', 'off'].includes(action)) return this.parse('/help mafia closedsetup');
if (game.started) {
return this.errorReply(`You can't ${action === 'on' ? 'enable' : 'disable'} closed setup because the game has already started.`);
throw new Chat.ErrorMessage(`You can't ${action === 'on' ? 'enable' : 'disable'} closed setup because the game has already started.`);
}
if ((action === 'on' && game.closedSetup) || (action === 'off' && !game.closedSetup)) {
return this.errorReply(`Closed setup is already ${game.closedSetup ? 'enabled' : 'disabled'}.`);
throw new Chat.ErrorMessage(`Closed setup is already ${game.closedSetup ? 'enabled' : 'disabled'}.`);
}
game.closedSetup = action === 'on';
game.sendDeclare(`The game is ${action === 'on' ? 'now' : 'no longer'} a closed setup.`);
@ -2428,7 +2428,7 @@ export const commands: Chat.ChatCommands = {
const action = toID(target);
if (!['on', 'off'].includes(action)) return this.parse('/help mafia takeidles');
if ((action === 'off' && !game.takeIdles) || (action === 'on' && game.takeIdles)) {
return this.errorReply(`Actions and idles are already ${game.takeIdles ? '' : 'not '}being accepted.`);
throw new Chat.ErrorMessage(`Actions and idles are already ${game.takeIdles ? '' : 'not '}being accepted.`);
}
game.takeIdles = action === 'on';
game.sendDeclare(`Actions and idles are ${game.takeIdles ? 'now' : 'no longer'} being accepted.`);
@ -2445,10 +2445,12 @@ export const commands: Chat.ChatCommands = {
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
const reset = cmd.includes('reset');
if (reset) {
if (game.phase !== 'day' && game.phase !== 'night') return this.errorReply(`The game has not started yet.`);
if (game.phase !== 'day' && game.phase !== 'night') throw new Chat.ErrorMessage(`The game has not started yet.`);
} else {
if (game.phase !== 'locked' && game.phase !== 'IDEAlocked') {
return this.errorReply(game.phase === 'signups' ? `You need to close signups first.` : `The game has already started.`);
throw new Chat.ErrorMessage(
game.phase === 'signups' ? `You need to close signups first.` : `The game has already started.`
);
}
}
if (!target) return this.parse('/help mafia setroles');
@ -2469,8 +2471,8 @@ export const commands: Chat.ChatCommands = {
const game = this.requireGame(Mafia);
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
if (target) return this.parse('/help mafia resetgame');
if (game.phase !== 'day' && game.phase !== 'night') return this.errorReply(`The game has not started yet.`);
if (game.IDEA.data) return this.errorReply(`You cannot use this command in IDEA.`);
if (game.phase !== 'day' && game.phase !== 'night') throw new Chat.ErrorMessage(`The game has not started yet.`);
if (game.IDEA.data) throw new Chat.ErrorMessage(`You cannot use this command in IDEA.`);
game.resetGame();
game.logAction(user, 'reset the game state');
},
@ -2483,11 +2485,11 @@ export const commands: Chat.ChatCommands = {
const game = this.requireGame(Mafia);
this.checkCan('show', null, room);
if (!user.can('mute', null, room) && game.hostid !== user.id && !game.cohostids.includes(user.id)) {
return this.errorReply(`/mafia idea - Access denied.`);
throw new Chat.ErrorMessage(`/mafia idea - Access denied.`);
}
if (game.started) return this.errorReply(`You cannot start an IDEA after the game has started.`);
if (game.started) throw new Chat.ErrorMessage(`You cannot start an IDEA after the game has started.`);
if (game.phase !== 'locked' && game.phase !== 'IDEAlocked') {
return this.errorReply(`You need to close the signups first.`);
throw new Chat.ErrorMessage(`You need to close the signups first.`);
}
game.ideaInit(user, toID(target));
game.logAction(user, `started an IDEA`);
@ -2503,17 +2505,17 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
this.checkCan('mute', null, room);
const game = this.requireGame(Mafia);
if (game.started) return this.errorReply(`You cannot start an IDEA after the game has started.`);
if (game.started) throw new Chat.ErrorMessage(`You cannot start an IDEA after the game has started.`);
if (game.phase !== 'locked' && game.phase !== 'IDEAlocked') {
return this.errorReply(`You need to close the signups first.`);
throw new Chat.ErrorMessage(`You need to close the signups first.`);
}
const [options, roles] = Utils.splitFirst(target, '\n');
if (!options || !roles) return this.parse('/help mafia idea');
const [choicesStr, ...picks] = options.split(',').map(x => x.trim());
const choices = parseInt(choicesStr);
if (!choices || choices <= picks.length) return this.errorReply(`You need to have more choices than picks.`);
if (!choices || choices <= picks.length) throw new Chat.ErrorMessage(`You need to have more choices than picks.`);
if (picks.some((value, index, arr) => arr.indexOf(value, index + 1) > 0)) {
return this.errorReply(`Your picks must be unique.`);
throw new Chat.ErrorMessage(`Your picks must be unique.`);
}
game.customIdeaInit(user, choices, picks, roles);
},
@ -2532,7 +2534,7 @@ export const commands: Chat.ChatCommands = {
return user.sendTo(room, '|error|You are not a player in the game.');
}
if (game.phase !== 'IDEApicking') {
return this.errorReply(`The game is not in the IDEA picking phase.`);
throw new Chat.ErrorMessage(`The game is not in the IDEA picking phase.`);
}
game.ideaPick(user, args); // TODO use player object
},
@ -2550,14 +2552,14 @@ export const commands: Chat.ChatCommands = {
ideadiscards(target, room, user) {
room = this.requireRoom();
const game = this.requireGame(Mafia);
if (!game.IDEA.data) return this.errorReply(`There is no IDEA module in the mafia game.`);
if (!game.IDEA.data) throw new Chat.ErrorMessage(`There is no IDEA module in the mafia game.`);
if (target) {
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
if (this.meansNo(target)) {
if (game.IDEA.discardsHidden) return this.errorReply(`IDEA discards are already hidden.`);
if (game.IDEA.discardsHidden) throw new Chat.ErrorMessage(`IDEA discards are already hidden.`);
game.IDEA.discardsHidden = true;
} else if (this.meansYes(target)) {
if (!game.IDEA.discardsHidden) return this.errorReply(`IDEA discards are already visible.`);
if (!game.IDEA.discardsHidden) throw new Chat.ErrorMessage(`IDEA discards are already visible.`);
game.IDEA.discardsHidden = false;
} else {
return this.parse('/help mafia ideadiscards');
@ -2565,8 +2567,8 @@ export const commands: Chat.ChatCommands = {
game.logAction(user, `${game.IDEA.discardsHidden ? 'hid' : 'unhid'} IDEA discards`);
return this.sendReply(`IDEA discards are now ${game.IDEA.discardsHidden ? 'hidden' : 'visible'}.`);
}
if (game.IDEA.discardsHidden) return this.errorReply(`Discards are not visible.`);
if (!game.IDEA.discardsHTML) return this.errorReply(`The IDEA module does not have finalised discards yet.`);
if (game.IDEA.discardsHidden) throw new Chat.ErrorMessage(`Discards are not visible.`);
if (!game.IDEA.discardsHTML) throw new Chat.ErrorMessage(`The IDEA module does not have finalised discards yet.`);
if (!this.runBroadcast()) return;
this.sendReplyBox(`<details><summary>IDEA discards:</summary>${game.IDEA.discardsHTML}</details>`);
},
@ -2649,7 +2651,7 @@ export const commands: Chat.ChatCommands = {
this.checkChat(null, room);
const player = game.getPlayer(user.id);
if (!player || (player.isEliminated() && !player.isSpirit())) {
return this.errorReply(`You are not in the game of ${game.title}.`);
throw new Chat.ErrorMessage(`You are not in the game of ${game.title}.`);
}
game.vote(player, toID(target));
},
@ -2664,7 +2666,7 @@ export const commands: Chat.ChatCommands = {
this.checkChat(null, room);
const player = game.getPlayer(user.id);
if (!player || (player.isEliminated() && !player.isSpirit())) {
return this.errorReply(`You are not in the game of ${game.title}.`);
throw new Chat.ErrorMessage(`You are not in the game of ${game.title}.`);
}
game.unvote(player);
},
@ -2706,12 +2708,12 @@ export const commands: Chat.ChatCommands = {
const game = this.requireGame(Mafia);
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
if (game.phase === 'IDEApicking') {
return this.errorReply(`You cannot add or remove players while IDEA roles are being picked.`); // needs to be here since eliminate doesn't pass the user
throw new Chat.ErrorMessage(`You cannot add or remove players while IDEA roles are being picked.`); // needs to be here since eliminate doesn't pass the user
}
if (!target) return this.parse('/help mafia kill');
const player = game.getPlayer(toID(target));
if (!player) {
return this.errorReply(`${target.trim()} is not a player.`);
throw new Chat.ErrorMessage(`${target.trim()} is not a player.`);
}
let repeat, elimType;
@ -2738,7 +2740,7 @@ export const commands: Chat.ChatCommands = {
break;
}
if (repeat) return this.errorReply(`${player.safeName} has already been ${cmd}ed.`);
if (repeat) throw new Chat.ErrorMessage(`${player.safeName} has already been ${cmd}ed.`);
game.eliminate(player, elimType);
game.logAction(user, `${cmd}ed ${player.safeName}`);
@ -2794,16 +2796,16 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
const game = this.requireGame(Mafia);
const player = game.getPlayer(user.id);
if (!player) return this.errorReply(`You are not in the game of ${game.title}.`);
if (!player) throw new Chat.ErrorMessage(`You are not in the game of ${game.title}.`);
if (player.isEliminated()) {
return this.errorReply(`You have been eliminated from the game and cannot take any actions.`);
throw new Chat.ErrorMessage(`You have been eliminated from the game and cannot take any actions.`);
}
if (game.phase !== 'night') {
return this.errorReply(`You can only submit an action or idle during the night phase.`);
throw new Chat.ErrorMessage(`You can only submit an action or idle during the night phase.`);
}
if (!game.takeIdles) {
return this.errorReply(`The host is not accepting idles through the script. Send your action or idle to the host.`);
throw new Chat.ErrorMessage(`The host is not accepting idles through the script. Send your action or idle to the host.`);
}
switch (cmd) {
@ -2900,7 +2902,7 @@ export const commands: Chat.ChatCommands = {
return this.parse(`/help mafia deadline`);
}
}
if (num < 1 || num > 20) return this.errorReply(`The deadline must be between 1 and 20 minutes.`);
if (num < 1 || num > 20) throw new Chat.ErrorMessage(`The deadline must be between 1 and 20 minutes.`);
game.setDeadline(num);
}
game.logAction(user, `changed deadline`);
@ -2914,7 +2916,7 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
const game = this.requireGame(Mafia);
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
if (!game.started) return this.errorReply(`The game has not started yet.`);
if (!game.started) throw new Chat.ErrorMessage(`The game has not started yet.`);
const [playerId, mod] = target.split(',');
const player = game.getPlayer(toID(playerId));
if (!player) {
@ -2934,7 +2936,7 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
const game = this.requireGame(Mafia);
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
if (!game.started) return this.errorReply(`The game has not started yet.`);
if (!game.started) throw new Chat.ErrorMessage(`The game has not started yet.`);
if (cmd === 'clearhammermodifiers') {
game.clearHammerModifiers(user);
game.secretLogAction(user, `cleared hammer modifiers`);
@ -2989,14 +2991,14 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
const game = this.requireGame(Mafia);
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
if (!game.started) return this.errorReply(`The game has not started yet.`);
if (!game.started) throw new Chat.ErrorMessage(`The game has not started yet.`);
target = toID(target);
const targetPlayer = game.getPlayer(target as ID);
const silence = cmd === 'silence';
if (!targetPlayer) return this.errorReply(`${target} is not in the game of mafia.`);
if (!targetPlayer) throw new Chat.ErrorMessage(`${target} is not in the game of mafia.`);
if (silence === targetPlayer.silenced) {
return this.errorReply(`${targetPlayer.name} is already ${!silence ? 'not' : ''} silenced.`);
throw new Chat.ErrorMessage(`${targetPlayer.name} is already ${!silence ? 'not' : ''} silenced.`);
}
targetPlayer.silenced = silence;
this.sendReply(`${targetPlayer.name} has been ${!silence ? 'un' : ''}silenced.`);
@ -3014,14 +3016,14 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
const game = this.requireGame(Mafia);
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
if (!game.started) return this.errorReply(`The game has not started yet.`);
if (!game.started) throw new Chat.ErrorMessage(`The game has not started yet.`);
target = toID(target);
const targetPlayer = game.getPlayer(target as ID);
const nighttalk = !cmd.startsWith('un');
if (!targetPlayer) return this.errorReply(`${target} is not in the game of mafia.`);
if (!targetPlayer) throw new Chat.ErrorMessage(`${target} is not in the game of mafia.`);
if (nighttalk === targetPlayer.nighttalk) {
return this.errorReply(`${targetPlayer.name} is already ${!nighttalk ? 'not' : ''} able to talk during the night.`);
throw new Chat.ErrorMessage(`${targetPlayer.name} is already ${!nighttalk ? 'not' : ''} able to talk during the night.`);
}
targetPlayer.nighttalk = nighttalk;
this.sendReply(`${targetPlayer.name} can ${!nighttalk ? 'no longer' : 'now'} talk during the night.`);
@ -3038,27 +3040,27 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
const game = this.requireGame(Mafia);
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
if (!game.started) return this.errorReply(`The game has not started yet.`);
if (!game.started) throw new Chat.ErrorMessage(`The game has not started yet.`);
target = toID(target);
const targetPlayer = game.getPlayer(target as ID);
if (!targetPlayer) return this.errorReply(`${target} is not in the game of mafia.`);
if (!targetPlayer) throw new Chat.ErrorMessage(`${target} is not in the game of mafia.`);
const actor = cmd.endsWith('actor');
const remove = cmd.startsWith('un');
if (remove) {
if (targetPlayer.hammerRestriction === null) {
return this.errorReply(`${targetPlayer.name} already has no voting restrictions.`);
throw new Chat.ErrorMessage(`${targetPlayer.name} already has no voting restrictions.`);
}
if (actor !== targetPlayer.hammerRestriction) {
return this.errorReply(`${targetPlayer.name} is ${targetPlayer.hammerRestriction ? 'an actor' : 'a priest'}.`);
throw new Chat.ErrorMessage(`${targetPlayer.name} is ${targetPlayer.hammerRestriction ? 'an actor' : 'a priest'}.`);
}
targetPlayer.hammerRestriction = null;
return this.sendReply(`${targetPlayer}'s hammer restriction was removed.`);
}
if (actor === targetPlayer.hammerRestriction) {
return this.errorReply(`${targetPlayer.name} is already ${targetPlayer.hammerRestriction ? 'an actor' : 'a priest'}.`);
throw new Chat.ErrorMessage(`${targetPlayer.name} is already ${targetPlayer.hammerRestriction ? 'an actor' : 'a priest'}.`);
}
targetPlayer.hammerRestriction = actor;
this.sendReply(`${targetPlayer.name} is now ${targetPlayer.hammerRestriction ? "an actor (can only hammer)" : "a priest (can't hammer)"}.`);
@ -3079,10 +3081,10 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
const game = this.requireGame(Mafia);
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
if (!game.started) return this.errorReply(`The game has not started yet.`);
if (!game.started) throw new Chat.ErrorMessage(`The game has not started yet.`);
const hammer = parseInt(target);
if (toID(cmd) !== `resethammer` && ((isNaN(hammer) && !this.meansNo(target)) || hammer < 1)) {
return this.errorReply(`${target} is not a valid hammer count.`);
throw new Chat.ErrorMessage(`${target} is not a valid hammer count.`);
}
switch (cmd.toLowerCase()) {
case 'shifthammer':
@ -3166,12 +3168,12 @@ export const commands: Chat.ChatCommands = {
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
target = toID(target);
if (this.meansYes(target)) {
if (game.forceVote) return this.errorReply(`Forcevoting is already enabled.`);
if (game.forceVote) throw new Chat.ErrorMessage(`Forcevoting is already enabled.`);
game.forceVote = true;
if (game.started) game.resetHammer();
game.sendDeclare(`Forcevoting has been enabled. Your vote will start on yourself, and you cannot unvote!`);
} else if (this.meansNo(target)) {
if (!game.forceVote) return this.errorReply(`Forcevoting is already disabled.`);
if (!game.forceVote) throw new Chat.ErrorMessage(`Forcevoting is already disabled.`);
game.forceVote = false;
game.sendDeclare(`Forcevoting has been disabled. You can vote normally now!`);
} else {
@ -3186,7 +3188,7 @@ export const commands: Chat.ChatCommands = {
votes(target, room, user) {
room = this.requireRoom();
const game = this.requireGame(Mafia);
if (!game.started) return this.errorReply(`The game of mafia has not started yet.`);
if (!game.started) throw new Chat.ErrorMessage(`The game of mafia has not started yet.`);
// hack to let hosts broadcast
if (game.hostid === user.id || game.cohostids.includes(user.id)) {
@ -3222,7 +3224,7 @@ export const commands: Chat.ChatCommands = {
rolelist(target, room, user, connection, cmd) {
room = this.requireRoom();
const game = this.requireGame(Mafia);
if (game.closedSetup) return this.errorReply(`You cannot show roles in a closed setup.`);
if (game.closedSetup) throw new Chat.ErrorMessage(`You cannot show roles in a closed setup.`);
if (!this.runBroadcast()) return false;
if (game.IDEA.data) {
const buf = `<details><summary>IDEA roles:</summary>${game.IDEA.data.roles.join(`<br />`)}</details>`;
@ -3240,9 +3242,9 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
const game = this.requireGame(Mafia);
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) {
return this.errorReply(`Only the host can view roles.`);
throw new Chat.ErrorMessage(`Only the host can view roles.`);
}
if (!game.started) return this.errorReply(`The game has not started.`);
if (!game.started) throw new Chat.ErrorMessage(`The game has not started.`);
this.sendReplyBox(game.players.map(
p => `${p.safeName}: ${p.role ? (p.role.alignment === 'solo' ? 'Solo ' : '') + p.role.safeName : 'No role'}`
).join('<br/>'));
@ -3278,15 +3280,15 @@ export const commands: Chat.ChatCommands = {
if (player) {
// Check if they have requested to be subbed out.
if (!game.requestedSub.includes(user.id)) {
return this.errorReply(`You have not requested to be subbed out.`);
throw new Chat.ErrorMessage(`You have not requested to be subbed out.`);
}
game.requestedSub.splice(game.requestedSub.indexOf(user.id), 1);
this.errorReply(`You have cancelled your request to sub out.`);
player.updateHtmlRoom();
} else {
this.checkChat(null, room);
if (game.subs.includes(user.id)) return this.errorReply(`You are already on the sub list.`);
if (game.played.includes(user.id)) return this.errorReply(`You cannot sub back into the game.`);
if (game.subs.includes(user.id)) throw new Chat.ErrorMessage(`You are already on the sub list.`);
if (game.played.includes(user.id)) throw new Chat.ErrorMessage(`You cannot sub back into the game.`);
// Change this to game.canJoin(user, true, true) if you're trying to test something sub related locally.
game.canJoin(user, true);
game.subs.push(user.id);
@ -3298,19 +3300,19 @@ export const commands: Chat.ChatCommands = {
case 'out':
if (player) {
if (player.isEliminated()) {
return this.errorReply(`You cannot request to be subbed out once eliminated.`);
throw new Chat.ErrorMessage(`You cannot request to be subbed out once eliminated.`);
}
if (game.requestedSub.includes(user.id)) {
return this.errorReply(`You have already requested to be subbed out.`);
throw new Chat.ErrorMessage(`You have already requested to be subbed out.`);
}
game.requestedSub.push(user.id);
player.updateHtmlRoom();
game.nextSub();
} else {
if (game.hostid === user.id || game.cohostids.includes(user.id)) {
return this.errorReply(`The host cannot sub out of the game.`);
throw new Chat.ErrorMessage(`The host cannot sub out of the game.`);
}
if (!game.subs.includes(user.id)) return this.errorReply(`You are not on the sub list.`);
if (!game.subs.includes(user.id)) throw new Chat.ErrorMessage(`You are not on the sub list.`);
game.subs.splice(game.subs.indexOf(user.id), 1);
// Update spectator's view
this.parse(`/join view-mafia-${room.roomid}`);
@ -3319,10 +3321,10 @@ export const commands: Chat.ChatCommands = {
case 'next':
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
const toSub = args.shift();
if (!game.getPlayer(toID(toSub))) return this.errorReply(`${toSub} is not in the game.`);
if (!game.getPlayer(toID(toSub))) throw new Chat.ErrorMessage(`${toSub} is not in the game.`);
if (!game.subs.length) {
if (game.hostRequestedSub.includes(toID(toSub))) {
return this.errorReply(`${toSub} is already on the list to be subbed out.`);
throw new Chat.ErrorMessage(`${toSub} is already on the list to be subbed out.`);
}
user.sendTo(
room,
@ -3366,10 +3368,10 @@ export const commands: Chat.ChatCommands = {
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
const toSubOut = game.getPlayer(action);
const toSubIn = toID(args.shift());
if (!toSubOut) return this.errorReply(`${toSubOut} is not in the game.`);
if (!toSubOut) throw new Chat.ErrorMessage(`${toSubOut} is not in the game.`);
const targetUser = Users.get(toSubIn);
if (!targetUser) return this.errorReply(`The user "${toSubIn}" was not found.`);
if (!targetUser) throw new Chat.ErrorMessage(`The user "${toSubIn}" was not found.`);
game.canJoin(targetUser, false, cmd === 'forcesub');
if (game.subs.includes(targetUser.id)) {
game.subs.splice(game.subs.indexOf(targetUser.id), 1);
@ -3399,12 +3401,12 @@ export const commands: Chat.ChatCommands = {
const game = this.requireGame(Mafia);
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) this.checkCan('mute', null, room);
if (this.meansYes(toID(target))) {
if (game.autoSub) return this.errorReply(`Automatic subbing of players is already enabled.`);
if (game.autoSub) throw new Chat.ErrorMessage(`Automatic subbing of players is already enabled.`);
game.autoSub = true;
user.sendTo(room, `Automatic subbing of players has been enabled.`);
game.nextSub();
} else if (this.meansNo(toID(target))) {
if (!game.autoSub) return this.errorReply(`Automatic subbing of players is already disabled.`);
if (!game.autoSub) throw new Chat.ErrorMessage(`Automatic subbing of players is already disabled.`);
game.autoSub = false;
user.sendTo(room, `Automatic subbing of players has been disabled.`);
} else {
@ -3424,12 +3426,14 @@ export const commands: Chat.ChatCommands = {
if (!target) return this.parse(`/help mafia ${cmd}`);
this.checkCan('mute', null, room);
const { targetUser } = this.requireUser(target);
if (!room.users[targetUser.id]) return this.errorReply(`${targetUser.name} is not in this room, and cannot be hosted.`);
if (game.hostid === targetUser.id) return this.errorReply(`${targetUser.name} is already the host.`);
if (game.cohostids.includes(targetUser.id)) return this.errorReply(`${targetUser.name} is already a cohost.`);
if (!room.users[targetUser.id]) {
throw new Chat.ErrorMessage(`${targetUser.name} is not in this room, and cannot be hosted.`);
}
if (game.hostid === targetUser.id) throw new Chat.ErrorMessage(`${targetUser.name} is already the host.`);
if (game.cohostids.includes(targetUser.id)) throw new Chat.ErrorMessage(`${targetUser.name} is already a cohost.`);
if (game.getPlayer(targetUser.id)) {
return this.errorReply(`${targetUser.name} cannot become a host because they are playing.`);
throw new Chat.ErrorMessage(`${targetUser.name} cannot become a host because they are playing.`);
}
if (game.subs.includes(targetUser.id)) game.subs.splice(game.subs.indexOf(targetUser.id), 1);
@ -3474,9 +3478,9 @@ export const commands: Chat.ChatCommands = {
const cohostIndex = game.cohostids.indexOf(targetID);
if (cohostIndex < 0) {
if (game.hostid === targetID) {
return this.errorReply(`${target} is the host, not a cohost. Use /mafia subhost to replace them.`);
throw new Chat.ErrorMessage(`${target} is the host, not a cohost. Use /mafia subhost to replace them.`);
}
return this.errorReply(`${target} is not a cohost.`);
throw new Chat.ErrorMessage(`${target} is not a cohost.`);
}
game.cohostids.splice(cohostIndex, 1);
game.sendDeclare(Utils.html`${target} was removed as a cohost by ${user.name}`);
@ -3498,18 +3502,18 @@ export const commands: Chat.ChatCommands = {
term: 'data',
dt: 'data',
data(target, room, user, connection, cmd) {
if (room?.settings.mafiaDisabled) return this.errorReply(`Mafia is disabled for this room.`);
if (room?.settings.mafiaDisabled) throw new Chat.ErrorMessage(`Mafia is disabled for this room.`);
if (cmd === 'role' && !target && room) {
// Support /mafia role showing your current role if you're in a game
const game = room.getGame(Mafia);
if (!game) {
return this.errorReply(`There is no game of mafia running in this room. If you meant to display information about a role, use /mafia role [role name]`);
throw new Chat.ErrorMessage(`There is no game of mafia running in this room. If you meant to display information about a role, use /mafia role [role name]`);
}
const player = game.getPlayer(user.id);
if (!player) return this.errorReply(`You are not in the game of ${game.title}.`);
if (!player) throw new Chat.ErrorMessage(`You are not in the game of ${game.title}.`);
const role = player.role;
if (!role) return this.errorReply(`You do not have a role yet.`);
if (!role) throw new Chat.ErrorMessage(`You do not have a role yet.`);
return this.sendReplyBox(`Your role is: ${role.safeName}`);
}
@ -3547,7 +3551,7 @@ export const commands: Chat.ChatCommands = {
}
}
}
if (!result) return this.errorReply(`"${target}" is not a valid mafia alignment, role, theme, or IDEA.`);
if (!result) throw new Chat.ErrorMessage(`"${target}" is not a valid mafia alignment, role, theme, or IDEA.`);
// @ts-expect-error property access
let buf = `<h3${result.color ? ` style="color: ${result.color}"` : ``}>${result.name}</h3><b>Type</b>: ${dataType}<br/>`;
@ -3593,7 +3597,7 @@ export const commands: Chat.ChatCommands = {
win(target, room, user, connection, cmd) {
const isUnwin = cmd.startsWith('unwin');
room = this.requireRoom('mafia' as RoomID);
if (!room || room.settings.mafiaDisabled) return this.errorReply(`Mafia is disabled for this room.`);
if (!room || room.settings.mafiaDisabled) throw new Chat.ErrorMessage(`Mafia is disabled for this room.`);
this.checkCan('mute', null, room);
const args = target.split(',');
let points = parseInt(args[0]);
@ -3607,7 +3611,7 @@ export const commands: Chat.ChatCommands = {
}
} else {
if (points > 100 || points < -100) {
return this.errorReply(`You cannot give or take more than 100 points at a time.`);
throw new Chat.ErrorMessage(`You cannot give or take more than 100 points at a time.`);
}
// shift out the point count
args.shift();
@ -3658,7 +3662,7 @@ export const commands: Chat.ChatCommands = {
unmvp: 'mvp',
mvp(target, room, user, connection, cmd) {
room = this.requireRoom('mafia' as RoomID);
if (!room || room.settings.mafiaDisabled) return this.errorReply(`Mafia is disabled for this room.`);
if (!room || room.settings.mafiaDisabled) throw new Chat.ErrorMessage(`Mafia is disabled for this room.`);
this.checkCan('mute', null, room);
const args = target.split(',');
if (!args.length) return this.parse('/help mafia mvp');
@ -3699,7 +3703,7 @@ export const commands: Chat.ChatCommands = {
lb: 'leaderboard',
leaderboard(target, room, user, connection, cmd) {
room = this.requireRoom('mafia' as RoomID);
if (!room || room.settings.mafiaDisabled) return this.errorReply(`Mafia is disabled for this room.`);
if (!room || room.settings.mafiaDisabled) throw new Chat.ErrorMessage(`Mafia is disabled for this room.`);
if (['hostlogs', 'playlogs', 'leaverlogs'].includes(cmd)) {
this.checkCan('mute', null, room);
} else {
@ -3737,14 +3741,14 @@ export const commands: Chat.ChatCommands = {
if (!duration) duration = 2;
if (!reason) reason = '';
if (reason.length > 300) {
return this.errorReply("The reason is too long. It cannot exceed 300 characters.");
throw new Chat.ErrorMessage("The reason is too long. It cannot exceed 300 characters.");
}
const userid = toID(targetUser);
if (Punishments.hasRoomPunishType(room, userid, `MAFIA${this.cmd.toUpperCase()}`)) {
return this.errorReply(`User '${targetUser.name}' is already ${this.cmd}ned in this room.`);
throw new Chat.ErrorMessage(`User '${targetUser.name}' is already ${this.cmd}ned in this room.`);
} else if (Punishments.hasRoomPunishType(room, userid, `MAFIAGAMEBAN`)) {
return this.errorReply(`User '${targetUser.name}' is already gamebanned in this room, which also means they can't host.`);
throw new Chat.ErrorMessage(`User '${targetUser.name}' is already gamebanned in this room, which also means they can't host.`);
} else if (Punishments.hasRoomPunishType(room, userid, `MAFIAHOSTBAN`)) {
user.sendTo(room, `User '${targetUser.name}' is already hostbanned in this room, but they will now be gamebanned.`);
this.parse(`/mafia unhostban ${targetUser.name}`);
@ -3778,9 +3782,9 @@ export const commands: Chat.ChatCommands = {
const { targetUser } = this.requireUser(target, { allowOffline: true });
if (!Mafia.isGameBanned(room, targetUser) && cmd === 'ungameban') {
return this.errorReply(`User '${targetUser.name}' isn't banned from playing mafia games.`);
throw new Chat.ErrorMessage(`User '${targetUser.name}' isn't banned from playing mafia games.`);
} else if (!Mafia.isHostBanned(room, targetUser) && cmd === 'unhostban') {
return this.errorReply(`User '${targetUser.name}' isn't banned from hosting mafia games.`);
throw new Chat.ErrorMessage(`User '${targetUser.name}' isn't banned from hosting mafia games.`);
}
if (cmd === 'unhostban') Mafia.unhostBan(room, targetUser);
@ -3801,15 +3805,17 @@ export const commands: Chat.ChatCommands = {
if (!id || !memo.length) return this.parse(`/help mafia addrole`);
if (alignment && !(alignment in MafiaData.alignments)) return this.errorReply(`${alignment} is not a valid alignment.`);
if (image && !VALID_IMAGES.includes(image)) return this.errorReply(`${image} is not a valid image.`);
if (alignment && !(alignment in MafiaData.alignments)) {
throw new Chat.ErrorMessage(`${alignment} is not a valid alignment.`);
}
if (image && !VALID_IMAGES.includes(image)) throw new Chat.ErrorMessage(`${image} is not a valid image.`);
if (!overwrite && id in MafiaData.roles) {
return this.errorReply(`${name} is already a role. Use /mafia overwriterole to overwrite.`);
throw new Chat.ErrorMessage(`${name} is already a role. Use /mafia overwriterole to overwrite.`);
}
if (id in MafiaData.alignments) return this.errorReply(`${name} is already an alignment.`);
if (id in MafiaData.alignments) throw new Chat.ErrorMessage(`${name} is already an alignment.`);
if (id in MafiaData.aliases) {
return this.errorReply(`${name} is already an alias (pointing to ${MafiaData.aliases[id]}).`);
throw new Chat.ErrorMessage(`${name} is already an alias (pointing to ${MafiaData.aliases[id]}).`);
}
const role: MafiaDataRole = { name, memo };
@ -3836,14 +3842,14 @@ export const commands: Chat.ChatCommands = {
if (!id || !plural || !memo.length) return this.parse(`/help mafia addalignment`);
if (image && !VALID_IMAGES.includes(image)) return this.errorReply(`${image} is not a valid image.`);
if (image && !VALID_IMAGES.includes(image)) throw new Chat.ErrorMessage(`${image} is not a valid image.`);
if (!overwrite && id in MafiaData.alignments) {
return this.errorReply(`${name} is already an alignment. Use /mafia overwritealignment to overwrite.`);
throw new Chat.ErrorMessage(`${name} is already an alignment. Use /mafia overwritealignment to overwrite.`);
}
if (id in MafiaData.roles) return this.errorReply(`${name} is already a role.`);
if (id in MafiaData.roles) throw new Chat.ErrorMessage(`${name} is already a role.`);
if (id in MafiaData.aliases) {
return this.errorReply(`${name} is already an alias (pointing to ${MafiaData.aliases[id]})`);
throw new Chat.ErrorMessage(`${name} is already an alias (pointing to ${MafiaData.aliases[id]})`);
}
const alignment: MafiaDataAlignment = { name, plural, memo };
@ -3872,11 +3878,11 @@ export const commands: Chat.ChatCommands = {
if (!id || !desc || !rolelists.length) return this.parse(`/help mafia addtheme`);
if (!overwrite && id in MafiaData.themes) {
return this.errorReply(`${name} is already a theme. Use /mafia overwritetheme to overwrite.`);
throw new Chat.ErrorMessage(`${name} is already a theme. Use /mafia overwritetheme to overwrite.`);
}
if (id in MafiaData.IDEAs) return this.errorReply(`${name} is already an IDEA.`);
if (id in MafiaData.IDEAs) throw new Chat.ErrorMessage(`${name} is already an IDEA.`);
if (id in MafiaData.aliases) {
return this.errorReply(`${name} is already an alias (pointing to ${MafiaData.aliases[id]})`);
throw new Chat.ErrorMessage(`${name} is already an alias (pointing to ${MafiaData.aliases[id]})`);
}
const rolelistsMap: { [players: number]: string } = {};
@ -3896,7 +3902,7 @@ export const commands: Chat.ChatCommands = {
const parsedRole = Mafia.parseRole(role);
if (parsedRole.problems.length) problems.push(...parsedRole.problems);
}
if (problems.length) return this.errorReply(`Problems found when parsing roles:\n${problems.join('\n')}`);
if (problems.length) throw new Chat.ErrorMessage(`Problems found when parsing roles:\n${problems.join('\n')}`);
const theme: MafiaDataTheme = { name, desc, ...rolelistsMap };
MafiaData.themes[id] = theme;
@ -3922,14 +3928,14 @@ export const commands: Chat.ChatCommands = {
const choices = parseInt(choicesStr);
if (!id || !choices || !picks.length) return this.parse(`/help mafia addidea`);
if (choices <= picks.length) return this.errorReply(`You need to have more choices than picks.`);
if (choices <= picks.length) throw new Chat.ErrorMessage(`You need to have more choices than picks.`);
if (!overwrite && id in MafiaData.IDEAs) {
return this.errorReply(`${name} is already an IDEA. Use /mafia overwriteidea to overwrite.`);
throw new Chat.ErrorMessage(`${name} is already an IDEA. Use /mafia overwriteidea to overwrite.`);
}
if (id in MafiaData.themes) return this.errorReply(`${name} is already a theme.`);
if (id in MafiaData.themes) throw new Chat.ErrorMessage(`${name} is already a theme.`);
if (id in MafiaData.aliases) {
return this.errorReply(`${name} is already an alias (pointing to ${MafiaData.aliases[id]})`);
throw new Chat.ErrorMessage(`${name} is already an alias (pointing to ${MafiaData.aliases[id]})`);
}
const checkedRoles: string[] = [];
@ -3940,7 +3946,7 @@ export const commands: Chat.ChatCommands = {
if (parsedRole.problems.length) problems.push(...parsedRole.problems);
checkedRoles.push(role);
}
if (problems.length) return this.errorReply(`Problems found when parsing roles:\n${problems.join('\n')}`);
if (problems.length) throw new Chat.ErrorMessage(`Problems found when parsing roles:\n${problems.join('\n')}`);
const IDEA: MafiaDataIDEA = { name, choices, picks, roles };
MafiaData.IDEAs[id] = IDEA;
@ -3964,10 +3970,10 @@ export const commands: Chat.ChatCommands = {
if (!id || !memo.length) return this.parse(`/help mafia addterm`);
if (!overwrite && id in MafiaData.terms) {
return this.errorReply(`${name} is already a term. Use /mafia overwriteterm to overwrite.`);
throw new Chat.ErrorMessage(`${name} is already a term. Use /mafia overwriteterm to overwrite.`);
}
if (id in MafiaData.aliases) {
return this.errorReply(`${name} is already an alias (pointing to ${MafiaData.aliases[id]})`);
throw new Chat.ErrorMessage(`${name} is already an alias (pointing to ${MafiaData.aliases[id]})`);
}
const term: MafiaDataTerm = { name, memo };
@ -3987,15 +3993,15 @@ export const commands: Chat.ChatCommands = {
if (!from || !to) return this.parse(`/help mafia addalias`);
if (from in MafiaData.aliases) {
return this.errorReply(`${from} is already an alias (pointing to ${MafiaData.aliases[from]})`);
throw new Chat.ErrorMessage(`${from} is already an alias (pointing to ${MafiaData.aliases[from]})`);
}
let foundTarget = false;
for (const entry of ['alignments', 'roles', 'themes', 'IDEAs', 'terms'] as (keyof MafiaData)[]) {
const dataEntry = MafiaData[entry];
if (from in dataEntry) return this.errorReply(`${from} is already a ${entry.slice(0, -1)}`);
if (from in dataEntry) throw new Chat.ErrorMessage(`${from} is already a ${entry.slice(0, -1)}`);
if (to in dataEntry) foundTarget = true;
}
if (!foundTarget) return this.errorReply(`No database entry exists with the key ${to}.`);
if (!foundTarget) throw new Chat.ErrorMessage(`No database entry exists with the key ${to}.`);
MafiaData.aliases[from] = to;
writeFile(DATA_FILE, MafiaData);
@ -4013,15 +4019,15 @@ export const commands: Chat.ChatCommands = {
let [source, entry] = target.split(',');
entry = toID(entry);
if (!(source in MafiaData)) {
return this.errorReply(`Invalid source. Valid sources are ${Object.keys(MafiaData).join(', ')}`);
throw new Chat.ErrorMessage(`Invalid source. Valid sources are ${Object.keys(MafiaData).join(', ')}`);
}
// @ts-expect-error checked above
const dataSource = MafiaData[source];
if (!(entry in dataSource)) return this.errorReply(`${entry} does not exist in ${source}.`);
if (!(entry in dataSource)) throw new Chat.ErrorMessage(`${entry} does not exist in ${source}.`);
let buf = '';
if (dataSource === MafiaData.alignments) {
if (entry === 'solo' || entry === 'town') return this.errorReply(`You cannot delete the solo or town alignments.`);
if (entry === 'solo' || entry === 'town') throw new Chat.ErrorMessage(`You cannot delete the solo or town alignments.`);
for (const key in MafiaData.roles) {
if (MafiaData.roles[key].alignment === entry) {
@ -4050,7 +4056,7 @@ export const commands: Chat.ChatCommands = {
deletedatahelp: [`/mafia deletedata source,entry - Removes an entry from the database. Requires % @ # ~`],
listdata(target, room, user) {
if (!(target in MafiaData)) {
return this.errorReply(`Invalid source. Valid sources are ${Object.keys(MafiaData).join(', ')}`);
throw new Chat.ErrorMessage(`Invalid source. Valid sources are ${Object.keys(MafiaData).join(', ')}`);
}
const dataSource = MafiaData[target as keyof MafiaData];
if (dataSource === MafiaData.aliases) {
@ -4070,7 +4076,7 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
this.checkCan('gamemanagement', null, room);
if (room.settings.mafiaDisabled) {
return this.errorReply("Mafia is already disabled.");
throw new Chat.ErrorMessage("Mafia is already disabled.");
}
room.settings.mafiaDisabled = true;
room.saveSettings();
@ -4083,7 +4089,7 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
this.checkCan('gamemanagement', null, room);
if (!room.settings.mafiaDisabled) {
return this.errorReply("Mafia is already enabled.");
throw new Chat.ErrorMessage("Mafia is already enabled.");
}
room.settings.mafiaDisabled = false;
room.saveSettings();

View File

@ -286,11 +286,11 @@ export const commands: Chat.ChatCommands = {
break;
case 'lines': case 'maxlines':
lines = parseInt(value);
if (isNaN(lines) || lines < 1) return this.errorReply(`Invalid linecount: '${value}'.`);
if (isNaN(lines) || lines < 1) throw new Chat.ErrorMessage(`Invalid linecount: '${value}'.`);
break;
default:
this.errorReply(`Invalid modlog parameter: '${param}'.`);
return this.errorReply(`Please specify 'room', 'note', 'user', 'ip', 'action', 'staff', or 'lines'.`);
throw new Chat.ErrorMessage([`Invalid modlog parameter: '${param}'.`,
`Please specify 'room', 'note', 'user', 'ip', 'action', 'staff', or 'lines'.`]);
}
}
@ -303,7 +303,7 @@ export const commands: Chat.ChatCommands = {
// default to global modlog for staff convenience
roomid = 'global';
} else {
return this.errorReply(`Only global staff may view battle and groupchat modlogs.`);
throw new Chat.ErrorMessage(`Only global staff may view battle and groupchat modlogs.`);
}
}
@ -364,7 +364,7 @@ export const pages: Chat.PageTable = {
this.checkCan('lock');
const target = toID(query.shift());
if (!target || target.length > 18) {
return this.errorReply(`Invalid userid - must be between 1 and 18 characters long.`);
throw new Chat.ErrorMessage(`Invalid userid - must be between 1 and 18 characters long.`);
}
this.title = `[Modlog Stats] ${target}`;
this.setHTML(`<div class="pad"><strong>Running modlog search...</strong></div>`);
@ -375,7 +375,7 @@ export const pages: Chat.PageTable = {
}], note: [], ip: [], action: [], actionTaker: [],
}, 1000);
if (!entries?.results.length) {
return this.errorReply(`No data found.`);
throw new Chat.ErrorMessage(`No data found.`);
}
const punishmentTable = new Utils.Multiset<string>();
const punishmentsByIp = new Map<string, Utils.Multiset<string>>();

View File

@ -513,7 +513,7 @@ export const commands: Chat.ChatCommands = {
if (!targetId) targetId = user.id;
const mon = Dex.species.get(monName);
if (!mon.exists) {
return this.errorReply(`Species ${monName} does not exist.`);
throw new Chat.ErrorMessage(`Species ${monName} does not exist.`);
}
Nominations.icons[targetId] = mon.name.toLowerCase();
Nominations.save();
@ -526,7 +526,7 @@ export const commands: Chat.ChatCommands = {
this.checkCan('rangeban');
const targetID = toID(target);
if (!Nominations.icons[targetID]) {
return this.errorReply(`${targetID} does not have an icon set.`);
throw new Chat.ErrorMessage(`${targetID} does not have an icon set.`);
}
delete Nominations.icons[targetID];
Nominations.save();
@ -550,9 +550,9 @@ export const pages: Chat.PageTable = {
view(query, user) {
this.checkCan('rangeban');
const id = toID(query.shift());
if (!id) return this.errorReply(`Invalid userid.`);
if (!id) throw new Chat.ErrorMessage(`Invalid userid.`);
const nom = Nominations.find(id);
if (!nom) return this.errorReply(`No nomination found for '${id}'.`);
if (!nom) throw new Chat.ErrorMessage(`No nomination found for '${id}'.`);
this.title = `[Perma Nom] ${nom.primaryID}`;
return Nominations.displayActionPage(nom);
},

View File

@ -377,11 +377,11 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
if (!target) return this.parse('/help poll new');
target = target.trim();
if (target.length > 1024) return this.errorReply(this.tr`Poll too long.`);
if (room.battle) return this.errorReply(this.tr`Battles do not support polls.`);
if (target.length > 1024) throw new Chat.ErrorMessage(this.tr`Poll too long.`);
if (room.battle) throw new Chat.ErrorMessage(this.tr`Battles do not support polls.`);
const text = this.filter(target);
if (target !== text) return this.errorReply(this.tr`You are not allowed to use filtered words in polls.`);
if (target !== text) throw new Chat.ErrorMessage(this.tr`You are not allowed to use filtered words in polls.`);
const supportHTML = cmd.includes('html');
const multiPoll = cmd.includes('multi');
@ -396,7 +396,7 @@ export const commands: Chat.ChatCommands = {
} else if (text.includes(',')) {
separator = ',';
} else {
return this.errorReply(this.tr`Not enough arguments for /poll new.`);
throw new Chat.ErrorMessage(this.tr`Not enough arguments for /poll new.`);
}
let currentParam = "";
@ -412,7 +412,7 @@ export const commands: Chat.ChatCommands = {
currentParam += nextCharacter;
i += 1;
} else {
return this.errorReply(this.tr`Extra escape character. To end a poll with '\\', enter it as '\\\\'`);
throw new Chat.ErrorMessage(this.tr`Extra escape character. To end a poll with '\\', enter it as '\\\\'`);
}
continue;
}
@ -436,21 +436,21 @@ export const commands: Chat.ChatCommands = {
if (supportHTML) this.checkCan('declare', null, room);
this.checkChat();
if (room.minorActivity && !queue) {
return this.errorReply(this.tr`There is already a poll or announcement in progress in this room.`);
throw new Chat.ErrorMessage(this.tr`There is already a poll or announcement in progress in this room.`);
}
if (params.length < 3) return this.errorReply(this.tr`Not enough arguments for /poll new.`);
if (params.length < 3) throw new Chat.ErrorMessage(this.tr`Not enough arguments for /poll new.`);
// the function throws on failure, so no handling needs to be done anymore
if (supportHTML) params = params.map(parameter => this.checkHTML(parameter));
const questions = params.splice(1);
if (questions.length > MAX_QUESTIONS) {
return this.errorReply(this.tr`Too many options for poll (maximum is ${MAX_QUESTIONS}).`);
throw new Chat.ErrorMessage(this.tr`Too many options for poll (maximum is ${MAX_QUESTIONS}).`);
}
if (new Set(questions).size !== questions.length) {
return this.errorReply(this.tr`There are duplicate options in the poll.`);
throw new Chat.ErrorMessage(this.tr`There are duplicate options in the poll.`);
}
if (room.minorActivity) {
@ -489,13 +489,13 @@ export const commands: Chat.ChatCommands = {
this.checkCan('mute', null, room);
const queue = room.getMinorActivityQueue();
if (!queue) {
return this.errorReply(this.tr`The queue is already empty.`);
throw new Chat.ErrorMessage(this.tr`The queue is already empty.`);
}
const slot = parseInt(target);
if (isNaN(slot)) {
return this.errorReply(this.tr`Can't delete poll at slot ${target} - "${target}" is not a number.`);
throw new Chat.ErrorMessage(this.tr`Can't delete poll at slot ${target} - "${target}" is not a number.`);
}
if (!queue[slot - 1]) return this.errorReply(this.tr`There is no poll in queue at slot ${slot}.`);
if (!queue[slot - 1]) throw new Chat.ErrorMessage(this.tr`There is no poll in queue at slot ${slot}.`);
room.clearMinorActivityQueue(slot - 1);
@ -516,7 +516,7 @@ export const commands: Chat.ChatCommands = {
this.checkCan('mute', null, room);
const queue = room.getMinorActivityQueue();
if (!queue) {
return this.errorReply(this.tr`The queue is already empty.`);
throw new Chat.ErrorMessage(this.tr`The queue is already empty.`);
}
room.clearMinorActivityQueue();
this.modlog('CLEARQUEUE');
@ -534,7 +534,7 @@ export const commands: Chat.ChatCommands = {
if (!target) return this.parse('/help poll vote');
const parsed = parseInt(target);
if (isNaN(parsed)) return this.errorReply(this.tr`To vote, specify the number of the option.`);
if (isNaN(parsed)) throw new Chat.ErrorMessage(this.tr`To vote, specify the number of the option.`);
if (!poll.answers.has(parsed)) return this.sendReply(this.tr`Option not in poll.`);
@ -564,12 +564,12 @@ export const commands: Chat.ChatCommands = {
if (target) {
this.checkCan('minigame', null, room);
if (target === 'clear') {
if (!poll.endTimer()) return this.errorReply(this.tr("There is no timer to clear."));
if (!poll.endTimer()) throw new Chat.ErrorMessage(this.tr("There is no timer to clear."));
return this.add(this.tr`The poll timer was turned off.`);
}
const timeoutMins = parseFloat(target);
if (isNaN(timeoutMins) || timeoutMins <= 0 || timeoutMins > 7 * 24 * 60) {
return this.errorReply(this.tr`Time should be a number of minutes less than one week.`);
throw new Chat.ErrorMessage(this.tr`Time should be a number of minutes less than one week.`);
}
poll.setTimer({ timeoutMins });
room.add(this.tr`The poll timer was turned on: the poll will end in ${Chat.toDurationString(timeoutMins * MINUTES)}.`);
@ -638,10 +638,10 @@ export const commands: Chat.ChatCommands = {
num = 0;
}
if (isNaN(num)) {
return this.errorReply(`Invalid max vote cap: '${target}'`);
throw new Chat.ErrorMessage(`Invalid max vote cap: '${target}'`);
}
if (poll.maxVotes === num) {
return this.errorReply(`The poll's vote cap is already set to ${num}.`);
throw new Chat.ErrorMessage(`The poll's vote cap is already set to ${num}.`);
}
poll.maxVotes = num;
this.addModAction(`${user.name} set the poll's vote cap to ${num}.`);

View File

@ -33,7 +33,7 @@ export const commands: Chat.ChatCommands = {
randquote(target, room, user) {
room = this.requireRoom();
const roomQuotes = quotes[room.roomid];
if (!roomQuotes?.length) return this.errorReply(`This room has no quotes.`);
if (!roomQuotes?.length) throw new Chat.ErrorMessage(`This room has no quotes.`);
this.runBroadcast(true);
const { quote, date, userid } = roomQuotes[Math.floor(Math.random() * roomQuotes.length)];
const time = Chat.toTimestamp(new Date(date), { human: true });
@ -45,7 +45,7 @@ export const commands: Chat.ChatCommands = {
addquote(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.");
}
target = target.trim();
this.checkCan('mute', null, room);
@ -56,16 +56,16 @@ export const commands: Chat.ChatCommands = {
const roomQuotes = quotes[room.roomid];
if (this.filter(target) !== target) {
return this.errorReply(`Invalid quote.`);
throw new Chat.ErrorMessage(`Invalid quote.`);
}
if (roomQuotes.filter(item => item.quote === target).length) {
return this.errorReply(`"${target}" is already quoted in this room.`);
throw new Chat.ErrorMessage(`"${target}" is already quoted in this room.`);
}
if (target.length > 8192) {
return this.errorReply(`Your quote cannot exceed 8192 characters.`);
throw new Chat.ErrorMessage(`Your quote cannot exceed 8192 characters.`);
}
if (room.settings.isPrivate && roomQuotes.length >= MAX_QUOTES) {
return this.errorReply(`This room already has ${MAX_QUOTES} quotes, which is the maximum for private rooms.`);
throw new Chat.ErrorMessage(`This room already has ${MAX_QUOTES} quotes, which is the maximum for private rooms.`);
}
roomQuotes.push({ userid: user.id, quote: target, date: Date.now() });
saveQuotes();
@ -79,14 +79,14 @@ export const commands: Chat.ChatCommands = {
removequote(target, room, user) {
room = this.requireRoom();
this.checkCan('mute', null, room);
if (!quotes[room.roomid]?.length) return this.errorReply(`This room has no quotes.`);
if (!quotes[room.roomid]?.length) throw new Chat.ErrorMessage(`This room has no quotes.`);
const roomQuotes = quotes[room.roomid];
const index = toID(target) === 'last' ? roomQuotes.length - 1 : parseInt(toID(target)) - 1;
if (isNaN(index)) {
return this.errorReply(`Invalid index.`);
throw new Chat.ErrorMessage(`Invalid index.`);
}
if (!roomQuotes[index]) {
return this.errorReply(`Quote not found.`);
throw new Chat.ErrorMessage(`Quote not found.`);
}
const [removed] = roomQuotes.splice(index, 1);
const collapsedQuote = removed.quote.replace(/\n/g, ' ');
@ -100,14 +100,14 @@ export const commands: Chat.ChatCommands = {
viewquote(target, room, user) {
room = this.requireRoom();
const roomQuotes = quotes[room.roomid];
if (!roomQuotes?.length) return this.errorReply(`This room has no quotes.`);
if (!roomQuotes?.length) throw new Chat.ErrorMessage(`This room has no quotes.`);
const [num, showAuthor] = Utils.splitFirst(target, ',');
const index = num === 'last' ? roomQuotes.length - 1 : parseInt(num) - 1;
if (isNaN(index)) {
return this.errorReply(`Invalid index.`);
throw new Chat.ErrorMessage(`Invalid index.`);
}
if (!roomQuotes[index]) {
return this.errorReply(`Quote not found.`);
throw new Chat.ErrorMessage(`Quote not found.`);
}
this.runBroadcast(true);
const { quote, date, userid } = roomQuotes[index];
@ -123,7 +123,7 @@ export const commands: Chat.ChatCommands = {
viewquotes: 'quotes',
quotes(target, room) {
const targetRoom = target ? Rooms.search(target) : room;
if (!targetRoom) return this.errorReply(`Invalid room.`);
if (!targetRoom) throw new Chat.ErrorMessage(`Invalid room.`);
this.parse(`/join view-quotes-${targetRoom.roomid}`);
},
quoteshelp: [`/quotes [room] - Shows all quotes for [room]. Defaults the room the command is used in.`],
@ -147,7 +147,7 @@ export const pages: Chat.PageTable = {
this.title = `[Quotes]`;
// allow it for users if they can access the room
if (!room.checkModjoin(user)) {
return this.errorReply(`Access denied.`);
throw new Chat.ErrorMessage(`Access denied.`);
}
let buffer = `<div class="pad">`;
buffer += `<button style="float:right;" class="button" name="send" value="/join view-quotes-${room.roomid}"><i class="fa fa-refresh"></i> Refresh</button>`;

View File

@ -493,8 +493,7 @@ export const commands: Chat.ChatCommands = {
const searchResults = dex.dataSearch(args[0], ['Pokedex']);
if (!searchResults?.length) {
this.errorReply(`No Pok\u00e9mon named '${args[0]}' was found${Dex.gen > dex.gen ? ` in Gen ${dex.gen}` : ""}. (Check your spelling?)`);
return;
throw new Chat.ErrorMessage(`No Pok\u00e9mon named '${args[0]}' was found${Dex.gen > dex.gen ? ` in Gen ${dex.gen}` : ""}. (Check your spelling?)`);
}
let inexactMsg = '';
@ -515,7 +514,7 @@ export const commands: Chat.ChatCommands = {
const rbyMoves = getRBYMoves(species);
if (!rbyMoves) {
this.sendReply(inexactMsg);
return this.errorReply(`Error: ${species.name} has no Random Battle data in ${GEN_NAMES[toID(args[1])]}`);
throw new Chat.ErrorMessage(`Error: ${species.name} has no Random Battle data in ${GEN_NAMES[toID(args[1])]}`);
}
movesets.push(`<span style="color:#999999;">Moves for ${species.name} in ${format.name}:</span>${rbyMoves}`);
setCount = 1;
@ -523,7 +522,7 @@ export const commands: Chat.ChatCommands = {
const lgpeMoves = getLetsGoMoves(species);
if (!lgpeMoves) {
this.sendReply(inexactMsg);
return this.errorReply(`Error: ${species.name} has no Random Battle data in [Gen 7 Let's Go]`);
throw new Chat.ErrorMessage(`Error: ${species.name} has no Random Battle data in [Gen 7 Let's Go]`);
}
movesets.push(`<span style="color:#999999;">Moves for ${species.name} in ${format.name}:</span><br />${lgpeMoves}`);
setCount = 1;
@ -582,7 +581,7 @@ export const commands: Chat.ChatCommands = {
if (!movesets.length) {
this.sendReply(inexactMsg);
return this.errorReply(`Error: ${species.name} has no Random Battle data in ${format.name}`);
throw new Chat.ErrorMessage(`Error: ${species.name} has no Random Battle data in ${format.name}`);
}
let buf = movesets.join('<hr/>');
if (setCount <= 2) {
@ -605,7 +604,7 @@ export const commands: Chat.ChatCommands = {
if (!args[0]) return this.parse(`/help battlefactory`);
const species = Dex.species.get(args[0]);
if (!species.exists) {
return this.errorReply(`Error: Pok\u00e9mon '${args[0].trim()}' not found.`);
throw new Chat.ErrorMessage(`Error: Pok\u00e9mon '${args[0].trim()}' not found.`);
}
let mod = 'gen9';
if (args[1] && toID(args[1]) in Dex.dexes && Dex.dexes[toID(args[1])].gen >= 7) mod = toID(args[1]);
@ -617,7 +616,7 @@ export const commands: Chat.ChatCommands = {
if (!args[0]) return this.parse(`/help battlefactory`);
const species = Dex.species.get(args[0]);
if (!species.exists) {
return this.errorReply(`Error: Pok\u00e9mon '${args[0].trim()}' not found.`);
throw new Chat.ErrorMessage(`Error: Pok\u00e9mon '${args[0].trim()}' not found.`);
}
let tier = '';
if (args[1] && toID(args[1]) in TIERS) {
@ -654,7 +653,7 @@ export const commands: Chat.ChatCommands = {
if (!this.runBroadcast()) return;
if (!target) return this.parse(`/help cap1v1`);
const species = Dex.species.get(target);
if (!species.exists) return this.errorReply(`Error: Pok\u00e9mon '${target.trim()}' not found.`);
if (!species.exists) throw new Chat.ErrorMessage(`Error: Pok\u00e9mon '${target.trim()}' not found.`);
const cap1v1Set = CAP1v1Sets(species);
if (!cap1v1Set) return this.parse(`/help cap1v1`);
if (typeof cap1v1Set !== 'string') {

View File

@ -216,11 +216,11 @@ export const commands: Chat.ChatCommands = {
async removewinrates(target, room, user) {
this.checkCan('rangeban');
if (!/^[0-9]{4}-[0-9]{2}$/.test(target) || target === getMonth()) {
return this.errorReply(`Invalid month: ${target}`);
throw new Chat.ErrorMessage(`Invalid month: ${target}`);
}
const path = STATS_PATH.replace('{{MON}}', target);
if (!(await FS(path).exists())) {
return this.errorReply(`No stats for the month ${target}.`);
throw new Chat.ErrorMessage(`No stats for the month ${target}.`);
}
await FS(path).unlinkIfExists();
this.globalModlog('REMOVEWINRATES', null, target);
@ -233,22 +233,22 @@ export const pages: Chat.PageTable = {
if (!user.named) return Rooms.RETRY_AFTER_LOGIN;
query = query.join('-').split('--');
const format = toID(query.shift());
if (!format) return this.errorReply(`Specify a format to view winrates for.`);
if (!format) throw new Chat.ErrorMessage(`Specify a format to view winrates for.`);
if (!stats.formats[format]) {
return this.errorReply(`That format does not have winrates tracked.`);
throw new Chat.ErrorMessage(`That format does not have winrates tracked.`);
}
checkRollover();
const sorter = toID(query.shift() || 'zscore');
if (!['zscore', 'raw'].includes(sorter)) {
return this.errorReply(`Invalid sorting method. Must be either 'zscore' or 'raw'.`);
throw new Chat.ErrorMessage(`Invalid sorting method. Must be either 'zscore' or 'raw'.`);
}
const month = query.shift() || getMonth();
if (!/^[0-9]{4}-[0-9]{2}$/.test(month)) {
return this.errorReply(`Invalid month: ${month}`);
throw new Chat.ErrorMessage(`Invalid month: ${month}`);
}
const isOldMonth = month !== getMonth();
if (isOldMonth && !(await FS(STATS_PATH.replace('{{MONTH}}', month)).exists())) {
return this.errorReply(`There are no winrates for that month.`);
throw new Chat.ErrorMessage(`There are no winrates for that month.`);
}
const formatTitle = Dex.formats.get(format).name;
let buf = `<div class="pad"><h2>Winrates for ${formatTitle} (${month})</h2>`;

View File

@ -160,7 +160,7 @@ export const commands: Chat.ChatCommands = {
const isHTML = cmd.includes('html');
const isByMessages = cmd.includes('bymessages');
room = this.requireRoom();
if (room.settings.isPersonal) return this.errorReply(`Personal rooms do not support repeated messages.`);
if (room.settings.isPersonal) throw new Chat.ErrorMessage(`Personal rooms do not support repeated messages.`);
this.checkCan(isHTML ? 'addhtml' : 'mute', null, room);
const [intervalString, name, ...messageArray] = target.split(',');
const id = toID(name);
@ -211,7 +211,7 @@ export const commands: Chat.ChatCommands = {
repeatfaq(target, room, user, connection, cmd) {
room = this.requireRoom();
this.checkCan('mute', null, room);
if (room.settings.isPersonal) return this.errorReply(`Personal rooms do not support repeated messages.`);
if (room.settings.isPersonal) throw new Chat.ErrorMessage(`Personal rooms do not support repeated messages.`);
const isByMessages = cmd.includes('bymessages');
let [intervalString, topic] = target.split(',');
@ -257,11 +257,11 @@ export const commands: Chat.ChatCommands = {
}
this.checkCan('mute', null, room);
if (!room.settings.repeats?.length) {
return this.errorReply(this.tr`There are no repeated phrases in this room.`);
throw new Chat.ErrorMessage(this.tr`There are no repeated phrases in this room.`);
}
if (!Repeats.hasRepeat(room, id)) {
return this.errorReply(this.tr`The phrase labeled with "${id}" is not being repeated in this room.`);
throw new Chat.ErrorMessage(this.tr`The phrase labeled with "${id}" is not being repeated in this room.`);
}
Repeats.removeRepeat(room, id);
@ -275,7 +275,7 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
this.checkCan('declare', null, room);
if (!room.settings.repeats?.length) {
return this.errorReply(this.tr`There are no repeated phrases in this room.`);
throw new Chat.ErrorMessage(this.tr`There are no repeated phrases in this room.`);
}
for (const { id } of room.settings.repeats) {
@ -289,7 +289,7 @@ export const commands: Chat.ChatCommands = {
repeats: 'viewrepeats',
viewrepeats(target, room, user) {
const roomid = toID(target) || room?.roomid;
if (!roomid) return this.errorReply(this.tr`You must specify a room when using this command in PMs.`);
if (!roomid) throw new Chat.ErrorMessage(this.tr`You must specify a room when using this command in PMs.`);
this.parse(`/j view-repeats-${roomid}`);
},
};

View File

@ -302,7 +302,7 @@ export const commands: Chat.ChatCommands = {
question(target, room, user) {
room = this.requireRoom();
const responder = room.responder;
if (!responder) return this.errorReply(`This room does not have an autoresponder configured.`);
if (!responder) throw new Chat.ErrorMessage(`This room does not have an autoresponder configured.`);
if (!target) return this.parse("/help question");
const reply = responder.visualize(target, true);
if (!reply) return this.sendReplyBox(`No answer found.`);
@ -317,7 +317,7 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
const responder = room.responder;
if (!responder) {
return this.errorReply(`This room has not configured an autoresponder.`);
throw new Chat.ErrorMessage(`This room has not configured an autoresponder.`);
}
if (!target) {
return this.parse('/help autoresponder');
@ -337,15 +337,15 @@ export const commands: Chat.ChatCommands = {
}
this.checkCan('ban', null, room);
if (room.settings.isPrivate === true) {
return this.errorReply(`Secret rooms cannot enable an autoresponder.`);
throw new Chat.ErrorMessage(`Secret rooms cannot enable an autoresponder.`);
}
if (this.meansYes(target)) {
if (room.responder) return this.errorReply(`The Autoresponder for this room is already enabled.`);
if (room.responder) throw new Chat.ErrorMessage(`The Autoresponder for this room is already enabled.`);
room.responder = new AutoResponder(room, answererData[room.roomid]);
room.responder.writeState();
}
if (this.meansNo(target)) {
if (!room.responder) return this.errorReply(`The Autoresponder for this room is already disabled.`);
if (!room.responder) throw new Chat.ErrorMessage(`The Autoresponder for this room is already disabled.`);
room.responder.destroy();
}
this.privateModAction(`${user.name} ${!room.responder ? 'disabled' : 'enabled'} the auto-response filter.`);
@ -355,11 +355,11 @@ export const commands: Chat.ChatCommands = {
add(target, room, user, connection, cmd) {
room = this.requireRoom();
if (!room.responder) {
return this.errorReply(`This room has not configured an auto-response filter.`);
throw new Chat.ErrorMessage(`This room has not configured an auto-response filter.`);
}
const force = cmd === 'forceadd';
if (force && !AutoResponder.canOverride(user, room)) {
return this.errorReply(`You cannot use raw regex - use /autoresponder add instead.`);
throw new Chat.ErrorMessage(`You cannot use raw regex - use /autoresponder add instead.`);
}
this.checkCan('ban', null, room);
room.responder.tryAddRegex(target, force);
@ -370,11 +370,11 @@ export const commands: Chat.ChatCommands = {
const [faq, index] = target.split(',');
room = this.requireRoom();
if (!room.responder) {
return this.errorReply(`${room.title} has not configured an auto-response filter.`);
throw new Chat.ErrorMessage(`${room.title} has not configured an auto-response filter.`);
}
this.checkCan('ban', null, room);
const num = parseInt(index);
if (isNaN(num)) return this.errorReply("Invalid index.");
if (isNaN(num)) throw new Chat.ErrorMessage("Invalid index.");
room.responder.tryRemoveRegex(faq, num - 1);
this.privateModAction(`${user.name} removed regex ${num} from the usable regexes for ${faq}.`);
this.modlog('AUTOFILTER REMOVE', null, `removed regex ${index} for FAQ ${faq}`);
@ -386,7 +386,7 @@ export const commands: Chat.ChatCommands = {
ignore(target, room, user) {
room = this.requireRoom();
if (!room.responder) {
return this.errorReply(`This room has not configured an auto-response filter.`);
throw new Chat.ErrorMessage(`This room has not configured an auto-response filter.`);
}
this.checkCan('ban', null, room);
if (!toID(target)) {
@ -402,7 +402,7 @@ export const commands: Chat.ChatCommands = {
unignore(target, room, user) {
room = this.requireRoom();
if (!room.responder) {
return this.errorReply(`${room.title} has not configured an auto-response filter.`);
throw new Chat.ErrorMessage(`${room.title} has not configured an auto-response filter.`);
}
this.checkCan('ban', null, room);
if (!toID(target)) {
@ -434,7 +434,7 @@ export const pages: Chat.PageTable = {
async autoresponder(args, user) {
const room = this.requireRoom();
if (!room.responder) {
return this.errorReply(`${room.title} does not have a configured autoresponder.`);
throw new Chat.ErrorMessage(`${room.title} does not have a configured autoresponder.`);
}
args.shift();
const roomData = answererData[room.roomid];
@ -508,7 +508,7 @@ export const pages: Chat.PageTable = {
this.title = `[${room.title} Autoresponder ignore list]`;
buf = `<div class="pad"><h2>${room.title} responder terms to ignore:</h2>${back}${refresh('ignore')}<hr />`;
if (!roomData.ignore) {
return this.errorReply(`No terms on ignore list.`);
throw new Chat.ErrorMessage(`No terms on ignore list.`);
}
for (const term of roomData.ignore) {
buf += `- ${term} <button class="button" name="send"value="/msgroom ${room.roomid},/ar unignore ${term}">Remove</button><br />`;

View File

@ -104,9 +104,9 @@ export const commands: Chat.ChatCommands = {
roomevents: {
''(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
if (!room.settings.events || !Object.keys(room.settings.events).length) {
return this.errorReply("There are currently no planned upcoming events for this room.");
throw new Chat.ErrorMessage("There are currently no planned upcoming events for this room.");
}
if (!this.runBroadcast()) return;
convertAliasFormat(room);
@ -131,7 +131,7 @@ export const commands: Chat.ChatCommands = {
edit: 'add',
add(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
this.checkCan('ban', null, room);
if (!room.settings.events) room.settings.events = Object.create(null);
convertAliasFormat(room);
@ -139,21 +139,21 @@ export const commands: Chat.ChatCommands = {
const [eventName, date, ...desc] = target.split(target.includes('|') ? '|' : ',');
if (!(eventName && date && desc)) {
return this.errorReply("You're missing a command parameter - to see this command's syntax, use /help roomevents.");
throw new Chat.ErrorMessage("You're missing a command parameter - to see this command's syntax, use /help roomevents.");
}
const dateActual = date.trim();
const descString = desc.join(target.includes('|') ? '|' : ',').trim();
if (eventName.trim().length > 50) return this.errorReply("Event names should not exceed 50 characters.");
if (dateActual.length > 150) return this.errorReply("Event dates should not exceed 150 characters.");
if (descString.length > 1000) return this.errorReply("Event descriptions should not exceed 1000 characters.");
if (eventName.trim().length > 50) throw new Chat.ErrorMessage("Event names should not exceed 50 characters.");
if (dateActual.length > 150) throw new Chat.ErrorMessage("Event dates should not exceed 150 characters.");
if (descString.length > 1000) throw new Chat.ErrorMessage("Event descriptions should not exceed 1000 characters.");
const eventId = getEventID(eventName, room);
if (!eventId) return this.errorReply("Event names must contain at least one alphanumerical character.");
if (!eventId) throw new Chat.ErrorMessage("Event names must contain at least one alphanumerical character.");
const oldEvent = room.settings.events?.[eventId] as RoomEvent;
if (oldEvent && 'events' in oldEvent) return this.errorReply(`"${eventId}" is already the name of a category.`);
if (oldEvent && 'events' in oldEvent) throw new Chat.ErrorMessage(`"${eventId}" is already the name of a category.`);
const eventNameActual = (oldEvent ? oldEvent.eventName : eventName.trim());
this.privateModAction(`${user.name} ${oldEvent ? "edited the" : "added a"} roomevent titled "${eventNameActual}".`);
@ -169,24 +169,24 @@ export const commands: Chat.ChatCommands = {
rename(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
this.checkCan('ban', null, room);
let [oldName, newName] = target.split(target.includes('|') ? '|' : ',');
if (!(oldName && newName)) return this.errorReply("Usage: /roomevents rename [old name], [new name]");
if (!(oldName && newName)) throw new Chat.ErrorMessage("Usage: /roomevents rename [old name], [new name]");
convertAliasFormat(room);
newName = newName.trim();
const newID = toID(newName);
const oldID = (getAliases(room).includes(toID(oldName)) ? getEventID(oldName, room) : toID(oldName));
if (newID === oldID) return this.errorReply("The new name must be different from the old one.");
if (!newID) return this.errorReply("Event names must contain at least one alphanumeric character.");
if (newName.length > 50) return this.errorReply("Event names should not exceed 50 characters.");
if (newID === oldID) throw new Chat.ErrorMessage("The new name must be different from the old one.");
if (!newID) throw new Chat.ErrorMessage("Event names must contain at least one alphanumeric character.");
if (newName.length > 50) throw new Chat.ErrorMessage("Event names should not exceed 50 characters.");
const events = room.settings.events!;
const eventData = events?.[oldID];
if (!(eventData && 'eventName' in eventData)) return this.errorReply(`There is no event titled "${oldName}".`);
if (!(eventData && 'eventName' in eventData)) throw new Chat.ErrorMessage(`There is no event titled "${oldName}".`);
if (events?.[newID]) {
return this.errorReply(`"${newName}" is already an event, alias, or category.`);
throw new Chat.ErrorMessage(`"${newName}" is already an event, alias, or category.`);
}
const originalName = eventData.eventName;
@ -202,19 +202,21 @@ export const commands: Chat.ChatCommands = {
begin: 'start',
start(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
this.checkCan('ban', null, room);
if (!room.settings.events || !Object.keys(room.settings.events).length) {
return this.errorReply("There are currently no planned upcoming events for this room to start.");
throw new Chat.ErrorMessage("There are currently no planned upcoming events for this room to start.");
}
if (!target) return this.errorReply("Usage: /roomevents start [event name]");
if (!target) throw new Chat.ErrorMessage("Usage: /roomevents start [event name]");
convertAliasFormat(room);
target = toID(target);
const event = room.settings.events[getEventID(target, room)];
if (!(event && 'eventName' in event)) return this.errorReply(`There is no event titled '${target}'. Check spelling?`);
if (!(event && 'eventName' in event)) {
throw new Chat.ErrorMessage(`There is no event titled '${target}'. Check spelling?`);
}
if (event.started) {
return this.errorReply(`The event ${event.eventName} has already started.`);
throw new Chat.ErrorMessage(`The event ${event.eventName} has already started.`);
}
for (const u in room.users) {
const activeUser = Users.get(u);
@ -237,17 +239,17 @@ export const commands: Chat.ChatCommands = {
delete: 'remove',
remove(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
this.checkCan('ban', null, room);
if (!room.settings.events || Object.keys(room.settings.events).length === 0) {
return this.errorReply("There are currently no planned upcoming events for this room to remove.");
throw new Chat.ErrorMessage("There are currently no planned upcoming events for this room to remove.");
}
if (!target) return this.errorReply("Usage: /roomevents remove [event name]");
if (!target) throw new Chat.ErrorMessage("Usage: /roomevents remove [event name]");
const eventID = toID(target);
convertAliasFormat(room);
if (getAliases(room).includes(eventID)) return this.errorReply("To delete aliases, use /roomevents removealias.");
if (getAliases(room).includes(eventID)) throw new Chat.ErrorMessage("To delete aliases, use /roomevents removealias.");
if (!(room.settings.events[eventID] && 'eventName' in room.settings.events[eventID])) {
return this.errorReply(`There is no event titled '${target}'. Check spelling?`);
throw new Chat.ErrorMessage(`There is no event titled '${target}'. Check spelling?`);
}
delete room.settings.events[eventID];
@ -265,12 +267,12 @@ export const commands: Chat.ChatCommands = {
view(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
if (!room.settings.events || !Object.keys(room.settings.events).length) {
return this.errorReply("There are currently no planned upcoming events for this room.");
throw new Chat.ErrorMessage("There are currently no planned upcoming events for this room.");
}
if (!target) return this.errorReply("Usage: /roomevents view [event name, alias, or category]");
if (!target) throw new Chat.ErrorMessage("Usage: /roomevents view [event name, alias, or category]");
convertAliasFormat(room);
target = getEventID(target, room);
@ -288,7 +290,7 @@ export const commands: Chat.ChatCommands = {
} else if (room.settings.events[target] && 'eventName' in room.settings.events[target]) {
events.push(room.settings.events[target] as RoomEvent);
} else {
return this.errorReply(`There is no event or category titled '${target}'. Check spelling?`);
throw new Chat.ErrorMessage(`There is no event or category titled '${target}'. Check spelling?`);
}
if (!this.runBroadcast()) return;
let hasAliases = false;
@ -325,19 +327,19 @@ export const commands: Chat.ChatCommands = {
alias: 'addalias',
addalias(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
this.checkCan('ban', null, room);
const [alias, eventId] = target.split(target.includes('|') ? '|' : ',').map(argument => toID(argument));
if (!(alias && eventId)) {
return this.errorReply("Usage: /roomevents addalias [alias], [event name]. Aliases must contain at least one alphanumeric character.");
throw new Chat.ErrorMessage("Usage: /roomevents addalias [alias], [event name]. Aliases must contain at least one alphanumeric character.");
}
if (!room.settings.events || Object.keys(room.settings.events).length === 0) {
return this.errorReply(`There are currently no scheduled events.`);
throw new Chat.ErrorMessage(`There are currently no scheduled events.`);
}
convertAliasFormat(room);
const event = room.settings.events[eventId];
if (!(event && 'eventName' in event)) return this.errorReply(`There is no event titled "${eventId}".`);
if (room.settings.events[alias]) return this.errorReply(`"${alias}" is already an event, alias, or category.`);
if (!(event && 'eventName' in event)) throw new Chat.ErrorMessage(`There is no event titled "${eventId}".`);
if (room.settings.events[alias]) throw new Chat.ErrorMessage(`"${alias}" is already an event, alias, or category.`);
room.settings.events[alias] = { eventID: eventId };
this.privateModAction(`${user.name} added an alias "${alias}" for the roomevent "${eventId}".`);
@ -348,16 +350,16 @@ export const commands: Chat.ChatCommands = {
deletealias: 'removealias',
removealias(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
this.checkCan('ban', null, room);
target = toID(target);
if (!target) return this.errorReply("Usage: /roomevents removealias <alias>");
if (!target) throw new Chat.ErrorMessage("Usage: /roomevents removealias <alias>");
if (!room.settings.events || Object.keys(room.settings.events).length === 0) {
return this.errorReply(`There are currently no scheduled events.`);
throw new Chat.ErrorMessage(`There are currently no scheduled events.`);
}
convertAliasFormat(room);
if (!(room.settings.events[target] && 'eventID' in room.settings.events[target])) {
return this.errorReply(`${target} isn't an alias.`);
throw new Chat.ErrorMessage(`${target} isn't an alias.`);
}
delete room.settings.events[target];
@ -368,25 +370,25 @@ export const commands: Chat.ChatCommands = {
addtocategory(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
this.checkCan('ban', null, room);
const [eventId, categoryId] = target.split(target.includes('|') ? '|' : ',').map(argument => toID(argument));
if (!(eventId && categoryId)) return this.errorReply("Usage: /roomevents addtocategory [event name], [category].");
if (!(eventId && categoryId)) throw new Chat.ErrorMessage("Usage: /roomevents addtocategory [event name], [category].");
if (!room.settings.events || Object.keys(room.settings.events).length === 0) {
return this.errorReply(`There are currently no scheduled events.`);
throw new Chat.ErrorMessage(`There are currently no scheduled events.`);
}
convertAliasFormat(room);
const event = room.settings.events[getEventID(eventId, room)];
if (!(event && 'eventName' in event)) return this.errorReply(`There is no event or alias titled "${eventId}".`);
if (!(event && 'eventName' in event)) throw new Chat.ErrorMessage(`There is no event or alias titled "${eventId}".`);
const category = room.settings.events[categoryId];
if (category && !('events' in category)) {
return this.errorReply(`There is already an event or alias titled "${categoryId}".`);
throw new Chat.ErrorMessage(`There is already an event or alias titled "${categoryId}".`);
}
if (!category) {
return this.errorReply(`There is no category titled "${categoryId}". To create it, use /roomevents addcategory ${categoryId}.`);
throw new Chat.ErrorMessage(`There is no category titled "${categoryId}". To create it, use /roomevents addcategory ${categoryId}.`);
}
if (category.events.includes(toID(event.eventName))) {
return this.errorReply(`The event "${eventId}" is already in the "${categoryId}" category.`);
throw new Chat.ErrorMessage(`The event "${eventId}" is already in the "${categoryId}" category.`);
}
category.events.push(toID(event.eventName));
room.settings.events[categoryId] = category;
@ -399,27 +401,27 @@ export const commands: Chat.ChatCommands = {
removefromcategory(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
this.checkCan('ban', null, room);
const [eventId, categoryId] = target.split(target.includes('|') ? '|' : ',').map(argument => toID(argument));
if (!(eventId && categoryId)) {
return this.errorReply("Usage: /roomevents removefromcategory [event name], [category].");
throw new Chat.ErrorMessage("Usage: /roomevents removefromcategory [event name], [category].");
}
if (!room.settings.events || Object.keys(room.settings.events).length === 0) {
return this.errorReply(`There are currently no scheduled events.`);
throw new Chat.ErrorMessage(`There are currently no scheduled events.`);
}
convertAliasFormat(room);
const event = room.settings.events[getEventID(eventId, room)];
if (!(event && 'eventName' in event)) return this.errorReply(`There is no event or alias titled "${eventId}".`);
if (!(event && 'eventName' in event)) throw new Chat.ErrorMessage(`There is no event or alias titled "${eventId}".`);
const category = room.settings.events[categoryId];
if (category && !('events' in category)) {
return this.errorReply(`There is already an event or alias titled "${categoryId}".`);
throw new Chat.ErrorMessage(`There is already an event or alias titled "${categoryId}".`);
}
if (!category) return this.errorReply(`There is no category titled "${categoryId}".`);
if (!category) throw new Chat.ErrorMessage(`There is no category titled "${categoryId}".`);
if (!category.events.includes(toID(event.eventName))) {
return this.errorReply(`The event "${eventId}" isn't in the "${categoryId}" category.`);
throw new Chat.ErrorMessage(`The event "${eventId}" isn't in the "${categoryId}" category.`);
}
category.events = category.events.filter(e => e !== eventId);
room.settings.events[categoryId] = category;
@ -433,15 +435,15 @@ export const commands: Chat.ChatCommands = {
addcat: 'addcategory',
addcategory(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
this.checkCan('ban', null, room);
const categoryId = toID(target);
if (!target) {
return this.errorReply("Usage: /roomevents addcategory [category name]. Categories must contain at least one alphanumeric character.");
throw new Chat.ErrorMessage("Usage: /roomevents addcategory [category name]. Categories must contain at least one alphanumeric character.");
}
convertAliasFormat(room);
if (!room.settings.events) room.settings.events = Object.create(null);
if (room.settings.events?.[categoryId]) return this.errorReply(`The category "${target}" already exists.`);
if (room.settings.events?.[categoryId]) throw new Chat.ErrorMessage(`The category "${target}" already exists.`);
room.settings.events![categoryId] = { events: [] };
@ -457,13 +459,13 @@ export const commands: Chat.ChatCommands = {
rmcat: 'removecategory',
removecategory(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
this.checkCan('ban', null, room);
const categoryId = toID(target);
if (!target) return this.errorReply("Usage: /roomevents removecategory [category name].");
if (!target) throw new Chat.ErrorMessage("Usage: /roomevents removecategory [category name].");
convertAliasFormat(room);
if (!room.settings.events) room.settings.events = Object.create(null);
if (!room.settings.events?.[categoryId]) return this.errorReply(`The category "${target}" doesn't exist.`);
if (!room.settings.events?.[categoryId]) throw new Chat.ErrorMessage(`The category "${target}" doesn't exist.`);
delete room.settings.events?.[categoryId];
@ -476,13 +478,13 @@ export const commands: Chat.ChatCommands = {
viewcategories: 'categories',
categories(target, room, user) {
room = this.requireRoom();
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
this.runBroadcast();
const categoryButtons = getAllCategories(room).map(
category => `<button class="button" name="send" value="/roomevents view ${category}">${category}</button>`
);
if (!categoryButtons.length) return this.errorReply(`There are no roomevent categories in ${room.title}.`);
if (!categoryButtons.length) throw new Chat.ErrorMessage(`There are no roomevent categories in ${room.title}.`);
this.sendReplyBox(`Roomevent categories in ${room.title}: ${categoryButtons.join(' ')}`);
},
@ -493,9 +495,9 @@ export const commands: Chat.ChatCommands = {
sortby(target, room, user) {
room = this.requireRoom();
// preconditions
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
if (!room.settings.events || !Object.keys(room.settings.events).length) {
return this.errorReply("There are currently no planned upcoming events for this room.");
throw new Chat.ErrorMessage("There are currently no planned upcoming events for this room.");
}
this.checkCan('ban', null, room);
@ -545,7 +547,7 @@ export const commands: Chat.ChatCommands = {
);
break;
default:
return this.errorReply(`Invalid column name "${columnName}". Please use one of: date, desc, name.`);
throw new Chat.ErrorMessage(`Invalid column name "${columnName}". Please use one of: date, desc, name.`);
}
// rebuild the room.settings.events object

View File

@ -61,26 +61,26 @@ export const commands: Chat.ChatCommands = {
const useHTML = this.cmd.includes('html');
this.checkCan('ban', null, room);
if (useHTML && !user.can('addhtml', null, room, this.fullCmd)) {
return this.errorReply(`You are not allowed to use raw HTML in roomfaqs.`);
throw new Chat.ErrorMessage(`You are not allowed to use raw HTML in roomfaqs.`);
}
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
if (!target) return this.parse('/help roomfaq');
target = target.trim();
const input = this.filter(target);
if (target !== input) return this.errorReply("You are not allowed to use fitered words in roomfaq entries.");
if (target !== input) throw new Chat.ErrorMessage("You are not allowed to use fitered words in roomfaq entries.");
let [topic, ...rest] = input.split(',');
topic = toID(topic);
if (!(topic && rest.length)) return this.parse('/help roomfaq');
let text = rest.join(',').trim();
if (topic.length > 25) return this.errorReply("FAQ topics should not exceed 25 characters.");
if (topic.length > 25) throw new Chat.ErrorMessage("FAQ topics should not exceed 25 characters.");
const lengthWithoutFormatting = Chat.stripFormatting(text).length;
if (lengthWithoutFormatting > MAX_ROOMFAQ_LENGTH) {
return this.errorReply(`FAQ entries must not exceed ${MAX_ROOMFAQ_LENGTH} characters.`);
throw new Chat.ErrorMessage(`FAQ entries must not exceed ${MAX_ROOMFAQ_LENGTH} characters.`);
} else if (lengthWithoutFormatting < 1) {
return this.errorReply(`FAQ entries must include at least one character.`);
throw new Chat.ErrorMessage(`FAQ entries must include at least one character.`);
}
if (!useHTML) {
@ -108,7 +108,7 @@ export const commands: Chat.ChatCommands = {
const topic = toID(target);
if (!topic) return this.parse('/help roomfaq');
if (!roomFaqs[room.roomid]?.[topic]) return this.errorReply("Invalid topic.");
if (!roomFaqs[room.roomid]?.[topic]) throw new Chat.ErrorMessage("Invalid topic.");
if (
room.settings.repeats?.length &&
room.settings.repeats.filter(x => x.faq && x.id === topic).length
@ -131,21 +131,21 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom();
this.checkChat();
this.checkCan('ban', null, room);
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!room.persist) throw new Chat.ErrorMessage("This command is unavailable in temporary rooms.");
const [alias, topic] = target.split(',').map(val => toID(val));
if (!(alias && topic)) return this.parse('/help roomfaq');
if (alias.length > 25) return this.errorReply("FAQ topics should not exceed 25 characters.");
if (alias === topic) return this.errorReply("You cannot make the alias have the same name as the topic.");
if (alias.length > 25) throw new Chat.ErrorMessage("FAQ topics should not exceed 25 characters.");
if (alias === topic) throw new Chat.ErrorMessage("You cannot make the alias have the same name as the topic.");
if (roomFaqs[room.roomid][alias] && !roomFaqs[room.roomid][alias].alias) {
return this.errorReply("You cannot overwrite an existing topic with an alias; please delete the topic first.");
throw new Chat.ErrorMessage("You cannot overwrite an existing topic with an alias; please delete the topic first.");
}
if (!(roomFaqs[room.roomid] && topic in roomFaqs[room.roomid])) {
return this.errorReply(`The topic ${topic} was not found in this room's faq list.`);
throw new Chat.ErrorMessage(`The topic ${topic} was not found in this room's faq list.`);
}
if (getAlias(room.roomid, topic)) {
return this.errorReply(`You cannot make an alias of an alias. Use /addalias ${alias}, ${getAlias(room.roomid, topic)} instead.`);
throw new Chat.ErrorMessage(`You cannot make an alias of an alias. Use /addalias ${alias}, ${getAlias(room.roomid, topic)} instead.`);
}
roomFaqs[room.roomid][alias] = {
alias: true,
@ -159,13 +159,13 @@ export const commands: Chat.ChatCommands = {
rfaq: 'roomfaq',
roomfaq(target, room, user, connection, cmd) {
room = this.requireRoom();
if (!roomFaqs[room.roomid]) return this.errorReply("This room has no FAQ topics.");
if (!roomFaqs[room.roomid]) throw new Chat.ErrorMessage("This room has no FAQ topics.");
let topic: string = toID(target);
if (topic === 'constructor') return false;
if (!topic) {
return this.parse(`/join view-roomfaqs-${room.roomid}`);
}
if (!roomFaqs[room.roomid][topic]) return this.errorReply("Invalid topic.");
if (!roomFaqs[room.roomid][topic]) throw new Chat.ErrorMessage("Invalid topic.");
topic = getAlias(room.roomid, topic) || topic;
if (!this.runBroadcast()) return;

View File

@ -1064,7 +1064,7 @@ const ScavengerCommands: Chat.ChatCommands = {
join(target, room, user) {
room = this.requireRoom();
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply("There is no scavenger hunt currently running.");
if (!game) throw new Chat.ErrorMessage("There is no scavenger hunt currently running.");
this.checkChat();
game.joinGame(user);
@ -1073,7 +1073,7 @@ const ScavengerCommands: Chat.ChatCommands = {
leave(target, room, user) {
room = this.requireRoom();
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply("There is no scavenger hunt currently running.");
if (!game) throw new Chat.ErrorMessage("There is no scavenger hunt currently running.");
game.leaveGame(user);
},
@ -1092,15 +1092,15 @@ const ScavengerCommands: Chat.ChatCommands = {
start(target, room, user) {
room = this.requireRoom();
this.checkCan('mute', null, room);
if (room.scavgame) return this.errorReply('There is already a scavenger game running.');
if (room.scavgame) throw new Chat.ErrorMessage('There is already a scavenger game running.');
if (room.getGame(ScavengerHunt)) {
return this.errorReply('You cannot start a scavenger game where there is already a scavenger hunt in the room.');
throw new Chat.ErrorMessage('You cannot start a scavenger game where there is already a scavenger hunt in the room.');
}
target = toID(target);
const game = ScavMods.LoadGame(room, target);
if (!game) return this.errorReply('Invalid game mode.');
if (!game) throw new Chat.ErrorMessage('Invalid game mode.');
room.scavgame = game;
@ -1113,7 +1113,7 @@ const ScavengerCommands: Chat.ChatCommands = {
end(target, room, user) {
room = this.requireRoom();
this.checkCan('mute', null, room);
if (!room.scavgame) return this.errorReply(`There is no scavenger game currently running.`);
if (!room.scavgame) throw new Chat.ErrorMessage(`There is no scavenger game currently running.`);
this.privateModAction(`The ${room.scavgame.name} has been forcibly ended by ${user.name}.`);
this.modlog('SCAVENGER', null, 'ended the scavenger game');
@ -1124,10 +1124,10 @@ const ScavengerCommands: Chat.ChatCommands = {
kick(target, room, user) {
room = this.requireRoom();
this.checkCan('mute', null, room);
if (!room.scavgame) return this.errorReply(`There is no scavenger game currently running.`);
if (!room.scavgame) throw new Chat.ErrorMessage(`There is no scavenger game currently running.`);
const targetId = toID(target);
if (targetId === 'constructor' || !targetId) return this.errorReply("Invalid player.");
if (targetId === 'constructor' || !targetId) throw new Chat.ErrorMessage("Invalid player.");
const success = room.scavgame.eliminate(targetId);
if (success) {
@ -1138,7 +1138,7 @@ const ScavengerCommands: Chat.ChatCommands = {
game.eliminate(targetId); // remove player from current hunt as well.
}
} else {
this.errorReply(`Unable to kick user '${targetId}'.`);
throw new Chat.ErrorMessage(`Unable to kick user '${targetId}'.`);
}
},
@ -1147,8 +1147,8 @@ const ScavengerCommands: Chat.ChatCommands = {
scoreboard: 'leaderboard',
async leaderboard(target, room, user) {
room = this.requireRoom();
if (!room.scavgame) return this.errorReply(`There is no scavenger game currently running.`);
if (!room.scavgame.leaderboard) return this.errorReply("This scavenger game does not have a leaderboard.");
if (!room.scavgame) throw new Chat.ErrorMessage(`There is no scavenger game currently running.`);
if (!room.scavgame.leaderboard) throw new Chat.ErrorMessage("This scavenger game does not have a leaderboard.");
if (!this.runBroadcast()) return false;
const html = await room.scavgame.leaderboard.htmlLadder();
@ -1157,8 +1157,8 @@ const ScavengerCommands: Chat.ChatCommands = {
async rank(target, room, user) {
room = this.requireRoom();
if (!room.scavgame) return this.errorReply(`There is no scavenger game currently running.`);
if (!room.scavgame.leaderboard) return this.errorReply("This scavenger game does not have a leaderboard.");
if (!room.scavgame) throw new Chat.ErrorMessage(`There is no scavenger game currently running.`);
if (!room.scavgame.leaderboard) throw new Chat.ErrorMessage("This scavenger game does not have a leaderboard.");
if (!this.runBroadcast()) return false;
const targetId = toID(target) || user.id;
@ -1177,18 +1177,18 @@ const ScavengerCommands: Chat.ChatCommands = {
createteam(target, room, user) {
room = this.requireRoom();
this.checkCan('mute', null, room);
// if (room.getGame(ScavengerHunt)) return this.errorReply('Teams cannot be modified after the hunt starts.');
// if (room.getGame(ScavengerHunt)) throw new Chat.ErrorMessage('Teams cannot be modified after the hunt starts.');
const game = room.scavgame;
if (!game || game.id !== 'teamscavs') return this.errorReply('There is currently no game of Team Scavs going on.');
if (!game || game.id !== 'teamscavs') throw new Chat.ErrorMessage('There is currently no game of Team Scavs going on.');
let [teamName, leader] = target.split(',');
teamName = teamName.trim();
if (game.teams[teamName]) return this.errorReply(`The team ${teamName} already exists.`);
if (game.teams[teamName]) throw new Chat.ErrorMessage(`The team ${teamName} already exists.`);
const leaderUser = Users.get(leader);
if (!leaderUser) return this.errorReply('The user you specified is currently not online');
if (game.getPlayerTeam(leaderUser)) return this.errorReply('The user is already a member of another team.');
if (!leaderUser) throw new Chat.ErrorMessage('The user you specified is currently not online');
if (game.getPlayerTeam(leaderUser)) throw new Chat.ErrorMessage('The user is already a member of another team.');
game.teams[teamName] = { name: teamName, answers: [], players: [leaderUser.id], question: 1, completed: false };
game.announce(Utils.html`A new team "${teamName}" has been created with ${leaderUser.name} as the leader.`);
@ -1198,12 +1198,12 @@ const ScavengerCommands: Chat.ChatCommands = {
removeteam(target, room, user) {
room = this.requireRoom();
this.checkCan('mute', null, room);
// if (room.getGame(ScavengerHunt)) return this.errorReply('Teams cannot be modified after the hunt starts.');
// if (room.getGame(ScavengerHunt)) throw new Chat.ErrorMessage('Teams cannot be modified after the hunt starts.');
const game = room.scavgame;
if (!game || game.id !== 'teamscavs') return this.errorReply('There is currently no game of Team Scavs going on.');
if (!game || game.id !== 'teamscavs') throw new Chat.ErrorMessage('There is currently no game of Team Scavs going on.');
if (!game.teams[target]) return this.errorReply(`The team ${target} does not exist.`);
if (!game.teams[target]) throw new Chat.ErrorMessage(`The team ${target} does not exist.`);
delete game.teams[target];
game.announce(Utils.html`The team "${target}" has been removed.`);
@ -1212,8 +1212,8 @@ const ScavengerCommands: Chat.ChatCommands = {
addplayer(target, room, user) {
room = this.requireRoom();
const game = room.scavgame;
if (!game || game.id !== 'teamscavs') return this.errorReply('There is currently no game of Team Scavs going on.');
// if (room.getGame(ScavengerHunt)) return this.errorReply('Teams cannot be modified after the hunt starts.');
if (!game || game.id !== 'teamscavs') throw new Chat.ErrorMessage('There is currently no game of Team Scavs going on.');
// if (room.getGame(ScavengerHunt)) throw new Chat.ErrorMessage('Teams cannot be modified after the hunt starts.');
let userTeam;
@ -1224,10 +1224,10 @@ const ScavengerCommands: Chat.ChatCommands = {
break;
}
}
if (!userTeam) return this.errorReply('You must be the leader of a team to add people into the team.');
if (!userTeam) throw new Chat.ErrorMessage('You must be the leader of a team to add people into the team.');
const targetUsers = target.split(',').map(id => Users.getExact(id)).filter(u => u?.connected) as User[];
if (!targetUsers.length) return this.errorReply('Please select a user that is currently online.');
if (!targetUsers.length) throw new Chat.ErrorMessage('Please select a user that is currently online.');
const errors = [];
for (const targetUser of targetUsers) {
@ -1247,9 +1247,9 @@ const ScavengerCommands: Chat.ChatCommands = {
editplayers(target, room, user) {
room = this.requireRoom();
const game = room.scavgame;
if (!game || game.id !== 'teamscavs') return this.errorReply('There is currently no game of Team Scavs going on.');
if (!game || game.id !== 'teamscavs') throw new Chat.ErrorMessage('There is currently no game of Team Scavs going on.');
this.checkCan('mute', null, room);
// if (room.getGame(ScavengerHunt)) return this.errorReply('Teams cannot be modified after the hunt starts.');
// if (room.getGame(ScavengerHunt)) throw new Chat.ErrorMessage('Teams cannot be modified after the hunt starts.');
const parts = target.split(',');
const teamName = parts[0].trim();
@ -1257,7 +1257,7 @@ const ScavengerCommands: Chat.ChatCommands = {
const team = game.teams[teamName];
if (!team) return this.errorReply('Invalid team.');
if (!team) throw new Chat.ErrorMessage('Invalid team.');
for (const entry of playerchanges) {
const userid = toID(entry);
@ -1298,7 +1298,7 @@ const ScavengerCommands: Chat.ChatCommands = {
room = this.requireRoom();
const game = room.scavgame;
if (!game || game.id !== 'teamscavs') return this.errorReply('There is currently no game of Team Scavs going on.');
if (!game || game.id !== 'teamscavs') throw new Chat.ErrorMessage('There is currently no game of Team Scavs going on.');
const display = [];
for (const teamID in game.teams) {
@ -1312,10 +1312,10 @@ const ScavengerCommands: Chat.ChatCommands = {
guesses(target, room, user) {
room = this.requireRoom();
const game = room.scavgame;
if (!game || game.id !== 'teamscavs') return this.errorReply('There is currently no game of Team Scavs going on.');
if (!game || game.id !== 'teamscavs') throw new Chat.ErrorMessage('There is currently no game of Team Scavs going on.');
const team = game.getPlayerTeam(user);
if (!team) return this.errorReply('You are not currently part of this Team Scavs game.');
if (!team) throw new Chat.ErrorMessage('You are not currently part of this Team Scavs game.');
this.sendReplyBox(Utils.html`<strong>Question #${team.question} guesses:</strong> ${team.answers.sort().join(', ')}`);
},
@ -1324,12 +1324,12 @@ const ScavengerCommands: Chat.ChatCommands = {
note(target, room, user) {
room = this.requireRoom();
const game = room.scavgame;
if (!game || game.id !== 'teamscavs') return this.errorReply('There is currently no game of Team Scavs going on.');
if (!game || game.id !== 'teamscavs') throw new Chat.ErrorMessage('There is currently no game of Team Scavs going on.');
const team = game.getPlayerTeam(user);
if (!team) return this.errorReply('You are not currently part of this Team Scavs game.');
if (!team) throw new Chat.ErrorMessage('You are not currently part of this Team Scavs game.');
if (!target) return this.errorReply('Please include a message as the note.');
if (!target) throw new Chat.ErrorMessage('Please include a message as the note.');
game.teamAnnounce(user, Utils.html`<strong> Note from ${user.name}:</strong> ${target}`);
},
@ -1376,10 +1376,10 @@ const ScavengerCommands: Chat.ChatCommands = {
create(target, room, user, connection, cmd) {
room = this.requireRoom();
if (!getScavsRoom(room)) {
return this.errorReply("Scavenger hunts can only be created in the scavengers room.");
throw new Chat.ErrorMessage("Scavenger hunts can only be created in the scavengers room.");
}
this.checkCan('mute', null, room);
if (room.game) return this.errorReply(`There is already a game in this room - ${room.game.title}.`);
if (room.game) throw new Chat.ErrorMessage(`There is already a game in this room - ${room.game.title}.`);
let gameType = 'regular' as GameTypes;
if (cmd.includes('practice')) {
gameType = 'practice';
@ -1409,18 +1409,18 @@ const ScavengerCommands: Chat.ChatCommands = {
!cmd.includes('force') && ['regular', 'unrated', 'recycled'].includes(gameType) && !mod &&
room.settings.scavQueue?.length && !room.scavgame
) {
return this.errorReply(`There are currently hunts in the queue! If you would like to start the hunt anyways, use /forcestart${gameType === 'regular' ? 'hunt' : gameType}.`);
throw new Chat.ErrorMessage(`There are currently hunts in the queue! If you would like to start the hunt anyways, use /forcestart${gameType === 'regular' ? 'hunt' : gameType}.`);
}
if (gameType === 'recycled') {
if (ScavengerHuntDatabase.isEmpty()) {
return this.errorReply("There are no hunts in the database.");
throw new Chat.ErrorMessage("There are no hunts in the database.");
}
let hunt;
if (questions) {
const huntNumber = parseInt(questions);
if (!ScavengerHuntDatabase.hasHunt(huntNumber)) return this.errorReply("You specified an invalid hunt number.");
if (!ScavengerHuntDatabase.hasHunt(huntNumber)) throw new Chat.ErrorMessage("You specified an invalid hunt number.");
hunt = scavengersData.recycledHunts[huntNumber - 1];
} else {
hunt = ScavengerHuntDatabase.getRecycledHuntFromDatabase();
@ -1440,11 +1440,11 @@ const ScavengerCommands: Chat.ChatCommands = {
gameType === 'official' || gameType === 'recycled'
);
if (!hosts.length) {
return this.errorReply("The user(s) you specified as the host is not online, or is not in the room.");
throw new Chat.ErrorMessage("The user(s) you specified as the host is not online, or is not in the room.");
}
const res = ScavengerHunt.parseQuestions(params);
if (res.err) return this.errorReply(res.err);
if (res.err) throw new Chat.ErrorMessage(res.err);
room.game = new ScavengerHunt({
room,
@ -1463,7 +1463,7 @@ const ScavengerCommands: Chat.ChatCommands = {
status(target, room, user) {
room = this.requireRoom();
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`);
const elapsedMsg = Chat.toDurationString(Date.now() - game.startTime, { hhmmss: true });
const gameTypeMsg = game.gameType ? `<em>${game.gameType}</em> ` : '';
@ -1508,15 +1508,15 @@ const ScavengerCommands: Chat.ChatCommands = {
hint(target, room, user) {
room = this.requireRoom();
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (!game.onSendQuestion(user, true)) this.errorReply("You are not currently participating in the hunt.");
if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`);
if (!game.onSendQuestion(user, true)) throw new Chat.ErrorMessage("You are not currently participating in the hunt.");
},
timer(target, room, user) {
room = this.requireRoom();
this.checkCan('mute', null, room);
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`);
const minutes = (toID(target) === 'off' ? 0 : parseFloat(target));
if (isNaN(minutes) || minutes < 0 || (minutes * 60 * 1000) > Chat.MAX_TIMEOUT_DURATION) {
@ -1535,9 +1535,9 @@ const ScavengerCommands: Chat.ChatCommands = {
room = this.requireRoom();
this.checkCan('mute', null, room);
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`);
if (game.staffHostId === user.id) return this.errorReply('You already have staff permissions for this hunt.');
if (game.staffHostId === user.id) throw new Chat.ErrorMessage('You already have staff permissions for this hunt.');
game.staffHostId = `${user.id}`;
game.staffHostName = `${user.name}`;
@ -1554,7 +1554,7 @@ const ScavengerCommands: Chat.ChatCommands = {
room = this.requireRoom();
this.checkCan('mute', null, room);
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`);
game.onEnd(true, user);
this.privateModAction(`${user.name} has reset the scavenger hunt.`);
@ -1565,7 +1565,7 @@ const ScavengerCommands: Chat.ChatCommands = {
room = this.requireRoom();
this.checkCan('mute', null, room);
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`);
const hunt: QueuedHunt = {
hosts: game.hosts,
@ -1592,16 +1592,16 @@ const ScavengerCommands: Chat.ChatCommands = {
this.checkCan('mute', null, room);
if (!room.game && room.scavgame) return this.parse('/scav games end');
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`);
const completed = game.preCompleted ? game.preCompleted : game.completed;
if (!this.cmd.includes('force')) {
if (!completed.length) {
return this.errorReply('No one has finished the hunt yet. Use /forceendhunt if you want to end the hunt and reveal the answers.');
throw new Chat.ErrorMessage('No one has finished the hunt yet. Use /forceendhunt if you want to end the hunt and reveal the answers.');
}
} else if (completed.length) {
return this.errorReply(`This hunt has ${Chat.count(completed, "finishers")}; use /endhunt`);
throw new Chat.ErrorMessage(`This hunt has ${Chat.count(completed, "finishers")}; use /endhunt`);
}
game.onEnd(false, user);
@ -1612,9 +1612,9 @@ const ScavengerCommands: Chat.ChatCommands = {
viewhunt(target, room, user) {
room = this.requireRoom();
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`);
if (!('onViewHunt' in game)) return this.errorReply('There is currently no hunt to be viewed.');
if (!('onViewHunt' in game)) throw new Chat.ErrorMessage('There is currently no hunt to be viewed.');
game.onViewHunt(user);
},
@ -1622,12 +1622,12 @@ const ScavengerCommands: Chat.ChatCommands = {
edithunt(target, room, user) {
room = this.requireRoom();
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`);
if (
(!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) &&
game.staffHostId !== user.id
) {
return this.errorReply("You cannot edit the hints and answers if you are not the host.");
throw new Chat.ErrorMessage("You cannot edit the hints and answers if you are not the host.");
}
const [question, type, ...value] = target.split(',');
@ -1640,19 +1640,19 @@ const ScavengerCommands: Chat.ChatCommands = {
spoiler(target, room, user) {
room = this.requireRoom();
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`);
if (
(!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) &&
game.staffHostId !== user.id
) {
return this.errorReply("You cannot add more hints if you are not the host.");
throw new Chat.ErrorMessage("You cannot add more hints if you are not the host.");
}
const parts = target.split(',');
const question = parseInt(parts[0]) - 1;
const hint = parts.slice(1).join(',');
if (!game.questions[question]) return this.errorReply(`Invalid question number.`);
if (!hint) return this.errorReply('The hint cannot be left empty.');
if (!game.questions[question]) throw new Chat.ErrorMessage(`Invalid question number.`);
if (!hint) throw new Chat.ErrorMessage('The hint cannot be left empty.');
game.questions[question].spoilers.push(hint);
room.addByUser(user, `Question #${question + 1} hint - spoiler: ${hint}`);
@ -1669,20 +1669,20 @@ const ScavengerCommands: Chat.ChatCommands = {
removehint(target, room, user) {
room = this.requireRoom();
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`);
if (
(!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) &&
game.staffHostId !== user.id
) {
return this.errorReply("You cannot remove hints if you are not the host.");
throw new Chat.ErrorMessage("You cannot remove hints if you are not the host.");
}
const parts = target.split(',');
const question = parseInt(parts[0]) - 1;
const hint = parseInt(parts[1]) - 1;
if (!game.questions[question]) return this.errorReply(`Invalid question number.`);
if (!game.questions[question].spoilers[hint]) return this.errorReply('Invalid hint number.');
if (!game.questions[question]) throw new Chat.ErrorMessage(`Invalid question number.`);
if (!game.questions[question].spoilers[hint]) throw new Chat.ErrorMessage('Invalid hint number.');
game.questions[question].spoilers.splice(hint, 1);
return this.sendReply("Hint has been removed.");
@ -1692,12 +1692,12 @@ const ScavengerCommands: Chat.ChatCommands = {
edithint(target, room, user) {
room = this.requireRoom();
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`);
if (
(!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) &&
game.staffHostId !== user.id
) {
return this.errorReply("You cannot edit hints if you are not the host.");
throw new Chat.ErrorMessage("You cannot edit hints if you are not the host.");
}
const parts = target.split(',');
@ -1705,9 +1705,9 @@ const ScavengerCommands: Chat.ChatCommands = {
const hint = parseInt(parts[1]) - 1;
const value = parts.slice(2).join(',');
if (!game.questions[question]) return this.errorReply(`Invalid question number.`);
if (!game.questions[question].spoilers[hint]) return this.errorReply('Invalid hint number.');
if (!value) return this.errorReply('The hint cannot be left empty.');
if (!game.questions[question]) throw new Chat.ErrorMessage(`Invalid question number.`);
if (!game.questions[question].spoilers[hint]) throw new Chat.ErrorMessage('Invalid hint number.');
if (!value) throw new Chat.ErrorMessage('The hint cannot be left empty.');
game.questions[question].spoilers[hint] = value;
room.addByUser(user, `Question #${question + 1} hint - spoiler: ${value}`);
@ -1724,17 +1724,17 @@ const ScavengerCommands: Chat.ChatCommands = {
kick(target, room, user) {
room = this.requireRoom();
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (!game) throw new Chat.ErrorMessage(`There is no scavenger hunt currently running.`);
const targetId = toID(target);
if (targetId === 'constructor' || !targetId) return this.errorReply("Invalid player.");
if (targetId === 'constructor' || !targetId) throw new Chat.ErrorMessage("Invalid player.");
const success = game.eliminate(targetId);
if (success) {
this.modlog('SCAV KICK', targetId);
return this.privateModAction(`${user.name} has kicked '${targetId}' from the scavenger hunt.`);
}
this.errorReply(`Unable to kick '${targetId}' from the scavenger hunt.`);
throw new Chat.ErrorMessage(`Unable to kick '${targetId}' from the scavenger hunt.`);
},
/**
@ -1758,7 +1758,7 @@ const ScavengerCommands: Chat.ChatCommands = {
queue(target, room, user) {
room = this.requireRoom();
if (!getScavsRoom(room)) {
return this.errorReply("This command can only be used in the scavengers room.");
throw new Chat.ErrorMessage("This command can only be used in the scavengers room.");
}
if (!target && this.cmd !== 'queuerecycled') {
if (this.cmd === 'queue') {
@ -1776,7 +1776,7 @@ const ScavengerCommands: Chat.ChatCommands = {
if (this.cmd === 'queuerecycled') {
if (ScavengerHuntDatabase.isEmpty()) {
return this.errorReply(`There are no hunts in the database.`);
throw new Chat.ErrorMessage(`There are no hunts in the database.`);
}
if (!room.settings.scavQueue) {
room.settings.scavQueue = [];
@ -1785,7 +1785,7 @@ const ScavengerCommands: Chat.ChatCommands = {
let next;
if (target) {
const huntNumber = parseInt(target);
if (!ScavengerHuntDatabase.hasHunt(huntNumber)) return this.errorReply("You specified an invalid hunt number.");
if (!ScavengerHuntDatabase.hasHunt(huntNumber)) throw new Chat.ErrorMessage("You specified an invalid hunt number.");
next = scavengersData.recycledHunts[huntNumber - 1];
} else {
next = ScavengerHuntDatabase.getRecycledHuntFromDatabase();
@ -1803,11 +1803,11 @@ const ScavengerCommands: Chat.ChatCommands = {
const [hostsArray, ...params] = target.split('|');
const hosts = ScavengerHunt.parseHosts(hostsArray.split(/[,;]/), room);
if (!hosts.length) {
return this.errorReply("The user(s) you specified as the host is not online, or is not in the room.");
throw new Chat.ErrorMessage("The user(s) you specified as the host is not online, or is not in the room.");
}
const results = ScavengerHunt.parseQuestions(params, this.cmd.includes('force'));
if (results.err) return this.errorReply(results.err);
if (results.err) throw new Chat.ErrorMessage(results.err);
if (!room.settings.scavQueue) room.settings.scavQueue = [];
@ -1828,7 +1828,7 @@ const ScavengerCommands: Chat.ChatCommands = {
dequeue(target, room, user) {
room = this.requireRoom();
if (!getScavsRoom(room)) {
return this.errorReply("This command can only be used in the scavengers room.");
throw new Chat.ErrorMessage("This command can only be used in the scavengers room.");
}
this.checkCan('mute', null, room);
const id = parseInt(target);
@ -1846,7 +1846,7 @@ const ScavengerCommands: Chat.ChatCommands = {
viewqueue(target, room, user) {
room = this.requireRoom();
if (!getScavsRoom(room)) {
return this.errorReply("This command can only be used in the scavengers room.");
throw new Chat.ErrorMessage("This command can only be used in the scavengers room.");
}
if (!this.runBroadcast()) return false;
@ -1856,14 +1856,14 @@ const ScavengerCommands: Chat.ChatCommands = {
next(target, room, user) {
room = this.requireRoom();
if (!getScavsRoom(room)) {
return this.errorReply("This command can only be used in the scavengers room.");
throw new Chat.ErrorMessage("This command can only be used in the scavengers room.");
}
this.checkCan('mute', null, room);
if (!room.settings.scavQueue?.length) {
return this.errorReply("The scavenger hunt queue is currently empty.");
throw new Chat.ErrorMessage("The scavenger hunt queue is currently empty.");
}
if (room.game) return this.errorReply(`There is already a game in this room - ${room.game.title}.`);
if (room.game) throw new Chat.ErrorMessage(`There is already a game in this room - ${room.game.title}.`);
const huntId = parseInt(target) || 0;
@ -1892,14 +1892,14 @@ const ScavengerCommands: Chat.ChatCommands = {
disablequeue(target, room, user) {
room = this.requireRoom();
if (!getScavsRoom(room)) {
return this.errorReply("This command can only be used in the scavengers room.");
throw new Chat.ErrorMessage("This command can only be used in the scavengers room.");
}
this.checkCan('mute', null, room);
if (!room.settings.scavSettings) room.settings.scavSettings = {};
const state = this.cmd === 'disablequeue';
if ((room.settings.scavSettings.scavQueueDisabled || false) === state) {
return this.errorReply(`The queue is already ${state ? 'disabled' : 'enabled'}.`);
throw new Chat.ErrorMessage(`The queue is already ${state ? 'disabled' : 'enabled'}.`);
}
room.settings.scavSettings.scavQueueDisabled = state;
@ -1913,7 +1913,7 @@ const ScavengerCommands: Chat.ChatCommands = {
defaulttimer(target, room, user) {
room = this.requireRoom();
if (!getScavsRoom(room)) {
return this.errorReply("This command can only be used in the scavengers room.");
throw new Chat.ErrorMessage("This command can only be used in the scavengers room.");
}
this.checkCan('declare', null, room);
@ -1925,7 +1925,7 @@ const ScavengerCommands: Chat.ChatCommands = {
const duration = parseInt(target);
if (!duration || duration < 0) {
return this.errorReply('The default timer must be an integer greater than zero, in minutes.');
throw new Chat.ErrorMessage('The default timer must be an integer greater than zero, in minutes.');
}
room.settings.scavSettings.defaultScavTimer = duration;
@ -1945,8 +1945,10 @@ const ScavengerCommands: Chat.ChatCommands = {
const targetId = toID(parts[0]);
const points = parseInt(parts[1]);
if (!targetId || targetId === 'constructor' || targetId.length > 18) return this.errorReply("Invalid username.");
if (!points || points < 0 || points > 1000) return this.errorReply("Points must be an integer between 1 and 1000.");
if (!targetId || targetId === 'constructor' || targetId.length > 18) throw new Chat.ErrorMessage("Invalid username.");
if (!points || points < 0 || points > 1000) {
throw new Chat.ErrorMessage("Points must be an integer between 1 and 1000.");
}
Leaderboard.addPoints(targetId, 'points', points, true).write();
@ -1962,8 +1964,10 @@ const ScavengerCommands: Chat.ChatCommands = {
const targetId = toID(parts[0]);
const points = parseInt(parts[1]);
if (!targetId || targetId === 'constructor' || targetId.length > 18) return this.errorReply("Invalid username.");
if (!points || points < 0 || points > 1000) return this.errorReply("Points must be an integer between 1 and 1000.");
if (!targetId || targetId === 'constructor' || targetId.length > 18) throw new Chat.ErrorMessage("Invalid username.");
if (!points || points < 0 || points > 1000) {
throw new Chat.ErrorMessage("Points must be an integer between 1 and 1000.");
}
Leaderboard.addPoints(targetId, 'points', -points, true).write();
@ -1984,7 +1988,7 @@ const ScavengerCommands: Chat.ChatCommands = {
async ladder(target, room, user) {
room = this.requireRoom();
if (!getScavsRoom(room)) {
return this.errorReply("This command can only be used in the scavengers room.");
throw new Chat.ErrorMessage("This command can only be used in the scavengers room.");
}
if (!this.runBroadcast()) return false;
@ -2010,7 +2014,7 @@ const ScavengerCommands: Chat.ChatCommands = {
async rank(target, room, user) {
room = this.requireRoom();
if (!getScavsRoom(room)) {
return this.errorReply("This command can only be used in the scavengers room.");
throw new Chat.ErrorMessage("This command can only be used in the scavengers room.");
}
if (!this.runBroadcast()) return false;
@ -2031,7 +2035,7 @@ const ScavengerCommands: Chat.ChatCommands = {
room = this.requireRoom();
const scavsRoom = getScavsRoom(room);
if (!scavsRoom) {
return this.errorReply("This command can only be used in the scavengers room.");
throw new Chat.ErrorMessage("This command can only be used in the scavengers room.");
}
this.checkCan('mute', null, room); // perms for viewing only
@ -2050,10 +2054,10 @@ const ScavengerCommands: Chat.ChatCommands = {
const parts = target.split(',');
const blitzPoints = parseInt(parts[1]);
const gameType = toID(parts[0]) as GameTypes;
if (!RATED_TYPES.includes(gameType)) return this.errorReply(`You cannot set blitz points for ${gameType} hunts.`);
if (!RATED_TYPES.includes(gameType)) throw new Chat.ErrorMessage(`You cannot set blitz points for ${gameType} hunts.`);
if (isNaN(blitzPoints) || blitzPoints < 0 || blitzPoints > 1000) {
return this.errorReply("The points value awarded for blitz must be an integer bewteen 0 and 1000.");
throw new Chat.ErrorMessage("The points value awarded for blitz must be an integer bewteen 0 and 1000.");
}
if (!room.settings.scavSettings.blitzPoints) room.settings.scavSettings.blitzPoints = {};
room.settings.scavSettings.blitzPoints[gameType] = blitzPoints;
@ -2078,7 +2082,7 @@ const ScavengerCommands: Chat.ChatCommands = {
room = this.requireRoom();
const scavsRoom = getScavsRoom(room);
if (!scavsRoom) {
return this.errorReply("This command can only be used in the scavengers room.");
throw new Chat.ErrorMessage("This command can only be used in the scavengers room.");
}
this.checkCan('mute', null, room); // perms for viewing only
if (!room.settings.scavSettings) room.settings.scavSettings = {};
@ -2090,7 +2094,7 @@ const ScavengerCommands: Chat.ChatCommands = {
this.checkCan('declare', null, room); // perms for editting
const points = parseInt(target);
if (isNaN(points)) return this.errorReply(`${target} is not a valid number of points.`);
if (isNaN(points)) throw new Chat.ErrorMessage(`${target} is not a valid number of points.`);
room.settings.scavSettings.hostPoints = points;
room.saveSettings();
@ -2112,7 +2116,7 @@ const ScavengerCommands: Chat.ChatCommands = {
room = this.requireRoom();
const scavsRoom = getScavsRoom(room);
if (!scavsRoom) {
return this.errorReply("This command can only be used in the scavengers room.");
throw new Chat.ErrorMessage("This command can only be used in the scavengers room.");
}
this.checkCan('mute', null, room); // perms for viewing only
if (!room.settings.scavSettings) room.settings.scavSettings = {};
@ -2133,11 +2137,11 @@ const ScavengerCommands: Chat.ChatCommands = {
let [type, ...pointsSet] = target.split(',');
type = toID(type) as GameTypes;
if (!RATED_TYPES.includes(type)) return this.errorReply(`You cannot set win points for ${type} hunts.`);
if (!RATED_TYPES.includes(type)) throw new Chat.ErrorMessage(`You cannot set win points for ${type} hunts.`);
const winPoints = pointsSet.map(p => parseInt(p));
if (winPoints.some(p => isNaN(p) || p < 0 || p > 1000) || !winPoints.length) {
return this.errorReply("The points value awarded for winning a scavenger hunt must be an integer between 0 and 1000.");
throw new Chat.ErrorMessage("The points value awarded for winning a scavenger hunt must be an integer between 0 and 1000.");
}
if (!room.settings.scavSettings.winPoints) room.settings.scavSettings.winPoints = {};
@ -2165,7 +2169,7 @@ const ScavengerCommands: Chat.ChatCommands = {
room = this.requireRoom();
const scavsRoom = getScavsRoom(room);
if (!scavsRoom) {
return this.errorReply("This command can only be used in the scavengers room.");
throw new Chat.ErrorMessage("This command can only be used in the scavengers room.");
}
if (this.cmd.includes('reset')) target = 'RESET';
@ -2179,7 +2183,7 @@ const ScavengerCommands: Chat.ChatCommands = {
room.settings.scavSettings.officialtwist = null;
} else {
const twist = toID(target);
if (!ScavMods.twists[twist] || twist === 'constructor') return this.errorReply('Invalid twist.');
if (!ScavMods.twists[twist] || twist === 'constructor') throw new Chat.ErrorMessage('Invalid twist.');
room.settings.scavSettings.officialtwist = twist;
room.saveSettings();
@ -2212,7 +2216,7 @@ const ScavengerCommands: Chat.ChatCommands = {
twists(target, room, user) {
room = this.requireRoom();
if (!getScavsRoom(room)) {
return this.errorReply("This command can only be used in the scavengers room.");
throw new Chat.ErrorMessage("This command can only be used in the scavengers room.");
}
this.checkCan('mute', null, room);
if (!this.runBroadcast()) return false;
@ -2335,7 +2339,7 @@ const ScavengerCommands: Chat.ChatCommands = {
this.checkCan('mute', null, room);
const targetId = toID(target);
if (!targetId) return this.errorReply(`Please include the name of the user to ${this.cmd}.`);
if (!targetId) throw new Chat.ErrorMessage(`Please include the name of the user to ${this.cmd}.`);
const change = this.cmd === 'infract' ? 1 : -1;
PlayerLeaderboard.addPoints(targetId, 'infraction', change, true).write();
@ -2393,7 +2397,7 @@ const ScavengerCommands: Chat.ChatCommands = {
room = this.requireRoom();
this.checkCan('mute', null, room);
if (!getScavsRoom(room)) {
return this.errorReply("Scavenger Hunts can only be added to the database in the scavengers room.");
throw new Chat.ErrorMessage("Scavenger Hunts can only be added to the database in the scavengers room.");
}
let cmd;
@ -2405,23 +2409,23 @@ const ScavengerCommands: Chat.ChatCommands = {
}
if (cmd === 'addhunt') {
if (!target) return this.errorReply(`Usage: ${cmd} Hunt Text`);
if (!target) throw new Chat.ErrorMessage(`Usage: ${cmd} Hunt Text`);
const [hostsArray, ...questions] = target.split('|');
const hosts = ScavengerHunt.parseHosts(hostsArray.split(/[,;]/), room, true);
if (!hosts.length) {
return this.errorReply("You need to specify a host.");
throw new Chat.ErrorMessage("You need to specify a host.");
}
const result = ScavengerHunt.parseQuestions(questions);
if (result.err) return this.errorReply(result.err);
if (result.err) throw new Chat.ErrorMessage(result.err);
ScavengerHuntDatabase.addRecycledHuntToDatabase(hosts, result.result);
return this.privateModAction(`A recycled hunt has been added to the database.`);
}
// The rest of the commands depend on there already being hunts in the database.
if (ScavengerHuntDatabase.isEmpty()) return this.errorReply("There are no hunts in the database.");
if (ScavengerHuntDatabase.isEmpty()) throw new Chat.ErrorMessage("There are no hunts in the database.");
if (cmd === 'list') {
return this.parse(`/join view-recycledHunts-${room}`);
@ -2435,7 +2439,7 @@ const ScavengerCommands: Chat.ChatCommands = {
'removehint': 'Usage: removehint hunt number, question number, hint text',
'autostart': 'Usage: autostart on/off',
};
if (!params) return this.errorReply(usageMessages[cmd]);
if (!params) throw new Chat.ErrorMessage(usageMessages[cmd]);
const numberOfRequiredParameters: { [k: string]: number } = {
'removehunt': 1,
@ -2443,12 +2447,12 @@ const ScavengerCommands: Chat.ChatCommands = {
'removehint': 3,
'autostart': 1,
};
if (params.length < numberOfRequiredParameters[cmd]) return this.errorReply(usageMessages[cmd]);
if (params.length < numberOfRequiredParameters[cmd]) throw new Chat.ErrorMessage(usageMessages[cmd]);
const [huntNumber, questionNumber, hintNumber] = params.map(param => parseInt(param));
const cmdsNeedingHuntNumber = ['removehunt', 'removehint', 'addhint'];
if (cmdsNeedingHuntNumber.includes(cmd)) {
if (!ScavengerHuntDatabase.hasHunt(huntNumber)) return this.errorReply("You specified an invalid hunt number.");
if (!ScavengerHuntDatabase.hasHunt(huntNumber)) throw new Chat.ErrorMessage("You specified an invalid hunt number.");
}
const cmdsNeedingQuestionNumber = ['addhint', 'removehint'];
@ -2458,7 +2462,7 @@ const ScavengerCommands: Chat.ChatCommands = {
questionNumber <= 0 ||
questionNumber > scavengersData.recycledHunts[huntNumber - 1].questions.length
) {
return this.errorReply("You specified an invalid question number.");
throw new Chat.ErrorMessage("You specified an invalid question number.");
}
}
@ -2466,7 +2470,7 @@ const ScavengerCommands: Chat.ChatCommands = {
if (cmdsNeedingHintNumber.includes(cmd)) {
const numQuestions = scavengersData.recycledHunts[huntNumber - 1].questions.length;
if (isNaN(questionNumber) || questionNumber <= 0 || questionNumber > numQuestions) {
return this.errorReply("You specified an invalid hint number.");
throw new Chat.ErrorMessage("You specified an invalid hint number.");
}
}
@ -2482,9 +2486,9 @@ const ScavengerCommands: Chat.ChatCommands = {
return this.privateModAction(`Hint #${hintNumber} was removed from Recycled hunt #${huntNumber} question #${questionNumber}.`);
} else if (cmd === 'autostart') {
if (!room.settings.scavSettings) room.settings.scavSettings = {};
if (params[0] !== 'on' && params[0] !== 'off') return this.errorReply(usageMessages[cmd]);
if (params[0] !== 'on' && params[0] !== 'off') throw new Chat.ErrorMessage(usageMessages[cmd]);
if ((params[0] === 'on') === !!room.settings.scavSettings.addRecycledHuntsToQueueAutomatically) {
return this.errorReply(`Autostarting recycled hunts is already ${room.settings.scavSettings.addRecycledHuntsToQueueAutomatically ? 'on' : 'off'}.`);
throw new Chat.ErrorMessage(`Autostarting recycled hunts is already ${room.settings.scavSettings.addRecycledHuntsToQueueAutomatically ? 'on' : 'off'}.`);
}
room.settings.scavSettings.addRecycledHuntsToQueueAutomatically =
!room.settings.scavSettings.addRecycledHuntsToQueueAutomatically;
@ -2632,7 +2636,7 @@ export const commands: Chat.ChatCommands = {
scavhelp: 'scavengershelp',
scavengershelp(target, room, user) {
if (!room || !getScavsRoom(room)) {
return this.errorReply("This command can only be used in the scavengers room.");
throw new Chat.ErrorMessage("This command can only be used in the scavengers room.");
}
if (!this.runBroadcast()) return false;

View File

@ -285,7 +285,7 @@ export const pages: Chat.PageTable = {
const format = toID(query.shift());
const season = toID(query.shift()) || `${data.current.season}`;
if (!data.badgeholders[season]) {
return this.errorReply(`Season ${season} not found.`);
throw new Chat.ErrorMessage(`Season ${season} not found.`);
}
this.title = `[Seasons]`;
let buf = '<div class="pad">';

View File

@ -118,7 +118,7 @@ export const commands: Chat.ChatCommands = {
}
const section = tours[sectionID];
if (!section) {
return this.errorReply(`Invalid section ID: "${sectionID}"`);
return this.popupReply(`Invalid section ID: "${sectionID}"`);
}
if (!isEdit && section.tours.find(f => toID(title) === f.id)) {
return this.popupReply(`A tour with that ID already exists. Please choose another.`);
@ -152,7 +152,7 @@ export const commands: Chat.ChatCommands = {
if (rawCredit || rawArtistName) { // if one exists, both should, as verified above
const artistUrl = (Chat.linkRegex.exec(rawCredit))?.[0];
if (!artistUrl) {
return this.errorReply(`Invalid artist credit URL.`);
return this.popupReply(`Invalid artist credit URL.`);
}
artistCredit = { url: artistUrl, name: rawArtistName.trim() };
}
@ -205,10 +205,10 @@ export const commands: Chat.ChatCommands = {
}
const section = tours[sectionID];
if (!section) {
return this.errorReply(`Invalid section ID: "${sectionID}". Valid IDs: ${Object.keys(tours).join(', ')}`);
throw new Chat.ErrorMessage(`Invalid section ID: "${sectionID}". Valid IDs: ${Object.keys(tours).join(', ')}`);
}
if (section.whitelist?.includes(targetID)) {
return this.errorReply(`That user is already whitelisted on that section.`);
throw new Chat.ErrorMessage(`That user is already whitelisted on that section.`);
}
if (!section.whitelist) section.whitelist = [];
section.whitelist.push(targetID);
@ -226,11 +226,11 @@ export const commands: Chat.ChatCommands = {
}
const section = tours[sectionID];
if (!section) {
return this.errorReply(`Invalid section ID: "${sectionID}". Valid IDs: ${Object.keys(tours).join(', ')}`);
throw new Chat.ErrorMessage(`Invalid section ID: "${sectionID}". Valid IDs: ${Object.keys(tours).join(', ')}`);
}
const idx = section.whitelist?.indexOf(targetID) ?? -1;
if (!section.whitelist || idx < 0) {
return this.errorReply(`${targetID} is not whitelisted in that section.`);
throw new Chat.ErrorMessage(`${targetID} is not whitelisted in that section.`);
}
section.whitelist.splice(idx, 1);
if (!section.whitelist.length) {

View File

@ -78,7 +78,7 @@ export const commands: Chat.ChatCommands = {
const reqData: Record<string, number> = {};
if (!reqs.length) {
return this.errorReply("At least one requirement for qualifying must be provided.");
throw new Chat.ErrorMessage("At least one requirement for qualifying must be provided.");
}
for (const req of reqs) {
let [k, v] = req.split('=');
@ -88,17 +88,17 @@ export const commands: Chat.ChatCommands = {
continue;
}
if (!['elo', 'gxe', 'coil'].includes(k)) {
return this.errorReply(`Invalid requirement type: ${k}. Must be 'coil', 'gxe', or 'elo'.`);
throw new Chat.ErrorMessage(`Invalid requirement type: ${k}. Must be 'coil', 'gxe', or 'elo'.`);
}
if (k === 'coil' && !reqs.some(x => toID(x).startsWith('b'))) {
throw new Chat.ErrorMessage("COIL reqs are specified, but you have not provided a B value (with the argument `b=num`)");
}
const val = Number(v);
if (isNaN(val) || val < 0) {
return this.errorReply(`Invalid value: ${v}`);
throw new Chat.ErrorMessage(`Invalid value: ${v}`);
}
if (reqData[k]) {
return this.errorReply(`Requirement type ${k} specified twice.`);
throw new Chat.ErrorMessage(`Requirement type ${k} specified twice.`);
}
reqData[k] = val;
}
@ -131,7 +131,7 @@ export const commands: Chat.ChatCommands = {
const format = toID(target);
const test = suspectTests.suspects[format];
if (!test) return this.errorReply(`There is no suspect test for '${target}'. Check spelling?`);
if (!test) throw new Chat.ErrorMessage(`There is no suspect test for '${target}'. Check spelling?`);
const [out, error] = await LoginServer.request('suspects/end', {
format,

View File

@ -458,7 +458,7 @@ export const commands: Chat.ChatCommands = {
const teamData = await TeamsHandler.get(teamid);
if (!teamData) return this.popupReply(`Team not found.`);
if (teamData.ownerid !== user.id && !user.can('rangeban')) {
return this.errorReply("You cannot delete teams you do not own.");
throw new Chat.ErrorMessage("You cannot delete teams you do not own.");
}
await TeamsHandler.delete(teamid);
this.popupReply(`Team ${teamid} deleted.`);
@ -598,11 +598,11 @@ export const pages: Chat.PageTable = {
const team = await TeamsHandler.get(teamid);
if (!team) {
this.title = `[Invalid Team]`;
return this.errorReply(`No team with the ID ${teamid} was found.`);
throw new Chat.ErrorMessage(`No team with the ID ${teamid} was found.`);
}
if (team?.private && user.id !== team.ownerid && password !== team.private) {
this.title = `[Private Team]`;
return this.errorReply(`That team is private.`);
throw new Chat.ErrorMessage(`That team is private.`);
}
this.title = `[Team] ${team.teamid}`;
if (user.id !== team.ownerid && team.views >= 0) {
@ -639,12 +639,12 @@ export const pages: Chat.PageTable = {
TeamsHandler.validateAccess(connection);
const teamID = toID(query.shift() || "");
if (!teamID.length) {
return this.errorReply(`Invalid team ID.`);
throw new Chat.ErrorMessage(`Invalid team ID.`);
}
this.title = `[Edit Team] ${teamID}`;
const data = await TeamsHandler.get(teamID);
if (!data) {
return this.errorReply(`Team ${teamID} not found.`);
throw new Chat.ErrorMessage(`Team ${teamID} not found.`);
}
let buf = `<div class="ladder pad"><h2>Edit team ${teamID}</h2>${refresh(this)}<hr />`;
// let [teamName, formatid, rawPrivacy, rawTeam] = Utils.splitFirst(target, ',', 4);
@ -702,15 +702,15 @@ export const pages: Chat.PageTable = {
const [rawOwner, rawFormat, rawPokemon, rawMoves, rawAbilities, rawGen] = query;
const owner = toID(rawOwner);
if (owner.length > 18) {
return this.errorReply(`Invalid owner name. Names must be under 18 characters long.`);
throw new Chat.ErrorMessage(`Invalid owner name. Names must be under 18 characters long.`);
}
const format = toID(rawFormat);
if (format && !Dex.formats.get(format).exists) {
return this.errorReply(`Format ${format} not found.`);
throw new Chat.ErrorMessage(`Format ${format} not found.`);
}
const gen = Number(rawGen);
if (rawGen && (isNaN(gen) || (gen < 1 || gen > Dex.gen))) {
return this.errorReply(`Invalid generation: '${rawGen}'`);
throw new Chat.ErrorMessage(`Invalid generation: '${rawGen}'`);
}
const pokemon = rawPokemon?.split(',').map(toID).filter(Boolean);
@ -764,7 +764,7 @@ export const pages: Chat.PageTable = {
queryStr += ` ORDER BY date DESC`;
break;
default:
return this.errorReply(`Invalid sort term '${sorter}'. Must be either 'views' or 'latest'.`);
throw new Chat.ErrorMessage(`Invalid sort term '${sorter}'. Must be either 'views' or 'latest'.`);
}
queryStr += ` LIMIT ${count}`;
let buf = `<div class="pad"><h2>Browse ${name} teams</h2>`;

View File

@ -470,7 +470,7 @@ export const commands: Chat.ChatCommands = {
async lastfm(target, room, user) {
this.checkChat();
if (!user.autoconfirmed) return this.errorReply(`You cannot use this command while not autoconfirmed.`);
if (!user.autoconfirmed) throw new Chat.ErrorMessage(`You cannot use this command while not autoconfirmed.`);
this.runBroadcast(true);
const targetUsername = this.splitUser(target).targetUsername || (user.named ? user.name : '');
const username = LastFM.getAccountName(targetUsername);
@ -484,7 +484,7 @@ export const commands: Chat.ChatCommands = {
async track(target, room, user) {
if (!target) return this.parse('/help track');
this.checkChat();
if (!user.autoconfirmed) return this.errorReply(`You cannot use this command while not autoconfirmed.`);
if (!user.autoconfirmed) throw new Chat.ErrorMessage(`You cannot use this command while not autoconfirmed.`);
const [track, artist] = this.splitOne(target);
if (!track) return this.parse('/help track');
this.runBroadcast(true);
@ -541,7 +541,7 @@ export const commands: Chat.ChatCommands = {
return this.parse('/help suggestrecommendation');
}
this.checkChat(target);
if (!user.autoconfirmed) return this.errorReply(`You cannot use this command while not autoconfirmed.`);
if (!user.autoconfirmed) throw new Chat.ErrorMessage(`You cannot use this command while not autoconfirmed.`);
const [artist, title, url, description, ...tags] = target.split('|').map(x => x.trim());
if (!(artist && title && url && description && tags?.length)) {
return this.parse(`/help suggestrecommendation`);

View File

@ -502,14 +502,12 @@ export const otdCommands: Chat.ChatCommands = {
const handler = selectHandler(this.message);
if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`);
if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`);
if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`);
if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`);
this.checkCan('mute', null, room);
if (handler.voting) {
return this.errorReply(
`The nomination for the ${handler.name} of the ${handler.timeLabel} nomination is already in progress.`
);
throw new Chat.ErrorMessage(`The nomination for the ${handler.name} of the ${handler.timeLabel} nomination is already in progress.`);
}
handler.startVote();
@ -523,15 +521,15 @@ export const otdCommands: Chat.ChatCommands = {
const handler = selectHandler(this.message);
if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`);
if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`);
if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`);
if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`);
this.checkCan('mute', null, room);
if (!handler.voting) {
return this.errorReply(`There is no ${handler.name} of the ${handler.timeLabel} nomination in progress.`);
throw new Chat.ErrorMessage(`There is no ${handler.name} of the ${handler.timeLabel} nomination in progress.`);
}
if (!handler.nominations.size) {
return this.errorReply(`Can't select the ${handler.name} of the ${handler.timeLabel} without nominations.`);
throw new Chat.ErrorMessage(`Can't select the ${handler.name} of the ${handler.timeLabel} without nominations.`);
}
handler.rollWinner();
@ -548,8 +546,8 @@ export const otdCommands: Chat.ChatCommands = {
const handler = selectHandler(this.message);
if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`);
if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`);
if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`);
if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`);
if (!toNominationId(target).length || target.length > 75) {
return this.sendReply(`'${target}' is not a valid ${handler.name.toLowerCase()} name.`);
@ -564,8 +562,8 @@ export const otdCommands: Chat.ChatCommands = {
const handler = selectHandler(this.message);
if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`);
if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`);
if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`);
if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`);
if (this.broadcasting) {
selectHandler(this.message).display();
@ -580,12 +578,12 @@ export const otdCommands: Chat.ChatCommands = {
const handler = selectHandler(this.message);
if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`);
if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`);
if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`);
if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`);
this.checkCan('mute', null, room);
const userid = toID(target);
if (!userid) return this.errorReply(`'${target}' is not a valid username.`);
if (!userid) throw new Chat.ErrorMessage(`'${target}' is not a valid username.`);
if (handler.removeNomination(userid)) {
this.privateModAction(`${user.name} removed ${target}'s nomination for the ${handler.name} of the ${handler.timeLabel}.`);
@ -622,8 +620,8 @@ export const otdCommands: Chat.ChatCommands = {
const handler = selectHandler(this.message);
if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`);
if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`);
if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`);
if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`);
this.checkCan('declare', null, room);
if (!toNominationId(target).length || target.length > 50) {
@ -643,12 +641,12 @@ export const otdCommands: Chat.ChatCommands = {
const handler = selectHandler(this.message);
if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`);
if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`);
if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`);
if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`);
this.checkCan('mute', null, room);
if (!(handler.voting && handler.timer)) {
return this.errorReply(`There is no ${handler.name} of the ${handler.timeLabel} nomination to disable the timer for.`);
throw new Chat.ErrorMessage(`There is no ${handler.name} of the ${handler.timeLabel} nomination to disable the timer for.`);
}
clearTimeout(handler.timer);
@ -663,8 +661,8 @@ export const otdCommands: Chat.ChatCommands = {
const handler = selectHandler(this.message);
if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`);
if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`);
if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`);
if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`);
this.checkCan('mute', null, room);
const params = target.split(target.includes('|') ? '|' : ',').map(param => param.trim());
@ -673,13 +671,13 @@ export const otdCommands: Chat.ChatCommands = {
for (const param of params) {
let [key, ...values] = param.split(':');
if (!key || !values.length) return this.errorReply(`Syntax error in '${param}'`);
if (!key || !values.length) throw new Chat.ErrorMessage(`Syntax error in '${param}'`);
key = key.trim();
const value = values.join(':').trim();
if (!handler.keys.includes(key)) {
return this.errorReply(`Invalid key: '${key}'. Valid keys: ${handler.keys.join(', ')}`);
throw new Chat.ErrorMessage(`Invalid key: '${key}'. Valid keys: ${handler.keys.join(', ')}`);
}
switch (key) {
@ -690,38 +688,38 @@ export const otdCommands: Chat.ChatCommands = {
case 'book':
case 'author':
case 'athlete':
if (!toNominationId(value) || value.length > 50) return this.errorReply(`Please enter a valid ${key} name.`);
if (!toNominationId(value) || value.length > 50) throw new Chat.ErrorMessage(`Please enter a valid ${key} name.`);
break;
case 'quote':
case 'tagline':
case 'match':
case 'event':
case 'videogame':
if (!value.length || value.length > 150) return this.errorReply(`Please enter a valid ${key}.`);
if (!value.length || value.length > 150) throw new Chat.ErrorMessage(`Please enter a valid ${key}.`);
break;
case 'sport':
case 'team':
case 'song':
case 'country':
if (!value.length || value.length > 50) return this.errorReply(`Please enter a valid ${key} name.`);
if (!value.length || value.length > 50) throw new Chat.ErrorMessage(`Please enter a valid ${key} name.`);
break;
case 'link':
case 'image':
if (!/https?:\/\//.test(value)) {
return this.errorReply(`Please enter a valid URL for the ${key} (starting with http:// or https://)`);
throw new Chat.ErrorMessage(`Please enter a valid URL for the ${key} (starting with http:// or https://)`);
}
if (value.length > 200) return this.errorReply("URL too long.");
if (value.length > 200) throw new Chat.ErrorMessage("URL too long.");
break;
case 'age':
const num = parseInt(value);
// let's assume someone isn't over 100 years old? Maybe we should for the memes
// but i doubt there's any legit athlete over 100.
if (isNaN(num) || num < 1 || num > 100) return this.errorReply('Please enter a valid number as an age');
if (isNaN(num) || num < 1 || num > 100) throw new Chat.ErrorMessage('Please enter a valid number as an age');
break;
default:
// another custom key w/o validation
if (!toNominationId(value)) {
return this.errorReply(`No value provided for key ${key}.`);
throw new Chat.ErrorMessage(`No value provided for key ${key}.`);
}
break;
}
@ -772,8 +770,8 @@ export const otdCommands: Chat.ChatCommands = {
const handler = selectHandler(this.message);
if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`);
if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`);
if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`);
if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`);
return this.parse(`/join view-${handler.id}`);
},
@ -784,12 +782,12 @@ export const otdCommands: Chat.ChatCommands = {
if (!this.runBroadcast()) return false;
const handler = selectHandler(this.message);
if (!handler.room) return this.errorReply(`The room for this -otd doesn't exist.`);
if (!handler.room) throw new Chat.ErrorMessage(`The room for this -otd doesn't exist.`);
if (room !== handler.room) return this.errorReply(`This command can only be used in ${handler.room.title}.`);
if (room !== handler.room) throw new Chat.ErrorMessage(`This command can only be used in ${handler.room.title}.`);
const text = await handler.generateWinnerDisplay();
if (!text) return this.errorReply("There is no winner yet.");
if (!text) throw new Chat.ErrorMessage("There is no winner yet.");
this.sendReplyBox(text);
},
};
@ -800,11 +798,11 @@ export const commands: Chat.ChatCommands = {
create(target, room, user) {
room = this.requireRoom();
if (room.settings.isPrivate) {
return this.errorReply(`This command is only available in public rooms`);
throw new Chat.ErrorMessage(`This command is only available in public rooms`);
}
const count = [...otds.values()].filter(otd => otd.room.roomid === room.roomid).length;
if (count > 3) {
return this.errorReply(`This room already has 3+ -otd's.`);
throw new Chat.ErrorMessage(`This room already has 3+ -otd's.`);
}
this.checkCan('rangeban');
@ -813,17 +811,19 @@ export const commands: Chat.ChatCommands = {
}
const [title, time, ...keyLabels] = target.split(',').map(i => i.trim());
if (!toID(title)) {
return this.errorReply(`Invalid title.`);
throw new Chat.ErrorMessage(`Invalid title.`);
}
const timeLabel = toID(time);
if (!['week', 'day'].includes(timeLabel)) {
return this.errorReply("Invalid time label - use 'week' or 'month'");
throw new Chat.ErrorMessage("Invalid time label - use 'week' or 'month'");
}
const id = `${title.charAt(0)}ot${timeLabel.charAt(0)}`;
const existing = otds.get(id);
if (existing) {
this.errorReply(`That -OTD already exists (${existing.name} of the ${existing.timeLabel}, in ${existing.room.title})`);
return this.errorReply(`Try picking a new title.`);
throw new Chat.ErrorMessage([
`That -OTD already exists (${existing.name} of the ${existing.timeLabel}, in ${existing.room.title})`,
`Try picking a new title.`,
]);
}
const titleIdx = keyLabels.map(toID).indexOf(toID(title));
if (titleIdx > -1) {
@ -833,16 +833,16 @@ export const commands: Chat.ChatCommands = {
const filteredKeys = keyLabels.map(toNominationId).filter(Boolean);
if (!filteredKeys.length) {
return this.errorReply(`No valid key labels given.`);
throw new Chat.ErrorMessage(`No valid key labels given.`);
}
if (new Set(filteredKeys).size !== keyLabels.length) {
return this.errorReply(`Invalid keys in set - do not use duplicate key labels.`);
throw new Chat.ErrorMessage(`Invalid keys in set - do not use duplicate key labels.`);
}
if (filteredKeys.length < 3) {
return this.errorReply(`Specify at least 3 key labels.`);
throw new Chat.ErrorMessage(`Specify at least 3 key labels.`);
}
if (filteredKeys.some(k => k.length < 3 || k.length > 50)) {
return this.errorReply(`All labels must be more than 3 characters and less than 50 characters long.`);
throw new Chat.ErrorMessage(`All labels must be more than 3 characters and less than 50 characters long.`);
}
const otd = OtdHandler.create(room, {
keyLabels, keys: filteredKeys, title, timeLabel, roomid: room.roomid,
@ -860,11 +860,11 @@ export const commands: Chat.ChatCommands = {
}
const otd = otds.get(otdId);
if (!otd) {
return this.errorReply(`OTD ${otd} not found.`);
throw new Chat.ErrorMessage(`OTD ${otd} not found.`);
}
const targetRoom = Rooms.get(roomid);
if (!targetRoom) {
return this.errorReply(`Room ${roomid} not found.`);
throw new Chat.ErrorMessage(`Room ${roomid} not found.`);
}
const oldRoom = otd.settings.roomid.slice();
otd.settings.roomid = targetRoom.roomid;
@ -882,7 +882,7 @@ export const commands: Chat.ChatCommands = {
return this.parse(`/help otd`);
}
const otd = otds.get(target);
if (!otd) return this.errorReply(`OTD ${target} not found.`);
if (!otd) throw new Chat.ErrorMessage(`OTD ${target} not found.`);
otd.destroy();
this.globalModlog(`OTD DELETE`, null, target);
this.privateGlobalModAction(`${user.name} deleted the OTD ${otd.name} of the ${otd.timeLabel}`);

View File

@ -1520,11 +1520,11 @@ const triviaCommands: Chat.ChatCommands = {
this.checkCan('show', null, room);
this.checkChat();
if (room.game) {
return this.errorReply(this.tr`There is already a game of ${room.game.title} in progress.`);
throw new Chat.ErrorMessage(this.tr`There is already a game of ${room.game.title} in progress.`);
}
const targets = (target ? target.split(',') : []);
if (targets.length < 3) return this.errorReply("Usage: /trivia new [mode], [category], [length]");
if (targets.length < 3) throw new Chat.ErrorMessage("Usage: /trivia new [mode], [category], [length]");
let mode: string = toID(targets[0]);
if (['triforce', 'tri'].includes(mode)) mode = 'triumvirate';
@ -1533,7 +1533,7 @@ const triviaCommands: Chat.ChatCommands = {
const acceptableModes = Object.keys(MODES).filter(curMode => curMode !== 'first');
mode = Utils.shuffle(acceptableModes)[0];
}
if (!MODES[mode]) return this.errorReply(this.tr`"${mode}" is an invalid mode.`);
if (!MODES[mode]) throw new Chat.ErrorMessage(this.tr`"${mode}" is an invalid mode.`);
let categories: ID[] | 'random' = targets[1]
.split('+')
@ -1551,23 +1551,23 @@ const triviaCommands: Chat.ChatCommands = {
let length: ID | number = toID(targets[2]);
if (!LENGTHS[length]) {
length = parseInt(length);
if (isNaN(length) || length < 1) return this.errorReply(this.tr`"${length}" is an invalid game length.`);
if (isNaN(length) || length < 1) throw new Chat.ErrorMessage(this.tr`"${length}" is an invalid game length.`);
}
// Assume that infinite mode will last for at least 75 points
const questionsNecessary = typeof length === 'string' ? (LENGTHS[length].cap || 75) / 5 : length;
if (questions.length < questionsNecessary) {
if (categories === 'random') {
return this.errorReply(
throw new Chat.ErrorMessage(
this.tr`There are not enough questions in the randomly chosen category to finish a trivia game.`
);
}
if (categories.length === 1 && categories[0] === 'all') {
return this.errorReply(
throw new Chat.ErrorMessage(
this.tr`There are not enough questions in the trivia database to finish a trivia game.`
);
}
return this.errorReply(
throw new Chat.ErrorMessage(
this.tr`There are not enough questions under the specified categories to finish a trivia game.`
);
}
@ -1642,7 +1642,7 @@ const triviaCommands: Chat.ChatCommands = {
}
const answer = toID(target);
if (!answer) return this.errorReply(this.tr`No valid answer was entered.`);
if (!answer) throw new Chat.ErrorMessage(this.tr`No valid answer was entered.`);
if (room.game?.gameid === 'trivia' && !Object.keys(game.playerTable).includes(user.id)) {
game.addTriviaPlayer(user);
@ -1683,7 +1683,7 @@ const triviaCommands: Chat.ChatCommands = {
const game = getTriviaGame(room);
if (game.game.length !== 'infinite' && !user.can('editroom', null, room)) {
return this.errorReply(
throw new Chat.ErrorMessage(
this.tr`Only Room Owners and higher can force a Trivia game to end with winners in a non-infinite length.`
);
}
@ -1699,7 +1699,7 @@ const triviaCommands: Chat.ChatCommands = {
const game = getTriviaGame(room);
const targetUser = this.getUserOrSelf(target);
if (!targetUser) return this.errorReply(this.tr`User ${target} does not exist.`);
if (!targetUser) throw new Chat.ErrorMessage(this.tr`User ${target} does not exist.`);
let buffer = `${game.isPaused ? this.tr`There is a paused trivia game` : this.tr`There is a trivia game in progress`}, ` +
this.tr`and it is in its ${game.phase} phase.` + `<br />` +
this.tr`Mode: ${game.game.mode} | Category: ${game.game.category} | Cap: ${game.getDisplayableCap()}`;
@ -1710,7 +1710,7 @@ const triviaCommands: Chat.ChatCommands = {
buffer += `<br />${this.tr`Current score: ${player.points} | Correct Answers: ${player.correctAnswers}`}`;
}
} else if (targetUser.id !== user.id) {
return this.errorReply(this.tr`User ${targetUser.name} is not a player in the current trivia game.`);
throw new Chat.ErrorMessage(this.tr`User ${targetUser.name} is not a player in the current trivia game.`);
}
buffer += `<br />${this.tr`Players: ${game.formatPlayerList({ max: null, requirePoints: false })}`}`;
@ -1881,7 +1881,7 @@ const triviaCommands: Chat.ChatCommands = {
const indicesLen = indices.length;
if (!indicesLen) {
return this.errorReply(
throw new Chat.ErrorMessage(
this.tr`'${target}' is not a valid set of submission index numbers.\n` +
this.tr`View /trivia review and /trivia help for more information.`
);
@ -1899,7 +1899,7 @@ const triviaCommands: Chat.ChatCommands = {
return this.privateModAction(`${user.name} ${message} from the submission database.`);
}
this.errorReply(this.tr`'${target}' is an invalid argument. View /trivia help questions for more information.`);
throw new Chat.ErrorMessage(this.tr`'${target}' is an invalid argument. View /trivia help questions for more information.`);
},
accepthelp: [`/trivia accept [index1], [index2], ... [indexn] OR all - Add questions from the submission database to the question database using their index numbers or ranges of them. Requires: @ # ~`],
rejecthelp: [`/trivia reject [index1], [index2], ... [indexn] OR all - Remove questions from the submission database using their index numbers or ranges of them. Requires: @ # ~`],
@ -1914,7 +1914,7 @@ const triviaCommands: Chat.ChatCommands = {
const question = Utils.escapeHTML(target).trim();
if (!question) {
return this.errorReply(this.tr`'${target}' is not a valid argument. View /trivia help questions for more information.`);
throw new Chat.ErrorMessage(this.tr`'${target}' is not a valid argument. View /trivia help questions for more information.`);
}
const { category } = await database.ensureQuestionExists(question);
@ -2112,7 +2112,7 @@ const triviaCommands: Chat.ChatCommands = {
const category = CATEGORY_ALIASES[target] || target;
if (category === 'random') return false;
if (!ALL_CATEGORIES[category]) {
return this.errorReply(this.tr`'${target}' is not a valid category. View /help trivia for more information.`);
throw new Chat.ErrorMessage(this.tr`'${target}' is not a valid category. View /help trivia for more information.`);
}
const list = await database.getQuestions([category], Number.MAX_SAFE_INTEGER, { order: 'oldestfirst' });
@ -2158,7 +2158,7 @@ const triviaCommands: Chat.ChatCommands = {
type = target;
} else {
[type, ...query] = target.split(',');
if (!target.includes(',')) return this.errorReply(this.tr`No valid search arguments entered.`);
if (!target.includes(',')) throw new Chat.ErrorMessage(this.tr`No valid search arguments entered.`);
}
type = toID(type);
@ -2176,7 +2176,7 @@ const triviaCommands: Chat.ChatCommands = {
let queryString = query.join(',');
if (cmd !== 'doublespacesearch') queryString = queryString.trim();
if (!queryString) return this.errorReply(this.tr`No valid search query was entered.`);
if (!queryString) throw new Chat.ErrorMessage(this.tr`No valid search query was entered.`);
const results = await database.searchQuestions(queryString, options);
if (!results.length) return this.sendReply(this.tr`No results found under the ${type} list.`);
@ -2305,7 +2305,7 @@ const triviaCommands: Chat.ChatCommands = {
if (cmd.includes('score')) leaderboard = 'nonAlltime';
const ladder = (await cachedLadder.get(leaderboard))?.ladder;
if (!ladder?.length) return this.errorReply(this.tr`No Trivia games have been played yet.`);
if (!ladder?.length) throw new Chat.ErrorMessage(this.tr`No Trivia games have been played yet.`);
let buffer = "|raw|<div class=\"ladder\" style=\"overflow-y: scroll; max-height: 300px;\"><table>" +
`<tr><th>${this.tr`Rank`}</th><th>${this.tr`User`}</th><th>${this.tr`Leaderboard score`}</th><th>${this.tr`Total game points`}</th><th>${this.tr`Total correct answers`}</th></tr>`;
@ -2340,9 +2340,10 @@ const triviaCommands: Chat.ChatCommands = {
if (user.lastCommand !== '/trivia resetcycleleaderboard') {
user.lastCommand = '/trivia resetcycleleaderboard';
this.errorReply(`Are you sure you want to reset the Trivia cycle-specific leaderboard? This action is IRREVERSIBLE.`);
this.errorReply(`To confirm, retype the command.`);
return;
throw new Chat.ErrorMessage([
`Are you sure you want to reset the Trivia cycle-specific leaderboard? This action is IRREVERSIBLE.`,
`To confirm, retype the command.`,
]);
}
user.lastCommand = '';
@ -2364,10 +2365,10 @@ const triviaCommands: Chat.ChatCommands = {
this.modlog(`TRIVIA CATEGORY CLEAR`, null, SPECIAL_CATEGORIES[category]);
return this.privateModAction(room.tr`${user.name} removed all questions of category '${category}'.`);
} else {
return this.errorReply(this.tr`You cannot clear the category '${ALL_CATEGORIES[category]}'.`);
throw new Chat.ErrorMessage(this.tr`You cannot clear the category '${ALL_CATEGORIES[category]}'.`);
}
} else {
return this.errorReply(this.tr`'${category}' is an invalid category.`);
throw new Chat.ErrorMessage(this.tr`'${category}' is an invalid category.`);
}
},
clearqshelp: [`/trivia clearqs [category] - Remove all questions of the given category. Requires: # ~`],
@ -2416,7 +2417,7 @@ const triviaCommands: Chat.ChatCommands = {
const [userid, pointString] = this.splitOne(target).map(toID);
const points = parseInt(pointString);
if (isNaN(points)) return this.errorReply(`You must specify a number of points to add/remove.`);
if (isNaN(points)) throw new Chat.ErrorMessage(`You must specify a number of points to add/remove.`);
const isRemoval = cmd === 'removepoints';
const change = { score: isRemoval ? -points : points, totalPoints: 0, totalCorrectAnswers: 0 };
@ -2447,7 +2448,7 @@ const triviaCommands: Chat.ChatCommands = {
const userid = toID(target);
if (!userid) return this.parse('/help trivia removeleaderboardentry');
if (!(await database.getLeaderboardEntry(userid, 'alltime'))) {
return this.errorReply(`The user '${userid}' has no Trivia leaderboard entry.`);
throw new Chat.ErrorMessage(`The user '${userid}' has no Trivia leaderboard entry.`);
}
const command = `/trivia removeleaderboardentry ${userid}`;
@ -2581,7 +2582,7 @@ const mastermindCommands: Chat.ChatCommands = {
const finalists = parseInt(target);
if (isNaN(finalists) || finalists < 2) {
return this.errorReply(this.tr`You must specify a number that is at least 2 for finalists.`);
throw new Chat.ErrorMessage(this.tr`You must specify a number that is at least 2 for finalists.`);
}
room.game = new Mastermind(room, finalists);
@ -2601,17 +2602,17 @@ const mastermindCommands: Chat.ChatCommands = {
category = CATEGORY_ALIASES[category] || category;
if (!(category in ALL_CATEGORIES)) {
return this.errorReply(this.tr`${category} is not a valid category.`);
throw new Chat.ErrorMessage(this.tr`${category} is not a valid category.`);
}
const categoryName = ALL_CATEGORIES[category];
const timeout = parseInt(timeoutString);
if (isNaN(timeout) || timeout < 1 || (timeout * 1000) > Chat.MAX_TIMEOUT_DURATION) {
return this.errorReply(this.tr`You must specify a round length of at least 1 second.`);
throw new Chat.ErrorMessage(this.tr`You must specify a round length of at least 1 second.`);
}
const questions = await getQuestions([category], 'random');
if (!questions.length) {
return this.errorReply(this.tr`There are no questions in the ${categoryName} category.`);
throw new Chat.ErrorMessage(this.tr`There are no questions in the ${categoryName} category.`);
}
game.startRound(player, category, questions, timeout);
@ -2629,7 +2630,7 @@ const mastermindCommands: Chat.ChatCommands = {
const timeout = parseInt(target);
if (isNaN(timeout) || timeout < 1 || (timeout * 1000) > Chat.MAX_TIMEOUT_DURATION) {
return this.errorReply(this.tr`You must specify a length of at least 1 second.`);
throw new Chat.ErrorMessage(this.tr`You must specify a length of at least 1 second.`);
}
await game.startFinals(timeout);
@ -2652,9 +2653,9 @@ const mastermindCommands: Chat.ChatCommands = {
pass(target, room, user) {
room = this.requireRoom();
const round = getMastermindGame(room).currentRound;
if (!round) return this.errorReply(this.tr`No round of Mastermind is currently being played.`);
if (!round) throw new Chat.ErrorMessage(this.tr`No round of Mastermind is currently being played.`);
if (!(user.id in round.playerTable)) {
return this.errorReply(this.tr`You are not a player in the current round of Mastermind.`);
throw new Chat.ErrorMessage(this.tr`You are not a player in the current round of Mastermind.`);
}
round.pass();
},

View File

@ -147,7 +147,7 @@ export const commands: Chat.ChatCommands = {
this.checkCan('lock');
const targets = target.split(',').map(toID).filter(Boolean);
if (!targets.length) {
return this.errorReply(`Specify at least one term.`);
throw new Chat.ErrorMessage(`Specify at least one term.`);
}
for (const [i, arg] of targets.entries()) {
if (nameList.has(arg)) {
@ -164,7 +164,7 @@ export const commands: Chat.ChatCommands = {
}
if (!targets.length) {
// fuck you too, "mia added 0 term to the usersearch name list"
return this.errorReply(`No terms could be added.`);
throw new Chat.ErrorMessage(`No terms could be added.`);
}
const count = Chat.count(targets, 'terms');
Rooms.get('staff')?.addByUser(
@ -180,7 +180,7 @@ export const commands: Chat.ChatCommands = {
this.checkCan('lock');
const targets = target.split(',').map(toID).filter(Boolean);
if (!targets.length) {
return this.errorReply(`Specify at least one term.`);
throw new Chat.ErrorMessage(`Specify at least one term.`);
}
for (const [i, arg] of targets.entries()) {
if (!nameList.has(arg)) {
@ -191,7 +191,7 @@ export const commands: Chat.ChatCommands = {
nameList.delete(arg);
}
if (!targets.length) {
return this.errorReply(`No terms could be removed.`);
throw new Chat.ErrorMessage(`No terms could be removed.`);
}
const count = Chat.count(targets, 'terms');
Rooms.get('staff')?.addByUser(

View File

@ -948,19 +948,21 @@ export const commands: Chat.ChatCommands = {
param => param.trim()
);
if (!(giver && amountStr && summary && deposit && lookfor)) {
return this.errorReply("Invalid arguments specified - /gts start giver | amount | summary | deposit | lookfor");
throw new Chat.ErrorMessage("Invalid arguments specified - /gts start giver | amount | summary | deposit | lookfor");
}
const amount = parseInt(amountStr);
if (!amount || amount < 20 || amount > 100) {
return this.errorReply("Please enter a valid amount. For a GTS giveaway, you need to give away at least 20 mons, and no more than 100.");
throw new Chat.ErrorMessage("Please enter a valid amount. For a GTS giveaway, you need to give away at least 20 mons, and no more than 100.");
}
const targetUser = Users.get(giver);
if (!targetUser?.connected) return this.errorReply(`User '${giver}' is not online.`);
if (!targetUser?.connected) throw new Chat.ErrorMessage(`User '${giver}' is not online.`);
this.checkCan('warn', null, room);
if (!targetUser.autoconfirmed) {
return this.errorReply(`User '${targetUser.name}' needs to be autoconfirmed to host a giveaway.`);
throw new Chat.ErrorMessage(`User '${targetUser.name}' needs to be autoconfirmed to host a giveaway.`);
}
if (Giveaway.checkBanned(room, targetUser)) {
throw new Chat.ErrorMessage(`User '${targetUser.name}' is giveaway banned.`);
}
if (Giveaway.checkBanned(room, targetUser)) return this.errorReply(`User '${targetUser.name}' is giveaway banned.`);
room.subGame = new GTS(room, targetUser, amount, summary, deposit, lookfor);
@ -981,8 +983,8 @@ export const commands: Chat.ChatCommands = {
return this.sendReply(output);
}
const newamount = parseInt(target);
if (isNaN(newamount)) return this.errorReply("Please enter a valid amount.");
if (newamount > game.left) return this.errorReply("The new amount must be lower than the old amount.");
if (isNaN(newamount)) throw new Chat.ErrorMessage("Please enter a valid amount.");
if (newamount > game.left) throw new Chat.ErrorMessage("The new amount must be lower than the old amount.");
if (newamount < game.left - 1) {
this.modlog(`GTS GIVEAWAY`, null, `set from ${game.left} to ${newamount} left`);
}
@ -993,10 +995,10 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom('wifi' as RoomID);
const game = this.requireGame(GTS, true);
if (!user.can('warn', null, room) && user !== game.giver) {
return this.errorReply("Only the host or a staff member can update GTS giveaways.");
throw new Chat.ErrorMessage("Only the host or a staff member can update GTS giveaways.");
}
if (!target || target.length > 12) return this.errorReply("Please enter a valid IGN.");
if (!target || target.length > 12) throw new Chat.ErrorMessage("Please enter a valid IGN.");
game.updateSent(target);
},
@ -1004,9 +1006,9 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom('wifi' as RoomID);
const game = this.requireGame(GTS, true);
if (!user.can('warn', null, room) && user !== game.giver) {
return this.errorReply("Only the host or a staff member can update GTS giveaways.");
throw new Chat.ErrorMessage("Only the host or a staff member can update GTS giveaways.");
}
if (game.noDeposits) return this.errorReply("The GTS giveaway was already set to not accept deposits.");
if (game.noDeposits) throw new Chat.ErrorMessage("The GTS giveaway was already set to not accept deposits.");
game.stopDeposits();
},
@ -1016,7 +1018,7 @@ export const commands: Chat.ChatCommands = {
this.checkCan('warn', null, room);
if (target && target.length > 300) {
return this.errorReply("The reason is too long. It cannot exceed 300 characters.");
throw new Chat.ErrorMessage("The reason is too long. It cannot exceed 300 characters.");
}
const amount = game.end(true);
if (target) target = `: ${target}`;
@ -1095,10 +1097,10 @@ export const commands: Chat.ChatCommands = {
const { targetUser, rest: reason } = this.requireUser(target, { allowOffline: true });
if (reason.length > 300) {
return this.errorReply("The reason is too long. It cannot exceed 300 characters.");
throw new Chat.ErrorMessage("The reason is too long. It cannot exceed 300 characters.");
}
if (Punishments.hasRoomPunishType(room, targetUser.name, 'GIVEAWAYBAN')) {
return this.errorReply(`User '${targetUser.name}' is already giveawaybanned.`);
throw new Chat.ErrorMessage(`User '${targetUser.name}' is already giveawaybanned.`);
}
const duration = cmd === 'monthban' ? 30 * DAY : cmd === 'permaban' ? 3650 * DAY : 7 * DAY;
@ -1119,7 +1121,7 @@ export const commands: Chat.ChatCommands = {
const { targetUser } = this.requireUser(target, { allowOffline: true });
if (!Giveaway.checkBanned(room, targetUser)) {
return this.errorReply(`User '${targetUser.name}' isn't banned from entering giveaways.`);
throw new Chat.ErrorMessage(`User '${targetUser.name}' isn't banned from entering giveaways.`);
}
Giveaway.unban(room, targetUser);
@ -1183,7 +1185,7 @@ export const commands: Chat.ChatCommands = {
if (user.id !== game.host.id) this.checkCan('warn', null, room);
if (target && target.length > 300) {
return this.errorReply("The reason is too long. It cannot exceed 300 characters.");
throw new Chat.ErrorMessage("The reason is too long. It cannot exceed 300 characters.");
}
game.end(true);
this.modlog('GIVEAWAY END', null, target);
@ -1396,7 +1398,7 @@ export const commands: Chat.ChatCommands = {
if (cmd.includes('un')) {
const idx = wifiData.whitelist.indexOf(targetId);
if (idx < 0) {
return this.errorReply(`'${targetId}' is not whitelisted.`);
throw new Chat.ErrorMessage(`'${targetId}' is not whitelisted.`);
}
wifiData.whitelist.splice(idx, 1);
this.privateModAction(`${user.name} removed '${targetId}' from the giveaway whitelist.`);
@ -1404,7 +1406,7 @@ export const commands: Chat.ChatCommands = {
saveData();
} else {
if (wifiData.whitelist.includes(targetId)) {
return this.errorReply(`'${targetId}' is already whitelisted.`);
throw new Chat.ErrorMessage(`'${targetId}' is already whitelisted.`);
}
wifiData.whitelist.push(targetId);
this.privateModAction(`${user.name} added ${targetId} to the giveaway whitelist.`);

View File

@ -494,7 +494,7 @@ export function destroy() {
export const commands: Chat.ChatCommands = {
async randchannel(target, room, user) {
room = this.requireRoom('internetexplorers' as RoomID);
if (Object.keys(YouTube.data.channels).length < 1) return this.errorReply(`No channels in the database.`);
if (Object.keys(YouTube.data.channels).length < 1) throw new Chat.ErrorMessage(`No channels in the database.`);
target = toID(target);
this.runBroadcast();
const data = await YouTube.randChannel(target);
@ -508,7 +508,7 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom('internetexplorers' as RoomID);
this.checkCan('mute', null, room);
const [id, name] = target.split(',').map(t => t.trim());
if (!id) return this.errorReply('Specify a channel ID.');
if (!id) throw new Chat.ErrorMessage('Specify a channel ID.');
await YouTube.getChannelData(id, name);
this.modlog('ADDCHANNEL', null, `${id} ${name ? `username: ${name}` : ''}`);
return this.privateModAction(
@ -521,7 +521,7 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom('internetexplorers' as RoomID);
this.checkCan('mute', null, room);
const id = YouTube.channelSearch(target);
if (!id) return this.errorReply(`Channel with ID or name ${target} not found.`);
if (!id) throw new Chat.ErrorMessage(`Channel with ID or name ${target} not found.`);
delete YouTube.data.channels[id];
YouTube.save();
this.privateModAction(`${user.name} deleted channel with ID or name ${target}.`);
@ -532,7 +532,7 @@ export const commands: Chat.ChatCommands = {
async channel(target, room, user) {
room = this.requireRoom('internetexplorers' as RoomID);
const channel = YouTube.channelSearch(target);
if (!channel) return this.errorReply(`No channels with ID or name ${target} found.`);
if (!channel) throw new Chat.ErrorMessage(`No channels with ID or name ${target} found.`);
const data = await YouTube.generateChannelDisplay(channel);
this.runBroadcast();
return this.sendReply(`|html|${data}`);
@ -565,7 +565,7 @@ export const commands: Chat.ChatCommands = {
this.checkCan('mute', null, room);
const [channel, name] = target.split(',');
const id = YouTube.channelSearch(channel);
if (!id) return this.errorReply(`Channel ${channel} is not in the database.`);
if (!id) throw new Chat.ErrorMessage(`Channel ${channel} is not in the database.`);
YouTube.data.channels[id].username = name;
this.modlog(`UPDATECHANNEL`, null, name);
this.privateModAction(`${user.name} updated channel ${id}'s username to ${name}.`);
@ -576,19 +576,19 @@ export const commands: Chat.ChatCommands = {
room = this.requireRoom('internetexplorers' as RoomID);
this.checkCan('declare', null, room);
if (!target) {
if (!YouTube.interval) return this.errorReply(`The YouTube plugin is not currently running an interval.`);
if (!YouTube.interval) throw new Chat.ErrorMessage(`The YouTube plugin is not currently running an interval.`);
return this.sendReply(`Interval is currently set to ${Chat.toDurationString(YouTube.intervalTime * 60 * 1000)}.`);
}
if (this.meansNo(target)) {
if (!YouTube.interval) return this.errorReply(`The interval is not currently running`);
if (!YouTube.interval) throw new Chat.ErrorMessage(`The interval is not currently running`);
clearInterval(YouTube.interval);
delete YouTube.data.intervalTime;
YouTube.save();
this.privateModAction(`${user.name} turned off the YouTube interval`);
return this.modlog(`YOUTUBE INTERVAL`, null, 'OFF');
}
if (Object.keys(channelData).length < 1) return this.errorReply(`No channels in the database.`);
if (isNaN(parseInt(target))) return this.errorReply(`Specify a number (in minutes) for the interval.`);
if (Object.keys(channelData).length < 1) throw new Chat.ErrorMessage(`No channels in the database.`);
if (isNaN(parseInt(target))) throw new Chat.ErrorMessage(`Specify a number (in minutes) for the interval.`);
YouTube.runInterval(target);
YouTube.save();
this.privateModAction(`${user.name} set a randchannel interval to ${target} minutes`);
@ -600,7 +600,7 @@ export const commands: Chat.ChatCommands = {
const categoryID = toID(target);
if (!categoryID) return this.parse(`/help youtube`);
if (YouTube.data.categories.map(toID).includes(categoryID)) {
return this.errorReply(`This category is already added. To change it, remove it and re-add it.`);
throw new Chat.ErrorMessage(`This category is already added. To change it, remove it and re-add it.`);
}
YouTube.data.categories.push(target);
this.modlog(`YOUTUBE ADDCATEGORY`, null, target);
@ -614,7 +614,7 @@ export const commands: Chat.ChatCommands = {
if (!categoryID) return this.parse(`/help youtube`);
const index = YouTube.data.categories.indexOf(target);
if (index < 0) {
return this.errorReply(`${target} is not a valid category.`);
throw new Chat.ErrorMessage(`${target} is not a valid category.`);
}
for (const id in YouTube.data.channels) {
const channel = YouTube.data.channels[id];
@ -633,10 +633,10 @@ export const commands: Chat.ChatCommands = {
return this.parse('/help youtube');
}
if (!YouTube.data.categories.includes(category)) {
return this.errorReply(`Invalid category.`);
throw new Chat.ErrorMessage(`Invalid category.`);
}
const name = YouTube.channelSearch(id);
if (!name) return this.errorReply(`Invalid channel.`);
if (!name) throw new Chat.ErrorMessage(`Invalid channel.`);
const channel = YouTube.data.channels[name];
YouTube.data.channels[name].category = category;
YouTube.save();
@ -651,10 +651,10 @@ export const commands: Chat.ChatCommands = {
return this.parse('/help youtube');
}
const name = YouTube.channelSearch(target);
if (!name) return this.errorReply(`Invalid channel.`);
if (!name) throw new Chat.ErrorMessage(`Invalid channel.`);
const channel = YouTube.data.channels[name];
const category = channel.category;
if (!category) return this.errorReply(`That channel does not have a category.`);
if (!category) throw new Chat.ErrorMessage(`That channel does not have a category.`);
delete channel.category;
YouTube.save();
this.modlog(`YOUTUBE DECATEGORIZE`, null, target);
@ -681,11 +681,11 @@ export const commands: Chat.ChatCommands = {
async create(target, room, user) {
room = this.requireRoom();
if (!GROUPWATCH_ROOMS.includes(room.roomid)) {
return this.errorReply(`This room is not allowed to use the groupwatch function.`);
throw new Chat.ErrorMessage(`This room is not allowed to use the groupwatch function.`);
}
this.checkCan('mute', null, room);
const [url, title] = Utils.splitFirst(target, ',').map(p => p.trim());
if (!url || !title) return this.errorReply(`You must specify a video to watch and a title for the group watch.`);
if (!url || !title) throw new Chat.ErrorMessage(`You must specify a video to watch and a title for the group watch.`);
const game = await YouTube.createGroupWatch(url, room, title);
this.modlog(`YOUTUBE GROUPWATCH`, null, `${url} (${title})`);
room.add(
@ -735,9 +735,9 @@ export const commands: Chat.ChatCommands = {
twitch: {
async channel(target, room, user) {
room = this.requireRoom('internetexplorers' as RoomID);
if (!Config.twitchKey) return this.errorReply(`Twitch is not configured`);
if (!Config.twitchKey) throw new Chat.ErrorMessage(`Twitch is not configured`);
const data = await Twitch.getChannel(target);
if (!data) return this.errorReply(`Channel not found`);
if (!data) throw new Chat.ErrorMessage(`Channel not found`);
const html = Twitch.visualizeChannel(data);
this.runBroadcast();
return this.sendReplyBox(html);
@ -761,7 +761,7 @@ export const pages: Chat.PageTable = {
switch (toID(type)) {
case 'categories':
if (!YouTube.data.categories.length) {
return this.errorReply(`There are currently no categories in the Youtube channel database.`);
throw new Chat.ErrorMessage(`There are currently no categories in the Youtube channel database.`);
}
const sorted: { [k: string]: string[] } = {};
const channels = YouTube.data.channels;
@ -802,7 +802,7 @@ export const pages: Chat.PageTable = {
if (!user.named) return Rooms.RETRY_AFTER_LOGIN;
const [roomid, num] = query;
const watch = GroupWatch.groupwatches.get(`${roomid}-${num}`);
if (!watch) return this.errorReply(`Groupwatch ${roomid}-${num} not found.`);
if (!watch) throw new Chat.ErrorMessage(`Groupwatch ${roomid}-${num} not found.`);
this.title = `[Groupwatch] ${watch.title}`;
return watch.display();
},

View File

@ -235,7 +235,8 @@ class PatternTester {
* Outside of a command/page context, it would still cause a crash.
*/
export class ErrorMessage extends Error {
constructor(message: string) {
constructor(message: string | string[]) {
if (Array.isArray(message)) message = message.join('\n');
super(message);
this.name = 'ErrorMessage';
Error.captureStackTrace(this, ErrorMessage);
@ -471,7 +472,7 @@ export class PageContext extends MessageContext {
res = await handler.call(this, parts, this.user, this.connection);
} catch (err: any) {
if (err.name?.endsWith('ErrorMessage')) {
if (err.message) this.errorReply(err.message);
if (err.message) this.errorReply(err.message.replace(/\n/g, '<br />'));
return;
}
if (err.name.endsWith('Interruption')) {
@ -693,7 +694,7 @@ export class CommandContext extends MessageContext {
!Users.globalAuth.atLeast(this.user, blockInvites as GroupSymbol)
) {
Chat.maybeNotifyBlocked(`invite`, this.pmTarget, this.user);
return this.errorReply(`${this.pmTarget.name} is blocking room invites.`);
throw new Chat.ErrorMessage(`${this.pmTarget.name} is blocking room invites.`);
}
}
Chat.PrivateMessages.send(message, this.user, this.pmTarget);
@ -707,9 +708,8 @@ export class CommandContext extends MessageContext {
run(handler: string | AnnotatedChatHandler) {
if (typeof handler === 'string') handler = Chat.commands[handler] as AnnotatedChatHandler;
if (!handler.broadcastable && this.cmdToken === '!') {
this.errorReply(`The command "${this.fullCmd}" can't be broadcast.`);
this.errorReply(`Use /${this.fullCmd} instead.`);
return false;
throw new Chat.ErrorMessage([`The command "${this.fullCmd}" can't be broadcast.`,
`Use /${this.fullCmd} instead.`]);
}
let result: any = handler.call(this, this.target, this.room, this.user, this.connection, this.cmd, this.message);
if (result === undefined) result = false;
@ -1024,9 +1024,7 @@ export class CommandContext extends MessageContext {
}
canUseConsole() {
if (!this.user.hasConsoleAccess(this.connection)) {
throw new Chat.ErrorMessage(
(this.cmdToken + this.fullCmd).trim() + " - Requires console access, please set up `Config.consoleips`."
);
throw new Chat.ErrorMessage(`${(this.cmdToken + this.fullCmd).trim()} - Requires console access, please set up \`Config.consoleips\`.`);
}
return true;
}
@ -1039,20 +1037,20 @@ export class CommandContext extends MessageContext {
}
if (this.user.locked && !(this.room?.roomid.startsWith('help-') || this.pmTarget?.can('lock'))) {
this.errorReply(`You cannot broadcast this command's information while locked.`);
throw new Chat.ErrorMessage(`To see it for yourself, use: /${this.message.slice(1)}`);
throw new Chat.ErrorMessage([`You cannot broadcast this command's information while locked.`,
`To see it for yourself, use: /${this.message.slice(1)}`]);
}
if (this.room && !this.user.can('show', null, this.room, this.cmd, this.cmdToken)) {
const perm = this.room.settings.permissions?.[`!${this.cmd}`];
const atLeast = perm ? `at least rank ${perm}` : 'voiced';
this.errorReply(`You need to be ${atLeast} to broadcast this command's information.`);
throw new Chat.ErrorMessage(`To see it for yourself, use: /${this.message.slice(1)}`);
throw new Chat.ErrorMessage([`You need to be ${atLeast} to broadcast this command's information.`,
`To see it for yourself, use: /${this.message.slice(1)}`]);
}
if (!this.room && !this.pmTarget) {
this.errorReply(`Broadcasting a command with "!" in a PM or chatroom will show it that user or room.`);
throw new Chat.ErrorMessage(`To see it for yourself, use: /${this.message.slice(1)}`);
throw new Chat.ErrorMessage([`Broadcasting a command with "!" in a PM or chatroom will show it that user or room.`,
`To see it for yourself, use: /${this.message.slice(1)}`]);
}
// broadcast cooldown
@ -1388,24 +1386,22 @@ export class CommandContext extends MessageContext {
if (tagName === 'img') {
if (!this.room || (this.room.settings.isPersonal && !this.user.can('lock'))) {
throw new Chat.ErrorMessage(
`This tag is not allowed: <${tagContent}>. Images are not allowed outside of chatrooms.`
);
throw new Chat.ErrorMessage(`This tag is not allowed: <${tagContent}>. Images are not allowed outside of chatrooms.`);
}
if (!/width ?= ?(?:[0-9]+|"[0-9]+")/i.test(tagContent) || !/height ?= ?(?:[0-9]+|"[0-9]+")/i.test(tagContent)) {
// Width and height are required because most browsers insert the
// <img> element before width and height are known, and when the
// image is loaded, this changes the height of the chat area, which
// messes up autoscrolling.
this.errorReply(`This image is missing a width/height attribute: <${tagContent}>`);
throw new Chat.ErrorMessage(`Images without predefined width/height cause problems with scrolling because loading them changes their height.`);
throw new Chat.ErrorMessage([`This image is missing a width/height attribute: <${tagContent}>`,
`Images without predefined width/height cause problems with scrolling because loading them changes their height.`]);
}
const srcMatch = / src ?= ?(?:"|')?([^ "']+)(?: ?(?:"|'))?/i.exec(tagContent);
if (srcMatch) {
this.checkEmbedURI(srcMatch[1]);
} else {
this.errorReply(`This image has a broken src attribute: <${tagContent}>`);
throw new Chat.ErrorMessage(`The src attribute must exist and have no spaces in the URL`);
throw new Chat.ErrorMessage([`This image has a broken src attribute: <${tagContent}>`,
`The src attribute must exist and have no spaces in the URL`]);
}
}
if (tagName === 'button') {
@ -1418,16 +1414,18 @@ export class CommandContext extends MessageContext {
const [pmTarget] = buttonValue.replace(msgCommandRegex, '').split(',');
const auth = this.room ? this.room.auth : Users.globalAuth;
if (auth.get(toID(pmTarget)) !== '*' && toID(pmTarget) !== this.user.id) {
this.errorReply(`This button is not allowed: <${tagContent}>`);
throw new Chat.ErrorMessage(`Your scripted button can't send PMs to ${pmTarget}, because that user is not a Room Bot.`);
throw new Chat.ErrorMessage([`This button is not allowed: <${tagContent}>`,
`Your scripted button can't send PMs to ${pmTarget}, because that user is not a Room Bot.`]);
}
} else if (buttonName === 'send' && buttonValue && botmsgCommandRegex.test(buttonValue)) {
// no need to validate the bot being an actual bot; `/botmsg` will do it for us and is not abusable
} else if (buttonName) {
this.errorReply(`This button is not allowed: <${tagContent}>`);
this.errorReply(`You do not have permission to use most buttons. Here are the two types you're allowed to use:`);
this.errorReply(`1. Linking to a room: <a href="/roomid"><button>go to a place</button></a>`);
throw new Chat.ErrorMessage(`2. Sending a message to a Bot: <button name="send" value="/msgroom BOT_ROOMID, /botmsg BOT_USERNAME, MESSAGE">send the thing</button>`);
throw new Chat.ErrorMessage([
`This button is not allowed: <${tagContent}>`,
`You do not have permission to use most buttons. Here are the two types you're allowed to use:`,
`1. Linking to a room: <a href="/roomid"><button>go to a place</button></a>`,
`2. Sending a message to a Bot: <button name="send" value="/msgroom BOT_ROOMID, /botmsg BOT_USERNAME, MESSAGE">send the thing</button>`,
]);
}
}
}

View File

@ -424,8 +424,7 @@ export class Tournament extends Rooms.RoomGame<TournamentPlayer> {
const gameCount = user.games.size;
if (gameCount > 4) {
output.errorReply("Due to high load, you are limited to 4 games at the same time.");
return;
throw new Chat.ErrorMessage("Due to high load, you are limited to 4 games at the same time.");
}
if (!Config.noipchecks) {
@ -486,21 +485,17 @@ export class Tournament extends Rooms.RoomGame<TournamentPlayer> {
return;
}
if (!(user.id in this.playerTable)) {
output.errorReply(`${user.name} isn't in the tournament.`);
return;
throw new Chat.ErrorMessage(`${user.name} isn't in the tournament.`);
}
if (!replacementUser.named) {
output.errorReply(`${replacementUser.name} must be named to join the tournament.`);
return;
throw new Chat.ErrorMessage(`${replacementUser.name} must be named to join the tournament.`);
}
if (replacementUser.id in this.playerTable) {
output.errorReply(`${replacementUser.name} is already in the tournament.`);
return;
throw new Chat.ErrorMessage(`${replacementUser.name} is already in the tournament.`);
}
if (Tournament.checkBanned(this.room, replacementUser) || Punishments.isBattleBanned(replacementUser) ||
replacementUser.namelocked) {
output.errorReply(`${replacementUser.name} is banned from joining tournaments.`);
return;
throw new Chat.ErrorMessage(`${replacementUser.name} is banned from joining tournaments.`);
}
if ((this.room.settings.tournaments?.autoconfirmedOnly || this.autoconfirmedOnly) && !user.autoconfirmed) {
user.popup("Signups for tournaments are only available for autoconfirmed users in this room.");
@ -511,17 +506,14 @@ export class Tournament extends Rooms.RoomGame<TournamentPlayer> {
for (const otherPlayer of this.players) {
if (!otherPlayer) continue;
const otherUser = Users.get(otherPlayer.id);
if (otherUser &&
otherUser.latestIp === replacementUser.latestIp &&
if (otherUser && otherUser.latestIp === replacementUser.latestIp &&
replacementUser.latestIp !== user.latestIp) {
output.errorReply(`${replacementUser.name} already has an alt in the tournament.`);
return;
throw new Chat.ErrorMessage(`${replacementUser.name} already has an alt in the tournament.`);
}
}
}
if (!(replacementUser.id in this.room.users)) {
output.errorReply(`${replacementUser.name} is not in this room (${this.room.title}).`);
return;
throw new Chat.ErrorMessage(`${replacementUser.name} is not in this room (${this.room.title}).`);
}
const player = this.playerTable[user.id];
if (player.pendingChallenge) {
@ -1210,14 +1202,12 @@ function getGenerator(generator: string | undefined) {
}
function createTournamentGenerator(
generatorName: string | undefined, modifier: string | undefined, output: Chat.CommandContext
generatorName: string | undefined, modifier: string | undefined
) {
const TourGenerator = getGenerator(generatorName);
if (!TourGenerator) {
output.errorReply(`${generatorName} is not a valid type.`);
const generatorNames = Object.keys(TournamentGenerators).join(', ');
output.errorReply(`Valid types: ${generatorNames}`);
return;
throw new Chat.ErrorMessage([`${generatorName} is not a valid type.`, `Valid types: ${generatorNames}`]);
}
return new TourGenerator(modifier || '');
}
@ -1226,16 +1216,13 @@ function createTournament(
isRated: boolean, generatorMod: string | undefined, name: string | undefined, output: Chat.CommandContext
) {
if (room.type !== 'chat') {
output.errorReply("Tournaments can only be created in chat rooms.");
return;
throw new Chat.ErrorMessage("Tournaments can only be created in chat rooms.");
}
if (room.game) {
output.errorReply(`You cannot have a tournament until the current room activity is over: ${room.game.title}`);
return;
throw new Chat.ErrorMessage(`You cannot have a tournament until the current room activity is over: ${room.game.title}`);
}
if (Rooms.global.lockdown) {
output.errorReply("The server is restarting soon, so a tournament cannot be created.");
return;
throw new Chat.ErrorMessage("The server is restarting soon, so a tournament cannot be created.");
}
const format = Dex.formats.get(formatId);
if (format.effectType !== 'Format' || !format.tournamentShow) {
@ -1247,19 +1234,15 @@ function createTournament(
if (settings?.blockRecents && settings.recentTours && settings.recentToursLength) {
const recentTours = settings.recentTours.map(x => x.baseFormat);
if (recentTours.includes(format.id)) {
output.errorReply(`A ${format.name} tournament was made too recently.`);
return;
throw new Chat.ErrorMessage(`A ${format.name} tournament was made too recently.`);
}
}
if (!getGenerator(generator)) {
output.errorReply(`${generator} is not a valid type.`);
const generators = Object.keys(TournamentGenerators).join(', ');
output.errorReply(`Valid types: ${generators}`);
return;
throw new Chat.ErrorMessage([`${generator} is not a valid type.`, `Valid types: ${generators}`]);
}
if (playerCap && parseInt(playerCap) < 2) {
output.errorReply("You cannot have a player cap that is less than 2.");
return;
throw new Chat.ErrorMessage("You cannot have a player cap that is less than 2.");
}
if (name?.trim().length) {
if (output.checkChat(name) !== name) {
@ -1272,7 +1255,7 @@ function createTournament(
if (name.includes('|')) throw new Chat.ErrorMessage("The tournament's name cannot include the | symbol.");
}
const tour = room.game = new Tournament(
room, format, createTournamentGenerator(generator, generatorMod, output)!, playerCap, isRated, name
room, format, createTournamentGenerator(generator, generatorMod), playerCap, isRated, name
);
if (settings) {
if (typeof settings.autostart === 'number') tour.setAutoStartTimeout(settings.autostart, output);
@ -1353,14 +1336,18 @@ const commands: Chat.ChatCommands = {
const option = target.toLowerCase();
if (this.meansYes(option)) {
if (room.settings.tournaments?.announcements) return this.errorReply("Tournament announcements are already enabled.");
if (room.settings.tournaments?.announcements) {
throw new Chat.ErrorMessage("Tournament announcements are already enabled.");
}
if (!room.settings.tournaments) room.settings.tournaments = {};
room.settings.tournaments.announcements = true;
room.saveSettings();
this.privateModAction(`Tournament announcements were enabled by ${user.name}`);
this.modlog('TOUR ANNOUNCEMENTS', null, 'ON');
} else if (this.meansNo(option)) {
if (!room.settings.tournaments?.announcements) return this.errorReply("Tournament announcements are already disabled.");
if (!room.settings.tournaments?.announcements) {
throw new Chat.ErrorMessage("Tournament announcements are already disabled.");
}
if (!room.settings.tournaments) room.settings.tournaments = {};
room.settings.tournaments.announcements = false;
room.saveSettings();
@ -1426,10 +1413,12 @@ const commands: Chat.ChatCommands = {
const targetUserid = targetUser ? targetUser.id : toID(userid);
if (!targetUser) return false;
if (reason?.length > MAX_REASON_LENGTH) {
return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`);
throw new Chat.ErrorMessage(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`);
}
if (Tournament.checkBanned(room, targetUser)) return this.errorReply("This user is already banned from tournaments.");
if (Tournament.checkBanned(room, targetUser)) {
throw new Chat.ErrorMessage("This user is already banned from tournaments.");
}
const punishment = {
type: 'TOURBAN',
@ -1460,7 +1449,9 @@ const commands: Chat.ChatCommands = {
const targetUserid = toID(targetUser || toID(target));
if (!Tournament.checkBanned(room, targetUserid)) return this.errorReply("This user isn't banned from tournaments.");
if (!Tournament.checkBanned(room, targetUserid)) {
throw new Chat.ErrorMessage("This user isn't banned from tournaments.");
}
if (targetUser) {
Punishments.roomUnpunish(room, targetUserid, 'TOURBAN', false);
@ -1484,7 +1475,7 @@ const commands: Chat.ChatCommands = {
if (tournament.getRemainingPlayers().some(player => player.id === user.id)) {
tournament.disqualifyUser(user.id, this, null, true);
} else {
this.errorReply("You have already been eliminated from this tournament.");
throw new Chat.ErrorMessage("You have already been eliminated from this tournament.");
}
} else {
tournament.removeUser(user.id, this);
@ -1548,7 +1539,7 @@ const commands: Chat.ChatCommands = {
const tournament = this.requireGame(Tournament);
if (!this.runBroadcast()) return;
if (tournament.customRules.length < 1) {
return this.errorReply("The tournament does not have any custom rules.");
throw new Chat.ErrorMessage("The tournament does not have any custom rules.");
}
this.sendReply(`|html|<div class='infobox infobox-limited'>This tournament includes:<br />${tournament.getCustomRules()}</div>`);
},
@ -1561,7 +1552,7 @@ const commands: Chat.ChatCommands = {
}
const [generatorType, cap, modifier] = target.split(',').map(item => item.trim());
const playerCap = parseInt(cap);
const generator = createTournamentGenerator(generatorType, modifier, this);
const generator = createTournamentGenerator(generatorType, modifier);
if (generator && tournament.setGenerator(generator, this)) {
if (playerCap && playerCap >= 2) {
tournament.playerCap = playerCap;
@ -1595,11 +1586,11 @@ const commands: Chat.ChatCommands = {
}
}
if (tournament.isTournamentStarted) {
return this.errorReply("The player cap cannot be changed once the tournament has started.");
throw new Chat.ErrorMessage("The player cap cannot be changed once the tournament has started.");
}
const option = target.toLowerCase();
if (['0', 'infinity', 'off', 'false', 'stop', 'remove'].includes(option)) {
if (!tournament.playerCap) return this.errorReply("The tournament does not have a player cap.");
if (!tournament.playerCap) throw new Chat.ErrorMessage("The tournament does not have a player cap.");
target = '0';
}
const playerCap = parseInt(target);
@ -1610,10 +1601,10 @@ const commands: Chat.ChatCommands = {
this.sendReply("Tournament cap removed.");
} else {
if (isNaN(playerCap) || playerCap < 2) {
return this.errorReply("The tournament cannot have a player cap less than 2.");
throw new Chat.ErrorMessage("The tournament cannot have a player cap less than 2.");
}
if (playerCap === tournament.playerCap) {
return this.errorReply(`The tournament's player cap is already ${playerCap}.`);
throw new Chat.ErrorMessage(`The tournament's player cap is already ${playerCap}.`);
}
tournament.playerCap = playerCap;
if (Config.tourdefaultplayercap && tournament.playerCap > Config.tourdefaultplayercap) {
@ -1642,7 +1633,7 @@ const commands: Chat.ChatCommands = {
room = this.requireRoom();
const tournament = this.requireGame(Tournament);
if (cmd === 'banlist') {
return this.errorReply('The new syntax is: /tour rules -bannedthing, +un[banned|restricted]thing, *restrictedthing, !removedrule, addedrule');
throw new Chat.ErrorMessage('The new syntax is: /tour rules -bannedthing, +un[banned|restricted]thing, *restrictedthing, !removedrule, addedrule');
}
if (!target) {
this.sendReply("Usage: /tour rules <list of rules>");
@ -1655,7 +1646,7 @@ const commands: Chat.ChatCommands = {
}
this.checkCan('tournaments', null, room);
if (tournament.isTournamentStarted) {
return this.errorReply("The custom rules cannot be changed once the tournament has started.");
throw new Chat.ErrorMessage("The custom rules cannot be changed once the tournament has started.");
}
if (tournament.setCustomRules(target)) {
room.addRaw(
@ -1674,10 +1665,10 @@ const commands: Chat.ChatCommands = {
this.checkCan('tournaments', null, room);
const tournament = this.requireGame(Tournament);
if (tournament.isTournamentStarted) {
return this.errorReply("The custom rules cannot be changed once the tournament has started.");
throw new Chat.ErrorMessage("The custom rules cannot be changed once the tournament has started.");
}
if (tournament.customRules.length < 1) {
return this.errorReply("The tournament does not have any custom rules.");
throw new Chat.ErrorMessage("The tournament does not have any custom rules.");
}
tournament.customRules = [];
tournament.fullFormat = tournament.baseFormat;
@ -1704,9 +1695,9 @@ const commands: Chat.ChatCommands = {
if (!name || typeof name !== 'string') return;
if (name.length > MAX_CUSTOM_NAME_LENGTH) {
return this.errorReply(`The tournament's name cannot exceed ${MAX_CUSTOM_NAME_LENGTH} characters.`);
throw new Chat.ErrorMessage(`The tournament's name cannot exceed ${MAX_CUSTOM_NAME_LENGTH} characters.`);
}
if (name.includes('|')) return this.errorReply("The tournament's name cannot include the | symbol.");
if (name.includes('|')) throw new Chat.ErrorMessage("The tournament's name cannot include the | symbol.");
tournament.name = name;
room.send(`|tournament|update|${JSON.stringify({ format: tournament.name })}`);
this.privateModAction(`${user.name} set the tournament's name to ${tournament.name}.`);
@ -1718,7 +1709,7 @@ const commands: Chat.ChatCommands = {
room = this.requireRoom();
this.checkCan('tournaments', null, room);
const tournament = this.requireGame(Tournament);
if (tournament.name === tournament.baseFormat) return this.errorReply("The tournament does not have a name.");
if (tournament.name === tournament.baseFormat) throw new Chat.ErrorMessage("The tournament does not have a name.");
tournament.name = tournament.baseFormat;
room.send(`|tournament|update|${JSON.stringify({ format: tournament.name })}`);
this.privateModAction(`${user.name} cleared the tournament's name.`);
@ -1746,7 +1737,7 @@ const commands: Chat.ChatCommands = {
const targetUser = Users.get(userid);
const targetUserid = toID(targetUser || userid);
if (reason?.length > MAX_REASON_LENGTH) {
return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`);
throw new Chat.ErrorMessage(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`);
}
if (tournament.disqualifyUser(targetUserid, this, reason)) {
this.privateModAction(`${(targetUser ? targetUser.name : targetUserid)} was disqualified from the tournament by ${user.name}${(reason ? ' (' + reason + ')' : '')}`);
@ -1759,8 +1750,8 @@ const commands: Chat.ChatCommands = {
this.checkCan('tournaments', null, room);
const tournament = this.requireGame(Tournament);
const [oldUser, newUser] = target.split(',').map(item => Users.get(item.trim()));
if (!oldUser) return this.errorReply(`User ${oldUser} not found.`);
if (!newUser) return this.errorReply(`User ${newUser} not found.`);
if (!oldUser) throw new Chat.ErrorMessage(`User ${oldUser} not found.`);
if (!newUser) throw new Chat.ErrorMessage(`User ${newUser} not found.`);
tournament.replaceUser(oldUser, newUser, this);
},
@ -1776,12 +1767,12 @@ const commands: Chat.ChatCommands = {
const option = target.toLowerCase();
if ((this.meansYes(option) && option !== '1') || option === 'start') {
if (tournament.isTournamentStarted) {
return this.errorReply("The tournament has already started.");
throw new Chat.ErrorMessage("The tournament has already started.");
} else if (!tournament.playerCap) {
return this.errorReply("The tournament does not have a player cap set.");
throw new Chat.ErrorMessage("The tournament does not have a player cap set.");
} else {
if (tournament.autostartcap) {
return this.errorReply("The tournament is already set to autostart when the player cap is reached.");
throw new Chat.ErrorMessage("The tournament is already set to autostart when the player cap is reached.");
}
tournament.setAutostartAtCap(true);
this.privateModAction(`The tournament was set to autostart when the player cap is reached by ${user.name}`);
@ -1790,14 +1781,14 @@ const commands: Chat.ChatCommands = {
} else {
if (option === '0' || option === 'infinity' || this.meansNo(option) || option === 'stop' || option === 'remove') {
if (!tournament.autostartcap && tournament.autoStartTimeout === Infinity) {
return this.errorReply("The automatic tournament start timer is already off.");
throw new Chat.ErrorMessage("The automatic tournament start timer is already off.");
}
target = 'off';
tournament.autostartcap = false;
}
const timeout = target.toLowerCase() === 'off' ? Infinity : Number(target) * 60 * 1000;
if (timeout <= 0 || (timeout !== Infinity && timeout > Chat.MAX_TIMEOUT_DURATION)) {
return this.errorReply(`The automatic tournament start timer must be set to a positive number.`);
throw new Chat.ErrorMessage(`The automatic tournament start timer must be set to a positive number.`);
}
if (tournament.setAutoStartTimeout(timeout, this)) {
this.privateModAction(`The tournament auto start timer was set to ${target} by ${user.name}`);
@ -1821,10 +1812,10 @@ const commands: Chat.ChatCommands = {
if (target.toLowerCase() === 'infinity' || target === '0') target = 'off';
const timeout = target.toLowerCase() === 'off' ? Infinity : Number(target) * 60 * 1000;
if (timeout <= 0 || (timeout !== Infinity && timeout > Chat.MAX_TIMEOUT_DURATION)) {
return this.errorReply(`The automatic disqualification timer must be set to a positive number.`);
throw new Chat.ErrorMessage(`The automatic disqualification timer must be set to a positive number.`);
}
if (timeout === tournament.autoDisqualifyTimeout) {
return this.errorReply(`The automatic tournament disqualify timer is already set to ${target} minute(s).`);
throw new Chat.ErrorMessage(`The automatic tournament disqualify timer is already set to ${target} minute(s).`);
}
if (tournament.setAutoDisqualifyTimeout(timeout, this)) {
this.privateModAction(`The tournament auto disqualify timer was set to ${target} by ${user.name}`);
@ -1836,7 +1827,7 @@ const commands: Chat.ChatCommands = {
this.checkCan('tournaments', null, room);
const tournament = this.requireGame(Tournament);
if (tournament.autoDisqualifyTimeout === Infinity) {
return this.errorReply("The automatic tournament disqualify timer is not set.");
throw new Chat.ErrorMessage("The automatic tournament disqualify timer is not set.");
}
tournament.runAutoDisqualify(this);
this.roomlog(`${user.name} used /tour runautodq`);
@ -1859,12 +1850,12 @@ const commands: Chat.ChatCommands = {
const option = target.toLowerCase();
if (this.meansYes(option) || option === 'allow' || option === 'allowed') {
if (tournament.allowScouting) return this.errorReply("Scouting for this tournament is already set to allowed.");
if (tournament.allowScouting) throw new Chat.ErrorMessage("Scouting for this tournament is already set to allowed.");
tournament.setScouting(true);
this.privateModAction(`The tournament was set to allow scouting by ${user.name}`);
this.modlog('TOUR SCOUT', null, 'allow');
} else if (this.meansNo(option) || option === 'disallow' || option === 'disallowed') {
if (!tournament.allowScouting) return this.errorReply("Scouting for this tournament is already disabled.");
if (!tournament.allowScouting) throw new Chat.ErrorMessage("Scouting for this tournament is already disabled.");
tournament.setScouting(false);
this.privateModAction(`The tournament was set to disallow scouting by ${user.name}`);
this.modlog('TOUR SCOUT', null, 'disallow');
@ -1888,12 +1879,12 @@ const commands: Chat.ChatCommands = {
const option = target.toLowerCase();
if (this.meansYes(option) || option === 'allowed') {
if (tournament.allowModjoin) return this.errorReply("Modjoining is already allowed for this tournament.");
if (tournament.allowModjoin) throw new Chat.ErrorMessage("Modjoining is already allowed for this tournament.");
tournament.setModjoin(true);
this.privateModAction(`The tournament was set to allow modjoin by ${user.name}`);
this.modlog('TOUR MODJOIN', null, option);
} else if (this.meansNo(option) || option === 'disallowed') {
if (!tournament.allowModjoin) return this.errorReply("Modjoining is already not allowed for this tournament.");
if (!tournament.allowModjoin) throw new Chat.ErrorMessage("Modjoining is already not allowed for this tournament.");
tournament.setModjoin(false);
this.privateModAction(`The tournament was set to disallow modjoin by ${user.name}`);
this.modlog('TOUR MODJOIN', null, option);
@ -1920,7 +1911,7 @@ const commands: Chat.ChatCommands = {
return this.parse(`/help tour`);
}
if (tournament.autoconfirmedOnly === value) {
return this.errorReply(`This tournament is already set to ${value ? 'disallow' : 'allow'} non-autoconfirmed users.`);
throw new Chat.ErrorMessage(`This tournament is already set to ${value ? 'disallow' : 'allow'} non-autoconfirmed users.`);
}
tournament.setAutoconfirmedOnly(value);
this.privateModAction(`${user.name} set this tournament to ${value ? 'disallow' : 'allow'} non-autoconfirmed users.`);
@ -2037,7 +2028,7 @@ const commands: Chat.ChatCommands = {
const value = this.meansYes(target) ? true : this.meansNo(target) ? false : null;
if (!target || value === null) return this.parse(`/help tour settings`);
if (room.settings.tournaments.autoconfirmedOnly === value) {
return this.errorReply(`All tournaments are already set to ${value ? 'disallow' : 'allow'} non-autoconfimed users.`);
throw new Chat.ErrorMessage(`All tournaments are already set to ${value ? 'disallow' : 'allow'} non-autoconfimed users.`);
}
room.settings.tournaments.autoconfirmedOnly = value;
room.saveSettings();