diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html
index 9eeaac74b498..50c94f26c9e1 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html
+++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html
@@ -57,17 +57,17 @@
-
+
- Reported parts
+ Reported part
{{ startAt }} - {{ endAt }}
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts
index ae3a63abdac4..8ea9bcec13a7 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts
+++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts
@@ -30,14 +30,6 @@ export class VideoAbuseDetailsComponent {
}
}
- get predefinedReasons () {
- if (!this.videoAbuse.predefinedReasons) return []
- return this.videoAbuse.predefinedReasons.map(r => ({
- id: r,
- label: this.predefinedReasonsTranslations[r]
- }))
- }
-
get startAt () {
return durationToString(this.videoAbuse.startAt)
}
@@ -46,6 +38,14 @@ export class VideoAbuseDetailsComponent {
return durationToString(this.videoAbuse.endAt)
}
+ predefinedReasons () {
+ if (!this.videoAbuse.predefinedReasons) return []
+ return this.videoAbuse.predefinedReasons.map(r => ({
+ id: r,
+ label: this.predefinedReasonsTranslations[r]
+ }))
+ }
+
switchToDefaultAvatar ($event: Event) {
($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL()
}
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts
index 86ad40dee903..d7f5beef3a4b 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts
+++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts
@@ -11,7 +11,7 @@ import { ModerationCommentModalComponent } from './moderation-comment-modal.comp
import { Video } from '../../../shared/video/video.model'
import { MarkdownService } from '@app/shared/renderer'
import { Actor } from '@app/shared/actor/actor.model'
-import { buildVideoEmbed } from 'src/assets/player/utils'
+import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils'
import { DomSanitizer } from '@angular/platform-browser'
import { BlocklistService } from '@app/shared/blocklist'
import { VideoService } from '@app/shared/video/video.service'
@@ -259,7 +259,15 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
}
getVideoEmbed (videoAbuse: VideoAbuse) {
- return buildVideoEmbed(`${environment.embedUrl}/videos/embed/${videoAbuse.video.uuid}`)
+ return buildVideoEmbed(
+ buildVideoLink({
+ baseUrl: `${environment.embedUrl}/videos/embed/${videoAbuse.video.uuid}`,
+ title: false,
+ warningTitle: false,
+ startTime: videoAbuse.startAt,
+ stopTime: videoAbuse.endAt
+ })
+ )
}
switchToDefaultAvatar ($event: Event) {
diff --git a/client/src/app/shared/video-abuse/index.ts b/client/src/app/shared/video-abuse/index.ts
index 0c3a48d83b70..92cbfb5f9040 100644
--- a/client/src/app/shared/video-abuse/index.ts
+++ b/client/src/app/shared/video-abuse/index.ts
@@ -1,2 +1 @@
export * from './video-abuse.service'
-export * from './video-abuse-predefined-reasons.model'
diff --git a/client/src/app/shared/video-abuse/video-abuse-predefined-reasons.model.ts b/client/src/app/shared/video-abuse/video-abuse-predefined-reasons.model.ts
deleted file mode 100644
index 5969f48cea89..000000000000
--- a/client/src/app/shared/video-abuse/video-abuse-predefined-reasons.model.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-export enum PredefinedReasons {
- violentOrRepulsive = 'violentOrRepulsive',
- hatefulOrAbusive = 'hatefulOrAbusive',
- spamOrMisleading = 'spamOrMisleading',
- privacy = 'privacy',
- rights = 'rights',
- serverRules = 'serverRules',
- thumbnails = 'thumbnails',
- captions = 'captions'
-}
-
-export type VideoAbusePredefinedReasons = {
- [key in PredefinedReasons]?: boolean
-}
diff --git a/client/src/app/shared/video-abuse/video-abuse.service.ts b/client/src/app/shared/video-abuse/video-abuse.service.ts
index 9af19f014956..19f556c34b8a 100644
--- a/client/src/app/shared/video-abuse/video-abuse.service.ts
+++ b/client/src/app/shared/video-abuse/video-abuse.service.ts
@@ -3,10 +3,10 @@ import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { SortMeta } from 'primeng/api'
import { Observable } from 'rxjs'
-import { ResultList, VideoAbuse, VideoAbuseUpdate, VideoAbuseState } from '../../../../../shared'
+import { ResultList, VideoAbuse, VideoAbuseCreate, VideoAbuseState, VideoAbuseUpdate } from '../../../../../shared'
import { environment } from '../../../environments/environment'
import { RestExtractor, RestPagination, RestService } from '../rest'
-import { VideoAbusePredefinedReasons } from './video-abuse-predefined-reasons.model'
+import { omit } from 'lodash-es'
@Injectable()
export class VideoAbuseService {
@@ -59,7 +59,8 @@ export class VideoAbuseService {
try {
const id = parseInt(v, 10)
return id
- } catch {
+ } catch (e) {
+ console.error('Cannot parse predefinedReasonId in search.', e)
return undefined
}
}
@@ -75,11 +76,10 @@ export class VideoAbuseService {
)
}
- reportVideo (parameters: { id: number, reason: string, predefinedReasons?: VideoAbusePredefinedReasons, timestamp: any }) {
+ reportVideo (parameters: { id: number } & VideoAbuseCreate) {
const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + parameters.id + '/abuse'
- delete parameters.id
- const body = { ...parameters }
+ const body = { ...omit(parameters, [ 'id' ]) }
return this.authHttp.post(url, body)
.pipe(
diff --git a/client/src/app/shared/video/modals/video-report.component.ts b/client/src/app/shared/video/modals/video-report.component.ts
index cd9a9bf9d56f..bc21d7caea48 100644
--- a/client/src/app/shared/video/modals/video-report.component.ts
+++ b/client/src/app/shared/video/modals/video-report.component.ts
@@ -6,10 +6,12 @@ import { FormValidatorService } from '@app/shared/forms/form-validators/form-val
import { VideoAbuseValidatorsService } from '@app/shared/forms/form-validators/video-abuse-validators.service'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
-import { VideoAbuseService, VideoAbusePredefinedReasons, PredefinedReasons } from '@app/shared/video-abuse'
+import { VideoAbuseService } from '@app/shared/video-abuse'
import { Video } from '@app/shared/video/video.model'
-import { buildVideoEmbed } from 'src/assets/player/utils'
+import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils'
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
+import { VideoAbusePredefinedReasonsIn } from '@shared/models/videos/abuse/video-abuse-reason.model'
+import { mapValues, pickBy, keys } from 'lodash-es'
@Component({
selector: 'my-video-report',
@@ -22,7 +24,7 @@ export class VideoReportComponent extends FormReactive implements OnInit {
@ViewChild('modal', { static: true }) modal: NgbModal
error: string = null
- predefinedReasons: { id: PredefinedReasons, label: string, description?: string, help?: string }[] = []
+ predefinedReasons: { id: keyof VideoAbusePredefinedReasonsIn, label: string, description?: string, help?: string }[] = []
embedHtml: SafeHtml
private openedModal: NgbModalRef
@@ -57,25 +59,20 @@ export class VideoReportComponent extends FormReactive implements OnInit {
getVideoEmbed () {
return this.sanitizer.bypassSecurityTrustHtml(
- buildVideoEmbed(this.video.embedUrl)
+ buildVideoEmbed(
+ buildVideoLink({
+ baseUrl: this.video.embedUrl,
+ title: false,
+ warningTitle: false
+ })
+ )
)
}
ngOnInit () {
- const predefinedReasons: Required = {
- violentOrRepulsive: null,
- hatefulOrAbusive: null,
- spamOrMisleading: null,
- privacy: null,
- rights: null,
- serverRules: null,
- captions: null,
- thumbnails: null
- }
-
this.buildForm({
reason: this.videoAbuseValidatorsService.VIDEO_ABUSE_REASON,
- predefinedReasons: predefinedReasons as {[key: string]: null},
+ predefinedReasons: mapValues(VideoAbusePredefinedReasonsIn, r => null),
timestamp: {
hasStart: null,
startAt: null,
@@ -86,42 +83,42 @@ export class VideoReportComponent extends FormReactive implements OnInit {
this.predefinedReasons = [
{
- id: PredefinedReasons.violentOrRepulsive,
+ id: 'violentOrRepulsive',
label: this.i18n('Violent or repulsive'),
help: this.i18n('Contains offensive, violent, or coarse language or iconography.')
},
{
- id: PredefinedReasons.hatefulOrAbusive,
+ id: 'hatefulOrAbusive',
label: this.i18n('Hateful or abusive'),
help: this.i18n('Contains abusive, racist or sexist language or iconography.')
},
{
- id: PredefinedReasons.spamOrMisleading,
+ id: 'spamOrMisleading',
label: this.i18n('Spam, ad or false news'),
help: this.i18n('Contains marketing, spam, purposefully deceitful news, or otherwise misleading thumbnail/text/tags. Please provide reputable sources to report hoaxes.')
},
{
- id: PredefinedReasons.privacy,
+ id: 'privacy',
label: this.i18n('Privacy breach or doxxing'),
help: this.i18n('Contains personal information that could be used to track, identify, contact or impersonate someone (e.g. name, address, phone number, email, or credit card details).')
},
{
- id: PredefinedReasons.rights,
+ id: 'rights',
label: this.i18n('Intellectual property violation'),
help: this.i18n('Infringes my intellectual property or copyright, wrt. the regional rules with which the server must comply.')
},
{
- id: PredefinedReasons.serverRules,
+ id: 'serverRules',
label: this.i18n('Breaks server rules'),
description: this.i18n('Anything not included in the above that breaks the terms of service, code of conduct, or general rules in place on the server.')
},
{
- id: PredefinedReasons.thumbnails,
+ id: 'thumbnails',
label: this.i18n('Thumbnails'),
help: this.i18n('The above can only be seen in thumbnails.')
},
{
- id: PredefinedReasons.captions,
+ id: 'captions',
label: this.i18n('Captions'),
help: this.i18n('The above can only be seen in captions (please describe which).')
}
@@ -140,15 +137,24 @@ export class VideoReportComponent extends FormReactive implements OnInit {
}
report () {
- this.videoAbuseService.reportVideo({ id: this.video.id, ...this.form.value })
- .subscribe(
- () => {
- this.notifier.success(this.i18n('Video reported.'))
- this.hide()
- },
-
- err => this.notifier.error(err.message)
- )
+ const reason = this.form.get('reason').value
+ const predefinedReasons = pickBy(this.form.get('predefinedReasons').value)
+ const { hasStart, startAt, hasEnd, endAt } = this.form.get('timestamp').value
+
+ this.videoAbuseService.reportVideo({
+ id: this.video.id,
+ reason,
+ predefinedReasons,
+ startAt: hasStart && startAt ? startAt : undefined,
+ endAt: hasEnd && endAt ? endAt : undefined
+ }).subscribe(
+ () => {
+ this.notifier.success(this.i18n('Video reported.'))
+ this.hide()
+ },
+
+ err => this.notifier.error(err.message)
+ )
}
isRemoteVideo () {
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts
index c761d3d29c6a..3338e00464bd 100644
--- a/server/controllers/api/videos/abuse.ts
+++ b/server/controllers/api/videos/abuse.ts
@@ -127,9 +127,6 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) {
const videoAbuseInstance = await sequelizeTypescript.transaction(async t => {
reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
const predefinedReasons = keys(pickBy(body.predefinedReasons)).map(r => VideoAbusePredefinedReasonsIn[r])
- const timestamp = body.timestamp
- const startAt = timestamp['hasStart'] && timestamp['startAt'] ? timestamp['startAt'] : undefined
- const endAt = timestamp['hasEnd'] && timestamp['endAt'] ? timestamp['endAt'] : undefined
const abuseToCreate = {
reporterAccountId: reporterAccount.id,
@@ -137,8 +134,8 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) {
videoId: videoInstance.id,
state: VideoAbuseState.PENDING,
predefinedReasons,
- startAt,
- endAt
+ startAt: body.startAt,
+ endAt: body.endAt
}
const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(abuseToCreate, { transaction: t })
diff --git a/server/helpers/custom-validators/video-abuses.ts b/server/helpers/custom-validators/video-abuses.ts
index 672654930542..ffebbca6962c 100644
--- a/server/helpers/custom-validators/video-abuses.ts
+++ b/server/helpers/custom-validators/video-abuses.ts
@@ -14,10 +14,6 @@ function isVideoAbusePredefinedReasonsValid (value: {}) {
return exists(value)
}
-function isVideoAbuseTimestampValid (value: {}) {
- return exists(value)
-}
-
function isVideoAbuseModerationCommentValid (value: string) {
return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.MODERATION_COMMENT)
}
@@ -38,7 +34,6 @@ function isAbuseVideoIsValid (value: VideoAbuseVideoIs) {
export {
isVideoAbuseReasonValid,
isVideoAbusePredefinedReasonsValid,
- isVideoAbuseTimestampValid,
isVideoAbuseModerationCommentValid,
isVideoAbuseStateValid,
isAbuseVideoIsValid
diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts
index fc06cf80a96a..aafb8e914f89 100644
--- a/server/lib/activitypub/process/process-flag.ts
+++ b/server/lib/activitypub/process/process-flag.ts
@@ -38,9 +38,9 @@ async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag,
const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object })
const reporterAccount = await sequelizeTypescript.transaction(async t => AccountModel.load(account.id, t))
- const predefinedReasons = flag.tag?.map(tag => parseInt(tag.name, 10)) || []
+ const predefinedReasons = flag.tag?.map(tag => parseInt(tag.name, 10)).filter(v => !isNaN(v)) || []
const startAt = flag.startAt
- const endAt = flag.startAt
+ const endAt = flag.endAt
const videoAbuseInstance = await sequelizeTypescript.transaction(async t => {
const videoAbuseData = {
diff --git a/server/middlewares/validators/videos/video-abuses.ts b/server/middlewares/validators/videos/video-abuses.ts
index 75797c10b91b..c4d540b221f6 100644
--- a/server/middlewares/validators/videos/video-abuses.ts
+++ b/server/middlewares/validators/videos/video-abuses.ts
@@ -1,6 +1,6 @@
import * as express from 'express'
import { body, param, query } from 'express-validator'
-import { exists, isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
+import { exists, isIdOrUUIDValid, isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc'
import {
isAbuseVideoIsValid,
isVideoAbuseModerationCommentValid,
@@ -26,10 +26,14 @@ const videoAbuseReportValidator = [
.optional()
.custom(isVideoAbusePredefinedReasonsValid)
.withMessage('Should have a valid list of predefined reasons'),
- body('timestamp')
+ body('startAt')
.optional()
- .custom(isVideoAbuseTimestampValid)
- .withMessage('Should have valid timestamp definition'),
+ .custom(toIntOrNull)
+ .withMessage('Should have valid starting time value'),
+ body('endAt')
+ .optional()
+ .custom(toIntOrNull)
+ .withMessage('Should have valid ending time value'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoAbuseReport parameters', { parameters: req.body })
diff --git a/shared/models/activitypub/objects/common-objects.ts b/shared/models/activitypub/objects/common-objects.ts
index 3c2bc3f35b8b..704979e59400 100644
--- a/shared/models/activitypub/objects/common-objects.ts
+++ b/shared/models/activitypub/objects/common-objects.ts
@@ -70,20 +70,19 @@ export type ActivityHtmlUrlObject = {
}
export interface ActivityHashTagObject {
- type: 'Hashtag' | 'Mention'
+ type: 'Hashtag'
href?: string
name: string
}
export interface ActivityMentionObject {
- type: 'Hashtag' | 'Mention'
+ type: 'Mention'
href?: string
name: string
}
export interface ActivityFlagReasonObject {
- type: 'Hashtag' | 'Mention'
- href?: string
+ type: 'Hashtag'
name: string
}
diff --git a/shared/models/videos/abuse/video-abuse-create.model.ts b/shared/models/videos/abuse/video-abuse-create.model.ts
index b2cc2da0b9f1..ba1fee3bda93 100644
--- a/shared/models/videos/abuse/video-abuse-create.model.ts
+++ b/shared/models/videos/abuse/video-abuse-create.model.ts
@@ -1,5 +1,6 @@
export interface VideoAbuseCreate {
reason: string
- predefinedReasons: any
- timestamp: any
+ predefinedReasons?: {[key: string]: boolean} // see VideoAbusePredefinedReasonsIn
+ startAt?: number
+ endAt?: number
}
diff --git a/shared/models/videos/abuse/video-abuse-reason.model.ts b/shared/models/videos/abuse/video-abuse-reason.model.ts
index 624871711401..ae9bb811cf40 100644
--- a/shared/models/videos/abuse/video-abuse-reason.model.ts
+++ b/shared/models/videos/abuse/video-abuse-reason.model.ts
@@ -9,7 +9,18 @@ export enum VideoAbusePredefinedReasons {
CAPTIONS
}
-export const VideoAbusePredefinedReasonsIn = {
+export interface VideoAbusePredefinedReasonsIn {
+ violentOrRepulsive: VideoAbusePredefinedReasons
+ hatefulOrAbusive: VideoAbusePredefinedReasons
+ spamOrMisleading: VideoAbusePredefinedReasons
+ privacy: VideoAbusePredefinedReasons
+ rights: VideoAbusePredefinedReasons
+ serverRules: VideoAbusePredefinedReasons
+ thumbnails: VideoAbusePredefinedReasons
+ captions: VideoAbusePredefinedReasons
+}
+
+export const VideoAbusePredefinedReasonsIn: VideoAbusePredefinedReasonsIn = {
violentOrRepulsive: VideoAbusePredefinedReasons.VIOLENT_OR_REPULSIVE,
hatefulOrAbusive: VideoAbusePredefinedReasons.HATEFUL_OR_ABUSIVE,
spamOrMisleading: VideoAbusePredefinedReasons.SPAM_OR_MISLEADING,