diff options
Diffstat (limited to 'server/lib/plugins')
-rw-r--r-- | server/lib/plugins/hooks.ts | 2 | ||||
-rw-r--r-- | server/lib/plugins/plugin-helpers.ts | 133 | ||||
-rw-r--r-- | server/lib/plugins/plugin-index.ts | 8 | ||||
-rw-r--r-- | server/lib/plugins/plugin-manager.ts | 307 | ||||
-rw-r--r-- | server/lib/plugins/register-helpers-store.ts | 355 |
5 files changed, 625 insertions, 180 deletions
diff --git a/server/lib/plugins/hooks.ts b/server/lib/plugins/hooks.ts index bcc8c674e..aa92f03cc 100644 --- a/server/lib/plugins/hooks.ts +++ b/server/lib/plugins/hooks.ts | |||
@@ -25,7 +25,7 @@ const Hooks = { | |||
25 | }, | 25 | }, |
26 | 26 | ||
27 | runAction: <T, U extends ServerActionHookName>(hookName: U, params?: T) => { | 27 | runAction: <T, U extends ServerActionHookName>(hookName: U, params?: T) => { |
28 | PluginManager.Instance.runHook(hookName, params) | 28 | PluginManager.Instance.runHook(hookName, undefined, params) |
29 | .catch(err => logger.error('Fatal hook error.', { err })) | 29 | .catch(err => logger.error('Fatal hook error.', { err })) |
30 | } | 30 | } |
31 | } | 31 | } |
diff --git a/server/lib/plugins/plugin-helpers.ts b/server/lib/plugins/plugin-helpers.ts new file mode 100644 index 000000000..de82b4918 --- /dev/null +++ b/server/lib/plugins/plugin-helpers.ts | |||
@@ -0,0 +1,133 @@ | |||
1 | import { PeerTubeHelpers } from '@server/typings/plugins' | ||
2 | import { sequelizeTypescript } from '@server/initializers/database' | ||
3 | import { buildLogger } from '@server/helpers/logger' | ||
4 | import { VideoModel } from '@server/models/video/video' | ||
5 | import { WEBSERVER } from '@server/initializers/constants' | ||
6 | import { ServerModel } from '@server/models/server/server' | ||
7 | import { getServerActor } from '@server/models/application/application' | ||
8 | import { addServerInBlocklist, removeServerFromBlocklist, addAccountInBlocklist, removeAccountFromBlocklist } from '../blocklist' | ||
9 | import { ServerBlocklistModel } from '@server/models/server/server-blocklist' | ||
10 | import { AccountModel } from '@server/models/account/account' | ||
11 | import { VideoBlacklistCreate } from '@shared/models' | ||
12 | import { blacklistVideo, unblacklistVideo } from '../video-blacklist' | ||
13 | import { VideoBlacklistModel } from '@server/models/video/video-blacklist' | ||
14 | import { AccountBlocklistModel } from '@server/models/account/account-blocklist' | ||
15 | |||
16 | function buildPluginHelpers (npmName: string): PeerTubeHelpers { | ||
17 | const logger = buildPluginLogger(npmName) | ||
18 | |||
19 | const database = buildDatabaseHelpers() | ||
20 | const videos = buildVideosHelpers() | ||
21 | |||
22 | const config = buildConfigHelpers() | ||
23 | |||
24 | const server = buildServerHelpers() | ||
25 | |||
26 | const moderation = buildModerationHelpers() | ||
27 | |||
28 | return { | ||
29 | logger, | ||
30 | database, | ||
31 | videos, | ||
32 | config, | ||
33 | moderation, | ||
34 | server | ||
35 | } | ||
36 | } | ||
37 | |||
38 | export { | ||
39 | buildPluginHelpers | ||
40 | } | ||
41 | |||
42 | // --------------------------------------------------------------------------- | ||
43 | |||
44 | function buildPluginLogger (npmName: string) { | ||
45 | return buildLogger(npmName) | ||
46 | } | ||
47 | |||
48 | function buildDatabaseHelpers () { | ||
49 | return { | ||
50 | query: sequelizeTypescript.query.bind(sequelizeTypescript) | ||
51 | } | ||
52 | } | ||
53 | |||
54 | function buildServerHelpers () { | ||
55 | return { | ||
56 | getServerActor: () => getServerActor() | ||
57 | } | ||
58 | } | ||
59 | |||
60 | function buildVideosHelpers () { | ||
61 | return { | ||
62 | loadByUrl: (url: string) => { | ||
63 | return VideoModel.loadByUrl(url) | ||
64 | }, | ||
65 | |||
66 | removeVideo: (id: number) => { | ||
67 | return sequelizeTypescript.transaction(async t => { | ||
68 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(id, t) | ||
69 | |||
70 | await video.destroy({ transaction: t }) | ||
71 | }) | ||
72 | } | ||
73 | } | ||
74 | } | ||
75 | |||
76 | function buildModerationHelpers () { | ||
77 | return { | ||
78 | blockServer: async (options: { byAccountId: number, hostToBlock: string }) => { | ||
79 | const serverToBlock = await ServerModel.loadOrCreateByHost(options.hostToBlock) | ||
80 | |||
81 | await addServerInBlocklist(options.byAccountId, serverToBlock.id) | ||
82 | }, | ||
83 | |||
84 | unblockServer: async (options: { byAccountId: number, hostToUnblock: string }) => { | ||
85 | const serverBlock = await ServerBlocklistModel.loadByAccountAndHost(options.byAccountId, options.hostToUnblock) | ||
86 | if (!serverBlock) return | ||
87 | |||
88 | await removeServerFromBlocklist(serverBlock) | ||
89 | }, | ||
90 | |||
91 | blockAccount: async (options: { byAccountId: number, handleToBlock: string }) => { | ||
92 | const accountToBlock = await AccountModel.loadByNameWithHost(options.handleToBlock) | ||
93 | if (!accountToBlock) return | ||
94 | |||
95 | await addAccountInBlocklist(options.byAccountId, accountToBlock.id) | ||
96 | }, | ||
97 | |||
98 | unblockAccount: async (options: { byAccountId: number, handleToUnblock: string }) => { | ||
99 | const targetAccount = await AccountModel.loadByNameWithHost(options.handleToUnblock) | ||
100 | if (!targetAccount) return | ||
101 | |||
102 | const accountBlock = await AccountBlocklistModel.loadByAccountAndTarget(options.byAccountId, targetAccount.id) | ||
103 | if (!accountBlock) return | ||
104 | |||
105 | await removeAccountFromBlocklist(accountBlock) | ||
106 | }, | ||
107 | |||
108 | blacklistVideo: async (options: { videoIdOrUUID: number | string, createOptions: VideoBlacklistCreate }) => { | ||
109 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(options.videoIdOrUUID) | ||
110 | if (!video) return | ||
111 | |||
112 | await blacklistVideo(video, options.createOptions) | ||
113 | }, | ||
114 | |||
115 | unblacklistVideo: async (options: { videoIdOrUUID: number | string }) => { | ||
116 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(options.videoIdOrUUID) | ||
117 | if (!video) return | ||
118 | |||
119 | const videoBlacklist = await VideoBlacklistModel.loadByVideoId(video.id) | ||
120 | if (!videoBlacklist) return | ||
121 | |||
122 | await unblacklistVideo(videoBlacklist, video) | ||
123 | } | ||
124 | } | ||
125 | } | ||
126 | |||
127 | function buildConfigHelpers () { | ||
128 | return { | ||
129 | getWebserverUrl () { | ||
130 | return WEBSERVER.URL | ||
131 | } | ||
132 | } | ||
133 | } | ||
diff --git a/server/lib/plugins/plugin-index.ts b/server/lib/plugins/plugin-index.ts index 25b4f3c61..170f0c7e2 100644 --- a/server/lib/plugins/plugin-index.ts +++ b/server/lib/plugins/plugin-index.ts | |||
@@ -27,11 +27,11 @@ async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList) | |||
27 | const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins' | 27 | const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins' |
28 | 28 | ||
29 | try { | 29 | try { |
30 | const { body } = await doRequest({ uri, qs, json: true }) | 30 | const { body } = await doRequest<any>({ uri, qs, json: true }) |
31 | 31 | ||
32 | logger.debug('Got result from PeerTube index.', { body }) | 32 | logger.debug('Got result from PeerTube index.', { body }) |
33 | 33 | ||
34 | await addInstanceInformation(body) | 34 | addInstanceInformation(body) |
35 | 35 | ||
36 | return body as ResultList<PeerTubePluginIndex> | 36 | return body as ResultList<PeerTubePluginIndex> |
37 | } catch (err) { | 37 | } catch (err) { |
@@ -40,7 +40,7 @@ async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList) | |||
40 | } | 40 | } |
41 | } | 41 | } |
42 | 42 | ||
43 | async function addInstanceInformation (result: ResultList<PeerTubePluginIndex>) { | 43 | function addInstanceInformation (result: ResultList<PeerTubePluginIndex>) { |
44 | for (const d of result.data) { | 44 | for (const d of result.data) { |
45 | d.installed = PluginManager.Instance.isRegistered(d.npmName) | 45 | d.installed = PluginManager.Instance.isRegistered(d.npmName) |
46 | d.name = PluginModel.normalizePluginName(d.npmName) | 46 | d.name = PluginModel.normalizePluginName(d.npmName) |
@@ -57,7 +57,7 @@ async function getLatestPluginsVersion (npmNames: string[]): Promise<PeertubePlu | |||
57 | 57 | ||
58 | const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins/latest-version' | 58 | const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins/latest-version' |
59 | 59 | ||
60 | const { body } = await doRequest({ uri, body: bodyRequest, json: true, method: 'POST' }) | 60 | const { body } = await doRequest<any>({ uri, body: bodyRequest, json: true, method: 'POST' }) |
61 | 61 | ||
62 | return body | 62 | return body |
63 | } | 63 | } |
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index 7ebdabd34..950acf7ad 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts | |||
@@ -9,23 +9,20 @@ import { | |||
9 | PluginTranslationPaths as PackagePluginTranslations | 9 | PluginTranslationPaths as PackagePluginTranslations |
10 | } from '../../../shared/models/plugins/plugin-package-json.model' | 10 | } from '../../../shared/models/plugins/plugin-package-json.model' |
11 | import { createReadStream, createWriteStream } from 'fs' | 11 | import { createReadStream, createWriteStream } from 'fs' |
12 | import { PLUGIN_GLOBAL_CSS_PATH, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../../initializers/constants' | 12 | import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants' |
13 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | 13 | import { PluginType } from '../../../shared/models/plugins/plugin.type' |
14 | import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' | 14 | import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' |
15 | import { outputFile, readJSON } from 'fs-extra' | 15 | import { outputFile, readJSON } from 'fs-extra' |
16 | import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model' | 16 | import { ServerHook, ServerHookName } from '../../../shared/models/plugins/server-hook.model' |
17 | import { PluginStorageManager } from '../../../shared/models/plugins/plugin-storage-manager.model' | ||
18 | import { ServerHook, ServerHookName, serverHookObject } from '../../../shared/models/plugins/server-hook.model' | ||
19 | import { getHookType, internalRunHook } from '../../../shared/core-utils/plugins/hooks' | 17 | import { getHookType, internalRunHook } from '../../../shared/core-utils/plugins/hooks' |
20 | import { RegisterServerOptions } from '../../typings/plugins/register-server-option.model' | 18 | import { RegisterServerOptions } from '../../typings/plugins/register-server-option.model' |
21 | import { PluginLibrary } from '../../typings/plugins' | 19 | import { PluginLibrary } from '../../typings/plugins' |
22 | import { ClientHtml } from '../client-html' | 20 | import { ClientHtml } from '../client-html' |
23 | import { RegisterServerHookOptions } from '../../../shared/models/plugins/register-server-hook.model' | ||
24 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' | ||
25 | import { PluginVideoLanguageManager } from '../../../shared/models/plugins/plugin-video-language-manager.model' | ||
26 | import { PluginVideoCategoryManager } from '../../../shared/models/plugins/plugin-video-category-manager.model' | ||
27 | import { PluginVideoLicenceManager } from '../../../shared/models/plugins/plugin-video-licence-manager.model' | ||
28 | import { PluginTranslation } from '../../../shared/models/plugins/plugin-translation.model' | 21 | import { PluginTranslation } from '../../../shared/models/plugins/plugin-translation.model' |
22 | import { RegisterHelpersStore } from './register-helpers-store' | ||
23 | import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model' | ||
24 | import { MOAuthTokenUser, MUser } from '@server/typings/models' | ||
25 | import { RegisterServerAuthPassOptions, RegisterServerAuthExternalOptions } from '@shared/models/plugins/register-server-auth.model' | ||
29 | 26 | ||
30 | export interface RegisteredPlugin { | 27 | export interface RegisteredPlugin { |
31 | npmName: string | 28 | npmName: string |
@@ -44,6 +41,7 @@ export interface RegisteredPlugin { | |||
44 | css: string[] | 41 | css: string[] |
45 | 42 | ||
46 | // Only if this is a plugin | 43 | // Only if this is a plugin |
44 | registerHelpersStore?: RegisterHelpersStore | ||
47 | unregister?: Function | 45 | unregister?: Function |
48 | } | 46 | } |
49 | 47 | ||
@@ -54,35 +52,18 @@ export interface HookInformationValue { | |||
54 | priority: number | 52 | priority: number |
55 | } | 53 | } |
56 | 54 | ||
57 | type AlterableVideoConstant = 'language' | 'licence' | 'category' | ||
58 | type VideoConstant = { [ key in number | string ]: string } | ||
59 | type UpdatedVideoConstant = { | ||
60 | [ name in AlterableVideoConstant ]: { | ||
61 | [ npmName: string ]: { | ||
62 | added: { key: number | string, label: string }[], | ||
63 | deleted: { key: number | string, label: string }[] | ||
64 | } | ||
65 | } | ||
66 | } | ||
67 | |||
68 | type PluginLocalesTranslations = { | 55 | type PluginLocalesTranslations = { |
69 | [ locale: string ]: PluginTranslation | 56 | [locale: string]: PluginTranslation |
70 | } | 57 | } |
71 | 58 | ||
72 | export class PluginManager implements ServerHook { | 59 | export class PluginManager implements ServerHook { |
73 | 60 | ||
74 | private static instance: PluginManager | 61 | private static instance: PluginManager |
75 | 62 | ||
76 | private registeredPlugins: { [ name: string ]: RegisteredPlugin } = {} | 63 | private registeredPlugins: { [name: string]: RegisteredPlugin } = {} |
77 | private settings: { [ name: string ]: RegisterServerSettingOptions[] } = {} | ||
78 | private hooks: { [ name: string ]: HookInformationValue[] } = {} | ||
79 | private translations: PluginLocalesTranslations = {} | ||
80 | 64 | ||
81 | private updatedVideoConstants: UpdatedVideoConstant = { | 65 | private hooks: { [name: string]: HookInformationValue[] } = {} |
82 | language: {}, | 66 | private translations: PluginLocalesTranslations = {} |
83 | licence: {}, | ||
84 | category: {} | ||
85 | } | ||
86 | 67 | ||
87 | private constructor () { | 68 | private constructor () { |
88 | } | 69 | } |
@@ -97,7 +78,7 @@ export class PluginManager implements ServerHook { | |||
97 | return this.registeredPlugins[npmName] | 78 | return this.registeredPlugins[npmName] |
98 | } | 79 | } |
99 | 80 | ||
100 | getRegisteredPlugin (name: string) { | 81 | getRegisteredPluginByShortName (name: string) { |
101 | const npmName = PluginModel.buildNpmName(name, PluginType.PLUGIN) | 82 | const npmName = PluginModel.buildNpmName(name, PluginType.PLUGIN) |
102 | const registered = this.getRegisteredPluginOrTheme(npmName) | 83 | const registered = this.getRegisteredPluginOrTheme(npmName) |
103 | 84 | ||
@@ -106,7 +87,7 @@ export class PluginManager implements ServerHook { | |||
106 | return registered | 87 | return registered |
107 | } | 88 | } |
108 | 89 | ||
109 | getRegisteredTheme (name: string) { | 90 | getRegisteredThemeByShortName (name: string) { |
110 | const npmName = PluginModel.buildNpmName(name, PluginType.THEME) | 91 | const npmName = PluginModel.buildNpmName(name, PluginType.THEME) |
111 | const registered = this.getRegisteredPluginOrTheme(npmName) | 92 | const registered = this.getRegisteredPluginOrTheme(npmName) |
112 | 93 | ||
@@ -123,17 +104,102 @@ export class PluginManager implements ServerHook { | |||
123 | return this.getRegisteredPluginsOrThemes(PluginType.THEME) | 104 | return this.getRegisteredPluginsOrThemes(PluginType.THEME) |
124 | } | 105 | } |
125 | 106 | ||
107 | getIdAndPassAuths () { | ||
108 | return this.getRegisteredPlugins() | ||
109 | .map(p => ({ | ||
110 | npmName: p.npmName, | ||
111 | name: p.name, | ||
112 | version: p.version, | ||
113 | idAndPassAuths: p.registerHelpersStore.getIdAndPassAuths() | ||
114 | })) | ||
115 | .filter(v => v.idAndPassAuths.length !== 0) | ||
116 | } | ||
117 | |||
118 | getExternalAuths () { | ||
119 | return this.getRegisteredPlugins() | ||
120 | .map(p => ({ | ||
121 | npmName: p.npmName, | ||
122 | name: p.name, | ||
123 | version: p.version, | ||
124 | externalAuths: p.registerHelpersStore.getExternalAuths() | ||
125 | })) | ||
126 | .filter(v => v.externalAuths.length !== 0) | ||
127 | } | ||
128 | |||
126 | getRegisteredSettings (npmName: string) { | 129 | getRegisteredSettings (npmName: string) { |
127 | return this.settings[npmName] || [] | 130 | const result = this.getRegisteredPluginOrTheme(npmName) |
131 | if (!result || result.type !== PluginType.PLUGIN) return [] | ||
132 | |||
133 | return result.registerHelpersStore.getSettings() | ||
134 | } | ||
135 | |||
136 | getRouter (npmName: string) { | ||
137 | const result = this.getRegisteredPluginOrTheme(npmName) | ||
138 | if (!result || result.type !== PluginType.PLUGIN) return null | ||
139 | |||
140 | return result.registerHelpersStore.getRouter() | ||
128 | } | 141 | } |
129 | 142 | ||
130 | getTranslations (locale: string) { | 143 | getTranslations (locale: string) { |
131 | return this.translations[locale] || {} | 144 | return this.translations[locale] || {} |
132 | } | 145 | } |
133 | 146 | ||
147 | async isTokenValid (token: MOAuthTokenUser, type: 'access' | 'refresh') { | ||
148 | const auth = this.getAuth(token.User.pluginAuth, token.authName) | ||
149 | if (!auth) return true | ||
150 | |||
151 | if (auth.hookTokenValidity) { | ||
152 | try { | ||
153 | const { valid } = await auth.hookTokenValidity({ token, type }) | ||
154 | |||
155 | if (valid === false) { | ||
156 | logger.info('Rejecting %s token validity from auth %s of plugin %s', type, token.authName, token.User.pluginAuth) | ||
157 | } | ||
158 | |||
159 | return valid | ||
160 | } catch (err) { | ||
161 | logger.warn('Cannot run check token validity from auth %s of plugin %s.', token.authName, token.User.pluginAuth, { err }) | ||
162 | return true | ||
163 | } | ||
164 | } | ||
165 | |||
166 | return true | ||
167 | } | ||
168 | |||
169 | // ###################### External events ###################### | ||
170 | |||
171 | onLogout (npmName: string, authName: string, user: MUser) { | ||
172 | const auth = this.getAuth(npmName, authName) | ||
173 | |||
174 | if (auth?.onLogout) { | ||
175 | logger.info('Running onLogout function from auth %s of plugin %s', authName, npmName) | ||
176 | |||
177 | try { | ||
178 | auth.onLogout(user) | ||
179 | } catch (err) { | ||
180 | logger.warn('Cannot run onLogout function from auth %s of plugin %s.', authName, npmName, { err }) | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | |||
185 | onSettingsChanged (name: string, settings: any) { | ||
186 | const registered = this.getRegisteredPluginByShortName(name) | ||
187 | if (!registered) { | ||
188 | logger.error('Cannot find plugin %s to call on settings changed.', name) | ||
189 | } | ||
190 | |||
191 | for (const cb of registered.registerHelpersStore.getOnSettingsChangedCallbacks()) { | ||
192 | try { | ||
193 | cb(settings) | ||
194 | } catch (err) { | ||
195 | logger.error('Cannot run on settings changed callback for %s.', registered.npmName, { err }) | ||
196 | } | ||
197 | } | ||
198 | } | ||
199 | |||
134 | // ###################### Hooks ###################### | 200 | // ###################### Hooks ###################### |
135 | 201 | ||
136 | async runHook <T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> { | 202 | async runHook<T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> { |
137 | if (!this.hooks[hookName]) return Promise.resolve(result) | 203 | if (!this.hooks[hookName]) return Promise.resolve(result) |
138 | 204 | ||
139 | const hookType = getHookType(hookName) | 205 | const hookType = getHookType(hookName) |
@@ -185,7 +251,6 @@ export class PluginManager implements ServerHook { | |||
185 | } | 251 | } |
186 | 252 | ||
187 | delete this.registeredPlugins[plugin.npmName] | 253 | delete this.registeredPlugins[plugin.npmName] |
188 | delete this.settings[plugin.npmName] | ||
189 | 254 | ||
190 | this.deleteTranslations(plugin.npmName) | 255 | this.deleteTranslations(plugin.npmName) |
191 | 256 | ||
@@ -197,7 +262,8 @@ export class PluginManager implements ServerHook { | |||
197 | this.hooks[key] = this.hooks[key].filter(h => h.npmName !== npmName) | 262 | this.hooks[key] = this.hooks[key].filter(h => h.npmName !== npmName) |
198 | } | 263 | } |
199 | 264 | ||
200 | this.reinitVideoConstants(plugin.npmName) | 265 | const store = plugin.registerHelpersStore |
266 | store.reinitVideoConstants(plugin.npmName) | ||
201 | 267 | ||
202 | logger.info('Regenerating registered plugin CSS to global file.') | 268 | logger.info('Regenerating registered plugin CSS to global file.') |
203 | await this.regeneratePluginGlobalCSS() | 269 | await this.regeneratePluginGlobalCSS() |
@@ -303,8 +369,11 @@ export class PluginManager implements ServerHook { | |||
303 | this.sanitizeAndCheckPackageJSONOrThrow(packageJSON, plugin.type) | 369 | this.sanitizeAndCheckPackageJSONOrThrow(packageJSON, plugin.type) |
304 | 370 | ||
305 | let library: PluginLibrary | 371 | let library: PluginLibrary |
372 | let registerHelpersStore: RegisterHelpersStore | ||
306 | if (plugin.type === PluginType.PLUGIN) { | 373 | if (plugin.type === PluginType.PLUGIN) { |
307 | library = await this.registerPlugin(plugin, pluginPath, packageJSON) | 374 | const result = await this.registerPlugin(plugin, pluginPath, packageJSON) |
375 | library = result.library | ||
376 | registerHelpersStore = result.registerStore | ||
308 | } | 377 | } |
309 | 378 | ||
310 | const clientScripts: { [id: string]: ClientScript } = {} | 379 | const clientScripts: { [id: string]: ClientScript } = {} |
@@ -312,7 +381,7 @@ export class PluginManager implements ServerHook { | |||
312 | clientScripts[c.script] = c | 381 | clientScripts[c.script] = c |
313 | } | 382 | } |
314 | 383 | ||
315 | this.registeredPlugins[ npmName ] = { | 384 | this.registeredPlugins[npmName] = { |
316 | npmName, | 385 | npmName, |
317 | name: plugin.name, | 386 | name: plugin.name, |
318 | type: plugin.type, | 387 | type: plugin.type, |
@@ -323,6 +392,7 @@ export class PluginManager implements ServerHook { | |||
323 | staticDirs: packageJSON.staticDirs, | 392 | staticDirs: packageJSON.staticDirs, |
324 | clientScripts, | 393 | clientScripts, |
325 | css: packageJSON.css, | 394 | css: packageJSON.css, |
395 | registerHelpersStore: registerHelpersStore || undefined, | ||
326 | unregister: library ? library.unregister : undefined | 396 | unregister: library ? library.unregister : undefined |
327 | } | 397 | } |
328 | 398 | ||
@@ -341,15 +411,15 @@ export class PluginManager implements ServerHook { | |||
341 | throw new Error('Library code is not valid (miss register or unregister function)') | 411 | throw new Error('Library code is not valid (miss register or unregister function)') |
342 | } | 412 | } |
343 | 413 | ||
344 | const registerHelpers = this.getRegisterHelpers(npmName, plugin) | 414 | const { registerOptions, registerStore } = this.getRegisterHelpers(npmName, plugin) |
345 | library.register(registerHelpers) | 415 | library.register(registerOptions) |
346 | .catch(err => logger.error('Cannot register plugin %s.', npmName, { err })) | 416 | .catch(err => logger.error('Cannot register plugin %s.', npmName, { err })) |
347 | 417 | ||
348 | logger.info('Add plugin %s CSS to global file.', npmName) | 418 | logger.info('Add plugin %s CSS to global file.', npmName) |
349 | 419 | ||
350 | await this.addCSSToGlobalFile(pluginPath, packageJSON.css) | 420 | await this.addCSSToGlobalFile(pluginPath, packageJSON.css) |
351 | 421 | ||
352 | return library | 422 | return { library, registerStore } |
353 | } | 423 | } |
354 | 424 | ||
355 | // ###################### Translations ###################### | 425 | // ###################### Translations ###################### |
@@ -432,13 +502,23 @@ export class PluginManager implements ServerHook { | |||
432 | return join(CONFIG.STORAGE.PLUGINS_DIR, 'node_modules', npmName) | 502 | return join(CONFIG.STORAGE.PLUGINS_DIR, 'node_modules', npmName) |
433 | } | 503 | } |
434 | 504 | ||
505 | private getAuth (npmName: string, authName: string) { | ||
506 | const plugin = this.getRegisteredPluginOrTheme(npmName) | ||
507 | if (!plugin || plugin.type !== PluginType.PLUGIN) return null | ||
508 | |||
509 | let auths: (RegisterServerAuthPassOptions | RegisterServerAuthExternalOptions)[] = plugin.registerHelpersStore.getIdAndPassAuths() | ||
510 | auths = auths.concat(plugin.registerHelpersStore.getExternalAuths()) | ||
511 | |||
512 | return auths.find(a => a.authName === authName) | ||
513 | } | ||
514 | |||
435 | // ###################### Private getters ###################### | 515 | // ###################### Private getters ###################### |
436 | 516 | ||
437 | private getRegisteredPluginsOrThemes (type: PluginType) { | 517 | private getRegisteredPluginsOrThemes (type: PluginType) { |
438 | const plugins: RegisteredPlugin[] = [] | 518 | const plugins: RegisteredPlugin[] = [] |
439 | 519 | ||
440 | for (const npmName of Object.keys(this.registeredPlugins)) { | 520 | for (const npmName of Object.keys(this.registeredPlugins)) { |
441 | const plugin = this.registeredPlugins[ npmName ] | 521 | const plugin = this.registeredPlugins[npmName] |
442 | if (plugin.type !== type) continue | 522 | if (plugin.type !== type) continue |
443 | 523 | ||
444 | plugins.push(plugin) | 524 | plugins.push(plugin) |
@@ -449,149 +529,26 @@ export class PluginManager implements ServerHook { | |||
449 | 529 | ||
450 | // ###################### Generate register helpers ###################### | 530 | // ###################### Generate register helpers ###################### |
451 | 531 | ||
452 | private getRegisterHelpers (npmName: string, plugin: PluginModel): RegisterServerOptions { | 532 | private getRegisterHelpers ( |
453 | const registerHook = (options: RegisterServerHookOptions) => { | 533 | npmName: string, |
454 | if (serverHookObject[options.target] !== true) { | 534 | plugin: PluginModel |
455 | logger.warn('Unknown hook %s of plugin %s. Skipping.', options.target, npmName) | 535 | ): { registerStore: RegisterHelpersStore, registerOptions: RegisterServerOptions } { |
456 | return | 536 | const onHookAdded = (options: RegisterServerHookOptions) => { |
457 | } | ||
458 | |||
459 | if (!this.hooks[options.target]) this.hooks[options.target] = [] | 537 | if (!this.hooks[options.target]) this.hooks[options.target] = [] |
460 | 538 | ||
461 | this.hooks[options.target].push({ | 539 | this.hooks[options.target].push({ |
462 | npmName, | 540 | npmName: npmName, |
463 | pluginName: plugin.name, | 541 | pluginName: plugin.name, |
464 | handler: options.handler, | 542 | handler: options.handler, |
465 | priority: options.priority || 0 | 543 | priority: options.priority || 0 |
466 | }) | 544 | }) |
467 | } | 545 | } |
468 | 546 | ||
469 | const registerSetting = (options: RegisterServerSettingOptions) => { | 547 | const registerHelpersStore = new RegisterHelpersStore(npmName, plugin, onHookAdded.bind(this)) |
470 | if (!this.settings[npmName]) this.settings[npmName] = [] | ||
471 | |||
472 | this.settings[npmName].push(options) | ||
473 | } | ||
474 | |||
475 | const settingsManager: PluginSettingsManager = { | ||
476 | getSetting: (name: string) => PluginModel.getSetting(plugin.name, plugin.type, name), | ||
477 | |||
478 | setSetting: (name: string, value: string) => PluginModel.setSetting(plugin.name, plugin.type, name, value) | ||
479 | } | ||
480 | |||
481 | const storageManager: PluginStorageManager = { | ||
482 | getData: (key: string) => PluginModel.getData(plugin.name, plugin.type, key), | ||
483 | |||
484 | storeData: (key: string, data: any) => PluginModel.storeData(plugin.name, plugin.type, key, data) | ||
485 | } | ||
486 | |||
487 | const videoLanguageManager: PluginVideoLanguageManager = { | ||
488 | addLanguage: (key: string, label: string) => this.addConstant({ npmName, type: 'language', obj: VIDEO_LANGUAGES, key, label }), | ||
489 | |||
490 | deleteLanguage: (key: string) => this.deleteConstant({ npmName, type: 'language', obj: VIDEO_LANGUAGES, key }) | ||
491 | } | ||
492 | |||
493 | const videoCategoryManager: PluginVideoCategoryManager = { | ||
494 | addCategory: (key: number, label: string) => this.addConstant({ npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label }), | ||
495 | |||
496 | deleteCategory: (key: number) => this.deleteConstant({ npmName, type: 'category', obj: VIDEO_CATEGORIES, key }) | ||
497 | } | ||
498 | |||
499 | const videoLicenceManager: PluginVideoLicenceManager = { | ||
500 | addLicence: (key: number, label: string) => this.addConstant({ npmName, type: 'licence', obj: VIDEO_LICENCES, key, label }), | ||
501 | |||
502 | deleteLicence: (key: number) => this.deleteConstant({ npmName, type: 'licence', obj: VIDEO_LICENCES, key }) | ||
503 | } | ||
504 | |||
505 | const peertubeHelpers = { | ||
506 | logger | ||
507 | } | ||
508 | 548 | ||
509 | return { | 549 | return { |
510 | registerHook, | 550 | registerStore: registerHelpersStore, |
511 | registerSetting, | 551 | registerOptions: registerHelpersStore.buildRegisterHelpers() |
512 | settingsManager, | ||
513 | storageManager, | ||
514 | videoLanguageManager, | ||
515 | videoCategoryManager, | ||
516 | videoLicenceManager, | ||
517 | peertubeHelpers | ||
518 | } | ||
519 | } | ||
520 | |||
521 | private addConstant <T extends string | number> (parameters: { | ||
522 | npmName: string, | ||
523 | type: AlterableVideoConstant, | ||
524 | obj: VideoConstant, | ||
525 | key: T, | ||
526 | label: string | ||
527 | }) { | ||
528 | const { npmName, type, obj, key, label } = parameters | ||
529 | |||
530 | if (obj[key]) { | ||
531 | logger.warn('Cannot add %s %s by plugin %s: key already exists.', type, npmName, key) | ||
532 | return false | ||
533 | } | ||
534 | |||
535 | if (!this.updatedVideoConstants[type][npmName]) { | ||
536 | this.updatedVideoConstants[type][npmName] = { | ||
537 | added: [], | ||
538 | deleted: [] | ||
539 | } | ||
540 | } | ||
541 | |||
542 | this.updatedVideoConstants[type][npmName].added.push({ key, label }) | ||
543 | obj[key] = label | ||
544 | |||
545 | return true | ||
546 | } | ||
547 | |||
548 | private deleteConstant <T extends string | number> (parameters: { | ||
549 | npmName: string, | ||
550 | type: AlterableVideoConstant, | ||
551 | obj: VideoConstant, | ||
552 | key: T | ||
553 | }) { | ||
554 | const { npmName, type, obj, key } = parameters | ||
555 | |||
556 | if (!obj[key]) { | ||
557 | logger.warn('Cannot delete %s %s by plugin %s: key does not exist.', type, npmName, key) | ||
558 | return false | ||
559 | } | ||
560 | |||
561 | if (!this.updatedVideoConstants[type][npmName]) { | ||
562 | this.updatedVideoConstants[type][npmName] = { | ||
563 | added: [], | ||
564 | deleted: [] | ||
565 | } | ||
566 | } | ||
567 | |||
568 | this.updatedVideoConstants[type][npmName].deleted.push({ key, label: obj[key] }) | ||
569 | delete obj[key] | ||
570 | |||
571 | return true | ||
572 | } | ||
573 | |||
574 | private reinitVideoConstants (npmName: string) { | ||
575 | const hash = { | ||
576 | language: VIDEO_LANGUAGES, | ||
577 | licence: VIDEO_LICENCES, | ||
578 | category: VIDEO_CATEGORIES | ||
579 | } | ||
580 | const types: AlterableVideoConstant[] = [ 'language', 'licence', 'category' ] | ||
581 | |||
582 | for (const type of types) { | ||
583 | const updatedConstants = this.updatedVideoConstants[type][npmName] | ||
584 | if (!updatedConstants) continue | ||
585 | |||
586 | for (const added of updatedConstants.added) { | ||
587 | delete hash[type][added.key] | ||
588 | } | ||
589 | |||
590 | for (const deleted of updatedConstants.deleted) { | ||
591 | hash[type][deleted.key] = deleted.label | ||
592 | } | ||
593 | |||
594 | delete this.updatedVideoConstants[type][npmName] | ||
595 | } | 552 | } |
596 | } | 553 | } |
597 | 554 | ||
@@ -604,7 +561,7 @@ export class PluginManager implements ServerHook { | |||
604 | const { result: packageJSONValid, badFields } = isPackageJSONValid(packageJSON, pluginType) | 561 | const { result: packageJSONValid, badFields } = isPackageJSONValid(packageJSON, pluginType) |
605 | if (!packageJSONValid) { | 562 | if (!packageJSONValid) { |
606 | const formattedFields = badFields.map(f => `"${f}"`) | 563 | const formattedFields = badFields.map(f => `"${f}"`) |
607 | .join(', ') | 564 | .join(', ') |
608 | 565 | ||
609 | throw new Error(`PackageJSON is invalid (invalid fields: ${formattedFields}).`) | 566 | throw new Error(`PackageJSON is invalid (invalid fields: ${formattedFields}).`) |
610 | } | 567 | } |
diff --git a/server/lib/plugins/register-helpers-store.ts b/server/lib/plugins/register-helpers-store.ts new file mode 100644 index 000000000..e337b1cb0 --- /dev/null +++ b/server/lib/plugins/register-helpers-store.ts | |||
@@ -0,0 +1,355 @@ | |||
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' | ||
11 | import { PluginModel } from '@server/models/server/plugin' | ||
12 | import { RegisterServerOptions } from '@server/typings/plugins' | ||
13 | import { PluginPlaylistPrivacyManager } from '@shared/models/plugins/plugin-playlist-privacy-manager.model' | ||
14 | import { PluginSettingsManager } from '@shared/models/plugins/plugin-settings-manager.model' | ||
15 | import { PluginStorageManager } from '@shared/models/plugins/plugin-storage-manager.model' | ||
16 | import { PluginVideoCategoryManager } from '@shared/models/plugins/plugin-video-category-manager.model' | ||
17 | import { PluginVideoLanguageManager } from '@shared/models/plugins/plugin-video-language-manager.model' | ||
18 | import { PluginVideoLicenceManager } from '@shared/models/plugins/plugin-video-licence-manager.model' | ||
19 | import { PluginVideoPrivacyManager } from '@shared/models/plugins/plugin-video-privacy-manager.model' | ||
20 | import { | ||
21 | RegisterServerAuthExternalOptions, | ||
22 | RegisterServerAuthExternalResult, | ||
23 | RegisterServerAuthPassOptions, | ||
24 | RegisterServerExternalAuthenticatedResult | ||
25 | } from '@shared/models/plugins/register-server-auth.model' | ||
26 | import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model' | ||
27 | import { RegisterServerSettingOptions } from '@shared/models/plugins/register-server-setting.model' | ||
28 | import { serverHookObject } from '@shared/models/plugins/server-hook.model' | ||
29 | import { buildPluginHelpers } from './plugin-helpers' | ||
30 | |||
31 | type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy' | ||
32 | type VideoConstant = { [key in number | string]: string } | ||
33 | |||
34 | type UpdatedVideoConstant = { | ||
35 | [name in AlterableVideoConstant]: { | ||
36 | added: { key: number | string, label: string }[] | ||
37 | deleted: { key: number | string, label: string }[] | ||
38 | } | ||
39 | } | ||
40 | |||
41 | export class RegisterHelpersStore { | ||
42 | private readonly updatedVideoConstants: UpdatedVideoConstant = { | ||
43 | playlistPrivacy: { added: [], deleted: [] }, | ||
44 | privacy: { added: [], deleted: [] }, | ||
45 | language: { added: [], deleted: [] }, | ||
46 | licence: { added: [], deleted: [] }, | ||
47 | category: { added: [], deleted: [] } | ||
48 | } | ||
49 | |||
50 | private readonly settings: RegisterServerSettingOptions[] = [] | ||
51 | |||
52 | private idAndPassAuths: RegisterServerAuthPassOptions[] = [] | ||
53 | private externalAuths: RegisterServerAuthExternalOptions[] = [] | ||
54 | |||
55 | private readonly onSettingsChangeCallbacks: ((settings: any) => void)[] = [] | ||
56 | |||
57 | private readonly router: express.Router | ||
58 | |||
59 | constructor ( | ||
60 | private readonly npmName: string, | ||
61 | private readonly plugin: PluginModel, | ||
62 | private readonly onHookAdded: (options: RegisterServerHookOptions) => void | ||
63 | ) { | ||
64 | this.router = express.Router() | ||
65 | } | ||
66 | |||
67 | buildRegisterHelpers (): RegisterServerOptions { | ||
68 | const registerHook = this.buildRegisterHook() | ||
69 | const registerSetting = this.buildRegisterSetting() | ||
70 | |||
71 | const getRouter = this.buildGetRouter() | ||
72 | |||
73 | const settingsManager = this.buildSettingsManager() | ||
74 | const storageManager = this.buildStorageManager() | ||
75 | |||
76 | const videoLanguageManager = this.buildVideoLanguageManager() | ||
77 | |||
78 | const videoLicenceManager = this.buildVideoLicenceManager() | ||
79 | const videoCategoryManager = this.buildVideoCategoryManager() | ||
80 | |||
81 | const videoPrivacyManager = this.buildVideoPrivacyManager() | ||
82 | const playlistPrivacyManager = this.buildPlaylistPrivacyManager() | ||
83 | |||
84 | const registerIdAndPassAuth = this.buildRegisterIdAndPassAuth() | ||
85 | const registerExternalAuth = this.buildRegisterExternalAuth() | ||
86 | const unregisterIdAndPassAuth = this.buildUnregisterIdAndPassAuth() | ||
87 | const unregisterExternalAuth = this.buildUnregisterExternalAuth() | ||
88 | |||
89 | const peertubeHelpers = buildPluginHelpers(this.npmName) | ||
90 | |||
91 | return { | ||
92 | registerHook, | ||
93 | registerSetting, | ||
94 | |||
95 | getRouter, | ||
96 | |||
97 | settingsManager, | ||
98 | storageManager, | ||
99 | |||
100 | videoLanguageManager, | ||
101 | videoCategoryManager, | ||
102 | videoLicenceManager, | ||
103 | |||
104 | videoPrivacyManager, | ||
105 | playlistPrivacyManager, | ||
106 | |||
107 | registerIdAndPassAuth, | ||
108 | registerExternalAuth, | ||
109 | unregisterIdAndPassAuth, | ||
110 | unregisterExternalAuth, | ||
111 | |||
112 | peertubeHelpers | ||
113 | } | ||
114 | } | ||
115 | |||
116 | reinitVideoConstants (npmName: string) { | ||
117 | const hash = { | ||
118 | language: VIDEO_LANGUAGES, | ||
119 | licence: VIDEO_LICENCES, | ||
120 | category: VIDEO_CATEGORIES, | ||
121 | privacy: VIDEO_PRIVACIES, | ||
122 | playlistPrivacy: VIDEO_PLAYLIST_PRIVACIES | ||
123 | } | ||
124 | const types: AlterableVideoConstant[] = [ 'language', 'licence', 'category', 'privacy', 'playlistPrivacy' ] | ||
125 | |||
126 | for (const type of types) { | ||
127 | const updatedConstants = this.updatedVideoConstants[type][npmName] | ||
128 | if (!updatedConstants) continue | ||
129 | |||
130 | for (const added of updatedConstants.added) { | ||
131 | delete hash[type][added.key] | ||
132 | } | ||
133 | |||
134 | for (const deleted of updatedConstants.deleted) { | ||
135 | hash[type][deleted.key] = deleted.label | ||
136 | } | ||
137 | |||
138 | delete this.updatedVideoConstants[type][npmName] | ||
139 | } | ||
140 | } | ||
141 | |||
142 | getSettings () { | ||
143 | return this.settings | ||
144 | } | ||
145 | |||
146 | getRouter () { | ||
147 | return this.router | ||
148 | } | ||
149 | |||
150 | getIdAndPassAuths () { | ||
151 | return this.idAndPassAuths | ||
152 | } | ||
153 | |||
154 | getExternalAuths () { | ||
155 | return this.externalAuths | ||
156 | } | ||
157 | |||
158 | getOnSettingsChangedCallbacks () { | ||
159 | return this.onSettingsChangeCallbacks | ||
160 | } | ||
161 | |||
162 | private buildGetRouter () { | ||
163 | return () => this.router | ||
164 | } | ||
165 | |||
166 | private buildRegisterSetting () { | ||
167 | return (options: RegisterServerSettingOptions) => { | ||
168 | this.settings.push(options) | ||
169 | } | ||
170 | } | ||
171 | |||
172 | private buildRegisterHook () { | ||
173 | return (options: RegisterServerHookOptions) => { | ||
174 | if (serverHookObject[options.target] !== true) { | ||
175 | logger.warn('Unknown hook %s of plugin %s. Skipping.', options.target, this.npmName) | ||
176 | return | ||
177 | } | ||
178 | |||
179 | return this.onHookAdded(options) | ||
180 | } | ||
181 | } | ||
182 | |||
183 | private buildRegisterIdAndPassAuth () { | ||
184 | return (options: RegisterServerAuthPassOptions) => { | ||
185 | if (!options.authName || typeof options.getWeight !== 'function' || typeof options.login !== 'function') { | ||
186 | logger.error('Cannot register auth plugin %s: authName, getWeight or login are not valid.', this.npmName, { options }) | ||
187 | return | ||
188 | } | ||
189 | |||
190 | this.idAndPassAuths.push(options) | ||
191 | } | ||
192 | } | ||
193 | |||
194 | private buildRegisterExternalAuth () { | ||
195 | const self = this | ||
196 | |||
197 | return (options: RegisterServerAuthExternalOptions) => { | ||
198 | if (!options.authName || typeof options.authDisplayName !== 'function' || typeof options.onAuthRequest !== 'function') { | ||
199 | logger.error('Cannot register auth plugin %s: authName, authDisplayName or onAuthRequest are not valid.', this.npmName, { options }) | ||
200 | return | ||
201 | } | ||
202 | |||
203 | this.externalAuths.push(options) | ||
204 | |||
205 | return { | ||
206 | userAuthenticated (result: RegisterServerExternalAuthenticatedResult): void { | ||
207 | onExternalUserAuthenticated({ | ||
208 | npmName: self.npmName, | ||
209 | authName: options.authName, | ||
210 | authResult: result | ||
211 | }).catch(err => { | ||
212 | logger.error('Cannot execute onExternalUserAuthenticated.', { npmName: self.npmName, authName: options.authName, err }) | ||
213 | }) | ||
214 | } | ||
215 | } as RegisterServerAuthExternalResult | ||
216 | } | ||
217 | } | ||
218 | |||
219 | private buildUnregisterExternalAuth () { | ||
220 | return (authName: string) => { | ||
221 | this.externalAuths = this.externalAuths.filter(a => a.authName !== authName) | ||
222 | } | ||
223 | } | ||
224 | |||
225 | private buildUnregisterIdAndPassAuth () { | ||
226 | return (authName: string) => { | ||
227 | this.idAndPassAuths = this.idAndPassAuths.filter(a => a.authName !== authName) | ||
228 | } | ||
229 | } | ||
230 | |||
231 | private buildSettingsManager (): PluginSettingsManager { | ||
232 | return { | ||
233 | getSetting: (name: string) => PluginModel.getSetting(this.plugin.name, this.plugin.type, name, this.settings), | ||
234 | |||
235 | getSettings: (names: string[]) => PluginModel.getSettings(this.plugin.name, this.plugin.type, names, this.settings), | ||
236 | |||
237 | setSetting: (name: string, value: string) => PluginModel.setSetting(this.plugin.name, this.plugin.type, name, value), | ||
238 | |||
239 | onSettingsChange: (cb: (settings: any) => void) => this.onSettingsChangeCallbacks.push(cb) | ||
240 | } | ||
241 | } | ||
242 | |||
243 | private buildStorageManager (): PluginStorageManager { | ||
244 | return { | ||
245 | getData: (key: string) => PluginModel.getData(this.plugin.name, this.plugin.type, key), | ||
246 | |||
247 | storeData: (key: string, data: any) => PluginModel.storeData(this.plugin.name, this.plugin.type, key, data) | ||
248 | } | ||
249 | } | ||
250 | |||
251 | private buildVideoLanguageManager (): PluginVideoLanguageManager { | ||
252 | return { | ||
253 | addLanguage: (key: string, label: string) => { | ||
254 | return this.addConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key, label }) | ||
255 | }, | ||
256 | |||
257 | deleteLanguage: (key: string) => { | ||
258 | return this.deleteConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key }) | ||
259 | } | ||
260 | } | ||
261 | } | ||
262 | |||
263 | private buildVideoCategoryManager (): PluginVideoCategoryManager { | ||
264 | return { | ||
265 | addCategory: (key: number, label: string) => { | ||
266 | return this.addConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label }) | ||
267 | }, | ||
268 | |||
269 | deleteCategory: (key: number) => { | ||
270 | return this.deleteConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key }) | ||
271 | } | ||
272 | } | ||
273 | } | ||
274 | |||
275 | private buildVideoPrivacyManager (): PluginVideoPrivacyManager { | ||
276 | return { | ||
277 | deletePrivacy: (key: number) => { | ||
278 | return this.deleteConstant({ npmName: this.npmName, type: 'privacy', obj: VIDEO_PRIVACIES, key }) | ||
279 | } | ||
280 | } | ||
281 | } | ||
282 | |||
283 | private buildPlaylistPrivacyManager (): PluginPlaylistPrivacyManager { | ||
284 | return { | ||
285 | deletePlaylistPrivacy: (key: number) => { | ||
286 | return this.deleteConstant({ npmName: this.npmName, type: 'playlistPrivacy', obj: VIDEO_PLAYLIST_PRIVACIES, key }) | ||
287 | } | ||
288 | } | ||
289 | } | ||
290 | |||
291 | private buildVideoLicenceManager (): PluginVideoLicenceManager { | ||
292 | return { | ||
293 | addLicence: (key: number, label: string) => { | ||
294 | return this.addConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key, label }) | ||
295 | }, | ||
296 | |||
297 | deleteLicence: (key: number) => { | ||
298 | return this.deleteConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key }) | ||
299 | } | ||
300 | } | ||
301 | } | ||
302 | |||
303 | private addConstant<T extends string | number> (parameters: { | ||
304 | npmName: string | ||
305 | type: AlterableVideoConstant | ||
306 | obj: VideoConstant | ||
307 | key: T | ||
308 | label: string | ||
309 | }) { | ||
310 | const { npmName, type, obj, key, label } = parameters | ||
311 | |||
312 | if (obj[key]) { | ||
313 | logger.warn('Cannot add %s %s by plugin %s: key already exists.', type, npmName, key) | ||
314 | return false | ||
315 | } | ||
316 | |||
317 | if (!this.updatedVideoConstants[type][npmName]) { | ||
318 | this.updatedVideoConstants[type][npmName] = { | ||
319 | added: [], | ||
320 | deleted: [] | ||
321 | } | ||
322 | } | ||
323 | |||
324 | this.updatedVideoConstants[type][npmName].added.push({ key, label }) | ||
325 | obj[key] = label | ||
326 | |||
327 | return true | ||
328 | } | ||
329 | |||
330 | private deleteConstant<T extends string | number> (parameters: { | ||
331 | npmName: string | ||
332 | type: AlterableVideoConstant | ||
333 | obj: VideoConstant | ||
334 | key: T | ||
335 | }) { | ||
336 | const { npmName, type, obj, key } = parameters | ||
337 | |||
338 | if (!obj[key]) { | ||
339 | logger.warn('Cannot delete %s %s by plugin %s: key does not exist.', type, npmName, key) | ||
340 | return false | ||
341 | } | ||
342 | |||
343 | if (!this.updatedVideoConstants[type][npmName]) { | ||
344 | this.updatedVideoConstants[type][npmName] = { | ||
345 | added: [], | ||
346 | deleted: [] | ||
347 | } | ||
348 | } | ||
349 | |||
350 | this.updatedVideoConstants[type][npmName].deleted.push({ key, label: obj[key] }) | ||
351 | delete obj[key] | ||
352 | |||
353 | return true | ||
354 | } | ||
355 | } | ||