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) {
|
switch (name) {
|
||||||
case 'chat-type-help-disabled':
|
case 'chat-type-help-disabled':
|
||||||
return options.formValues['chat-type'] !== ('disabled' as ChatType)
|
return options.formValues['chat-type'] !== ('disabled' as ChatType)
|
||||||
|
case 'prosody-room-type':
|
||||||
case 'prosody-port':
|
case 'prosody-port':
|
||||||
case 'prosody-peertube-uri':
|
case 'prosody-peertube-uri':
|
||||||
case 'chat-type-help-builtin-prosody':
|
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.
|
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
|
#### 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.
|
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
|
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 {
|
export {
|
||||||
getChannelNameById,
|
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 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.
|
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`)
|
result.messages.push(`The prosody configuration file (${filePath}) exists`)
|
||||||
const actualContent = await fs.promises.readFile(filePath, {
|
const actualContent = await fs.promises.readFile(filePath, {
|
||||||
|
@ -69,6 +69,7 @@ interface ProsodyConfig {
|
|||||||
host: string
|
host: string
|
||||||
port: string
|
port: string
|
||||||
baseApiUrl: string
|
baseApiUrl: string
|
||||||
|
roomType: 'video' | 'channel'
|
||||||
}
|
}
|
||||||
async function getProsodyConfig (options: RegisterServerOptions): Promise<ProsodyConfig> {
|
async function getProsodyConfig (options: RegisterServerOptions): Promise<ProsodyConfig> {
|
||||||
const logger = options.peertubeHelpers.logger
|
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 enableC2s = (await options.settingsManager.getSetting('prosody-c2s') as boolean) || false
|
||||||
const prosodyDomain = await getProsodyDomain(options)
|
const prosodyDomain = await getProsodyDomain(options)
|
||||||
const paths = await getProsodyFilePaths(options)
|
const paths = await getProsodyFilePaths(options)
|
||||||
|
const roomType = (await options.settingsManager.getSetting('prosody-room-type')) === 'channel' ? 'channel' : 'video'
|
||||||
|
|
||||||
const apikey = await getAPIKey(options)
|
const apikey = await getAPIKey(options)
|
||||||
let baseApiUrl = await options.settingsManager.getSetting('prosody-peertube-uri') as string
|
let baseApiUrl = await options.settingsManager.getSetting('prosody-peertube-uri') as string
|
||||||
@ -139,7 +141,8 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise<Prosod
|
|||||||
paths,
|
paths,
|
||||||
port,
|
port,
|
||||||
baseApiUrl,
|
baseApiUrl,
|
||||||
host: prosodyDomain
|
host: prosodyDomain,
|
||||||
|
roomType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,11 +3,9 @@ import { getUserNameByChannelId } from '../../database/channel'
|
|||||||
|
|
||||||
interface Affiliations { [jid: string]: 'outcast' | 'none' | 'member' | 'admin' | 'owner' }
|
interface Affiliations { [jid: string]: 'outcast' | 'none' | 'member' | 'admin' | 'owner' }
|
||||||
|
|
||||||
async function getVideoAffiliations (options: RegisterServerOptions, video: MVideoThumbnail): Promise<Affiliations> {
|
async function _getCommonAffiliations (options: RegisterServerOptions, prosodyDomain: string): Promise<Affiliations> {
|
||||||
const peertubeHelpers = options.peertubeHelpers
|
|
||||||
const prosodyDomain = await getProsodyDomain(options)
|
|
||||||
// Get all admins and moderators
|
// Get all admins and moderators
|
||||||
const [results] = await peertubeHelpers.database.query(
|
const [results] = await options.peertubeHelpers.database.query(
|
||||||
'SELECT "username" FROM "user"' +
|
'SELECT "username" FROM "user"' +
|
||||||
' WHERE "user"."role" IN (0, 1)'
|
' WHERE "user"."role" IN (0, 1)'
|
||||||
)
|
)
|
||||||
@ -27,33 +25,57 @@ async function getVideoAffiliations (options: RegisterServerOptions, video: MVid
|
|||||||
r[jid] = 'owner'
|
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...
|
// NB: if it fails, we want previous results to be returned...
|
||||||
try {
|
try {
|
||||||
if (!video.remote) {
|
const username = await getUserNameByChannelId(options, channelId)
|
||||||
// don't add the video owner if it is a remote video!
|
if (username === null) {
|
||||||
const userName = await _getVideoOwnerUsername(options, video)
|
options.peertubeHelpers.logger.error(`Failed to get the username for channelId '${channelId}'.`)
|
||||||
const userJid = userName + '@' + prosodyDomain
|
} else {
|
||||||
|
const userJid = username + '@' + prosodyDomain
|
||||||
if (!(userJid in r)) { // don't override if already owner!
|
if (!(userJid in r)) { // don't override if already owner!
|
||||||
r[userJid] = 'admin'
|
r[userJid] = 'admin'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _getVideoOwnerUsername (options: RegisterServerOptions, video: MVideoThumbnail): Promise<string> {
|
async function getChannelAffiliations (options: RegisterServerOptions, channelId: number): Promise<Affiliations> {
|
||||||
const username = await getUserNameByChannelId(options, video.channelId)
|
const prosodyDomain = await getProsodyDomain(options)
|
||||||
if (username === null) {
|
const r = await _getCommonAffiliations(options, prosodyDomain)
|
||||||
throw new Error('Username not found')
|
|
||||||
}
|
// Adding an 'admin' affiliation for channel owner
|
||||||
return username
|
// NB: remote channel can't be found, there are not in the videoChannel table.
|
||||||
|
await _addAffiliationByChannelId(options, prosodyDomain, r, channelId)
|
||||||
|
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Affiliations,
|
Affiliations,
|
||||||
getVideoAffiliations
|
getVideoAffiliations,
|
||||||
|
getChannelAffiliations
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,11 @@ import { asyncMiddleware } from '../middlewares/async'
|
|||||||
import { getCheckAPIKeyMiddleware } from '../middlewares/apikey'
|
import { getCheckAPIKeyMiddleware } from '../middlewares/apikey'
|
||||||
import { prosodyCheckUserPassword, prosodyRegisterUser, prosodyUserRegistered } from '../prosody/auth'
|
import { prosodyCheckUserPassword, prosodyRegisterUser, prosodyUserRegistered } from '../prosody/auth'
|
||||||
import { getUserNickname } from '../helpers'
|
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 { getProsodyDomain } from '../prosody/config/domain'
|
||||||
import type { ChatType } from '../../../shared/lib/types'
|
import type { ChatType } from '../../../shared/lib/types'
|
||||||
import { fillVideoCustomFields } from '../custom-fields'
|
import { fillVideoCustomFields } from '../custom-fields'
|
||||||
|
import { getChannelInfosById } from '../database/channel'
|
||||||
|
|
||||||
// See here for description: https://modules.prosody.im/mod_muc_http_defaults.html
|
// See here for description: https://modules.prosody.im/mod_muc_http_defaults.html
|
||||||
interface RoomDefaults {
|
interface RoomDefaults {
|
||||||
@ -48,6 +49,50 @@ async function initApiRouter (options: RegisterServerOptions): Promise<Router> {
|
|||||||
const jid: string = req.query.jid as string || ''
|
const jid: string = req.query.jid as string || ''
|
||||||
logger.info(`Requesting room information for room '${jid}'.`)
|
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)
|
const video = await peertubeHelpers.videos.loadByIdOrUUID(jid)
|
||||||
if (!video) {
|
if (!video) {
|
||||||
logger.warn(`Video ${jid} not found`)
|
logger.warn(`Video ${jid} not found`)
|
||||||
@ -104,6 +149,7 @@ async function initApiRouter (options: RegisterServerOptions): Promise<Router> {
|
|||||||
}
|
}
|
||||||
res.json(roomDefaults)
|
res.json(roomDefaults)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]))
|
]))
|
||||||
|
|
||||||
router.get('/auth', asyncMiddleware(
|
router.get('/auth', asyncMiddleware(
|
||||||
|
@ -37,11 +37,12 @@ async function initWebchatRouter (options: RegisterServerOptions): Promise<Route
|
|||||||
|
|
||||||
const settings = await settingsManager.getSettings([
|
const settings = await settingsManager.getSettings([
|
||||||
'chat-type', 'chat-room', 'chat-server',
|
'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
|
const chatType: ChatType = (settings['chat-type'] ?? 'disabled') as ChatType
|
||||||
|
|
||||||
let server: string
|
let jid: string
|
||||||
let room: string
|
let room: string
|
||||||
let boshUri: string
|
let boshUri: string
|
||||||
let wsUri: string
|
let wsUri: string
|
||||||
@ -49,8 +50,12 @@ async function initWebchatRouter (options: RegisterServerOptions): Promise<Route
|
|||||||
let advancedControls: boolean = false
|
let advancedControls: boolean = false
|
||||||
if (chatType === 'builtin-prosody') {
|
if (chatType === 'builtin-prosody') {
|
||||||
const prosodyDomain = await getProsodyDomain(options)
|
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
|
room = '{{VIDEO_UUID}}@room.' + prosodyDomain
|
||||||
|
}
|
||||||
boshUri = getBaseRouterRoute(options) + 'webchat/http-bind'
|
boshUri = getBaseRouterRoute(options) + 'webchat/http-bind'
|
||||||
wsUri = ''
|
wsUri = ''
|
||||||
authenticationUrl = options.peertubeHelpers.config.getWebserverUrl() +
|
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']) {
|
if (!settings['chat-bosh-uri'] && !settings['chat-ws-uri']) {
|
||||||
throw new Error('Missing BOSH or Websocket 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
|
room = settings['chat-room'] as string
|
||||||
boshUri = settings['chat-bosh-uri'] as string
|
boshUri = settings['chat-bosh-uri'] as string
|
||||||
wsUri = settings['chat-ws-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)
|
let page = '' + (converseJSIndex as string)
|
||||||
const baseStaticUrl = getBaseStaticRoute(options)
|
const baseStaticUrl = getBaseStaticRoute(options)
|
||||||
page = page.replace(/{{BASE_STATIC_URL}}/g, baseStaticUrl)
|
page = page.replace(/{{BASE_STATIC_URL}}/g, baseStaticUrl)
|
||||||
page = page.replace(/{{JID}}/g, server)
|
page = page.replace(/{{JID}}/g, jid)
|
||||||
// Computing the room name...
|
// Computing the room name...
|
||||||
room = room.replace(/{{VIDEO_UUID}}/g, video.uuid)
|
room = room.replace(/{{VIDEO_UUID}}/g, video.uuid)
|
||||||
room = room.replace(/{{CHANNEL_ID}}/g, `${video.channelId}`)
|
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>',
|
descriptionHTML: '<a class="peertube-plugin-livechat-prosody-list-rooms">List rooms</a>',
|
||||||
private: true
|
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({
|
registerSetting({
|
||||||
name: 'prosody-port',
|
name: 'prosody-port',
|
||||||
label: 'Prosody port',
|
label: 'Prosody port',
|
||||||
|
Loading…
Reference in New Issue
Block a user