Add new abuses tests

pull/2962/head
Chocobozzz 2020-07-08 15:51:46 +02:00 committed by Chocobozzz
parent 811cef146c
commit 310b5219b3
29 changed files with 869 additions and 386 deletions

View File

@ -138,8 +138,8 @@
<tr>
<td colspan="6">
<div class="no-results">
<ng-container *ngIf="search" i18n>No video abuses found matching current filters.</ng-container>
<ng-container *ngIf="!search" i18n>No video abuses found.</ng-container>
<ng-container *ngIf="search" i18n>No abuses found matching current filters.</ng-container>
<ng-container *ngIf="!search" i18n>No abuses found.</ng-container>
</div>
</td>
</tr>

View File

@ -118,7 +118,7 @@ export class UserNotification implements UserNotificationServer {
this.commentUrl = [ this.buildVideoUrl(this.comment.video), { threadId: this.comment.threadId } ]
break
case UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS:
case UserNotificationType.NEW_ABUSE_FOR_MODERATORS:
this.abuseUrl = '/admin/moderation/abuses/list'
if (this.abuse.video) this.videoUrl = this.buildVideoUrl(this.abuse.video)

View File

@ -42,7 +42,7 @@
</div>
</ng-container>
<ng-container *ngSwitchCase="UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS">
<ng-container *ngSwitchCase="UserNotificationType.NEW_ABUSE_FOR_MODERATORS">
<my-global-icon iconName="flag" aria-hidden="true"></my-global-icon>
<div class="message" i18n>

View File

@ -140,7 +140,6 @@ export class VideoReportComponent extends FormReactive implements OnInit {
const { hasStart, startAt, hasEnd, endAt } = this.form.get('timestamp').value
this.abuseService.reportVideo({
accountId: this.video.account.id,
reason,
predefinedReasons,
video: {

View File

@ -100,7 +100,7 @@ async function updateAbuse (req: express.Request, res: express.Response) {
return abuse.save({ transaction: t })
})
// Do not send the delete to other instances, we updated OUR copy of this video abuse
// Do not send the delete to other instances, we updated OUR copy of this abuse
return res.type('json').status(204).end()
}
@ -112,7 +112,7 @@ async function deleteAbuse (req: express.Request, res: express.Response) {
return abuse.destroy({ transaction: t })
})
// Do not send the delete to other instances, we delete OUR copy of this video abuse
// Do not send the delete to other instances, we delete OUR copy of this abuse
return res.type('json').status(204).end()
}

View File

@ -50,7 +50,5 @@ async function removeUserHistory (req: express.Request, res: express.Response) {
return UserVideoHistoryModel.removeUserHistoryBefore(user, beforeDate, t)
})
// Do not send the delete to other instances, we delete OUR copy of this video abuse
return res.type('json').status(204).end()
}

View File

@ -3,10 +3,10 @@ import { AbuseFilter, abusePredefinedReasonsMap, AbusePredefinedReasonsString, A
import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
import { exists, isArray } from './misc'
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES
const ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES
function isAbuseReasonValid (value: string) {
return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON)
return exists(value) && validator.isLength(value, ABUSES_CONSTRAINTS_FIELDS.REASON)
}
function isAbusePredefinedReasonValid (value: AbusePredefinedReasonsString) {
@ -32,7 +32,7 @@ function isAbuseTimestampCoherent (endAt: number, { req }) {
}
function isAbuseModerationCommentValid (value: string) {
return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.MODERATION_COMMENT)
return exists(value) && validator.isLength(value, ABUSES_CONSTRAINTS_FIELDS.MODERATION_COMMENT)
}
function isAbuseStateValid (value: string) {

View File

@ -68,7 +68,7 @@ async function doesVideoCommentExist (idArg: number | string, video: MVideoId, r
async function doesCommentIdExist (idArg: number | string, res: express.Response) {
const id = parseInt(idArg + '', 10)
const videoComment = await VideoCommentModel.loadById(id)
const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
if (!videoComment) {
res.status(404)
@ -77,7 +77,7 @@ async function doesCommentIdExist (idArg: number | string, res: express.Response
return false
}
res.locals.videoComment = videoComment
res.locals.videoCommentFull = videoComment
return true
}

View File

@ -30,7 +30,7 @@ async function doesAbuseExist (abuseId: number | string, res: Response) {
if (!abuse) {
res.status(404)
.json({ error: 'Video abuse not found' })
.json({ error: 'Abuse not found' })
return false
}

View File

@ -43,12 +43,10 @@ async function up (utils: {
await utils.sequelize.query(`
CREATE TABLE IF NOT EXISTS "commentAbuse" (
"id" serial,
"deletedComment" jsonb DEFAULT NULL,
"abuseId" integer NOT NULL REFERENCES "abuse" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
"videoCommentId" integer REFERENCES "videoComment" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
"createdAt" timestamp WITH time zone NOT NULL,
"updatedAt" timestamp WITH time zone NOT NULL,
"commentId" integer REFERENCES "videoComment" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
PRIMARY KEY ("id")
);
`)

View File

@ -325,6 +325,7 @@ class Emailer {
subject: `New comment abuse report from ${reporter}`,
locals: {
commentUrl,
videoName: comment.Video.name,
isLocal: comment.isOwned(),
commentCreatedAt: new Date(comment.createdAt).toLocaleString(),
reason: abuse.reason,

View File

@ -7,7 +7,8 @@ block title
block content
p
| #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}comment "
a(href=commentUrl) of #{flaggedAccount}
a(href=commentUrl) on video #{videoName}
| of #{flaggedAccount}
| created on #{commentCreatedAt}
p The reporter, #{reporter}, cited the following reason(s):

View File

@ -371,7 +371,7 @@ class Notifier {
async function notificationCreator (user: MUserWithNotificationSetting) {
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS,
type: UserNotificationType.NEW_ABUSE_FOR_MODERATORS,
userId: user.id,
abuseId: abuse.id
})

View File

@ -128,7 +128,7 @@ const abuseListValidator = [
.custom(exists).withMessage('Should have a valid search'),
query('state')
.optional()
.custom(isAbuseStateValid).withMessage('Should have a valid video abuse state'),
.custom(isAbuseStateValid).withMessage('Should have a valid abuse state'),
query('videoIs')
.optional()
.custom(isAbuseVideoIsValid).withMessage('Should have a valid "video is" attribute'),

View File

@ -362,8 +362,8 @@ export class AbuseModel extends Model<AbuseModel> {
const countReportsForReporter = this.get('countReportsForReporter') as number
const countReportsForReportee = this.get('countReportsForReportee') as number
let video: VideoAbuse
let comment: VideoCommentAbuse
let video: VideoAbuse = null
let comment: VideoCommentAbuse = null
if (this.VideoAbuse) {
const abuseModel = this.VideoAbuse
@ -391,13 +391,13 @@ export class AbuseModel extends Model<AbuseModel> {
if (this.VideoCommentAbuse) {
const abuseModel = this.VideoCommentAbuse
const entity = abuseModel.VideoComment || abuseModel.deletedComment
const entity = abuseModel.VideoComment
comment = {
id: entity.id,
text: entity.text,
text: entity.text ?? '',
deleted: !abuseModel.VideoComment,
deleted: entity.isDeleted(),
video: {
id: entity.Video.id,

View File

@ -1,5 +1,4 @@
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
import { VideoComment } from '@shared/models'
import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
import { VideoCommentModel } from '../video/video-comment'
import { AbuseModel } from './abuse'
@ -22,11 +21,6 @@ export class VideoCommentAbuseModel extends Model<VideoCommentAbuseModel> {
@UpdatedAt
updatedAt: Date
@AllowNull(true)
@Default(null)
@Column(DataType.JSONB)
deletedComment: VideoComment & { Video: { name: string, id: number, uuid: string }}
@ForeignKey(() => AbuseModel)
@Column
abuseId: number

View File

@ -109,7 +109,7 @@ function buildAccountInclude (required: boolean, withActor = false) {
required: true,
include: [
{
attributes: [ 'uuid' ],
attributes: [ 'id', 'name', 'uuid' ],
model: VideoModel.unscoped(),
required: true
}
@ -492,6 +492,8 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(),
video: {
id: abuse.VideoCommentAbuse.VideoComment.Video.id,
name: abuse.VideoCommentAbuse.VideoComment.Video.name,
uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid
}
} : undefined

View File

@ -3,7 +3,6 @@ import { uniq } from 'lodash'
import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize'
import {
AllowNull,
BeforeDestroy,
BelongsTo,
Column,
CreatedAt,
@ -16,7 +15,6 @@ import {
Table,
UpdatedAt
} from 'sequelize-typescript'
import { logger } from '@server/helpers/logger'
import { getServerActor } from '@server/models/application/application'
import { MAccount, MAccountId, MUserAccountId } from '@server/types/models'
import { VideoPrivacy } from '@shared/models'
@ -242,51 +240,13 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
@HasMany(() => VideoCommentAbuseModel, {
foreignKey: {
name: 'commentId',
name: 'videoCommentId',
allowNull: true
},
onDelete: 'set null'
})
CommentAbuses: VideoCommentAbuseModel[]
@BeforeDestroy
static async saveEssentialDataToAbuses (instance: VideoCommentModel, options) {
const tasks: Promise<any>[] = []
if (!Array.isArray(instance.CommentAbuses)) {
instance.CommentAbuses = await instance.$get('CommentAbuses')
if (instance.CommentAbuses.length === 0) return undefined
}
if (!instance.Video) {
instance.Video = await instance.$get('Video')
}
logger.info('Saving video comment %s for abuse.', instance.url)
const details = Object.assign(instance.toFormattedJSON(), {
Video: {
id: instance.Video.id,
name: instance.Video.name,
uuid: instance.Video.uuid
}
})
for (const abuse of instance.CommentAbuses) {
abuse.deletedComment = details
tasks.push(abuse.save({ transaction: options.transaction }))
}
Promise.all(tasks)
.catch(err => {
logger.error('Some errors when saving details of comment %s in its abuses before destroy hook.', instance.url, { err })
})
return undefined
}
static loadById (id: number, t?: Transaction): Bluebird<MComment> {
const query: FindOptions = {
where: {

View File

@ -21,9 +21,7 @@ import {
checkBadStartPagination
} from '../../../../shared/extra-utils/requests/check-api-params'
// FIXME: deprecated in 2.3. Remove this controller
describe('Test video abuses API validators', function () {
describe('Test abuses API validators', function () {
const basePath = '/api/v1/abuses/'
let server: ServerInfo

File diff suppressed because it is too large Load Diff

View File

@ -3,10 +3,16 @@
import 'mocha'
import { v4 as uuidv4 } from 'uuid'
import {
addVideoCommentThread,
addVideoToBlacklist,
cleanupTests,
createUser,
follow,
generateUserAccessToken,
getAccount,
getCustomConfig,
getVideoCommentThreads,
getVideoIdFromUUID,
immutableAssign,
MockInstancesIndex,
registerUser,
@ -23,7 +29,9 @@ import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
import {
checkAutoInstanceFollowing,
CheckerBaseParams,
checkNewAccountAbuseForModerators,
checkNewBlacklistOnMyVideo,
checkNewCommentAbuseForModerators,
checkNewInstanceFollower,
checkNewVideoAbuseForModerators,
checkNewVideoFromSubscription,
@ -91,11 +99,74 @@ describe('Test moderation notifications', function () {
await waitJobs(servers)
await reportAbuse({ url: servers[1].url, token: servers[1].accessToken, videoId: video.id, reason: 'super reason' })
const videoId = await getVideoIdFromUUID(servers[1].url, video.uuid)
await reportAbuse({ url: servers[1].url, token: servers[1].accessToken, videoId, reason: 'super reason' })
await waitJobs(servers)
await checkNewVideoAbuseForModerators(baseParams, video.uuid, name, 'presence')
})
it('Should send a notification to moderators on local comment abuse', async function () {
this.timeout(10000)
const name = 'video for abuse ' + uuidv4()
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
const video = resVideo.body.video
const resComment = await addVideoCommentThread(servers[0].url, userAccessToken, video.id, 'comment abuse ' + uuidv4())
const comment = resComment.body.comment
await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, commentId: comment.id, reason: 'super reason' })
await waitJobs(servers)
await checkNewCommentAbuseForModerators(baseParams, video.uuid, name, 'presence')
})
it('Should send a notification to moderators on remote comment abuse', async function () {
this.timeout(10000)
const name = 'video for abuse ' + uuidv4()
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
const video = resVideo.body.video
await addVideoCommentThread(servers[0].url, userAccessToken, video.id, 'comment abuse ' + uuidv4())
await waitJobs(servers)
const resComments = await getVideoCommentThreads(servers[1].url, video.uuid, 0, 5)
const commentId = resComments.body.data[0].id
await reportAbuse({ url: servers[1].url, token: servers[1].accessToken, commentId, reason: 'super reason' })
await waitJobs(servers)
await checkNewCommentAbuseForModerators(baseParams, video.uuid, name, 'presence')
})
it('Should send a notification to moderators on local account abuse', async function () {
this.timeout(10000)
const username = 'user' + new Date().getTime()
const resUser = await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username, password: 'donald' })
const accountId = resUser.body.user.account.id
await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, accountId, reason: 'super reason' })
await waitJobs(servers)
await checkNewAccountAbuseForModerators(baseParams, username, 'presence')
})
it('Should send a notification to moderators on remote account abuse', async function () {
this.timeout(10000)
const username = 'user' + new Date().getTime()
const tmpToken = await generateUserAccessToken(servers[0], username)
await uploadVideo(servers[0].url, tmpToken, { name: 'super video' })
await waitJobs(servers)
const resAccount = await getAccount(servers[1].url, username + '@' + servers[0].host)
await reportAbuse({ url: servers[1].url, token: servers[1].accessToken, accountId: resAccount.body.id, reason: 'super reason' })
await waitJobs(servers)
await checkNewAccountAbuseForModerators(baseParams, username, 'presence')
})
})
describe('Video blacklist on my video', function () {

View File

@ -180,7 +180,7 @@ describe('Test emails', function () {
})
})
describe('When creating a video abuse', function () {
describe('When creating an abuse', function () {
it('Should send the notification email', async function () {
this.timeout(10000)

View File

@ -53,7 +53,7 @@ export module UserNotificationIncludes {
Pick<VideoCommentAbuseModel, 'id'> &
PickWith<VideoCommentAbuseModel, 'VideoComment',
Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> &
PickWith<VideoCommentModel, 'Video', Pick<VideoModel, 'uuid'>>>
PickWith<VideoCommentModel, 'Video', Pick<VideoModel, 'id' | 'name' | 'uuid'>>>
export type AbuseInclude =
Pick<AbuseModel, 'id'> &

View File

@ -91,7 +91,6 @@ declare module 'express' {
accountVideoRate?: MAccountVideoRateAccountVideo
videoComment?: MComment
videoCommentFull?: MCommentOwnerVideoReply
videoCommentThread?: MComment

View File

@ -57,10 +57,15 @@ function reportAbuse (options: {
function getAbusesList (options: {
url: string
token: string
start?: number
count?: number
sort?: string
id?: number
predefinedReason?: AbusePredefinedReasonsString
search?: string
filter?: AbuseFilter,
filter?: AbuseFilter
state?: AbuseState
videoIs?: AbuseVideoIs
searchReporter?: string
@ -71,6 +76,9 @@ function getAbusesList (options: {
const {
url,
token,
start,
count,
sort,
id,
predefinedReason,
search,
@ -85,13 +93,15 @@ function getAbusesList (options: {
const path = '/api/v1/abuses'
const query = {
sort: 'createdAt',
id,
predefinedReason,
search,
state,
filter,
videoIs,
start,
count,
sort: sort || 'createdAt',
searchReporter,
searchReportee,
searchVideo,

View File

@ -37,8 +37,8 @@ interface ServerInfo {
video?: {
id: number
uuid: string
name: string
account: {
name?: string
account?: {
name: string
}
}

View File

@ -139,13 +139,17 @@ async function checkNotification (
}
function checkVideo (video: any, videoName?: string, videoUUID?: string) {
expect(video.name).to.be.a('string')
expect(video.name).to.not.be.empty
if (videoName) expect(video.name).to.equal(videoName)
if (videoName) {
expect(video.name).to.be.a('string')
expect(video.name).to.not.be.empty
expect(video.name).to.equal(videoName)
}
expect(video.uuid).to.be.a('string')
expect(video.uuid).to.not.be.empty
if (videoUUID) expect(video.uuid).to.equal(videoUUID)
if (videoUUID) {
expect(video.uuid).to.be.a('string')
expect(video.uuid).to.not.be.empty
expect(video.uuid).to.equal(videoUUID)
}
expect(video.id).to.be.a('number')
}
@ -436,7 +440,7 @@ async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string,
}
async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
const notificationType = UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS
const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
function notificationChecker (notification: UserNotification, type: CheckerType) {
if (type === 'presence') {
@ -460,6 +464,56 @@ async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUU
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
}
async function checkNewCommentAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
function notificationChecker (notification: UserNotification, type: CheckerType) {
if (type === 'presence') {
expect(notification).to.not.be.undefined
expect(notification.type).to.equal(notificationType)
expect(notification.abuse.id).to.be.a('number')
checkVideo(notification.abuse.comment.video, videoName, videoUUID)
} else {
expect(notification).to.satisfy((n: UserNotification) => {
return n === undefined || n.abuse === undefined || n.abuse.comment.video.uuid !== videoUUID
})
}
}
function emailNotificationFinder (email: object) {
const text = email['text']
return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
}
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
}
async function checkNewAccountAbuseForModerators (base: CheckerBaseParams, displayName: string, type: CheckerType) {
const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
function notificationChecker (notification: UserNotification, type: CheckerType) {
if (type === 'presence') {
expect(notification).to.not.be.undefined
expect(notification.type).to.equal(notificationType)
expect(notification.abuse.id).to.be.a('number')
expect(notification.abuse.account.displayName).to.equal(displayName)
} else {
expect(notification).to.satisfy((n: UserNotification) => {
return n === undefined || n.abuse === undefined || n.abuse.account.displayName !== displayName
})
}
}
function emailNotificationFinder (email: object) {
const text = email['text']
return text.indexOf(displayName) !== -1 && text.indexOf('abuse') !== -1
}
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
}
async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS
@ -541,6 +595,9 @@ async function prepareNotificationsTest (serversCount = 3) {
smtp: {
hostname: 'localhost',
port
},
signup: {
limit: 20
}
}
const servers = await flushAndRunMultipleServers(serversCount, overrideConfig)
@ -623,5 +680,7 @@ export {
markAsReadNotifications,
getLastNotification,
checkNewInstanceFollower,
prepareNotificationsTest
prepareNotificationsTest,
checkNewCommentAbuseForModerators,
checkNewAccountAbuseForModerators
}

View File

@ -3,7 +3,7 @@ import { FollowState } from '../actors'
export enum UserNotificationType {
NEW_VIDEO_FROM_SUBSCRIPTION = 1,
NEW_COMMENT_ON_MY_VIDEO = 2,
NEW_VIDEO_ABUSE_FOR_MODERATORS = 3,
NEW_ABUSE_FOR_MODERATORS = 3,
BLACKLIST_ON_MY_VIDEO = 4,
UNBLACKLIST_ON_MY_VIDEO = 5,

View File

@ -106,9 +106,9 @@ tags:
Managing plugins installed from a local path or from NPM, or search for new ones.
externalDocs:
url: https://docs.joinpeertube.org/#/api-plugins
- name: Video Abuses
- name: Abuses
description: |
Video abuses deal with reports of local or remote videos alike.
Abuses deal with reports of local or remote videos/comments/accounts alike.
- name: Video
description: |
Operations dealing with listing, uploading, fetching or modifying videos.
@ -166,7 +166,7 @@ x-tagGroups:
- Search
- name: Moderation
tags:
- Video Abuses
- Abuses
- Video Blocks
- Account Blocks
- Server Blocks
@ -1474,13 +1474,13 @@ paths:
/videos/abuse:
get:
deprecated: true
summary: List video abuses
summary: List abuses
security:
- OAuth2:
- admin
- moderator
tags:
- Video Abuses
- Abuses
parameters:
- name: id
in: query
@ -1508,7 +1508,7 @@ paths:
type: string
- name: state
in: query
description: 'The video playlist privacy (Pending = `1`, Rejected = `2`, Accepted = `3`)'
description: 'The abuse state (Pending = `1`, Rejected = `2`, Accepted = `3`)'
schema:
type: integer
enum:
@ -1554,7 +1554,7 @@ paths:
security:
- OAuth2: []
tags:
- Video Abuses
- Abuses
- Videos
parameters:
- $ref: '#/components/parameters/idOrUUID'
@ -1607,7 +1607,7 @@ paths:
- admin
- moderator
tags:
- Video Abuses
- Abuses
parameters:
- $ref: '#/components/parameters/idOrUUID'
- $ref: '#/components/parameters/abuseId'
@ -1626,11 +1626,11 @@ paths:
'204':
description: successful operation
'404':
description: video abuse not found
description: abuse not found
delete:
deprecated: true
tags:
- Video Abuses
- Abuses
summary: Delete an abuse
security:
- OAuth2:
@ -3320,7 +3320,7 @@ components:
name: abuseId
in: path
required: true
description: Video abuse id
description: Abuse id
schema:
type: integer
captionLanguage:
@ -5098,7 +5098,7 @@ components:
- `2` NEW_COMMENT_ON_MY_VIDEO
- `3` NEW_VIDEO_ABUSE_FOR_MODERATORS
- `3` NEW_ABUSE_FOR_MODERATORS
- `4` BLACKLIST_ON_MY_VIDEO