diff --git a/prosody-modules/mod_http_peertubelivechat_manage_rooms/mod_http_peertubelivechat_manage_rooms.lua b/prosody-modules/mod_http_peertubelivechat_manage_rooms/mod_http_peertubelivechat_manage_rooms.lua index e616e9a7..94a131e1 100644 --- a/prosody-modules/mod_http_peertubelivechat_manage_rooms/mod_http_peertubelivechat_manage_rooms.lua +++ b/prosody-modules/mod_http_peertubelivechat_manage_rooms/mod_http_peertubelivechat_manage_rooms.lua @@ -18,7 +18,7 @@ local mod_muc_peertubelivechat_terms = module:depends"muc_peertubelivechat_terms local set_muc_terms = rawget(mod_muc_peertubelivechat_terms, "set_muc_terms"); local mod_muc_peertubelivechat_restrict_message = module:depends"muc_peertubelivechat_restrict_message"; local set_peertubelivechat_emoji_only_mode = rawget(mod_muc_peertubelivechat_restrict_message, "set_peertubelivechat_emoji_only_mode"); -local set_peertubelivechat_emoji_only_regexp = rawget(mod_muc_peertubelivechat_restrict_message, "set_peertubelivechat_emoji_only_regexp"); +local set_peertubelivechat_custom_emoji_regexp = rawget(mod_muc_peertubelivechat_restrict_message, "set_peertubelivechat_custom_emoji_regexp"); function check_auth(routes) local function check_request_auth(event) @@ -110,9 +110,9 @@ local function update_room(event) set_peertubelivechat_emoji_only_mode(room, config.livechat_emoji_only) end end - if type(config.livechat_emoji_only_regexp) == "string" then - if set_peertubelivechat_emoji_only_mode then - set_peertubelivechat_emoji_only_regexp(room, config.livechat_emoji_only_regexp) + if type(config.livechat_custom_emoji_regexp) == "string" then + if set_peertubelivechat_custom_emoji_regexp then + set_peertubelivechat_custom_emoji_regexp(room, config.livechat_custom_emoji_regexp) end end if (type(config.livechat_muc_terms) == "string") then diff --git a/prosody-modules/mod_muc_http_defaults/mod_muc_http_defaults.lua b/prosody-modules/mod_muc_http_defaults/mod_muc_http_defaults.lua index 39cff2e0..7bbc5deb 100644 --- a/prosody-modules/mod_muc_http_defaults/mod_muc_http_defaults.lua +++ b/prosody-modules/mod_muc_http_defaults/mod_muc_http_defaults.lua @@ -10,7 +10,7 @@ -- * "moderation_delay" -- * "anonymize_moderation_actions" -- * "livechat_emoji_only" --- * "livechat_emoji_only_regexp" +-- * "livechat_custom_emoji_regexp" -- * "livechat_muc_terms" -- These options are introduced in the Peertube livechat plugin. -- @@ -134,8 +134,8 @@ local function apply_config(room, settings) if (type(config.livechat_emoji_only) == "boolean") then room._data.x_peertubelivechat_emoji_only_mode = config.livechat_emoji_only; end - if (type(config.livechat_emoji_only_regexp) == "string" and config.livechat_emoji_only_regexp ~= "") then - room._data.x_peertubelivechat_emoji_only_regexp = config.livechat_emoji_only_regexp; + if (type(config.livechat_custom_emoji_regexp) == "string" and config.livechat_custom_emoji_regexp ~= "") then + room._data.x_peertubelivechat_custom_emoji_regexp = config.livechat_custom_emoji_regexp; end if (type(config.livechat_muc_terms) == "string") then -- we don't need to use set_muc_terms here, as this is called for a newly created room diff --git a/prosody-modules/mod_muc_peertubelivechat_restrict_message/mod_muc_peertubelivechat_restrict_message.lua b/prosody-modules/mod_muc_peertubelivechat_restrict_message/mod_muc_peertubelivechat_restrict_message.lua index 1f8d2781..0ac27b65 100644 --- a/prosody-modules/mod_muc_peertubelivechat_restrict_message/mod_muc_peertubelivechat_restrict_message.lua +++ b/prosody-modules/mod_muc_peertubelivechat_restrict_message/mod_muc_peertubelivechat_restrict_message.lua @@ -19,6 +19,7 @@ local mod_muc = module:depends "muc"; local muc_util = module:require "muc/util"; local valid_roles = muc_util.valid_roles; +local common_emoji_regexp = assert(module:get_option_string('peertubelivechat_restrict_message_common_emoji_regexp'), 'Common emoji regexp is mandatory'); function get_peertubelivechat_emoji_only_mode(room) return room._data.x_peertubelivechat_emoji_only_mode; @@ -31,17 +32,17 @@ function set_peertubelivechat_emoji_only_mode(room, emoji_only) return true; end -function get_peertubelivechat_emoji_only_regexp(room) - return room._data.x_peertubelivechat_emoji_only_regexp; +function get_peertubelivechat_custom_emoji_regexp(room) + return room._data.x_peertubelivechat_custom_emoji_regexp; end -function set_peertubelivechat_emoji_only_regexp(room, emoji_only_regexp) +function set_peertubelivechat_custom_emoji_regexp(room, emoji_only_regexp) if (emoji_only_regexp ~= nil and type(emoji_only_regexp) ~= "string") then return false; end if emoji_only_regexp == "" then emoji_only_regexp = nil; end - if get_peertubelivechat_emoji_only_regexp(room) == emoji_only_regexp then return false; end - room._data.x_peertubelivechat_emoji_only_regexp = emoji_only_regexp; + if get_peertubelivechat_custom_emoji_regexp(room) == emoji_only_regexp then return false; end + room._data.x_peertubelivechat_custom_emoji_regexp = emoji_only_regexp; -- and we must decache the compile regexp room.x_peertubelivechat_emoji_only_compiled_regexp = nil; @@ -49,25 +50,23 @@ function set_peertubelivechat_emoji_only_regexp(room, emoji_only_regexp) end module:hook("muc-disco#info", function(event) - if get_peertubelivechat_emoji_only_mode(event.room) and get_peertubelivechat_emoji_only_regexp(event.room) ~= nil then + if get_peertubelivechat_emoji_only_mode(event.room) then event.reply:tag("feature", {var = "x_peertubelivechat_emoji_only_mode"}):up(); end end); module:hook("muc-config-form", function(event) - if (get_peertubelivechat_emoji_only_regexp(event.room) ~= nil) then - table.insert(event.form, { - name = "muc#roomconfig_x_peertubelivechat_emoji_only_mode"; - type = "boolean"; - label = "Emoji only mode"; - desc = "Occupants will only be able to send emoji. This does not affect moderators."; - value = get_peertubelivechat_emoji_only_mode(event.room); - }); - end -end, 121); + table.insert(event.form, { + name = "muc#roomconfig_x_peertubelivechat_emoji_only_mode"; + type = "boolean"; + label = "Emoji only mode"; + desc = "Occupants will only be able to send emoji. This does not affect moderators."; + value = get_peertubelivechat_emoji_only_mode(event.room); + }); +end, 122); module:hook("muc-config-submitted/muc#roomconfig_x_peertubelivechat_emoji_only_mode", function(event) - if get_peertubelivechat_emoji_only_regexp(event.room) ~= nil and set_peertubelivechat_emoji_only_mode(event.room, event.value) then + if set_peertubelivechat_emoji_only_mode(event.room, event.value) then event.status_codes["104"] = true; end end); @@ -84,10 +83,14 @@ function handle_groupchat(event) if not room.x_peertubelivechat_emoji_only_compiled_regexp then -- compute the regexp on first access - local r = get_peertubelivechat_emoji_only_regexp(room); - if (r == nil) then - return; + local r = get_peertubelivechat_custom_emoji_regexp(room); + if (r == nil or r == "") then + r = common_emoji_regexp; + else + r = r .. "|" .. common_emoji_regexp; end + r = "^\\s*(?:(?:" .. r .. ")\\s*)+\\s*$" + room.x_peertubelivechat_emoji_only_compiled_regexp = rex.new(r, "i", "UTF8"); end diff --git a/server/lib/configuration/channel/init.ts b/server/lib/configuration/channel/init.ts index 96bd1af4..0ffebcf5 100644 --- a/server/lib/configuration/channel/init.ts +++ b/server/lib/configuration/channel/init.ts @@ -120,9 +120,10 @@ async function initChannelConfiguration (options: RegisterServerOptions): Promis // but will be more efficient to add here, as we already tested hasChat). // Note: no need to await here, would only degrade performances. // FIXME: should also update livechat_muc_terms if channel has changed. - // FIXME: should also update livechat_emoji_only_regexp if channel has changed. updateProsodyRoom(options, video.uuid, { - name: video.name + name: video.name, + // In case the channel changed: + livechat_custom_emoji_regexp: await Emojis.singletonSafe()?.getChannelCustomEmojisRegexp(video.channelId) }).then( () => {}, (err) => logger.error(err) diff --git a/server/lib/emojis/emojis.ts b/server/lib/emojis/emojis.ts index 5d7232e4..f0bd852c 100644 --- a/server/lib/emojis/emojis.ts +++ b/server/lib/emojis/emojis.ts @@ -144,7 +144,7 @@ export class Emojis { * @param sn short name */ public validShortName (sn: any): boolean { - // Important note: do not change this without checking if it can breaks getChannelEmojisOnlyRegexp. + // Important note: do not change this without checking if it can breaks getChannelCustomEmojisRegexp. if ((typeof sn !== 'string') || !/^:?[\w-]+:?$/.test(sn)) { this.logger.debug('Short name invalid: ' + (typeof sn === 'string' ? sn : '???')) return false @@ -395,14 +395,13 @@ export class Emojis { } /** - * Returns a string representing a regular expression (Perl Compatible RE) that can validate that a message - * contains only emojis (for this channel). + * Returns a string representing a regular expression validating channel custom emojis. * This is used for the emoji only mode (test are made on the Prosody server). * * @param channelId channel id */ - public async getChannelEmojisOnlyRegexp (channelId: number): Promise { - const parts = [...this.commonEmojisCodes] + public async getChannelCustomEmojisRegexp (channelId: number): Promise { + const parts = [] if (await this.channelHasCustomEmojis(channelId)) { const def = await this.channelCustomEmojisDefinition(channelId) @@ -411,11 +410,17 @@ export class Emojis { } } - // Note: validShortName should ensure we won't put special chars. - // And for the common emojis, we assume that there is no special regexp chars - const regexp = '^\\s*(?:(?:' + parts.join('|') + ')\\s*)+\\s*$' + if (parts.length === 0) { + return undefined + } - return regexp + // Note: validShortName should ensure we won't put special chars. + return parts.join('|') + } + + public getCommonEmojisRegexp (): string { + // We assume that there is no special regexp chars (should only contains unicode emojis) + return this.commonEmojisCodes.join('|') } /** diff --git a/server/lib/prosody/api/manage-rooms.ts b/server/lib/prosody/api/manage-rooms.ts index ec52563e..f7358045 100644 --- a/server/lib/prosody/api/manage-rooms.ts +++ b/server/lib/prosody/api/manage-rooms.ts @@ -66,7 +66,7 @@ async function updateProsodyRoom ( slow_mode_duration?: number moderation_delay?: number livechat_emoji_only?: boolean - livechat_emoji_only_regexp?: string + livechat_custom_emoji_regexp?: string livechat_muc_terms?: string addAffiliations?: Affiliations removeAffiliationsFor?: string[] @@ -105,8 +105,8 @@ async function updateProsodyRoom ( if ('livechat_emoji_only' in data) { apiData.livechat_emoji_only = data.livechat_emoji_only ?? false } - if ('livechat_emoji_only_regexp' in data) { - apiData.livechat_emoji_only_regexp = data.livechat_emoji_only_regexp ?? '' + if ('livechat_custom_emoji_regexp' in data) { + apiData.livechat_custom_emoji_regexp = data.livechat_custom_emoji_regexp ?? '' } if (('addAffiliations' in data) && data.addAffiliations !== undefined) { apiData.addAffiliations = data.addAffiliations diff --git a/server/lib/prosody/config.ts b/server/lib/prosody/config.ts index fc59551c..d9edee7e 100644 --- a/server/lib/prosody/config.ts +++ b/server/lib/prosody/config.ts @@ -19,6 +19,7 @@ import { BotConfiguration } from '../configuration/bot' import { debugMucAdmins } from '../debug' import { ExternalAuthOIDC } from '../external-auth/oidc' import { listModFirewallFiles } from '../firewall/config' +import { Emojis } from '../emojis' async function getWorkingDir (options: RegisterServerOptions): Promise { const peertubeHelpers = options.peertubeHelpers @@ -389,6 +390,13 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise +// +// SPDX-License-Identifier: AGPL-3.0-only + +import type { RegisterServerOptions } from '@peertube/peertube-types' +import { listProsodyRooms, updateProsodyRoom } from '../api/manage-rooms' +import * as path from 'path' +import * as fs from 'fs' +import { Emojis } from '../../emojis' + +/** + * Livechat v11.1.0: we must send channel custom emojis regexp to Prosody. + * + * This script will only be launched one time. + */ +async function updateProsodyChannelEmojisRegex (options: RegisterServerOptions): Promise { + const logger = options.peertubeHelpers.logger + + // First, detect if we already run this script. + const doneFilePath = path.resolve(options.peertubeHelpers.plugin.getDataDirectoryPath(), 'fix-v11.1-emojis') + if (fs.existsSync(doneFilePath)) { + logger.debug('[migratev11_1_ChannelEmojis] Channel Emojis Regex already updated on Prosody.') + return + } + + logger.info('[migratev11_1_ChannelEmojis] Updating Channel custom emojis regexp on Prosody') + + const emojis = Emojis.singleton() + const rooms = await listProsodyRooms(options) + logger.debug('[migratev11_1_ChannelEmojis] Found ' + rooms.length.toString() + ' rooms.') + + for (const room of rooms) { + try { + let channelId: number + logger.info('[migratev11_1_ChannelEmojis] Must update custom emojis regexp for room ' + room.localpart) + const matches = room.localpart.match(/^channel\.(\d+)$/) + if (matches?.[1]) { + // room associated to a channel + channelId = parseInt(matches[1]) + } else { + // room associated to a video + const video = await options.peertubeHelpers.videos.loadByIdOrUUID(room.localpart) + if (!video || video.remote) { + logger.info('[migratev11_1_ChannelEmojis] Video ' + room.localpart + ' not found or remote, skipping') + continue + } + channelId = video.channelId + } + + if (!channelId) { + throw new Error('Cant find channelId') + } + + const regexp = await emojis.getChannelCustomEmojisRegexp(channelId) + if (regexp === undefined) { + logger.info('[migratev11_1_ChannelEmojis] Room ' + room.localpart + ' channel has no custom emojis, skipping.') + continue + } + + await updateProsodyRoom(options, room.jid, { + livechat_custom_emoji_regexp: regexp + }) + } catch (err) { + logger.error( + '[migratev11_1_ChannelEmojis] Failed to handle room ' + room.localpart + ', skipping. Error: ' + (err as string) + ) + continue + } + } + + await fs.promises.writeFile(doneFilePath, '') +} + +export { + updateProsodyChannelEmojisRegex +} diff --git a/server/lib/routers/api/configuration.ts b/server/lib/routers/api/configuration.ts index 7038c4e2..4127d8a3 100644 --- a/server/lib/routers/api/configuration.ts +++ b/server/lib/routers/api/configuration.ts @@ -171,13 +171,13 @@ async function initConfigurationApiRouter (options: RegisterServerOptions, route await emojis.saveChannelDefinition(channelInfos.id, emojisDefinitionSanitized, bufferInfos) // We must update the emoji only regexp on the Prosody server. - const emojisOnlyRegexp = await emojis.getChannelEmojisOnlyRegexp(channelInfos.id) + const customEmojisRegexp = await emojis.getChannelCustomEmojisRegexp(channelInfos.id) const roomJIDs = RoomChannel.singleton().getChannelRoomJIDs(channelInfos.id) for (const roomJID of roomJIDs) { // No need to await here logger.info(`Updating room ${roomJID} emoji only regexp...`) updateProsodyRoom(options, roomJID, { - livechat_emoji_only_regexp: emojisOnlyRegexp + livechat_custom_emoji_regexp: customEmojisRegexp }).then( () => {}, (err) => logger.error(err) diff --git a/server/lib/routers/api/room.ts b/server/lib/routers/api/room.ts index f0d922ca..ec4df8e9 100644 --- a/server/lib/routers/api/room.ts +++ b/server/lib/routers/api/room.ts @@ -39,7 +39,7 @@ interface RoomDefaults { slow_mode_duration?: number mute_anonymous?: boolean livechat_emoji_only?: boolean - livechat_emoji_only_regexp?: string + livechat_custom_emoji_regexp?: string livechat_muc_terms?: string moderation_delay?: number anonymize_moderation_actions?: boolean @@ -54,12 +54,12 @@ async function _getChannelSpecificOptions ( const channelOptions = await getChannelConfigurationOptions(options, channelId) ?? getDefaultChannelConfigurationOptions(options) - const emojiOnlyRegexp = await Emojis.singletonSafe()?.getChannelEmojisOnlyRegexp(channelId) + const customEmojisRegexp = await Emojis.singletonSafe()?.getChannelCustomEmojisRegexp(channelId) return { slow_mode_duration: channelOptions.slowMode.duration, mute_anonymous: channelOptions.mute.anonymous, - livechat_emoji_only_regexp: emojiOnlyRegexp, + livechat_custom_emoji_regexp: customEmojisRegexp, livechat_muc_terms: channelOptions.terms, moderation_delay: channelOptions.moderation.delay, anonymize_moderation_actions: channelOptions.moderation.anonymize diff --git a/server/main.ts b/server/main.ts index 61ae90ac..f7e27547 100644 --- a/server/main.ts +++ b/server/main.ts @@ -18,6 +18,7 @@ import { BotConfiguration } from './lib/configuration/bot' import { BotsCtl } from './lib/bots/ctl' import { ExternalAuthOIDC } from './lib/external-auth/oidc' import { migrateMUCAffiliations } from './lib/prosody/migration/migrateV10' +import { updateProsodyChannelEmojisRegex } from './lib/prosody/migration/migrageV11-1' import { Emojis } from './lib/emojis' import { LivechatProsodyAuth } from './lib/prosody/auth' import decache from 'decache' @@ -87,11 +88,24 @@ async function register (options: RegisterServerOptions): Promise { // livechat v10.0.0: we must migrate MUC affiliations (but we don't have to wait) // we do this after the preBotPromise, just to avoid doing both at the same time. preBotPromise.then(() => { - migrateMUCAffiliations(options).then( + const p = migrateMUCAffiliations(options).then( () => {}, (err) => { logger.error(err) - }) + } + ) + + // livechat v11.1: we must send channel emojis regexp to Prosody rooms + p.finally( + () => { + updateProsodyChannelEmojisRegex(options).then( + () => {}, + (err: any) => { + logger.error(err) + } + ) + } + ) }, () => {}) } catch (error) { options.peertubeHelpers.logger.error('Error when launching Prosody: ' + (error as string))