This commit is contained in:
2024-12-03 17:03:42 -05:00
231 changed files with 18277 additions and 11174 deletions

View File

@ -65,6 +65,8 @@ async function updateProsodyRoom (
name?: string
slow_mode_duration?: number
moderation_delay?: number
livechat_emoji_only?: boolean
livechat_custom_emoji_regexp?: string
livechat_muc_terms?: string
addAffiliations?: Affiliations
removeAffiliationsFor?: string[]
@ -100,6 +102,12 @@ async function updateProsodyRoom (
if ('livechat_muc_terms' in data) {
apiData.livechat_muc_terms = data.livechat_muc_terms ?? ''
}
if ('livechat_emoji_only' in data) {
apiData.livechat_emoji_only = data.livechat_emoji_only ?? false
}
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
}
@ -107,7 +115,7 @@ async function updateProsodyRoom (
apiData.removeAffiliationsFor = data.removeAffiliationsFor
}
try {
logger.debug('Calling update room API on url: ' + apiUrl + ', with data: ' + JSON.stringify(apiData))
logger.debug('Calling update room API on url: ' + apiUrl)
const result = await got(apiUrl, {
method: 'POST',
headers: {

View File

@ -65,8 +65,8 @@ export class LivechatProsodyAuth {
private readonly _prosodyDomain: string
private _userTokensEnabled: boolean
private readonly _tokensPath: string
private readonly _passwords: Map<string, Password> = new Map()
private readonly _tokensInfoByJID: Map<string, LivechatTokenInfos | undefined> = new Map()
private readonly _passwords = new Map<string, Password>()
private readonly _tokensInfoByJID = new Map<string, LivechatTokenInfos | undefined>()
private readonly _secretKey: string
protected readonly _logger: {
debug: (s: string) => void
@ -122,8 +122,8 @@ export class LivechatProsodyAuth {
const nickname: string | undefined = await getUserNickname(this._options, user)
return {
jid: normalizedUsername + '@' + this._prosodyDomain,
password: password,
nickname: nickname,
password,
nickname,
type: 'peertube'
}
}
@ -136,7 +136,7 @@ export class LivechatProsodyAuth {
if (this._userTokensEnabled) {
try {
const tokensInfo = await this._getTokensInfoForJID(normalizedUsername + '@' + this._prosodyDomain)
if (!tokensInfo || !tokensInfo.tokens.length) {
if (!tokensInfo?.tokens.length) {
return false
}
// Checking that the user is valid:
@ -159,7 +159,7 @@ export class LivechatProsodyAuth {
if (this._userTokensEnabled) {
try {
const tokensInfo = await this._getTokensInfoForJID(normalizedUsername + '@' + this._prosodyDomain)
if (!tokensInfo || !tokensInfo.tokens.length) {
if (!tokensInfo?.tokens.length) {
return false
}
// Checking that the user is valid:
@ -247,7 +247,7 @@ export class LivechatProsodyAuth {
}
const nickname: string | undefined = await getUserNickname(this._options, user)
const jid = normalizedUsername + '@' + this._prosodyDomain
const token = await this._createToken(user.id, jid, label)
const token = await this._createToken(user.id as number, jid, label)
token.nickname = nickname
return token
@ -279,7 +279,7 @@ export class LivechatProsodyAuth {
return false
}
await this._saveTokens(user.id, jid, tokensInfo.tokens.filter(t => t.id !== id))
await this._saveTokens(user.id as number, jid, tokensInfo.tokens.filter(t => t.id !== id))
return true
}
@ -293,8 +293,8 @@ export class LivechatProsodyAuth {
const password = generatePassword(20)
this._passwords.set(normalizedUsername, {
password: password,
validity: validity
password,
validity
})
return password
}
@ -330,7 +330,7 @@ export class LivechatProsodyAuth {
const user = await this._options.peertubeHelpers.user.loadById(userId)
if (!user || user.blocked) { return false }
return true
} catch (err) {
} catch (_err) {
return false
}
}
@ -381,7 +381,7 @@ export class LivechatProsodyAuth {
this._tokensInfoByJID.set(jid, undefined)
return undefined
}
throw err
throw err as Error
}
}
@ -452,11 +452,15 @@ export class LivechatProsodyAuth {
const encryptedArray = data.split(':')
const iv = Buffer.from(encryptedArray[0], outputEncoding)
const encrypted = Buffer.from(encryptedArray[1], outputEncoding)
const encrypted = encryptedArray[1]
const decipher = createDecipheriv(algorithm, this._secretKey, iv)
// FIXME: dismiss the "as any" below (dont understand why Typescript is not happy without)
return decipher.update(encrypted as any, outputEncoding, inputEncoding) + decipher.final(inputEncoding)
return decipher.update(
encrypted,
// here we must revert outputEncoding and inputEncoding, as were are decrypting.
outputEncoding,
inputEncoding
) + decipher.final(inputEncoding)
}
public static singleton (): LivechatProsodyAuth {

View File

@ -39,7 +39,7 @@ function startProsodyCertificatesRenewCheck (options: RegisterServerOptions, con
}, checkInterval)
renew = {
timer: timer
timer
}
}

View File

@ -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<string> {
const peertubeHelpers = options.peertubeHelpers
@ -122,7 +123,7 @@ async function getProsodyFilePaths (options: RegisterServerOptions): Promise<Pro
}
return {
dir: dir,
dir,
pid: path.resolve(dir, 'prosody.pid'),
error: path.resolve(dir, 'prosody.err'),
log: path.resolve(dir, 'prosody.log'),
@ -216,7 +217,7 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<Pros
? settings['chat-terms']
: undefined
let useExternal: boolean = false
let useExternal = false
try {
const oidcs = ExternalAuthOIDC.allSingletons()
for (const oidc of oidcs) {
@ -376,7 +377,7 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<Pros
config.useBotsVirtualHost(paths.botAvatars, paths.botAvatarsFiles)
bots.moderation = await BotConfiguration.singleton().getModerationBotGlobalConf()
if (bots.moderation?.connection?.password && typeof bots.moderation.connection.password === 'string') {
valuesToHideInDiagnostic.set('BotPassword', bots.moderation.connection.password)
valuesToHideInDiagnostic.set('BotPassword', bots.moderation.connection.password as string)
}
}
@ -389,6 +390,13 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<Pros
config.useModFirewall(modFirewallFiles)
}
const commonEmojisRegexp = Emojis.singletonSafe()?.getCommonEmojisRegexp()
if (commonEmojisRegexp) {
config.useRestrictMessage(commonEmojisRegexp)
} else {
logger.error('Fail to load common emojis regexp, disabling restrict message module.')
}
config.useTestModule(apikey, testApiUrl)
const debugMucAdminJids = debugMucAdmins(options)
@ -500,6 +508,11 @@ function getProsodyConfigContentForDiagnostic (config: ProsodyConfig, content?:
// replaceAll not available, using trick:
r = r.split(value).join(`***${key}***`)
}
// We also replace `peertubelivechat_restrict_message_common_emoji_regexp` because it could be a very long line
r = r.replace(
/^(?:(\s*peertubelivechat_restrict_message_common_emoji_regexp\s*=\s*.{0,10}).*)$/gm,
'$1 ***long line truncated***'
)
return r
}

View File

@ -7,7 +7,7 @@ import { getProsodyDomain } from './domain'
import { getUserNameByChannelId } from '../../database/channel'
import { BotConfiguration } from '../../configuration/bot'
interface Affiliations { [jid: string]: 'outcast' | 'none' | 'member' | 'admin' | 'owner' }
type Affiliations = Record<string, 'outcast' | 'none' | 'member' | 'admin' | 'owner'>
async function _getCommonAffiliations (options: RegisterServerOptions, _prosodyDomain: string): Promise<Affiliations> {
const r: Affiliations = {}

View File

@ -97,7 +97,7 @@ abstract class ProsodyConfigBlock {
if (!this.entries.has(name)) {
this.entries.set(name, [])
}
let entry = this.entries.get(name) as ConfigEntryValue
let entry = this.entries.get(name) ?? []
if (!Array.isArray(entry)) {
entry = [entry]
}
@ -112,7 +112,7 @@ abstract class ProsodyConfigBlock {
if (!this.entries.has(name)) {
return
}
let entry = this.entries.get(name) as ConfigEntryValue
let entry = this.entries.get(name) ?? []
if (!Array.isArray(entry)) {
entry = [entry]
}
@ -246,6 +246,7 @@ class ProsodyConfigContent {
this.muc.add('modules_enabled', 'pubsub_peertubelivechat')
this.muc.add('modules_enabled', 'muc_peertubelivechat_roles')
this.muc.add('modules_enabled', 'muc_peertubelivechat_announcements')
this.muc.add('modules_enabled', 'muc_peertubelivechat_terms')
this.muc.set('muc_terms_service_nickname', 'Peertube')
@ -562,6 +563,18 @@ class ProsodyConfigContent {
this.global.set('firewall_scripts', files)
}
/**
* Enable and configure the restrict message module.
* @param commonEmojiRegexp A regexp to match common emojis.
*/
useRestrictMessage (commonEmojiRegexp: string): void {
this.muc.add('modules_enabled', 'muc_peertubelivechat_restrict_message')
this.muc.set(
'peertubelivechat_restrict_message_common_emoji_regexp',
new ConfigEntryValueMultiLineString(commonEmojiRegexp)
)
}
addMucAdmins (jids: string[]): void {
for (const jid of jids) {
this.muc.add('admins', jid)
@ -587,6 +600,17 @@ class ProsodyConfigContent {
let content = ''
content += this.global.write()
content += this.log + '\n'
// Add some performance tweaks for Prosody 0.12.4+lua5.4.
// See https://github.com/JohnXLivingston/livechat-perf-test/tree/main/tests/33-prosody-gc
content += `
gc = {
mode = "generational";
minor_threshold = 5;
major_threshold = 50;
};
`
content += '\n\n'
if (this.authenticated) {
content += this.authenticated.write()

View File

@ -139,9 +139,9 @@ async function prosodyCtl (
reject(new Error('Missing prosodyctl command executable'))
return
}
let d: string = ''
let e: string = ''
let m: string = ''
let d = ''
let e = ''
let m = ''
const cmdArgs = [
...filePaths.execCtlArgs,
'--config',
@ -196,7 +196,7 @@ async function prosodyCtl (
// (else it can cause trouble by cleaning AppImage extract too soon)
spawned.on('close', (code) => {
resolve({
code: code,
code,
stdout: d,
sterr: e,
message: m
@ -399,8 +399,8 @@ async function ensureProsodyRunning (
})
}
logger.info('Waiting for the prosody process to launch')
let count: number = 0
let processStarted: boolean = false
let count = 0
let processStarted = false
while (!processStarted && count < 5) {
count++
await sleep(500)
@ -418,8 +418,8 @@ async function ensureProsodyRunning (
return
}
logger.info('Prosody is running')
await startProsodyLogRotate(options, filePaths)
await startProsodyCertificatesRenewCheck(options, config)
startProsodyLogRotate(options, filePaths)
startProsodyCertificatesRenewCheck(options, config)
}
async function ensureProsodyNotRunning (options: RegisterServerOptions): Promise<void> {

View File

@ -10,7 +10,7 @@ import { reloadProsody } from './ctl'
type Rotate = (file: string, options: {
count?: number
compress?: boolean
}, cb: Function) => void
}, cb: (err: any) => void) => void
const rotate: Rotate = require('log-rotate')
interface ProsodyLogRotate {
@ -27,9 +27,10 @@ async function _rotate (options: RegisterServerOptions, path: string): Promise<v
rotate(path, { count: 14, compress: false }, (err: any) => {
if (err) {
options.peertubeHelpers.logger.error('Failed to rotate file ' + path, err)
return resolve()
resolve()
return
}
return resolve()
resolve()
})
})
return p
@ -79,7 +80,7 @@ function startProsodyLogRotate (options: RegisterServerOptions, paths: ProsodyFi
}, checkInterval)
logRotate = {
timer: timer,
timer,
lastRotation: Date.now()
}
}

View File

@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
//
// 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 v12.0.0: we must send channel custom emojis regexp to Prosody.
*
* This script will only be launched one time.
*/
async function updateProsodyChannelEmojisRegex (options: RegisterServerOptions): Promise<void> {
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
}