118 lines
4.4 KiB
TypeScript
118 lines
4.4 KiB
TypeScript
// 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 { Affiliations, getVideoAffiliations, getChannelAffiliations } from '../config/affiliations'
|
|
import { getProsodyDomain } from '../config/domain'
|
|
import * as path from 'path'
|
|
import * as fs from 'fs'
|
|
|
|
/**
|
|
* Livechat v10.0.0: we change the way MUC affiliations are handled.
|
|
* So we must remove all affiliations to peertube admin/owner (unless there are video/channel owners).
|
|
* For more info, see https://github.com/JohnXLivingston/peertube-plugin-livechat/issues/385
|
|
*
|
|
* This script will only be launched one time.
|
|
*/
|
|
async function migrateMUCAffiliations (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-v10-affiliations')
|
|
if (fs.existsSync(doneFilePath)) {
|
|
logger.debug('[migratev10MUCAffiliations] MUC affiliations for v10 already migrated.')
|
|
return
|
|
}
|
|
|
|
logger.info('[migratev10MUCAffiliations] Migrating MUC affiliations for livechat v10...')
|
|
|
|
const prosodyDomain = await getProsodyDomain(options)
|
|
const rooms = await listProsodyRooms(options)
|
|
logger.debug('[migratev10MUCAffiliations] Found ' + rooms.length.toString() + ' rooms.')
|
|
|
|
logger.debug('[migratev10MUCAffiliations] loading peertube admins and moderators...')
|
|
const peertubeAff = await _getPeertubeAdminsAndModerators(options, prosodyDomain)
|
|
|
|
for (const room of rooms) {
|
|
try {
|
|
let affiliations: Affiliations
|
|
logger.info('[migratev10MUCAffiliations] Must migrate affiliations for room ' + room.localpart)
|
|
const matches = room.localpart.match(/^channel\.(\d+)$/)
|
|
if (matches?.[1]) {
|
|
// room associated to a channel
|
|
const channelId: number = parseInt(matches[1])
|
|
if (isNaN(channelId)) { throw new Error('Invalid channelId ' + room.localpart) }
|
|
affiliations = await getChannelAffiliations(options, channelId)
|
|
} else {
|
|
// room associated to a video
|
|
const video = await options.peertubeHelpers.videos.loadByIdOrUUID(room.localpart)
|
|
if (!video || video.remote) {
|
|
logger.info('[migratev10MUCAffiliations] Video ' + room.localpart + ' not found or remote, skipping')
|
|
continue
|
|
}
|
|
affiliations = await getVideoAffiliations(options, video)
|
|
}
|
|
|
|
const affiliationsToRemove: string[] = []
|
|
for (const jid in peertubeAff) {
|
|
if (jid in affiliations) {
|
|
continue
|
|
}
|
|
affiliationsToRemove.push(jid)
|
|
}
|
|
|
|
logger.debug(
|
|
'[migratev10MUCAffiliations] Room ' + room.localpart + ', affiliations to set: ' + JSON.stringify(affiliations)
|
|
)
|
|
logger.debug(
|
|
'[migratev10MUCAffiliations] Room ' +
|
|
room.localpart + ', affilations to remove: ' + JSON.stringify(affiliationsToRemove)
|
|
)
|
|
await updateProsodyRoom(options, room.jid, {
|
|
addAffiliations: affiliations,
|
|
removeAffiliationsFor: affiliationsToRemove
|
|
})
|
|
} catch (err) {
|
|
logger.error(
|
|
'[migratev10MUCAffiliations] Failed to handle room ' + room.localpart + ', skipping. Error: ' + (err as string)
|
|
)
|
|
continue
|
|
}
|
|
}
|
|
|
|
await fs.promises.writeFile(doneFilePath, '')
|
|
}
|
|
|
|
async function _getPeertubeAdminsAndModerators (
|
|
options: RegisterServerOptions,
|
|
prosodyDomain: string
|
|
): Promise<Affiliations> {
|
|
// Get all admins and moderators
|
|
const [results] = await options.peertubeHelpers.database.query(
|
|
'SELECT "username" FROM "user"' +
|
|
' WHERE "user"."role" IN (0, 1)'
|
|
)
|
|
if (!Array.isArray(results)) {
|
|
throw new Error('_getPeertubeAdminsAndModerators: query result is not an array.')
|
|
}
|
|
const r: Affiliations = {}
|
|
for (let i = 0; i < results.length; i++) {
|
|
const result = results[i]
|
|
if (typeof result !== 'object') {
|
|
throw new Error('_getPeertubeAdminsAndModerators: query result is not an object')
|
|
}
|
|
if (!('username' in result)) {
|
|
throw new Error('_getPeertubeAdminsAndModerators: no username field in result')
|
|
}
|
|
const jid = (result.username as string) + '@' + prosodyDomain
|
|
r[jid] = 'member' // member, but in fact the migration will just remove the affilation.
|
|
}
|
|
return r
|
|
}
|
|
|
|
export {
|
|
migrateMUCAffiliations
|
|
}
|