258 lines
7.6 KiB
TypeScript
258 lines
7.6 KiB
TypeScript
|
import express, { VideoUploadFile } from 'express'
|
||
|
import { PathLike } from 'fs-extra/esm'
|
||
|
import { Transaction } from 'sequelize'
|
||
|
import { AbuseAuditView, auditLoggerFactory } from '@server/helpers/audit-logger.js'
|
||
|
import { afterCommitIfTransaction } from '@server/helpers/database-utils.js'
|
||
|
import { logger } from '@server/helpers/logger.js'
|
||
|
import { AbuseModel } from '@server/models/abuse/abuse.js'
|
||
|
import { VideoAbuseModel } from '@server/models/abuse/video-abuse.js'
|
||
|
import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse.js'
|
||
|
import { VideoFileModel } from '@server/models/video/video-file.js'
|
||
|
import { FilteredModelAttributes } from '@server/types/index.js'
|
||
|
import {
|
||
|
MAbuseFull,
|
||
|
MAccountDefault,
|
||
|
MAccountLight,
|
||
|
MComment,
|
||
|
MCommentAbuseAccountVideo,
|
||
|
MCommentOwnerVideo,
|
||
|
MUser,
|
||
|
MVideoAbuseVideoFull,
|
||
|
MVideoAccountLightBlacklistAllFiles
|
||
|
} from '@server/types/models/index.js'
|
||
|
import { LiveVideoCreate, VideoCommentCreate, VideoCreate, VideoImportCreate } from '@peertube/peertube-models'
|
||
|
import { UserModel } from '../models/user/user.js'
|
||
|
import { VideoCommentModel } from '../models/video/video-comment.js'
|
||
|
import { VideoModel } from '../models/video/video.js'
|
||
|
import { sendAbuse } from './activitypub/send/send-flag.js'
|
||
|
import { Notifier } from './notifier/index.js'
|
||
|
|
||
|
export type AcceptResult = {
|
||
|
accepted: boolean
|
||
|
errorMessage?: string
|
||
|
}
|
||
|
|
||
|
// ---------------------------------------------------------------------------
|
||
|
|
||
|
// Stub function that can be filtered by plugins
|
||
|
function isLocalVideoFileAccepted (object: {
|
||
|
videoBody: VideoCreate
|
||
|
videoFile: VideoUploadFile
|
||
|
user: UserModel
|
||
|
}): AcceptResult {
|
||
|
return { accepted: true }
|
||
|
}
|
||
|
|
||
|
// ---------------------------------------------------------------------------
|
||
|
|
||
|
// Stub function that can be filtered by plugins
|
||
|
function isLocalLiveVideoAccepted (object: {
|
||
|
liveVideoBody: LiveVideoCreate
|
||
|
user: UserModel
|
||
|
}): AcceptResult {
|
||
|
return { accepted: true }
|
||
|
}
|
||
|
|
||
|
// ---------------------------------------------------------------------------
|
||
|
|
||
|
// Stub function that can be filtered by plugins
|
||
|
function isLocalVideoThreadAccepted (_object: {
|
||
|
req: express.Request
|
||
|
commentBody: VideoCommentCreate
|
||
|
video: VideoModel
|
||
|
user: UserModel
|
||
|
}): AcceptResult {
|
||
|
return { accepted: true }
|
||
|
}
|
||
|
|
||
|
// Stub function that can be filtered by plugins
|
||
|
function isLocalVideoCommentReplyAccepted (_object: {
|
||
|
req: express.Request
|
||
|
commentBody: VideoCommentCreate
|
||
|
parentComment: VideoCommentModel
|
||
|
video: VideoModel
|
||
|
user: UserModel
|
||
|
}): AcceptResult {
|
||
|
return { accepted: true }
|
||
|
}
|
||
|
|
||
|
// ---------------------------------------------------------------------------
|
||
|
|
||
|
// Stub function that can be filtered by plugins
|
||
|
function isRemoteVideoCommentAccepted (_object: {
|
||
|
comment: MComment
|
||
|
}): AcceptResult {
|
||
|
return { accepted: true }
|
||
|
}
|
||
|
|
||
|
// ---------------------------------------------------------------------------
|
||
|
|
||
|
// Stub function that can be filtered by plugins
|
||
|
function isPreImportVideoAccepted (object: {
|
||
|
videoImportBody: VideoImportCreate
|
||
|
user: MUser
|
||
|
}): AcceptResult {
|
||
|
return { accepted: true }
|
||
|
}
|
||
|
|
||
|
// Stub function that can be filtered by plugins
|
||
|
function isPostImportVideoAccepted (object: {
|
||
|
videoFilePath: PathLike
|
||
|
videoFile: VideoFileModel
|
||
|
user: MUser
|
||
|
}): AcceptResult {
|
||
|
return { accepted: true }
|
||
|
}
|
||
|
|
||
|
// ---------------------------------------------------------------------------
|
||
|
|
||
|
async function createVideoAbuse (options: {
|
||
|
baseAbuse: FilteredModelAttributes<AbuseModel>
|
||
|
videoInstance: MVideoAccountLightBlacklistAllFiles
|
||
|
startAt: number
|
||
|
endAt: number
|
||
|
transaction: Transaction
|
||
|
reporterAccount: MAccountDefault
|
||
|
skipNotification: boolean
|
||
|
}) {
|
||
|
const { baseAbuse, videoInstance, startAt, endAt, transaction, reporterAccount, skipNotification } = options
|
||
|
|
||
|
const associateFun = async (abuseInstance: MAbuseFull) => {
|
||
|
const videoAbuseInstance: MVideoAbuseVideoFull = await VideoAbuseModel.create({
|
||
|
abuseId: abuseInstance.id,
|
||
|
videoId: videoInstance.id,
|
||
|
startAt,
|
||
|
endAt
|
||
|
}, { transaction })
|
||
|
|
||
|
videoAbuseInstance.Video = videoInstance
|
||
|
abuseInstance.VideoAbuse = videoAbuseInstance
|
||
|
|
||
|
return { isOwned: videoInstance.isOwned() }
|
||
|
}
|
||
|
|
||
|
return createAbuse({
|
||
|
base: baseAbuse,
|
||
|
reporterAccount,
|
||
|
flaggedAccount: videoInstance.VideoChannel.Account,
|
||
|
transaction,
|
||
|
skipNotification,
|
||
|
associateFun
|
||
|
})
|
||
|
}
|
||
|
|
||
|
function createVideoCommentAbuse (options: {
|
||
|
baseAbuse: FilteredModelAttributes<AbuseModel>
|
||
|
commentInstance: MCommentOwnerVideo
|
||
|
transaction: Transaction
|
||
|
reporterAccount: MAccountDefault
|
||
|
skipNotification: boolean
|
||
|
}) {
|
||
|
const { baseAbuse, commentInstance, transaction, reporterAccount, skipNotification } = options
|
||
|
|
||
|
const associateFun = async (abuseInstance: MAbuseFull) => {
|
||
|
const commentAbuseInstance: MCommentAbuseAccountVideo = await VideoCommentAbuseModel.create({
|
||
|
abuseId: abuseInstance.id,
|
||
|
videoCommentId: commentInstance.id
|
||
|
}, { transaction })
|
||
|
|
||
|
commentAbuseInstance.VideoComment = commentInstance
|
||
|
abuseInstance.VideoCommentAbuse = commentAbuseInstance
|
||
|
|
||
|
return { isOwned: commentInstance.isOwned() }
|
||
|
}
|
||
|
|
||
|
return createAbuse({
|
||
|
base: baseAbuse,
|
||
|
reporterAccount,
|
||
|
flaggedAccount: commentInstance.Account,
|
||
|
transaction,
|
||
|
skipNotification,
|
||
|
associateFun
|
||
|
})
|
||
|
}
|
||
|
|
||
|
function createAccountAbuse (options: {
|
||
|
baseAbuse: FilteredModelAttributes<AbuseModel>
|
||
|
accountInstance: MAccountDefault
|
||
|
transaction: Transaction
|
||
|
reporterAccount: MAccountDefault
|
||
|
skipNotification: boolean
|
||
|
}) {
|
||
|
const { baseAbuse, accountInstance, transaction, reporterAccount, skipNotification } = options
|
||
|
|
||
|
const associateFun = () => {
|
||
|
return Promise.resolve({ isOwned: accountInstance.isOwned() })
|
||
|
}
|
||
|
|
||
|
return createAbuse({
|
||
|
base: baseAbuse,
|
||
|
reporterAccount,
|
||
|
flaggedAccount: accountInstance,
|
||
|
transaction,
|
||
|
skipNotification,
|
||
|
associateFun
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// ---------------------------------------------------------------------------
|
||
|
|
||
|
export {
|
||
|
isLocalLiveVideoAccepted,
|
||
|
|
||
|
isLocalVideoFileAccepted,
|
||
|
isLocalVideoThreadAccepted,
|
||
|
isRemoteVideoCommentAccepted,
|
||
|
isLocalVideoCommentReplyAccepted,
|
||
|
isPreImportVideoAccepted,
|
||
|
isPostImportVideoAccepted,
|
||
|
|
||
|
createAbuse,
|
||
|
createVideoAbuse,
|
||
|
createVideoCommentAbuse,
|
||
|
createAccountAbuse
|
||
|
}
|
||
|
|
||
|
// ---------------------------------------------------------------------------
|
||
|
|
||
|
async function createAbuse (options: {
|
||
|
base: FilteredModelAttributes<AbuseModel>
|
||
|
reporterAccount: MAccountDefault
|
||
|
flaggedAccount: MAccountLight
|
||
|
associateFun: (abuseInstance: MAbuseFull) => Promise<{ isOwned: boolean }>
|
||
|
skipNotification: boolean
|
||
|
transaction: Transaction
|
||
|
}) {
|
||
|
const { base, reporterAccount, flaggedAccount, associateFun, transaction, skipNotification } = options
|
||
|
const auditLogger = auditLoggerFactory('abuse')
|
||
|
|
||
|
const abuseAttributes = Object.assign({}, base, { flaggedAccountId: flaggedAccount.id })
|
||
|
const abuseInstance: MAbuseFull = await AbuseModel.create(abuseAttributes, { transaction })
|
||
|
|
||
|
abuseInstance.ReporterAccount = reporterAccount
|
||
|
abuseInstance.FlaggedAccount = flaggedAccount
|
||
|
|
||
|
const { isOwned } = await associateFun(abuseInstance)
|
||
|
|
||
|
if (isOwned === false) {
|
||
|
sendAbuse(reporterAccount.Actor, abuseInstance, abuseInstance.FlaggedAccount, transaction)
|
||
|
}
|
||
|
|
||
|
const abuseJSON = abuseInstance.toFormattedAdminJSON()
|
||
|
auditLogger.create(reporterAccount.Actor.getIdentifier(), new AbuseAuditView(abuseJSON))
|
||
|
|
||
|
if (!skipNotification) {
|
||
|
afterCommitIfTransaction(transaction, () => {
|
||
|
Notifier.Instance.notifyOnNewAbuse({
|
||
|
abuse: abuseJSON,
|
||
|
abuseInstance,
|
||
|
reporter: reporterAccount.Actor.getIdentifier()
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
logger.info('Abuse report %d created.', abuseInstance.id)
|
||
|
|
||
|
return abuseJSON
|
||
|
}
|