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