From 5225257bb5135cd45da7c0416603e07f47dd08f0 Mon Sep 17 00:00:00 2001 From: John Livingston Date: Tue, 10 Sep 2024 18:59:56 +0200 Subject: [PATCH] New option for the moderation bot: forbid duplicate messages (#516). --- CHANGELOG.md | 4 + client/@types/global.d.ts | 5 + .../templates/channel-configuration.ts | 120 ++++++++++++++++++ .../configuration/services/channel-details.ts | 16 +++ languages/en.yml | 8 ++ package-lock.json | 14 +- package.json | 2 +- server/lib/configuration/channel/sanitize.ts | 29 ++++- server/lib/configuration/channel/storage.ts | 28 ++++ server/lib/routers/api/configuration.ts | 3 +- shared/lib/types.ts | 8 ++ 11 files changed, 227 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9c6f20c..346d95ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,13 @@ ## 11.1.0 (Not Released Yet) +TODO Before releasing: +* update ConverseJS with latest merges. + ### New features * #131: Emoji only mode. +* #516: new option for the moderation bot: forbid duplicate messages. * #517: new option for the moderation bot: forbid messages with too many special characters. ### Minor changes and fixes diff --git a/client/@types/global.d.ts b/client/@types/global.d.ts index 84de939f..ccb7d1e9 100644 --- a/client/@types/global.d.ts +++ b/client/@types/global.d.ts @@ -155,3 +155,8 @@ declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_SPECIAL_CHARS_LABEL: string declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_SPECIAL_CHARS_DESC: string declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_SPECIAL_CHARS_TOLERANCE_LABEL: string declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_SPECIAL_CHARS_TOLERANCE_DESC: string + +declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_NO_DUPLICATE_LABEL: string +declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_NO_DUPLICATE_DESC: string +declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_NO_DUPLICATE_DELAY_LABEL: string +declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_NO_DUPLICATE_DELAY_DESC: string diff --git a/client/common/configuration/elements/templates/channel-configuration.ts b/client/common/configuration/elements/templates/channel-configuration.ts index ecae85a3..93be5857 100644 --- a/client/common/configuration/elements/templates/channel-configuration.ts +++ b/client/common/configuration/elements/templates/channel-configuration.ts @@ -460,6 +460,126 @@ export function tplChannelConfiguration (el: ChannelConfigurationElement): Templ ` } + + +
+ +
+ ${!el.channelConfiguration?.configuration.bot.noDuplicate.enabled + ? '' + : html` +
+ + + ${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_NO_DUPLICATE_DELAY_DESC)} + + ${el.renderFeedback('peertube-livechat-no-duplicate-delay-feedback', + 'bot.noDuplicate.delay') + } +
+
+ + + ${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_RETRACTATION_REASON_DESC)} + + ${el.renderFeedback('peertube-livechat-no-duplicate-reason-feedback', + 'bot.noDuplicate.reason') + } +
+
+ + + ${ptTr(LOC_LIVECHAT_CONFIGURATION_APPLYTOMODERATORS_DESC)} + +
+ ` + } + 24 * 3600 + ) { + propertiesError['bot.noDuplicate.delay'].push(ValidationErrorType.NotInRange) + } + } + for (const [i, fw] of botConf.forbiddenWords.entries()) { for (const v of fw.entries) { propertiesError[`bot.forbiddenWords.${i}.entries`] = [] diff --git a/languages/en.yml b/languages/en.yml index f8e6f2fe..6aa540f4 100644 --- a/languages/en.yml +++ b/languages/en.yml @@ -662,3 +662,11 @@ livechat_configuration_channel_special_chars_tolerance_label: Tolerance livechat_configuration_channel_special_chars_tolerance_desc: Number of special characters to accept before deleting messages. feature_comes_with: This feature comes with the livechat plugin version X.X.X. + +livechat_configuration_channel_no_duplicate_label: "No duplicate message" +livechat_configuration_channel_no_duplicate_desc: | + By enabling this options, the moderation bot will automatically moderate duplicate messages. + That means if a user send the same message twice within X seconds, the second message will be deleted. +livechat_configuration_channel_no_duplicate_delay_label: Time interval +livechat_configuration_channel_no_duplicate_delay_desc: | + The interval, in seconds, during which a user can't send again the same message. diff --git a/package-lock.json b/package-lock.json index 388f667a..af229ece 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "log-rotate": "^0.2.8", "openid-client": "^5.7.0", "validate-color": "^2.2.4", - "xmppjs-chat-bot": "^0.4.0" + "xmppjs-chat-bot": "^0.5.0" }, "devDependencies": { "@eslint/js": "^9.10.0", @@ -12932,9 +12932,9 @@ } }, "node_modules/xmppjs-chat-bot": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/xmppjs-chat-bot/-/xmppjs-chat-bot-0.4.0.tgz", - "integrity": "sha512-vN+hWlrSDKmOK+XDOx3VmBffQkEYtfEhLDiovwy8PqPJnyEGESsIcva33hvzWrBYES8hTz1DX320aFYx5tnnNA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xmppjs-chat-bot/-/xmppjs-chat-bot-0.5.0.tgz", + "integrity": "sha512-P+C2x00zS3Zd0PvkKeiT+cPYMQOW/pZu2pEG+iNkUjHU5MYBukn48aMgbzzPwJVQpk/9FuZZeK1m/pl3iD2KFA==", "funding": [ "https://paypal.me/JohnXLivingston", "https://liberapay.com/JohnLivingston/" @@ -22351,9 +22351,9 @@ } }, "xmppjs-chat-bot": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/xmppjs-chat-bot/-/xmppjs-chat-bot-0.4.0.tgz", - "integrity": "sha512-vN+hWlrSDKmOK+XDOx3VmBffQkEYtfEhLDiovwy8PqPJnyEGESsIcva33hvzWrBYES8hTz1DX320aFYx5tnnNA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xmppjs-chat-bot/-/xmppjs-chat-bot-0.5.0.tgz", + "integrity": "sha512-P+C2x00zS3Zd0PvkKeiT+cPYMQOW/pZu2pEG+iNkUjHU5MYBukn48aMgbzzPwJVQpk/9FuZZeK1m/pl3iD2KFA==", "requires": { "@xmpp/client": "^0.13.1", "@xmpp/component": "^0.13.1", diff --git a/package.json b/package.json index 9079ea0d..f7064277 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "log-rotate": "^0.2.8", "openid-client": "^5.7.0", "validate-color": "^2.2.4", - "xmppjs-chat-bot": "^0.4.0" + "xmppjs-chat-bot": "^0.5.0" }, "devDependencies": { "@eslint/js": "^9.10.0", diff --git a/server/lib/configuration/channel/sanitize.ts b/server/lib/configuration/channel/sanitize.ts index c064bd71..ded2a78e 100644 --- a/server/lib/configuration/channel/sanitize.ts +++ b/server/lib/configuration/channel/sanitize.ts @@ -57,7 +57,18 @@ async function sanitizeChannelConfigurationOptions ( applyToModerators: false } if (!_assertObjectType(botData.forbidSpecialChars)) { - throw new Error('Invalid data.forbidSpecialChars data type') + throw new Error('Invalid data.bot.forbidSpecialChars data type') + } + + // noDuplicate comes with livechat 11.1.0 + botData.noDuplicate ??= { + enabled: false, + reason: '', + delay: 60, + applyToModerators: false + } + if (!_assertObjectType(botData.noDuplicate)) { + throw new Error('Invalid data.bot.noDuplicate data type') } // terms not present in livechat <= 10.2.0 @@ -76,6 +87,7 @@ async function sanitizeChannelConfigurationOptions ( nickname: _readSimpleInput(botData, 'nickname', true), forbiddenWords: await _readForbiddenWords(botData), forbidSpecialChars: await _readForbidSpecialChars(botData), + noDuplicate: await _readNoDuplicate(botData), quotes: _readQuotes(botData), commands: _readCommands(botData) // TODO: bannedJIDs @@ -266,6 +278,21 @@ async function _readForbidSpecialChars ( return result } +async function _readNoDuplicate ( + botData: Record +): Promise { + if (!_assertObjectType(botData.noDuplicate)) { + throw new Error('Invalid forbidSpecialChars data') + } + const result: ChannelConfigurationOptions['bot']['noDuplicate'] = { + enabled: _readBoolean(botData.noDuplicate, 'enabled'), + reason: _readSimpleInput(botData.noDuplicate, 'reason'), + delay: _readInteger(botData.noDuplicate, 'delay', 0, 24 * 3600), + applyToModerators: _readBoolean(botData.noDuplicate, 'applyToModerators') + } + return result +} + function _readQuotes (botData: Record): ChannelConfigurationOptions['bot']['quotes'] { if (!Array.isArray(botData.quotes)) { throw new Error('Invalid quotes data') diff --git a/server/lib/configuration/channel/storage.ts b/server/lib/configuration/channel/storage.ts index 096700d0..cf9cce34 100644 --- a/server/lib/configuration/channel/storage.ts +++ b/server/lib/configuration/channel/storage.ts @@ -50,6 +50,12 @@ function getDefaultChannelConfigurationOptions (_options: RegisterServerOptions) tolerance: 0, applyToModerators: false }, + noDuplicate: { + enabled: false, + reason: '', + delay: 60, + applyToModerators: false + }, quotes: [], commands: [] }, @@ -124,6 +130,11 @@ function channelConfigurationOptionsToBotRoomConf ( handlersIds.set(id, true) handlers.push(_getForbidSpecialCharsHandler(id, channelConfigurationOptions.bot.forbidSpecialChars)) } + if (channelConfigurationOptions.bot.noDuplicate.enabled) { + const id = 'no_duplicate' + handlersIds.set(id, true) + handlers.push(_getNoDuplicateHandler(id, channelConfigurationOptions.bot.noDuplicate)) + } channelConfigurationOptions.bot.quotes.forEach((v, i) => { const id = 'quote_' + i.toString() handlersIds.set(id, true) @@ -254,6 +265,23 @@ function _getForbidSpecialCharsHandler ( return handler } +function _getNoDuplicateHandler ( + id: string, + noDuplicate: ChannelConfigurationOptions['bot']['noDuplicate'] +): ConfigHandler { + const handler: ConfigHandler = { + type: 'no-duplicate', + id, + enabled: true, + options: { + reason: noDuplicate.reason, + delay: noDuplicate.delay, + applyToModerators: !!noDuplicate.applyToModerators + } + } + return handler +} + function _getQuotesHandler ( id: string, quotes: ChannelConfigurationOptions['bot']['quotes'][0] diff --git a/server/lib/routers/api/configuration.ts b/server/lib/routers/api/configuration.ts index 8ff5014b..92dfb342 100644 --- a/server/lib/routers/api/configuration.ts +++ b/server/lib/routers/api/configuration.ts @@ -96,7 +96,7 @@ async function initConfigurationApiRouter (options: RegisterServerOptions, route req.body.bot = channelOptions.bot req.body.bot.enabled = false } - // TODO: Same for forbidSpecialChars: if disabled, don't save reason and tolerance + // TODO: Same for forbidSpecialChars/noDuplicate: if disabled, don't save reason and tolerance // (disabling for now, because it is not acceptable to load twice the channel configuration. // Must find better way) // if (req.body.bot?.enabled === true && req.body.bot.forbidSpecialChars?.enabled === false) { @@ -108,6 +108,7 @@ async function initConfigurationApiRouter (options: RegisterServerOptions, route // req.body.bot.forbidSpecialChars.tolerance = channelOptions.bot.forbidSpecialChars.tolerance // req.body.bot.forbidSpecialChars.applyToModerators = channelOptions.bot.forbidSpecialChars.applyToModerators // req.body.bot.forbidSpecialChars.enabled = false + // ... NoDuplicate... // } channelOptions = await sanitizeChannelConfigurationOptions(options, channelInfos.id, req.body) } catch (err) { diff --git a/shared/lib/types.ts b/shared/lib/types.ts index e2a9fe47..34c97027 100644 --- a/shared/lib/types.ts +++ b/shared/lib/types.ts @@ -97,6 +97,7 @@ interface ChannelConfigurationOptions { quotes: ChannelQuotes[] commands: ChannelCommands[] forbidSpecialChars: ChannelForbidSpecialChars + noDuplicate: ChannelNoDuplicate // TODO: bannedJIDs: string[] } slowMode: { @@ -140,6 +141,13 @@ interface ChannelForbidSpecialChars { applyToModerators: boolean } +interface ChannelNoDuplicate { + enabled: boolean + reason: string + delay: number + applyToModerators: boolean +} + interface ChannelConfiguration { channel: ChannelInfos configuration: ChannelConfigurationOptions