add quarantine videos feature (#1637)
* add quarantine videos feature * increase Notification settings test timeout to 20000ms. was completing 7000 locally but timing out after 10000 on travis * fix quarantine video test issues -propagate misspelling -remove skip from server/tests/client.ts * WIP use blacklist for moderator video approval instead of video.quarantine boolean * finish auto-blacklist feature
This commit is contained in:
parent
12fed49eba
commit
7ccddd7b52
|
@ -11,7 +11,12 @@ import { JobsComponent } from './jobs/job.component'
|
||||||
import { JobsListComponent } from './jobs/jobs-list/jobs-list.component'
|
import { JobsListComponent } from './jobs/jobs-list/jobs-list.component'
|
||||||
import { JobService } from './jobs/shared/job.service'
|
import { JobService } from './jobs/shared/job.service'
|
||||||
import { UserCreateComponent, UserListComponent, UsersComponent, UserUpdateComponent, UserPasswordComponent } from './users'
|
import { UserCreateComponent, UserListComponent, UsersComponent, UserUpdateComponent, UserPasswordComponent } from './users'
|
||||||
import { ModerationCommentModalComponent, VideoAbuseListComponent, VideoBlacklistListComponent } from './moderation'
|
import {
|
||||||
|
ModerationCommentModalComponent,
|
||||||
|
VideoAbuseListComponent,
|
||||||
|
VideoBlacklistListComponent,
|
||||||
|
VideoAutoBlacklistListComponent
|
||||||
|
} from './moderation'
|
||||||
import { ModerationComponent } from '@app/+admin/moderation/moderation.component'
|
import { ModerationComponent } from '@app/+admin/moderation/moderation.component'
|
||||||
import { RedundancyCheckboxComponent } from '@app/+admin/follows/shared/redundancy-checkbox.component'
|
import { RedundancyCheckboxComponent } from '@app/+admin/follows/shared/redundancy-checkbox.component'
|
||||||
import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service'
|
import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service'
|
||||||
|
@ -42,6 +47,7 @@ import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } f
|
||||||
ModerationComponent,
|
ModerationComponent,
|
||||||
VideoBlacklistListComponent,
|
VideoBlacklistListComponent,
|
||||||
VideoAbuseListComponent,
|
VideoAbuseListComponent,
|
||||||
|
VideoAutoBlacklistListComponent,
|
||||||
ModerationCommentModalComponent,
|
ModerationCommentModalComponent,
|
||||||
InstanceServerBlocklistComponent,
|
InstanceServerBlocklistComponent,
|
||||||
InstanceAccountBlocklistComponent,
|
InstanceAccountBlocklistComponent,
|
||||||
|
|
|
@ -161,6 +161,23 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<div i18n class="inner-form-title">Auto-blacklist</div>
|
||||||
|
|
||||||
|
<ng-container formGroupName="autoBlacklist">
|
||||||
|
<ng-container formGroupName="videos">
|
||||||
|
<ng-container formGroupName="ofUsers">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<my-peertube-checkbox
|
||||||
|
inputName="autoBlacklistVideosOfUsersEnabled" formControlName="enabled"
|
||||||
|
i18n-labelText labelText="New videos of users automatically blacklisted enabled"
|
||||||
|
></my-peertube-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<div i18n class="inner-form-title">Administrator</div>
|
<div i18n class="inner-form-title">Administrator</div>
|
||||||
|
|
||||||
<div class="form-group" formGroupName="admin">
|
<div class="form-group" formGroupName="admin">
|
||||||
|
|
|
@ -117,6 +117,13 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
threads: this.customConfigValidatorsService.TRANSCODING_THREADS,
|
threads: this.customConfigValidatorsService.TRANSCODING_THREADS,
|
||||||
allowAdditionalExtensions: null,
|
allowAdditionalExtensions: null,
|
||||||
resolutions: {}
|
resolutions: {}
|
||||||
|
},
|
||||||
|
autoBlacklist: {
|
||||||
|
videos: {
|
||||||
|
ofUsers: {
|
||||||
|
enabled: null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export * from './video-abuse-list'
|
export * from './video-abuse-list'
|
||||||
|
export * from './video-auto-blacklist-list'
|
||||||
export * from './video-blacklist-list'
|
export * from './video-blacklist-list'
|
||||||
export * from './moderation.component'
|
export * from './moderation.component'
|
||||||
export * from './moderation.routes'
|
export * from './moderation.routes'
|
||||||
|
|
|
@ -4,7 +4,9 @@
|
||||||
<div class="admin-sub-nav">
|
<div class="admin-sub-nav">
|
||||||
<a *ngIf="hasVideoAbusesRight()" i18n routerLink="video-abuses/list" routerLinkActive="active">Video abuses</a>
|
<a *ngIf="hasVideoAbusesRight()" i18n routerLink="video-abuses/list" routerLinkActive="active">Video abuses</a>
|
||||||
|
|
||||||
<a *ngIf="hasVideoBlacklistRight()" i18n routerLink="video-blacklist/list" routerLinkActive="active">Blacklisted videos</a>
|
<a *ngIf="hasVideoBlacklistRight()" i18n routerLink="video-blacklist/list" routerLinkActive="active">{{ autoBlacklistVideosEnabled ? 'Manually blacklisted videos' : 'Blacklisted videos' }}</a>
|
||||||
|
|
||||||
|
<a *ngIf="autoBlacklistVideosEnabled && hasVideoBlacklistRight()" i18n routerLink="video-auto-blacklist/list" routerLinkActive="active">Auto-blacklisted videos</a>
|
||||||
|
|
||||||
<a *ngIf="hasAccountsBlocklistRight()" i18n routerLink="blocklist/accounts" routerLinkActive="active">Muted accounts</a>
|
<a *ngIf="hasAccountsBlocklistRight()" i18n routerLink="blocklist/accounts" routerLinkActive="active">Muted accounts</a>
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { UserRight } from '../../../../../shared'
|
import { UserRight } from '../../../../../shared'
|
||||||
import { AuthService } from '@app/core/auth/auth.service'
|
import { AuthService, ServerService } from '@app/core'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './moderation.component.html',
|
templateUrl: './moderation.component.html',
|
||||||
styleUrls: [ './moderation.component.scss' ]
|
styleUrls: [ './moderation.component.scss' ]
|
||||||
})
|
})
|
||||||
export class ModerationComponent {
|
export class ModerationComponent {
|
||||||
constructor (private auth: AuthService) {}
|
autoBlacklistVideosEnabled: boolean
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private auth: AuthService,
|
||||||
|
private serverService: ServerService
|
||||||
|
) {
|
||||||
|
this.autoBlacklistVideosEnabled = this.serverService.getConfig().autoBlacklist.videos.ofUsers.enabled
|
||||||
|
}
|
||||||
|
|
||||||
hasVideoAbusesRight () {
|
hasVideoAbusesRight () {
|
||||||
return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_ABUSES)
|
return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_ABUSES)
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { UserRight } from '../../../../../shared'
|
||||||
import { UserRightGuard } from '@app/core'
|
import { UserRightGuard } from '@app/core'
|
||||||
import { VideoAbuseListComponent } from '@app/+admin/moderation/video-abuse-list'
|
import { VideoAbuseListComponent } from '@app/+admin/moderation/video-abuse-list'
|
||||||
import { VideoBlacklistListComponent } from '@app/+admin/moderation/video-blacklist-list'
|
import { VideoBlacklistListComponent } from '@app/+admin/moderation/video-blacklist-list'
|
||||||
|
import { VideoAutoBlacklistListComponent } from '@app/+admin/moderation/video-auto-blacklist-list'
|
||||||
import { ModerationComponent } from '@app/+admin/moderation/moderation.component'
|
import { ModerationComponent } from '@app/+admin/moderation/moderation.component'
|
||||||
import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist'
|
import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist'
|
||||||
|
|
||||||
|
@ -26,6 +27,11 @@ export const ModerationRoutes: Routes = [
|
||||||
redirectTo: 'video-blacklist/list',
|
redirectTo: 'video-blacklist/list',
|
||||||
pathMatch: 'full'
|
pathMatch: 'full'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'video-auto-blacklist',
|
||||||
|
redirectTo: 'video-auto-blacklist/list',
|
||||||
|
pathMatch: 'full'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'video-abuses/list',
|
path: 'video-abuses/list',
|
||||||
component: VideoAbuseListComponent,
|
component: VideoAbuseListComponent,
|
||||||
|
@ -37,6 +43,17 @@ export const ModerationRoutes: Routes = [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'video-auto-blacklist/list',
|
||||||
|
component: VideoAutoBlacklistListComponent,
|
||||||
|
canActivate: [ UserRightGuard ],
|
||||||
|
data: {
|
||||||
|
userRight: UserRight.MANAGE_VIDEO_BLACKLIST,
|
||||||
|
meta: {
|
||||||
|
title: 'Auto-blacklisted videos'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'video-blacklist/list',
|
path: 'video-blacklist/list',
|
||||||
component: VideoBlacklistListComponent,
|
component: VideoBlacklistListComponent,
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './video-auto-blacklist-list.component'
|
|
@ -0,0 +1,49 @@
|
||||||
|
<div i18n *ngIf="pagination.totalItems === 0">No results.</div>
|
||||||
|
<div
|
||||||
|
myInfiniteScroller
|
||||||
|
[pageHeight]="pageHeight"
|
||||||
|
(nearOfTop)="onNearOfTop()"
|
||||||
|
(nearOfBottom)="onNearOfBottom()"
|
||||||
|
(pageChanged)="onPageChanged($event)"
|
||||||
|
class="videos" #videosElement
|
||||||
|
>
|
||||||
|
<div *ngFor="let videos of videoPages; let i = index" class="videos-page">
|
||||||
|
<div class="video" *ngFor="let video of videos; let j = index">
|
||||||
|
<div class="checkbox-container">
|
||||||
|
<my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
|
||||||
|
</div>
|
||||||
|
<my-video-thumbnail [video]="video"></my-video-thumbnail>
|
||||||
|
|
||||||
|
<div class="video-info">
|
||||||
|
<a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
|
||||||
|
<div>{{ video.account.displayName }}</div>
|
||||||
|
<div>{{ video.publishedAt | myFromNow }}</div>
|
||||||
|
<div><span i18n>Privacy: </span><span>{{ video.privacy.label }}</span></div>
|
||||||
|
<div><span i18n>Sensitve: </span><span> {{ video.nsfw }}</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Display only once -->
|
||||||
|
<div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0">
|
||||||
|
<div class="action-selection-mode-child">
|
||||||
|
<span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
|
||||||
|
Cancel
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="action-button action-button-unblacklist-selection" (click)="removeSelectedVideosFromBlacklist()">
|
||||||
|
<my-global-icon iconName="tick"></my-global-icon>
|
||||||
|
<ng-container i18n>Unblacklist</ng-container>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="video-buttons" *ngIf="isInSelectionMode() === false">
|
||||||
|
<my-button
|
||||||
|
i18n-label
|
||||||
|
label="Unblacklist"
|
||||||
|
icon="tick"
|
||||||
|
(click)="removeVideoFromBlacklist(video)"
|
||||||
|
></my-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
|
@ -0,0 +1,94 @@
|
||||||
|
@import '_variables';
|
||||||
|
@import '_mixins';
|
||||||
|
|
||||||
|
.action-selection-mode {
|
||||||
|
width: 194px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.action-selection-mode-child {
|
||||||
|
position: fixed;
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button-cancel-selection {
|
||||||
|
@include peertube-button;
|
||||||
|
@include grey-button;
|
||||||
|
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button-unblacklist-selection {
|
||||||
|
@include peertube-button;
|
||||||
|
@include orange-button;
|
||||||
|
@include button-with-icon(21px);
|
||||||
|
|
||||||
|
my-global-icon {
|
||||||
|
@include apply-svg-color(#fff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video {
|
||||||
|
@include row-blocks;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 47px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 20px;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
my-video-thumbnail {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-info {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.video-info-name {
|
||||||
|
@include disable-default-a-behaviour;
|
||||||
|
|
||||||
|
color: var(--mainForegroundColor);
|
||||||
|
display: block;
|
||||||
|
width: fit-content;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: $font-semibold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-buttons {
|
||||||
|
min-width: 190px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $small-view) {
|
||||||
|
.video {
|
||||||
|
flex-direction: column;
|
||||||
|
height: auto;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.video-info-name {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
my-video-thumbnail {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-buttons {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { Component, OnInit, OnDestroy } from '@angular/core'
|
||||||
|
import { Location } from '@angular/common'
|
||||||
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
|
import { Router, ActivatedRoute } from '@angular/router'
|
||||||
|
import { AbstractVideoList } from '@app/shared/video/abstract-video-list'
|
||||||
|
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
||||||
|
import { Notifier, AuthService } from '@app/core'
|
||||||
|
import { Video } from '@shared/models'
|
||||||
|
import { VideoBlacklistService } from '@app/shared'
|
||||||
|
import { immutableAssign } from '@app/shared/misc/utils'
|
||||||
|
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-video-auto-blacklist-list',
|
||||||
|
templateUrl: './video-auto-blacklist-list.component.html',
|
||||||
|
styleUrls: [ './video-auto-blacklist-list.component.scss' ]
|
||||||
|
})
|
||||||
|
export class VideoAutoBlacklistListComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||||
|
titlePage: string
|
||||||
|
currentRoute = '/admin/moderation/video-auto-blacklist/list'
|
||||||
|
checkedVideos: { [ id: number ]: boolean } = {}
|
||||||
|
pagination: ComponentPagination = {
|
||||||
|
currentPage: 1,
|
||||||
|
itemsPerPage: 5,
|
||||||
|
totalItems: null
|
||||||
|
}
|
||||||
|
|
||||||
|
protected baseVideoWidth = -1
|
||||||
|
protected baseVideoHeight = 155
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
protected router: Router,
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected i18n: I18n,
|
||||||
|
protected notifier: Notifier,
|
||||||
|
protected location: Location,
|
||||||
|
protected authService: AuthService,
|
||||||
|
protected screenService: ScreenService,
|
||||||
|
private videoBlacklistService: VideoBlacklistService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.titlePage = this.i18n('Auto-blacklisted videos')
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
super.ngOnInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
super.ngOnDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
abortSelectionMode () {
|
||||||
|
this.checkedVideos = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
isInSelectionMode () {
|
||||||
|
return Object.keys(this.checkedVideos).some(k => this.checkedVideos[k] === true)
|
||||||
|
}
|
||||||
|
|
||||||
|
getVideosObservable (page: number) {
|
||||||
|
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
||||||
|
|
||||||
|
return this.videoBlacklistService.getAutoBlacklistedAsVideoList(newPagination)
|
||||||
|
}
|
||||||
|
|
||||||
|
generateSyndicationList () {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
|
||||||
|
removeVideoFromBlacklist (entry: Video) {
|
||||||
|
this.videoBlacklistService.removeVideoFromBlacklist(entry.id).subscribe(
|
||||||
|
() => {
|
||||||
|
this.notifier.success(this.i18n('Video {{name}} removed from blacklist.', { name: entry.name }))
|
||||||
|
this.reloadVideos()
|
||||||
|
},
|
||||||
|
|
||||||
|
error => this.notifier.error(error.message)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeSelectedVideosFromBlacklist () {
|
||||||
|
const toReleaseVideosIds = Object.keys(this.checkedVideos)
|
||||||
|
.filter(k => this.checkedVideos[ k ] === true)
|
||||||
|
.map(k => parseInt(k, 10))
|
||||||
|
|
||||||
|
this.videoBlacklistService.removeVideoFromBlacklist(toReleaseVideosIds).subscribe(
|
||||||
|
() => {
|
||||||
|
this.notifier.success(this.i18n('{{num}} videos removed from blacklist.', { num: toReleaseVideosIds.length }))
|
||||||
|
|
||||||
|
this.abortSelectionMode()
|
||||||
|
this.reloadVideos()
|
||||||
|
},
|
||||||
|
|
||||||
|
error => this.notifier.error(error.message)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
import { Component, OnInit } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
import { SortMeta } from 'primeng/components/common/sortmeta'
|
import { SortMeta } from 'primeng/components/common/sortmeta'
|
||||||
import { Notifier } from '@app/core'
|
import { Notifier, ServerService } from '@app/core'
|
||||||
import { ConfirmService } from '../../../core'
|
import { ConfirmService } from '../../../core'
|
||||||
import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared'
|
import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared'
|
||||||
import { VideoBlacklist } from '../../../../../../shared'
|
import { VideoBlacklist, VideoBlacklistType } from '../../../../../../shared'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { DropdownAction } from '../../../shared/buttons/action-dropdown.component'
|
import { DropdownAction } from '../../../shared/buttons/action-dropdown.component'
|
||||||
import { Video } from '../../../shared/video/video.model'
|
import { Video } from '../../../shared/video/video.model'
|
||||||
|
@ -20,11 +20,13 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
|
||||||
rowsPerPage = 10
|
rowsPerPage = 10
|
||||||
sort: SortMeta = { field: 'createdAt', order: 1 }
|
sort: SortMeta = { field: 'createdAt', order: 1 }
|
||||||
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
||||||
|
listBlacklistTypeFilter: VideoBlacklistType = undefined
|
||||||
|
|
||||||
videoBlacklistActions: DropdownAction<VideoBlacklist>[] = []
|
videoBlacklistActions: DropdownAction<VideoBlacklist>[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private notifier: Notifier,
|
private notifier: Notifier,
|
||||||
|
private serverService: ServerService,
|
||||||
private confirmService: ConfirmService,
|
private confirmService: ConfirmService,
|
||||||
private videoBlacklistService: VideoBlacklistService,
|
private videoBlacklistService: VideoBlacklistService,
|
||||||
private markdownRenderer: MarkdownService,
|
private markdownRenderer: MarkdownService,
|
||||||
|
@ -32,6 +34,11 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
|
// don't filter if auto-blacklist not enabled as this will be only list
|
||||||
|
if (this.serverService.getConfig().autoBlacklist.videos.ofUsers.enabled) {
|
||||||
|
this.listBlacklistTypeFilter = VideoBlacklistType.MANUAL
|
||||||
|
}
|
||||||
|
|
||||||
this.videoBlacklistActions = [
|
this.videoBlacklistActions = [
|
||||||
{
|
{
|
||||||
label: this.i18n('Unblacklist'),
|
label: this.i18n('Unblacklist'),
|
||||||
|
@ -77,7 +84,7 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected loadData () {
|
protected loadData () {
|
||||||
this.videoBlacklistService.listBlacklist(this.pagination, this.sort)
|
this.videoBlacklistService.listBlacklist(this.pagination, this.sort, this.listBlacklistTypeFilter)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
async resultList => {
|
async resultList => {
|
||||||
this.totalRecords = resultList.total
|
this.totalRecords = resultList.total
|
||||||
|
|
|
@ -31,10 +31,12 @@ export class MyAccountNotificationPreferencesComponent implements OnInit {
|
||||||
private serverService: ServerService,
|
private serverService: ServerService,
|
||||||
private notifier: Notifier
|
private notifier: Notifier
|
||||||
) {
|
) {
|
||||||
|
|
||||||
this.labelNotifications = {
|
this.labelNotifications = {
|
||||||
newVideoFromSubscription: this.i18n('New video from your subscriptions'),
|
newVideoFromSubscription: this.i18n('New video from your subscriptions'),
|
||||||
newCommentOnMyVideo: this.i18n('New comment on your video'),
|
newCommentOnMyVideo: this.i18n('New comment on your video'),
|
||||||
videoAbuseAsModerator: this.i18n('New video abuse'),
|
videoAbuseAsModerator: this.i18n('New video abuse'),
|
||||||
|
videoAutoBlacklistAsModerator: this.i18n('Video auto-blacklisted waiting review'),
|
||||||
blacklistOnMyVideo: this.i18n('One of your video is blacklisted/unblacklisted'),
|
blacklistOnMyVideo: this.i18n('One of your video is blacklisted/unblacklisted'),
|
||||||
myVideoPublished: this.i18n('Video published (after transcoding/scheduled update)'),
|
myVideoPublished: this.i18n('Video published (after transcoding/scheduled update)'),
|
||||||
myVideoImportFinished: this.i18n('Video import finished'),
|
myVideoImportFinished: this.i18n('Video import finished'),
|
||||||
|
@ -46,6 +48,7 @@ export class MyAccountNotificationPreferencesComponent implements OnInit {
|
||||||
|
|
||||||
this.rightNotifications = {
|
this.rightNotifications = {
|
||||||
videoAbuseAsModerator: UserRight.MANAGE_VIDEO_ABUSES,
|
videoAbuseAsModerator: UserRight.MANAGE_VIDEO_ABUSES,
|
||||||
|
videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST,
|
||||||
newUserRegistration: UserRight.MANAGE_USERS
|
newUserRegistration: UserRight.MANAGE_USERS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,13 @@ export class ServerService {
|
||||||
videos: {
|
videos: {
|
||||||
intervalDays: 0
|
intervalDays: 0
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
autoBlacklist: {
|
||||||
|
videos: {
|
||||||
|
ofUsers: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private videoCategories: Array<VideoConstant<number>> = []
|
private videoCategories: Array<VideoConstant<number>> = []
|
||||||
|
|
|
@ -54,6 +54,7 @@ export class UserNotification implements UserNotificationServer {
|
||||||
videoUrl?: string
|
videoUrl?: string
|
||||||
commentUrl?: any[]
|
commentUrl?: any[]
|
||||||
videoAbuseUrl?: string
|
videoAbuseUrl?: string
|
||||||
|
videoAutoBlacklistUrl?: string
|
||||||
accountUrl?: string
|
accountUrl?: string
|
||||||
videoImportIdentifier?: string
|
videoImportIdentifier?: string
|
||||||
videoImportUrl?: string
|
videoImportUrl?: string
|
||||||
|
@ -107,6 +108,11 @@ export class UserNotification implements UserNotificationServer {
|
||||||
this.videoUrl = this.buildVideoUrl(this.videoAbuse.video)
|
this.videoUrl = this.buildVideoUrl(this.videoAbuse.video)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS:
|
||||||
|
this.videoAutoBlacklistUrl = '/admin/moderation/video-auto-blacklist/list'
|
||||||
|
this.videoUrl = this.buildVideoUrl(this.video)
|
||||||
|
break
|
||||||
|
|
||||||
case UserNotificationType.BLACKLIST_ON_MY_VIDEO:
|
case UserNotificationType.BLACKLIST_ON_MY_VIDEO:
|
||||||
this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video)
|
this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video)
|
||||||
break
|
break
|
||||||
|
|
|
@ -36,6 +36,14 @@
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container i18n *ngSwitchCase="UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS">
|
||||||
|
<my-global-icon iconName="no"></my-global-icon>
|
||||||
|
|
||||||
|
<div class="message">
|
||||||
|
The recently added video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> has been <a (click)="markAsRead(notification)" [routerLink]="notification.videoAutoBlacklistUrl">auto-blacklisted</a>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container i18n *ngSwitchCase="UserNotificationType.NEW_COMMENT_ON_MY_VIDEO">
|
<ng-container i18n *ngSwitchCase="UserNotificationType.NEW_COMMENT_ON_MY_VIDEO">
|
||||||
<img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" />
|
<img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" />
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { catchError, map } from 'rxjs/operators'
|
import { catchError, map, concatMap, toArray } from 'rxjs/operators'
|
||||||
import { HttpClient, HttpParams } from '@angular/common/http'
|
import { HttpClient, HttpParams } from '@angular/common/http'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { SortMeta } from 'primeng/components/common/sortmeta'
|
import { SortMeta } from 'primeng/components/common/sortmeta'
|
||||||
import { Observable } from 'rxjs'
|
import { from as observableFrom, Observable } from 'rxjs'
|
||||||
import { VideoBlacklist, ResultList } from '../../../../../shared'
|
import { VideoBlacklist, VideoBlacklistType, ResultList } from '../../../../../shared'
|
||||||
|
import { Video } from '../video/video.model'
|
||||||
import { environment } from '../../../environments/environment'
|
import { environment } from '../../../environments/environment'
|
||||||
import { RestExtractor, RestPagination, RestService } from '../rest'
|
import { RestExtractor, RestPagination, RestService } from '../rest'
|
||||||
|
import { ComponentPagination } from '../rest/component-pagination.model'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VideoBlacklistService {
|
export class VideoBlacklistService {
|
||||||
|
@ -17,10 +19,14 @@ export class VideoBlacklistService {
|
||||||
private restExtractor: RestExtractor
|
private restExtractor: RestExtractor
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
listBlacklist (pagination: RestPagination, sort: SortMeta): Observable<ResultList<VideoBlacklist>> {
|
listBlacklist (pagination: RestPagination, sort: SortMeta, type?: VideoBlacklistType): Observable<ResultList<VideoBlacklist>> {
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
params = this.restService.addRestGetParams(params, pagination, sort)
|
params = this.restService.addRestGetParams(params, pagination, sort)
|
||||||
|
|
||||||
|
if (type) {
|
||||||
|
params = params.set('type', type.toString())
|
||||||
|
}
|
||||||
|
|
||||||
return this.authHttp.get<ResultList<VideoBlacklist>>(VideoBlacklistService.BASE_VIDEOS_URL + 'blacklist', { params })
|
return this.authHttp.get<ResultList<VideoBlacklist>>(VideoBlacklistService.BASE_VIDEOS_URL + 'blacklist', { params })
|
||||||
.pipe(
|
.pipe(
|
||||||
map(res => this.restExtractor.convertResultListDateToHuman(res)),
|
map(res => this.restExtractor.convertResultListDateToHuman(res)),
|
||||||
|
@ -28,12 +34,37 @@ export class VideoBlacklistService {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
removeVideoFromBlacklist (videoId: number) {
|
getAutoBlacklistedAsVideoList (videoPagination: ComponentPagination): Observable<{ videos: Video[], totalVideos: number}> {
|
||||||
return this.authHttp.delete(VideoBlacklistService.BASE_VIDEOS_URL + videoId + '/blacklist')
|
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
||||||
.pipe(
|
|
||||||
map(this.restExtractor.extractDataBool),
|
// prioritize first created since waiting longest
|
||||||
catchError(res => this.restExtractor.handleError(res))
|
const AUTO_BLACKLIST_SORT = 'createdAt'
|
||||||
)
|
|
||||||
|
let params = new HttpParams()
|
||||||
|
params = this.restService.addRestGetParams(params, pagination, AUTO_BLACKLIST_SORT)
|
||||||
|
|
||||||
|
params = params.set('type', VideoBlacklistType.AUTO_BEFORE_PUBLISHED.toString())
|
||||||
|
|
||||||
|
return this.authHttp.get<ResultList<VideoBlacklist>>(VideoBlacklistService.BASE_VIDEOS_URL + 'blacklist', { params })
|
||||||
|
.pipe(
|
||||||
|
map(res => {
|
||||||
|
const videos = res.data.map(videoBlacklist => new Video(videoBlacklist.video))
|
||||||
|
const totalVideos = res.total
|
||||||
|
return { videos, totalVideos }
|
||||||
|
}),
|
||||||
|
catchError(res => this.restExtractor.handleError(res))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeVideoFromBlacklist (videoIdArgs: number | number[]) {
|
||||||
|
const videoIds = Array.isArray(videoIdArgs) ? videoIdArgs : [ videoIdArgs ]
|
||||||
|
|
||||||
|
return observableFrom(videoIds)
|
||||||
|
.pipe(
|
||||||
|
concatMap(id => this.authHttp.delete(VideoBlacklistService.BASE_VIDEOS_URL + id + '/blacklist')),
|
||||||
|
toArray(),
|
||||||
|
catchError(err => this.restExtractor.handleError(err))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
blacklistVideo (videoId: number, reason: string, unfederate: boolean) {
|
blacklistVideo (videoId: number, reason: string, unfederate: boolean) {
|
||||||
|
|
|
@ -162,6 +162,12 @@ import:
|
||||||
torrent: # Magnet URI or torrent file (use classic TCP/UDP/WebSeed to download the file)
|
torrent: # Magnet URI or torrent file (use classic TCP/UDP/WebSeed to download the file)
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
auto_blacklist:
|
||||||
|
# New videos automatically blacklisted so moderators can review before publishing
|
||||||
|
videos:
|
||||||
|
of_users:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
instance:
|
instance:
|
||||||
name: 'PeerTube'
|
name: 'PeerTube'
|
||||||
short_description: 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.'
|
short_description: 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.'
|
||||||
|
|
|
@ -176,6 +176,12 @@ import:
|
||||||
torrent: # Magnet URI or torrent file (use classic TCP/UDP/WebSeed to download the file)
|
torrent: # Magnet URI or torrent file (use classic TCP/UDP/WebSeed to download the file)
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
auto_blacklist:
|
||||||
|
# New videos automatically blacklisted so moderators can review before publishing
|
||||||
|
videos:
|
||||||
|
of_users:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
# Instance settings
|
# Instance settings
|
||||||
instance:
|
instance:
|
||||||
name: 'PeerTube'
|
name: 'PeerTube'
|
||||||
|
|
|
@ -94,6 +94,13 @@ async function getConfig (req: express.Request, res: express.Response) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
autoBlacklist: {
|
||||||
|
videos: {
|
||||||
|
ofUsers: {
|
||||||
|
enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
file: {
|
file: {
|
||||||
size: {
|
size: {
|
||||||
|
@ -265,6 +272,13 @@ function customConfig (): CustomConfig {
|
||||||
enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
|
enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
autoBlacklist: {
|
||||||
|
videos: {
|
||||||
|
ofUsers: {
|
||||||
|
enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,7 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
|
||||||
newVideoFromSubscription: body.newVideoFromSubscription,
|
newVideoFromSubscription: body.newVideoFromSubscription,
|
||||||
newCommentOnMyVideo: body.newCommentOnMyVideo,
|
newCommentOnMyVideo: body.newCommentOnMyVideo,
|
||||||
videoAbuseAsModerator: body.videoAbuseAsModerator,
|
videoAbuseAsModerator: body.videoAbuseAsModerator,
|
||||||
|
videoAutoBlacklistAsModerator: body.videoAutoBlacklistAsModerator,
|
||||||
blacklistOnMyVideo: body.blacklistOnMyVideo,
|
blacklistOnMyVideo: body.blacklistOnMyVideo,
|
||||||
myVideoPublished: body.myVideoPublished,
|
myVideoPublished: body.myVideoPublished,
|
||||||
myVideoImportFinished: body.myVideoImportFinished,
|
myVideoImportFinished: body.myVideoImportFinished,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import { UserRight, VideoBlacklist, VideoBlacklistCreate } from '../../../../shared'
|
import { VideoBlacklist, UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared'
|
||||||
import { logger } from '../../../helpers/logger'
|
import { logger } from '../../../helpers/logger'
|
||||||
import { getFormattedObjects } from '../../../helpers/utils'
|
import { getFormattedObjects } from '../../../helpers/utils'
|
||||||
import {
|
import {
|
||||||
|
@ -12,7 +12,8 @@ import {
|
||||||
setDefaultPagination,
|
setDefaultPagination,
|
||||||
videosBlacklistAddValidator,
|
videosBlacklistAddValidator,
|
||||||
videosBlacklistRemoveValidator,
|
videosBlacklistRemoveValidator,
|
||||||
videosBlacklistUpdateValidator
|
videosBlacklistUpdateValidator,
|
||||||
|
videosBlacklistFiltersValidator
|
||||||
} from '../../../middlewares'
|
} from '../../../middlewares'
|
||||||
import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
|
import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
|
||||||
import { sequelizeTypescript } from '../../../initializers'
|
import { sequelizeTypescript } from '../../../initializers'
|
||||||
|
@ -36,6 +37,7 @@ blacklistRouter.get('/blacklist',
|
||||||
blacklistSortValidator,
|
blacklistSortValidator,
|
||||||
setBlacklistSort,
|
setBlacklistSort,
|
||||||
setDefaultPagination,
|
setDefaultPagination,
|
||||||
|
videosBlacklistFiltersValidator,
|
||||||
asyncMiddleware(listBlacklist)
|
asyncMiddleware(listBlacklist)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,7 +70,8 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response)
|
||||||
const toCreate = {
|
const toCreate = {
|
||||||
videoId: videoInstance.id,
|
videoId: videoInstance.id,
|
||||||
unfederated: body.unfederate === true,
|
unfederated: body.unfederate === true,
|
||||||
reason: body.reason
|
reason: body.reason,
|
||||||
|
type: VideoBlacklistType.MANUAL
|
||||||
}
|
}
|
||||||
|
|
||||||
const blacklist = await VideoBlacklistModel.create(toCreate)
|
const blacklist = await VideoBlacklistModel.create(toCreate)
|
||||||
|
@ -98,7 +101,7 @@ async function updateVideoBlacklistController (req: express.Request, res: expres
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort)
|
const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.type)
|
||||||
|
|
||||||
return res.json(getFormattedObjects<VideoBlacklist, VideoBlacklistModel>(resultList.data, resultList.total))
|
return res.json(getFormattedObjects<VideoBlacklist, VideoBlacklistModel>(resultList.data, resultList.total))
|
||||||
}
|
}
|
||||||
|
@ -107,18 +110,30 @@ async function removeVideoFromBlacklistController (req: express.Request, res: ex
|
||||||
const videoBlacklist = res.locals.videoBlacklist
|
const videoBlacklist = res.locals.videoBlacklist
|
||||||
const video = res.locals.video
|
const video = res.locals.video
|
||||||
|
|
||||||
await sequelizeTypescript.transaction(async t => {
|
const videoBlacklistType = await sequelizeTypescript.transaction(async t => {
|
||||||
const unfederated = videoBlacklist.unfederated
|
const unfederated = videoBlacklist.unfederated
|
||||||
|
const videoBlacklistType = videoBlacklist.type
|
||||||
|
|
||||||
await videoBlacklist.destroy({ transaction: t })
|
await videoBlacklist.destroy({ transaction: t })
|
||||||
|
|
||||||
// Re federate the video
|
// Re federate the video
|
||||||
if (unfederated === true) {
|
if (unfederated === true) {
|
||||||
await federateVideoIfNeeded(video, true, t)
|
await federateVideoIfNeeded(video, true, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return videoBlacklistType
|
||||||
})
|
})
|
||||||
|
|
||||||
Notifier.Instance.notifyOnVideoUnblacklist(video)
|
Notifier.Instance.notifyOnVideoUnblacklist(video)
|
||||||
|
|
||||||
|
if (videoBlacklistType === VideoBlacklistType.AUTO_BEFORE_PUBLISHED) {
|
||||||
|
Notifier.Instance.notifyOnVideoPublishedAfterRemovedFromAutoBlacklist(video)
|
||||||
|
|
||||||
|
// Delete on object so new video notifications will send
|
||||||
|
delete video.VideoBlacklist
|
||||||
|
Notifier.Instance.notifyOnNewVideo(video)
|
||||||
|
}
|
||||||
|
|
||||||
logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
|
logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
|
||||||
|
|
||||||
return res.type('json').status(204).end()
|
return res.type('json').status(204).end()
|
||||||
|
|
|
@ -18,10 +18,12 @@ import { join } from 'path'
|
||||||
import { isArray } from '../../../helpers/custom-validators/misc'
|
import { isArray } from '../../../helpers/custom-validators/misc'
|
||||||
import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model'
|
import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model'
|
||||||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||||
|
import { UserModel } from '../../../models/account/user'
|
||||||
import * as Bluebird from 'bluebird'
|
import * as Bluebird from 'bluebird'
|
||||||
import * as parseTorrent from 'parse-torrent'
|
import * as parseTorrent from 'parse-torrent'
|
||||||
import { getSecureTorrentName } from '../../../helpers/utils'
|
import { getSecureTorrentName } from '../../../helpers/utils'
|
||||||
import { readFile, move } from 'fs-extra'
|
import { readFile, move } from 'fs-extra'
|
||||||
|
import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
|
||||||
|
|
||||||
const auditLogger = auditLoggerFactory('video-imports')
|
const auditLogger = auditLoggerFactory('video-imports')
|
||||||
const videoImportsRouter = express.Router()
|
const videoImportsRouter = express.Router()
|
||||||
|
@ -85,7 +87,7 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
|
||||||
videoName = isArray(parsed.name) ? parsed.name[ 0 ] : parsed.name as string
|
videoName = isArray(parsed.name) ? parsed.name[ 0 ] : parsed.name as string
|
||||||
}
|
}
|
||||||
|
|
||||||
const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName })
|
const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName }, user)
|
||||||
|
|
||||||
await processThumbnail(req, video)
|
await processThumbnail(req, video)
|
||||||
await processPreview(req, video)
|
await processPreview(req, video)
|
||||||
|
@ -128,7 +130,7 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
|
||||||
}).end()
|
}).end()
|
||||||
}
|
}
|
||||||
|
|
||||||
const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo)
|
const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo, user)
|
||||||
|
|
||||||
const downloadThumbnail = !await processThumbnail(req, video)
|
const downloadThumbnail = !await processThumbnail(req, video)
|
||||||
const downloadPreview = !await processPreview(req, video)
|
const downloadPreview = !await processPreview(req, video)
|
||||||
|
@ -156,7 +158,7 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
|
||||||
return res.json(videoImport.toFormattedJSON()).end()
|
return res.json(videoImport.toFormattedJSON()).end()
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildVideo (channelId: number, body: VideoImportCreate, importData: YoutubeDLInfo) {
|
function buildVideo (channelId: number, body: VideoImportCreate, importData: YoutubeDLInfo, user: UserModel) {
|
||||||
const videoData = {
|
const videoData = {
|
||||||
name: body.name || importData.name || 'Unknown name',
|
name: body.name || importData.name || 'Unknown name',
|
||||||
remote: false,
|
remote: false,
|
||||||
|
@ -218,6 +220,8 @@ function insertIntoDB (
|
||||||
const videoCreated = await video.save(sequelizeOptions)
|
const videoCreated = await video.save(sequelizeOptions)
|
||||||
videoCreated.VideoChannel = videoChannel
|
videoCreated.VideoChannel = videoChannel
|
||||||
|
|
||||||
|
await autoBlacklistVideoIfNeeded(video, videoChannel.Account.User, t)
|
||||||
|
|
||||||
// Set tags to the video
|
// Set tags to the video
|
||||||
if (tags) {
|
if (tags) {
|
||||||
const tagInstances = await TagModel.findOrCreateTags(tags, t)
|
const tagInstances = await TagModel.findOrCreateTags(tags, t)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { processImage } from '../../../helpers/image-utils'
|
||||||
import { logger } from '../../../helpers/logger'
|
import { logger } from '../../../helpers/logger'
|
||||||
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
|
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
|
||||||
import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
|
import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
|
||||||
|
import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
|
||||||
import {
|
import {
|
||||||
CONFIG,
|
CONFIG,
|
||||||
MIMETYPES,
|
MIMETYPES,
|
||||||
|
@ -193,6 +194,7 @@ async function addVideo (req: express.Request, res: express.Response) {
|
||||||
channelId: res.locals.videoChannel.id,
|
channelId: res.locals.videoChannel.id,
|
||||||
originallyPublishedAt: videoInfo.originallyPublishedAt
|
originallyPublishedAt: videoInfo.originallyPublishedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
const video = new VideoModel(videoData)
|
const video = new VideoModel(videoData)
|
||||||
video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object
|
video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object
|
||||||
|
|
||||||
|
@ -237,7 +239,7 @@ async function addVideo (req: express.Request, res: express.Response) {
|
||||||
// Create the torrent file
|
// Create the torrent file
|
||||||
await video.createTorrentAndSetInfoHash(videoFile)
|
await video.createTorrentAndSetInfoHash(videoFile)
|
||||||
|
|
||||||
const videoCreated = await sequelizeTypescript.transaction(async t => {
|
const { videoCreated, videoWasAutoBlacklisted } = await sequelizeTypescript.transaction(async t => {
|
||||||
const sequelizeOptions = { transaction: t }
|
const sequelizeOptions = { transaction: t }
|
||||||
|
|
||||||
const videoCreated = await video.save(sequelizeOptions)
|
const videoCreated = await video.save(sequelizeOptions)
|
||||||
|
@ -266,15 +268,23 @@ async function addVideo (req: express.Request, res: express.Response) {
|
||||||
}, { transaction: t })
|
}, { transaction: t })
|
||||||
}
|
}
|
||||||
|
|
||||||
await federateVideoIfNeeded(video, true, t)
|
const videoWasAutoBlacklisted = await autoBlacklistVideoIfNeeded(video, res.locals.oauth.token.User, t)
|
||||||
|
|
||||||
|
if (!videoWasAutoBlacklisted) {
|
||||||
|
await federateVideoIfNeeded(video, true, t)
|
||||||
|
}
|
||||||
|
|
||||||
auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON()))
|
auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON()))
|
||||||
logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid)
|
logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid)
|
||||||
|
|
||||||
return videoCreated
|
return { videoCreated, videoWasAutoBlacklisted }
|
||||||
})
|
})
|
||||||
|
|
||||||
Notifier.Instance.notifyOnNewVideo(videoCreated)
|
if (videoWasAutoBlacklisted) {
|
||||||
|
Notifier.Instance.notifyOnVideoAutoBlacklist(videoCreated)
|
||||||
|
} else {
|
||||||
|
Notifier.Instance.notifyOnNewVideo(videoCreated)
|
||||||
|
}
|
||||||
|
|
||||||
if (video.state === VideoState.TO_TRANSCODE) {
|
if (video.state === VideoState.TO_TRANSCODE) {
|
||||||
// Put uuid because we don't have id auto incremented for now
|
// Put uuid because we don't have id auto incremented for now
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { Response } from 'express'
|
import { Response } from 'express'
|
||||||
import * as validator from 'validator'
|
import * as validator from 'validator'
|
||||||
|
import { exists } from './misc'
|
||||||
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
||||||
import { VideoBlacklistModel } from '../../models/video/video-blacklist'
|
import { VideoBlacklistModel } from '../../models/video/video-blacklist'
|
||||||
|
import { VideoBlacklistType } from '../../../shared/models/videos'
|
||||||
|
|
||||||
const VIDEO_BLACKLIST_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_BLACKLIST
|
const VIDEO_BLACKLIST_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_BLACKLIST
|
||||||
|
|
||||||
|
@ -24,9 +26,14 @@ async function doesVideoBlacklistExist (videoId: number, res: Response) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isVideoBlacklistTypeValid (value: any) {
|
||||||
|
return exists(value) && validator.isInt('' + value) && VideoBlacklistType[value] !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
isVideoBlacklistReasonValid,
|
isVideoBlacklistReasonValid,
|
||||||
|
isVideoBlacklistTypeValid,
|
||||||
doesVideoBlacklistExist
|
doesVideoBlacklistExist
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import { CONFIG } from '../initializers'
|
||||||
import { VideoModel } from '../models/video/video'
|
import { VideoModel } from '../models/video/video'
|
||||||
|
import { UserRight } from '../../shared'
|
||||||
|
import { UserModel } from '../models/account/user'
|
||||||
|
|
||||||
type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none'
|
type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none'
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ function checkMissedConfig () {
|
||||||
'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
|
'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
|
||||||
'redundancy.videos.strategies', 'redundancy.videos.check_interval',
|
'redundancy.videos.strategies', 'redundancy.videos.check_interval',
|
||||||
'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions',
|
'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions',
|
||||||
'import.videos.http.enabled', 'import.videos.torrent.enabled',
|
'import.videos.http.enabled', 'import.videos.torrent.enabled', 'auto_blacklist.videos.of_users.enabled',
|
||||||
'trending.videos.interval_days',
|
'trending.videos.interval_days',
|
||||||
'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
|
'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
|
||||||
'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt',
|
'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt',
|
||||||
|
|
|
@ -18,7 +18,7 @@ let config: IConfig = require('config')
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const LAST_MIGRATION_VERSION = 345
|
const LAST_MIGRATION_VERSION = 350
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -288,6 +288,13 @@ const CONFIG = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
AUTO_BLACKLIST: {
|
||||||
|
VIDEOS: {
|
||||||
|
OF_USERS: {
|
||||||
|
get ENABLED () { return config.get<boolean>('auto_blacklist.videos.of_users.enabled') }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
CACHE: {
|
CACHE: {
|
||||||
PREVIEWS: {
|
PREVIEWS: {
|
||||||
get SIZE () { return config.get<number>('cache.previews.size') }
|
get SIZE () { return config.get<number>('cache.previews.size') }
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
import * as Sequelize from 'sequelize'
|
||||||
|
import { VideoBlacklistType } from '../../../shared/models/videos'
|
||||||
|
|
||||||
|
async function up (utils: {
|
||||||
|
transaction: Sequelize.Transaction,
|
||||||
|
queryInterface: Sequelize.QueryInterface,
|
||||||
|
sequelize: Sequelize.Sequelize,
|
||||||
|
db: any
|
||||||
|
}): Promise<void> {
|
||||||
|
{
|
||||||
|
const data = {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null
|
||||||
|
}
|
||||||
|
|
||||||
|
await utils.queryInterface.addColumn('videoBlacklist', 'type', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const query = 'UPDATE "videoBlacklist" SET "type" = ' + VideoBlacklistType.MANUAL
|
||||||
|
await utils.sequelize.query(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const data = {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: null
|
||||||
|
}
|
||||||
|
await utils.queryInterface.changeColumn('videoBlacklist', 'type', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const data = {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
defaultValue: null,
|
||||||
|
allowNull: true
|
||||||
|
}
|
||||||
|
await utils.queryInterface.addColumn('userNotificationSetting', 'videoAutoBlacklistAsModerator', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const query = 'UPDATE "userNotificationSetting" SET "videoAutoBlacklistAsModerator" = 3'
|
||||||
|
await utils.sequelize.query(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const data = {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
defaultValue: null,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
await utils.queryInterface.changeColumn('userNotificationSetting', 'videoAutoBlacklistAsModerator', data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function down (options) {
|
||||||
|
throw new Error('Not implemented.')
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
up,
|
||||||
|
down
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ import { VideoShareModel } from '../../models/video/video-share'
|
||||||
import { VideoCommentModel } from '../../models/video/video-comment'
|
import { VideoCommentModel } from '../../models/video/video-comment'
|
||||||
|
|
||||||
async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
|
async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
|
||||||
// If the video is not private and published, we federate it
|
// If the video is not private and is published, we federate it
|
||||||
if (video.privacy !== VideoPrivacy.PRIVATE && video.state === VideoState.PUBLISHED) {
|
if (video.privacy !== VideoPrivacy.PRIVATE && video.state === VideoState.PUBLISHED) {
|
||||||
// Fetch more attributes that we will need to serialize in AP object
|
// Fetch more attributes that we will need to serialize in AP object
|
||||||
if (isArray(video.VideoCaptions) === false) {
|
if (isArray(video.VideoCaptions) === false) {
|
||||||
|
|
|
@ -250,6 +250,29 @@ class Emailer {
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addVideoAutoBlacklistModeratorsNotification (to: string[], video: VideoModel) {
|
||||||
|
const VIDEO_AUTO_BLACKLIST_URL = CONFIG.WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
|
||||||
|
const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
|
||||||
|
|
||||||
|
const text = `Hi,\n\n` +
|
||||||
|
`A recently added video was auto-blacklisted and requires moderator review before publishing.` +
|
||||||
|
`\n\n` +
|
||||||
|
`You can view it and take appropriate action on ${videoUrl}` +
|
||||||
|
`\n\n` +
|
||||||
|
`A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` +
|
||||||
|
`\n\n` +
|
||||||
|
`Cheers,\n` +
|
||||||
|
`PeerTube.`
|
||||||
|
|
||||||
|
const emailPayload: EmailPayload = {
|
||||||
|
to,
|
||||||
|
subject: '[PeerTube] An auto-blacklisted video is awaiting review',
|
||||||
|
text
|
||||||
|
}
|
||||||
|
|
||||||
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
}
|
||||||
|
|
||||||
addNewUserRegistrationNotification (to: string[], user: UserModel) {
|
addNewUserRegistrationNotification (to: string[], user: UserModel) {
|
||||||
const text = `Hi,\n\n` +
|
const text = `Hi,\n\n` +
|
||||||
`User ${user.username} just registered on ${CONFIG.WEBSERVER.HOST} PeerTube instance.\n\n` +
|
`User ${user.username} just registered on ${CONFIG.WEBSERVER.HOST} PeerTube instance.\n\n` +
|
||||||
|
|
|
@ -196,9 +196,14 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
|
||||||
return videoImportUpdated
|
return videoImportUpdated
|
||||||
})
|
})
|
||||||
|
|
||||||
Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video)
|
|
||||||
Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true)
|
Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true)
|
||||||
|
|
||||||
|
if (videoImportUpdated.Video.VideoBlacklist) {
|
||||||
|
Notifier.Instance.notifyOnVideoAutoBlacklist(videoImportUpdated.Video)
|
||||||
|
} else {
|
||||||
|
Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video)
|
||||||
|
}
|
||||||
|
|
||||||
// Create transcoding jobs?
|
// Create transcoding jobs?
|
||||||
if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) {
|
if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) {
|
||||||
// Put uuid because we don't have id auto incremented for now
|
// Put uuid because we don't have id auto incremented for now
|
||||||
|
|
|
@ -85,10 +85,9 @@ async function publishVideoIfNeeded (video: VideoModel, payload?: VideoTranscodi
|
||||||
return { videoDatabase, videoPublished }
|
return { videoDatabase, videoPublished }
|
||||||
})
|
})
|
||||||
|
|
||||||
// don't notify prior to scheduled video update
|
if (videoPublished) {
|
||||||
if (videoPublished && !videoDatabase.ScheduleVideoUpdate) {
|
|
||||||
Notifier.Instance.notifyOnNewVideo(videoDatabase)
|
Notifier.Instance.notifyOnNewVideo(videoDatabase)
|
||||||
Notifier.Instance.notifyOnPendingVideoPublished(videoDatabase)
|
Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase)
|
||||||
}
|
}
|
||||||
|
|
||||||
await createHlsJobIfEnabled(payload)
|
await createHlsJobIfEnabled(payload)
|
||||||
|
@ -146,11 +145,8 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: Video
|
||||||
return { videoDatabase, videoPublished }
|
return { videoDatabase, videoPublished }
|
||||||
})
|
})
|
||||||
|
|
||||||
// don't notify prior to scheduled video update
|
if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase)
|
||||||
if (!videoDatabase.ScheduleVideoUpdate) {
|
if (videoPublished) Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase)
|
||||||
if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase)
|
|
||||||
if (videoPublished) Notifier.Instance.notifyOnPendingVideoPublished(videoDatabase)
|
|
||||||
}
|
|
||||||
|
|
||||||
await createHlsJobIfEnabled(Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution }))
|
await createHlsJobIfEnabled(Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,19 +23,35 @@ class Notifier {
|
||||||
private constructor () {}
|
private constructor () {}
|
||||||
|
|
||||||
notifyOnNewVideo (video: VideoModel): void {
|
notifyOnNewVideo (video: VideoModel): void {
|
||||||
// Only notify on public and published videos
|
// Only notify on public and published videos which are not blacklisted
|
||||||
if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED) return
|
if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.VideoBlacklist) return
|
||||||
|
|
||||||
this.notifySubscribersOfNewVideo(video)
|
this.notifySubscribersOfNewVideo(video)
|
||||||
.catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
|
.catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyOnPendingVideoPublished (video: VideoModel): void {
|
notifyOnVideoPublishedAfterTranscoding (video: VideoModel): void {
|
||||||
// Only notify on public videos that has been published while the user waited transcoding/scheduled update
|
// don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update
|
||||||
if (video.waitTranscoding === false && !video.ScheduleVideoUpdate) return
|
if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return
|
||||||
|
|
||||||
this.notifyOwnedVideoHasBeenPublished(video)
|
this.notifyOwnedVideoHasBeenPublished(video)
|
||||||
.catch(err => logger.error('Cannot notify owner that its video %s has been published.', video.url, { err }))
|
.catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err }))
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyOnVideoPublishedAfterScheduledUpdate (video: VideoModel): void {
|
||||||
|
// don't notify if video is still blacklisted or waiting for transcoding
|
||||||
|
if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
|
||||||
|
|
||||||
|
this.notifyOwnedVideoHasBeenPublished(video)
|
||||||
|
.catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err }))
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: VideoModel): void {
|
||||||
|
// don't notify if video is still waiting for transcoding or scheduled update
|
||||||
|
if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
|
||||||
|
|
||||||
|
this.notifyOwnedVideoHasBeenPublished(video)
|
||||||
|
.catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyOnNewComment (comment: VideoCommentModel): void {
|
notifyOnNewComment (comment: VideoCommentModel): void {
|
||||||
|
@ -51,6 +67,11 @@ class Notifier {
|
||||||
.catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err }))
|
.catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notifyOnVideoAutoBlacklist (video: VideoModel): void {
|
||||||
|
this.notifyModeratorsOfVideoAutoBlacklist(video)
|
||||||
|
.catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', video.url, { err }))
|
||||||
|
}
|
||||||
|
|
||||||
notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void {
|
notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void {
|
||||||
this.notifyVideoOwnerOfBlacklist(videoBlacklist)
|
this.notifyVideoOwnerOfBlacklist(videoBlacklist)
|
||||||
.catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
|
.catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
|
||||||
|
@ -58,7 +79,7 @@ class Notifier {
|
||||||
|
|
||||||
notifyOnVideoUnblacklist (video: VideoModel): void {
|
notifyOnVideoUnblacklist (video: VideoModel): void {
|
||||||
this.notifyVideoOwnerOfUnblacklist(video)
|
this.notifyVideoOwnerOfUnblacklist(video)
|
||||||
.catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', video.url, { err }))
|
.catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err }))
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void {
|
notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void {
|
||||||
|
@ -268,6 +289,34 @@ class Notifier {
|
||||||
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
|
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async notifyModeratorsOfVideoAutoBlacklist (video: VideoModel) {
|
||||||
|
const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST)
|
||||||
|
if (moderators.length === 0) return
|
||||||
|
|
||||||
|
logger.info('Notifying %s moderators of video auto-blacklist %s.', moderators.length, video.url)
|
||||||
|
|
||||||
|
function settingGetter (user: UserModel) {
|
||||||
|
return user.NotificationSetting.videoAutoBlacklistAsModerator
|
||||||
|
}
|
||||||
|
async function notificationCreator (user: UserModel) {
|
||||||
|
|
||||||
|
const notification = await UserNotificationModel.create({
|
||||||
|
type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS,
|
||||||
|
userId: user.id,
|
||||||
|
videoId: video.id
|
||||||
|
})
|
||||||
|
notification.Video = video
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
function emailSender (emails: string[]) {
|
||||||
|
return Emailer.Instance.addVideoAutoBlacklistModeratorsNotification(emails, video)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
|
||||||
|
}
|
||||||
|
|
||||||
private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) {
|
private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) {
|
||||||
const user = await UserModel.loadByVideoId(videoBlacklist.videoId)
|
const user = await UserModel.loadByVideoId(videoBlacklist.videoId)
|
||||||
if (!user) return
|
if (!user) return
|
||||||
|
|
|
@ -57,7 +57,7 @@ export class UpdateVideosScheduler extends AbstractScheduler {
|
||||||
|
|
||||||
for (const v of publishedVideos) {
|
for (const v of publishedVideos) {
|
||||||
Notifier.Instance.notifyOnNewVideo(v)
|
Notifier.Instance.notifyOnNewVideo(v)
|
||||||
Notifier.Instance.notifyOnPendingVideoPublished(v)
|
Notifier.Instance.notifyOnVideoPublishedAfterScheduledUpdate(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,7 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Tr
|
||||||
myVideoImportFinished: UserNotificationSettingValue.WEB,
|
myVideoImportFinished: UserNotificationSettingValue.WEB,
|
||||||
myVideoPublished: UserNotificationSettingValue.WEB,
|
myVideoPublished: UserNotificationSettingValue.WEB,
|
||||||
videoAbuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
videoAbuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
|
videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
newUserRegistration: UserNotificationSettingValue.WEB,
|
newUserRegistration: UserNotificationSettingValue.WEB,
|
||||||
commentMention: UserNotificationSettingValue.WEB,
|
commentMention: UserNotificationSettingValue.WEB,
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import * as sequelize from 'sequelize'
|
||||||
|
import { CONFIG } from '../initializers/constants'
|
||||||
|
import { VideoBlacklistType, UserRight } from '../../shared/models'
|
||||||
|
import { VideoBlacklistModel } from '../models/video/video-blacklist'
|
||||||
|
import { UserModel } from '../models/account/user'
|
||||||
|
import { VideoModel } from '../models/video/video'
|
||||||
|
import { logger } from '../helpers/logger'
|
||||||
|
|
||||||
|
async function autoBlacklistVideoIfNeeded (video: VideoModel, user: UserModel, transaction: sequelize.Transaction) {
|
||||||
|
if (!CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED) return false
|
||||||
|
|
||||||
|
if (user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) return false
|
||||||
|
|
||||||
|
const sequelizeOptions = { transaction }
|
||||||
|
const videoBlacklistToCreate = {
|
||||||
|
videoId: video.id,
|
||||||
|
unfederated: true,
|
||||||
|
reason: 'Auto-blacklisted. Moderator review required.',
|
||||||
|
type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
|
||||||
|
}
|
||||||
|
await VideoBlacklistModel.create(videoBlacklistToCreate, sequelizeOptions)
|
||||||
|
logger.info('Video %s auto-blacklisted.', video.uuid)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
autoBlacklistVideoIfNeeded
|
||||||
|
}
|
|
@ -1,10 +1,14 @@
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import { body, param } from 'express-validator/check'
|
import { body, param, query } from 'express-validator/check'
|
||||||
import { isBooleanValid, isIdOrUUIDValid } from '../../../helpers/custom-validators/misc'
|
import { isBooleanValid, isIdOrUUIDValid } from '../../../helpers/custom-validators/misc'
|
||||||
import { doesVideoExist } from '../../../helpers/custom-validators/videos'
|
import { doesVideoExist } from '../../../helpers/custom-validators/videos'
|
||||||
import { logger } from '../../../helpers/logger'
|
import { logger } from '../../../helpers/logger'
|
||||||
import { areValidationErrors } from '../utils'
|
import { areValidationErrors } from '../utils'
|
||||||
import { doesVideoBlacklistExist, isVideoBlacklistReasonValid } from '../../../helpers/custom-validators/video-blacklist'
|
import {
|
||||||
|
doesVideoBlacklistExist,
|
||||||
|
isVideoBlacklistReasonValid,
|
||||||
|
isVideoBlacklistTypeValid
|
||||||
|
} from '../../../helpers/custom-validators/video-blacklist'
|
||||||
|
|
||||||
const videosBlacklistRemoveValidator = [
|
const videosBlacklistRemoveValidator = [
|
||||||
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
||||||
|
@ -65,10 +69,25 @@ const videosBlacklistUpdateValidator = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const videosBlacklistFiltersValidator = [
|
||||||
|
query('type')
|
||||||
|
.optional()
|
||||||
|
.custom(isVideoBlacklistTypeValid).withMessage('Should have a valid video blacklist type attribute'),
|
||||||
|
|
||||||
|
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
|
logger.debug('Checking videos blacklist filters query', { parameters: req.query })
|
||||||
|
|
||||||
|
if (areValidationErrors(req, res)) return
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
videosBlacklistAddValidator,
|
videosBlacklistAddValidator,
|
||||||
videosBlacklistRemoveValidator,
|
videosBlacklistRemoveValidator,
|
||||||
videosBlacklistUpdateValidator
|
videosBlacklistUpdateValidator,
|
||||||
|
videosBlacklistFiltersValidator
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,15 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
|
||||||
@Column
|
@Column
|
||||||
videoAbuseAsModerator: UserNotificationSettingValue
|
videoAbuseAsModerator: UserNotificationSettingValue
|
||||||
|
|
||||||
|
@AllowNull(false)
|
||||||
|
@Default(null)
|
||||||
|
@Is(
|
||||||
|
'UserNotificationSettingVideoAutoBlacklistAsModerator',
|
||||||
|
value => throwIfNotValid(value, isUserNotificationSettingValid, 'videoAutoBlacklistAsModerator')
|
||||||
|
)
|
||||||
|
@Column
|
||||||
|
videoAutoBlacklistAsModerator: UserNotificationSettingValue
|
||||||
|
|
||||||
@AllowNull(false)
|
@AllowNull(false)
|
||||||
@Default(null)
|
@Default(null)
|
||||||
@Is(
|
@Is(
|
||||||
|
@ -139,6 +148,7 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
|
||||||
newCommentOnMyVideo: this.newCommentOnMyVideo,
|
newCommentOnMyVideo: this.newCommentOnMyVideo,
|
||||||
newVideoFromSubscription: this.newVideoFromSubscription,
|
newVideoFromSubscription: this.newVideoFromSubscription,
|
||||||
videoAbuseAsModerator: this.videoAbuseAsModerator,
|
videoAbuseAsModerator: this.videoAbuseAsModerator,
|
||||||
|
videoAutoBlacklistAsModerator: this.videoAutoBlacklistAsModerator,
|
||||||
blacklistOnMyVideo: this.blacklistOnMyVideo,
|
blacklistOnMyVideo: this.blacklistOnMyVideo,
|
||||||
myVideoPublished: this.myVideoPublished,
|
myVideoPublished: this.myVideoPublished,
|
||||||
myVideoImportFinished: this.myVideoImportFinished,
|
myVideoImportFinished: this.myVideoImportFinished,
|
||||||
|
|
|
@ -72,7 +72,8 @@ export class ScheduleVideoUpdateModel extends Model<ScheduleVideoUpdateModel> {
|
||||||
model: VideoModel.scope(
|
model: VideoModel.scope(
|
||||||
[
|
[
|
||||||
VideoScopeNames.WITH_FILES,
|
VideoScopeNames.WITH_FILES,
|
||||||
VideoScopeNames.WITH_ACCOUNT_DETAILS
|
VideoScopeNames.WITH_ACCOUNT_DETAILS,
|
||||||
|
VideoScopeNames.WITH_BLACKLISTED
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,21 @@
|
||||||
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
|
import {
|
||||||
|
AllowNull,
|
||||||
|
BelongsTo,
|
||||||
|
Column,
|
||||||
|
CreatedAt,
|
||||||
|
DataType,
|
||||||
|
Default,
|
||||||
|
ForeignKey,
|
||||||
|
Is, Model,
|
||||||
|
Table,
|
||||||
|
UpdatedAt,
|
||||||
|
IFindOptions
|
||||||
|
} from 'sequelize-typescript'
|
||||||
import { getSortOnModel, SortType, throwIfNotValid } from '../utils'
|
import { getSortOnModel, SortType, throwIfNotValid } from '../utils'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
import { isVideoBlacklistReasonValid } from '../../helpers/custom-validators/video-blacklist'
|
import { VideoChannelModel, ScopeNames as VideoChannelScopeNames } from './video-channel'
|
||||||
import { VideoBlacklist } from '../../../shared/models/videos'
|
import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist'
|
||||||
|
import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos'
|
||||||
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
|
@ -25,6 +38,12 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
|
||||||
@Column
|
@Column
|
||||||
unfederated: boolean
|
unfederated: boolean
|
||||||
|
|
||||||
|
@AllowNull(false)
|
||||||
|
@Default(null)
|
||||||
|
@Is('VideoBlacklistType', value => throwIfNotValid(value, isVideoBlacklistTypeValid, 'type'))
|
||||||
|
@Column
|
||||||
|
type: VideoBlacklistType
|
||||||
|
|
||||||
@CreatedAt
|
@CreatedAt
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
|
|
||||||
|
@ -43,19 +62,29 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
|
||||||
})
|
})
|
||||||
Video: VideoModel
|
Video: VideoModel
|
||||||
|
|
||||||
static listForApi (start: number, count: number, sort: SortType) {
|
static listForApi (start: number, count: number, sort: SortType, type?: VideoBlacklistType) {
|
||||||
const query = {
|
const query: IFindOptions<VideoBlacklistModel> = {
|
||||||
offset: start,
|
offset: start,
|
||||||
limit: count,
|
limit: count,
|
||||||
order: getSortOnModel(sort.sortModel, sort.sortValue),
|
order: getSortOnModel(sort.sortModel, sort.sortValue),
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: VideoModel,
|
model: VideoModel,
|
||||||
required: true
|
required: true,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, true ] }),
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type) {
|
||||||
|
query.where = { type }
|
||||||
|
}
|
||||||
|
|
||||||
return VideoBlacklistModel.findAndCountAll(query)
|
return VideoBlacklistModel.findAndCountAll(query)
|
||||||
.then(({ rows, count }) => {
|
.then(({ rows, count }) => {
|
||||||
return {
|
return {
|
||||||
|
@ -76,26 +105,15 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
toFormattedJSON (): VideoBlacklist {
|
toFormattedJSON (): VideoBlacklist {
|
||||||
const video = this.Video
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
createdAt: this.createdAt,
|
createdAt: this.createdAt,
|
||||||
updatedAt: this.updatedAt,
|
updatedAt: this.updatedAt,
|
||||||
reason: this.reason,
|
reason: this.reason,
|
||||||
unfederated: this.unfederated,
|
unfederated: this.unfederated,
|
||||||
|
type: this.type,
|
||||||
|
|
||||||
video: {
|
video: this.Video.toFormattedJSON()
|
||||||
id: video.id,
|
|
||||||
name: video.name,
|
|
||||||
uuid: video.uuid,
|
|
||||||
description: video.description,
|
|
||||||
duration: video.duration,
|
|
||||||
views: video.views,
|
|
||||||
likes: video.likes,
|
|
||||||
dislikes: video.dislikes,
|
|
||||||
nsfw: video.nsfw
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,13 @@ describe('Test config API validators', function () {
|
||||||
enabled: false
|
enabled: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
autoBlacklist: {
|
||||||
|
videos: {
|
||||||
|
ofUsers: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -168,6 +168,7 @@ describe('Test user notifications API validators', function () {
|
||||||
newVideoFromSubscription: UserNotificationSettingValue.WEB,
|
newVideoFromSubscription: UserNotificationSettingValue.WEB,
|
||||||
newCommentOnMyVideo: UserNotificationSettingValue.WEB,
|
newCommentOnMyVideo: UserNotificationSettingValue.WEB,
|
||||||
videoAbuseAsModerator: UserNotificationSettingValue.WEB,
|
videoAbuseAsModerator: UserNotificationSettingValue.WEB,
|
||||||
|
videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB,
|
||||||
blacklistOnMyVideo: UserNotificationSettingValue.WEB,
|
blacklistOnMyVideo: UserNotificationSettingValue.WEB,
|
||||||
myVideoImportFinished: UserNotificationSettingValue.WEB,
|
myVideoImportFinished: UserNotificationSettingValue.WEB,
|
||||||
myVideoPublished: UserNotificationSettingValue.WEB,
|
myVideoPublished: UserNotificationSettingValue.WEB,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
flushAndRunMultipleServers,
|
flushAndRunMultipleServers,
|
||||||
flushTests,
|
flushTests,
|
||||||
getBlacklistedVideosList,
|
getBlacklistedVideosList,
|
||||||
|
getBlacklistedVideosListWithTypeFilter,
|
||||||
getVideo,
|
getVideo,
|
||||||
getVideoWithToken,
|
getVideoWithToken,
|
||||||
killallServers,
|
killallServers,
|
||||||
|
@ -24,7 +25,7 @@ import {
|
||||||
checkBadSortPagination,
|
checkBadSortPagination,
|
||||||
checkBadStartPagination
|
checkBadStartPagination
|
||||||
} from '../../../../shared/utils/requests/check-api-params'
|
} from '../../../../shared/utils/requests/check-api-params'
|
||||||
import { VideoDetails } from '../../../../shared/models/videos'
|
import { VideoDetails, VideoBlacklistType } from '../../../../shared/models/videos'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
|
|
||||||
describe('Test video blacklist API validators', function () {
|
describe('Test video blacklist API validators', function () {
|
||||||
|
@ -238,6 +239,14 @@ describe('Test video blacklist API validators', function () {
|
||||||
it('Should fail with an incorrect sort', async function () {
|
it('Should fail with an incorrect sort', async function () {
|
||||||
await checkBadSortPagination(servers[0].url, basePath, servers[0].accessToken)
|
await checkBadSortPagination(servers[0].url, basePath, servers[0].accessToken)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should fail with an invalid type', async function () {
|
||||||
|
await getBlacklistedVideosListWithTypeFilter(servers[0].url, servers[0].accessToken, 0, 400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should succeed with the correct parameters', async function () {
|
||||||
|
await getBlacklistedVideosListWithTypeFilter(servers[0].url, servers[0].accessToken, VideoBlacklistType.MANUAL)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
|
|
|
@ -7,7 +7,8 @@ import { join } from 'path'
|
||||||
import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum'
|
import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum'
|
||||||
import {
|
import {
|
||||||
createUser, flushTests, getMyUserInformation, getVideo, getVideosList, immutableAssign, killallServers, makeDeleteRequest,
|
createUser, flushTests, getMyUserInformation, getVideo, getVideosList, immutableAssign, killallServers, makeDeleteRequest,
|
||||||
makeGetRequest, makeUploadRequest, makePutBodyRequest, removeVideo, runServer, ServerInfo, setAccessTokensToServers, userLogin
|
makeGetRequest, makeUploadRequest, makePutBodyRequest, removeVideo, uploadVideo,
|
||||||
|
runServer, ServerInfo, setAccessTokensToServers, userLogin, updateCustomSubConfig
|
||||||
} from '../../../../shared/utils'
|
} from '../../../../shared/utils'
|
||||||
import {
|
import {
|
||||||
checkBadCountPagination,
|
checkBadCountPagination,
|
||||||
|
|
|
@ -62,6 +62,7 @@ function checkInitialConfig (data: CustomConfig) {
|
||||||
|
|
||||||
expect(data.import.videos.http.enabled).to.be.true
|
expect(data.import.videos.http.enabled).to.be.true
|
||||||
expect(data.import.videos.torrent.enabled).to.be.true
|
expect(data.import.videos.torrent.enabled).to.be.true
|
||||||
|
expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.false
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkUpdatedConfig (data: CustomConfig) {
|
function checkUpdatedConfig (data: CustomConfig) {
|
||||||
|
@ -103,6 +104,7 @@ function checkUpdatedConfig (data: CustomConfig) {
|
||||||
|
|
||||||
expect(data.import.videos.http.enabled).to.be.false
|
expect(data.import.videos.http.enabled).to.be.false
|
||||||
expect(data.import.videos.torrent.enabled).to.be.false
|
expect(data.import.videos.torrent.enabled).to.be.false
|
||||||
|
expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.true
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Test config', function () {
|
describe('Test config', function () {
|
||||||
|
@ -225,6 +227,13 @@ describe('Test config', function () {
|
||||||
enabled: false
|
enabled: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
autoBlacklist: {
|
||||||
|
videos: {
|
||||||
|
ofUsers: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await updateCustomConfig(server.url, server.accessToken, newCustomConfig)
|
await updateCustomConfig(server.url, server.accessToken, newCustomConfig)
|
||||||
|
|
|
@ -17,7 +17,9 @@ import {
|
||||||
updateVideo,
|
updateVideo,
|
||||||
updateVideoChannel,
|
updateVideoChannel,
|
||||||
userLogin,
|
userLogin,
|
||||||
wait
|
wait,
|
||||||
|
getCustomConfig,
|
||||||
|
updateCustomConfig
|
||||||
} from '../../../../shared/utils'
|
} from '../../../../shared/utils'
|
||||||
import { killallServers, ServerInfo, uploadVideo } from '../../../../shared/utils/index'
|
import { killallServers, ServerInfo, uploadVideo } from '../../../../shared/utils/index'
|
||||||
import { setAccessTokensToServers } from '../../../../shared/utils/users/login'
|
import { setAccessTokensToServers } from '../../../../shared/utils/users/login'
|
||||||
|
@ -31,6 +33,7 @@ import {
|
||||||
checkNewBlacklistOnMyVideo,
|
checkNewBlacklistOnMyVideo,
|
||||||
checkNewCommentOnMyVideo,
|
checkNewCommentOnMyVideo,
|
||||||
checkNewVideoAbuseForModerators,
|
checkNewVideoAbuseForModerators,
|
||||||
|
checkVideoAutoBlacklistForModerators,
|
||||||
checkNewVideoFromSubscription,
|
checkNewVideoFromSubscription,
|
||||||
checkUserRegistered,
|
checkUserRegistered,
|
||||||
checkVideoIsPublished,
|
checkVideoIsPublished,
|
||||||
|
@ -54,6 +57,7 @@ import { getBadVideoUrl, getYoutubeVideoUrl, importVideo } from '../../../../sha
|
||||||
import { addVideoCommentReply, addVideoCommentThread } from '../../../../shared/utils/videos/video-comments'
|
import { addVideoCommentReply, addVideoCommentThread } from '../../../../shared/utils/videos/video-comments'
|
||||||
import * as uuidv4 from 'uuid/v4'
|
import * as uuidv4 from 'uuid/v4'
|
||||||
import { addAccountToAccountBlocklist, removeAccountFromAccountBlocklist } from '../../../../shared/utils/users/blocklist'
|
import { addAccountToAccountBlocklist, removeAccountFromAccountBlocklist } from '../../../../shared/utils/users/blocklist'
|
||||||
|
import { CustomConfig } from '../../../../shared/models/server'
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
|
@ -92,6 +96,7 @@ describe('Test users notifications', function () {
|
||||||
newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
videoAbuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
videoAbuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
|
videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||||
|
@ -305,7 +310,7 @@ describe('Test users notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should send a new video notification after a video import', async function () {
|
it('Should send a new video notification after a video import', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(100000)
|
||||||
|
|
||||||
const name = 'video import ' + uuidv4()
|
const name = 'video import ' + uuidv4()
|
||||||
|
|
||||||
|
@ -907,6 +912,180 @@ describe('Test users notifications', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Video-related notifications when video auto-blacklist is enabled', function () {
|
||||||
|
let userBaseParams: CheckerBaseParams
|
||||||
|
let adminBaseParamsServer1: CheckerBaseParams
|
||||||
|
let adminBaseParamsServer2: CheckerBaseParams
|
||||||
|
let videoUUID: string
|
||||||
|
let videoName: string
|
||||||
|
let currentCustomConfig: CustomConfig
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
|
||||||
|
adminBaseParamsServer1 = {
|
||||||
|
server: servers[0],
|
||||||
|
emails,
|
||||||
|
socketNotifications: adminNotifications,
|
||||||
|
token: servers[0].accessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
adminBaseParamsServer2 = {
|
||||||
|
server: servers[1],
|
||||||
|
emails,
|
||||||
|
socketNotifications: adminNotificationsServer2,
|
||||||
|
token: servers[1].accessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
userBaseParams = {
|
||||||
|
server: servers[0],
|
||||||
|
emails,
|
||||||
|
socketNotifications: userNotifications,
|
||||||
|
token: userAccessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
const resCustomConfig = await getCustomConfig(servers[0].url, servers[0].accessToken)
|
||||||
|
currentCustomConfig = resCustomConfig.body
|
||||||
|
const autoBlacklistTestsCustomConfig = immutableAssign(currentCustomConfig, {
|
||||||
|
autoBlacklist: {
|
||||||
|
videos: {
|
||||||
|
ofUsers: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// enable transcoding otherwise own publish notification after transcoding not expected
|
||||||
|
autoBlacklistTestsCustomConfig.transcoding.enabled = true
|
||||||
|
await updateCustomConfig(servers[0].url, servers[0].accessToken, autoBlacklistTestsCustomConfig)
|
||||||
|
|
||||||
|
await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:9001')
|
||||||
|
await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:9001')
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should send notification to moderators on new video with auto-blacklist', async function () {
|
||||||
|
this.timeout(20000)
|
||||||
|
|
||||||
|
videoName = 'video with auto-blacklist ' + uuidv4()
|
||||||
|
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName })
|
||||||
|
videoUUID = resVideo.body.video.uuid
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
await checkVideoAutoBlacklistForModerators(adminBaseParamsServer1, videoUUID, videoName, 'presence')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not send video publish notification if auto-blacklisted', async function () {
|
||||||
|
await checkVideoIsPublished(userBaseParams, videoName, videoUUID, 'absence')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not send a local user subscription notification if auto-blacklisted', async function () {
|
||||||
|
await checkNewVideoFromSubscription(adminBaseParamsServer1, videoName, videoUUID, 'absence')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not send a remote user subscription notification if auto-blacklisted', async function () {
|
||||||
|
await checkNewVideoFromSubscription(adminBaseParamsServer2, videoName, videoUUID, 'absence')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should send video published and unblacklist after video unblacklisted', async function () {
|
||||||
|
this.timeout(20000)
|
||||||
|
|
||||||
|
await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, videoUUID)
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
// FIXME: Can't test as two notifications sent to same user and util only checks last one
|
||||||
|
// One notification might be better anyways
|
||||||
|
// await checkNewBlacklistOnMyVideo(userBaseParams, videoUUID, videoName, 'unblacklist')
|
||||||
|
// await checkVideoIsPublished(userBaseParams, videoName, videoUUID, 'presence')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should send a local user subscription notification after removed from blacklist', async function () {
|
||||||
|
await checkNewVideoFromSubscription(adminBaseParamsServer1, videoName, videoUUID, 'presence')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should send a remote user subscription notification after removed from blacklist', async function () {
|
||||||
|
await checkNewVideoFromSubscription(adminBaseParamsServer2, videoName, videoUUID, 'presence')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should send unblacklist but not published/subscription notes after unblacklisted if scheduled update pending', async function () {
|
||||||
|
this.timeout(20000)
|
||||||
|
|
||||||
|
let updateAt = new Date(new Date().getTime() + 100000)
|
||||||
|
|
||||||
|
const name = 'video with auto-blacklist and future schedule ' + uuidv4()
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name,
|
||||||
|
privacy: VideoPrivacy.PRIVATE,
|
||||||
|
scheduleUpdate: {
|
||||||
|
updateAt: updateAt.toISOString(),
|
||||||
|
privacy: VideoPrivacy.PUBLIC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resVideo = await uploadVideo(servers[0].url, userAccessToken, data)
|
||||||
|
const uuid = resVideo.body.video.uuid
|
||||||
|
|
||||||
|
await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, uuid)
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
await checkNewBlacklistOnMyVideo(userBaseParams, uuid, name, 'unblacklist')
|
||||||
|
|
||||||
|
// FIXME: Can't test absence as two notifications sent to same user and util only checks last one
|
||||||
|
// One notification might be better anyways
|
||||||
|
// await checkVideoIsPublished(userBaseParams, name, uuid, 'absence')
|
||||||
|
|
||||||
|
await checkNewVideoFromSubscription(adminBaseParamsServer1, name, uuid, 'absence')
|
||||||
|
await checkNewVideoFromSubscription(adminBaseParamsServer2, name, uuid, 'absence')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not send publish/subscription notifications after scheduled update if video still auto-blacklisted', async function () {
|
||||||
|
this.timeout(20000)
|
||||||
|
|
||||||
|
// In 2 seconds
|
||||||
|
let updateAt = new Date(new Date().getTime() + 2000)
|
||||||
|
|
||||||
|
const name = 'video with schedule done and still auto-blacklisted ' + uuidv4()
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name,
|
||||||
|
privacy: VideoPrivacy.PRIVATE,
|
||||||
|
scheduleUpdate: {
|
||||||
|
updateAt: updateAt.toISOString(),
|
||||||
|
privacy: VideoPrivacy.PUBLIC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resVideo = await uploadVideo(servers[0].url, userAccessToken, data)
|
||||||
|
const uuid = resVideo.body.video.uuid
|
||||||
|
|
||||||
|
await wait(6000)
|
||||||
|
await checkVideoIsPublished(userBaseParams, name, uuid, 'absence')
|
||||||
|
await checkNewVideoFromSubscription(adminBaseParamsServer1, name, uuid, 'absence')
|
||||||
|
await checkNewVideoFromSubscription(adminBaseParamsServer2, name, uuid, 'absence')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not send a notification to moderators on new video without auto-blacklist', async function () {
|
||||||
|
this.timeout(20000)
|
||||||
|
|
||||||
|
const name = 'video without auto-blacklist ' + uuidv4()
|
||||||
|
|
||||||
|
// admin with blacklist right will not be auto-blacklisted
|
||||||
|
const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name })
|
||||||
|
const uuid = resVideo.body.video.uuid
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
await checkVideoAutoBlacklistForModerators(adminBaseParamsServer1, uuid, name, 'absence')
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await updateCustomConfig(servers[0].url, servers[0].accessToken, currentCustomConfig)
|
||||||
|
|
||||||
|
await removeUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:9001')
|
||||||
|
await removeUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:9001')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('Mark as read', function () {
|
describe('Mark as read', function () {
|
||||||
it('Should mark as read some notifications', async function () {
|
it('Should mark as read some notifications', async function () {
|
||||||
const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 2, 3)
|
const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 2, 3)
|
||||||
|
@ -968,7 +1147,7 @@ describe('Test users notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should not have notifications', async function () {
|
it('Should not have notifications', async function () {
|
||||||
this.timeout(10000)
|
this.timeout(20000)
|
||||||
|
|
||||||
await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
|
await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
|
||||||
newVideoFromSubscription: UserNotificationSettingValue.NONE
|
newVideoFromSubscription: UserNotificationSettingValue.NONE
|
||||||
|
@ -987,7 +1166,7 @@ describe('Test users notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should only have web notifications', async function () {
|
it('Should only have web notifications', async function () {
|
||||||
this.timeout(10000)
|
this.timeout(20000)
|
||||||
|
|
||||||
await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
|
await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
|
||||||
newVideoFromSubscription: UserNotificationSettingValue.WEB
|
newVideoFromSubscription: UserNotificationSettingValue.WEB
|
||||||
|
@ -1013,7 +1192,7 @@ describe('Test users notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should only have mail notifications', async function () {
|
it('Should only have mail notifications', async function () {
|
||||||
this.timeout(10000)
|
this.timeout(20000)
|
||||||
|
|
||||||
await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
|
await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
|
||||||
newVideoFromSubscription: UserNotificationSettingValue.EMAIL
|
newVideoFromSubscription: UserNotificationSettingValue.EMAIL
|
||||||
|
@ -1039,7 +1218,7 @@ describe('Test users notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should have email and web notifications', async function () {
|
it('Should have email and web notifications', async function () {
|
||||||
this.timeout(10000)
|
this.timeout(20000)
|
||||||
|
|
||||||
await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
|
await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
|
||||||
newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
|
newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
addVideoToBlacklist,
|
addVideoToBlacklist,
|
||||||
flushAndRunMultipleServers,
|
flushAndRunMultipleServers,
|
||||||
getBlacklistedVideosList,
|
getBlacklistedVideosList,
|
||||||
|
getBlacklistedVideosListWithTypeFilter,
|
||||||
getMyVideos,
|
getMyVideos,
|
||||||
getSortedBlacklistedVideosList,
|
getSortedBlacklistedVideosList,
|
||||||
getVideosList,
|
getVideosList,
|
||||||
|
@ -22,7 +23,7 @@ import {
|
||||||
} from '../../../../shared/utils/index'
|
} from '../../../../shared/utils/index'
|
||||||
import { doubleFollow } from '../../../../shared/utils/server/follows'
|
import { doubleFollow } from '../../../../shared/utils/server/follows'
|
||||||
import { waitJobs } from '../../../../shared/utils/server/jobs'
|
import { waitJobs } from '../../../../shared/utils/server/jobs'
|
||||||
import { VideoBlacklist } from '../../../../shared/models/videos'
|
import { VideoBlacklist, VideoBlacklistType } from '../../../../shared/models/videos'
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ describe('Test video blacklist management', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('When listing blacklisted videos', function () {
|
describe('When listing manually blacklisted videos', function () {
|
||||||
it('Should display all the blacklisted videos', async function () {
|
it('Should display all the blacklisted videos', async function () {
|
||||||
const res = await getBlacklistedVideosList(servers[0].url, servers[0].accessToken)
|
const res = await getBlacklistedVideosList(servers[0].url, servers[0].accessToken)
|
||||||
|
|
||||||
|
@ -117,6 +118,26 @@ describe('Test video blacklist management', function () {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should display all the blacklisted videos when applying manual type filter', async function () {
|
||||||
|
const res = await getBlacklistedVideosListWithTypeFilter(servers[0].url, servers[0].accessToken, VideoBlacklistType.MANUAL)
|
||||||
|
|
||||||
|
expect(res.body.total).to.equal(2)
|
||||||
|
|
||||||
|
const blacklistedVideos = res.body.data
|
||||||
|
expect(blacklistedVideos).to.be.an('array')
|
||||||
|
expect(blacklistedVideos.length).to.equal(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should display nothing when applying automatic type filter', async function () {
|
||||||
|
const res = await getBlacklistedVideosListWithTypeFilter(servers[0].url, servers[0].accessToken, VideoBlacklistType.AUTO_BEFORE_PUBLISHED) // tslint:disable:max-line-length
|
||||||
|
|
||||||
|
expect(res.body.total).to.equal(0)
|
||||||
|
|
||||||
|
const blacklistedVideos = res.body.data
|
||||||
|
expect(blacklistedVideos).to.be.an('array')
|
||||||
|
expect(blacklistedVideos.length).to.equal(0)
|
||||||
|
})
|
||||||
|
|
||||||
it('Should get the correct sort when sorting by descending id', async function () {
|
it('Should get the correct sort when sorting by descending id', async function () {
|
||||||
const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-id')
|
const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-id')
|
||||||
expect(res.body.total).to.equal(2)
|
expect(res.body.total).to.equal(2)
|
||||||
|
|
|
@ -77,4 +77,13 @@ export interface CustomConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
autoBlacklist: {
|
||||||
|
videos: {
|
||||||
|
ofUsers: {
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,14 @@ export interface ServerConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
autoBlacklist: {
|
||||||
|
videos: {
|
||||||
|
ofUsers: {
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
avatar: {
|
avatar: {
|
||||||
file: {
|
file: {
|
||||||
size: {
|
size: {
|
||||||
|
|
|
@ -8,6 +8,7 @@ export interface UserNotificationSetting {
|
||||||
newVideoFromSubscription: UserNotificationSettingValue
|
newVideoFromSubscription: UserNotificationSettingValue
|
||||||
newCommentOnMyVideo: UserNotificationSettingValue
|
newCommentOnMyVideo: UserNotificationSettingValue
|
||||||
videoAbuseAsModerator: UserNotificationSettingValue
|
videoAbuseAsModerator: UserNotificationSettingValue
|
||||||
|
videoAutoBlacklistAsModerator: UserNotificationSettingValue
|
||||||
blacklistOnMyVideo: UserNotificationSettingValue
|
blacklistOnMyVideo: UserNotificationSettingValue
|
||||||
myVideoPublished: UserNotificationSettingValue
|
myVideoPublished: UserNotificationSettingValue
|
||||||
myVideoImportFinished: UserNotificationSettingValue
|
myVideoImportFinished: UserNotificationSettingValue
|
||||||
|
|
|
@ -13,7 +13,9 @@ export enum UserNotificationType {
|
||||||
|
|
||||||
NEW_USER_REGISTRATION = 9,
|
NEW_USER_REGISTRATION = 9,
|
||||||
NEW_FOLLOW = 10,
|
NEW_FOLLOW = 10,
|
||||||
COMMENT_MENTION = 11
|
COMMENT_MENTION = 11,
|
||||||
|
|
||||||
|
VIDEO_AUTO_BLACKLIST_FOR_MODERATORS = 12
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VideoInfo {
|
export interface VideoInfo {
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
|
import { Video } from '../video.model'
|
||||||
|
|
||||||
|
export enum VideoBlacklistType {
|
||||||
|
MANUAL = 1,
|
||||||
|
AUTO_BEFORE_PUBLISHED = 2
|
||||||
|
}
|
||||||
|
|
||||||
export interface VideoBlacklist {
|
export interface VideoBlacklist {
|
||||||
id: number
|
id: number
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
unfederated: boolean
|
unfederated: boolean
|
||||||
reason?: string
|
reason?: string
|
||||||
|
type: VideoBlacklistType
|
||||||
|
|
||||||
video: {
|
video: Video
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
uuid: string
|
|
||||||
description: string
|
|
||||||
duration: number
|
|
||||||
views: number
|
|
||||||
likes: number
|
|
||||||
dislikes: number
|
|
||||||
nsfw: boolean
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,13 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) {
|
||||||
enabled: false
|
enabled: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
autoBlacklist: {
|
||||||
|
videos: {
|
||||||
|
ofUsers: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ function updateMyNotificationSettings (url: string, token: string, settings: Use
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUserNotifications (
|
async function getUserNotifications (
|
||||||
url: string,
|
url: string,
|
||||||
token: string,
|
token: string,
|
||||||
start: number,
|
start: number,
|
||||||
|
@ -165,12 +165,15 @@ async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName
|
||||||
checkVideo(notification.video, videoName, videoUUID)
|
checkVideo(notification.video, videoName, videoUUID)
|
||||||
checkActor(notification.video.channel)
|
checkActor(notification.video.channel)
|
||||||
} else {
|
} else {
|
||||||
expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
|
expect(notification).to.satisfy((n: UserNotification) => {
|
||||||
|
return n === undefined || n.type !== UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION || n.video.name !== videoName
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emailFinder (email: object) {
|
function emailFinder (email: object) {
|
||||||
return email[ 'text' ].indexOf(videoUUID) !== -1
|
const text = email[ 'text' ]
|
||||||
|
return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkNotification(base, notificationChecker, emailFinder, type)
|
await checkNotification(base, notificationChecker, emailFinder, type)
|
||||||
|
@ -387,6 +390,31 @@ async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUU
|
||||||
await checkNotification(base, notificationChecker, emailFinder, type)
|
await checkNotification(base, notificationChecker, emailFinder, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
|
||||||
|
const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_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.video.id).to.be.a('number')
|
||||||
|
checkVideo(notification.video, videoName, videoUUID)
|
||||||
|
} else {
|
||||||
|
expect(notification).to.satisfy((n: UserNotification) => {
|
||||||
|
return n === undefined || n.video === undefined || n.video.uuid !== videoUUID
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function emailFinder (email: object) {
|
||||||
|
const text = email[ 'text' ]
|
||||||
|
return text.indexOf(videoUUID) !== -1 && email[ 'text' ].indexOf('video-auto-blacklist/list') !== -1
|
||||||
|
}
|
||||||
|
|
||||||
|
await checkNotification(base, notificationChecker, emailFinder, type)
|
||||||
|
}
|
||||||
|
|
||||||
async function checkNewBlacklistOnMyVideo (
|
async function checkNewBlacklistOnMyVideo (
|
||||||
base: CheckerBaseParams,
|
base: CheckerBaseParams,
|
||||||
videoUUID: string,
|
videoUUID: string,
|
||||||
|
@ -431,6 +459,7 @@ export {
|
||||||
checkCommentMention,
|
checkCommentMention,
|
||||||
updateMyNotificationSettings,
|
updateMyNotificationSettings,
|
||||||
checkNewVideoAbuseForModerators,
|
checkNewVideoAbuseForModerators,
|
||||||
|
checkVideoAutoBlacklistForModerators,
|
||||||
getUserNotifications,
|
getUserNotifications,
|
||||||
markAsReadNotifications,
|
markAsReadNotifications,
|
||||||
getLastNotification
|
getLastNotification
|
||||||
|
|
|
@ -51,6 +51,18 @@ function getBlacklistedVideosList (url: string, token: string, specialStatus = 2
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBlacklistedVideosListWithTypeFilter (url: string, token: string, type: number, specialStatus = 200) {
|
||||||
|
const path = '/api/v1/videos/blacklist/'
|
||||||
|
|
||||||
|
return request(url)
|
||||||
|
.get(path)
|
||||||
|
.query({ sort: 'createdAt', type })
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', 'Bearer ' + token)
|
||||||
|
.expect(specialStatus)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
}
|
||||||
|
|
||||||
function getSortedBlacklistedVideosList (url: string, token: string, sort: string, specialStatus = 200) {
|
function getSortedBlacklistedVideosList (url: string, token: string, sort: string, specialStatus = 200) {
|
||||||
const path = '/api/v1/videos/blacklist/'
|
const path = '/api/v1/videos/blacklist/'
|
||||||
|
|
||||||
|
@ -69,6 +81,7 @@ export {
|
||||||
addVideoToBlacklist,
|
addVideoToBlacklist,
|
||||||
removeVideoFromBlacklist,
|
removeVideoFromBlacklist,
|
||||||
getBlacklistedVideosList,
|
getBlacklistedVideosList,
|
||||||
|
getBlacklistedVideosListWithTypeFilter,
|
||||||
getSortedBlacklistedVideosList,
|
getSortedBlacklistedVideosList,
|
||||||
updateVideoBlacklist
|
updateVideoBlacklist
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as request from 'supertest'
|
import * as request from 'supertest'
|
||||||
|
|
||||||
function changeVideoOwnership (url: string, token: string, videoId: number | string, username) {
|
function changeVideoOwnership (url: string, token: string, videoId: number | string, username, expectedStatus = 204) {
|
||||||
const path = '/api/v1/videos/' + videoId + '/give-ownership'
|
const path = '/api/v1/videos/' + videoId + '/give-ownership'
|
||||||
|
|
||||||
return request(url)
|
return request(url)
|
||||||
|
@ -8,7 +8,7 @@ function changeVideoOwnership (url: string, token: string, videoId: number | str
|
||||||
.set('Accept', 'application/json')
|
.set('Accept', 'application/json')
|
||||||
.set('Authorization', 'Bearer ' + token)
|
.set('Authorization', 'Bearer ' + token)
|
||||||
.send({ username })
|
.send({ username })
|
||||||
.expect(204)
|
.expect(expectedStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVideoChangeOwnershipList (url: string, token: string) {
|
function getVideoChangeOwnershipList (url: string, token: string) {
|
||||||
|
|
Loading…
Reference in New Issue