Builtin Prosody: adding the prosody-room-type settings to allow rooms to be per channel or per video. WIP.
This commit is contained in:
parent
5237c20332
commit
5c0b274f39
@ -167,6 +167,7 @@ function register ({ registerHook, registerSettingsScript, peertubeHelpers }: Re
|
||||
switch (name) {
|
||||
case 'chat-type-help-disabled':
|
||||
return options.formValues['chat-type'] !== ('disabled' as ChatType)
|
||||
case 'prosody-room-type':
|
||||
case 'prosody-port':
|
||||
case 'prosody-peertube-uri':
|
||||
case 'chat-type-help-builtin-prosody':
|
||||
|
@ -52,6 +52,10 @@ You can find the source for this Dockerfile [here](../docker/Dockerfile.buster).
|
||||
|
||||
Just select «Prosody server controlled by Peertube» as chat mode.
|
||||
|
||||
### Room type
|
||||
|
||||
You can choose here to have separate rooms for each video, or to group them by channel.
|
||||
|
||||
#### Prosody port
|
||||
|
||||
This is the port that the Prosody server will use. By default it is set to 52800. If you want to use another port, just change the value here.
|
||||
|
@ -45,7 +45,43 @@ async function getUserNameByChannelId (options: RegisterServerOptions, channelId
|
||||
return results[0].username ?? null
|
||||
}
|
||||
|
||||
interface ChannelInfos {
|
||||
id: number
|
||||
name: string
|
||||
displayName: string
|
||||
}
|
||||
|
||||
async function getChannelInfosById (options: RegisterServerOptions, channelId: number): Promise<ChannelInfos | null> {
|
||||
if (!channelId) {
|
||||
throw new Error('Missing channelId')
|
||||
}
|
||||
if (!Number.isInteger(channelId)) {
|
||||
throw new Error('Invalid channelId: not an integer')
|
||||
}
|
||||
const [results] = await options.peertubeHelpers.database.query(
|
||||
'SELECT' +
|
||||
' "actor"."preferredUsername" as "channelName", ' +
|
||||
' "videoChannel"."name" as "channelDisplayName"' +
|
||||
' FROM "videoChannel"' +
|
||||
' RIGHT JOIN "actor" ON "actor"."id" = "videoChannel"."actorId"' +
|
||||
' WHERE "videoChannel"."id" = ' + channelId.toString()
|
||||
)
|
||||
if (!Array.isArray(results)) {
|
||||
throw new Error('getChannelInfosById: query result is not an array.')
|
||||
}
|
||||
if (!results[0]) {
|
||||
options.peertubeHelpers.logger.debug(`getChannelInfosById: channel ${channelId} not found.`)
|
||||
return null
|
||||
}
|
||||
return {
|
||||
id: channelId,
|
||||
name: results[0].channelName ?? '',
|
||||
displayName: results[0].channelDisplayName ?? ''
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getChannelNameById,
|
||||
getUserNameByChannelId
|
||||
getUserNameByChannelId,
|
||||
getChannelInfosById
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ export async function diagProsody (test: string, options: RegisterServerOptions)
|
||||
|
||||
result.messages.push(`Prosody modules path will be '${wantedConfig.paths.modules}'`)
|
||||
|
||||
result.messages.push(`Prosody rooms will be grouped by '${wantedConfig.roomType}'.`)
|
||||
|
||||
await fs.promises.access(filePath, fs.constants.R_OK) // throw an error if file does not exist.
|
||||
result.messages.push(`The prosody configuration file (${filePath}) exists`)
|
||||
const actualContent = await fs.promises.readFile(filePath, {
|
||||
|
@ -69,6 +69,7 @@ interface ProsodyConfig {
|
||||
host: string
|
||||
port: string
|
||||
baseApiUrl: string
|
||||
roomType: 'video' | 'channel'
|
||||
}
|
||||
async function getProsodyConfig (options: RegisterServerOptions): Promise<ProsodyConfig> {
|
||||
const logger = options.peertubeHelpers.logger
|
||||
@ -81,6 +82,7 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise<Prosod
|
||||
const enableC2s = (await options.settingsManager.getSetting('prosody-c2s') as boolean) || false
|
||||
const prosodyDomain = await getProsodyDomain(options)
|
||||
const paths = await getProsodyFilePaths(options)
|
||||
const roomType = (await options.settingsManager.getSetting('prosody-room-type')) === 'channel' ? 'channel' : 'video'
|
||||
|
||||
const apikey = await getAPIKey(options)
|
||||
let baseApiUrl = await options.settingsManager.getSetting('prosody-peertube-uri') as string
|
||||
@ -139,7 +141,8 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise<Prosod
|
||||
paths,
|
||||
port,
|
||||
baseApiUrl,
|
||||
host: prosodyDomain
|
||||
host: prosodyDomain,
|
||||
roomType
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,9 @@ import { getUserNameByChannelId } from '../../database/channel'
|
||||
|
||||
interface Affiliations { [jid: string]: 'outcast' | 'none' | 'member' | 'admin' | 'owner' }
|
||||
|
||||
async function getVideoAffiliations (options: RegisterServerOptions, video: MVideoThumbnail): Promise<Affiliations> {
|
||||
const peertubeHelpers = options.peertubeHelpers
|
||||
const prosodyDomain = await getProsodyDomain(options)
|
||||
async function _getCommonAffiliations (options: RegisterServerOptions, prosodyDomain: string): Promise<Affiliations> {
|
||||
// Get all admins and moderators
|
||||
const [results] = await peertubeHelpers.database.query(
|
||||
const [results] = await options.peertubeHelpers.database.query(
|
||||
'SELECT "username" FROM "user"' +
|
||||
' WHERE "user"."role" IN (0, 1)'
|
||||
)
|
||||
@ -27,33 +25,57 @@ async function getVideoAffiliations (options: RegisterServerOptions, video: MVid
|
||||
r[jid] = 'owner'
|
||||
}
|
||||
|
||||
// Adding an 'admin' affiliation for video owner
|
||||
return r
|
||||
}
|
||||
|
||||
async function _addAffiliationByChannelId (
|
||||
options: RegisterServerOptions,
|
||||
prosodyDomain: string,
|
||||
r: Affiliations,
|
||||
channelId: number
|
||||
): Promise<void> {
|
||||
// NB: if it fails, we want previous results to be returned...
|
||||
try {
|
||||
if (!video.remote) {
|
||||
// don't add the video owner if it is a remote video!
|
||||
const userName = await _getVideoOwnerUsername(options, video)
|
||||
const userJid = userName + '@' + prosodyDomain
|
||||
const username = await getUserNameByChannelId(options, channelId)
|
||||
if (username === null) {
|
||||
options.peertubeHelpers.logger.error(`Failed to get the username for channelId '${channelId}'.`)
|
||||
} else {
|
||||
const userJid = username + '@' + prosodyDomain
|
||||
if (!(userJid in r)) { // don't override if already owner!
|
||||
r[userJid] = 'admin'
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
peertubeHelpers.logger.error('Failed to get video owner informations:', error)
|
||||
options.peertubeHelpers.logger.error('Failed to get channel owner informations:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function getVideoAffiliations (options: RegisterServerOptions, video: MVideoThumbnail): Promise<Affiliations> {
|
||||
const prosodyDomain = await getProsodyDomain(options)
|
||||
const r = await _getCommonAffiliations(options, prosodyDomain)
|
||||
|
||||
// Adding an 'admin' affiliation for video owner
|
||||
if (!video.remote) {
|
||||
// don't add the video owner if it is a remote video!
|
||||
await _addAffiliationByChannelId(options, prosodyDomain, r, video.channelId)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
async function _getVideoOwnerUsername (options: RegisterServerOptions, video: MVideoThumbnail): Promise<string> {
|
||||
const username = await getUserNameByChannelId(options, video.channelId)
|
||||
if (username === null) {
|
||||
throw new Error('Username not found')
|
||||
}
|
||||
return username
|
||||
async function getChannelAffiliations (options: RegisterServerOptions, channelId: number): Promise<Affiliations> {
|
||||
const prosodyDomain = await getProsodyDomain(options)
|
||||
const r = await _getCommonAffiliations(options, prosodyDomain)
|
||||
|
||||
// Adding an 'admin' affiliation for channel owner
|
||||
// NB: remote channel can't be found, there are not in the videoChannel table.
|
||||
await _addAffiliationByChannelId(options, prosodyDomain, r, channelId)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
export {
|
||||
Affiliations,
|
||||
getVideoAffiliations
|
||||
getVideoAffiliations,
|
||||
getChannelAffiliations
|
||||
}
|
||||
|
@ -4,10 +4,11 @@ import { asyncMiddleware } from '../middlewares/async'
|
||||
import { getCheckAPIKeyMiddleware } from '../middlewares/apikey'
|
||||
import { prosodyCheckUserPassword, prosodyRegisterUser, prosodyUserRegistered } from '../prosody/auth'
|
||||
import { getUserNickname } from '../helpers'
|
||||
import { Affiliations, getVideoAffiliations } from '../prosody/config/affiliations'
|
||||
import { Affiliations, getVideoAffiliations, getChannelAffiliations } from '../prosody/config/affiliations'
|
||||
import { getProsodyDomain } from '../prosody/config/domain'
|
||||
import type { ChatType } from '../../../shared/lib/types'
|
||||
import { fillVideoCustomFields } from '../custom-fields'
|
||||
import { getChannelInfosById } from '../database/channel'
|
||||
|
||||
// See here for description: https://modules.prosody.im/mod_muc_http_defaults.html
|
||||
interface RoomDefaults {
|
||||
@ -48,6 +49,50 @@ async function initApiRouter (options: RegisterServerOptions): Promise<Router> {
|
||||
const jid: string = req.query.jid as string || ''
|
||||
logger.info(`Requesting room information for room '${jid}'.`)
|
||||
|
||||
const settings = await options.settingsManager.getSettings([
|
||||
'chat-type',
|
||||
'prosody-room-type'
|
||||
])
|
||||
if (settings['chat-type'] !== ('builtin-prosody' as ChatType)) {
|
||||
logger.warn('Prosody chat is not active')
|
||||
res.sendStatus(403)
|
||||
return
|
||||
}
|
||||
// Now, we have two different room type: per video or per channel.
|
||||
if (settings['prosody-room-type'] === 'channel') {
|
||||
const matches = jid.match(/^channel\.(\d+)$/)
|
||||
if (!matches || !matches[1]) {
|
||||
logger.warn(`Invalid channel room jid '${jid}'.`)
|
||||
res.sendStatus(403)
|
||||
return
|
||||
}
|
||||
const channelId = parseInt(matches[1])
|
||||
const channelInfos = await getChannelInfosById(options, channelId)
|
||||
if (!channelInfos) {
|
||||
logger.warn(`Channel ${channelId} not found`)
|
||||
res.sendStatus(403)
|
||||
return
|
||||
}
|
||||
|
||||
let affiliations: Affiliations
|
||||
try {
|
||||
affiliations = await getChannelAffiliations(options, channelId)
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get channel affiliations for ${channelId}:`, error)
|
||||
// affiliations: should at least be {}, so that the first user will not be moderator/admin
|
||||
affiliations = {}
|
||||
}
|
||||
|
||||
const roomDefaults: RoomDefaults = {
|
||||
config: {
|
||||
name: channelInfos.displayName,
|
||||
description: '',
|
||||
subject: channelInfos.displayName
|
||||
},
|
||||
affiliations: affiliations
|
||||
}
|
||||
res.json(roomDefaults)
|
||||
} else {
|
||||
const video = await peertubeHelpers.videos.loadByIdOrUUID(jid)
|
||||
if (!video) {
|
||||
logger.warn(`Video ${jid} not found`)
|
||||
@ -104,6 +149,7 @@ async function initApiRouter (options: RegisterServerOptions): Promise<Router> {
|
||||
}
|
||||
res.json(roomDefaults)
|
||||
}
|
||||
}
|
||||
]))
|
||||
|
||||
router.get('/auth', asyncMiddleware(
|
||||
|
@ -37,11 +37,12 @@ async function initWebchatRouter (options: RegisterServerOptions): Promise<Route
|
||||
|
||||
const settings = await settingsManager.getSettings([
|
||||
'chat-type', 'chat-room', 'chat-server',
|
||||
'chat-bosh-uri', 'chat-ws-uri'
|
||||
'chat-bosh-uri', 'chat-ws-uri',
|
||||
'prosody-room-type'
|
||||
])
|
||||
const chatType: ChatType = (settings['chat-type'] ?? 'disabled') as ChatType
|
||||
|
||||
let server: string
|
||||
let jid: string
|
||||
let room: string
|
||||
let boshUri: string
|
||||
let wsUri: string
|
||||
@ -49,8 +50,12 @@ async function initWebchatRouter (options: RegisterServerOptions): Promise<Route
|
||||
let advancedControls: boolean = false
|
||||
if (chatType === 'builtin-prosody') {
|
||||
const prosodyDomain = await getProsodyDomain(options)
|
||||
server = 'anon.' + prosodyDomain
|
||||
jid = 'anon.' + prosodyDomain
|
||||
if (settings['prosody-room-type'] === 'channel') {
|
||||
room = 'channel.{{CHANNEL_ID}}@room.' + prosodyDomain
|
||||
} else {
|
||||
room = '{{VIDEO_UUID}}@room.' + prosodyDomain
|
||||
}
|
||||
boshUri = getBaseRouterRoute(options) + 'webchat/http-bind'
|
||||
wsUri = ''
|
||||
authenticationUrl = options.peertubeHelpers.config.getWebserverUrl() +
|
||||
@ -67,7 +72,7 @@ async function initWebchatRouter (options: RegisterServerOptions): Promise<Route
|
||||
if (!settings['chat-bosh-uri'] && !settings['chat-ws-uri']) {
|
||||
throw new Error('Missing BOSH or Websocket uri.')
|
||||
}
|
||||
server = settings['chat-server'] as string
|
||||
jid = settings['chat-server'] as string
|
||||
room = settings['chat-room'] as string
|
||||
boshUri = settings['chat-bosh-uri'] as string
|
||||
wsUri = settings['chat-ws-uri'] as string
|
||||
@ -84,7 +89,7 @@ async function initWebchatRouter (options: RegisterServerOptions): Promise<Route
|
||||
let page = '' + (converseJSIndex as string)
|
||||
const baseStaticUrl = getBaseStaticRoute(options)
|
||||
page = page.replace(/{{BASE_STATIC_URL}}/g, baseStaticUrl)
|
||||
page = page.replace(/{{JID}}/g, server)
|
||||
page = page.replace(/{{JID}}/g, jid)
|
||||
// Computing the room name...
|
||||
room = room.replace(/{{VIDEO_UUID}}/g, video.uuid)
|
||||
room = room.replace(/{{CHANNEL_ID}}/g, `${video.channelId}`)
|
||||
|
@ -98,6 +98,20 @@ Please read the
|
||||
descriptionHTML: '<a class="peertube-plugin-livechat-prosody-list-rooms">List rooms</a>',
|
||||
private: true
|
||||
})
|
||||
|
||||
registerSetting({
|
||||
name: 'prosody-room-type',
|
||||
label: 'Room type',
|
||||
type: 'select',
|
||||
descriptionHTML: 'You can choose here to have separate rooms for each video, or to group them by channel.',
|
||||
private: false,
|
||||
default: 'video',
|
||||
options: [
|
||||
{ value: 'video', label: 'Each video has its own webchat room' },
|
||||
{ value: 'channel', label: 'Webchat rooms are grouped by channel' }
|
||||
]
|
||||
})
|
||||
|
||||
registerSetting({
|
||||
name: 'prosody-port',
|
||||
label: 'Prosody port',
|
||||
|
Loading…
Reference in New Issue
Block a user