Add video abuse to activity pub
This commit is contained in:
parent
59c857da59
commit
8e13fa7d09
|
@ -11,6 +11,7 @@
|
|||
<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>
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
} from '../../../middlewares'
|
||||
import { VideoInstance } from '../../../models'
|
||||
import { VideoAbuseCreate, UserRight } from '../../../../shared'
|
||||
import { sendVideoAbuse } from '../../../lib/index'
|
||||
|
||||
const abuseVideoRouter = express.Router()
|
||||
|
||||
|
@ -63,28 +64,21 @@ async function reportVideoAbuseRetryWrapper (req: express.Request, res: express.
|
|||
|
||||
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
|
||||
if (videoInstance.isOwned() === false) {
|
||||
const reportData = {
|
||||
reporterUsername,
|
||||
reportReason: abuse.reason,
|
||||
videoUUID: videoInstance.uuid
|
||||
}
|
||||
const videoAbuseInstance = await db.VideoAbuse.create(abuseToCreate, { transaction: t })
|
||||
|
||||
// await friends.reportAbuseVideoToFriend(reportData, videoInstance, t)
|
||||
// TODO: send abuse to origin server
|
||||
// We send the video abuse to the origin server
|
||||
if (videoInstance.isOwned() === false) {
|
||||
await sendVideoAbuse(reporterAccount, videoAbuseInstance, videoInstance, t)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -22,10 +22,11 @@ function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObjec
|
|||
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 ''
|
||||
}
|
||||
|
@ -134,7 +135,8 @@ function activityPubContextify <T> (data: T) {
|
|||
'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'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -112,10 +112,6 @@ function isVideoAbuseReasonValid (value: string) {
|
|||
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)
|
||||
}
|
||||
|
@ -209,7 +205,6 @@ export {
|
|||
isVideoThumbnailDataValid,
|
||||
isVideoFileExtnameValid,
|
||||
isVideoAbuseReasonValid,
|
||||
isVideoAbuseReporterUsernameValid,
|
||||
isVideoFile,
|
||||
isVideoViewsValid,
|
||||
isVideoLikesValid,
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { ActivityCreate, VideoChannelObject, VideoTorrentObject } from '../../../shared'
|
||||
import { ActivityAdd } from '../../../shared/models/activitypub/activity'
|
||||
import { generateThumbnailFromUrl, logger, retryTransactionWrapper } from '../../helpers'
|
||||
import { database as db } from '../../initializers'
|
||||
import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
|
||||
import Bluebird = require('bluebird')
|
||||
import { AccountInstance } from '../../models/account/account-interface'
|
||||
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 { AccountInstance } from '../../models/account/account-interface'
|
||||
|
||||
async function processCreateActivity (activity: ActivityCreate) {
|
||||
const activityObject = activity.object
|
||||
|
@ -14,6 +12,8 @@ async function processCreateActivity (activity: ActivityCreate) {
|
|||
|
||||
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 })
|
||||
|
@ -62,3 +62,34 @@ async function addRemoteVideoChannel (account: AccountInstance, videoChannelToCr
|
|||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import {
|
|||
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()
|
||||
|
@ -56,16 +58,28 @@ async function sendDeleteAccount (account: AccountInstance, t: Sequelize.Transac
|
|||
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)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -79,7 +93,8 @@ export {
|
|||
sendDeleteVideo,
|
||||
sendDeleteAccount,
|
||||
sendAccept,
|
||||
sendFollow
|
||||
sendFollow,
|
||||
sendVideoAbuse
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -95,9 +110,9 @@ async function broadcastToFollowers (data: any, fromAccount: AccountInstance, t:
|
|||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
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
|
||||
|
@ -18,9 +17,12 @@ export interface VideoAbuseClass {
|
|||
}
|
||||
|
||||
export interface VideoAbuseAttributes {
|
||||
reporterUsername: string
|
||||
reason: string
|
||||
videoId: number
|
||||
reporterAccountId: number
|
||||
|
||||
Account?: AccountInstance
|
||||
Video?: VideoInstance
|
||||
}
|
||||
|
||||
export interface VideoAbuseInstance extends VideoAbuseClass, VideoAbuseAttributes, Sequelize.Instance<VideoAbuseAttributes> {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
|
||||
import { CONFIG } from '../../initializers'
|
||||
import { isVideoAbuseReporterUsernameValid, isVideoAbuseReasonValid } from '../../helpers'
|
||||
import { isVideoAbuseReasonValid } from '../../helpers'
|
||||
|
||||
import { addMethodsToModel, getSort } from '../utils'
|
||||
import {
|
||||
|
@ -18,16 +18,6 @@ let listForApi: VideoAbuseMethods.ListForApi
|
|||
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,
|
||||
|
@ -45,7 +35,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
fields: [ 'videoId' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'reporterServerId' ]
|
||||
fields: [ 'reporterAccountId' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -69,8 +59,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
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
|
||||
|
@ -78,10 +68,12 @@ toFormattedJSON = function (this: VideoAbuseInstance) {
|
|||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -91,9 +83,9 @@ toFormattedJSON = function (this: VideoAbuseInstance) {
|
|||
// ------------------------------ STATICS ------------------------------
|
||||
|
||||
function associate (models) {
|
||||
VideoAbuse.belongsTo(models.Server, {
|
||||
VideoAbuse.belongsTo(models.Account, {
|
||||
foreignKey: {
|
||||
name: 'reporterServerId',
|
||||
name: 'reporterAccountId',
|
||||
allowNull: true
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
|
@ -113,12 +105,22 @@ listForApi = function (start: number, count: number, sort: string) {
|
|||
offset: start,
|
||||
limit: count,
|
||||
order: [ getSort(sort) ],
|
||||
include: [
|
||||
{
|
||||
model: VideoAbuse['sequelize'].models.Account,
|
||||
required: true,
|
||||
include: [
|
||||
{
|
||||
model: VideoAbuse['sequelize'].models.Server,
|
||||
required: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: VideoAbuse['sequelize'].models.Video,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return VideoAbuse.findAndCountAll(query).then(({ rows, count }) => {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
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
|
||||
|
@ -21,7 +19,7 @@ export interface BaseActivity {
|
|||
|
||||
export interface ActivityCreate extends BaseActivity {
|
||||
type: 'Create'
|
||||
object: VideoChannelObject
|
||||
object: VideoChannelObject | VideoAbuseObject
|
||||
}
|
||||
|
||||
export interface ActivityAdd extends BaseActivity {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from './common-objects'
|
||||
export * from './video-abuse-object'
|
||||
export * from './video-channel-object'
|
||||
export * from './video-torrent-object'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export interface VideoAbuseObject {
|
||||
type: 'Flag',
|
||||
content: string
|
||||
object: string
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
import { ActivityIdentifierObject } from './common-objects'
|
||||
|
||||
export interface VideoChannelObject {
|
||||
type: 'VideoChannel'
|
||||
id: string
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
export interface VideoAbuse {
|
||||
id: number
|
||||
reporterServerHost: string
|
||||
reason: string
|
||||
reporterUsername: string
|
||||
reporterServerHost: string
|
||||
videoId: number
|
||||
videoUUID: string
|
||||
videoName: string
|
||||
createdAt: Date
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue