From 385a0074c11020998d5ea4d89fdedaa6411c8009 Mon Sep 17 00:00:00 2001 From: John Livingston Date: Tue, 8 Jun 2021 18:08:58 +0200 Subject: [PATCH] Chat can be enabled in video properties. --- CHANGELOG.md | 3 ++ client/@types/peertube.d.ts | 10 ++++- client/admin-plugin-client-plugin.ts | 2 + client/common-client-plugin.ts | 30 ++++++++++++- server/@types/peertube.d.ts | 2 + server/lib/custom-fields.ts | 63 ++++++++++++++++++++++++++++ server/lib/diagnostic/video.ts | 5 +++ server/lib/routers/api.ts | 25 ++++++----- server/lib/settings.ts | 20 ++++++++- server/main.ts | 2 + shared/lib/video.ts | 8 ++++ 11 files changed, 154 insertions(+), 16 deletions(-) create mode 100644 server/lib/custom-fields.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f245c04..8a821705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,14 @@ ### Features * New simpler settings screen. +* New field in live video form, to activate the webchat per video. There is a setting for enabling this new feature. ### Changes * Removed compatibility with Peertube previous to 3.2.0. * Restoring default values for checkboxes in settings (there was a bug with Peertube previous to 3.2.0) +* New settings +* By default, the «activate chat for all lives» is disabled (now that we can enable the webchat per video) ### Fixes diff --git a/client/@types/peertube.d.ts b/client/@types/peertube.d.ts index c701f0fe..3121a03d 100644 --- a/client/@types/peertube.d.ts +++ b/client/@types/peertube.d.ts @@ -37,7 +37,7 @@ interface RegisterClientFormFieldOptions { type: 'input' | 'input-checkbox' | 'input-password' | 'input-textarea' | 'markdown-text' | 'markdown-enhanced' descriptionHTML?: string default?: string | boolean - private: boolean + hidden?: (options: any) => boolean } interface RegisterClientSettingsScript { isSettingHidden: (options: { @@ -46,10 +46,18 @@ interface RegisterClientSettingsScript { }) => boolean } +interface RegisterClientVideoFieldOptions { + type: 'update' | 'upload' | 'import-url' | 'import-torrent' | 'go-live' +} + interface RegisterOptions { registerHook: (options: RegisterClientHookOptions) => void peertubeHelpers: RegisterClientHelpers registerSettingsScript: (options: RegisterClientSettingsScript) => void + registerVideoField: ( + commonOptions: RegisterClientFormFieldOptions, + videoFormOptions: RegisterClientVideoFieldOptions + ) => void } interface Video { diff --git a/client/admin-plugin-client-plugin.ts b/client/admin-plugin-client-plugin.ts index 44a39664..06504ee3 100644 --- a/client/admin-plugin-client-plugin.ts +++ b/client/admin-plugin-client-plugin.ts @@ -23,6 +23,8 @@ function register ({ registerSettingsScript }: RegisterOptions): void { return options.formValues['chat-type'] === 'disabled' case 'chat-only-locals-warning': return options.formValues['chat-only-locals'] === true + case 'chat-per-live-video-warning': + return !(options.formValues['chat-all-lives'] === true && options.formValues['chat-per-live-video'] === true) } return false diff --git a/client/common-client-plugin.ts b/client/common-client-plugin.ts index 522ee580..e5c40d82 100644 --- a/client/common-client-plugin.ts +++ b/client/common-client-plugin.ts @@ -1,5 +1,5 @@ -function register ({ registerHook }: RegisterOptions): void { +async function register ({ peertubeHelpers, registerHook, registerVideoField }: RegisterOptions): Promise { registerHook({ target: 'action:router.navigation-end', handler: () => { @@ -9,6 +9,34 @@ function register ({ registerHook }: RegisterOptions): void { } } }) + + const [label, description, settings] = await Promise.all([ + peertubeHelpers.translate('Use chat'), + peertubeHelpers.translate('If enabled, there will be a chat next to the video.'), + peertubeHelpers.getSettings() + ]) + const webchatFieldOptions: RegisterClientFormFieldOptions = { + name: 'livechat-active', + label: label, + descriptionHTML: description, + type: 'input-checkbox', + default: true, + hidden: ({ liveVideo }) => { + if (!liveVideo) { + return true + } + if (!settings['chat-per-live-video']) { + return true + } + if (settings['chat-all-lives']) { + // No need to add this field if live is active for all live videos + return true + } + return false + } + } + registerVideoField(webchatFieldOptions, { type: 'update' }) + registerVideoField(webchatFieldOptions, { type: 'go-live' }) } export { diff --git a/server/@types/peertube.d.ts b/server/@types/peertube.d.ts index 00fd80bd..8e1a2404 100644 --- a/server/@types/peertube.d.ts +++ b/server/@types/peertube.d.ts @@ -61,6 +61,7 @@ enum VideoState { } interface MVideoThumbnail { // FIXME: this interface is not complete. + id?: number uuid: string name: string category: number @@ -81,6 +82,7 @@ interface MVideoThumbnail { // FIXME: this interface is not complete. downloadEnabled: boolean state: VideoState channelId: number + pluginData?: any } // Keep the order diff --git a/server/lib/custom-fields.ts b/server/lib/custom-fields.ts new file mode 100644 index 00000000..117fb29b --- /dev/null +++ b/server/lib/custom-fields.ts @@ -0,0 +1,63 @@ +async function initCustomFields (options: RegisterServerOptions): Promise { + const registerHook = options.registerHook + const storageManager = options.storageManager + const logger = options.peertubeHelpers.logger + + registerHook({ + target: 'action:api.video.updated', + handler: async (params: any) => { + logger.debug('Saving a video, checking for custom fields') + + const body: any = params.body + const video: MVideoThumbnail | undefined = params.video + if (!video || !video.id) { + 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') { + logger.info(`Saving livechat-active=true for video ${video.id}`) + await storageManager.storeData(`livechat-active-${video.id}`, true) + } else if (value === false || value === 'false' || value === 'null') { + logger.info(`Saving livechat-active=false for video ${video.id}`) + await storageManager.storeData(`livechat-active-${video.id}`, false) + } else { + logger.error('Unknown value ' + JSON.stringify(value) + ' for livechat-active field.') + } + } + }) + + registerHook({ + target: 'filter:api.video.get.result', + handler: async (video: MVideoThumbnail): Promise => { + logger.debug('Getting a video, searching for custom fields') + await fillVideoCustomFields(options, video) + return video + } + }) +} + +async function fillVideoCustomFields (options: RegisterServerOptions, video: MVideoThumbnail): Promise { + 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) { + const result: any = await storageManager.getData(`livechat-active-${video.id}`) + logger.debug(`Video ${video.id} has livechat-active=` + JSON.stringify(result)) + // 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 + } + } +} + +export { + initCustomFields, + fillVideoCustomFields +} diff --git a/server/lib/diagnostic/video.ts b/server/lib/diagnostic/video.ts index b14a4194..76e43ee9 100644 --- a/server/lib/diagnostic/video.ts +++ b/server/lib/diagnostic/video.ts @@ -7,6 +7,7 @@ export async function diagVideo (test: string, { settingsManager }: RegisterServ 'chat-auto-display', 'chat-open-blank', 'chat-only-locals', + 'chat-per-live-video', 'chat-all-lives', 'chat-all-non-lives', 'chat-videos-list' @@ -26,6 +27,10 @@ export async function diagVideo (test: string, { settingsManager }: RegisterServ } let atLeastOne: boolean = false + if (videoSettings['chat-per-live-video']) { + result.messages.push('Chat can be enabled on live videos.') + atLeastOne = true + } if (videoSettings['chat-all-lives']) { result.messages.push('Chat is enabled for all lives.') atLeastOne = true diff --git a/server/lib/routers/api.ts b/server/lib/routers/api.ts index 543583f3..cf76cebc 100644 --- a/server/lib/routers/api.ts +++ b/server/lib/routers/api.ts @@ -7,6 +7,7 @@ import { getUserNickname } from '../helpers' import { Affiliations, getVideoAffiliations } from '../prosody/config/affiliations' import { getProsodyDomain } from '../prosody/config/domain' import type { ChatType } from '../../../shared/lib/types' +import { fillVideoCustomFields } from '../custom-fields' // See here for description: https://modules.prosody.im/mod_muc_http_defaults.html interface RoomDefaults { @@ -45,10 +46,15 @@ async function initApiRouter (options: RegisterServerOptions): Promise { res.sendStatus(403) return } + + // Adding the custom fields: + await fillVideoCustomFields(options, video) + // check settings (chat enabled for this video?) const settings = await options.settingsManager.getSettings([ 'chat-type', 'chat-only-locals', + 'chat-per-live-video', 'chat-all-lives', 'chat-all-non-lives', 'chat-videos-list' @@ -59,9 +65,10 @@ async function initApiRouter (options: RegisterServerOptions): Promise { return } if (!videoHasWebchat({ - 'chat-only-locals': settings['chat-only-locals'] as boolean, - 'chat-all-lives': settings['chat-all-lives'] as boolean, - 'chat-all-non-lives': settings['chat-all-non-lives'] as boolean, + 'chat-only-locals': !!settings['chat-only-locals'], + '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)) { logger.warn(`Video ${jid} has not chat activated`) @@ -122,11 +129,7 @@ async function initApiRouter (options: RegisterServerOptions): Promise { router.get('/user/check_password', asyncMiddleware( async (req: Request, res: Response, _next: NextFunction) => { const settings = await options.settingsManager.getSettings([ - 'chat-type', - 'chat-only-locals', - 'chat-all-lives', - 'chat-all-non-lives', - 'chat-videos-list' + 'chat-type' ]) if (settings['chat-type'] !== ('builtin-prosody' as ChatType)) { logger.warn('Prosody chat is not active') @@ -153,11 +156,7 @@ async function initApiRouter (options: RegisterServerOptions): Promise { router.get('/user/user_exists', asyncMiddleware( async (req: Request, res: Response, _next: NextFunction) => { const settings = await options.settingsManager.getSettings([ - 'chat-type', - 'chat-only-locals', - 'chat-all-lives', - 'chat-all-non-lives', - 'chat-videos-list' + 'chat-type' ]) if (settings['chat-type'] !== ('builtin-prosody' as ChatType)) { logger.warn('Prosody chat is not active') diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 643d0739..dd8815e8 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -195,13 +195,31 @@ function initSettings (options: RegisterServerOptions): void { The plugin is not compatible with video federation yet. The webchat will only be accessible for people watching videos on your server. +` + }) + registerSetting({ + name: 'chat-per-live-video', + label: 'Users can activate the chat for their lives', + type: 'input-checkbox', + default: true, + descriptionHTML: 'If checked, all live videos will have a checkbox in there properties for enabling the webchat.', + private: false + }) + registerSetting({ + name: 'chat-per-live-video-warning', + type: 'html', + private: true, + descriptionHTML: ` + + You have enabled the setting «Users can activate the chat for their lives». + It is redundant with the «Activate chat for all lives» setting. ` }) registerSetting({ name: 'chat-all-lives', label: 'Activate chat for all lives', type: 'input-checkbox', - default: true, + default: false, descriptionHTML: 'If checked, the chat will be enabled for all lives.', private: false }) diff --git a/server/main.ts b/server/main.ts index 94154c46..225685be 100644 --- a/server/main.ts +++ b/server/main.ts @@ -1,5 +1,6 @@ import { migrateSettings } from './lib/migration/settings' import { initSettings } from './lib/settings' +import { initCustomFields } from './lib/custom-fields' import { initRouters } from './lib/routers/index' import { ensureProsodyRunning, ensureProsodyNotRunning } from './lib/prosody/ctl' import decache from 'decache' @@ -19,6 +20,7 @@ async function register (options: RegisterServerOptions): Promise { await migrateSettings(options) await initSettings(options) + await initCustomFields(options) await initRouters(options) await ensureProsodyRunning(options) diff --git a/shared/lib/video.ts b/shared/lib/video.ts index 3a29fa99..db3d81f8 100644 --- a/shared/lib/video.ts +++ b/shared/lib/video.ts @@ -2,6 +2,7 @@ import { parseConfigUUIDs } from './config' interface SharedSettings { 'chat-only-locals': boolean + 'chat-per-live-video': boolean 'chat-all-lives': boolean 'chat-all-non-lives': boolean 'chat-videos-list': string @@ -10,6 +11,9 @@ interface SharedSettings { interface SharedVideoBase { uuid: string isLive: boolean + pluginData?: { + 'livechat-active'?: boolean + } } interface SharedVideoFrontend extends SharedVideoBase { @@ -31,6 +35,10 @@ function videoHasWebchat (settings: SharedSettings, video: SharedVideo): boolean } } + if (settings['chat-per-live-video'] && video.isLive && video.pluginData && video.pluginData['livechat-active']) { + return true + } + if (settings['chat-all-lives']) { if (video.isLive) return true }