1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-03 09:49:20 +02:00

Add ability for admins to refuse remote comments

This commit is contained in:
Chocobozzz 2025-06-18 09:29:45 +02:00
parent 031b61c466
commit f5fd593976
No known key found for this signature in database
GPG key ID: 583A612D890159BE
12 changed files with 157 additions and 14 deletions

View file

@ -463,6 +463,19 @@
<div class="content-col"> <div class="content-col">
<ng-container formGroupName="videoComments">
<div class="form-group">
<my-peertube-checkbox
inputName="videoCommentsAcceptRemoteComments" formControlName="acceptRemoteComments"
i18n-labelText labelText="Accept comments made on remote platforms"
>
<ng-container ngProjectAs="description">
<span i18n>This setting is not retroactive: current remote comments platform will not be deleted</span>
</ng-container>
</my-peertube-checkbox>
</div>
</ng-container>
<ng-container formGroupName="followers"> <ng-container formGroupName="followers">
<ng-container formGroupName="channels"> <ng-container formGroupName="channels">
<div class="form-group"> <div class="form-group">

View file

@ -198,6 +198,10 @@ type Form = {
autoPlay: FormControl<boolean> autoPlay: FormControl<boolean>
}> }>
}> }>
videoComments: FormGroup<{
acceptRemoteComments: FormControl<boolean>
}>
} }
@Component({ @Component({
@ -424,6 +428,9 @@ export class AdminConfigGeneralComponent implements OnInit, OnDestroy, CanCompon
player: { player: {
autoPlay: null autoPlay: null
} }
},
videoComments: {
acceptRemoteComments: null
} }
} }

View file

@ -546,7 +546,7 @@ signup:
user: user:
history: history:
videos: videos:
# Enable or disable video history by default for new users. # Enable or disable video history by default for new users
enabled: true enabled: true
# Default value of maximum video bytes the user can upload # Default value of maximum video bytes the user can upload
@ -797,7 +797,7 @@ import:
# * https://yt-dl.org/downloads/latest/youtube-dl # * https://yt-dl.org/downloads/latest/youtube-dl
# #
# You can also use a youtube-dl standalone binary (requires python_path: null) # You can also use a youtube-dl standalone binary (requires python_path: null)
# GNU/Linux binaries with support for impersonating browser requests (required by some platforms such as Vimeo) examples: # GNU/Linux binaries with support for impersonating browser requests (required by some i such as Vimeo) examples:
# * https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux (x64) # * https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux (x64)
# * https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux_armv7l (ARMv7) # * https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux_armv7l (ARMv7)
# * https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux_armv7l (ARMv8/AArch64/ARM64) # * https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux_armv7l (ARMv8/AArch64/ARM64)
@ -1143,3 +1143,8 @@ email:
subject: subject:
# Support {{instanceName}} template variable # Support {{instanceName}} template variable
prefix: '[{{instanceName}}] ' prefix: '[{{instanceName}}] '
video_comments:
# Accept or not comments from remote instances
# This setting is not retroactive: current remote comments of your instance will not be affected
accept_remote_comments: true

View file

@ -358,4 +358,8 @@ export interface CustomConfig {
autoPlay: boolean autoPlay: boolean
} }
} }
videoComments: {
acceptRemoteComments: boolean
}
} }

View file

@ -158,6 +158,8 @@ function checkInitialConfig (server: PeerTubeServer, data: CustomConfig) {
expect(data.email.body.signature).to.equal('') expect(data.email.body.signature).to.equal('')
expect(data.email.subject.prefix).to.equal('[{{instanceName}}] ') expect(data.email.subject.prefix).to.equal('[{{instanceName}}] ')
expect(data.videoComments.acceptRemoteComments).to.be.true
} }
function buildNewCustomConfig (server: PeerTubeServer): CustomConfig { function buildNewCustomConfig (server: PeerTubeServer): CustomConfig {
@ -464,6 +466,9 @@ function buildNewCustomConfig (server: PeerTubeServer): CustomConfig {
subject: { subject: {
prefix: 'my prefix' prefix: 'my prefix'
} }
},
videoComments: {
acceptRemoteComments: false
} }
} }
} }

View file

@ -5,12 +5,15 @@ import {
PeerTubeServer, PeerTubeServer,
cleanupTests, cleanupTests,
createSingleServer, createSingleServer,
doubleFollow,
setAccessTokensToServers, setAccessTokensToServers,
setDefaultAccountAvatar, setDefaultAccountAvatar,
setDefaultChannelAvatar setDefaultChannelAvatar,
waitJobs
} from '@peertube/peertube-server-commands' } from '@peertube/peertube-server-commands'
import { dateIsValid, testImage } from '@tests/shared/checks.js' import { dateIsValid, testImage } from '@tests/shared/checks.js'
import { expect } from 'chai' import { expect } from 'chai'
import { VideoCreateResult } from '../../../../models/src/videos/video-create-result.model.js'
describe('Test video comments', function () { describe('Test video comments', function () {
let server: PeerTubeServer let server: PeerTubeServer
@ -431,6 +434,95 @@ describe('Test video comments', function () {
}) })
}) })
describe('Disabling remote comments', function () {
let server2: PeerTubeServer
let server3: PeerTubeServer
let video1: VideoCreateResult
let video2: VideoCreateResult
before(async function () {
this.timeout(120000)
server2 = await createSingleServer(2)
server3 = await createSingleServer(3)
await setAccessTokensToServers([ server2, server3 ])
await doubleFollow(server, server2)
})
it('Should federate comments', async function () {
video1 = await server.videos.quickUpload({ name: 'video on server 1' })
video2 = await server2.videos.quickUpload({ name: 'video on server 2' })
await server2.comments.createThread({ videoId: video1.uuid, text: 'comment on server 2' })
await server2.comments.createThread({ videoId: video2.uuid, text: 'comment on server 2' })
await waitJobs([ server, server2 ])
for (const s of [ server, server2 ]) {
const threads = await s.comments.listThreads({ videoId: video1.uuid })
expect(threads.total).to.equal(1)
expect(threads.data[0].text).to.equal('comment on server 2')
const threads2 = await s.comments.listThreads({ videoId: video2.uuid })
expect(threads2.total).to.equal(1)
expect(threads2.data[0].text).to.equal('comment on server 2')
}
})
it('Should not accept remote comments anymore', async function () {
await server.config.updateExistingConfig({
newConfig: {
videoComments: {
acceptRemoteComments: false
}
}
})
await server2.comments.createThread({ videoId: video1.uuid, text: 'comment on server 2 - 2' })
await server2.comments.createThread({ videoId: video2.uuid, text: 'comment on server 2 - 2' })
await waitJobs([ server, server2 ])
// Server 1
{
const threads = await server.comments.listThreads({ videoId: video1.uuid })
expect(threads.total).to.equal(1)
const threads2 = await server.comments.listThreads({ videoId: video2.uuid })
expect(threads2.total).to.equal(1)
}
// Server 2
{
const threads = await server2.comments.listThreads({ videoId: video1.uuid })
expect(threads.total).to.equal(2)
const threads2 = await server2.comments.listThreads({ videoId: video2.uuid })
expect(threads2.total).to.equal(2)
}
})
it('Should not fetch remote comments on new follow', async function () {
const video3 = await server3.videos.quickUpload({ name: 'video on server 2' })
await server3.comments.createThread({ videoId: video3.uuid, text: 'comment on server 3' })
await waitJobs([ server3 ])
await doubleFollow(server, server3)
{
const threads = await server3.comments.listThreads({ videoId: video3.uuid })
expect(threads.total).to.equal(1)
}
{
const threads = await server.comments.listThreads({ videoId: video3.uuid })
expect(threads.total).to.equal(0)
}
})
})
after(async function () { after(async function () {
await cleanupTests([ server ]) await cleanupTests([ server ])
}) })

View file

@ -540,6 +540,10 @@ function customConfig (): CustomConfig {
subject: { subject: {
prefix: CONFIG.EMAIL.SUBJECT.PREFIX prefix: CONFIG.EMAIL.SUBJECT.PREFIX
} }
},
videoComments: {
acceptRemoteComments: CONFIG.VIDEO_COMMENTS.ACCEPT_REMOTE_COMMENTS
} }
} }
} }

View file

@ -251,7 +251,8 @@ export function checkMissedConfig () {
'storyboards.enabled', 'storyboards.enabled',
'webrtc.stun_servers', 'webrtc.stun_servers',
'nsfw_flags_settings.enabled', 'nsfw_flags_settings.enabled',
'download_generate_video.max_parallel_downloads' 'download_generate_video.max_parallel_downloads',
'video_comments.accept_remote_comments'
] ]
const requiredAlternatives = [ const requiredAlternatives = [

View file

@ -1,7 +1,3 @@
import bytes from 'bytes'
import { IConfig } from 'config'
import { createRequire } from 'module'
import { dirname, join } from 'path'
import { import {
BroadcastMessageLevel, BroadcastMessageLevel,
NSFWPolicyType, NSFWPolicyType,
@ -10,10 +6,14 @@ import {
VideoRedundancyConfigFilter, VideoRedundancyConfigFilter,
VideosRedundancyStrategy VideosRedundancyStrategy
} from '@peertube/peertube-models' } from '@peertube/peertube-models'
import { decacheModule } from '@server/helpers/decache.js'
import { buildPath, root } from '@peertube/peertube-node-utils' import { buildPath, root } from '@peertube/peertube-node-utils'
import { parseBytes, parseDurationToMs } from '../helpers/core-utils.js'
import { TranscriptionEngineName, WhisperBuiltinModelName } from '@peertube/peertube-transcription' import { TranscriptionEngineName, WhisperBuiltinModelName } from '@peertube/peertube-transcription'
import { decacheModule } from '@server/helpers/decache.js'
import bytes from 'bytes'
import { IConfig } from 'config'
import { createRequire } from 'module'
import { dirname, join } from 'path'
import { parseBytes, parseDurationToMs } from '../helpers/core-utils.js'
const require = createRequire(import.meta.url) const require = createRequire(import.meta.url)
let config: IConfig = require('config') let config: IConfig = require('config')
@ -1090,6 +1090,11 @@ const CONFIG = {
return config.get<string>('email.subject.prefix') return config.get<string>('email.subject.prefix')
} }
} }
},
VIDEO_COMMENTS: {
get ACCEPT_REMOTE_COMMENTS () {
return config.get<boolean>('video_comments.accept_remote_comments')
}
} }
} }
@ -1125,8 +1130,8 @@ export {
CONFIG, CONFIG,
getConfigModule, getConfigModule,
getLocalConfigFilePath, getLocalConfigFilePath,
registerConfigChangedHandler, isEmailEnabled,
isEmailEnabled registerConfigChangedHandler
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View file

@ -1,3 +1,4 @@
import { arrayify } from '@peertube/peertube-core-utils'
import { import {
AbuseObject, AbuseObject,
ActivityCreate, ActivityCreate,
@ -9,6 +10,7 @@ import {
VideoObject, VideoObject,
WatchActionObject WatchActionObject
} from '@peertube/peertube-models' } from '@peertube/peertube-models'
import { CONFIG } from '@server/initializers/config.js'
import { isBlockedByServerOrAccount } from '@server/lib/blocklist.js' import { isBlockedByServerOrAccount } from '@server/lib/blocklist.js'
import { isRedundancyAccepted } from '@server/lib/redundancy.js' import { isRedundancyAccepted } from '@server/lib/redundancy.js'
import { VideoCommentModel } from '@server/models/video/video-comment.js' import { VideoCommentModel } from '@server/models/video/video-comment.js'
@ -27,7 +29,6 @@ import { sendReplyApproval } from '../send/send-reply-approval.js'
import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js' import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js'
import { resolveThread } from '../video-comments.js' import { resolveThread } from '../video-comments.js'
import { canVideoBeFederated, getOrCreateAPVideo } from '../videos/index.js' import { canVideoBeFederated, getOrCreateAPVideo } from '../videos/index.js'
import { arrayify } from '@peertube/peertube-core-utils'
async function processCreateActivity (options: APProcessorOptions<ActivityCreate<ActivityCreateObject>>) { async function processCreateActivity (options: APProcessorOptions<ActivityCreate<ActivityCreateObject>>) {
const { activity, byActor } = options const { activity, byActor } = options
@ -123,10 +124,11 @@ async function processCreateVideoComment (
byActor: MActorSignature, byActor: MActorSignature,
fromFetch: false fromFetch: false
) { ) {
if (CONFIG.VIDEO_COMMENTS.ACCEPT_REMOTE_COMMENTS !== true) return
if (fromFetch) throw new Error('Processing create video comment from fetch is not supported') if (fromFetch) throw new Error('Processing create video comment from fetch is not supported')
const byAccount = byActor.Account const byAccount = byActor.Account
if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url)
let video: MVideoAccountLightBlacklistAllFiles let video: MVideoAccountLightBlacklistAllFiles

View file

@ -20,6 +20,7 @@ import { fetchAP } from './activity.js'
import { getOrCreateAPActor } from './actors/index.js' import { getOrCreateAPActor } from './actors/index.js'
import { checkUrlsSameHost } from './url.js' import { checkUrlsSameHost } from './url.js'
import { canVideoBeFederated, getOrCreateAPVideo } from './videos/index.js' import { canVideoBeFederated, getOrCreateAPVideo } from './videos/index.js'
import { CONFIG } from '@server/initializers/config.js'
type ResolveThreadParams = { type ResolveThreadParams = {
url: string url: string
@ -30,6 +31,8 @@ type ResolveThreadParams = {
type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }> type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }>
export async function addVideoComments (commentUrls: string[]) { export async function addVideoComments (commentUrls: string[]) {
if (CONFIG.VIDEO_COMMENTS.ACCEPT_REMOTE_COMMENTS !== true) return
return Bluebird.map(commentUrls, async commentUrl => { return Bluebird.map(commentUrls, async commentUrl => {
try { try {
await resolveThread({ url: commentUrl, isVideo: false }) await resolveThread({ url: commentUrl, isVideo: false })

View file

@ -147,6 +147,8 @@ const customConfigUpdateValidator = [
body('email.body.signature').exists(), body('email.body.signature').exists(),
body('email.subject.prefix').exists(), body('email.subject.prefix').exists(),
body('videoComments.acceptRemoteComments').isBoolean(),
(req: express.Request, res: express.Response, next: express.NextFunction) => { (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!checkInvalidConfigIfEmailDisabled(req.body, res)) return if (!checkInvalidConfigIfEmailDisabled(req.body, res)) return