2024-05-23 09:42:14 +00:00
|
|
|
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2023-05-24 13:09:56 +00:00
|
|
|
import type { RegisterServerOptions, VideoObject, SettingValue } from '@peertube/peertube-types'
|
|
|
|
import type {
|
|
|
|
LiveChatVideoObject,
|
|
|
|
VideoBuildResultContext,
|
|
|
|
LiveChatJSONLDLink,
|
|
|
|
LiveChatJSONLDAttribute,
|
|
|
|
PeertubeXMPPServerInfos
|
|
|
|
} from './types'
|
2023-04-20 16:28:08 +00:00
|
|
|
import { storeVideoLiveChatInfos } from './storage'
|
2023-04-20 14:07:00 +00:00
|
|
|
import { videoHasWebchat } from '../../../shared/lib/video'
|
2023-07-04 16:07:59 +00:00
|
|
|
import { getBoshUri, getWSUri, getWSS2SUri, getPublicChatUri } from '../uri/webchat'
|
2023-04-20 14:07:00 +00:00
|
|
|
import { canonicalizePluginUri } from '../uri/canonicalize'
|
|
|
|
import { getProsodyDomain } from '../prosody/config/domain'
|
2023-04-21 17:28:43 +00:00
|
|
|
import { fillVideoCustomFields } from '../custom-fields'
|
2024-06-06 16:04:17 +00:00
|
|
|
import { Emojis } from '../emojis'
|
2023-07-05 10:10:27 +00:00
|
|
|
import { loc } from '../loc'
|
2023-07-06 14:39:32 +00:00
|
|
|
import { isDebugMode } from '../debug'
|
2023-04-20 14:07:00 +00:00
|
|
|
|
2023-04-20 16:28:08 +00:00
|
|
|
/**
|
|
|
|
* This function adds LiveChat information on video ActivityPub data if relevant.
|
|
|
|
* @param options server options
|
2023-07-04 16:07:59 +00:00
|
|
|
* @param videoJsonld JSON-LD video data to fill
|
2023-04-20 16:28:08 +00:00
|
|
|
* @param context handler context
|
|
|
|
* @returns void
|
|
|
|
*/
|
2023-04-20 14:07:00 +00:00
|
|
|
async function videoBuildJSONLD (
|
|
|
|
options: RegisterServerOptions,
|
2023-07-04 16:07:59 +00:00
|
|
|
videoJsonld: VideoObject | LiveChatVideoObject,
|
2023-04-20 14:07:00 +00:00
|
|
|
context: VideoBuildResultContext
|
|
|
|
): Promise<VideoObject | LiveChatVideoObject> {
|
|
|
|
const logger = options.peertubeHelpers.logger
|
|
|
|
const video = context.video
|
2023-07-04 16:07:59 +00:00
|
|
|
if (video.remote) { return videoJsonld } // should not happen, but... just in case...
|
2023-04-20 14:07:00 +00:00
|
|
|
|
|
|
|
const settings = await options.settingsManager.getSettings([
|
|
|
|
'chat-per-live-video',
|
|
|
|
'chat-all-lives',
|
|
|
|
'chat-all-non-lives',
|
|
|
|
'chat-videos-list',
|
|
|
|
'disable-websocket',
|
|
|
|
'prosody-room-type',
|
2023-04-21 15:24:16 +00:00
|
|
|
'federation-dont-publish-remotely',
|
2023-05-04 17:14:23 +00:00
|
|
|
'chat-no-anonymous',
|
2023-05-19 10:52:52 +00:00
|
|
|
'prosody-room-allow-s2s',
|
|
|
|
'prosody-s2s-port'
|
2023-04-20 14:07:00 +00:00
|
|
|
])
|
|
|
|
|
|
|
|
if (settings['federation-dont-publish-remotely']) {
|
2023-04-20 16:32:13 +00:00
|
|
|
// Note: we store also outgoing data. Could help for migration/cleanup scripts, for example.
|
|
|
|
await storeVideoLiveChatInfos(options, video, false)
|
2023-07-04 16:07:59 +00:00
|
|
|
return videoJsonld
|
2023-04-20 14:07:00 +00:00
|
|
|
}
|
|
|
|
|
2023-04-21 17:24:55 +00:00
|
|
|
await fillVideoCustomFields(options, video)
|
2023-04-20 14:07:00 +00:00
|
|
|
const hasChat = await videoHasWebchat({
|
|
|
|
'chat-per-live-video': !!settings['chat-per-live-video'],
|
|
|
|
'chat-all-lives': !!settings['chat-all-lives'],
|
|
|
|
'chat-all-non-lives': !!settings['chat-all-non-lives'],
|
|
|
|
'chat-videos-list': settings['chat-videos-list'] as string
|
|
|
|
}, video)
|
|
|
|
|
|
|
|
if (!hasChat) {
|
2023-04-20 16:28:08 +00:00
|
|
|
logger.debug(`Video uuid=${video.uuid} has not livechat, adding peertubeLiveChat=false.`)
|
|
|
|
// Note: we store also outgoing data. Could help for migration/cleanup scripts, for example.
|
|
|
|
await storeVideoLiveChatInfos(options, video, false)
|
2023-07-04 16:07:59 +00:00
|
|
|
return videoJsonld
|
2023-04-20 14:07:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
logger.debug(`Adding LiveChat data on video uuid=${video.uuid}...`)
|
|
|
|
|
|
|
|
const prosodyDomain = await getProsodyDomain(options)
|
|
|
|
let roomJID: string
|
|
|
|
if (settings['prosody-room-type'] === 'channel') {
|
|
|
|
roomJID = `channel.${video.channelId}@room.${prosodyDomain}`
|
|
|
|
} else {
|
|
|
|
roomJID = `${video.uuid}@room.${prosodyDomain}`
|
|
|
|
}
|
|
|
|
|
2023-05-24 13:09:56 +00:00
|
|
|
const serverInfos = await _serverBuildInfos(options, {
|
|
|
|
'federation-dont-publish-remotely': settings['federation-dont-publish-remotely'],
|
|
|
|
'prosody-s2s-port': settings['prosody-s2s-port'],
|
|
|
|
'prosody-room-allow-s2s': settings['prosody-room-allow-s2s'],
|
|
|
|
'disable-websocket': settings['disable-websocket'],
|
|
|
|
'chat-no-anonymous': settings['chat-no-anonymous']
|
|
|
|
})
|
|
|
|
|
2023-07-05 10:10:27 +00:00
|
|
|
const chatTitle = loc('chat_for_live_stream') + ' ' + video.name
|
|
|
|
|
2023-07-04 16:07:59 +00:00
|
|
|
// Adding attachments, as described in FEP-1970
|
|
|
|
const discussionLinks: LiveChatVideoObject['attachment'] = []
|
|
|
|
discussionLinks.push({
|
|
|
|
type: 'Link',
|
2023-07-05 10:10:27 +00:00
|
|
|
name: chatTitle,
|
2023-07-04 16:07:59 +00:00
|
|
|
rel: 'discussion',
|
|
|
|
href: getPublicChatUri(options, videoJsonld)
|
|
|
|
})
|
|
|
|
// Adding the xmpp:// link requires:
|
|
|
|
// - prosody-room-allow-s2s
|
|
|
|
// - prosody-s2s-port
|
|
|
|
// For now, this can be tested reading serverInfos.directs2s
|
2023-07-06 14:39:32 +00:00
|
|
|
// There is a debug_mode flag to always enable it.
|
|
|
|
if (!!serverInfos.directs2s || isDebugMode(options, 'alwaysPublishXMPPRoom')) {
|
2023-07-04 16:07:59 +00:00
|
|
|
discussionLinks.push({
|
|
|
|
type: 'Link',
|
2023-07-05 10:10:27 +00:00
|
|
|
name: chatTitle,
|
2023-07-04 16:07:59 +00:00
|
|
|
rel: 'discussion',
|
|
|
|
href: 'xmpp://' + roomJID + '?join'
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!('attachment' in videoJsonld) || !videoJsonld.attachment) {
|
|
|
|
Object.assign(videoJsonld, {
|
|
|
|
attachment: discussionLinks
|
|
|
|
})
|
|
|
|
} else if (Array.isArray(videoJsonld.attachment)) {
|
|
|
|
videoJsonld.attachment.push(...discussionLinks)
|
|
|
|
} else {
|
|
|
|
videoJsonld.attachment = [
|
|
|
|
videoJsonld.attachment,
|
|
|
|
...discussionLinks
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Code beneath this point is for backward compatibility, before v7.2.0.
|
|
|
|
// Since then, the ActivityPub metadata were not standardized.
|
2024-05-05 10:47:33 +00:00
|
|
|
// Note: plugin version >=7.2.0 still uses these data to get remote server informations
|
|
|
|
// (not 100% sure if it is needed or not)
|
2023-07-04 16:07:59 +00:00
|
|
|
|
2023-05-24 13:09:56 +00:00
|
|
|
// For backward compatibility with remote servers, using plugin <=6.3.0, we must provide links:
|
2023-04-21 15:24:16 +00:00
|
|
|
const links: LiveChatJSONLDLink[] = []
|
2023-05-24 13:09:56 +00:00
|
|
|
if (serverInfos.anonymous) {
|
|
|
|
if (serverInfos.anonymous.bosh) {
|
|
|
|
links.push({
|
|
|
|
type: 'xmpp-bosh-anonymous',
|
|
|
|
url: serverInfos.anonymous.bosh,
|
|
|
|
jid: serverInfos.anonymous.virtualhost
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (serverInfos.anonymous.websocket) {
|
|
|
|
links.push({
|
|
|
|
type: 'xmpp-websocket-anonymous',
|
|
|
|
url: serverInfos.anonymous.websocket,
|
|
|
|
jid: serverInfos.anonymous.virtualhost
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-04 16:07:59 +00:00
|
|
|
// This is the custom format used by plugin > 6.3.0.
|
2023-05-24 13:09:56 +00:00
|
|
|
const peertubeLiveChat: LiveChatJSONLDAttribute = {
|
|
|
|
type: 'xmpp',
|
|
|
|
jid: roomJID,
|
|
|
|
links,
|
2024-06-06 16:04:17 +00:00
|
|
|
xmppserver: serverInfos,
|
|
|
|
customEmojisUrl: await Emojis.singletonSafe()?.channelCustomEmojisUrl(video.channelId)
|
2023-05-24 13:09:56 +00:00
|
|
|
}
|
2023-07-04 16:07:59 +00:00
|
|
|
Object.assign(videoJsonld, {
|
2023-05-24 13:09:56 +00:00
|
|
|
peertubeLiveChat
|
|
|
|
})
|
|
|
|
// Note: we store also outgoing data. Could help for migration/cleanup scripts, for example.
|
|
|
|
await storeVideoLiveChatInfos(options, video, peertubeLiveChat)
|
2023-07-04 16:07:59 +00:00
|
|
|
return videoJsonld
|
2023-05-24 13:09:56 +00:00
|
|
|
}
|
|
|
|
|
2024-05-05 10:47:33 +00:00
|
|
|
async function videoContextBuildJSONLD (_options: RegisterServerOptions, jsonld: any[]): Promise<any> {
|
|
|
|
// Note: this function is called for all kind of context, not only video.
|
|
|
|
// We have no parameter to know on which context we currently are.
|
|
|
|
// See: https://github.com/Chocobozzz/PeerTube/issues/6375
|
|
|
|
// But we only want to add some context on videos...
|
|
|
|
// So, to detect if we are on video, we search for a 'isLiveBroadcast' field in jsonld (this only exists for videos).
|
|
|
|
|
|
|
|
const entry = jsonld.find(e => typeof e === 'object' && ('isLiveBroadcast' in e))
|
|
|
|
if (!entry) {
|
|
|
|
return jsonld
|
|
|
|
}
|
|
|
|
|
|
|
|
// We are on a video!
|
|
|
|
return jsonld.concat([{
|
|
|
|
ptlc: 'urn:peertube-plugin-livechat',
|
|
|
|
peertubeLiveChat: {
|
|
|
|
'@id': 'ptlc:peertubeLiveChat', '@type': '@json'
|
|
|
|
}
|
|
|
|
}])
|
|
|
|
}
|
|
|
|
|
2023-05-24 13:09:56 +00:00
|
|
|
async function serverBuildInfos (options: RegisterServerOptions): Promise<PeertubeXMPPServerInfos> {
|
|
|
|
const settings = await options.settingsManager.getSettings([
|
|
|
|
'federation-dont-publish-remotely',
|
|
|
|
'prosody-s2s-port',
|
|
|
|
'prosody-room-allow-s2s',
|
|
|
|
'disable-websocket',
|
|
|
|
'chat-no-anonymous'
|
|
|
|
])
|
|
|
|
return _serverBuildInfos(options, {
|
|
|
|
'federation-dont-publish-remotely': settings['federation-dont-publish-remotely'],
|
|
|
|
'prosody-s2s-port': settings['prosody-s2s-port'],
|
|
|
|
'prosody-room-allow-s2s': settings['prosody-room-allow-s2s'],
|
|
|
|
'disable-websocket': settings['disable-websocket'],
|
|
|
|
'chat-no-anonymous': settings['chat-no-anonymous']
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
async function _serverBuildInfos (
|
|
|
|
options: RegisterServerOptions,
|
|
|
|
settings: {
|
|
|
|
'federation-dont-publish-remotely': SettingValue
|
|
|
|
'prosody-s2s-port': SettingValue
|
|
|
|
'prosody-room-allow-s2s': SettingValue
|
|
|
|
'disable-websocket': SettingValue
|
|
|
|
'chat-no-anonymous': SettingValue
|
|
|
|
}
|
|
|
|
): Promise<PeertubeXMPPServerInfos> {
|
|
|
|
const prosodyDomain = await getProsodyDomain(options)
|
|
|
|
const mucDomain = 'room.' + prosodyDomain
|
|
|
|
const anonDomain = 'anon.' + prosodyDomain
|
2024-04-18 13:42:06 +00:00
|
|
|
const externalDomain = 'external.' + prosodyDomain
|
2023-05-24 13:09:56 +00:00
|
|
|
|
2024-05-05 10:47:33 +00:00
|
|
|
let directs2s: PeertubeXMPPServerInfos['directs2s'] | undefined
|
2023-05-24 13:09:56 +00:00
|
|
|
if (settings['prosody-room-allow-s2s'] && settings['prosody-s2s-port']) {
|
|
|
|
directs2s = {
|
|
|
|
port: (settings['prosody-s2s-port'] as string) ?? ''
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-05 10:47:33 +00:00
|
|
|
let websockets2s: PeertubeXMPPServerInfos['websockets2s'] | undefined
|
2023-05-19 10:52:52 +00:00
|
|
|
if (!settings['federation-dont-publish-remotely']) {
|
|
|
|
const wsS2SUri = getWSS2SUri(options)
|
2023-05-24 13:09:56 +00:00
|
|
|
if (wsS2SUri) { // can be undefined for old Peertube version that dont allow WS for plugins
|
|
|
|
websockets2s = {
|
2023-05-19 10:52:52 +00:00
|
|
|
url: canonicalizePluginUri(options, wsS2SUri, {
|
|
|
|
removePluginVersion: true,
|
|
|
|
protocol: 'ws'
|
|
|
|
})
|
2023-05-24 13:09:56 +00:00
|
|
|
}
|
2023-05-19 10:52:52 +00:00
|
|
|
}
|
|
|
|
}
|
2023-05-24 13:09:56 +00:00
|
|
|
|
|
|
|
let anonymous: PeertubeXMPPServerInfos['anonymous'] | undefined
|
2023-04-21 15:24:16 +00:00
|
|
|
if (!settings['chat-no-anonymous']) {
|
2023-05-24 13:09:56 +00:00
|
|
|
anonymous = {
|
|
|
|
bosh: canonicalizePluginUri(options, getBoshUri(options), { removePluginVersion: true }),
|
|
|
|
virtualhost: anonDomain
|
|
|
|
}
|
|
|
|
|
2023-04-21 15:24:16 +00:00
|
|
|
if (!settings['disable-websocket']) {
|
|
|
|
const wsUri = getWSUri(options)
|
|
|
|
if (wsUri) {
|
2023-05-24 13:09:56 +00:00
|
|
|
anonymous.websocket = canonicalizePluginUri(options, wsUri, {
|
|
|
|
removePluginVersion: true,
|
|
|
|
protocol: 'ws'
|
2023-04-21 15:24:16 +00:00
|
|
|
})
|
|
|
|
}
|
2023-04-20 14:07:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-24 13:09:56 +00:00
|
|
|
return {
|
|
|
|
host: prosodyDomain,
|
|
|
|
muc: mucDomain,
|
2024-04-18 13:42:06 +00:00
|
|
|
external: externalDomain, // we will always add it, even if disabled. Can't cause trouble.
|
2023-05-24 13:09:56 +00:00
|
|
|
directs2s,
|
|
|
|
websockets2s,
|
|
|
|
anonymous
|
2023-04-20 14:07:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export {
|
2023-05-24 13:09:56 +00:00
|
|
|
videoBuildJSONLD,
|
2024-05-05 10:47:33 +00:00
|
|
|
videoContextBuildJSONLD,
|
2023-05-24 13:09:56 +00:00
|
|
|
serverBuildInfos
|
2023-04-20 14:07:00 +00:00
|
|
|
}
|