Merge branch 'main' of https://github.com/JohnXLivingston/peertube-plugin-livechat
This commit is contained in:
@ -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: {
|
||||
|
@ -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 {
|
||||
|
@ -39,7 +39,7 @@ function startProsodyCertificatesRenewCheck (options: RegisterServerOptions, con
|
||||
}, checkInterval)
|
||||
|
||||
renew = {
|
||||
timer: timer
|
||||
timer
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 = {}
|
||||
|
@ -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()
|
||||
|
@ -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> {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
76
server/lib/prosody/migration/migrateV12.ts
Normal file
76
server/lib/prosody/migration/migrateV12.ts
Normal 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
|
||||
}
|
Reference in New Issue
Block a user