1 import { Component, OnInit, ViewChild } from '@angular/core'
2 import { Account } from '@app/shared/account/account.model'
3 import { Notifier } from '@app/core'
4 import { SortMeta } from 'primeng/api'
5 import { VideoAbuse, VideoAbuseState } from '../../../../../../shared'
6 import { RestPagination, RestTable, VideoAbuseService, VideoBlacklistService } from '../../../shared'
7 import { I18n } from '@ngx-translate/i18n-polyfill'
8 import { DropdownAction } from '../../../shared/buttons/action-dropdown.component'
9 import { ConfirmService } from '../../../core/index'
10 import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
11 import { Video } from '../../../shared/video/video.model'
12 import { MarkdownService } from '@app/shared/renderer'
13 import { Actor } from '@app/shared/actor/actor.model'
14 import { buildVideoLink, buildVideoEmbed } from 'src/assets/player/utils'
15 import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
16 import { DomSanitizer } from '@angular/platform-browser'
17 import { BlocklistService } from '@app/shared/blocklist'
18 import { VideoService } from '@app/shared/video/video.service'
19 import { ActivatedRoute } from '@angular/router'
20 import { first } from 'rxjs/operators'
23 selector: 'my-video-abuse-list',
24 templateUrl: './video-abuse-list.component.html',
25 styleUrls: [ '../moderation.component.scss', './video-abuse-list.component.scss' ]
27 export class VideoAbuseListComponent extends RestTable implements OnInit {
28 @ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent
30 videoAbuses: (VideoAbuse & { moderationCommentHtml?: string, reasonHtml?: string })[] = []
32 rowsPerPageOptions = [ 20, 50, 100 ]
33 rowsPerPage = this.rowsPerPageOptions[0]
34 sort: SortMeta = { field: 'createdAt', order: 1 }
35 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
37 videoAbuseActions: DropdownAction<VideoAbuse>[][] = []
40 private notifier: Notifier,
41 private videoAbuseService: VideoAbuseService,
42 private blocklistService: BlocklistService,
43 private videoService: VideoService,
44 private videoBlacklistService: VideoBlacklistService,
45 private confirmService: ConfirmService,
47 private markdownRenderer: MarkdownService,
48 private sanitizer: DomSanitizer,
49 private route: ActivatedRoute
53 this.videoAbuseActions = [
56 label: this.i18n('Internal actions'),
60 label: this.i18n('Delete report'),
61 handler: videoAbuse => this.removeVideoAbuse(videoAbuse)
64 label: this.i18n('Add note'),
65 handler: videoAbuse => this.openModerationCommentModal(videoAbuse),
66 isDisplayed: videoAbuse => !videoAbuse.moderationComment
69 label: this.i18n('Update note'),
70 handler: videoAbuse => this.openModerationCommentModal(videoAbuse),
71 isDisplayed: videoAbuse => !!videoAbuse.moderationComment
74 label: this.i18n('Mark as accepted'),
75 handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED),
76 isDisplayed: videoAbuse => !this.isVideoAbuseAccepted(videoAbuse)
79 label: this.i18n('Mark as rejected'),
80 handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.REJECTED),
81 isDisplayed: videoAbuse => !this.isVideoAbuseRejected(videoAbuse)
86 label: this.i18n('Actions for the video'),
88 isDisplayed: videoAbuse => !videoAbuse.video.deleted
91 label: this.i18n('Blacklist video'),
92 isDisplayed: videoAbuse => !videoAbuse.video.deleted && !videoAbuse.video.blacklisted,
93 handler: videoAbuse => {
94 this.videoBlacklistService.blacklistVideo(videoAbuse.video.id, undefined, true)
97 this.notifier.success(this.i18n('Video blacklisted.'))
99 this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED)
102 err => this.notifier.error(err.message)
107 label: this.i18n('Unblacklist video'),
108 isDisplayed: videoAbuse => !videoAbuse.video.deleted && videoAbuse.video.blacklisted,
109 handler: videoAbuse => {
110 this.videoBlacklistService.removeVideoFromBlacklist(videoAbuse.video.id)
113 this.notifier.success(this.i18n('Video unblacklisted.'))
115 this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED)
118 err => this.notifier.error(err.message)
123 label: this.i18n('Delete video'),
124 isDisplayed: videoAbuse => !videoAbuse.video.deleted,
125 handler: async videoAbuse => {
126 const res = await this.confirmService.confirm(
127 this.i18n('Do you really want to delete this video?'),
130 if (res === false) return
132 this.videoService.removeVideo(videoAbuse.video.id)
135 this.notifier.success(this.i18n('Video deleted.'))
137 this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED)
140 err => this.notifier.error(err.message)
147 label: this.i18n('Actions for the reporter'),
151 label: this.i18n('Mute reporter'),
152 handler: async videoAbuse => {
153 const account = videoAbuse.reporterAccount as Account
155 this.blocklistService.blockAccountByInstance(account)
158 this.notifier.success(
159 this.i18n('Account {{nameWithHost}} muted by the instance.', { nameWithHost: account.nameWithHost })
162 account.mutedByInstance = true
165 err => this.notifier.error(err.message)
170 label: this.i18n('Mute server'),
171 isDisplayed: videoAbuse => !videoAbuse.reporterAccount.userId,
172 handler: async videoAbuse => {
173 this.blocklistService.blockServerByInstance(videoAbuse.reporterAccount.host)
176 this.notifier.success(
177 this.i18n('Server {{host}} muted by the instance.', { host: videoAbuse.reporterAccount.host })
181 err => this.notifier.error(err.message)
192 this.route.queryParams
193 .pipe(first(params => params.search !== undefined && params.search !== null))
194 .subscribe(params => this.search = params.search)
198 return 'VideoAbuseListComponent'
201 openModerationCommentModal (videoAbuse: VideoAbuse) {
202 this.moderationCommentModal.openModal(videoAbuse)
205 onModerationCommentUpdated () {
209 createByString (account: Account) {
210 return Account.CREATE_BY_STRING(account.name, account.host)
213 isVideoAbuseAccepted (videoAbuse: VideoAbuse) {
214 return videoAbuse.state.id === VideoAbuseState.ACCEPTED
217 isVideoAbuseRejected (videoAbuse: VideoAbuse) {
218 return videoAbuse.state.id === VideoAbuseState.REJECTED
221 getVideoUrl (videoAbuse: VideoAbuse) {
222 return Video.buildClientUrl(videoAbuse.video.uuid)
225 getVideoEmbed (videoAbuse: VideoAbuse) {
226 const absoluteAPIUrl = 'http://localhost:9000' || getAbsoluteAPIUrl() // TODO
227 const embedUrl = buildVideoLink({
228 baseUrl: absoluteAPIUrl + '/videos/embed/' + videoAbuse.video.uuid,
231 return buildVideoEmbed(embedUrl)
234 switchToDefaultAvatar ($event: Event) {
235 ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL()
238 async removeVideoAbuse (videoAbuse: VideoAbuse) {
239 const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse report?'), this.i18n('Delete'))
240 if (res === false) return
242 this.videoAbuseService.removeVideoAbuse(videoAbuse).subscribe(
244 this.notifier.success(this.i18n('Abuse deleted.'))
248 err => this.notifier.error(err.message)
252 updateVideoAbuseState (videoAbuse: VideoAbuse, state: VideoAbuseState) {
253 this.videoAbuseService.updateVideoAbuse(videoAbuse, { state })
255 () => this.loadData(),
257 err => this.notifier.error(err.message)
262 protected loadData () {
263 return this.videoAbuseService.getVideoAbuses({
264 pagination: this.pagination,
268 async resultList => {
269 this.totalRecords = resultList.total
271 this.videoAbuses = resultList.data
273 for (const abuse of this.videoAbuses) {
274 Object.assign(abuse, {
275 reasonHtml: await this.toHtml(abuse.reason),
276 moderationCommentHtml: await this.toHtml(abuse.moderationComment),
277 embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse)),
278 reporterAccount: new Account(abuse.reporterAccount)
284 err => this.notifier.error(err.message)
288 private toHtml (text: string) {
289 return this.markdownRenderer.textMarkdownToHTML(text)