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-04-21 10:02:52 +00:00
|
|
|
import type { RegisterServerOptions, Video, MVideoThumbnail } from '@peertube/peertube-types'
|
|
|
|
import { getVideoLiveChatInfos } from './federation/storage'
|
2023-05-24 13:09:56 +00:00
|
|
|
import { anonymousConnectionInfos, compatibleRemoteAuthenticatedConnectionEnabled } from './federation/connection-infos'
|
2023-04-21 10:02:52 +00:00
|
|
|
|
2021-06-08 16:08:58 +00:00
|
|
|
async function initCustomFields (options: RegisterServerOptions): Promise<void> {
|
|
|
|
const registerHook = options.registerHook
|
|
|
|
const storageManager = options.storageManager
|
|
|
|
const logger = options.peertubeHelpers.logger
|
|
|
|
|
2024-06-11 08:41:12 +00:00
|
|
|
registerHook({
|
|
|
|
target: 'action:api.live-video.created',
|
2024-06-14 15:56:22 +00:00
|
|
|
handler: async ({ video, req }: { video: Video | undefined, req: any }) => {
|
2024-06-11 08:41:12 +00:00
|
|
|
if (!video?.id) { return }
|
2024-06-14 15:56:22 +00:00
|
|
|
// When creating a new live, if 'chat-per-live-video' is true,
|
|
|
|
// we must read req.body.pluginData['livechat-active'] (as for action:api.video.updated).
|
2024-06-11 08:41:12 +00:00
|
|
|
// This is done for the Peertube live Android app, which does not update the video after creation.
|
|
|
|
// See: https://github.com/JohnXLivingston/peertube-plugin-livechat/issues/400
|
2024-06-14 15:56:22 +00:00
|
|
|
|
|
|
|
// Note: following code is a little bit verbose, to be sure it can't fail, even with old Peertube versions.
|
|
|
|
if (!req || (typeof req !== 'object') || !('body' in req)) { return }
|
|
|
|
if (!req.body || (typeof req.body !== 'object') || !('pluginData' in req.body)) { return }
|
|
|
|
const pluginData = req.body?.pluginData
|
|
|
|
if (!pluginData || (typeof pluginData !== 'object') || !('livechat-active' in pluginData)) { return }
|
|
|
|
if (pluginData['livechat-active'] !== true) { return }
|
|
|
|
|
2024-06-11 08:41:12 +00:00
|
|
|
const setting = await options.settingsManager.getSetting('chat-per-live-video')
|
|
|
|
if (setting !== true) { return }
|
2024-06-14 15:56:22 +00:00
|
|
|
|
2024-06-11 08:41:12 +00:00
|
|
|
logger.info(
|
2024-06-14 15:56:22 +00:00
|
|
|
'New live created, livechat-active parameter given, ' +
|
|
|
|
`enabling chat by default by setting livechat-active=true for video ${video.id.toString()}.`
|
2024-06-11 08:41:12 +00:00
|
|
|
)
|
|
|
|
await storageManager.storeData(`livechat-active-${video.id.toString()}`, true)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2021-06-08 16:08:58 +00:00
|
|
|
registerHook({
|
|
|
|
target: 'action:api.video.updated',
|
|
|
|
handler: async (params: any) => {
|
|
|
|
logger.debug('Saving a video, checking for custom fields')
|
|
|
|
|
|
|
|
const body: any = params.body
|
2022-12-07 15:55:44 +00:00
|
|
|
const video: Video | undefined = params.video
|
2024-09-07 12:49:27 +00:00
|
|
|
if (!video?.id) {
|
2021-06-08 16:08:58 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if (!body.pluginData) return
|
|
|
|
const value = body.pluginData['livechat-active']
|
|
|
|
// NB: on Peertube 3.2.1, value is "true", "false", or "null", as strings.
|
|
|
|
if (value === true || value === 'true') {
|
2022-12-07 15:55:44 +00:00
|
|
|
logger.info(`Saving livechat-active=true for video ${video.id.toString()}`)
|
|
|
|
await storageManager.storeData(`livechat-active-${video.id.toString()}`, true)
|
2021-06-08 16:08:58 +00:00
|
|
|
} else if (value === false || value === 'false' || value === 'null') {
|
2022-12-07 15:55:44 +00:00
|
|
|
logger.info(`Saving livechat-active=false for video ${video.id.toString()}`)
|
|
|
|
await storageManager.storeData(`livechat-active-${video.id.toString()}`, false)
|
2021-06-08 16:08:58 +00:00
|
|
|
} else {
|
|
|
|
logger.error('Unknown value ' + JSON.stringify(value) + ' for livechat-active field.')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
registerHook({
|
|
|
|
target: 'filter:api.video.get.result',
|
2022-12-07 15:55:44 +00:00
|
|
|
handler: async (video: Video): Promise<Video> => {
|
2023-04-21 10:02:52 +00:00
|
|
|
logger.debug('Getting a video, searching for custom fields and data')
|
2021-06-08 16:08:58 +00:00
|
|
|
await fillVideoCustomFields(options, video)
|
2023-04-21 10:02:52 +00:00
|
|
|
if (!video.isLocal) {
|
|
|
|
await fillVideoRemoteLiveChat(options, video)
|
|
|
|
}
|
2021-06-08 16:08:58 +00:00
|
|
|
return video
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-12-07 15:55:44 +00:00
|
|
|
// FIXME: @peertube/peertype-types@4.2.2: wrongly thinks that loadByIdOrUUID return a MVideoThumbnail.
|
|
|
|
// So we create this custom interface for fillVideoCustomFields to accept Video and MVideoThumbnail types.
|
|
|
|
interface LiveChatCustomFieldsVideo {
|
|
|
|
id?: number | string
|
|
|
|
isLive: boolean
|
2022-01-11 00:29:33 +00:00
|
|
|
pluginData?: {
|
|
|
|
'livechat-active'?: boolean
|
2023-04-21 10:02:52 +00:00
|
|
|
'livechat-remote'?: boolean
|
2022-01-11 00:29:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-07 15:55:44 +00:00
|
|
|
async function fillVideoCustomFields (options: RegisterServerOptions, video: LiveChatCustomFieldsVideo): Promise<void> {
|
2021-06-08 16:08:58 +00:00
|
|
|
if (!video) return video
|
|
|
|
if (!video.pluginData) video.pluginData = {}
|
|
|
|
if (!video.id) return
|
|
|
|
const storageManager = options.storageManager
|
|
|
|
const logger = options.peertubeHelpers.logger
|
|
|
|
|
|
|
|
if (video.isLive) {
|
2022-01-11 00:29:33 +00:00
|
|
|
const result: any = await storageManager.getData(`livechat-active-${video.id as string}`)
|
|
|
|
logger.debug(`Video ${video.id as string} has livechat-active=` + JSON.stringify(result))
|
2021-06-08 16:08:58 +00:00
|
|
|
// NB: I got weird stuff here. Maybe Peertube 3.2.1 bug?
|
|
|
|
if (result === true || result === 'true') {
|
|
|
|
video.pluginData['livechat-active'] = true
|
|
|
|
} else if (result === false || result === 'false' || result === 'null') {
|
|
|
|
video.pluginData['livechat-active'] = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-21 10:02:52 +00:00
|
|
|
async function fillVideoRemoteLiveChat (
|
|
|
|
options: RegisterServerOptions,
|
|
|
|
video: Video | MVideoThumbnail
|
|
|
|
): Promise<void> {
|
|
|
|
if (('remote' in video) && !video.remote) { return }
|
|
|
|
if (('isLocal' in video) && video.isLocal) { return }
|
|
|
|
const infos = await getVideoLiveChatInfos(options, video)
|
|
|
|
if (!infos) { return }
|
|
|
|
|
2024-09-07 12:49:27 +00:00
|
|
|
let ok = false
|
2023-04-21 15:24:16 +00:00
|
|
|
// We must check if there is a compatible connection protocol...
|
2023-05-24 13:09:56 +00:00
|
|
|
if (anonymousConnectionInfos(infos)) {
|
|
|
|
// Connection ok using a remote anonymous account. That's enought.
|
|
|
|
ok = true
|
|
|
|
} else {
|
|
|
|
const settings = await options.settingsManager.getSettings([
|
|
|
|
'federation-no-remote-chat',
|
2023-05-24 14:55:03 +00:00
|
|
|
'prosody-room-allow-s2s',
|
|
|
|
'disable-websocket'
|
2023-05-24 13:09:56 +00:00
|
|
|
])
|
2023-05-24 14:55:03 +00:00
|
|
|
const canWebsocketS2S = !settings['federation-no-remote-chat'] && !settings['disable-websocket']
|
2023-05-24 13:09:56 +00:00
|
|
|
const canDirectS2S = !settings['federation-no-remote-chat'] && !!settings['prosody-room-allow-s2s']
|
|
|
|
if (compatibleRemoteAuthenticatedConnectionEnabled(infos, canWebsocketS2S, canDirectS2S)) {
|
|
|
|
// Even better, we can do a proper S2S connection!
|
|
|
|
ok = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ok) {
|
|
|
|
return
|
|
|
|
}
|
2023-04-21 15:24:16 +00:00
|
|
|
|
2023-04-21 10:02:52 +00:00
|
|
|
const v: LiveChatCustomFieldsVideo = video
|
|
|
|
if (!v.pluginData) v.pluginData = {}
|
|
|
|
v.pluginData['livechat-remote'] = true
|
|
|
|
}
|
|
|
|
|
2021-06-08 16:08:58 +00:00
|
|
|
export {
|
|
|
|
initCustomFields,
|
2023-04-21 10:02:52 +00:00
|
|
|
fillVideoCustomFields,
|
|
|
|
fillVideoRemoteLiveChat
|
2021-06-08 16:08:58 +00:00
|
|
|
}
|