diff --git a/server/chat-commands/room-settings.ts b/server/chat-commands/room-settings.ts
index 6de86e12ee..8f689055a4 100644
--- a/server/chat-commands/room-settings.ts
+++ b/server/chat-commands/room-settings.ts
@@ -9,7 +9,7 @@
'use strict';
-const RANKS = Config.groupsranking;
+const RANKS: string[] = Config.groupsranking;
const SLOWCHAT_MINIMUM = 2;
const SLOWCHAT_MAXIMUM = 60;
@@ -17,218 +17,41 @@ const SLOWCHAT_USER_REQUIREMENT = 10;
const MAX_CHATROOM_ID_LENGTH = 225;
-class RoomSettings {
- room: BasicChatRoom;
- user: User;
- connection: Connection;
- sameCommand: boolean;
- constructor(user: User, room: BasicChatRoom, connection: Connection) {
- this.room = room;
- this.user = user;
- this.connection = connection;
- this.sameCommand = true;
- }
- updateSetting(command: string) {
- this.sameCommand = false;
- this.generateDisplay();
- }
- button(setting: string, disable: boolean, command = ' ') {
- if (disable) {
- return Chat.html` `;
- }
- return Chat.html` `;
- }
- modchat() {
- if (!this.user.can('modchat', null, this.room)) {
- return this.button(this.room.modchat ? this.room.modchat : 'off', true);
- }
- const 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', false, 'modchat off'));
- } else if (rank === this.room.modchat) {
- modchatOutput.push(this.button(rank, true));
- } else if (rank) {
- const rankIndex = RANKS.indexOf(rank);
- const roomAuth = (this.room.auth && this.room.auth[this.user.id] ? this.room.auth[this.user.id] : false);
- const 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, false, `modchat ${rank}`));
- }
- }
- // Since autoconfirmed isn't technically a Config rank...
- const 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);
- }
- const 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', false, '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', false, 'stretchfilter off')} ${this.button('filter stretching', true)}`;
- } else {
- return `${this.button('off', true)} ${this.button('filter stretching', false, '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', false, 'capsfilter off')} ${this.button('filter capitals', true)}`;
- } else {
- return `${this.button('off', true)} ${this.button('filter capitals', false, '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', false, 'emojifilter off')} ${this.button('filter emojis', true)}`;
- } else {
- return `${this.button('off', true)} ${this.button('filter emojis', false, '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);
- }
- const slowchatOutput = [];
- for (const 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`, false, `slowchat ${i}`));
- }
- }
- if (!this.room.slowchat) {
- slowchatOutput.unshift(this.button('off', true));
- } else {
- slowchatOutput.unshift(this.button('off', false, '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('%', false, 'tournament enable %')} ${this.button('@', true)} ${this.button('#', false, 'tournament disable')}`;
- } else if (this.room.toursEnabled === '%') {
- return `${this.button('%', true)} ${this.button('@', false, 'tournament enable @')} ${this.button('#', false, 'tournament disable')}`;
- } else {
- return `${this.button('%', false, 'tournament enable %')} ${this.button('@', false, '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', false, 'uno enable')} ${this.button('off', true)}`;
- } else {
- return `${this.button('UNO enabled', true)} ${this.button('off', false, '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', false, 'hangman enable')} ${this.button('off', true)}`;
- } else {
- return `${this.button('Hangman enabled', true)} ${this.button('off', false, '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', false, 'mafia enable')} ${this.button('off', true)}`;
- } else {
- return `${this.button('Mafia enabled', true)} ${this.button('off', false, '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', false, 'blackjack enable')} ${this.button('off', true)}`;
- } else {
- return `${this.button('Blackjack enabled', true)} ${this.button('off', false, '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);
- }
-
- const languageOutput = [];
- languageOutput.push(this.button(`English`, !this.room.language, 'roomlanguage english'));
- for (const [id, text] of Chat.languages) {
- languageOutput.push(this.button(text, this.room.language === id, `roomlanguage ${id}`));
- }
- return languageOutput.join(' ');
- }
- generateDisplay() {
- let output = Chat.html`
Room Settings for ${this.room.title}
`;
- output += `Language:
${this.language()}
`;
- output += `Modchat:
${this.modchat()}
`;
- output += `Modjoin:
${this.modjoin()}
`;
- output += `Stretch filter:
${this.stretching()}
`;
- output += `Caps filter:
${this.capitals()}
`;
- output += `Emoji filter:
${this.emojis()}
`;
- output += `Slowchat:
${this.slowchat()}
`;
- output += `Tournaments:
${this.tourStatus()}
`;
- output += `UNO:
${this.uno()}
`;
- output += `Hangman:
${this.hangman()}
`;
- output += `Blackjack:
${this.blackjack()}
`;
- output += `Mafia:
${this.mafia()}
`;
- output += '
';
-
- this.user.sendTo(this.room, `|uhtml${(this.sameCommand ? '' : 'change')}|roomsettings|${output}`);
- }
-}
-
export const commands: ChatCommands = {
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);
+ let uhtml = 'uhtml';
if (!target) {
room.update();
- settings.generateDisplay();
} else {
this.parse(`/${target}`);
- settings.updateSetting(target);
+ uhtml = 'uhtmlchange';
}
+
+ let output = Chat.html`Room Settings for ${room.title}
`;
+ for (const handler of Chat.roomSettings) {
+ const setting = handler(room, user, connection);
+ if (typeof setting.permission === 'string') setting.permission = user.can(setting.permission, null, room);
+
+ output += `${setting.label}:
`;
+
+ for (const option of setting.options) {
+ // disabled button
+ if (option[1] === true) {
+ output += Chat.html` `;
+ } else {
+ // only show proper buttons if we have the permissions to use them
+ if (!setting.permission) continue;
+
+ output += Chat.html` `;
+ }
+ }
+ output += `
`;
+ }
+ output += '
';
+ user.sendTo(room, `|${uhtml}|roomsettings|${output}`);
},
roomsettingshelp: [`/roomsettings - Shows current room settings with buttons to change them (if you can).`],
@@ -1382,3 +1205,81 @@ export const commands: ChatCommands = {
},
removeroomaliashelp: [`/removeroomalias [alias] - removes the given room alias of the room the command was entered in. Requires: & ~`],
};
+
+export const roomSettings: SettingsHandler[] = [
+ // modchat
+ (room, user) => {
+ const threshold = user.can('makeroom') ? Infinity :
+ user.can('modchatall', null, room) ? Config.groupsranking.indexOf(room.getAuth(user)) :
+ user.can('modchat', null, room) ? 1 :
+ null;
+
+ const permission = !!threshold;
+
+ // typescript seems to think that [prop, true] is of type [prop, boolean] unless we tell it explicitly
+ const options: [string, string | true][] = !permission ? [[room.modchat || 'off', true]] :
+ [
+ 'off',
+ 'autoconfirmed',
+ 'trusted',
+ ...RANKS.slice(1, threshold + 1),
+ ].map(rank => [rank, (rank === 'off' ? !room.modchat : rank === room.modchat) || `modchat ${rank || 'off'}`]);
+
+ return {
+ label: "Modchat",
+ permission,
+ options,
+ };
+ },
+ (room, user) => ({
+ label: "Modjoin",
+ permission: room.isPersonal ? user.can('editroom', null, room) : user.can('makeroom'),
+ options: [
+ 'off',
+ 'autoconfirmed',
+ // groupchat ROs can set modjoin, but only to +
+ // first rank is for modjoin off
+ ...RANKS.slice(1, room.isPersonal && !user.can('makeroom') ? 2 : undefined),
+ ].map(rank =>
+ [rank, (rank === 'off' ? !room.modjoin : rank === room.modjoin) || `modjoin ${rank || 'off'}`]
+ ),
+ }),
+ room => ({
+ label: "Language",
+ permission: 'editroom',
+ options: [...Chat.languages].map(([id, name]) =>
+ [name, (id === 'english' ? !room.language : id === room.language) || `roomlanguage ${id}`]
+ ),
+ }),
+ room => ({
+ label: "Stretch filter",
+ permission: 'editroom',
+ options: [
+ [`off`, !room.filterStretching || 'stretchfilter off'],
+ [`on`, room.filterStretching || 'stretchfilter on'],
+ ],
+ }),
+ room => ({
+ label: "Caps filter",
+ permission: 'editroom',
+ options: [
+ [`off`, !room.filterCaps || 'capsfilter off'],
+ [`on`, room.filterCaps || 'capsfilter on'],
+ ],
+ }),
+ room => ({
+ label: "Emoji filter",
+ permission: 'editroom',
+ options: [
+ [`off`, !room.filterEmojis || 'emojifilter off'],
+ [`on`, room.filterEmojis || 'emojifilter on'],
+ ],
+ }),
+ room => ({
+ label: "Slowchat",
+ permission: room.userCount < SLOWCHAT_USER_REQUIREMENT ? 'bypassall' : 'editroom',
+ options: ['off', 5, 10, 20, 30, 60].map(time =>
+ [`${time}`, (time === 'off' ? !room.slowchat : time === room.slowchat) || `slowchat ${time || 'false'}`]
+ ),
+ }),
+];
diff --git a/server/chat-plugins/blackjack.js b/server/chat-plugins/blackjack.js
index a88ba1cb59..587466a289 100644
--- a/server/chat-plugins/blackjack.js
+++ b/server/chat-plugins/blackjack.js
@@ -651,3 +651,11 @@ exports.commands = {
"/blackjack enable - Allows games of blackjack to be made in the room. Requires: # & ~",
],
};
+exports.roomSettings = room => ({
+ label: "Blackjack",
+ permission: 'editroom',
+ options: [
+ [`disabled`, room.blackjackDisabled || 'blackjack disable'],
+ [`enabled`, !room.blackjackDisabled || 'blackjack enable'],
+ ],
+});
diff --git a/server/chat-plugins/hangman.js b/server/chat-plugins/hangman.js
index 0f707d7308..68df279c96 100644
--- a/server/chat-plugins/hangman.js
+++ b/server/chat-plugins/hangman.js
@@ -352,5 +352,17 @@ const commands = {
`/hangman guess [word] - Same as a letter, but guesses an entire word.`,
],
};
+/** @type {SettingsHandler} */
+const roomSettings = room => ({
+ label: "Hangman",
+ permission: 'editroom',
+ options: [
+ [`disabled`, room.hangmanDisabled || 'hangman disable'],
+ [`enabled`, !room.hangmanDisabled || 'hangman enable'],
+ ],
+});
-exports.commands = commands;
+module.exports = {
+ commands,
+ roomSettings,
+};
diff --git a/server/chat-plugins/mafia.js b/server/chat-plugins/mafia.js
index 880ba096be..3f20f78dd0 100644
--- a/server/chat-plugins/mafia.js
+++ b/server/chat-plugins/mafia.js
@@ -3285,9 +3285,20 @@ const commands = {
},
};
+/** @type {SettingsHandler} */
+const roomSettings = room => ({
+ label: "Mafia",
+ permission: 'editroom',
+ options: [
+ [`disabled`, room.mafiaDisabled || 'mafia disable'],
+ [`enabled`, !room.mafiaDisabled || 'mafia enable'],
+ ],
+});
+
module.exports = {
commands,
pages,
+ roomSettings,
};
process.nextTick(() => {
diff --git a/server/chat-plugins/uno.js b/server/chat-plugins/uno.js
index b0de74c113..a04fbf67b9 100644
--- a/server/chat-plugins/uno.js
+++ b/server/chat-plugins/uno.js
@@ -973,5 +973,17 @@ const commands = {
`/uno suppress [on|off] - Toggles suppression of game messages.`,
],
};
+/** @type {SettingsHandler} */
+const roomSettings = room => ({
+ label: "UNO",
+ permission: 'editroom',
+ options: [
+ [`disabled`, room.unoDisabled || 'uno disable'],
+ [`enabled`, !room.unoDisabled || 'uno enable'],
+ ],
+});
-exports.commands = commands;
+module.exports = {
+ commands,
+ roomSettings,
+};
diff --git a/server/chat.ts b/server/chat.ts
index 1c4089c1f2..6fd85a7f5c 100644
--- a/server/chat.ts
+++ b/server/chat.ts
@@ -44,6 +44,17 @@ export interface ChatCommands {
[k: string]: ChatHandler | string | string[] | true | ChatCommands;
}
+export type SettingsHandler = (
+ room: BasicChatRoom,
+ user: User,
+ connection: Connection
+) => {
+ label: string,
+ permission: boolean | string,
+ // button label, command | disabled
+ options: [string, string | true][],
+};
+
/**
* Chat filters can choose to:
* 1. return false OR null - to not send a user's message
@@ -1128,6 +1139,7 @@ export const Chat = new class {
basePages: PageTable = undefined!;
pages: PageTable = undefined!;
readonly destroyHandlers: (() => void)[] = [];
+ roomSettings: SettingsHandler[] = [];
/*********************************************************
* Load chat filters
@@ -1253,6 +1265,8 @@ export const Chat = new class {
loadTranslations() {
return FS(TRANSLATION_DIRECTORY).readdir().then(files => {
+ // ensure that english is the first entry when we iterate over Chat.languages
+ Chat.languages.set('english', 'English');
for (const fname of files) {
if (!fname.endsWith('.json')) continue;
@@ -1423,7 +1437,10 @@ export const Chat = new class {
if (plugin.pages) Object.assign(Chat.pages, plugin.pages);
if (plugin.destroy) Chat.destroyHandlers.push(plugin.destroy);
-
+ if (plugin.roomSettings) {
+ if (!Array.isArray(plugin.roomSettings)) plugin.roomSettings = [plugin.roomSettings];
+ Chat.roomSettings = Chat.roomSettings.concat(plugin.roomSettings);
+ }
if (plugin.chatfilter) Chat.filters.push(plugin.chatfilter);
if (plugin.namefilter) Chat.namefilters.push(plugin.namefilter);
if (plugin.hostfilter) Chat.hostfilters.push(plugin.hostfilter);
diff --git a/server/global-types.ts b/server/global-types.ts
index 54e07c0c39..e58fc50452 100644
--- a/server/global-types.ts
+++ b/server/global-types.ts
@@ -7,6 +7,7 @@ type CommandContext = Chat.CommandContext;
type PageContext = Chat.PageContext;
type PageTable = Chat.PageTable;
type ChatCommands = Chat.ChatCommands;
+type SettingsHandler = Chat.SettingsHandler;
type ChatFilter = Chat.ChatFilter;
type NameFilter = Chat.NameFilter;
type StatusFilter = Chat.StatusFilter;
@@ -14,6 +15,7 @@ type LoginFilter = Chat.LoginFilter;
namespace Chat {
export type CommandContext = import('./chat').CommandContext;
export type PageContext = import('./chat').PageContext;
+ export type SettingsHandler = import('./chat').SettingsHandler;
export type PageTable = import('./chat').PageTable;
export type ChatCommands = import('./chat').ChatCommands;
export type ChatFilter = import('./chat').ChatFilter;
diff --git a/server/tournaments/index.ts b/server/tournaments/index.ts
index 187a65bb1e..b3b3f8bb3d 100644
--- a/server/tournaments/index.ts
+++ b/server/tournaments/index.ts
@@ -1761,6 +1761,15 @@ export const commands: ChatCommands = {
);
},
};
+const roomSettings: SettingsHandler = room => ({
+ label: "Tournaments",
+ permission: 'gamemanagement',
+ options: [
+ ['%', room.toursEnabled === '%' || 'tournament enable %'],
+ ['@', room.toursEnabled === true || 'tournament enable @'],
+ ['#', room.toursEnabled === false || 'tournament disable'],
+ ],
+});
export const Tournaments = {
TournamentGenerators,
@@ -1769,4 +1778,5 @@ export const Tournaments = {
createTournament,
tourCommands,
commands,
+ roomSettings,
};