<p-column field="reason" header="Reason"></p-column>
<p-column field="reporterServerHost" header="Reporter server host"></p-column>
<p-column field="reporterUsername" header="Reporter username"></p-column>
+ <p-column field="videoName" header="Video name"></p-column>
<p-column header="Video" styleClass="action-cell">
<ng-template pTemplate="body" let-videoAbuse="rowData">
<a [routerLink]="getRouterVideoLink(videoAbuse.videoId)" title="Go to the video">{{ videoAbuse.videoId }}</a>
} from '../../../middlewares'
import { VideoInstance } from '../../../models'
import { VideoAbuseCreate, UserRight } from '../../../../shared'
+import { sendVideoAbuse } from '../../../lib/index'
const abuseVideoRouter = express.Router()
async function reportVideoAbuse (req: express.Request, res: express.Response) {
const videoInstance = res.locals.video as VideoInstance
- const reporterUsername = res.locals.oauth.token.User.username
+ const reporterAccount = res.locals.oauth.token.User.Account
const body: VideoAbuseCreate = req.body
const abuseToCreate = {
- reporterUsername,
+ reporterAccountId: reporterAccount.id,
reason: body.reason,
- videoId: videoInstance.id,
- reporterServerId: null // This is our server that reported this abuse
+ videoId: videoInstance.id
}
await db.sequelize.transaction(async t => {
- const abuse = await db.VideoAbuse.create(abuseToCreate, { transaction: t })
- // We send the information to the destination server
+ const videoAbuseInstance = await db.VideoAbuse.create(abuseToCreate, { transaction: t })
+
+ // We send the video abuse to the origin server
if (videoInstance.isOwned() === false) {
- const reportData = {
- reporterUsername,
- reportReason: abuse.reason,
- videoUUID: videoInstance.uuid
- }
-
- // await friends.reportAbuseVideoToFriend(reportData, videoInstance, t)
- // TODO: send abuse to origin server
+ await sendVideoAbuse(reporterAccount, videoAbuseInstance, videoInstance, t)
}
})
return doRequestAndSaveToFile(options, thumbnailPath)
}
-function getActivityPubUrl (type: 'video' | 'videoChannel' | 'account', id: string) {
+function getActivityPubUrl (type: 'video' | 'videoChannel' | 'account' | 'videoAbuse', id: string) {
if (type === 'video') return CONFIG.WEBSERVER.URL + '/videos/watch/' + id
else if (type === 'videoChannel') return CONFIG.WEBSERVER.URL + '/video-channels/' + id
else if (type === 'account') return CONFIG.WEBSERVER.URL + '/account/' + id
+ else if (type === 'videoAbuse') return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + id
return ''
}
'nsfw': 'as:sensitive',
'language': 'http://schema.org/inLanguage',
'views': 'http://schema.org/Number',
- 'size': 'http://schema.org/Number'
+ 'size': 'http://schema.org/Number',
+ 'VideoChannel': 'https://peertu.be/ns/VideoChannel'
}
]
})
return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON)
}
-function isVideoAbuseReporterUsernameValid (value: string) {
- return isUserUsernameValid(value)
-}
-
function isVideoViewsValid (value: string) {
return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS)
}
isVideoThumbnailDataValid,
isVideoFileExtnameValid,
isVideoAbuseReasonValid,
- isVideoAbuseReporterUsernameValid,
isVideoFile,
isVideoViewsValid,
isVideoLikesValid,
-import { ActivityCreate, VideoChannelObject, VideoTorrentObject } from '../../../shared'
-import { ActivityAdd } from '../../../shared/models/activitypub/activity'
-import { generateThumbnailFromUrl, logger, retryTransactionWrapper } from '../../helpers'
+import { ActivityCreate, VideoChannelObject } from '../../../shared'
+import { VideoAbuseObject } from '../../../shared/models/activitypub/objects/video-abuse-object'
+import { logger, retryTransactionWrapper } from '../../helpers'
+import { getActivityPubUrl, getOrCreateAccount } from '../../helpers/activitypub'
import { database as db } from '../../initializers'
-import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
-import Bluebird = require('bluebird')
import { AccountInstance } from '../../models/account/account-interface'
-import { getActivityPubUrl, getOrCreateAccount } from '../../helpers/activitypub'
async function processCreateActivity (activity: ActivityCreate) {
const activityObject = activity.object
if (activityType === 'VideoChannel') {
return processCreateVideoChannel(account, activityObject as VideoChannelObject)
+ } else if (activityType === 'Flag') {
+ return processCreateVideoAbuse(account, activityObject as VideoAbuseObject)
}
logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)
}
+
+function processCreateVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) {
+ const options = {
+ arguments: [ account, videoAbuseToCreateData ],
+ errorMessage: 'Cannot insert the remote video abuse with many retries.'
+ }
+
+ return retryTransactionWrapper(addRemoteVideoAbuse, options)
+}
+
+async function addRemoteVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) {
+ logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
+
+ return db.sequelize.transaction(async t => {
+ const video = await db.Video.loadByUrl(videoAbuseToCreateData.object, t)
+ if (!video) {
+ logger.warn('Unknown video %s for remote video abuse.', videoAbuseToCreateData.object)
+ return
+ }
+
+ const videoAbuseData = {
+ reporterAccountId: account.id,
+ reason: videoAbuseToCreateData.content,
+ videoId: video.id
+ }
+
+ await db.VideoAbuse.create(videoAbuseData)
+
+ logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object)
+ })
+}
import { httpRequestJobScheduler } from '../jobs'
import { signObject, activityPubContextify } from '../../helpers'
import { Activity } from '../../../shared'
+import { VideoAbuseInstance } from '../../models/video/video-abuse-interface'
+import { getActivityPubUrl } from '../../helpers/activitypub'
async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
const videoChannelObject = videoChannel.toActivityPubObject()
return broadcastToFollowers(data, account, t)
}
+async function sendVideoAbuse (
+ fromAccount: AccountInstance,
+ videoAbuse: VideoAbuseInstance,
+ video: VideoInstance,
+ t: Sequelize.Transaction
+) {
+ const url = getActivityPubUrl('videoAbuse', videoAbuse.id.toString())
+ const data = await createActivityData(url, fromAccount, video.url)
+
+ return unicastTo(data, video.VideoChannel.Account.sharedInboxUrl, t)
+}
+
async function sendAccept (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) {
const data = await acceptActivityData(fromAccount)
- return unicastTo(data, toAccount, t)
+ return unicastTo(data, toAccount.inboxUrl, t)
}
async function sendFollow (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) {
const data = await followActivityData(toAccount.url, fromAccount)
- return unicastTo(data, toAccount, t)
+ return unicastTo(data, toAccount.inboxUrl, t)
}
// ---------------------------------------------------------------------------
sendDeleteVideo,
sendDeleteAccount,
sendAccept,
- sendFollow
+ sendFollow,
+ sendVideoAbuse
}
// ---------------------------------------------------------------------------
return httpRequestJobScheduler.createJob(t, 'httpRequestBroadcastHandler', jobPayload)
}
-async function unicastTo (data: any, toAccount: AccountInstance, t: Sequelize.Transaction) {
+async function unicastTo (data: any, toAccountUrl: string, t: Sequelize.Transaction) {
const jobPayload = {
- uris: [ toAccount.inboxUrl ],
+ uris: [ toAccountUrl ],
body: data
}
-import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
-
-import { ServerInstance } from '../server/server-interface'
+import * as Sequelize from 'sequelize'
import { ResultList } from '../../../shared'
-
-// Don't use barrel, import just what we need
import { VideoAbuse as FormattedVideoAbuse } from '../../../shared/models/videos/video-abuse.model'
+import { AccountInstance } from '../account/account-interface'
+import { ServerInstance } from '../server/server-interface'
+import { VideoInstance } from './video-interface'
export namespace VideoAbuseMethods {
export type ToFormattedJSON = (this: VideoAbuseInstance) => FormattedVideoAbuse
}
export interface VideoAbuseAttributes {
- reporterUsername: string
reason: string
videoId: number
+ reporterAccountId: number
+
+ Account?: AccountInstance
+ Video?: VideoInstance
}
export interface VideoAbuseInstance extends VideoAbuseClass, VideoAbuseAttributes, Sequelize.Instance<VideoAbuseAttributes> {
import * as Sequelize from 'sequelize'
import { CONFIG } from '../../initializers'
-import { isVideoAbuseReporterUsernameValid, isVideoAbuseReasonValid } from '../../helpers'
+import { isVideoAbuseReasonValid } from '../../helpers'
import { addMethodsToModel, getSort } from '../utils'
import {
export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
VideoAbuse = sequelize.define<VideoAbuseInstance, VideoAbuseAttributes>('VideoAbuse',
{
- reporterUsername: {
- type: DataTypes.STRING,
- allowNull: false,
- validate: {
- reporterUsernameValid: value => {
- const res = isVideoAbuseReporterUsernameValid(value)
- if (res === false) throw new Error('Video abuse reporter username is not valid.')
- }
- }
- },
reason: {
type: DataTypes.STRING,
allowNull: false,
fields: [ 'videoId' ]
},
{
- fields: [ 'reporterServerId' ]
+ fields: [ 'reporterAccountId' ]
}
]
}
toFormattedJSON = function (this: VideoAbuseInstance) {
let reporterServerHost
- if (this.Server) {
- reporterServerHost = this.Server.host
+ if (this.Account.Server) {
+ reporterServerHost = this.Account.Server.host
} else {
// It means it's our video
reporterServerHost = CONFIG.WEBSERVER.HOST
const json = {
id: this.id,
- reporterServerHost,
reason: this.reason,
- reporterUsername: this.reporterUsername,
- videoId: this.videoId,
+ reporterUsername: this.Account.name,
+ reporterServerHost,
+ videoId: this.Video.id,
+ videoUUID: this.Video.uuid,
+ videoName: this.Video.name,
createdAt: this.createdAt
}
// ------------------------------ STATICS ------------------------------
function associate (models) {
- VideoAbuse.belongsTo(models.Server, {
+ VideoAbuse.belongsTo(models.Account, {
foreignKey: {
- name: 'reporterServerId',
+ name: 'reporterAccountId',
allowNull: true
},
onDelete: 'CASCADE'
order: [ getSort(sort) ],
include: [
{
- model: VideoAbuse['sequelize'].models.Server,
- required: false
+ model: VideoAbuse['sequelize'].models.Account,
+ required: true,
+ include: [
+ {
+ model: VideoAbuse['sequelize'].models.Server,
+ required: false
+ }
+ ]
+ },
+ {
+ model: VideoAbuse['sequelize'].models.Video,
+ required: true
}
]
}
-import {
- VideoChannelObject,
- VideoTorrentObject
-} from './objects'
+import { VideoChannelObject, VideoTorrentObject } from './objects'
import { ActivityPubSignature } from './activitypub-signature'
+import { VideoAbuseObject } from './objects/video-abuse-object'
export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate | ActivityFlag |
ActivityDelete | ActivityFollow | ActivityAccept
export interface ActivityCreate extends BaseActivity {
type: 'Create'
- object: VideoChannelObject
+ object: VideoChannelObject | VideoAbuseObject
}
export interface ActivityAdd extends BaseActivity {
export * from './common-objects'
+export * from './video-abuse-object'
export * from './video-channel-object'
export * from './video-torrent-object'
--- /dev/null
+export interface VideoAbuseObject {
+ type: 'Flag',
+ content: string
+ object: string
+}
-import { ActivityIdentifierObject } from './common-objects'
-
export interface VideoChannelObject {
type: 'VideoChannel'
id: string
export interface VideoAbuse {
id: number
- reporterServerHost: string
reason: string
reporterUsername: string
+ reporterServerHost: string
videoId: number
+ videoUUID: string
+ videoName: string
createdAt: Date
}