]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/lib/plugins/register-helpers.ts
Merge remote-tracking branch 'weblate/develop' into develop
[github/Chocobozzz/PeerTube.git] / server / lib / plugins / register-helpers.ts
1 import * as express from 'express'
2 import { logger } from '@server/helpers/logger'
3 import {
4 VIDEO_CATEGORIES,
5 VIDEO_LANGUAGES,
6 VIDEO_LICENCES,
7 VIDEO_PLAYLIST_PRIVACIES,
8 VIDEO_PRIVACIES
9 } from '@server/initializers/constants'
10 import { onExternalUserAuthenticated } from '@server/lib/auth/external-auth'
11 import { PluginModel } from '@server/models/server/plugin'
12 import {
13 RegisterServerAuthExternalOptions,
14 RegisterServerAuthExternalResult,
15 RegisterServerAuthPassOptions,
16 RegisterServerExternalAuthenticatedResult,
17 RegisterServerOptions
18 } from '@server/types/plugins'
19 import {
20 EncoderOptionsBuilder,
21 PluginPlaylistPrivacyManager,
22 PluginSettingsManager,
23 PluginStorageManager,
24 PluginVideoCategoryManager,
25 PluginVideoLanguageManager,
26 PluginVideoLicenceManager,
27 PluginVideoPrivacyManager,
28 RegisterServerHookOptions,
29 RegisterServerSettingOptions
30 } from '@shared/models'
31 import { serverHookObject } from '@shared/models/plugins/server-hook.model'
32 import { VideoTranscodingProfilesManager } from '../video-transcoding-profiles'
33 import { buildPluginHelpers } from './plugin-helpers-builder'
34
35 type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy'
36 type VideoConstant = { [key in number | string]: string }
37
38 type UpdatedVideoConstant = {
39 [name in AlterableVideoConstant]: {
40 added: { key: number | string, label: string }[]
41 deleted: { key: number | string, label: string }[]
42 }
43 }
44
45 export class RegisterHelpers {
46 private readonly updatedVideoConstants: UpdatedVideoConstant = {
47 playlistPrivacy: { added: [], deleted: [] },
48 privacy: { added: [], deleted: [] },
49 language: { added: [], deleted: [] },
50 licence: { added: [], deleted: [] },
51 category: { added: [], deleted: [] }
52 }
53
54 private readonly transcodingProfiles: {
55 [ npmName: string ]: {
56 type: 'vod' | 'live'
57 encoder: string
58 profile: string
59 }[]
60 } = {}
61
62 private readonly transcodingEncoders: {
63 [ npmName: string ]: {
64 type: 'vod' | 'live'
65 streamType: 'audio' | 'video'
66 encoder: string
67 priority: number
68 }[]
69 } = {}
70
71 private readonly settings: RegisterServerSettingOptions[] = []
72
73 private idAndPassAuths: RegisterServerAuthPassOptions[] = []
74 private externalAuths: RegisterServerAuthExternalOptions[] = []
75
76 private readonly onSettingsChangeCallbacks: ((settings: any) => Promise<any>)[] = []
77
78 private readonly router: express.Router
79
80 constructor (
81 private readonly npmName: string,
82 private readonly plugin: PluginModel,
83 private readonly onHookAdded: (options: RegisterServerHookOptions) => void
84 ) {
85 this.router = express.Router()
86 }
87
88 buildRegisterHelpers (): RegisterServerOptions {
89 const registerHook = this.buildRegisterHook()
90 const registerSetting = this.buildRegisterSetting()
91
92 const getRouter = this.buildGetRouter()
93
94 const settingsManager = this.buildSettingsManager()
95 const storageManager = this.buildStorageManager()
96
97 const videoLanguageManager = this.buildVideoLanguageManager()
98
99 const videoLicenceManager = this.buildVideoLicenceManager()
100 const videoCategoryManager = this.buildVideoCategoryManager()
101
102 const videoPrivacyManager = this.buildVideoPrivacyManager()
103 const playlistPrivacyManager = this.buildPlaylistPrivacyManager()
104
105 const transcodingManager = this.buildTranscodingManager()
106
107 const registerIdAndPassAuth = this.buildRegisterIdAndPassAuth()
108 const registerExternalAuth = this.buildRegisterExternalAuth()
109 const unregisterIdAndPassAuth = this.buildUnregisterIdAndPassAuth()
110 const unregisterExternalAuth = this.buildUnregisterExternalAuth()
111
112 const peertubeHelpers = buildPluginHelpers(this.plugin, this.npmName)
113
114 return {
115 registerHook,
116 registerSetting,
117
118 getRouter,
119
120 settingsManager,
121 storageManager,
122
123 videoLanguageManager,
124 videoCategoryManager,
125 videoLicenceManager,
126
127 videoPrivacyManager,
128 playlistPrivacyManager,
129
130 transcodingManager,
131
132 registerIdAndPassAuth,
133 registerExternalAuth,
134 unregisterIdAndPassAuth,
135 unregisterExternalAuth,
136
137 peertubeHelpers
138 }
139 }
140
141 reinitVideoConstants (npmName: string) {
142 const hash = {
143 language: VIDEO_LANGUAGES,
144 licence: VIDEO_LICENCES,
145 category: VIDEO_CATEGORIES,
146 privacy: VIDEO_PRIVACIES,
147 playlistPrivacy: VIDEO_PLAYLIST_PRIVACIES
148 }
149 const types: AlterableVideoConstant[] = [ 'language', 'licence', 'category', 'privacy', 'playlistPrivacy' ]
150
151 for (const type of types) {
152 const updatedConstants = this.updatedVideoConstants[type][npmName]
153 if (!updatedConstants) continue
154
155 for (const added of updatedConstants.added) {
156 delete hash[type][added.key]
157 }
158
159 for (const deleted of updatedConstants.deleted) {
160 hash[type][deleted.key] = deleted.label
161 }
162
163 delete this.updatedVideoConstants[type][npmName]
164 }
165 }
166
167 reinitTranscodingProfilesAndEncoders (npmName: string) {
168 const profiles = this.transcodingProfiles[npmName]
169 if (Array.isArray(profiles)) {
170 for (const profile of profiles) {
171 VideoTranscodingProfilesManager.Instance.removeProfile(profile)
172 }
173 }
174
175 const encoders = this.transcodingEncoders[npmName]
176 if (Array.isArray(encoders)) {
177 for (const o of encoders) {
178 VideoTranscodingProfilesManager.Instance.removeEncoderPriority(o.type, o.streamType, o.encoder, o.priority)
179 }
180 }
181 }
182
183 getSettings () {
184 return this.settings
185 }
186
187 getRouter () {
188 return this.router
189 }
190
191 getIdAndPassAuths () {
192 return this.idAndPassAuths
193 }
194
195 getExternalAuths () {
196 return this.externalAuths
197 }
198
199 getOnSettingsChangedCallbacks () {
200 return this.onSettingsChangeCallbacks
201 }
202
203 private buildGetRouter () {
204 return () => this.router
205 }
206
207 private buildRegisterSetting () {
208 return (options: RegisterServerSettingOptions) => {
209 this.settings.push(options)
210 }
211 }
212
213 private buildRegisterHook () {
214 return (options: RegisterServerHookOptions) => {
215 if (serverHookObject[options.target] !== true) {
216 logger.warn('Unknown hook %s of plugin %s. Skipping.', options.target, this.npmName)
217 return
218 }
219
220 return this.onHookAdded(options)
221 }
222 }
223
224 private buildRegisterIdAndPassAuth () {
225 return (options: RegisterServerAuthPassOptions) => {
226 if (!options.authName || typeof options.getWeight !== 'function' || typeof options.login !== 'function') {
227 logger.error('Cannot register auth plugin %s: authName, getWeight or login are not valid.', this.npmName, { options })
228 return
229 }
230
231 this.idAndPassAuths.push(options)
232 }
233 }
234
235 private buildRegisterExternalAuth () {
236 const self = this
237
238 return (options: RegisterServerAuthExternalOptions) => {
239 if (!options.authName || typeof options.authDisplayName !== 'function' || typeof options.onAuthRequest !== 'function') {
240 logger.error('Cannot register auth plugin %s: authName, authDisplayName or onAuthRequest are not valid.', this.npmName, { options })
241 return
242 }
243
244 this.externalAuths.push(options)
245
246 return {
247 userAuthenticated (result: RegisterServerExternalAuthenticatedResult): void {
248 onExternalUserAuthenticated({
249 npmName: self.npmName,
250 authName: options.authName,
251 authResult: result
252 }).catch(err => {
253 logger.error('Cannot execute onExternalUserAuthenticated.', { npmName: self.npmName, authName: options.authName, err })
254 })
255 }
256 } as RegisterServerAuthExternalResult
257 }
258 }
259
260 private buildUnregisterExternalAuth () {
261 return (authName: string) => {
262 this.externalAuths = this.externalAuths.filter(a => a.authName !== authName)
263 }
264 }
265
266 private buildUnregisterIdAndPassAuth () {
267 return (authName: string) => {
268 this.idAndPassAuths = this.idAndPassAuths.filter(a => a.authName !== authName)
269 }
270 }
271
272 private buildSettingsManager (): PluginSettingsManager {
273 return {
274 getSetting: (name: string) => PluginModel.getSetting(this.plugin.name, this.plugin.type, name, this.settings),
275
276 getSettings: (names: string[]) => PluginModel.getSettings(this.plugin.name, this.plugin.type, names, this.settings),
277
278 setSetting: (name: string, value: string) => PluginModel.setSetting(this.plugin.name, this.plugin.type, name, value),
279
280 onSettingsChange: (cb: (settings: any) => Promise<any>) => this.onSettingsChangeCallbacks.push(cb)
281 }
282 }
283
284 private buildStorageManager (): PluginStorageManager {
285 return {
286 getData: (key: string) => PluginModel.getData(this.plugin.name, this.plugin.type, key),
287
288 storeData: (key: string, data: any) => PluginModel.storeData(this.plugin.name, this.plugin.type, key, data)
289 }
290 }
291
292 private buildVideoLanguageManager (): PluginVideoLanguageManager {
293 return {
294 addLanguage: (key: string, label: string) => {
295 return this.addConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key, label })
296 },
297
298 deleteLanguage: (key: string) => {
299 return this.deleteConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key })
300 }
301 }
302 }
303
304 private buildVideoCategoryManager (): PluginVideoCategoryManager {
305 return {
306 addCategory: (key: number, label: string) => {
307 return this.addConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label })
308 },
309
310 deleteCategory: (key: number) => {
311 return this.deleteConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key })
312 }
313 }
314 }
315
316 private buildVideoPrivacyManager (): PluginVideoPrivacyManager {
317 return {
318 deletePrivacy: (key: number) => {
319 return this.deleteConstant({ npmName: this.npmName, type: 'privacy', obj: VIDEO_PRIVACIES, key })
320 }
321 }
322 }
323
324 private buildPlaylistPrivacyManager (): PluginPlaylistPrivacyManager {
325 return {
326 deletePlaylistPrivacy: (key: number) => {
327 return this.deleteConstant({ npmName: this.npmName, type: 'playlistPrivacy', obj: VIDEO_PLAYLIST_PRIVACIES, key })
328 }
329 }
330 }
331
332 private buildVideoLicenceManager (): PluginVideoLicenceManager {
333 return {
334 addLicence: (key: number, label: string) => {
335 return this.addConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key, label })
336 },
337
338 deleteLicence: (key: number) => {
339 return this.deleteConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key })
340 }
341 }
342 }
343
344 private addConstant<T extends string | number> (parameters: {
345 npmName: string
346 type: AlterableVideoConstant
347 obj: VideoConstant
348 key: T
349 label: string
350 }) {
351 const { npmName, type, obj, key, label } = parameters
352
353 if (obj[key]) {
354 logger.warn('Cannot add %s %s by plugin %s: key already exists.', type, npmName, key)
355 return false
356 }
357
358 if (!this.updatedVideoConstants[type][npmName]) {
359 this.updatedVideoConstants[type][npmName] = {
360 added: [],
361 deleted: []
362 }
363 }
364
365 this.updatedVideoConstants[type][npmName].added.push({ key, label })
366 obj[key] = label
367
368 return true
369 }
370
371 private deleteConstant<T extends string | number> (parameters: {
372 npmName: string
373 type: AlterableVideoConstant
374 obj: VideoConstant
375 key: T
376 }) {
377 const { npmName, type, obj, key } = parameters
378
379 if (!obj[key]) {
380 logger.warn('Cannot delete %s %s by plugin %s: key does not exist.', type, npmName, key)
381 return false
382 }
383
384 if (!this.updatedVideoConstants[type][npmName]) {
385 this.updatedVideoConstants[type][npmName] = {
386 added: [],
387 deleted: []
388 }
389 }
390
391 this.updatedVideoConstants[type][npmName].deleted.push({ key, label: obj[key] })
392 delete obj[key]
393
394 return true
395 }
396
397 private buildTranscodingManager () {
398 const self = this
399
400 function addProfile (type: 'live' | 'vod', encoder: string, profile: string, builder: EncoderOptionsBuilder) {
401 if (profile === 'default') {
402 logger.error('A plugin cannot add a default live transcoding profile')
403 return false
404 }
405
406 VideoTranscodingProfilesManager.Instance.addProfile({
407 type,
408 encoder,
409 profile,
410 builder
411 })
412
413 if (!self.transcodingProfiles[self.npmName]) self.transcodingProfiles[self.npmName] = []
414 self.transcodingProfiles[self.npmName].push({ type, encoder, profile })
415
416 return true
417 }
418
419 function addEncoderPriority (type: 'live' | 'vod', streamType: 'audio' | 'video', encoder: string, priority: number) {
420 VideoTranscodingProfilesManager.Instance.addEncoderPriority(type, streamType, encoder, priority)
421
422 if (!self.transcodingEncoders[self.npmName]) self.transcodingEncoders[self.npmName] = []
423 self.transcodingEncoders[self.npmName].push({ type, streamType, encoder, priority })
424 }
425
426 return {
427 addLiveProfile (encoder: string, profile: string, builder: EncoderOptionsBuilder) {
428 return addProfile('live', encoder, profile, builder)
429 },
430
431 addVODProfile (encoder: string, profile: string, builder: EncoderOptionsBuilder) {
432 return addProfile('vod', encoder, profile, builder)
433 },
434
435 addLiveEncoderPriority (streamType: 'audio' | 'video', encoder: string, priority: number) {
436 return addEncoderPriority('live', streamType, encoder, priority)
437 },
438
439 addVODEncoderPriority (streamType: 'audio' | 'video', encoder: string, priority: number) {
440 return addEncoderPriority('vod', streamType, encoder, priority)
441 },
442
443 removeAllProfilesAndEncoderPriorities () {
444 return self.reinitTranscodingProfilesAndEncoders(self.npmName)
445 }
446 }
447 }
448 }