diff --git a/client/common/configuration/templates/channel.mustache b/client/common/configuration/templates/channel.mustache index 2d25a253..ffa65f62 100644 --- a/client/common/configuration/templates/channel.mustache +++ b/client/common/configuration/templates/channel.mustache @@ -58,7 +58,7 @@ name="forbidden_words_{{fieldNumber}}" id="peertube-livechat-forbidden-words-{{fieldNumber}}" class="form-control" - >{{entries}} + >{{joinedEntries}}

{{forbiddenWordsDesc2}}

@@ -121,7 +121,7 @@ name="quote_{{fieldNumber}}" id="peertube-livechat-quote-{{fieldNumber}}" class="form-control" - >{{TODO}} + >{{joinedMessages}}

{{quoteDesc2}}

@@ -134,7 +134,7 @@ name="quote_delay_{{fieldNumber}}" class="form-control" id="peertube-livechat-quote-delay-{{fieldNumber}}" - value="5" + value="{{delay}}" />

{{quoteDelayDesc}}

@@ -159,7 +159,7 @@ name="command_{{fieldNumber}}" class="form-control" id="peertube-livechat-command-{{fieldNumber}}" - value="" + value="{{command}}" />

{{commandCmdDesc}}

@@ -170,7 +170,7 @@ name="command_message_{{fieldNumber}}" class="form-control" id="peertube-livechat-command-message-{{fieldNumber}}" - value="" + value="{{message}}" />

{{commandMessageDesc}}

diff --git a/client/common/configuration/templates/logic/channel.ts b/client/common/configuration/templates/logic/channel.ts index ce57d4aa..b8f9a172 100644 --- a/client/common/configuration/templates/logic/channel.ts +++ b/client/common/configuration/templates/logic/channel.ts @@ -33,37 +33,90 @@ async function getConfigurationChannelViewData ( throw new Error('Invalid channel configuration options.') } - const forbiddenWordsArray = [] + const forbiddenWordsArray: Object[] = [] for (let i = 0; i < channelConfiguration.configuration.bot.forbiddenWords.length; i++) { const fw = channelConfiguration.configuration.bot.forbiddenWords[i] forbiddenWordsArray.push({ displayNumber: i + 1, fieldNumber: i, displayHelp: i === 0, - entries: fw.entries.join('\n'), + joinedEntries: fw.entries.join('\n'), regexp: !!fw.regexp, applyToModerators: fw.applyToModerators, reason: fw.reason }) } + // Ensuring we have at least N blocks: + while (forbiddenWordsArray.length < 3) { + const i = forbiddenWordsArray.length + // default value + forbiddenWordsArray.push({ + displayNumber: i + 1, + fieldNumber: i, + displayHelp: i === 0, + joinedEntries: '', + regexp: false, + applyToModerators: false, + reason: '' + }) + continue + } + + const quotesArray: Object[] = [] + for (let i = 0; i < channelConfiguration.configuration.bot.quotes.length; i++) { + const qs = channelConfiguration.configuration.bot.quotes[i] + quotesArray.push({ + displayNumber: i + 1, + fieldNumber: i, + displayHelp: i === 0, + joinedMessages: qs.messages.join('\n'), + delay: Math.round(qs.delay / 60) // converting to minutes + }) + } + // Ensuring we have at least N blocks: + while (quotesArray.length < 1) { + const i = quotesArray.length + // default value + quotesArray.push({ + displayNumber: i + 1, + fieldNumber: i, + displayHelp: i === 0, + joinedMessages: '', + delay: 5 + }) + continue + } + + const cmdsArray: Object[] = [] + for (let i = 0; i < channelConfiguration.configuration.bot.commands.length; i++) { + const cs = channelConfiguration.configuration.bot.commands[i] + cmdsArray.push({ + displayNumber: i + 1, + fieldNumber: i, + displayHelp: i === 0, + message: cs.message, + command: cs.command + }) + } + // Ensuring we have at least N blocks: + while (cmdsArray.length < 3) { + const i = cmdsArray.length + // default value + cmdsArray.push({ + displayNumber: i + 1, + fieldNumber: i, + displayHelp: i === 0, + message: '', + command: '' + }) + continue + } return { channelConfiguration, forbiddenWordsArray, - quotesArray: [0].map(count => { - return { - displayNumber: count + 1, - fieldNumber: count, - displayHelp: count === 0 - } - }), - cmdsArray: [0, 1, 2].map(count => { - return { - displayNumber: count + 1, - fieldNumber: count, - displayHelp: count === 0 - } - }) + quotesArray, + cmdsArray } } @@ -110,7 +163,9 @@ async function vivifyConfigurationChannel ( // TODO: handle form errors. for (let i = 0; data.has('forbidden_words_' + i.toString()); i++) { - const entries = (data.get('forbidden_words_' + i.toString())?.toString() ?? '').split(/\r?\n|\r|\n/g) + const entries = (data.get('forbidden_words_' + i.toString())?.toString() ?? '') + .split(/\r?\n|\r|\n/g) + .filter(s => !/^\s*$/.test(s)) // filtering empty lines const regexp = data.get('forbidden_words_regexp_' + i.toString()) const applyToModerators = data.get('forbidden_words_applytomoderators_' + i.toString()) const reason = data.get('forbidden_words_reason_' + i.toString())?.toString() @@ -125,7 +180,31 @@ async function vivifyConfigurationChannel ( channelConfigurationOptions.bot.forbiddenWords.push(fw) } - // TODO: quotes and commands. + for (let i = 0; data.has('quote_' + i.toString()); i++) { + const messages = (data.get('quote_' + i.toString())?.toString() ?? '') + .split(/\r?\n|\r|\n/g) + .filter(s => !/^\s*$/.test(s)) // filtering empty lines + let delay = parseInt(data.get('quote_delay_' + i.toString())?.toString() ?? '') + if (!delay || isNaN(delay) || delay < 1) { + delay = 5 + } + delay = delay * 60 // converting to seconds + const q: ChannelConfigurationOptions['bot']['quotes'][0] = { + messages, + delay + } + channelConfigurationOptions.bot.quotes.push(q) + } + + for (let i = 0; data.has('command_' + i.toString()); i++) { + const command = (data.get('command_' + i.toString())?.toString() ?? '') + const message = (data.get('command_message_' + i.toString())?.toString() ?? '') + const c: ChannelConfigurationOptions['bot']['commands'][0] = { + command, + message + } + channelConfigurationOptions.bot.commands.push(c) + } const headers: any = clientOptions.peertubeHelpers.getAuthHeader() ?? {} headers['content-type'] = 'application/json;charset=UTF-8' diff --git a/server/lib/configuration/bot.ts b/server/lib/configuration/bot.ts index 95c112d6..cd320548 100644 --- a/server/lib/configuration/bot.ts +++ b/server/lib/configuration/bot.ts @@ -103,6 +103,21 @@ class BotConfiguration { return singleton } + /** + * Get the current room conf content. + * @param roomJIDParam room JID (local or full) + * @returns the room conf, or null if does not exist + */ + public async getRoom (roomJIDParam: string): Promise { + const roomJID = this._canonicJID(roomJIDParam) + if (!roomJID) { + this.logger.error('Invalid room JID') + return null + } + const conf = await this._getRoomConf(roomJID) + return conf + } + /** * Update the bot configuration for a given room. * @param roomJIDParam Room full or local JID diff --git a/server/lib/configuration/channel/sanitize.ts b/server/lib/configuration/channel/sanitize.ts index abcf6cec..4bc3e3c7 100644 --- a/server/lib/configuration/channel/sanitize.ts +++ b/server/lib/configuration/channel/sanitize.ts @@ -166,9 +166,9 @@ function _readQuotes (botData: any): ChannelConfigurationOptions['bot']['quotes' throw new Error('Invalid quotes data') } const result: ChannelConfigurationOptions['bot']['quotes'] = [] - for (const fw of botData.quotes) { - const messages = _readStringArray(fw, 'message') - const delay = _readInteger(fw, 'delay', 1, 6000) + for (const qs of botData.quotes) { + const messages = _readStringArray(qs, 'messages') + const delay = _readInteger(qs, 'delay', 1, 6000) result.push({ messages, @@ -183,9 +183,9 @@ function _readCommands (botData: any): ChannelConfigurationOptions['bot']['comma throw new Error('Invalid commands data') } const result: ChannelConfigurationOptions['bot']['commands'] = [] - for (const fw of botData.commands) { - const message = _readSimpleInput(fw, 'message') - const command = _readSimpleInput(fw, 'command') + for (const cs of botData.commands) { + const message = _readSimpleInput(cs, 'message') + const command = _readSimpleInput(cs, 'command') result.push({ message, diff --git a/server/lib/configuration/channel/storage.ts b/server/lib/configuration/channel/storage.ts index abc51e4d..681cfeae 100644 --- a/server/lib/configuration/channel/storage.ts +++ b/server/lib/configuration/channel/storage.ts @@ -39,39 +39,9 @@ function getDefaultChannelConfigurationOptions (_options: RegisterServerOptions) bot: { enabled: false, nickname: 'Sepia', - // Note: we are instanciating several data for forbiddenWords, quotes and commands. - // This will be used by the frontend to instanciates requires fields - forbiddenWords: [ - { - entries: [] - }, - { - entries: [] - }, - { - entries: [] - } - ], - quotes: [ - { - messages: [], - delay: 5 * 60 // seconds to minutes - } - ], - commands: [ - { - command: '', - message: '' - }, - { - command: '', - message: '' - }, - { - command: '', - message: '' - } - ] + forbiddenWords: [], + quotes: [], + commands: [] } } } @@ -109,22 +79,47 @@ async function storeChannelConfigurationOptions ( * Converts the channel configuration to the bot room configuration object (minus the room JID and domain) * @param options server options * @param channelConfigurationOptions The channel configuration + * @param previousRoomConf the previous saved room conf, if available. Used to merge handlers. * @returns Partial bot room configuration */ function channelConfigurationOptionsToBotRoomConf ( options: RegisterServerOptions, - channelConfigurationOptions: ChannelConfigurationOptions + channelConfigurationOptions: ChannelConfigurationOptions, + previousRoomConf: ChannelCommonRoomConf | null ): ChannelCommonRoomConf { // Note concerning handlers: // If we want the bot to correctly enable/disable the handlers, // we must always define all handlers, even if not used. + // That's why we are gathering handlers ids in handlersId, and disabling missing handlers at the end of this function. + const handlersIds: Map = new Map() const handlers: ConfigHandlers = [] channelConfigurationOptions.bot.forbiddenWords.forEach((v, i) => { - handlers.push(_getForbiddenWordsHandler( - 'forbidden_words_' + i.toString(), - channelConfigurationOptions.bot.forbiddenWords[i] - )) + const id = 'forbidden_words_' + i.toString() + handlersIds.set(id, true) + handlers.push(_getForbiddenWordsHandler(id, v)) }) + channelConfigurationOptions.bot.quotes.forEach((v, i) => { + const id = 'quote_' + i.toString() + handlersIds.set(id, true) + handlers.push(_getQuotesHandler(id, v)) + }) + channelConfigurationOptions.bot.commands.forEach((v, i) => { + const id = 'command_' + i.toString() + handlersIds.set(id, true) + handlers.push(_getCommandsHandler(id, v)) + }) + + // Disabling missing handlers: + if (previousRoomConf) { + for (const handler of previousRoomConf.handlers) { + if (!handlersIds.has(handler.id)) { + // cloning to avoid issues... + const disabledHandler = JSON.parse(JSON.stringify(handler)) + disabledHandler.enabled = false + handlers.push(disabledHandler) + } + } + } const roomConf: ChannelCommonRoomConf = { enabled: channelConfigurationOptions.bot.enabled, @@ -192,6 +187,52 @@ function _getForbiddenWordsHandler ( return handler } +function _getQuotesHandler ( + id: string, + quotes: ChannelConfigurationOptions['bot']['quotes'][0] +): ConfigHandler { + const handler: ConfigHandler = { + type: 'quotes_random', + id, + enabled: false, + options: { + quotes: [], + delay: 5 * 60 + } + } + if (quotes.messages.length === 0) { + return handler + } + + handler.enabled = true + handler.options.quotes = quotes.messages + handler.options.delay = quotes.delay + return handler +} + +function _getCommandsHandler ( + id: string, + command: ChannelConfigurationOptions['bot']['commands'][0] +): ConfigHandler { + const handler: ConfigHandler = { + type: 'command_say', + id, + enabled: false, + options: { + quotes: [], + command: 'undefined' // This is arbitrary, and does not matter as enabled=false + } + } + if (!command.message || command.message === '') { + return handler + } + + handler.enabled = true + handler.options.command = command.command + handler.options.quotes = [command.message] + return handler +} + const stringToWordRegexpSpecials = [ // order matters for these '-', '[', ']', diff --git a/server/lib/room-channel/room-channel-class.ts b/server/lib/room-channel/room-channel-class.ts index 4d20e39c..f22c394d 100644 --- a/server/lib/room-channel/room-channel-class.ts +++ b/server/lib/room-channel/room-channel-class.ts @@ -334,12 +334,13 @@ class RoomChannel { } this.logger.info(`Room ${roomJID} has associated channel options, writing it`) + const previousRoomConf = await BotConfiguration.singleton().getRoom(roomJID) const botConf: RoomConf = Object.assign( { local: roomJID, domain: this.mucDomain }, - channelConfigurationOptionsToBotRoomConf(this.options, channelConfigurationOptions) + channelConfigurationOptionsToBotRoomConf(this.options, channelConfigurationOptions, previousRoomConf) ) await BotConfiguration.singleton().updateRoom(roomJID, botConf)