1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-05 19:42:24 +02:00

Implement auto tag on comments and videos

* Comments and videos can be automatically tagged using core rules or
   watched word lists
 * These tags can be used to automatically filter videos and comments
 * Introduce a new video comment policy where comments must be approved
   first
 * Comments may have to be approved if the user auto block them using
   core rules or watched word lists
 * Implement FEP-5624 to federate reply control policies
This commit is contained in:
Chocobozzz 2024-03-29 14:25:03 +01:00 committed by Chocobozzz
parent b3e39df59e
commit 29329d6c45
241 changed files with 8090 additions and 1399 deletions

View file

@ -10,7 +10,7 @@ import { sanitizeAndCheckVideoCommentObject } from './video-comments.js'
import { sanitizeAndCheckVideoTorrentObject } from './videos.js'
import { isWatchActionObjectValid } from './watch-action.js'
function isRootActivityValid (activity: any) {
export function isRootActivityValid (activity: any) {
return isCollection(activity) || isActivity(activity)
}
@ -26,6 +26,8 @@ function isActivity (activity: any) {
(isActivityPubUrlValid(activity.actor) || isActivityPubUrlValid(activity.actor.id))
}
// ---------------------------------------------------------------------------
const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean } = {
Create: isCreateActivityValid,
Update: isUpdateActivityValid,
@ -38,10 +40,12 @@ const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean
Like: isLikeActivityValid,
View: isViewActivityValid,
Flag: isFlagActivityValid,
Dislike: isDislikeActivityValid
Dislike: isDislikeActivityValid,
ApproveReply: isApproveReplyActivityValid,
RejectReply: isRejectReplyActivityValid
}
function isActivityValid (activity: any) {
export function isActivityValid (activity: any) {
const checker = activityCheckers[activity.type]
// Unknown activity type
if (!checker) return false
@ -49,34 +53,34 @@ function isActivityValid (activity: any) {
return checker(activity)
}
function isFlagActivityValid (activity: any) {
export function isFlagActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Flag') &&
isAbuseReasonValid(activity.content) &&
isActivityPubUrlValid(activity.object)
}
function isLikeActivityValid (activity: any) {
export function isLikeActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Like') &&
isObjectValid(activity.object)
}
function isDislikeActivityValid (activity: any) {
export function isDislikeActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Dislike') &&
isObjectValid(activity.object)
}
function isAnnounceActivityValid (activity: any) {
export function isAnnounceActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Announce') &&
isObjectValid(activity.object)
}
function isViewActivityValid (activity: any) {
export function isViewActivityValid (activity: any) {
return isBaseActivityValid(activity, 'View') &&
isActivityPubUrlValid(activity.actor) &&
isActivityPubUrlValid(activity.object)
}
function isCreateActivityValid (activity: any) {
export function isCreateActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Create') &&
(
isViewActivityValid(activity.object) ||
@ -91,7 +95,7 @@ function isCreateActivityValid (activity: any) {
)
}
function isUpdateActivityValid (activity: any) {
export function isUpdateActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Update') &&
(
isCacheFileObjectValid(activity.object) ||
@ -101,26 +105,26 @@ function isUpdateActivityValid (activity: any) {
)
}
function isDeleteActivityValid (activity: any) {
export function isDeleteActivityValid (activity: any) {
// We don't really check objects
return isBaseActivityValid(activity, 'Delete') &&
isObjectValid(activity.object)
}
function isFollowActivityValid (activity: any) {
export function isFollowActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Follow') &&
isObjectValid(activity.object)
}
function isAcceptActivityValid (activity: any) {
export function isAcceptActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Accept')
}
function isRejectActivityValid (activity: any) {
export function isRejectActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Reject')
}
function isUndoActivityValid (activity: any) {
export function isUndoActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Undo') &&
(
isFollowActivityValid(activity.object) ||
@ -131,21 +135,14 @@ function isUndoActivityValid (activity: any) {
)
}
// ---------------------------------------------------------------------------
export {
isRootActivityValid,
isActivityValid,
isFlagActivityValid,
isLikeActivityValid,
isDislikeActivityValid,
isAnnounceActivityValid,
isViewActivityValid,
isCreateActivityValid,
isUpdateActivityValid,
isDeleteActivityValid,
isFollowActivityValid,
isAcceptActivityValid,
isRejectActivityValid,
isUndoActivityValid
export function isApproveReplyActivityValid (activity: any) {
return isBaseActivityValid(activity, 'ApproveReply') &&
isActivityPubUrlValid(activity.object) &&
isActivityPubUrlValid(activity.inReplyTo)
}
export function isRejectReplyActivityValid (activity: any) {
return isBaseActivityValid(activity, 'RejectReply') &&
isActivityPubUrlValid(activity.object) &&
isActivityPubUrlValid(activity.inReplyTo)
}

View file

@ -2,8 +2,9 @@ import { hasAPPublic } from '@server/helpers/activity-pub-utils.js'
import validator from 'validator'
import { exists, isArray, isDateValid } from '../misc.js'
import { isActivityPubUrlValid } from './misc.js'
import { ActivityTombstoneObject, VideoCommentObject } from '@peertube/peertube-models'
function sanitizeAndCheckVideoCommentObject (comment: any) {
function sanitizeAndCheckVideoCommentObject (comment: VideoCommentObject | ActivityTombstoneObject) {
if (!comment) return false
if (!isCommentTypeValid(comment)) return false
@ -23,6 +24,7 @@ function sanitizeAndCheckVideoCommentObject (comment: any) {
isDateValid(comment.published) &&
isActivityPubUrlValid(comment.url) &&
isArray(comment.to) &&
(!exists(comment.replyApproval) || isActivityPubUrlValid(comment.replyApproval)) &&
(hasAPPublic(comment.to) || hasAPPublic(comment.cc)) // Only accept public comments
}

View file

@ -1,18 +1,20 @@
import validator from 'validator'
import {
ActivityPubStoryboard,
ActivityTrackerUrlObject,
ActivityVideoFileMetadataUrlObject,
LiveVideoLatencyMode,
VideoCommentPolicy,
VideoObject,
VideoState
} from '@peertube/peertube-models'
import { logger } from '@server/helpers/logger.js'
import validator from 'validator'
import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants.js'
import { peertubeTruncate } from '../../core-utils.js'
import { isArray, isBooleanValid, isDateValid, isUUIDValid } from '../misc.js'
import { isLiveLatencyModeValid } from '../video-lives.js'
import {
isVideoCommentsPolicyValid,
isVideoDescriptionValid,
isVideoDurationValid,
isVideoNameValid,
@ -66,12 +68,21 @@ function sanitizeAndCheckVideoTorrentObject (video: VideoObject) {
if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED
if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false
if (!isBooleanValid(video.downloadEnabled)) video.downloadEnabled = true
if (!isBooleanValid(video.commentsEnabled)) video.commentsEnabled = false
if (!isBooleanValid(video.isLiveBroadcast)) video.isLiveBroadcast = false
if (!isBooleanValid(video.liveSaveReplay)) video.liveSaveReplay = false
if (!isBooleanValid(video.permanentLive)) video.permanentLive = false
if (!isLiveLatencyModeValid(video.latencyMode)) video.latencyMode = LiveVideoLatencyMode.DEFAULT
if (video.commentsPolicy) {
if (!isVideoCommentsPolicyValid(video.commentsPolicy)) {
video.commentsPolicy = VideoCommentPolicy.DISABLED
}
} else if (video.commentsEnabled === true) { // Fallback to deprecated attribute
video.commentsPolicy = VideoCommentPolicy.ENABLED
} else {
video.commentsPolicy = VideoCommentPolicy.DISABLED
}
return isActivityPubUrlValid(video.id) &&
isVideoNameValid(video.name) &&
isActivityPubVideoDurationValid(video.duration) &&
@ -138,12 +149,12 @@ function isAPVideoTrackerUrlObject (url: any): url is ActivityTrackerUrlObject {
// ---------------------------------------------------------------------------
export {
sanitizeAndCheckVideoTorrentUpdateActivity,
isRemoteStringIdentifierValid,
sanitizeAndCheckVideoTorrentObject,
isRemoteVideoUrlValid,
isAPVideoFileUrlMetadataObject,
isAPVideoTrackerUrlObject
isAPVideoTrackerUrlObject,
isRemoteStringIdentifierValid,
isRemoteVideoUrlValid,
sanitizeAndCheckVideoTorrentObject,
sanitizeAndCheckVideoTorrentUpdateActivity
}
// ---------------------------------------------------------------------------

View file

@ -1,12 +1,13 @@
import { HttpStatusCode, VideoIncludeType, VideoPrivacy, VideoPrivacyType, VideoRateType } from '@peertube/peertube-models'
import { getVideoWithAttributes } from '@server/helpers/video.js'
import { Request, Response, UploadFilesForCheck } from 'express'
import { decode as magnetUriDecode } from 'magnet-uri'
import validator from 'validator'
import { HttpStatusCode, VideoIncludeType, VideoPrivacy, VideoPrivacyType, VideoRateType } from '@peertube/peertube-models'
import { getVideoWithAttributes } from '@server/helpers/video.js'
import {
CONSTRAINTS_FIELDS,
MIMETYPES,
VIDEO_CATEGORIES,
VIDEO_COMMENTS_POLICY,
VIDEO_LICENCES,
VIDEO_LIVE,
VIDEO_PRIVACIES,
@ -46,6 +47,10 @@ export function isVideoDescriptionValid (value: string) {
return value === null || (exists(value) && validator.default.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION))
}
export function isVideoCommentsPolicyValid (value: any) {
return value === null || VIDEO_COMMENTS_POLICY[value] !== undefined
}
export function isVideoSupportValid (value: string) {
return value === null || (exists(value) && validator.default.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.SUPPORT))
}

View file

@ -0,0 +1,17 @@
import validator from 'validator'
import { CONSTRAINTS_FIELDS } from '../../initializers/constants.js'
import { exists, isArray } from './misc.js'
export function isWatchedWordListNameValid (listName: string) {
return exists(listName) && validator.default.isLength(listName, CONSTRAINTS_FIELDS.WATCHED_WORDS.LIST_NAME)
}
export function isWatchedWordValid (word: string) {
return exists(word) && validator.default.isLength(word, CONSTRAINTS_FIELDS.WATCHED_WORDS.WORD)
}
export function areWatchedWordsValid (words: string[]) {
return isArray(words) &&
validator.default.isInt(words.length.toString(), CONSTRAINTS_FIELDS.WATCHED_WORDS.WORDS) &&
words.every(word => isWatchedWordValid(word))
}