mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-05-21 14:59:50 -05:00
`server/chat-commands.js` is now a directory. It's been split into `core`, `moderation`, and `admin`. `info` and `roomsettings` from `chat-plugins` have also moved to `chat-commands`. Some cleanup: - Bot commands for inserting HTML into rooms like `/adduhtml` have been moved from `info` into `admin`. - `/a` has been renamed `/addline`, for clarity (and also moved from `info` into `admin`). - Room management commands like `/createroom` and `/roomintro` were moved to `room-settings` - `chat-commands/admin` has been TypeScripted
1350 lines
57 KiB
JavaScript
1350 lines
57 KiB
JavaScript
/**
|
|
* Room settings commands
|
|
* Pokemon Showdown - http://pokemonshowdown.com/
|
|
*
|
|
* Commands for settings relating to room setting filtering.
|
|
*
|
|
* @license MIT
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const RANKS = Config.groupsranking;
|
|
|
|
const SLOWCHAT_MINIMUM = 2;
|
|
const SLOWCHAT_MAXIMUM = 60;
|
|
const SLOWCHAT_USER_REQUIREMENT = 10;
|
|
|
|
const MAX_CHATROOM_ID_LENGTH = 225;
|
|
|
|
class RoomSettings {
|
|
constructor(user, room, connection) {
|
|
this.room = room;
|
|
this.user = user;
|
|
this.connection = connection;
|
|
this.sameCommand = true;
|
|
}
|
|
updateSetting(command) {
|
|
this.sameCommand = false;
|
|
this.generateDisplay();
|
|
}
|
|
button(setting, disable, command) {
|
|
if (disable) {
|
|
return Chat.html`<button class="button disabled" style="font-weight:bold; color:#575757; font-weight:bold; background-color:#d3d3d3;">${setting}</button> `;
|
|
}
|
|
return Chat.html`<button class="button" name="send" value="/roomsetting ${command}">${setting}</button> `;
|
|
}
|
|
modchat() {
|
|
if (!this.user.can('modchat', null, this.room)) return this.button(this.room.modchat ? this.room.modchat : 'off', true);
|
|
let modchatOutput = [];
|
|
for (const rank of RANKS) {
|
|
if (rank === Config.groupsranking[0] && !this.room.modchat) {
|
|
modchatOutput.push(this.button('off', true));
|
|
} else if (rank === Config.groupsranking[0]) {
|
|
modchatOutput.push(this.button('off', null, 'modchat off'));
|
|
} else if (rank === this.room.modchat) {
|
|
modchatOutput.push(this.button(rank, true));
|
|
} else if (rank) {
|
|
let rankIndex = RANKS.indexOf(rank);
|
|
let roomAuth = (this.room.auth && this.room.auth[this.user.id] ? this.room.auth[this.user.id] : false);
|
|
let roomAuthIndex = (roomAuth ? RANKS.indexOf(roomAuth) : false);
|
|
if (rankIndex > 1 && !this.user.can('modchatall', null, this.room)) continue;
|
|
if (roomAuth && !this.user.can('bypassall')) {
|
|
if (rankIndex > roomAuthIndex) continue;
|
|
}
|
|
modchatOutput.push(this.button(rank, null, `modchat ${rank}`));
|
|
}
|
|
}
|
|
// Since autoconfirmed isn't technically a Config rank...
|
|
let acStatus = this.room.modchat === 'autoconfirmed';
|
|
modchatOutput.splice(1, 0, this.button('AC', acStatus, 'modchat autoconfirmed'));
|
|
return modchatOutput.join(' ');
|
|
}
|
|
modjoin() {
|
|
if (!this.user.can('makeroom') && !this.room.isPersonal ||
|
|
!this.user.can('editroom', null, this.room)) {
|
|
return this.button(this.room.modjoin ? this.room.modjoin : 'off', true);
|
|
}
|
|
let modjoinOutput = [];
|
|
for (const rank of RANKS) {
|
|
if (rank === Config.groupsranking[0] && !this.room.modjoin) {
|
|
modjoinOutput.push(this.button('off', true));
|
|
} else if (rank === Config.groupsranking[0]) {
|
|
modjoinOutput.push(this.button('off', null, 'modjoin off'));
|
|
} else if (rank === this.room.modjoin) {
|
|
modjoinOutput.push(this.button(rank, true));
|
|
} else if (rank) {
|
|
// groupchat hosts can set modjoin, but only to +
|
|
if (this.room.isPersonal && !this.user.can('makeroom') && rank !== '+') continue;
|
|
|
|
modjoinOutput.push(this.button(rank, false, `modjoin ${rank}`));
|
|
}
|
|
}
|
|
return modjoinOutput.join(' ');
|
|
}
|
|
stretching() {
|
|
if (!this.user.can('editroom', null, this.room)) return this.button(this.room.filterStretching ? 'filter stretching' : 'off', true);
|
|
if (this.room.filterStretching) {
|
|
return `${this.button('off', null, 'stretchfilter off')} ${this.button('filter stretching', true)}`;
|
|
} else {
|
|
return `${this.button('off', true)} ${this.button('filter stretching', null, 'stretchfilter on')}`;
|
|
}
|
|
}
|
|
capitals() {
|
|
if (!this.user.can('editroom', null, this.room)) return this.button(this.room.filterCaps ? 'filter capitals' : 'off', true);
|
|
if (this.room.filterCaps) {
|
|
return `${this.button('off', null, 'capsfilter off')} ${this.button('filter capitals', true)}`;
|
|
} else {
|
|
return `${this.button('off', true)} ${this.button('filter capitals', null, 'capsfilter on')}`;
|
|
}
|
|
}
|
|
emojis() {
|
|
if (!this.user.can('editroom', null, this.room)) return this.button(this.room.filterEmojis ? 'filter emojis' : 'off', true);
|
|
if (this.room.filterEmojis) {
|
|
return `${this.button('off', null, 'emojifilter off')} ${this.button('filter emojis', true)}`;
|
|
} else {
|
|
return `${this.button('off', true)} ${this.button('filter emojis', null, 'emojifilter on')}`;
|
|
}
|
|
}
|
|
slowchat() {
|
|
if (!this.user.can('editroom', null, this.room) || (!this.user.can('bypassall') && this.room.userCount < SLOWCHAT_USER_REQUIREMENT)) return this.button(this.room.slowchat ? this.room.slowchat : 'off', true);
|
|
|
|
let slowchatOutput = [];
|
|
for (let i of [5, 10, 20, 30, 60]) {
|
|
if (this.room.slowchat === i) {
|
|
slowchatOutput.push(this.button(`${i}s`, true));
|
|
} else {
|
|
slowchatOutput.push(this.button(`${i}s`, null, `slowchat ${i}`));
|
|
}
|
|
}
|
|
if (!this.room.slowchat) {
|
|
slowchatOutput.unshift(this.button('off', true));
|
|
} else {
|
|
slowchatOutput.unshift(this.button('off', null, 'slowchat false'));
|
|
}
|
|
return slowchatOutput.join(' ');
|
|
}
|
|
tourStatus() {
|
|
if (!this.user.can('gamemanagement', null, this.room)) return this.button(this.room.toursEnabled === true ? '@' : this.room.toursEnabled === '%' ? '%' : '#', true);
|
|
|
|
if (this.room.toursEnabled === true) {
|
|
return `${this.button('%', null, 'tournament enable %')} ${this.button('@', true)} ${this.button('#', null, 'tournament disable')}`;
|
|
} else if (this.room.toursEnabled === '%') {
|
|
return `${this.button('%', true)} ${this.button('@', null, 'tournament enable @')} ${this.button('#', null, 'tournament disable')}`;
|
|
} else {
|
|
return `${this.button('%', null, 'tournament enable %')} ${this.button('@', null, 'tournament enable @')} ${this.button('#', true)}`;
|
|
}
|
|
}
|
|
uno() {
|
|
if (!this.user.can('editroom', null, this.room)) return this.button(this.room.unoDisabled ? 'off' : 'UNO enabled', true);
|
|
if (this.room.unoDisabled) {
|
|
return `${this.button('UNO enabled', null, 'uno enable')} ${this.button('off', true)}`;
|
|
} else {
|
|
return `${this.button('UNO enabled', true)} ${this.button('off', null, 'uno disable')}`;
|
|
}
|
|
}
|
|
hangman() {
|
|
if (!this.user.can('editroom', null, this.room)) return this.button(this.room.hangmanDisabled ? 'off' : 'Hangman enabled', true);
|
|
if (this.room.hangmanDisabled) {
|
|
return `${this.button('Hangman enabled', null, 'hangman enable')} ${this.button('off', true)}`;
|
|
} else {
|
|
return `${this.button('Hangman enabled', true)} ${this.button('off', null, 'hangman disable')}`;
|
|
}
|
|
}
|
|
mafia() {
|
|
if (!this.user.can('editroom', null, this.room)) return this.button(this.room.mafiaDisabled ? 'off' : 'Mafia enabled', true);
|
|
if (this.room.mafiaDisabled) {
|
|
return `${this.button('Mafia enabled', null, 'mafia enable')} ${this.button('off', true)}`;
|
|
} else {
|
|
return `${this.button('Mafia enabled', true)} ${this.button('off', null, 'mafia disable')}`;
|
|
}
|
|
}
|
|
blackjack() {
|
|
if (!this.user.can('editroom', null, this.room)) return this.button(this.room.blackjackDisabled ? 'off' : 'Blackjack enabled', true);
|
|
if (this.room.blackjackDisabled) {
|
|
return `${this.button('Blackjack enabled', null, 'blackjack enable')} ${this.button('off', true)}`;
|
|
} else {
|
|
return `${this.button('Blackjack enabled', true)} ${this.button('off', null, 'blackjack disable')}`;
|
|
}
|
|
}
|
|
language() {
|
|
if (!this.user.can('editroom', null, this.room)) return this.button(this.room.language ? Chat.languages.get(this.room.language) : 'English', true);
|
|
|
|
let languageOutput = [];
|
|
languageOutput.push(this.button(`English`, !this.room.language, 'roomlanguage english'));
|
|
for (let [id, text] of Chat.languages) {
|
|
languageOutput.push(this.button(text, this.room.language === id, `roomlanguage ${id}`));
|
|
}
|
|
return languageOutput.join(' ');
|
|
}
|
|
generateDisplay(user, room, connection) {
|
|
let output = Chat.html`<div class="infobox">Room Settings for ${this.room.title}<br />`;
|
|
output += `<strong>Language:</strong> <br />${this.language()}<br />`;
|
|
output += `<strong>Modchat:</strong> <br />${this.modchat()}<br />`;
|
|
output += `<strong>Modjoin:</strong> <br />${this.modjoin()}<br />`;
|
|
output += `<strong>Stretch filter:</strong> <br />${this.stretching()}<br />`;
|
|
output += `<strong>Caps filter:</strong> <br />${this.capitals()}<br />`;
|
|
output += `<strong>Emoji filter:</strong> <br />${this.emojis()}<br />`;
|
|
output += `<strong>Slowchat:</strong> <br />${this.slowchat()}<br />`;
|
|
output += `<strong>Tournaments:</strong> <br />${this.tourStatus()}<br />`;
|
|
output += `<strong>UNO:</strong> <br />${this.uno()}<br />`;
|
|
output += `<strong>Hangman:</strong> <br />${this.hangman()}<br />`;
|
|
output += `<strong>Blackjack:</strong> <br />${this.blackjack()}<br />`;
|
|
output += `<strong>Mafia:</strong> <br />${this.mafia()}<br />`;
|
|
output += '</div>';
|
|
|
|
this.user.sendTo(this.room, `|uhtml${(this.sameCommand ? '' : 'change')}|roomsettings|${output}`);
|
|
}
|
|
}
|
|
|
|
exports.commands = {
|
|
roomsetting: 'roomsettings',
|
|
roomsettings(target, room, user, connection) {
|
|
if (room.battle) return this.errorReply("This command cannot be used in battle rooms.");
|
|
const settings = new RoomSettings(user, room, connection);
|
|
|
|
if (!target) {
|
|
room.update();
|
|
settings.generateDisplay(user, room, connection);
|
|
} else {
|
|
this.parse(`/${target}`);
|
|
settings.updateSetting(target);
|
|
}
|
|
},
|
|
roomsettingshelp: [`/roomsettings - Shows current room settings with buttons to change them (if you can).`],
|
|
|
|
modchat(target, room, user) {
|
|
if (!target) {
|
|
const modchatSetting = (room.modchat || "OFF");
|
|
return this.sendReply(`Moderated chat is currently set to: ${modchatSetting}`);
|
|
}
|
|
if (!this.can('modchat', null, room)) return false;
|
|
|
|
// 'modchat' lets you set up to 1 (ac/trusted also allowed)
|
|
// 'modchatall' lets you set up to your current rank
|
|
// 'makeroom' lets you set any rank, no limit
|
|
let threshold = 1;
|
|
let roomGroup = Config.groups[room.getAuth(user)];
|
|
if (roomGroup && user.can('modchatall', null, room)) {
|
|
if (user.can('makeroom')) {
|
|
threshold = Infinity;
|
|
} else {
|
|
threshold = roomGroup.rank;
|
|
}
|
|
}
|
|
|
|
if (room.modchat && room.modchat.length <= 1 && Config.groupsranking.indexOf(room.modchat) > threshold) {
|
|
return this.errorReply(`/modchat - Access denied for changing a setting higher than ${Config.groupsranking[threshold]}.`);
|
|
}
|
|
if (room.requestModchat) {
|
|
const error = room.requestModchat(user);
|
|
if (error) return this.errorReply(error);
|
|
}
|
|
|
|
target = target.toLowerCase().trim();
|
|
const currentModchat = room.modchat;
|
|
switch (target) {
|
|
case 'off':
|
|
case 'false':
|
|
case 'no':
|
|
case 'disable':
|
|
room.modchat = false;
|
|
break;
|
|
case 'ac':
|
|
case 'autoconfirmed':
|
|
room.modchat = 'autoconfirmed';
|
|
break;
|
|
case 'trusted':
|
|
room.modchat = 'trusted';
|
|
break;
|
|
case 'player':
|
|
target = Users.PLAYER_SYMBOL;
|
|
/* falls through */
|
|
default:
|
|
if (!Config.groups[target]) {
|
|
this.errorReply(`The rank '${target}' was unrecognized as a modchat level.`);
|
|
return this.parse('/help modchat');
|
|
}
|
|
if (Config.groupsranking.indexOf(target) > threshold) {
|
|
return this.errorReply(`/modchat - Access denied for setting higher than ${Config.groupsranking[threshold]}.`);
|
|
}
|
|
room.modchat = target;
|
|
break;
|
|
}
|
|
if (currentModchat === room.modchat) {
|
|
return this.errorReply(`Modchat is already set to ${currentModchat}.`);
|
|
}
|
|
if (!room.modchat) {
|
|
this.add("|raw|<div class=\"broadcast-blue\"><strong>Moderated chat was disabled!</strong><br />Anyone may talk now.</div>");
|
|
} else {
|
|
const modchatSetting = Chat.escapeHTML(room.modchat);
|
|
this.add(`|raw|<div class="broadcast-red"><strong>Moderated chat was set to ${modchatSetting}!</strong><br />Only users of rank ${modchatSetting} and higher can talk.</div>`);
|
|
}
|
|
if (room.requestModchat && !room.modchat) room.requestModchat(null);
|
|
this.privateModAction(`(${user.name} set modchat to ${room.modchat})`);
|
|
this.modlog('MODCHAT', null, `to ${room.modchat}`);
|
|
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.modchat = room.modchat;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
modchathelp: [`/modchat [off/autoconfirmed/+/%/@/*/player/#/&/~] - Set the level of moderated chat. Requires: * @ \u2606 for off/autoconfirmed/+ options, # & ~ for all the options`],
|
|
|
|
ioo(target, room, user) {
|
|
return this.parse('/modjoin %');
|
|
},
|
|
'!ionext': true,
|
|
inviteonlynext: 'ionext',
|
|
ionext(target, room, user) {
|
|
const groupConfig = Config.groups[Users.PLAYER_SYMBOL];
|
|
if (!(groupConfig && groupConfig.editprivacy)) return this.errorReply(`/ionext - Access denied.`);
|
|
if (this.meansNo(target)) {
|
|
user.inviteOnlyNextBattle = false;
|
|
user.update('inviteOnlyNextBattle');
|
|
this.sendReply("Your next battle will be publicly visible.");
|
|
} else {
|
|
user.inviteOnlyNextBattle = true;
|
|
user.update('inviteOnlyNextBattle');
|
|
if (user.forcedPublic) return this.errorReply(`Your next battle will be invite-only provided it is not rated, otherwise your '${user.forcedPublic}' prefix will force the battle to be public.`);
|
|
this.sendReply("Your next battle will be invite-only.");
|
|
}
|
|
},
|
|
ionexthelp: [
|
|
`/ionext - Sets your next battle to be invite-only.`,
|
|
`/ionext off - Sets your next battle to be publicly visible.`,
|
|
],
|
|
|
|
inviteonly(target, room, user) {
|
|
if (!target) return this.parse('/help inviteonly');
|
|
if (this.meansYes(target)) {
|
|
return this.parse("/modjoin %");
|
|
} else {
|
|
return this.parse(`/modjoin ${target}`);
|
|
}
|
|
},
|
|
inviteonlyhelp: [
|
|
`/inviteonly [on|off] - Sets modjoin %. Users can't join unless invited with /invite. Requires: # & ~`,
|
|
`/ioo - Shortcut for /inviteonly on`,
|
|
`/inviteonlynext OR /ionext - Sets your next battle to be invite-only.`,
|
|
`/ionext off - Sets your next battle to be publicly visible.`,
|
|
],
|
|
|
|
modjoin(target, room, user) {
|
|
if (!target) {
|
|
const modjoinSetting = room.modjoin === true ? "SYNC" : room.modjoin || "OFF";
|
|
return this.sendReply(`Modjoin is currently set to: ${modjoinSetting}`);
|
|
}
|
|
if (room.isPersonal) {
|
|
if (!this.can('editroom', null, room)) return;
|
|
} else if (room.battle) {
|
|
if (!this.can('editprivacy', null, room)) return;
|
|
const prefix = room.battle.forcedPublic();
|
|
if (prefix && !user.can('editprivacy')) return this.errorReply(`This battle is required to be public due to a player having a name prefixed by '${prefix}'.`);
|
|
} else {
|
|
if (!this.can('makeroom')) return;
|
|
}
|
|
if (room.tour && !room.tour.modjoin) return this.errorReply(`You can't do this in tournaments where modjoin is prohibited.`);
|
|
if (target === 'player') target = Users.PLAYER_SYMBOL;
|
|
if (this.meansNo(target)) {
|
|
if (!room.modjoin) return this.errorReply(`Modjoin is already turned off in this room.`);
|
|
room.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.`);
|
|
this.modlog('MODJOIN', null, 'OFF');
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.modjoin = null;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
return;
|
|
} else if (target === 'sync') {
|
|
if (room.modjoin === true) return this.errorReply(`Modjoin is already set to sync modchat in this room.`);
|
|
room.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.modjoin === 'autoconfirmed') return this.errorReply(`Modjoin is already set to autoconfirmed.`);
|
|
room.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 (target in Config.groups || target === 'trusted') {
|
|
if (room.battle && !user.can('makeroom') && !'+%'.includes(target)) {
|
|
return this.errorReply(`/modjoin - Access denied from setting modjoin past % in battles.`);
|
|
}
|
|
if (room.isPersonal && !user.can('makeroom') && !'+%'.includes(target)) {
|
|
return this.errorReply(`/modjoin - Access denied from setting modjoin past % in group chats.`);
|
|
}
|
|
if (room.modjoin === target) return this.errorReply(`Modjoin is already set to ${target} in this room.`);
|
|
room.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}.`);
|
|
this.modlog('MODJOIN', null, target);
|
|
} else {
|
|
this.errorReply(`Unrecognized modjoin setting.`);
|
|
this.parse('/help modjoin');
|
|
return false;
|
|
}
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.modjoin = room.modjoin;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
if (target === 'sync' && !room.modchat) this.parse(`/modchat ${Config.groupsranking[1]}`);
|
|
if (!room.isPrivate) this.parse('/hiddenroom');
|
|
},
|
|
modjoinhelp: [
|
|
`/modjoin [+|%|@|*|player|&|~|#|off] - Sets modjoin. Users lower than the specified rank can't join this room unless they have a room rank. Requires: \u2606 # & ~`,
|
|
`/modjoin [sync|off] - Sets modjoin. Only users who can speak in modchat can join this room. Requires: \u2606 # & ~`,
|
|
],
|
|
|
|
roomlanguage(target, room, user) {
|
|
if (!target) return this.sendReply(`This room's primary language is ${Chat.languages.get(room.language) || 'English'}`);
|
|
if (!this.can('editroom', null, room)) return false;
|
|
|
|
let targetLanguage = toID(target);
|
|
if (!Chat.languages.has(targetLanguage)) return this.errorReply(`"${target}" is not a supported language.`);
|
|
|
|
room.language = targetLanguage === 'english' ? false : targetLanguage;
|
|
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.language = room.language;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
this.modlog(`LANGUAGE`, null, Chat.languages.get(targetLanguage));
|
|
this.sendReply(`The room's language has been set to ${Chat.languages.get(targetLanguage)}`);
|
|
},
|
|
roomlanguagehelp: [
|
|
`/roomlanguage [language] - Sets the the language for the room, which changes language of a few commands. Requires # & ~`,
|
|
`Supported Languages: English, Spanish, Italian, French, Simplified Chinese, Traditional Chinese, Japanese, Hindi, Turkish, Dutch, German.`,
|
|
],
|
|
|
|
slowchat(target, room, user) {
|
|
if (!target) {
|
|
const slowchatSetting = (room.slowchat || "OFF");
|
|
return this.sendReply(`Slow chat is currently set to: ${slowchatSetting}`);
|
|
}
|
|
if (!this.canTalk()) return;
|
|
if (!this.can('modchat', null, room)) return false;
|
|
|
|
let targetInt = parseInt(target);
|
|
if (this.meansNo(target)) {
|
|
if (!room.slowchat) return this.errorReply(`Slow chat is already disabled in this room.`);
|
|
room.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.`);
|
|
if (room.slowchat === targetInt) return this.errorReply(`Slow chat is already set to ${room.slowchat} seconds in this room.`);
|
|
if (targetInt < SLOWCHAT_MINIMUM) targetInt = SLOWCHAT_MINIMUM;
|
|
if (targetInt > SLOWCHAT_MAXIMUM) targetInt = SLOWCHAT_MAXIMUM;
|
|
room.slowchat = targetInt;
|
|
} else {
|
|
return this.parse("/help slowchat");
|
|
}
|
|
const slowchatSetting = (room.slowchat || "OFF");
|
|
this.privateModAction(`(${user.name} set slowchat to ${slowchatSetting})`);
|
|
this.modlog('SLOWCHAT', null, '' + slowchatSetting);
|
|
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.slowchat = room.slowchat;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
slowchathelp: [
|
|
`/slowchat [number] - Sets a limit on how often users in the room can send messages, between 2 and 60 seconds. Requires @ # & ~`,
|
|
`/slowchat off - Disables slowchat in the room. Requires @ # & ~`,
|
|
],
|
|
|
|
stretching: 'stretchfilter',
|
|
stretchingfilter: 'stretchfilter',
|
|
stretchfilter(target, room, user) {
|
|
if (!target) {
|
|
const stretchSetting = (room.filterStretching ? "ON" : "OFF");
|
|
return this.sendReply(`This room's stretch filter is currently: ${stretchSetting}`);
|
|
}
|
|
if (!this.canTalk()) return;
|
|
if (!this.can('editroom', null, room)) return false;
|
|
|
|
if (this.meansYes(target)) {
|
|
if (room.filterStretching) return this.errorReply(`This room's stretch filter is already ON`);
|
|
room.filterStretching = true;
|
|
} else if (this.meansNo(target)) {
|
|
if (!room.filterStretching) return this.errorReply(`This room's stretch filter is already OFF`);
|
|
room.filterStretching = false;
|
|
} else {
|
|
return this.parse("/help stretchfilter");
|
|
}
|
|
const stretchSetting = (room.filterStretching ? "ON" : "OFF");
|
|
this.privateModAction(`(${user.name} turned the stretch filter ${stretchSetting})`);
|
|
this.modlog('STRETCH FILTER', null, stretchSetting);
|
|
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.filterStretching = room.filterStretching;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
stretchfilterhelp: [`/stretchfilter [on/off] - Toggles filtering messages in the room for stretchingggggggg. Requires # & ~`],
|
|
|
|
capitals: 'capsfilter',
|
|
capitalsfilter: 'capsfilter',
|
|
capsfilter(target, room, user) {
|
|
if (!target) {
|
|
const capsSetting = (room.filterCaps ? "ON" : "OFF");
|
|
return this.sendReply(`This room's caps filter is currently: ${capsSetting}`);
|
|
}
|
|
if (!this.canTalk()) return;
|
|
if (!this.can('editroom', null, room)) return false;
|
|
|
|
if (this.meansYes(target)) {
|
|
if (room.filterCaps) return this.errorReply(`This room's caps filter is already ON`);
|
|
room.filterCaps = true;
|
|
} else if (this.meansNo(target)) {
|
|
if (!room.filterCaps) return this.errorReply(`This room's caps filter is already OFF`);
|
|
room.filterCaps = false;
|
|
} else {
|
|
return this.parse("/help capsfilter");
|
|
}
|
|
const capsSetting = (room.filterCaps ? "ON" : "OFF");
|
|
this.privateModAction(`(${user.name} turned the caps filter ${capsSetting})`);
|
|
this.modlog('CAPS FILTER', null, capsSetting);
|
|
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.filterCaps = room.filterCaps;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
capsfilterhelp: [`/capsfilter [on/off] - Toggles filtering messages in the room for EXCESSIVE CAPS. Requires # & ~`],
|
|
|
|
emojis: 'emojifilter',
|
|
emoji: 'emojifilter',
|
|
emojifilter(target, room, user) {
|
|
if (!target) {
|
|
const emojiSetting = (room.filterEmojis ? "ON" : "OFF");
|
|
return this.sendReply(`This room's emoji filter is currently: ${emojiSetting}`);
|
|
}
|
|
if (!this.canTalk()) return;
|
|
if (!this.can('editroom', null, room)) return false;
|
|
|
|
if (this.meansYes(target)) {
|
|
if (room.filterEmojis) return this.errorReply(`This room's emoji filter is already ON`);
|
|
room.filterEmojis = true;
|
|
} else if (this.meansNo(target)) {
|
|
if (!room.filterEmojis) return this.errorReply(`This room's emoji filter is already OFF`);
|
|
room.filterEmojis = false;
|
|
} else {
|
|
return this.parse("/help emojifilter");
|
|
}
|
|
const emojiSetting = (room.filterEmojis ? "ON" : "OFF");
|
|
this.privateModAction(`(${user.name} turned the emoji filter ${emojiSetting})`);
|
|
this.modlog('EMOJI FILTER', null, emojiSetting);
|
|
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.filterEmojis = room.filterEmojis;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
emojifilterhelp: [`/emojifilter [on/off] - Toggles filtering messages in the room for emojis. Requires # & ~`],
|
|
|
|
banwords: 'banword',
|
|
banword: {
|
|
regexadd: 'add',
|
|
addregex: 'add',
|
|
add(target, room, user, connection, cmd) {
|
|
if (!target || target === ' ') return this.parse('/help banword');
|
|
if (!this.can('declare', null, room)) return false;
|
|
|
|
const regex = cmd.includes('regex');
|
|
if (regex && !user.can('makeroom')) return this.errorReply("Regex banwords are only allowed for leaders or above.");
|
|
|
|
if (!room.banwords) room.banwords = [];
|
|
|
|
// Most of the regex code is copied from the client. TODO: unify them?
|
|
// Regex banwords can have commas in the {1,5} pattern
|
|
let words = (regex ? target.match(/[^,]+(,\d*}[^,]*)?/g) : target.split(','))
|
|
.map(word => word.replace(/\n/g, '').trim());
|
|
if (!words) return this.parse('/help banword');
|
|
|
|
// Escape any character with a special meaning in regex
|
|
if (!regex) {
|
|
words = words.map(word => {
|
|
if (/[\\^$*+?()|{}[\]]/.test(word)) this.errorReply(`"${word}" might be a regular expression, did you mean "/banword addregex"?`);
|
|
return word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
});
|
|
}
|
|
// PS adds a preamble to the banword regex that's 32 chars long
|
|
let banwordRegexLen = (room.banwordRegex instanceof RegExp) ? room.banwordRegex.source.length : 32;
|
|
for (let word of words) {
|
|
try {
|
|
new RegExp(word); // eslint-disable-line no-new
|
|
} catch (e) {
|
|
return this.errorReply(e.message.startsWith('Invalid regular expression: ') ? e.message : `Invalid regular expression: /${word}/: ${e.message}`);
|
|
}
|
|
if (room.banwords.includes(word)) return this.errorReply(`${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;
|
|
// RegExp instances whose source is greater than or equal to
|
|
// v8's RegExpMacroAssembler::kMaxRegister in length will crash
|
|
// 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.");
|
|
}
|
|
|
|
room.banwords = room.banwords.concat(words);
|
|
room.banwordRegex = null;
|
|
if (words.length > 1) {
|
|
this.privateModAction(`(The banwords ${words.map(w => `'${w}'`).join(', ')} were added by ${user.name}.)`);
|
|
this.modlog('BANWORD', null, words.map(w => `'${w}'`).join(', '));
|
|
this.sendReply(`Banned phrases successfully added.`);
|
|
} else {
|
|
this.privateModAction(`(The banword '${words[0]}' was added by ${user.name}.)`);
|
|
this.modlog('BANWORD', null, words[0]);
|
|
this.sendReply(`Banned phrase successfully added.`);
|
|
}
|
|
this.sendReply(`The list is currently: ${room.banwords.join(', ')}`);
|
|
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.banwords = room.banwords;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
|
|
delete(target, room, user) {
|
|
if (!target) return this.parse('/help banword');
|
|
if (!this.can('declare', null, room)) return false;
|
|
|
|
if (!room.banwords) return this.errorReply("This room has no banned phrases.");
|
|
|
|
let words = target.match(/[^,]+(,\d*}[^,]*)?/g);
|
|
if (!words) return this.parse('/help banword');
|
|
|
|
words = words.map(word => word.replace(/\n/g, '').trim());
|
|
|
|
for (let word of words) {
|
|
if (!room.banwords.includes(word)) return this.errorReply(`${word} is not a banned phrase in this room.`);
|
|
}
|
|
|
|
room.banwords = room.banwords.filter(w => !words.includes(w));
|
|
if (!room.banwords.length) room.banwords = null;
|
|
room.banwordRegex = null;
|
|
if (words.length > 1) {
|
|
this.privateModAction(`(The banwords ${words.map(w => `'${w}'`).join(', ')} were removed by ${user.name}.)`);
|
|
this.modlog('UNBANWORD', null, words.map(w => `'${w}'`).join(', '));
|
|
this.sendReply(`Banned phrases successfully deleted.`);
|
|
} else {
|
|
this.privateModAction(`(The banword '${words[0]}' was removed by ${user.name}.)`);
|
|
this.modlog('UNBANWORD', null, words[0]);
|
|
this.sendReply(`Banned phrase successfully deleted.`);
|
|
}
|
|
this.sendReply(room.banwords ? `The list is currently: ${room.banwords.join(', ')}` : `The list is now empty.`);
|
|
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.banwords = room.banwords;
|
|
if (!room.banwords) delete room.chatRoomData.banwords;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
|
|
list(target, room, user) {
|
|
if (!this.can('mute', null, room)) return false;
|
|
|
|
if (!room.banwords) return this.sendReply("This room has no banned phrases.");
|
|
|
|
return this.sendReply(`Banned phrases in room ${room.roomid}: ${room.banwords.join(', ')}`);
|
|
},
|
|
|
|
""(target, room, user) {
|
|
return this.parse("/help banword");
|
|
},
|
|
},
|
|
banwordhelp: [
|
|
`/banword add [words] - Adds the comma-separated list of phrases to the banword list of the current room. Requires: # & ~`,
|
|
`/banword addregex [words] - Adds the comma-separated list of regular expressions to the banword list of the current room. Requires & ~`,
|
|
`/banword delete [words] - Removes the comma-separated list of phrases from the banword list. Requires: # & ~`,
|
|
`/banword list - Shows the list of banned words in the current room. Requires: % @ # & ~`,
|
|
],
|
|
|
|
hightraffic(target, room, user) {
|
|
if (!target) return this.sendReply(`This room is${!room.highTraffic ? ' not' : ''} currently marked as high traffic.`);
|
|
if (!this.can('makeroom')) return false;
|
|
|
|
if (this.meansYes(target)) {
|
|
room.highTraffic = true;
|
|
} else if (this.meansNo(target)) {
|
|
room.highTraffic = false;
|
|
} else {
|
|
return this.parse('/help hightraffic');
|
|
}
|
|
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.highTraffic = room.highTraffic;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
this.modlog(`HIGHTRAFFIC`, null, room.highTraffic);
|
|
this.addModAction(`This room was marked as high traffic by ${user.name}.`);
|
|
},
|
|
hightraffichelp: [
|
|
`/hightraffic [true|false] - (Un)marks a room as a high traffic room. Requires & ~`,
|
|
`When a room is marked as high-traffic, PS requires all messages sent to that room to contain at least 2 letters.`,
|
|
],
|
|
|
|
/*********************************************************
|
|
* Room management
|
|
*********************************************************/
|
|
|
|
makeprivatechatroom: 'makechatroom',
|
|
makechatroom(target, room, user, connection, cmd) {
|
|
if (!this.can('makeroom')) return;
|
|
|
|
// `,` 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: ,|[-");
|
|
}
|
|
|
|
let id = toID(target);
|
|
if (!id) return this.parse('/help makechatroom');
|
|
if (id.length > MAX_CHATROOM_ID_LENGTH) return this.errorReply("The given room title is too long.");
|
|
// Check if the name already exists as a room or alias
|
|
if (Rooms.search(id)) return this.errorReply(`The room '${target}' already exists.`);
|
|
if (!Rooms.global.addChatRoom(target)) return this.errorReply(`An error occurred while trying to create the room '${target}'.`);
|
|
|
|
if (cmd === 'makeprivatechatroom') {
|
|
let targetRoom = Rooms.search(target);
|
|
targetRoom.isPrivate = true;
|
|
targetRoom.chatRoomData.isPrivate = true;
|
|
Rooms.global.writeChatRoomData();
|
|
if (Rooms.get('upperstaff')) {
|
|
Rooms.get('upperstaff').add(`|raw|<div class="broadcast-green">Private chat room created: <b>${Chat.escapeHTML(target)}</b></div>`).update();
|
|
}
|
|
this.sendReply(`The private chat room '${target}' was created.`);
|
|
} else {
|
|
if (Rooms.get('staff')) {
|
|
Rooms.get('staff').add(`|raw|<div class="broadcast-green">Public chat room created: <b>${Chat.escapeHTML(target)}</b></div>`).update();
|
|
}
|
|
if (Rooms.get('upperstaff')) {
|
|
Rooms.get('upperstaff').add(`|raw|<div class="broadcast-green">Public chat room created: <b>${Chat.escapeHTML(target)}</b></div>`).update();
|
|
}
|
|
this.sendReply(`The chat room '${target}' was created.`);
|
|
}
|
|
},
|
|
makechatroomhelp: [`/makechatroom [roomname] - Creates a new room named [roomname]. Requires: & ~`],
|
|
|
|
subroomgroupchat: 'makegroupchat',
|
|
makegroupchat(target, room, user, connection, cmd) {
|
|
if (!this.canTalk()) return;
|
|
if (!user.autoconfirmed) {
|
|
return this.errorReply("You must be autoconfirmed to make a groupchat.");
|
|
}
|
|
if (cmd === 'subroomgroupchat') {
|
|
if (!user.can('mute', null, room)) return this.errorReply("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.isPersonal) return this.errorReply("You cannot create a subroom of a groupchat.");
|
|
}
|
|
let parent = cmd === 'subroomgroupchat' ? room.roomid : null;
|
|
// if (!this.can('makegroupchat')) return false;
|
|
|
|
// 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.");
|
|
} else if (!title) {
|
|
title = (`${Math.floor(Math.random() * 100000000)}`);
|
|
} else if (Config.chatfilter) {
|
|
let filterResult = Config.chatfilter.call(this, title, user, null, connection);
|
|
if (!filterResult) return;
|
|
if (title !== filterResult) {
|
|
return this.errorReply("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: ,|[-");
|
|
}
|
|
|
|
// Even though they're different namespaces, to cut down on confusion, you
|
|
// can't share names with registered chatrooms.
|
|
let existingRoom = Rooms.search(toID(title));
|
|
if (existingRoom && !existingRoom.modjoin) return this.errorReply(`The room '${title}' already exists.`);
|
|
// Room IDs for groupchats are groupchat-TITLEID
|
|
let titleid = toID(title);
|
|
if (!titleid) {
|
|
titleid = `${Math.floor(Math.random() * 100000000)}`;
|
|
}
|
|
let roomid = `groupchat-${parent || user.id}-${titleid}`;
|
|
// Titles must be unique.
|
|
if (Rooms.search(roomid)) return this.errorReply(`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;
|
|
}
|
|
|
|
let titleMsg = Chat.html `Welcome to ${parent ? room.title : user.name}'s${!/^[0-9]+$/.test(title) ? ` ${title}` : ''}${parent ? ' subroom' : ''} groupchat!`;
|
|
let targetRoom = Rooms.createChatRoom(roomid, `[G] ${title}`, {
|
|
isPersonal: true,
|
|
isPrivate: 'hidden',
|
|
creationTime: parent ? null : Date.now(),
|
|
modjoin: parent ? null : '+',
|
|
parentid: parent,
|
|
auth: {},
|
|
introMessage: `<div style="text-align: center"><table style="margin:auto;"><tr><td><img src="//${Config.routes.client}/fx/groupchat.png" width=120 height=100></td><td><h2>${titleMsg}</h2><p>Follow the <a href="/rules">Pokémon Showdown Global Rules</a>!<br>Don't be disruptive to the rest of the site.</p></td></tr></table></div>`,
|
|
staffMessage: `<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) {
|
|
// The creator is a Room Owner in subroom groupchats and a Host otherwise..
|
|
targetRoom.auth[user.id] = parent ? '#' : Users.HOST_SYMBOL;
|
|
// Join after creating room. No other response is given.
|
|
user.joinRoom(targetRoom.roomid);
|
|
user.popup(`You've just made a groupchat; it is now your responsibility, regardless of whether or not you actively partake in the room. For more info, read your groupchat's staff intro.`);
|
|
if (parent) this.modlog('SUBROOMGROUPCHAT', null, title);
|
|
return;
|
|
}
|
|
return this.errorReply(`An unknown error occurred while trying to create the room '${title}'.`);
|
|
},
|
|
makegroupchathelp: [
|
|
`/makegroupchat [roomname] - Creates an invite-only group chat named [roomname].`,
|
|
`/subroomgroupchat [roomname] - Creates a subroom groupchat of the current room. Can only be used in a public room you have staff in.`,
|
|
],
|
|
|
|
'!groupchatuptime': true,
|
|
groupchatuptime(target, room, user) {
|
|
if (!room || !room.creationTime) return this.errorReply("Can only be used in a groupchat.");
|
|
if (!this.runBroadcast()) return;
|
|
const uptime = Chat.toDurationString(Date.now() - room.creationTime);
|
|
this.sendReplyBox(`Groupchat uptime: <b>${uptime}</b>`);
|
|
},
|
|
groupchatuptimehelp: [`/groupchatuptime - Displays the uptime if the current room is a groupchat.`],
|
|
|
|
deregisterchatroom(target, room, user) {
|
|
if (!this.can('makeroom')) return;
|
|
this.errorReply("NOTE: You probably want to use `/deleteroom` now that it exists.");
|
|
let id = toID(target);
|
|
if (!id) return this.parse('/help deregisterchatroom');
|
|
let targetRoom = Rooms.search(id);
|
|
if (!targetRoom) return this.errorReply(`The room '${target}' doesn't exist.`);
|
|
target = targetRoom.title || targetRoom.roomid;
|
|
const isPrivate = targetRoom.isPrivate;
|
|
const staffRoom = Rooms.get('staff');
|
|
const upperStaffRoom = Rooms.get('upperstaff');
|
|
if (Rooms.global.deregisterChatRoom(id)) {
|
|
this.sendReply(`The room '${target}' was deregistered.`);
|
|
this.sendReply("It will be deleted as of the next server restart.");
|
|
target = Chat.escapeHTML(target);
|
|
if (isPrivate) {
|
|
if (upperStaffRoom) upperStaffRoom.add(`|raw|<div class="broadcast-red">Private chat room deregistered by ${user.id}: <b>${target}</b></div>`).update();
|
|
} else {
|
|
if (staffRoom) staffRoom.add(`|raw|<div class="broadcast-red">Public chat room deregistered: <b>${target}</b></div>`).update();
|
|
if (upperStaffRoom) upperStaffRoom.add(`|raw|<div class="broadcast-red">Public chat room deregistered by ${user.id}: <b>${target}</b></div>`).update();
|
|
}
|
|
return;
|
|
}
|
|
return this.errorReply(`The room "${target}" isn't registered.`);
|
|
},
|
|
deregisterchatroomhelp: [`/deregisterchatroom [roomname] - Deletes room [roomname] after the next server restart. Requires: & ~`],
|
|
|
|
deletechatroom: 'deleteroom',
|
|
deletegroupchat: 'deleteroom',
|
|
deleteroom(target, room, user, connection, cmd) {
|
|
let roomid = target.trim();
|
|
if (!roomid) {
|
|
// allow deleting personal rooms without typing out the room name
|
|
if (!room.isPersonal || cmd !== "deletegroupchat") {
|
|
return this.parse(`/help deleteroom`);
|
|
}
|
|
} else {
|
|
let targetRoom = Rooms.search(roomid);
|
|
if (targetRoom !== room) {
|
|
return this.parse(`/help deleteroom`);
|
|
}
|
|
}
|
|
|
|
if (room.isPersonal) {
|
|
if (!this.can('gamemanagement', null, room)) return;
|
|
} else {
|
|
if (!this.can('makeroom')) return;
|
|
}
|
|
|
|
const title = room.title || room.roomid;
|
|
|
|
if (room.roomid === 'global') {
|
|
return this.errorReply(`This room can't be deleted.`);
|
|
}
|
|
|
|
if (room.chatRoomData) {
|
|
if (room.isPrivate) {
|
|
if (Rooms.get('upperstaff')) {
|
|
Rooms.get('upperstaff').add(Chat.html`|raw|<div class="broadcast-red">Private chat room deleted by ${user.id}: <b>${title}</b></div>`).update();
|
|
}
|
|
} else {
|
|
if (Rooms.get('staff')) {
|
|
Rooms.get('staff').add(Chat.html`|raw|<div class="broadcast-red">Public chat room deleted: <b>${title}</b></div>`).update();
|
|
}
|
|
if (Rooms.get('upperstaff')) {
|
|
Rooms.get('upperstaff').add(Chat.html`|raw|<div class="broadcast-red">Public chat room deleted by ${user.id}: <b>${title}</b></div>`).update();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (room.subRooms) {
|
|
for (const subRoom of room.subRooms.values()) subRoom.parent = null;
|
|
}
|
|
|
|
room.add(`|raw|<div class="broadcast-red"><b>This room has been deleted.</b></div>`);
|
|
room.update(); // |expire| needs to be its own message
|
|
room.add(`|expire|This room has been deleted.`);
|
|
this.sendReply(`The room "${title}" was deleted.`);
|
|
room.update();
|
|
room.destroy();
|
|
},
|
|
deleteroomhelp: [
|
|
`/deleteroom [roomname] - Deletes room [roomname]. Must be typed in the room to delete. Requires: & ~`,
|
|
`/deletegroupchat - Deletes the current room, if it's a groupchat. Requires: ★ # & ~`,
|
|
],
|
|
|
|
hideroom: 'privateroom',
|
|
hiddenroom: 'privateroom',
|
|
secretroom: 'privateroom',
|
|
publicroom: 'privateroom',
|
|
privateroom(target, room, user, connection, cmd) {
|
|
if (room.isPersonal) {
|
|
if (!this.can('editroom', null, room)) return;
|
|
} else if (room.battle) {
|
|
if (!this.can('editprivacy', null, room)) return;
|
|
const prefix = room.battle.forcedPublic();
|
|
if (prefix && !user.can('editprivacy')) return this.errorReply(`This battle is required to be public due to a player having a name prefixed by '${prefix}'.`);
|
|
} else {
|
|
// registered chatrooms show up on the room list and so require
|
|
// higher permissions to modify privacy settings
|
|
if (!this.can('makeroom')) return;
|
|
}
|
|
let setting;
|
|
switch (cmd) {
|
|
case 'privateroom':
|
|
return this.parse('/help privateroom');
|
|
case 'publicroom':
|
|
setting = false;
|
|
break;
|
|
case 'secretroom':
|
|
setting = true;
|
|
break;
|
|
default:
|
|
if (room.isPrivate === true && target !== 'force') {
|
|
return this.sendReply(`This room is a secret room. Use "/publicroom" to make it public, or "/hiddenroom force" to force it hidden.`);
|
|
}
|
|
setting = 'hidden';
|
|
break;
|
|
}
|
|
|
|
if ((setting === true || room.isPrivate === true) && !room.isPersonal) {
|
|
if (!this.can('makeroom')) return;
|
|
}
|
|
|
|
if (this.meansNo(target) || !setting) {
|
|
if (!room.isPrivate) {
|
|
return this.errorReply(`This room is already public.`);
|
|
}
|
|
if (room.parent && room.parent.isPrivate) {
|
|
return this.errorReply(`This room's parent ${room.parent.title} must be public for this room to be public.`);
|
|
}
|
|
if (room.isPersonal) return this.errorReply(`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.`);
|
|
}
|
|
room.privacySetter.delete(user.id);
|
|
if (room.privacySetter.size) {
|
|
const privacySetters = [...room.privacySetter].join(', ');
|
|
return this.sendReply(`You are no longer forcing the room to stay private, but ${privacySetters} also need${Chat.plural(room.privacySetter, "", "s")} to use /publicroom to make the room public.`);
|
|
}
|
|
}
|
|
delete room.isPrivate;
|
|
room.privacySetter = null;
|
|
this.addModAction(`${user.name} made this room public.`);
|
|
this.modlog('PUBLICROOM');
|
|
if (room.chatRoomData) {
|
|
delete room.chatRoomData.isPrivate;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
} else {
|
|
const settingName = (setting === true ? 'secret' : setting);
|
|
if (room.subRooms) {
|
|
if (settingName === 'secret') return this.errorReply("Secret rooms cannot have subrooms.");
|
|
for (const subRoom of room.subRooms.values()) {
|
|
if (!subRoom.isPrivate) return this.errorReply(`Subroom ${subRoom.title} must be private to make this room private.`);
|
|
}
|
|
}
|
|
if (room.isPrivate === setting) {
|
|
if (room.privacySetter && !room.privacySetter.has(user.id)) {
|
|
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}.`);
|
|
}
|
|
room.isPrivate = setting;
|
|
this.addModAction(`${user.name} made this room ${settingName}.`);
|
|
this.modlog(`${settingName.toUpperCase()}ROOM`);
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.isPrivate = setting;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
room.privacySetter = new Set([user.id]);
|
|
}
|
|
},
|
|
privateroomhelp: [
|
|
`/secretroom - Makes a room secret. Secret rooms are visible to & and up. Requires: & ~`,
|
|
`/hiddenroom [on/off] - Makes a room hidden. Hidden rooms are visible to % and up, and inherit global ranks. Requires: \u2606 & ~`,
|
|
`/publicroom - Makes a room public. Requires: \u2606 & ~`,
|
|
],
|
|
|
|
officialchatroom: 'officialroom',
|
|
officialroom(target, room, user) {
|
|
if (!this.can('makeroom')) return;
|
|
if (!room.chatRoomData) {
|
|
return this.errorReply(`/officialroom - This room can't be made official`);
|
|
}
|
|
if (this.meansNo(target)) {
|
|
if (!room.isOfficial) return this.errorReply(`This chat room is already unofficial.`);
|
|
delete room.isOfficial;
|
|
this.addModAction(`${user.name} made this chat room unofficial.`);
|
|
this.modlog('UNOFFICIALROOM');
|
|
delete room.chatRoomData.isOfficial;
|
|
Rooms.global.writeChatRoomData();
|
|
} else {
|
|
if (room.isOfficial) return this.errorReply(`This chat room is already official.`);
|
|
room.isOfficial = true;
|
|
this.addModAction(`${user.name} made this chat room official.`);
|
|
this.modlog('OFFICIALROOM');
|
|
room.chatRoomData.isOfficial = true;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
|
|
psplwinnerroom(target, room, user) {
|
|
if (!this.can('makeroom')) return;
|
|
if (!room.chatRoomData) {
|
|
return this.errorReply(`/psplwinnerroom - This room can't be marked as a PSPL Winner room`);
|
|
}
|
|
if (this.meansNo(target)) {
|
|
if (!room.pspl) return this.errorReply(`This chat room is already not a PSPL Winner room.`);
|
|
delete room.pspl;
|
|
this.addModAction(`${user.name} made this chat room no longer a PSPL Winner room.`);
|
|
this.modlog('PSPLROOM');
|
|
delete room.chatRoomData.pspl;
|
|
Rooms.global.writeChatRoomData();
|
|
} else {
|
|
if (room.pspl) return this.errorReply("This chat room is already a PSPL Winner room.");
|
|
room.pspl = true;
|
|
this.addModAction(`${user.name} made this chat room a PSPL Winner room.`);
|
|
this.modlog('UNPSPLROOM');
|
|
room.chatRoomData.pspl = true;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
|
|
setsubroom: 'subroom',
|
|
subroom(target, room, user) {
|
|
if (!user.can('makeroom')) return this.errorReply(`/subroom - Access denied. Did you mean /subrooms?`);
|
|
if (!target) return this.parse('/help subroom');
|
|
|
|
if (!room.chatRoomData) return this.errorReply(`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.`);
|
|
if (room.subRooms) return this.errorReply(`This room is already a parent room, and a parent room cannot be made as a subroom.`);
|
|
|
|
const main = Rooms.search(target);
|
|
|
|
if (!main) return this.errorReply(`The room '${target}' does not exist.`);
|
|
if (main.parent) return this.errorReply(`Subrooms cannot have subrooms.`);
|
|
if (main.isPrivate === true) return this.errorReply(`Only public and hidden rooms can have subrooms.`);
|
|
if (main.isPrivate && !room.isPrivate) return this.errorReply(`Private rooms cannot have public subrooms.`);
|
|
if (!main.chatRoomData) return this.errorReply(`Temporary rooms cannot be parent rooms.`);
|
|
if (room === main) return this.errorReply(`You cannot set a room to be a subroom of itself.`);
|
|
|
|
room.parent = main;
|
|
if (!main.subRooms) main.subRooms = new Map();
|
|
main.subRooms.set(room.roomid, room);
|
|
|
|
const mainIdx = Rooms.global.chatRoomDataList.findIndex(r => r.title === main.title);
|
|
const subIdx = Rooms.global.chatRoomDataList.findIndex(r => r.title === room.title);
|
|
|
|
// This is needed to ensure that the main room gets loaded before the subroom.
|
|
if (mainIdx > subIdx) {
|
|
const tmp = Rooms.global.chatRoomDataList[mainIdx];
|
|
Rooms.global.chatRoomDataList[mainIdx] = Rooms.global.chatRoomDataList[subIdx];
|
|
Rooms.global.chatRoomDataList[subIdx] = tmp;
|
|
}
|
|
|
|
room.chatRoomData.parentid = main.roomid;
|
|
Rooms.global.writeChatRoomData();
|
|
|
|
for (let userid in room.users) {
|
|
room.users[userid].updateIdentity(room.roomid);
|
|
}
|
|
|
|
this.modlog('SUBROOM', null, `of ${main.title}`);
|
|
return this.addModAction(`This room was set as a subroom of ${main.title} by ${user.name}.`);
|
|
},
|
|
|
|
removesubroom: 'unsubroom',
|
|
desubroom: 'unsubroom',
|
|
unsubroom(target, room, user) {
|
|
if (!this.can('makeroom')) return;
|
|
if (!room.parent || !room.chatRoomData) return this.errorReply(`This room is not currently a subroom of a public room.`);
|
|
|
|
const parent = room.parent;
|
|
if (parent && parent.subRooms) {
|
|
parent.subRooms.delete(room.roomid);
|
|
if (!parent.subRooms.size) parent.subRooms = null;
|
|
}
|
|
|
|
room.parent = null;
|
|
|
|
delete room.chatRoomData.parentid;
|
|
Rooms.global.writeChatRoomData();
|
|
|
|
for (let userid in room.users) {
|
|
room.users[userid].updateIdentity(room.roomid);
|
|
}
|
|
|
|
this.modlog('UNSUBROOM');
|
|
return this.addModAction(`This room was unset as a subroom by ${user.name}.`);
|
|
},
|
|
|
|
parentroom: 'subrooms',
|
|
subrooms(target, room, user, connection, cmd) {
|
|
if (cmd === 'parentroom') {
|
|
if (!room.parent) return this.errorReply(`This room is not a subroom.`);
|
|
return this.sendReply(`This is a subroom of ${room.parent.title}.`);
|
|
}
|
|
if (!room.chatRoomData) return this.errorReply(`Temporary rooms cannot have subrooms.`);
|
|
|
|
if (!this.runBroadcast()) return;
|
|
|
|
const showSecret = !this.broadcasting && user.can('mute', null, room);
|
|
|
|
const subRooms = room.getSubRooms(showSecret);
|
|
|
|
if (!subRooms.length) return this.sendReply(`This room doesn't have any subrooms.`);
|
|
|
|
const subRoomText = subRooms.map(room => Chat.html`<a href="/${room.roomid}">${room.title}</a><br/><small>${room.desc}</small>`);
|
|
|
|
return this.sendReplyBox(`<p style="font-weight:bold;">${Chat.escapeHTML(room.title)}'s subroom${Chat.plural(subRooms)}:</p><ul><li>${subRoomText.join('</li><br/><li>')}</li></ul></strong>`);
|
|
},
|
|
|
|
subroomhelp: [
|
|
`/subroom [room] - Marks the current room as a subroom of [room]. Requires: & ~`,
|
|
`/unsubroom - Unmarks the current room as a subroom. Requires: & ~`,
|
|
`/subrooms - Displays the current room's subrooms.`,
|
|
`/parentroom - Displays the current room's parent room.`,
|
|
],
|
|
|
|
roomdesc(target, room, user) {
|
|
if (!target) {
|
|
if (!this.runBroadcast()) return;
|
|
if (!room.desc) return this.sendReply(`This room does not have a description set.`);
|
|
this.sendReplyBox(Chat.html`The room description is: ${room.desc}`);
|
|
return;
|
|
}
|
|
if (!this.can('declare')) return false;
|
|
if (target.length > 80) return this.errorReply(`Error: Room description is too long (must be at most 80 characters).`);
|
|
let 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".`);
|
|
}
|
|
if (normalizedTarget.slice(0, 9) === ' discuss ') {
|
|
return this.errorReply(`Error: Room description must not start with the word "discuss".`);
|
|
}
|
|
if (normalizedTarget.slice(0, 12) === ' talk about ' || normalizedTarget.slice(0, 17) === ' talk here about ') {
|
|
return this.errorReply(`Error: Room description must not start with the phrase "talk about".`);
|
|
}
|
|
|
|
room.desc = target;
|
|
this.sendReply(`(The room description is now: ${target})`);
|
|
|
|
this.privateModAction(`(${user.name} changed the roomdesc to: "${target}".)`);
|
|
this.modlog('ROOMDESC', null, `to "${target}"`);
|
|
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.desc = room.desc;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
|
|
topic: 'roomintro',
|
|
roomintro(target, room, user, connection, cmd) {
|
|
if (!target) {
|
|
if (!this.runBroadcast()) return;
|
|
if (!room.introMessage) return this.sendReply("This room does not have an introduction set.");
|
|
this.sendReply('|raw|<div class="infobox infobox-limited">' + room.introMessage.replace(/\n/g, '') + '</div>');
|
|
if (!this.broadcasting && user.can('declare', null, room) && cmd !== 'topic') {
|
|
this.sendReply('Source:');
|
|
const code = Chat.escapeHTML(room.introMessage).replace(/\n/g, '<br />');
|
|
this.sendReplyBox(`<code style="white-space: pre-wrap">/roomintro ${code}</code>`);
|
|
}
|
|
return;
|
|
}
|
|
if (!this.can('editroom', null, room)) return false;
|
|
if (this.meansNo(target) || target === 'delete') return this.errorReply('Did you mean "/deleteroomintro"?');
|
|
target = this.canHTML(target);
|
|
if (!target) return;
|
|
if (!/</.test(target)) {
|
|
// not HTML, do some simple URL linking
|
|
let re = /(https?:\/\/(([\w.-]+)+(:\d+)?(\/([\w/_.]*(\?\S+)?)?)?))/g;
|
|
target = target.replace(re, '<a href="$1">$1</a>');
|
|
}
|
|
if (target.substr(0, 11) === '/roomintro ') target = target.substr(11);
|
|
|
|
room.introMessage = target.replace(/\r/g, '');
|
|
this.sendReply("(The room introduction has been changed to:)");
|
|
this.sendReply(`|raw|<div class="infobox infobox-limited">${room.introMessage.replace(/\n/g, '')}</div>`);
|
|
|
|
this.privateModAction(`(${user.name} changed the roomintro.)`);
|
|
this.modlog('ROOMINTRO');
|
|
this.roomlog(room.introMessage.replace(/\n/g, ''));
|
|
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.introMessage = room.introMessage;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
|
|
deletetopic: 'deleteroomintro',
|
|
deleteroomintro(target, room, user) {
|
|
if (!this.can('declare', null, room)) return false;
|
|
if (!room.introMessage) return this.errorReply("This room does not have a introduction set.");
|
|
|
|
this.privateModAction(`(${user.name} deleted the roomintro.)`);
|
|
this.modlog('DELETEROOMINTRO');
|
|
this.roomlog(target);
|
|
|
|
delete room.introMessage;
|
|
if (room.chatRoomData) {
|
|
delete room.chatRoomData.introMessage;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
|
|
stafftopic: 'staffintro',
|
|
staffintro(target, room, user, connection, cmd) {
|
|
if (!target) {
|
|
if (!this.can('mute', null, room)) return false;
|
|
if (!room.staffMessage) return this.sendReply("This room does not have a staff introduction set.");
|
|
this.sendReply(`|raw|<div class="infobox">${room.staffMessage.replace(/\n/g, ``)}</div>`);
|
|
if (user.can('ban', null, room) && cmd !== 'stafftopic') {
|
|
this.sendReply('Source:');
|
|
const code = Chat.escapeHTML(room.staffMessage).replace(/\n/g, '<br />');
|
|
this.sendReplyBox(`<code style="white-space: pre-wrap">/staffintro ${code}</code>`);
|
|
}
|
|
return;
|
|
}
|
|
if (!this.can('ban', null, room)) return false;
|
|
if (!this.canTalk()) return;
|
|
if (this.meansNo(target) || target === 'delete') return this.errorReply('Did you mean "/deletestaffintro"?');
|
|
target = this.canHTML(target);
|
|
if (!target) return;
|
|
if (!/</.test(target)) {
|
|
// not HTML, do some simple URL linking
|
|
let re = /(https?:\/\/(([\w.-]+)+(:\d+)?(\/([\w/_.]*(\?\S+)?)?)?))/g;
|
|
target = target.replace(re, '<a href="$1">$1</a>');
|
|
}
|
|
if (target.substr(0, 12) === '/staffintro ') target = target.substr(12);
|
|
|
|
room.staffMessage = target.replace(/\r/g, '');
|
|
this.sendReply("(The staff introduction has been changed to:)");
|
|
this.sendReply(`|raw|<div class="infobox">${target.replace(/\n/g, ``)}</div>`);
|
|
|
|
this.privateModAction(`(${user.name} changed the staffintro.)`);
|
|
this.modlog('STAFFINTRO');
|
|
this.roomlog(room.staffMessage.replace(/\n/g, ``));
|
|
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.staffMessage = room.staffMessage;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
|
|
deletestafftopic: 'deletestaffintro',
|
|
deletestaffintro(target, room, user) {
|
|
if (!this.can('ban', null, room)) return false;
|
|
if (!room.staffMessage) return this.errorReply("This room does not have a staff introduction set.");
|
|
|
|
this.privateModAction(`(${user.name} deleted the staffintro.)`);
|
|
this.modlog('DELETESTAFFINTRO');
|
|
this.roomlog(target);
|
|
|
|
delete room.staffMessage;
|
|
if (room.chatRoomData) {
|
|
delete room.chatRoomData.staffMessage;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
|
|
roomalias(target, room, user) {
|
|
if (!target) {
|
|
if (!this.runBroadcast()) return;
|
|
if (!room.aliases || !room.aliases.length) return this.sendReplyBox("This room does not have any aliases.");
|
|
return this.sendReplyBox(`This room has the following aliases: ${room.aliases.join(", ")}`);
|
|
}
|
|
if (!this.can('makeroom')) return false;
|
|
if (target.includes(',')) {
|
|
this.errorReply(`Invalid room alias: ${target.trim()}`);
|
|
return this.parse('/help roomalias');
|
|
}
|
|
|
|
let alias = toID(target);
|
|
if (!alias.length) return this.errorReply("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.");
|
|
if (room.isPersonal) return this.errorReply("Personal rooms can't have aliases.");
|
|
|
|
Rooms.aliases.set(alias, room.roomid);
|
|
this.privateModAction(`(${user.name} added the room alias '${alias}'.)`);
|
|
this.modlog('ROOMALIAS', null, alias);
|
|
|
|
if (!room.aliases) room.aliases = [];
|
|
room.aliases.push(alias);
|
|
if (room.chatRoomData) {
|
|
room.chatRoomData.aliases = room.aliases;
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
roomaliashelp: [
|
|
`/roomalias - displays a list of all room aliases of the room the command was entered in.`,
|
|
`/roomalias [alias] - adds the given room alias to the room the command was entered in. Requires: & ~`,
|
|
`/removeroomalias [alias] - removes the given room alias of the room the command was entered in. Requires: & ~`,
|
|
],
|
|
|
|
deleteroomalias: 'removeroomalias',
|
|
deroomalias: 'removeroomalias',
|
|
unroomalias: 'removeroomalias',
|
|
removeroomalias(target, room, user) {
|
|
if (!room.aliases) return this.errorReply("This room does not have any aliases.");
|
|
if (!this.can('makeroom')) return false;
|
|
if (target.includes(',')) {
|
|
this.errorReply(`Invalid room alias: ${target.trim()}`);
|
|
return this.parse('/help removeroomalias');
|
|
}
|
|
|
|
let alias = toID(target);
|
|
if (!alias || !Rooms.aliases.has(alias)) return this.errorReply("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.");
|
|
|
|
this.privateModAction(`(${user.name} removed the room alias '${alias}'.)`);
|
|
this.modlog('REMOVEALIAS', null, alias);
|
|
|
|
let aliasIndex = room.aliases.indexOf(alias);
|
|
if (aliasIndex >= 0) {
|
|
room.aliases.splice(aliasIndex, 1);
|
|
Rooms.aliases.delete(alias);
|
|
Rooms.global.writeChatRoomData();
|
|
}
|
|
},
|
|
removeroomaliashelp: [`/removeroomalias [alias] - removes the given room alias of the room the command was entered in. Requires: & ~`],
|
|
|
|
};
|