diff options
author | Josh Morel <morel.josh@hotmail.com> | 2019-04-02 05:26:47 -0400 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-04-02 11:26:47 +0200 |
commit | 7ccddd7b5250bd25a917a6e77e58b87b9484a2a4 (patch) | |
tree | e75dc991369c1768804fefa114eb2a832881087f /server/lib | |
parent | 12fed49ebab0c414713d57ea316b6488ae6bef99 (diff) | |
download | PeerTube-7ccddd7b5250bd25a917a6e77e58b87b9484a2a4.tar.gz PeerTube-7ccddd7b5250bd25a917a6e77e58b87b9484a2a4.tar.zst PeerTube-7ccddd7b5250bd25a917a6e77e58b87b9484a2a4.zip |
add quarantine videos feature (#1637)
* add quarantine videos feature
* increase Notification settings test timeout
to 20000ms. was completing 7000 locally but timing out
after 10000 on travis
* fix quarantine video test issues
-propagate misspelling
-remove skip from server/tests/client.ts
* WIP use blacklist for moderator video approval
instead of video.quarantine boolean
* finish auto-blacklist feature
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/activitypub/videos.ts | 2 | ||||
-rw-r--r-- | server/lib/emailer.ts | 23 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-import.ts | 7 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-transcoding.ts | 12 | ||||
-rw-r--r-- | server/lib/notifier.ts | 63 | ||||
-rw-r--r-- | server/lib/schedulers/update-videos-scheduler.ts | 2 | ||||
-rw-r--r-- | server/lib/user.ts | 1 | ||||
-rw-r--r-- | server/lib/video-blacklist.ts | 31 |
8 files changed, 123 insertions, 18 deletions
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 2c932371b..d935e3f90 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -45,7 +45,7 @@ import { VideoShareModel } from '../../models/video/video-share' | |||
45 | import { VideoCommentModel } from '../../models/video/video-comment' | 45 | import { VideoCommentModel } from '../../models/video/video-comment' |
46 | 46 | ||
47 | async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { | 47 | async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { |
48 | // If the video is not private and published, we federate it | 48 | // If the video is not private and is published, we federate it |
49 | if (video.privacy !== VideoPrivacy.PRIVATE && video.state === VideoState.PUBLISHED) { | 49 | if (video.privacy !== VideoPrivacy.PRIVATE && video.state === VideoState.PUBLISHED) { |
50 | // Fetch more attributes that we will need to serialize in AP object | 50 | // Fetch more attributes that we will need to serialize in AP object |
51 | if (isArray(video.VideoCaptions) === false) { | 51 | if (isArray(video.VideoCaptions) === false) { |
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 04e4b94b6..eec97c27e 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -250,6 +250,29 @@ class Emailer { | |||
250 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 250 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
251 | } | 251 | } |
252 | 252 | ||
253 | addVideoAutoBlacklistModeratorsNotification (to: string[], video: VideoModel) { | ||
254 | const VIDEO_AUTO_BLACKLIST_URL = CONFIG.WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' | ||
255 | const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath() | ||
256 | |||
257 | const text = `Hi,\n\n` + | ||
258 | `A recently added video was auto-blacklisted and requires moderator review before publishing.` + | ||
259 | `\n\n` + | ||
260 | `You can view it and take appropriate action on ${videoUrl}` + | ||
261 | `\n\n` + | ||
262 | `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` + | ||
263 | `\n\n` + | ||
264 | `Cheers,\n` + | ||
265 | `PeerTube.` | ||
266 | |||
267 | const emailPayload: EmailPayload = { | ||
268 | to, | ||
269 | subject: '[PeerTube] An auto-blacklisted video is awaiting review', | ||
270 | text | ||
271 | } | ||
272 | |||
273 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | ||
274 | } | ||
275 | |||
253 | addNewUserRegistrationNotification (to: string[], user: UserModel) { | 276 | addNewUserRegistrationNotification (to: string[], user: UserModel) { |
254 | const text = `Hi,\n\n` + | 277 | const text = `Hi,\n\n` + |
255 | `User ${user.username} just registered on ${CONFIG.WEBSERVER.HOST} PeerTube instance.\n\n` + | 278 | `User ${user.username} just registered on ${CONFIG.WEBSERVER.HOST} PeerTube instance.\n\n` + |
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index d96bfdf43..c5fc1061c 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts | |||
@@ -196,9 +196,14 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
196 | return videoImportUpdated | 196 | return videoImportUpdated |
197 | }) | 197 | }) |
198 | 198 | ||
199 | Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video) | ||
200 | Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true) | 199 | Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true) |
201 | 200 | ||
201 | if (videoImportUpdated.Video.VideoBlacklist) { | ||
202 | Notifier.Instance.notifyOnVideoAutoBlacklist(videoImportUpdated.Video) | ||
203 | } else { | ||
204 | Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video) | ||
205 | } | ||
206 | |||
202 | // Create transcoding jobs? | 207 | // Create transcoding jobs? |
203 | if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { | 208 | if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { |
204 | // Put uuid because we don't have id auto incremented for now | 209 | // Put uuid because we don't have id auto incremented for now |
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index d9dad795e..581ec283e 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts | |||
@@ -85,10 +85,9 @@ async function publishVideoIfNeeded (video: VideoModel, payload?: VideoTranscodi | |||
85 | return { videoDatabase, videoPublished } | 85 | return { videoDatabase, videoPublished } |
86 | }) | 86 | }) |
87 | 87 | ||
88 | // don't notify prior to scheduled video update | 88 | if (videoPublished) { |
89 | if (videoPublished && !videoDatabase.ScheduleVideoUpdate) { | ||
90 | Notifier.Instance.notifyOnNewVideo(videoDatabase) | 89 | Notifier.Instance.notifyOnNewVideo(videoDatabase) |
91 | Notifier.Instance.notifyOnPendingVideoPublished(videoDatabase) | 90 | Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase) |
92 | } | 91 | } |
93 | 92 | ||
94 | await createHlsJobIfEnabled(payload) | 93 | await createHlsJobIfEnabled(payload) |
@@ -146,11 +145,8 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: Video | |||
146 | return { videoDatabase, videoPublished } | 145 | return { videoDatabase, videoPublished } |
147 | }) | 146 | }) |
148 | 147 | ||
149 | // don't notify prior to scheduled video update | 148 | if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) |
150 | if (!videoDatabase.ScheduleVideoUpdate) { | 149 | if (videoPublished) Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase) |
151 | if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) | ||
152 | if (videoPublished) Notifier.Instance.notifyOnPendingVideoPublished(videoDatabase) | ||
153 | } | ||
154 | 150 | ||
155 | await createHlsJobIfEnabled(Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution })) | 151 | await createHlsJobIfEnabled(Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution })) |
156 | } | 152 | } |
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts index 501680f6b..9fe93ec0d 100644 --- a/server/lib/notifier.ts +++ b/server/lib/notifier.ts | |||
@@ -23,19 +23,35 @@ class Notifier { | |||
23 | private constructor () {} | 23 | private constructor () {} |
24 | 24 | ||
25 | notifyOnNewVideo (video: VideoModel): void { | 25 | notifyOnNewVideo (video: VideoModel): void { |
26 | // Only notify on public and published videos | 26 | // Only notify on public and published videos which are not blacklisted |
27 | if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED) return | 27 | if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.VideoBlacklist) return |
28 | 28 | ||
29 | this.notifySubscribersOfNewVideo(video) | 29 | this.notifySubscribersOfNewVideo(video) |
30 | .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) | 30 | .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) |
31 | } | 31 | } |
32 | 32 | ||
33 | notifyOnPendingVideoPublished (video: VideoModel): void { | 33 | notifyOnVideoPublishedAfterTranscoding (video: VideoModel): void { |
34 | // Only notify on public videos that has been published while the user waited transcoding/scheduled update | 34 | // don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update |
35 | if (video.waitTranscoding === false && !video.ScheduleVideoUpdate) return | 35 | if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return |
36 | 36 | ||
37 | this.notifyOwnedVideoHasBeenPublished(video) | 37 | this.notifyOwnedVideoHasBeenPublished(video) |
38 | .catch(err => logger.error('Cannot notify owner that its video %s has been published.', video.url, { err })) | 38 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err })) |
39 | } | ||
40 | |||
41 | notifyOnVideoPublishedAfterScheduledUpdate (video: VideoModel): void { | ||
42 | // don't notify if video is still blacklisted or waiting for transcoding | ||
43 | if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return | ||
44 | |||
45 | this.notifyOwnedVideoHasBeenPublished(video) | ||
46 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err })) | ||
47 | } | ||
48 | |||
49 | notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: VideoModel): void { | ||
50 | // don't notify if video is still waiting for transcoding or scheduled update | ||
51 | if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return | ||
52 | |||
53 | this.notifyOwnedVideoHasBeenPublished(video) | ||
54 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length | ||
39 | } | 55 | } |
40 | 56 | ||
41 | notifyOnNewComment (comment: VideoCommentModel): void { | 57 | notifyOnNewComment (comment: VideoCommentModel): void { |
@@ -51,6 +67,11 @@ class Notifier { | |||
51 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) | 67 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) |
52 | } | 68 | } |
53 | 69 | ||
70 | notifyOnVideoAutoBlacklist (video: VideoModel): void { | ||
71 | this.notifyModeratorsOfVideoAutoBlacklist(video) | ||
72 | .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', video.url, { err })) | ||
73 | } | ||
74 | |||
54 | notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void { | 75 | notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void { |
55 | this.notifyVideoOwnerOfBlacklist(videoBlacklist) | 76 | this.notifyVideoOwnerOfBlacklist(videoBlacklist) |
56 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) | 77 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) |
@@ -58,7 +79,7 @@ class Notifier { | |||
58 | 79 | ||
59 | notifyOnVideoUnblacklist (video: VideoModel): void { | 80 | notifyOnVideoUnblacklist (video: VideoModel): void { |
60 | this.notifyVideoOwnerOfUnblacklist(video) | 81 | this.notifyVideoOwnerOfUnblacklist(video) |
61 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', video.url, { err })) | 82 | .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err })) |
62 | } | 83 | } |
63 | 84 | ||
64 | notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void { | 85 | notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void { |
@@ -268,6 +289,34 @@ class Notifier { | |||
268 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | 289 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) |
269 | } | 290 | } |
270 | 291 | ||
292 | private async notifyModeratorsOfVideoAutoBlacklist (video: VideoModel) { | ||
293 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST) | ||
294 | if (moderators.length === 0) return | ||
295 | |||
296 | logger.info('Notifying %s moderators of video auto-blacklist %s.', moderators.length, video.url) | ||
297 | |||
298 | function settingGetter (user: UserModel) { | ||
299 | return user.NotificationSetting.videoAutoBlacklistAsModerator | ||
300 | } | ||
301 | async function notificationCreator (user: UserModel) { | ||
302 | |||
303 | const notification = await UserNotificationModel.create({ | ||
304 | type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS, | ||
305 | userId: user.id, | ||
306 | videoId: video.id | ||
307 | }) | ||
308 | notification.Video = video | ||
309 | |||
310 | return notification | ||
311 | } | ||
312 | |||
313 | function emailSender (emails: string[]) { | ||
314 | return Emailer.Instance.addVideoAutoBlacklistModeratorsNotification(emails, video) | ||
315 | } | ||
316 | |||
317 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | ||
318 | } | ||
319 | |||
271 | private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) { | 320 | private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) { |
272 | const user = await UserModel.loadByVideoId(videoBlacklist.videoId) | 321 | const user = await UserModel.loadByVideoId(videoBlacklist.videoId) |
273 | if (!user) return | 322 | if (!user) return |
diff --git a/server/lib/schedulers/update-videos-scheduler.ts b/server/lib/schedulers/update-videos-scheduler.ts index 2618a5857..2179a2f26 100644 --- a/server/lib/schedulers/update-videos-scheduler.ts +++ b/server/lib/schedulers/update-videos-scheduler.ts | |||
@@ -57,7 +57,7 @@ export class UpdateVideosScheduler extends AbstractScheduler { | |||
57 | 57 | ||
58 | for (const v of publishedVideos) { | 58 | for (const v of publishedVideos) { |
59 | Notifier.Instance.notifyOnNewVideo(v) | 59 | Notifier.Instance.notifyOnNewVideo(v) |
60 | Notifier.Instance.notifyOnPendingVideoPublished(v) | 60 | Notifier.Instance.notifyOnVideoPublishedAfterScheduledUpdate(v) |
61 | } | 61 | } |
62 | } | 62 | } |
63 | 63 | ||
diff --git a/server/lib/user.ts b/server/lib/user.ts index 02a84f15b..5588b0f76 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts | |||
@@ -106,6 +106,7 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Tr | |||
106 | myVideoImportFinished: UserNotificationSettingValue.WEB, | 106 | myVideoImportFinished: UserNotificationSettingValue.WEB, |
107 | myVideoPublished: UserNotificationSettingValue.WEB, | 107 | myVideoPublished: UserNotificationSettingValue.WEB, |
108 | videoAbuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 108 | videoAbuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
109 | videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | ||
109 | blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 110 | blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
110 | newUserRegistration: UserNotificationSettingValue.WEB, | 111 | newUserRegistration: UserNotificationSettingValue.WEB, |
111 | commentMention: UserNotificationSettingValue.WEB, | 112 | commentMention: UserNotificationSettingValue.WEB, |
diff --git a/server/lib/video-blacklist.ts b/server/lib/video-blacklist.ts new file mode 100644 index 000000000..dc4e0aed9 --- /dev/null +++ b/server/lib/video-blacklist.ts | |||
@@ -0,0 +1,31 @@ | |||
1 | import * as sequelize from 'sequelize' | ||
2 | import { CONFIG } from '../initializers/constants' | ||
3 | import { VideoBlacklistType, UserRight } from '../../shared/models' | ||
4 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | ||
5 | import { UserModel } from '../models/account/user' | ||
6 | import { VideoModel } from '../models/video/video' | ||
7 | import { logger } from '../helpers/logger' | ||
8 | |||
9 | async function autoBlacklistVideoIfNeeded (video: VideoModel, user: UserModel, transaction: sequelize.Transaction) { | ||
10 | if (!CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED) return false | ||
11 | |||
12 | if (user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) return false | ||
13 | |||
14 | const sequelizeOptions = { transaction } | ||
15 | const videoBlacklistToCreate = { | ||
16 | videoId: video.id, | ||
17 | unfederated: true, | ||
18 | reason: 'Auto-blacklisted. Moderator review required.', | ||
19 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED | ||
20 | } | ||
21 | await VideoBlacklistModel.create(videoBlacklistToCreate, sequelizeOptions) | ||
22 | logger.info('Video %s auto-blacklisted.', video.uuid) | ||
23 | |||
24 | return true | ||
25 | } | ||
26 | |||
27 | // --------------------------------------------------------------------------- | ||
28 | |||
29 | export { | ||
30 | autoBlacklistVideoIfNeeded | ||
31 | } | ||