aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/users.ts6
-rw-r--r--server/controllers/api/videos/blacklist.ts56
-rw-r--r--server/helpers/custom-validators/video-blacklist.ts32
-rw-r--r--server/initializers/constants.ts7
-rw-r--r--server/initializers/migrations/0255-video-blacklist-reason.ts25
-rw-r--r--server/lib/emailer.ts49
-rw-r--r--server/middlewares/validators/video-blacklist.ts55
-rw-r--r--server/models/video/video-blacklist.ts59
-rw-r--r--server/models/video/video.ts30
-rw-r--r--server/tests/api/check-params/video-blacklist.ts92
-rw-r--r--server/tests/api/server/email.ts51
-rw-r--r--server/tests/api/videos/video-blacklist-management.ts78
-rw-r--r--server/tests/utils/videos/video-blacklist.ts16
13 files changed, 452 insertions, 104 deletions
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index 0e2be7123..543b20baa 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -201,14 +201,14 @@ async function getUserVideos (req: express.Request, res: express.Response, next:
201 user.Account.id, 201 user.Account.id,
202 req.query.start as number, 202 req.query.start as number,
203 req.query.count as number, 203 req.query.count as number,
204 req.query.sort as VideoSortField, 204 req.query.sort as VideoSortField
205 false // Display my NSFW videos
206 ) 205 )
207 206
208 const additionalAttributes = { 207 const additionalAttributes = {
209 waitTranscoding: true, 208 waitTranscoding: true,
210 state: true, 209 state: true,
211 scheduledUpdate: true 210 scheduledUpdate: true,
211 blacklistInfo: true
212 } 212 }
213 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes })) 213 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes }))
214} 214}
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts
index 8112b59b8..358f339ed 100644
--- a/server/controllers/api/videos/blacklist.ts
+++ b/server/controllers/api/videos/blacklist.ts
@@ -1,12 +1,21 @@
1import * as express from 'express' 1import * as express from 'express'
2import { BlacklistedVideo, UserRight } from '../../../../shared' 2import { BlacklistedVideo, UserRight, VideoBlacklistCreate } from '../../../../shared'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { getFormattedObjects } from '../../../helpers/utils' 4import { getFormattedObjects } from '../../../helpers/utils'
5import { 5import {
6 asyncMiddleware, authenticate, blacklistSortValidator, ensureUserHasRight, paginationValidator, setBlacklistSort, setDefaultPagination, 6 asyncMiddleware,
7 videosBlacklistAddValidator, videosBlacklistRemoveValidator 7 authenticate,
8 blacklistSortValidator,
9 ensureUserHasRight,
10 paginationValidator,
11 setBlacklistSort,
12 setDefaultPagination,
13 videosBlacklistAddValidator,
14 videosBlacklistRemoveValidator,
15 videosBlacklistUpdateValidator
8} from '../../../middlewares' 16} from '../../../middlewares'
9import { VideoBlacklistModel } from '../../../models/video/video-blacklist' 17import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
18import { sequelizeTypescript } from '../../../initializers'
10 19
11const blacklistRouter = express.Router() 20const blacklistRouter = express.Router()
12 21
@@ -27,6 +36,13 @@ blacklistRouter.get('/blacklist',
27 asyncMiddleware(listBlacklist) 36 asyncMiddleware(listBlacklist)
28) 37)
29 38
39blacklistRouter.put('/:videoId/blacklist',
40 authenticate,
41 ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST),
42 asyncMiddleware(videosBlacklistUpdateValidator),
43 asyncMiddleware(updateVideoBlacklistController)
44)
45
30blacklistRouter.delete('/:videoId/blacklist', 46blacklistRouter.delete('/:videoId/blacklist',
31 authenticate, 47 authenticate,
32 ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST), 48 ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST),
@@ -42,17 +58,32 @@ export {
42 58
43// --------------------------------------------------------------------------- 59// ---------------------------------------------------------------------------
44 60
45async function addVideoToBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) { 61async function addVideoToBlacklist (req: express.Request, res: express.Response) {
46 const videoInstance = res.locals.video 62 const videoInstance = res.locals.video
63 const body: VideoBlacklistCreate = req.body
47 64
48 const toCreate = { 65 const toCreate = {
49 videoId: videoInstance.id 66 videoId: videoInstance.id,
67 reason: body.reason
50 } 68 }
51 69
52 await VideoBlacklistModel.create(toCreate) 70 await VideoBlacklistModel.create(toCreate)
53 return res.type('json').status(204).end() 71 return res.type('json').status(204).end()
54} 72}
55 73
74async function updateVideoBlacklistController (req: express.Request, res: express.Response) {
75 const videoBlacklist = res.locals.videoBlacklist as VideoBlacklistModel
76 logger.info(videoBlacklist)
77
78 if (req.body.reason !== undefined) videoBlacklist.reason = req.body.reason
79
80 await sequelizeTypescript.transaction(t => {
81 return videoBlacklist.save({ transaction: t })
82 })
83
84 return res.type('json').status(204).end()
85}
86
56async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) { 87async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
57 const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort) 88 const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort)
58 89
@@ -60,16 +91,13 @@ async function listBlacklist (req: express.Request, res: express.Response, next:
60} 91}
61 92
62async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) { 93async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
63 const blacklistedVideo = res.locals.blacklistedVideo as VideoBlacklistModel 94 const videoBlacklist = res.locals.videoBlacklist as VideoBlacklistModel
64 95
65 try { 96 await sequelizeTypescript.transaction(t => {
66 await blacklistedVideo.destroy() 97 return videoBlacklist.destroy({ transaction: t })
98 })
67 99
68 logger.info('Video %s removed from blacklist.', res.locals.video.uuid) 100 logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
69 101
70 return res.sendStatus(204) 102 return res.type('json').status(204).end()
71 } catch (err) {
72 logger.error('Some error while removing video %s from blacklist.', res.locals.video.uuid, { err })
73 throw err
74 }
75} 103}
diff --git a/server/helpers/custom-validators/video-blacklist.ts b/server/helpers/custom-validators/video-blacklist.ts
new file mode 100644
index 000000000..b36b08d8b
--- /dev/null
+++ b/server/helpers/custom-validators/video-blacklist.ts
@@ -0,0 +1,32 @@
1import { Response } from 'express'
2import * as validator from 'validator'
3import { CONSTRAINTS_FIELDS } from '../../initializers'
4import { VideoBlacklistModel } from '../../models/video/video-blacklist'
5
6const VIDEO_BLACKLIST_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_BLACKLIST
7
8function isVideoBlacklistReasonValid (value: string) {
9 return value === null || validator.isLength(value, VIDEO_BLACKLIST_CONSTRAINTS_FIELDS.REASON)
10}
11
12async function isVideoBlacklistExist (videoId: number, res: Response) {
13 const videoBlacklist = await VideoBlacklistModel.loadByVideoId(videoId)
14
15 if (videoBlacklist === null) {
16 res.status(404)
17 .json({ error: 'Blacklisted video not found' })
18 .end()
19
20 return false
21 }
22
23 res.locals.videoBlacklist = videoBlacklist
24 return true
25}
26
27// ---------------------------------------------------------------------------
28
29export {
30 isVideoBlacklistReasonValid,
31 isVideoBlacklistExist
32}
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index a008bf4c5..ff8e64330 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -15,7 +15,7 @@ let config: IConfig = require('config')
15 15
16// --------------------------------------------------------------------------- 16// ---------------------------------------------------------------------------
17 17
18const LAST_MIGRATION_VERSION = 250 18const LAST_MIGRATION_VERSION = 255
19 19
20// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
21 21
@@ -34,7 +34,7 @@ const SORTABLE_COLUMNS = {
34 USERS: [ 'id', 'username', 'createdAt' ], 34 USERS: [ 'id', 'username', 'createdAt' ],
35 ACCOUNTS: [ 'createdAt' ], 35 ACCOUNTS: [ 'createdAt' ],
36 JOBS: [ 'createdAt' ], 36 JOBS: [ 'createdAt' ],
37 VIDEO_ABUSES: [ 'id', 'createdAt' ], 37 VIDEO_ABUSES: [ 'id', 'createdAt', 'state' ],
38 VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], 38 VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
39 VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes' ], 39 VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes' ],
40 VIDEO_IMPORTS: [ 'createdAt' ], 40 VIDEO_IMPORTS: [ 'createdAt' ],
@@ -261,6 +261,9 @@ const CONSTRAINTS_FIELDS = {
261 REASON: { min: 2, max: 300 }, // Length 261 REASON: { min: 2, max: 300 }, // Length
262 MODERATION_COMMENT: { min: 2, max: 300 } // Length 262 MODERATION_COMMENT: { min: 2, max: 300 } // Length
263 }, 263 },
264 VIDEO_BLACKLIST: {
265 REASON: { min: 2, max: 300 } // Length
266 },
264 VIDEO_CHANNELS: { 267 VIDEO_CHANNELS: {
265 NAME: { min: 3, max: 120 }, // Length 268 NAME: { min: 3, max: 120 }, // Length
266 DESCRIPTION: { min: 3, max: 500 }, // Length 269 DESCRIPTION: { min: 3, max: 500 }, // Length
diff --git a/server/initializers/migrations/0255-video-blacklist-reason.ts b/server/initializers/migrations/0255-video-blacklist-reason.ts
new file mode 100644
index 000000000..a380e620e
--- /dev/null
+++ b/server/initializers/migrations/0255-video-blacklist-reason.ts
@@ -0,0 +1,25 @@
1import * as Sequelize from 'sequelize'
2import { CONSTRAINTS_FIELDS } from '../constants'
3import { VideoAbuseState } from '../../../shared/models/videos'
4
5async function up (utils: {
6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize
9}): Promise<any> {
10
11 {
12 const data = {
13 type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEO_BLACKLIST.REASON.max),
14 allowNull: true,
15 defaultValue: null
16 }
17 await utils.queryInterface.addColumn('videoBlacklist', 'reason', data)
18 }
19}
20
21function down (options) {
22 throw new Error('Not implemented.')
23}
24
25export { up, down }
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index 3faeffd77..a1212878f 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -108,6 +108,55 @@ class Emailer {
108 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 108 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
109 } 109 }
110 110
111 async addVideoBlacklistReportJob (videoId: number, reason?: string) {
112 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
113 if (!video) throw new Error('Unknown Video id during Blacklist report.')
114 // It's not our user
115 if (video.remote === true) return
116
117 const user = await UserModel.loadById(video.VideoChannel.Account.userId)
118
119 const reasonString = reason ? ` for the following reason: ${reason}` : ''
120 const blockedString = `Your video ${video.name} on ${CONFIG.WEBSERVER.HOST} has been blacklisted${reasonString}.`
121
122 const text = 'Hi,\n\n' +
123 blockedString +
124 '\n\n' +
125 'Cheers,\n' +
126 `PeerTube.`
127
128 const to = user.email
129 const emailPayload: EmailPayload = {
130 to: [ to ],
131 subject: `[PeerTube] Video ${video.name} blacklisted`,
132 text
133 }
134
135 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
136 }
137
138 async addVideoUnblacklistReportJob (videoId: number) {
139 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
140 if (!video) throw new Error('Unknown Video id during Blacklist report.')
141
142 const user = await UserModel.loadById(video.VideoChannel.Account.userId)
143
144 const text = 'Hi,\n\n' +
145 `Your video ${video.name} on ${CONFIG.WEBSERVER.HOST} has been unblacklisted.` +
146 '\n\n' +
147 'Cheers,\n' +
148 `PeerTube.`
149
150 const to = user.email
151 const emailPayload: EmailPayload = {
152 to: [ to ],
153 subject: `[PeerTube] Video ${video.name} unblacklisted`,
154 text
155 }
156
157 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
158 }
159
111 addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) { 160 addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) {
112 const reasonString = reason ? ` for the following reason: ${reason}` : '' 161 const reasonString = reason ? ` for the following reason: ${reason}` : ''
113 const blockedWord = blocked ? 'blocked' : 'unblocked' 162 const blockedWord = blocked ? 'blocked' : 'unblocked'
diff --git a/server/middlewares/validators/video-blacklist.ts b/server/middlewares/validators/video-blacklist.ts
index 3c1ef1b4e..95a2b9f17 100644
--- a/server/middlewares/validators/video-blacklist.ts
+++ b/server/middlewares/validators/video-blacklist.ts
@@ -1,11 +1,10 @@
1import * as express from 'express' 1import * as express from 'express'
2import { param } from 'express-validator/check' 2import { body, param } from 'express-validator/check'
3import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' 3import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
4import { isVideoExist } from '../../helpers/custom-validators/videos' 4import { isVideoExist } from '../../helpers/custom-validators/videos'
5import { logger } from '../../helpers/logger' 5import { logger } from '../../helpers/logger'
6import { VideoModel } from '../../models/video/video'
7import { VideoBlacklistModel } from '../../models/video/video-blacklist'
8import { areValidationErrors } from './utils' 6import { areValidationErrors } from './utils'
7import { isVideoBlacklistExist, isVideoBlacklistReasonValid } from '../../helpers/custom-validators/video-blacklist'
9 8
10const videosBlacklistRemoveValidator = [ 9const videosBlacklistRemoveValidator = [
11 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 10 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
@@ -15,7 +14,7 @@ const videosBlacklistRemoveValidator = [
15 14
16 if (areValidationErrors(req, res)) return 15 if (areValidationErrors(req, res)) return
17 if (!await isVideoExist(req.params.videoId, res)) return 16 if (!await isVideoExist(req.params.videoId, res)) return
18 if (!await checkVideoIsBlacklisted(res.locals.video, res)) return 17 if (!await isVideoBlacklistExist(res.locals.video.id, res)) return
19 18
20 return next() 19 return next()
21 } 20 }
@@ -23,47 +22,41 @@ const videosBlacklistRemoveValidator = [
23 22
24const videosBlacklistAddValidator = [ 23const videosBlacklistAddValidator = [
25 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 24 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
25 body('reason')
26 .optional()
27 .custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'),
26 28
27 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 29 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
28 logger.debug('Checking videosBlacklist parameters', { parameters: req.params }) 30 logger.debug('Checking videosBlacklistAdd parameters', { parameters: req.params })
29 31
30 if (areValidationErrors(req, res)) return 32 if (areValidationErrors(req, res)) return
31 if (!await isVideoExist(req.params.videoId, res)) return 33 if (!await isVideoExist(req.params.videoId, res)) return
32 if (!checkVideoIsBlacklistable(res.locals.video, res)) return
33 34
34 return next() 35 return next()
35 } 36 }
36] 37]
37 38
38// --------------------------------------------------------------------------- 39const videosBlacklistUpdateValidator = [
40 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
41 body('reason')
42 .optional()
43 .custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'),
39 44
40export { 45 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
41 videosBlacklistAddValidator, 46 logger.debug('Checking videosBlacklistUpdate parameters', { parameters: req.params })
42 videosBlacklistRemoveValidator
43}
44// ---------------------------------------------------------------------------
45 47
46function checkVideoIsBlacklistable (video: VideoModel, res: express.Response) { 48 if (areValidationErrors(req, res)) return
47 if (video.isOwned() === true) { 49 if (!await isVideoExist(req.params.videoId, res)) return
48 res.status(403) 50 if (!await isVideoBlacklistExist(res.locals.video.id, res)) return
49 .json({ error: 'Cannot blacklist a local video' })
50 .end()
51 51
52 return false 52 return next()
53 } 53 }
54]
54 55
55 return true 56// ---------------------------------------------------------------------------
56}
57
58async function checkVideoIsBlacklisted (video: VideoModel, res: express.Response) {
59 const blacklistedVideo = await VideoBlacklistModel.loadByVideoId(video.id)
60 if (!blacklistedVideo) {
61 res.status(404)
62 .send('Blacklisted video not found')
63
64 return false
65 }
66 57
67 res.locals.blacklistedVideo = blacklistedVideo 58export {
68 return true 59 videosBlacklistAddValidator,
60 videosBlacklistRemoveValidator,
61 videosBlacklistUpdateValidator
69} 62}
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts
index 26167174a..1b8a338cb 100644
--- a/server/models/video/video-blacklist.ts
+++ b/server/models/video/video-blacklist.ts
@@ -1,7 +1,23 @@
1import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import {
2 AfterCreate,
3 AfterDestroy,
4 AllowNull,
5 BelongsTo,
6 Column,
7 CreatedAt, DataType,
8 ForeignKey,
9 Is,
10 Model,
11 Table,
12 UpdatedAt
13} from 'sequelize-typescript'
2import { SortType } from '../../helpers/utils' 14import { SortType } from '../../helpers/utils'
3import { getSortOnModel } from '../utils' 15import { getSortOnModel, throwIfNotValid } from '../utils'
4import { VideoModel } from './video' 16import { VideoModel } from './video'
17import { isVideoBlacklistReasonValid } from '../../helpers/custom-validators/video-blacklist'
18import { Emailer } from '../../lib/emailer'
19import { BlacklistedVideo } from '../../../shared/models/videos'
20import { CONSTRAINTS_FIELDS } from '../../initializers'
5 21
6@Table({ 22@Table({
7 tableName: 'videoBlacklist', 23 tableName: 'videoBlacklist',
@@ -14,6 +30,11 @@ import { VideoModel } from './video'
14}) 30})
15export class VideoBlacklistModel extends Model<VideoBlacklistModel> { 31export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
16 32
33 @AllowNull(true)
34 @Is('VideoBlacklistReason', value => throwIfNotValid(value, isVideoBlacklistReasonValid, 'reason'))
35 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_BLACKLIST.REASON.max))
36 reason: string
37
17 @CreatedAt 38 @CreatedAt
18 createdAt: Date 39 createdAt: Date
19 40
@@ -32,6 +53,16 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
32 }) 53 })
33 Video: VideoModel 54 Video: VideoModel
34 55
56 @AfterCreate
57 static sendBlacklistEmailNotification (instance: VideoBlacklistModel) {
58 return Emailer.Instance.addVideoBlacklistReportJob(instance.videoId, instance.reason)
59 }
60
61 @AfterDestroy
62 static sendUnblacklistEmailNotification (instance: VideoBlacklistModel) {
63 return Emailer.Instance.addVideoUnblacklistReportJob(instance.videoId)
64 }
65
35 static listForApi (start: number, count: number, sort: SortType) { 66 static listForApi (start: number, count: number, sort: SortType) {
36 const query = { 67 const query = {
37 offset: start, 68 offset: start,
@@ -59,22 +90,26 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
59 return VideoBlacklistModel.findOne(query) 90 return VideoBlacklistModel.findOne(query)
60 } 91 }
61 92
62 toFormattedJSON () { 93 toFormattedJSON (): BlacklistedVideo {
63 const video = this.Video 94 const video = this.Video
64 95
65 return { 96 return {
66 id: this.id, 97 id: this.id,
67 videoId: this.videoId,
68 createdAt: this.createdAt, 98 createdAt: this.createdAt,
69 updatedAt: this.updatedAt, 99 updatedAt: this.updatedAt,
70 name: video.name, 100 reason: this.reason,
71 uuid: video.uuid, 101
72 description: video.description, 102 video: {
73 duration: video.duration, 103 id: video.id,
74 views: video.views, 104 name: video.name,
75 likes: video.likes, 105 uuid: video.uuid,
76 dislikes: video.dislikes, 106 description: video.description,
77 nsfw: video.nsfw 107 duration: video.duration,
108 views: video.views,
109 likes: video.likes,
110 dislikes: video.dislikes,
111 nsfw: video.nsfw
112 }
78 } 113 }
79 } 114 }
80} 115}
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 39fe21007..f3a900bc9 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -93,6 +93,7 @@ import { VideoShareModel } from './video-share'
93import { VideoTagModel } from './video-tag' 93import { VideoTagModel } from './video-tag'
94import { ScheduleVideoUpdateModel } from './schedule-video-update' 94import { ScheduleVideoUpdateModel } from './schedule-video-update'
95import { VideoCaptionModel } from './video-caption' 95import { VideoCaptionModel } from './video-caption'
96import { VideoBlacklistModel } from './video-blacklist'
96 97
97// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation 98// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
98const indexes: Sequelize.DefineIndexesOptions[] = [ 99const indexes: Sequelize.DefineIndexesOptions[] = [
@@ -581,6 +582,15 @@ export class VideoModel extends Model<VideoModel> {
581 }) 582 })
582 ScheduleVideoUpdate: ScheduleVideoUpdateModel 583 ScheduleVideoUpdate: ScheduleVideoUpdateModel
583 584
585 @HasOne(() => VideoBlacklistModel, {
586 foreignKey: {
587 name: 'videoId',
588 allowNull: false
589 },
590 onDelete: 'cascade'
591 })
592 VideoBlacklist: VideoBlacklistModel
593
584 @HasMany(() => VideoCaptionModel, { 594 @HasMany(() => VideoCaptionModel, {
585 foreignKey: { 595 foreignKey: {
586 name: 'videoId', 596 name: 'videoId',
@@ -755,7 +765,7 @@ export class VideoModel extends Model<VideoModel> {
755 }) 765 })
756 } 766 }
757 767
758 static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, hideNSFW: boolean, withFiles = false) { 768 static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) {
759 const query: IFindOptions<VideoModel> = { 769 const query: IFindOptions<VideoModel> = {
760 offset: start, 770 offset: start,
761 limit: count, 771 limit: count,
@@ -777,6 +787,10 @@ export class VideoModel extends Model<VideoModel> {
777 { 787 {
778 model: ScheduleVideoUpdateModel, 788 model: ScheduleVideoUpdateModel,
779 required: false 789 required: false
790 },
791 {
792 model: VideoBlacklistModel,
793 required: false
780 } 794 }
781 ] 795 ]
782 } 796 }
@@ -788,12 +802,6 @@ export class VideoModel extends Model<VideoModel> {
788 }) 802 })
789 } 803 }
790 804
791 if (hideNSFW === true) {
792 query.where = {
793 nsfw: false
794 }
795 }
796
797 return VideoModel.findAndCountAll(query).then(({ rows, count }) => { 805 return VideoModel.findAndCountAll(query).then(({ rows, count }) => {
798 return { 806 return {
799 data: rows, 807 data: rows,
@@ -1177,7 +1185,8 @@ export class VideoModel extends Model<VideoModel> {
1177 additionalAttributes: { 1185 additionalAttributes: {
1178 state?: boolean, 1186 state?: boolean,
1179 waitTranscoding?: boolean, 1187 waitTranscoding?: boolean,
1180 scheduledUpdate?: boolean 1188 scheduledUpdate?: boolean,
1189 blacklistInfo?: boolean
1181 } 1190 }
1182 }): Video { 1191 }): Video {
1183 const formattedAccount = this.VideoChannel.Account.toFormattedJSON() 1192 const formattedAccount = this.VideoChannel.Account.toFormattedJSON()
@@ -1254,6 +1263,11 @@ export class VideoModel extends Model<VideoModel> {
1254 privacy: this.ScheduleVideoUpdate.privacy || undefined 1263 privacy: this.ScheduleVideoUpdate.privacy || undefined
1255 } 1264 }
1256 } 1265 }
1266
1267 if (options.additionalAttributes.blacklistInfo === true) {
1268 videoObject.blacklisted = !!this.VideoBlacklist
1269 videoObject.blacklistedReason = this.VideoBlacklist ? this.VideoBlacklist.reason : null
1270 }
1257 } 1271 }
1258 1272
1259 return videoObject 1273 return videoObject
diff --git a/server/tests/api/check-params/video-blacklist.ts b/server/tests/api/check-params/video-blacklist.ts
index 6cd13d23f..415474718 100644
--- a/server/tests/api/check-params/video-blacklist.ts
+++ b/server/tests/api/check-params/video-blacklist.ts
@@ -3,13 +3,24 @@
3import 'mocha' 3import 'mocha'
4 4
5import { 5import {
6 createUser, flushTests, getBlacklistedVideosList, killallServers, makePostBodyRequest, removeVideoFromBlacklist, runServer, 6 createUser,
7 ServerInfo, setAccessTokensToServers, uploadVideo, userLogin 7 flushTests,
8 getBlacklistedVideosList,
9 killallServers,
10 makePostBodyRequest,
11 makePutBodyRequest,
12 removeVideoFromBlacklist,
13 runServer,
14 ServerInfo,
15 setAccessTokensToServers,
16 uploadVideo,
17 userLogin
8} from '../../utils' 18} from '../../utils'
9import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' 19import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
10 20
11describe('Test video blacklist API validators', function () { 21describe('Test video blacklist API validators', function () {
12 let server: ServerInfo 22 let server: ServerInfo
23 let notBlacklistedVideoId: number
13 let userAccessToken = '' 24 let userAccessToken = ''
14 25
15 // --------------------------------------------------------------- 26 // ---------------------------------------------------------------
@@ -28,8 +39,15 @@ describe('Test video blacklist API validators', function () {
28 await createUser(server.url, server.accessToken, username, password) 39 await createUser(server.url, server.accessToken, username, password)
29 userAccessToken = await userLogin(server, { username, password }) 40 userAccessToken = await userLogin(server, { username, password })
30 41
31 const res = await uploadVideo(server.url, server.accessToken, {}) 42 {
32 server.video = res.body.video 43 const res = await uploadVideo(server.url, server.accessToken, {})
44 server.video = res.body.video
45 }
46
47 {
48 const res = await uploadVideo(server.url, server.accessToken, {})
49 notBlacklistedVideoId = res.body.video.uuid
50 }
33 }) 51 })
34 52
35 describe('When adding a video in blacklist', function () { 53 describe('When adding a video in blacklist', function () {
@@ -59,20 +77,70 @@ describe('Test video blacklist API validators', function () {
59 await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields, statusCodeExpected: 403 }) 77 await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields, statusCodeExpected: 403 })
60 }) 78 })
61 79
62 it('Should fail with a local video', async function () { 80 it('Should fail with an invalid reason', async function () {
63 const path = basePath + server.video.id + '/blacklist' 81 const path = basePath + server.video.uuid + '/blacklist'
82 const fields = { reason: 'a'.repeat(305) }
83
84 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
85 })
86
87 it('Should succeed with the correct params', async function () {
88 const path = basePath + server.video.uuid + '/blacklist'
89 const fields = { }
90
91 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 204 })
92 })
93 })
94
95 describe('When updating a video in blacklist', function () {
96 const basePath = '/api/v1/videos/'
97
98 it('Should fail with a wrong video', async function () {
99 const wrongPath = '/api/v1/videos/blabla/blacklist'
100 const fields = {}
101 await makePutBodyRequest({ url: server.url, path: wrongPath, token: server.accessToken, fields })
102 })
103
104 it('Should fail with a video not blacklisted', async function () {
105 const path = '/api/v1/videos/' + notBlacklistedVideoId + '/blacklist'
64 const fields = {} 106 const fields = {}
65 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 403 }) 107 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 404 })
108 })
109
110 it('Should fail with a non authenticated user', async function () {
111 const path = basePath + server.video + '/blacklist'
112 const fields = {}
113 await makePutBodyRequest({ url: server.url, path, token: 'hello', fields, statusCodeExpected: 401 })
114 })
115
116 it('Should fail with a non admin user', async function () {
117 const path = basePath + server.video + '/blacklist'
118 const fields = {}
119 await makePutBodyRequest({ url: server.url, path, token: userAccessToken, fields, statusCodeExpected: 403 })
120 })
121
122 it('Should fail with an invalid reason', async function () {
123 const path = basePath + server.video.uuid + '/blacklist'
124 const fields = { reason: 'a'.repeat(305) }
125
126 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
127 })
128
129 it('Should succeed with the correct params', async function () {
130 const path = basePath + server.video.uuid + '/blacklist'
131 const fields = { reason: 'hello' }
132
133 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 204 })
66 }) 134 })
67 }) 135 })
68 136
69 describe('When removing a video in blacklist', function () { 137 describe('When removing a video in blacklist', function () {
70 it('Should fail with a non authenticated user', async function () { 138 it('Should fail with a non authenticated user', async function () {
71 await removeVideoFromBlacklist(server.url, 'fake token', server.video.id, 401) 139 await removeVideoFromBlacklist(server.url, 'fake token', server.video.uuid, 401)
72 }) 140 })
73 141
74 it('Should fail with a non admin user', async function () { 142 it('Should fail with a non admin user', async function () {
75 await removeVideoFromBlacklist(server.url, userAccessToken, server.video.id, 403) 143 await removeVideoFromBlacklist(server.url, userAccessToken, server.video.uuid, 403)
76 }) 144 })
77 145
78 it('Should fail with an incorrect id', async function () { 146 it('Should fail with an incorrect id', async function () {
@@ -81,7 +149,11 @@ describe('Test video blacklist API validators', function () {
81 149
82 it('Should fail with a not blacklisted video', async function () { 150 it('Should fail with a not blacklisted video', async function () {
83 // The video was not added to the blacklist so it should fail 151 // The video was not added to the blacklist so it should fail
84 await removeVideoFromBlacklist(server.url, server.accessToken, server.video.id, 404) 152 await removeVideoFromBlacklist(server.url, server.accessToken, notBlacklistedVideoId, 404)
153 })
154
155 it('Should succeed with the correct params', async function () {
156 await removeVideoFromBlacklist(server.url, server.accessToken, server.video.uuid, 204)
85 }) 157 })
86 }) 158 })
87 159
diff --git a/server/tests/api/server/email.ts b/server/tests/api/server/email.ts
index 65d6a759f..db937f288 100644
--- a/server/tests/api/server/email.ts
+++ b/server/tests/api/server/email.ts
@@ -3,9 +3,10 @@
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 addVideoToBlacklist,
6 askResetPassword, 7 askResetPassword,
7 blockUser, 8 blockUser,
8 createUser, 9 createUser, removeVideoFromBlacklist,
9 reportVideoAbuse, 10 reportVideoAbuse,
10 resetPassword, 11 resetPassword,
11 runServer, 12 runServer,
@@ -22,7 +23,9 @@ const expect = chai.expect
22describe('Test emails', function () { 23describe('Test emails', function () {
23 let server: ServerInfo 24 let server: ServerInfo
24 let userId: number 25 let userId: number
26 let userAccessToken: string
25 let videoUUID: string 27 let videoUUID: string
28 let videoUserUUID: string
26 let verificationString: string 29 let verificationString: string
27 const emails: object[] = [] 30 const emails: object[] = []
28 const user = { 31 const user = {
@@ -48,6 +51,16 @@ describe('Test emails', function () {
48 { 51 {
49 const res = await createUser(server.url, server.accessToken, user.username, user.password) 52 const res = await createUser(server.url, server.accessToken, user.username, user.password)
50 userId = res.body.user.id 53 userId = res.body.user.id
54
55 userAccessToken = await userLogin(server, user)
56 }
57
58 {
59 const attributes = {
60 name: 'my super user video'
61 }
62 const res = await uploadVideo(server.url, userAccessToken, attributes)
63 videoUserUUID = res.body.video.uuid
51 } 64 }
52 65
53 { 66 {
@@ -158,6 +171,42 @@ describe('Test emails', function () {
158 }) 171 })
159 }) 172 })
160 173
174 describe('When blacklisting a video', function () {
175 it('Should send the notification email', async function () {
176 this.timeout(10000)
177
178 const reason = 'my super reason'
179 await addVideoToBlacklist(server.url, server.accessToken, videoUserUUID, reason)
180
181 await waitJobs(server)
182 expect(emails).to.have.lengthOf(5)
183
184 const email = emails[4]
185
186 expect(email['from'][0]['address']).equal('test-admin@localhost')
187 expect(email['to'][0]['address']).equal('user_1@example.com')
188 expect(email['subject']).contains(' blacklisted')
189 expect(email['text']).contains('my super user video')
190 expect(email['text']).contains('my super reason')
191 })
192
193 it('Should send the notification email', async function () {
194 this.timeout(10000)
195
196 await removeVideoFromBlacklist(server.url, server.accessToken, videoUserUUID)
197
198 await waitJobs(server)
199 expect(emails).to.have.lengthOf(6)
200
201 const email = emails[5]
202
203 expect(email['from'][0]['address']).equal('test-admin@localhost')
204 expect(email['to'][0]['address']).equal('user_1@example.com')
205 expect(email['subject']).contains(' unblacklisted')
206 expect(email['text']).contains('my super user video')
207 })
208 })
209
161 after(async function () { 210 after(async function () {
162 killallServers([ server ]) 211 killallServers([ server ])
163 }) 212 })
diff --git a/server/tests/api/videos/video-blacklist-management.ts b/server/tests/api/videos/video-blacklist-management.ts
index 4d1a06436..7bf39dc99 100644
--- a/server/tests/api/videos/video-blacklist-management.ts
+++ b/server/tests/api/videos/video-blacklist-management.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expressions */ 1/* tslint:disable:no-unused-expression */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import * as lodash from 'lodash' 4import * as lodash from 'lodash'
@@ -7,29 +7,33 @@ import {
7 addVideoToBlacklist, 7 addVideoToBlacklist,
8 flushAndRunMultipleServers, 8 flushAndRunMultipleServers,
9 getBlacklistedVideosList, 9 getBlacklistedVideosList,
10 getMyVideos,
10 getSortedBlacklistedVideosList, 11 getSortedBlacklistedVideosList,
11 getVideosList, 12 getVideosList,
12 killallServers, 13 killallServers,
13 removeVideoFromBlacklist, 14 removeVideoFromBlacklist,
14 ServerInfo, 15 ServerInfo,
15 setAccessTokensToServers, 16 setAccessTokensToServers,
17 updateVideoBlacklist,
16 uploadVideo 18 uploadVideo
17} from '../../utils/index' 19} from '../../utils/index'
18import { doubleFollow } from '../../utils/server/follows' 20import { doubleFollow } from '../../utils/server/follows'
19import { waitJobs } from '../../utils/server/jobs' 21import { waitJobs } from '../../utils/server/jobs'
22import { VideoAbuse } from '../../../../shared/models/videos'
20 23
21const expect = chai.expect 24const expect = chai.expect
22const orderBy = lodash.orderBy 25const orderBy = lodash.orderBy
23 26
24describe('Test video blacklist management', function () { 27describe('Test video blacklist management', function () {
25 let servers: ServerInfo[] = [] 28 let servers: ServerInfo[] = []
29 let videoId: number
26 30
27 async function blacklistVideosOnServer (server: ServerInfo) { 31 async function blacklistVideosOnServer (server: ServerInfo) {
28 const res = await getVideosList(server.url) 32 const res = await getVideosList(server.url)
29 33
30 const videos = res.body.data 34 const videos = res.body.data
31 for (let video of videos) { 35 for (let video of videos) {
32 await addVideoToBlacklist(server.url, server.accessToken, video.id) 36 await addVideoToBlacklist(server.url, server.accessToken, video.id, 'super reason')
33 } 37 }
34 } 38 }
35 39
@@ -62,53 +66,85 @@ describe('Test video blacklist management', function () {
62 66
63 expect(res.body.total).to.equal(2) 67 expect(res.body.total).to.equal(2)
64 68
65 const videos = res.body.data 69 const blacklistedVideos = res.body.data
66 expect(videos).to.be.an('array') 70 expect(blacklistedVideos).to.be.an('array')
67 expect(videos.length).to.equal(2) 71 expect(blacklistedVideos.length).to.equal(2)
72
73 for (const blacklistedVideo of blacklistedVideos) {
74 expect(blacklistedVideo.reason).to.equal('super reason')
75 videoId = blacklistedVideo.video.id
76 }
68 }) 77 })
69 78
70 it('Should get the correct sort when sorting by descending id', async function () { 79 it('Should get the correct sort when sorting by descending id', async function () {
71 const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-id') 80 const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-id')
72 expect(res.body.total).to.equal(2) 81 expect(res.body.total).to.equal(2)
73 82
74 const videos = res.body.data 83 const blacklistedVideos = res.body.data
75 expect(videos).to.be.an('array') 84 expect(blacklistedVideos).to.be.an('array')
76 expect(videos.length).to.equal(2) 85 expect(blacklistedVideos.length).to.equal(2)
77 86
78 const result = orderBy(res.body.data, [ 'id' ], [ 'desc' ]) 87 const result = orderBy(res.body.data, [ 'id' ], [ 'desc' ])
79 88
80 expect(videos).to.deep.equal(result) 89 expect(blacklistedVideos).to.deep.equal(result)
81 }) 90 })
82 91
83 it('Should get the correct sort when sorting by descending video name', async function () { 92 it('Should get the correct sort when sorting by descending video name', async function () {
84 const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name') 93 const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
85 expect(res.body.total).to.equal(2) 94 expect(res.body.total).to.equal(2)
86 95
87 const videos = res.body.data 96 const blacklistedVideos = res.body.data
88 expect(videos).to.be.an('array') 97 expect(blacklistedVideos).to.be.an('array')
89 expect(videos.length).to.equal(2) 98 expect(blacklistedVideos.length).to.equal(2)
90 99
91 const result = orderBy(res.body.data, [ 'name' ], [ 'desc' ]) 100 const result = orderBy(res.body.data, [ 'name' ], [ 'desc' ])
92 101
93 expect(videos).to.deep.equal(result) 102 expect(blacklistedVideos).to.deep.equal(result)
94 }) 103 })
95 104
96 it('Should get the correct sort when sorting by ascending creation date', async function () { 105 it('Should get the correct sort when sorting by ascending creation date', async function () {
97 const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, 'createdAt') 106 const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, 'createdAt')
98 expect(res.body.total).to.equal(2) 107 expect(res.body.total).to.equal(2)
99 108
100 const videos = res.body.data 109 const blacklistedVideos = res.body.data
101 expect(videos).to.be.an('array') 110 expect(blacklistedVideos).to.be.an('array')
102 expect(videos.length).to.equal(2) 111 expect(blacklistedVideos.length).to.equal(2)
103 112
104 const result = orderBy(res.body.data, [ 'createdAt' ]) 113 const result = orderBy(res.body.data, [ 'createdAt' ])
105 114
106 expect(videos).to.deep.equal(result) 115 expect(blacklistedVideos).to.deep.equal(result)
116 })
117 })
118
119 describe('When updating blacklisted videos', function () {
120 it('Should change the reason', async function () {
121 await updateVideoBlacklist(servers[0].url, servers[0].accessToken, videoId, 'my super reason updated')
122
123 const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
124 const video = res.body.data.find(b => b.video.id === videoId)
125
126 expect(video.reason).to.equal('my super reason updated')
127 })
128 })
129
130 describe('When listing my videos', function () {
131 it('Should display blacklisted videos', async function () {
132 await blacklistVideosOnServer(servers[1])
133
134 const res = await getMyVideos(servers[1].url, servers[1].accessToken, 0, 5)
135
136 expect(res.body.total).to.equal(2)
137 expect(res.body.data).to.have.lengthOf(2)
138
139 for (const video of res.body.data) {
140 expect(video.blacklisted).to.be.true
141 expect(video.blacklistedReason).to.equal('super reason')
142 }
107 }) 143 })
108 }) 144 })
109 145
110 describe('When removing a blacklisted video', function () { 146 describe('When removing a blacklisted video', function () {
111 let videoToRemove 147 let videoToRemove: VideoAbuse
112 let blacklist = [] 148 let blacklist = []
113 149
114 it('Should not have any video in videos list on server 1', async function () { 150 it('Should not have any video in videos list on server 1', async function () {
@@ -125,7 +161,7 @@ describe('Test video blacklist management', function () {
125 blacklist = res.body.data.slice(1) 161 blacklist = res.body.data.slice(1)
126 162
127 // Remove it 163 // Remove it
128 await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, videoToRemove.videoId) 164 await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, videoToRemove.video.id)
129 }) 165 })
130 166
131 it('Should have the ex-blacklisted video in videos list on server 1', async function () { 167 it('Should have the ex-blacklisted video in videos list on server 1', async function () {
@@ -136,8 +172,8 @@ describe('Test video blacklist management', function () {
136 expect(videos).to.be.an('array') 172 expect(videos).to.be.an('array')
137 expect(videos.length).to.equal(1) 173 expect(videos.length).to.equal(1)
138 174
139 expect(videos[0].name).to.equal(videoToRemove.name) 175 expect(videos[0].name).to.equal(videoToRemove.video.name)
140 expect(videos[0].id).to.equal(videoToRemove.videoId) 176 expect(videos[0].id).to.equal(videoToRemove.video.id)
141 }) 177 })
142 178
143 it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () { 179 it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () {
diff --git a/server/tests/utils/videos/video-blacklist.ts b/server/tests/utils/videos/video-blacklist.ts
index aa0d232b6..7819f4b25 100644
--- a/server/tests/utils/videos/video-blacklist.ts
+++ b/server/tests/utils/videos/video-blacklist.ts
@@ -1,15 +1,26 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2 2
3function addVideoToBlacklist (url: string, token: string, videoId: number, specialStatus = 204) { 3function addVideoToBlacklist (url: string, token: string, videoId: number | string, reason?: string, specialStatus = 204) {
4 const path = '/api/v1/videos/' + videoId + '/blacklist' 4 const path = '/api/v1/videos/' + videoId + '/blacklist'
5 5
6 return request(url) 6 return request(url)
7 .post(path) 7 .post(path)
8 .send({ reason })
8 .set('Accept', 'application/json') 9 .set('Accept', 'application/json')
9 .set('Authorization', 'Bearer ' + token) 10 .set('Authorization', 'Bearer ' + token)
10 .expect(specialStatus) 11 .expect(specialStatus)
11} 12}
12 13
14function updateVideoBlacklist (url: string, token: string, videoId: number, reason?: string, specialStatus = 204) {
15 const path = '/api/v1/videos/' + videoId + '/blacklist'
16
17 return request(url)
18 .put(path)
19 .send({ reason })
20 .set('Accept', 'application/json')
21 .set('Authorization', 'Bearer ' + token)
22 .expect(specialStatus)}
23
13function removeVideoFromBlacklist (url: string, token: string, videoId: number | string, specialStatus = 204) { 24function removeVideoFromBlacklist (url: string, token: string, videoId: number | string, specialStatus = 204) {
14 const path = '/api/v1/videos/' + videoId + '/blacklist' 25 const path = '/api/v1/videos/' + videoId + '/blacklist'
15 26
@@ -50,5 +61,6 @@ export {
50 addVideoToBlacklist, 61 addVideoToBlacklist,
51 removeVideoFromBlacklist, 62 removeVideoFromBlacklist,
52 getBlacklistedVideosList, 63 getBlacklistedVideosList,
53 getSortedBlacklistedVideosList 64 getSortedBlacklistedVideosList,
65 updateVideoBlacklist
54} 66}