From 0b9d8dd109f0452bec0a592ee2c1940fefc4ee73 Mon Sep 17 00:00:00 2001 From: whales Date: Sat, 7 Dec 2019 00:46:55 +1030 Subject: [PATCH] Support declarative roomsettings syntax (#6119) --- server/chat-commands/room-settings.ts | 307 +++++++++----------------- server/chat-plugins/blackjack.js | 8 + server/chat-plugins/hangman.js | 14 +- server/chat-plugins/mafia.js | 11 + server/chat-plugins/uno.js | 14 +- server/chat.ts | 19 +- server/global-types.ts | 2 + server/tournaments/index.ts | 10 + 8 files changed, 179 insertions(+), 206 deletions(-) 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, };