diff options
author | Chocobozzz <me@florianbigard.com> | 2019-07-18 14:28:37 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-07-24 10:58:16 +0200 |
commit | b4055e1c23eeefb0c8a85a77f312b2827d98f483 (patch) | |
tree | 51b6b04c1ad10897047817d2eaaa037d1331fa6a /server/lib | |
parent | 66e001c848c009412c65cbce41be344d8985fd83 (diff) | |
download | PeerTube-b4055e1c23eeefb0c8a85a77f312b2827d98f483.tar.gz PeerTube-b4055e1c23eeefb0c8a85a77f312b2827d98f483.tar.zst PeerTube-b4055e1c23eeefb0c8a85a77f312b2827d98f483.zip |
Add server hooks
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/activitypub/video-comments.ts | 2 | ||||
-rw-r--r-- | server/lib/activitypub/videos.ts | 132 | ||||
-rw-r--r-- | server/lib/moderation.ts | 64 | ||||
-rw-r--r-- | server/lib/plugins/hooks.ts | 26 | ||||
-rw-r--r-- | server/lib/plugins/plugin-manager.ts | 22 | ||||
-rw-r--r-- | server/lib/video-blacklist.ts | 25 |
6 files changed, 186 insertions, 85 deletions
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index c3fc6b462..2f26ddefd 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts | |||
@@ -134,7 +134,7 @@ async function resolveThread (url: string, comments: VideoCommentModel[] = []): | |||
134 | }) | 134 | }) |
135 | 135 | ||
136 | if (sanitizeAndCheckVideoCommentObject(body) === false) { | 136 | if (sanitizeAndCheckVideoCommentObject(body) === false) { |
137 | throw new Error('Remote video comment JSON is not valid :' + JSON.stringify(body)) | 137 | throw new Error('Remote video comment JSON is not valid:' + JSON.stringify(body)) |
138 | } | 138 | } |
139 | 139 | ||
140 | const actorUrl = body.attributedTo | 140 | const actorUrl = body.attributedTo |
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 4f26cb6be..dade6b55f 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -54,6 +54,8 @@ import { ThumbnailModel } from '../../models/video/thumbnail' | |||
54 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | 54 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' |
55 | import { join } from 'path' | 55 | import { join } from 'path' |
56 | import { FilteredModelAttributes } from '../../typings/sequelize' | 56 | import { FilteredModelAttributes } from '../../typings/sequelize' |
57 | import { Hooks } from '../plugins/hooks' | ||
58 | import { autoBlacklistVideoIfNeeded } from '../video-blacklist' | ||
57 | 59 | ||
58 | async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { | 60 | async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { |
59 | // If the video is not private and is published, we federate it | 61 | // If the video is not private and is published, we federate it |
@@ -236,72 +238,74 @@ async function updateVideoFromAP (options: { | |||
236 | channel: VideoChannelModel, | 238 | channel: VideoChannelModel, |
237 | overrideTo?: string[] | 239 | overrideTo?: string[] |
238 | }) { | 240 | }) { |
241 | const { video, videoObject, account, channel, overrideTo } = options | ||
242 | |||
239 | logger.debug('Updating remote video "%s".', options.videoObject.uuid) | 243 | logger.debug('Updating remote video "%s".', options.videoObject.uuid) |
240 | 244 | ||
241 | let videoFieldsSave: any | 245 | let videoFieldsSave: any |
242 | const wasPrivateVideo = options.video.privacy === VideoPrivacy.PRIVATE | 246 | const wasPrivateVideo = video.privacy === VideoPrivacy.PRIVATE |
243 | const wasUnlistedVideo = options.video.privacy === VideoPrivacy.UNLISTED | 247 | const wasUnlistedVideo = video.privacy === VideoPrivacy.UNLISTED |
244 | 248 | ||
245 | try { | 249 | try { |
246 | let thumbnailModel: ThumbnailModel | 250 | let thumbnailModel: ThumbnailModel |
247 | 251 | ||
248 | try { | 252 | try { |
249 | thumbnailModel = await createVideoMiniatureFromUrl(options.videoObject.icon.url, options.video, ThumbnailType.MINIATURE) | 253 | thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) |
250 | } catch (err) { | 254 | } catch (err) { |
251 | logger.warn('Cannot generate thumbnail of %s.', options.videoObject.id, { err }) | 255 | logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }) |
252 | } | 256 | } |
253 | 257 | ||
254 | await sequelizeTypescript.transaction(async t => { | 258 | await sequelizeTypescript.transaction(async t => { |
255 | const sequelizeOptions = { transaction: t } | 259 | const sequelizeOptions = { transaction: t } |
256 | 260 | ||
257 | videoFieldsSave = options.video.toJSON() | 261 | videoFieldsSave = video.toJSON() |
258 | 262 | ||
259 | // Check actor has the right to update the video | 263 | // Check actor has the right to update the video |
260 | const videoChannel = options.video.VideoChannel | 264 | const videoChannel = video.VideoChannel |
261 | if (videoChannel.Account.id !== options.account.id) { | 265 | if (videoChannel.Account.id !== account.id) { |
262 | throw new Error('Account ' + options.account.Actor.url + ' does not own video channel ' + videoChannel.Actor.url) | 266 | throw new Error('Account ' + account.Actor.url + ' does not own video channel ' + videoChannel.Actor.url) |
263 | } | 267 | } |
264 | 268 | ||
265 | const to = options.overrideTo ? options.overrideTo : options.videoObject.to | 269 | const to = overrideTo ? overrideTo : videoObject.to |
266 | const videoData = await videoActivityObjectToDBAttributes(options.channel, options.videoObject, to) | 270 | const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, to) |
267 | options.video.set('name', videoData.name) | 271 | video.name = videoData.name |
268 | options.video.set('uuid', videoData.uuid) | 272 | video.uuid = videoData.uuid |
269 | options.video.set('url', videoData.url) | 273 | video.url = videoData.url |
270 | options.video.set('category', videoData.category) | 274 | video.category = videoData.category |
271 | options.video.set('licence', videoData.licence) | 275 | video.licence = videoData.licence |
272 | options.video.set('language', videoData.language) | 276 | video.language = videoData.language |
273 | options.video.set('description', videoData.description) | 277 | video.description = videoData.description |
274 | options.video.set('support', videoData.support) | 278 | video.support = videoData.support |
275 | options.video.set('nsfw', videoData.nsfw) | 279 | video.nsfw = videoData.nsfw |
276 | options.video.set('commentsEnabled', videoData.commentsEnabled) | 280 | video.commentsEnabled = videoData.commentsEnabled |
277 | options.video.set('downloadEnabled', videoData.downloadEnabled) | 281 | video.downloadEnabled = videoData.downloadEnabled |
278 | options.video.set('waitTranscoding', videoData.waitTranscoding) | 282 | video.waitTranscoding = videoData.waitTranscoding |
279 | options.video.set('state', videoData.state) | 283 | video.state = videoData.state |
280 | options.video.set('duration', videoData.duration) | 284 | video.duration = videoData.duration |
281 | options.video.set('createdAt', videoData.createdAt) | 285 | video.createdAt = videoData.createdAt |
282 | options.video.set('publishedAt', videoData.publishedAt) | 286 | video.publishedAt = videoData.publishedAt |
283 | options.video.set('originallyPublishedAt', videoData.originallyPublishedAt) | 287 | video.originallyPublishedAt = videoData.originallyPublishedAt |
284 | options.video.set('privacy', videoData.privacy) | 288 | video.privacy = videoData.privacy |
285 | options.video.set('channelId', videoData.channelId) | 289 | video.channelId = videoData.channelId |
286 | options.video.set('views', videoData.views) | 290 | video.views = videoData.views |
287 | 291 | ||
288 | await options.video.save(sequelizeOptions) | 292 | await video.save(sequelizeOptions) |
289 | 293 | ||
290 | if (thumbnailModel) if (thumbnailModel) await options.video.addAndSaveThumbnail(thumbnailModel, t) | 294 | if (thumbnailModel) await video.addAndSaveThumbnail(thumbnailModel, t) |
291 | 295 | ||
292 | // FIXME: use icon URL instead | 296 | // FIXME: use icon URL instead |
293 | const previewUrl = buildRemoteBaseUrl(options.video, join(STATIC_PATHS.PREVIEWS, options.video.getPreview().filename)) | 297 | const previewUrl = buildRemoteBaseUrl(video, join(STATIC_PATHS.PREVIEWS, video.getPreview().filename)) |
294 | const previewModel = createPlaceholderThumbnail(previewUrl, options.video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | 298 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) |
295 | await options.video.addAndSaveThumbnail(previewModel, t) | 299 | await video.addAndSaveThumbnail(previewModel, t) |
296 | 300 | ||
297 | { | 301 | { |
298 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(options.video, options.videoObject) | 302 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(video, videoObject) |
299 | const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a)) | 303 | const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a)) |
300 | 304 | ||
301 | // Remove video files that do not exist anymore | 305 | // Remove video files that do not exist anymore |
302 | const destroyTasks = options.video.VideoFiles | 306 | const destroyTasks = video.VideoFiles |
303 | .filter(f => !newVideoFiles.find(newFile => newFile.hasSameUniqueKeysThan(f))) | 307 | .filter(f => !newVideoFiles.find(newFile => newFile.hasSameUniqueKeysThan(f))) |
304 | .map(f => f.destroy(sequelizeOptions)) | 308 | .map(f => f.destroy(sequelizeOptions)) |
305 | await Promise.all(destroyTasks) | 309 | await Promise.all(destroyTasks) |
306 | 310 | ||
307 | // Update or add other one | 311 | // Update or add other one |
@@ -310,21 +314,17 @@ async function updateVideoFromAP (options: { | |||
310 | .then(([ file ]) => file) | 314 | .then(([ file ]) => file) |
311 | }) | 315 | }) |
312 | 316 | ||
313 | options.video.VideoFiles = await Promise.all(upsertTasks) | 317 | video.VideoFiles = await Promise.all(upsertTasks) |
314 | } | 318 | } |
315 | 319 | ||
316 | { | 320 | { |
317 | const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes( | 321 | const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(video, videoObject, video.VideoFiles) |
318 | options.video, | ||
319 | options.videoObject, | ||
320 | options.video.VideoFiles | ||
321 | ) | ||
322 | const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a)) | 322 | const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a)) |
323 | 323 | ||
324 | // Remove video files that do not exist anymore | 324 | // Remove video files that do not exist anymore |
325 | const destroyTasks = options.video.VideoStreamingPlaylists | 325 | const destroyTasks = video.VideoStreamingPlaylists |
326 | .filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f))) | 326 | .filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f))) |
327 | .map(f => f.destroy(sequelizeOptions)) | 327 | .map(f => f.destroy(sequelizeOptions)) |
328 | await Promise.all(destroyTasks) | 328 | await Promise.all(destroyTasks) |
329 | 329 | ||
330 | // Update or add other one | 330 | // Update or add other one |
@@ -333,36 +333,36 @@ async function updateVideoFromAP (options: { | |||
333 | .then(([ streamingPlaylist ]) => streamingPlaylist) | 333 | .then(([ streamingPlaylist ]) => streamingPlaylist) |
334 | }) | 334 | }) |
335 | 335 | ||
336 | options.video.VideoStreamingPlaylists = await Promise.all(upsertTasks) | 336 | video.VideoStreamingPlaylists = await Promise.all(upsertTasks) |
337 | } | 337 | } |
338 | 338 | ||
339 | { | 339 | { |
340 | // Update Tags | 340 | // Update Tags |
341 | const tags = options.videoObject.tag.map(tag => tag.name) | 341 | const tags = videoObject.tag.map(tag => tag.name) |
342 | const tagInstances = await TagModel.findOrCreateTags(tags, t) | 342 | const tagInstances = await TagModel.findOrCreateTags(tags, t) |
343 | await options.video.$set('Tags', tagInstances, sequelizeOptions) | 343 | await video.$set('Tags', tagInstances, sequelizeOptions) |
344 | } | 344 | } |
345 | 345 | ||
346 | { | 346 | { |
347 | // Update captions | 347 | // Update captions |
348 | await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(options.video.id, t) | 348 | await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(video.id, t) |
349 | 349 | ||
350 | const videoCaptionsPromises = options.videoObject.subtitleLanguage.map(c => { | 350 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { |
351 | return VideoCaptionModel.insertOrReplaceLanguage(options.video.id, c.identifier, t) | 351 | return VideoCaptionModel.insertOrReplaceLanguage(video.id, c.identifier, t) |
352 | }) | 352 | }) |
353 | options.video.VideoCaptions = await Promise.all(videoCaptionsPromises) | 353 | video.VideoCaptions = await Promise.all(videoCaptionsPromises) |
354 | } | 354 | } |
355 | }) | 355 | }) |
356 | 356 | ||
357 | // Notify our users? | 357 | const autoBlacklisted = await autoBlacklistVideoIfNeeded(video, undefined, undefined) |
358 | if (wasPrivateVideo || wasUnlistedVideo) { | 358 | |
359 | Notifier.Instance.notifyOnNewVideo(options.video) | 359 | if (autoBlacklisted) Notifier.Instance.notifyOnVideoAutoBlacklist(video) |
360 | } | 360 | else if (!wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideo(video) // Notify our users? |
361 | 361 | ||
362 | logger.info('Remote video with uuid %s updated', options.videoObject.uuid) | 362 | logger.info('Remote video with uuid %s updated', videoObject.uuid) |
363 | } catch (err) { | 363 | } catch (err) { |
364 | if (options.video !== undefined && videoFieldsSave !== undefined) { | 364 | if (video !== undefined && videoFieldsSave !== undefined) { |
365 | resetSequelizeInstance(options.video, videoFieldsSave) | 365 | resetSequelizeInstance(video, videoFieldsSave) |
366 | } | 366 | } |
367 | 367 | ||
368 | // This is just a debug because we will retry the insert | 368 | // This is just a debug because we will retry the insert |
@@ -379,7 +379,9 @@ async function refreshVideoIfNeeded (options: { | |||
379 | if (!options.video.isOutdated()) return options.video | 379 | if (!options.video.isOutdated()) return options.video |
380 | 380 | ||
381 | // We need more attributes if the argument video was fetched with not enough joints | 381 | // We need more attributes if the argument video was fetched with not enough joints |
382 | const video = options.fetchedType === 'all' ? options.video : await VideoModel.loadByUrlAndPopulateAccount(options.video.url) | 382 | const video = options.fetchedType === 'all' |
383 | ? options.video | ||
384 | : await VideoModel.loadByUrlAndPopulateAccount(options.video.url) | ||
383 | 385 | ||
384 | try { | 386 | try { |
385 | const { response, videoObject } = await fetchRemoteVideo(video.url) | 387 | const { response, videoObject } = await fetchRemoteVideo(video.url) |
diff --git a/server/lib/moderation.ts b/server/lib/moderation.ts new file mode 100644 index 000000000..b609f4585 --- /dev/null +++ b/server/lib/moderation.ts | |||
@@ -0,0 +1,64 @@ | |||
1 | import { VideoModel } from '../models/video/video' | ||
2 | import { VideoCommentModel } from '../models/video/video-comment' | ||
3 | import { VideoCommentCreate } from '../../shared/models/videos/video-comment.model' | ||
4 | import { VideoCreate } from '../../shared/models/videos' | ||
5 | import { UserModel } from '../models/account/user' | ||
6 | import { VideoTorrentObject } from '../../shared/models/activitypub/objects' | ||
7 | import { ActivityCreate } from '../../shared/models/activitypub' | ||
8 | import { ActorModel } from '../models/activitypub/actor' | ||
9 | import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object' | ||
10 | |||
11 | export type AcceptResult = { | ||
12 | accepted: boolean | ||
13 | errorMessage?: string | ||
14 | } | ||
15 | |||
16 | // Can be filtered by plugins | ||
17 | function isLocalVideoAccepted (object: { | ||
18 | videoBody: VideoCreate, | ||
19 | videoFile: Express.Multer.File & { duration?: number }, | ||
20 | user: UserModel | ||
21 | }): AcceptResult { | ||
22 | return { accepted: true } | ||
23 | } | ||
24 | |||
25 | function isLocalVideoThreadAccepted (_object: { | ||
26 | commentBody: VideoCommentCreate, | ||
27 | video: VideoModel, | ||
28 | user: UserModel | ||
29 | }): AcceptResult { | ||
30 | return { accepted: true } | ||
31 | } | ||
32 | |||
33 | function isLocalVideoCommentReplyAccepted (_object: { | ||
34 | commentBody: VideoCommentCreate, | ||
35 | parentComment: VideoCommentModel, | ||
36 | video: VideoModel, | ||
37 | user: UserModel | ||
38 | }): AcceptResult { | ||
39 | return { accepted: true } | ||
40 | } | ||
41 | |||
42 | function isRemoteVideoAccepted (_object: { | ||
43 | activity: ActivityCreate, | ||
44 | videoAP: VideoTorrentObject, | ||
45 | byActor: ActorModel | ||
46 | }): AcceptResult { | ||
47 | return { accepted: true } | ||
48 | } | ||
49 | |||
50 | function isRemoteVideoCommentAccepted (_object: { | ||
51 | activity: ActivityCreate, | ||
52 | commentAP: VideoCommentObject, | ||
53 | byActor: ActorModel | ||
54 | }): AcceptResult { | ||
55 | return { accepted: true } | ||
56 | } | ||
57 | |||
58 | export { | ||
59 | isLocalVideoAccepted, | ||
60 | isLocalVideoThreadAccepted, | ||
61 | isRemoteVideoAccepted, | ||
62 | isRemoteVideoCommentAccepted, | ||
63 | isLocalVideoCommentReplyAccepted | ||
64 | } | ||
diff --git a/server/lib/plugins/hooks.ts b/server/lib/plugins/hooks.ts new file mode 100644 index 000000000..7bb907e6a --- /dev/null +++ b/server/lib/plugins/hooks.ts | |||
@@ -0,0 +1,26 @@ | |||
1 | import { ServerActionHookName, ServerFilterHookName } from '../../../shared/models/plugins/server-hook.model' | ||
2 | import { PluginManager } from './plugin-manager' | ||
3 | import { logger } from '../../helpers/logger' | ||
4 | import * as Bluebird from 'bluebird' | ||
5 | |||
6 | // Helpers to run hooks | ||
7 | const Hooks = { | ||
8 | wrapObject: <T, U extends ServerFilterHookName>(obj: T, hookName: U) => { | ||
9 | return PluginManager.Instance.runHook(hookName, obj) as Promise<T> | ||
10 | }, | ||
11 | |||
12 | wrapPromise: async <T, U extends ServerFilterHookName>(fun: Promise<T> | Bluebird<T>, hookName: U) => { | ||
13 | const result = await fun | ||
14 | |||
15 | return PluginManager.Instance.runHook(hookName, result) | ||
16 | }, | ||
17 | |||
18 | runAction: <T, U extends ServerActionHookName>(hookName: U, params?: T) => { | ||
19 | PluginManager.Instance.runHook(hookName, params) | ||
20 | .catch(err => logger.error('Fatal hook error.', { err })) | ||
21 | } | ||
22 | } | ||
23 | |||
24 | export { | ||
25 | Hooks | ||
26 | } | ||
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index 570b56193..85ee3decb 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts | |||
@@ -14,6 +14,10 @@ import { RegisterSettingOptions } from '../../../shared/models/plugins/register- | |||
14 | import { RegisterHookOptions } from '../../../shared/models/plugins/register-hook.model' | 14 | import { RegisterHookOptions } from '../../../shared/models/plugins/register-hook.model' |
15 | import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model' | 15 | import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model' |
16 | import { PluginStorageManager } from '../../../shared/models/plugins/plugin-storage-manager.model' | 16 | import { PluginStorageManager } from '../../../shared/models/plugins/plugin-storage-manager.model' |
17 | import { ServerHookName, ServerHook } from '../../../shared/models/plugins/server-hook.model' | ||
18 | import { isCatchable, isPromise } from '../../../shared/core-utils/miscs/miscs' | ||
19 | import { getHookType, internalRunHook } from '../../../shared/core-utils/plugins/hooks' | ||
20 | import { HookType } from '../../../shared/models/plugins/hook-type.enum' | ||
17 | 21 | ||
18 | export interface RegisteredPlugin { | 22 | export interface RegisteredPlugin { |
19 | npmName: string | 23 | npmName: string |
@@ -42,7 +46,7 @@ export interface HookInformationValue { | |||
42 | priority: number | 46 | priority: number |
43 | } | 47 | } |
44 | 48 | ||
45 | export class PluginManager { | 49 | export class PluginManager implements ServerHook { |
46 | 50 | ||
47 | private static instance: PluginManager | 51 | private static instance: PluginManager |
48 | 52 | ||
@@ -95,25 +99,17 @@ export class PluginManager { | |||
95 | 99 | ||
96 | // ###################### Hooks ###################### | 100 | // ###################### Hooks ###################### |
97 | 101 | ||
98 | async runHook (hookName: string, param?: any) { | 102 | async runHook (hookName: ServerHookName, param?: any) { |
99 | let result = param | 103 | let result = param |
100 | 104 | ||
101 | if (!this.hooks[hookName]) return result | 105 | if (!this.hooks[hookName]) return result |
102 | 106 | ||
103 | const wait = hookName.startsWith('static:') | 107 | const hookType = getHookType(hookName) |
104 | 108 | ||
105 | for (const hook of this.hooks[hookName]) { | 109 | for (const hook of this.hooks[hookName]) { |
106 | try { | 110 | result = await internalRunHook(hook.handler, hookType, param, err => { |
107 | const p = hook.handler(param) | ||
108 | |||
109 | if (wait) { | ||
110 | result = await p | ||
111 | } else if (p.catch) { | ||
112 | p.catch(err => logger.warn('Hook %s of plugin %s thrown an error.', hookName, hook.pluginName, { err })) | ||
113 | } | ||
114 | } catch (err) { | ||
115 | logger.error('Cannot run hook %s of plugin %s.', hookName, hook.pluginName, { err }) | 111 | logger.error('Cannot run hook %s of plugin %s.', hookName, hook.pluginName, { err }) |
116 | } | 112 | }) |
117 | } | 113 | } |
118 | 114 | ||
119 | return result | 115 | return result |
diff --git a/server/lib/video-blacklist.ts b/server/lib/video-blacklist.ts index 985b89e31..32b1a28fa 100644 --- a/server/lib/video-blacklist.ts +++ b/server/lib/video-blacklist.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import * as sequelize from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { CONFIG } from '../initializers/config' | 2 | import { CONFIG } from '../initializers/config' |
3 | import { UserRight, VideoBlacklistType } from '../../shared/models' | 3 | import { UserRight, VideoBlacklistType } from '../../shared/models' |
4 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | 4 | import { VideoBlacklistModel } from '../models/video/video-blacklist' |
@@ -6,26 +6,39 @@ import { UserModel } from '../models/account/user' | |||
6 | import { VideoModel } from '../models/video/video' | 6 | import { VideoModel } from '../models/video/video' |
7 | import { logger } from '../helpers/logger' | 7 | import { logger } from '../helpers/logger' |
8 | import { UserAdminFlag } from '../../shared/models/users/user-flag.model' | 8 | import { UserAdminFlag } from '../../shared/models/users/user-flag.model' |
9 | import { Hooks } from './plugins/hooks' | ||
9 | 10 | ||
10 | async function autoBlacklistVideoIfNeeded (video: VideoModel, user: UserModel, transaction: sequelize.Transaction) { | 11 | async function autoBlacklistVideoIfNeeded (video: VideoModel, user?: UserModel, transaction?: Transaction) { |
11 | if (!CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED) return false | 12 | const doAutoBlacklist = await Hooks.wrapPromise( |
13 | autoBlacklistNeeded({ video, user }), | ||
14 | 'filter:video.auto-blacklist.result' | ||
15 | ) | ||
12 | 16 | ||
13 | if (user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) || user.hasAdminFlag(UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST)) return false | 17 | if (!doAutoBlacklist) return false |
14 | 18 | ||
15 | const sequelizeOptions = { transaction } | ||
16 | const videoBlacklistToCreate = { | 19 | const videoBlacklistToCreate = { |
17 | videoId: video.id, | 20 | videoId: video.id, |
18 | unfederated: true, | 21 | unfederated: true, |
19 | reason: 'Auto-blacklisted. Moderator review required.', | 22 | reason: 'Auto-blacklisted. Moderator review required.', |
20 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED | 23 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED |
21 | } | 24 | } |
22 | await VideoBlacklistModel.create(videoBlacklistToCreate, sequelizeOptions) | 25 | await VideoBlacklistModel.create(videoBlacklistToCreate, { transaction }) |
23 | 26 | ||
24 | logger.info('Video %s auto-blacklisted.', video.uuid) | 27 | logger.info('Video %s auto-blacklisted.', video.uuid) |
25 | 28 | ||
26 | return true | 29 | return true |
27 | } | 30 | } |
28 | 31 | ||
32 | async function autoBlacklistNeeded (parameters: { video: VideoModel, user?: UserModel }) { | ||
33 | const { user } = parameters | ||
34 | |||
35 | if (!CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED || !user) return false | ||
36 | |||
37 | if (user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) || user.hasAdminFlag(UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST)) return false | ||
38 | |||
39 | return true | ||
40 | } | ||
41 | |||
29 | // --------------------------------------------------------------------------- | 42 | // --------------------------------------------------------------------------- |
30 | 43 | ||
31 | export { | 44 | export { |