aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/controllers/activitypub/client.ts2
-rw-r--r--server/controllers/api/videos/comment.ts2
-rw-r--r--server/lib/activitypub/process/process-create.ts23
-rw-r--r--server/lib/blocklist.ts25
-rw-r--r--server/lib/notifier.ts55
-rw-r--r--server/models/account/account.ts26
-rw-r--r--server/models/utils.ts5
-rw-r--r--server/models/video/video-abuse.ts2
-rw-r--r--server/models/video/video-comment.ts76
-rw-r--r--server/tests/api/users/blocklist.ts170
-rw-r--r--server/tests/api/videos/multiple-servers.ts9
-rw-r--r--server/tests/feeds/feeds.ts41
-rw-r--r--shared/extra-utils/videos/video-comments.ts7
-rw-r--r--shared/extra-utils/videos/videos.ts9
14 files changed, 350 insertions, 102 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index f94abf808..e48641836 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -285,7 +285,7 @@ async function videoCommentsController (req: express.Request, res: express.Respo
285 const video = res.locals.onlyImmutableVideo 285 const video = res.locals.onlyImmutableVideo
286 286
287 const handler = async (start: number, count: number) => { 287 const handler = async (start: number, count: number) => {
288 const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) 288 const result = await VideoCommentModel.listAndCountByVideoForAP(video, start, count)
289 return { 289 return {
290 total: result.count, 290 total: result.count,
291 data: result.rows.map(r => r.url) 291 data: result.rows.map(r => r.url)
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts
index 2dcb85ecf..45ff969d9 100644
--- a/server/controllers/api/videos/comment.ts
+++ b/server/controllers/api/videos/comment.ts
@@ -78,6 +78,7 @@ async function listVideoThreads (req: express.Request, res: express.Response) {
78 if (video.commentsEnabled === true) { 78 if (video.commentsEnabled === true) {
79 const apiOptions = await Hooks.wrapObject({ 79 const apiOptions = await Hooks.wrapObject({
80 videoId: video.id, 80 videoId: video.id,
81 isVideoOwned: video.isOwned(),
81 start: req.query.start, 82 start: req.query.start,
82 count: req.query.count, 83 count: req.query.count,
83 sort: req.query.sort, 84 sort: req.query.sort,
@@ -108,6 +109,7 @@ async function listVideoThreadComments (req: express.Request, res: express.Respo
108 if (video.commentsEnabled === true) { 109 if (video.commentsEnabled === true) {
109 const apiOptions = await Hooks.wrapObject({ 110 const apiOptions = await Hooks.wrapObject({
110 videoId: video.id, 111 videoId: video.id,
112 isVideoOwned: video.isOwned(),
111 threadId: res.locals.videoCommentThread.id, 113 threadId: res.locals.videoCommentThread.id,
112 user 114 user
113 }, 'filter:api.video-thread-comments.list.params') 115 }, 'filter:api.video-thread-comments.list.params')
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index 566bf6992..f8f9b80c6 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -1,18 +1,19 @@
1import { isRedundancyAccepted } from '@server/lib/redundancy'
1import { ActivityCreate, CacheFileObject, VideoTorrentObject } from '../../../../shared' 2import { ActivityCreate, CacheFileObject, VideoTorrentObject } from '../../../../shared'
3import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
2import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object' 4import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object'
3import { retryTransactionWrapper } from '../../../helpers/database-utils' 5import { retryTransactionWrapper } from '../../../helpers/database-utils'
4import { logger } from '../../../helpers/logger' 6import { logger } from '../../../helpers/logger'
5import { sequelizeTypescript } from '../../../initializers/database' 7import { sequelizeTypescript } from '../../../initializers/database'
6import { resolveThread } from '../video-comments'
7import { getOrCreateVideoAndAccountAndChannel } from '../videos'
8import { forwardVideoRelatedActivity } from '../send/utils'
9import { createOrUpdateCacheFile } from '../cache-file'
10import { Notifier } from '../../notifier'
11import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
12import { createOrUpdateVideoPlaylist } from '../playlist'
13import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 8import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
14import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models' 9import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models'
15import { isRedundancyAccepted } from '@server/lib/redundancy' 10import { Notifier } from '../../notifier'
11import { createOrUpdateCacheFile } from '../cache-file'
12import { createOrUpdateVideoPlaylist } from '../playlist'
13import { forwardVideoRelatedActivity } from '../send/utils'
14import { resolveThread } from '../video-comments'
15import { getOrCreateVideoAndAccountAndChannel } from '../videos'
16import { isBlockedByServerOrAccount } from '@server/lib/blocklist'
16 17
17async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) { 18async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) {
18 const { activity, byActor } = options 19 const { activity, byActor } = options
@@ -101,6 +102,12 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: MAc
101 return 102 return
102 } 103 }
103 104
105 // Try to not forward unwanted commments on our videos
106 if (video.isOwned() && await isBlockedByServerOrAccount(comment.Account, video.VideoChannel.Account)) {
107 logger.info('Skip comment forward from blocked account or server %s.', comment.Account.Actor.url)
108 return
109 }
110
104 if (video.isOwned() && created === true) { 111 if (video.isOwned() && created === true) {
105 // Don't resend the activity to the sender 112 // Don't resend the activity to the sender
106 const exceptions = [ byActor ] 113 const exceptions = [ byActor ]
diff --git a/server/lib/blocklist.ts b/server/lib/blocklist.ts
index 842eecb5b..d282d091b 100644
--- a/server/lib/blocklist.ts
+++ b/server/lib/blocklist.ts
@@ -1,5 +1,6 @@
1import { sequelizeTypescript } from '@server/initializers/database' 1import { sequelizeTypescript } from '@server/initializers/database'
2import { MAccountBlocklist, MServerBlocklist } from '@server/typings/models' 2import { getServerActor } from '@server/models/application/application'
3import { MAccountBlocklist, MAccountId, MAccountServer, MServerBlocklist } from '@server/typings/models'
3import { AccountBlocklistModel } from '../models/account/account-blocklist' 4import { AccountBlocklistModel } from '../models/account/account-blocklist'
4import { ServerBlocklistModel } from '../models/server/server-blocklist' 5import { ServerBlocklistModel } from '../models/server/server-blocklist'
5 6
@@ -33,9 +34,29 @@ function removeServerFromBlocklist (serverBlock: MServerBlocklist) {
33 }) 34 })
34} 35}
35 36
37async function isBlockedByServerOrAccount (targetAccount: MAccountServer, userAccount?: MAccountId) {
38 const serverAccountId = (await getServerActor()).Account.id
39 const sourceAccounts = [ serverAccountId ]
40
41 if (userAccount) sourceAccounts.push(userAccount.id)
42
43 const accountMutedHash = await AccountBlocklistModel.isAccountMutedByMulti(sourceAccounts, targetAccount.id)
44 if (accountMutedHash[serverAccountId] || (userAccount && accountMutedHash[userAccount.id])) {
45 return true
46 }
47
48 const instanceMutedHash = await ServerBlocklistModel.isServerMutedByMulti(sourceAccounts, targetAccount.Actor.serverId)
49 if (instanceMutedHash[serverAccountId] || (userAccount && instanceMutedHash[userAccount.id])) {
50 return true
51 }
52
53 return false
54}
55
36export { 56export {
37 addAccountInBlocklist, 57 addAccountInBlocklist,
38 addServerInBlocklist, 58 addServerInBlocklist,
39 removeAccountFromBlocklist, 59 removeAccountFromBlocklist,
40 removeServerFromBlocklist 60 removeServerFromBlocklist,
61 isBlockedByServerOrAccount
41} 62}
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts
index 017739523..89f91e031 100644
--- a/server/lib/notifier.ts
+++ b/server/lib/notifier.ts
@@ -1,12 +1,22 @@
1import { getServerActor } from '@server/models/application/application'
2import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
3import {
4 MUser,
5 MUserAccount,
6 MUserDefault,
7 MUserNotifSettingAccount,
8 MUserWithNotificationSetting,
9 UserNotificationModelForApi
10} from '@server/typings/models/user'
11import { MVideoImportVideo } from '@server/typings/models/video/video-import'
1import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users' 12import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users'
13import { VideoAbuse, VideoPrivacy, VideoState } from '../../shared/models/videos'
2import { logger } from '../helpers/logger' 14import { logger } from '../helpers/logger'
3import { Emailer } from './emailer'
4import { UserNotificationModel } from '../models/account/user-notification'
5import { UserModel } from '../models/account/user'
6import { PeerTubeSocket } from './peertube-socket'
7import { CONFIG } from '../initializers/config' 15import { CONFIG } from '../initializers/config'
8import { VideoPrivacy, VideoState, VideoAbuse } from '../../shared/models/videos'
9import { AccountBlocklistModel } from '../models/account/account-blocklist' 16import { AccountBlocklistModel } from '../models/account/account-blocklist'
17import { UserModel } from '../models/account/user'
18import { UserNotificationModel } from '../models/account/user-notification'
19import { MAccountServer, MActorFollowFull } from '../typings/models'
10import { 20import {
11 MCommentOwnerVideo, 21 MCommentOwnerVideo,
12 MVideoAbuseVideo, 22 MVideoAbuseVideo,
@@ -15,18 +25,9 @@ import {
15 MVideoBlacklistVideo, 25 MVideoBlacklistVideo,
16 MVideoFullLight 26 MVideoFullLight
17} from '../typings/models/video' 27} from '../typings/models/video'
18import { 28import { isBlockedByServerOrAccount } from './blocklist'
19 MUser, 29import { Emailer } from './emailer'
20 MUserAccount, 30import { PeerTubeSocket } from './peertube-socket'
21 MUserDefault,
22 MUserNotifSettingAccount,
23 MUserWithNotificationSetting,
24 UserNotificationModelForApi
25} from '@server/typings/models/user'
26import { MAccountDefault, MActorFollowFull } from '../typings/models'
27import { MVideoImportVideo } from '@server/typings/models/video/video-import'
28import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
29import { getServerActor } from '@server/models/application/application'
30 31
31class Notifier { 32class Notifier {
32 33
@@ -169,7 +170,7 @@ class Notifier {
169 // Not our user or user comments its own video 170 // Not our user or user comments its own video
170 if (!user || comment.Account.userId === user.id) return 171 if (!user || comment.Account.userId === user.id) return
171 172
172 if (await this.isBlockedByServerOrAccount(user, comment.Account)) return 173 if (await this.isBlockedByServerOrUser(comment.Account, user)) return
173 174
174 logger.info('Notifying user %s of new comment %s.', user.username, comment.url) 175 logger.info('Notifying user %s of new comment %s.', user.username, comment.url)
175 176
@@ -270,7 +271,7 @@ class Notifier {
270 const followerAccount = actorFollow.ActorFollower.Account 271 const followerAccount = actorFollow.ActorFollower.Account
271 const followerAccountWithActor = Object.assign(followerAccount, { Actor: actorFollow.ActorFollower }) 272 const followerAccountWithActor = Object.assign(followerAccount, { Actor: actorFollow.ActorFollower })
272 273
273 if (await this.isBlockedByServerOrAccount(user, followerAccountWithActor)) return 274 if (await this.isBlockedByServerOrUser(followerAccountWithActor, user)) return
274 275
275 logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName()) 276 logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName())
276 277
@@ -299,6 +300,9 @@ class Notifier {
299 private async notifyAdminsOfNewInstanceFollow (actorFollow: MActorFollowFull) { 300 private async notifyAdminsOfNewInstanceFollow (actorFollow: MActorFollowFull) {
300 const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW) 301 const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
301 302
303 const follower = Object.assign(actorFollow.ActorFollower.Account, { Actor: actorFollow.ActorFollower })
304 if (await this.isBlockedByServerOrUser(follower)) return
305
302 logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url) 306 logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url)
303 307
304 function settingGetter (user: MUserWithNotificationSetting) { 308 function settingGetter (user: MUserWithNotificationSetting) {
@@ -590,17 +594,8 @@ class Notifier {
590 return value & UserNotificationSettingValue.WEB 594 return value & UserNotificationSettingValue.WEB
591 } 595 }
592 596
593 private async isBlockedByServerOrAccount (user: MUserAccount, targetAccount: MAccountDefault) { 597 private isBlockedByServerOrUser (targetAccount: MAccountServer, user?: MUserAccount) {
594 const serverAccountId = (await getServerActor()).Account.id 598 return isBlockedByServerOrAccount(targetAccount, user?.Account)
595 const sourceAccounts = [ serverAccountId, user.Account.id ]
596
597 const accountMutedHash = await AccountBlocklistModel.isAccountMutedByMulti(sourceAccounts, targetAccount.id)
598 if (accountMutedHash[serverAccountId] || accountMutedHash[user.Account.id]) return true
599
600 const instanceMutedHash = await ServerBlocklistModel.isServerMutedByMulti(sourceAccounts, targetAccount.Actor.serverId)
601 if (instanceMutedHash[serverAccountId] || instanceMutedHash[user.Account.id]) return true
602
603 return false
604 } 599 }
605 600
606 static get Instance () { 601 static get Instance () {
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index a0081f259..ad649837a 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -32,9 +32,10 @@ import { FindOptions, IncludeOptions, Op, Transaction, WhereOptions } from 'sequ
32import { AccountBlocklistModel } from './account-blocklist' 32import { AccountBlocklistModel } from './account-blocklist'
33import { ServerBlocklistModel } from '../server/server-blocklist' 33import { ServerBlocklistModel } from '../server/server-blocklist'
34import { ActorFollowModel } from '../activitypub/actor-follow' 34import { ActorFollowModel } from '../activitypub/actor-follow'
35import { MAccountActor, MAccountAP, MAccountDefault, MAccountFormattable, MAccountSummaryFormattable } from '../../typings/models' 35import { MAccountActor, MAccountAP, MAccountDefault, MAccountFormattable, MAccountSummaryFormattable, MAccount } from '../../typings/models'
36import * as Bluebird from 'bluebird' 36import * as Bluebird from 'bluebird'
37import { ModelCache } from '@server/models/model-cache' 37import { ModelCache } from '@server/models/model-cache'
38import { VideoModel } from '../video/video'
38 39
39export enum ScopeNames { 40export enum ScopeNames {
40 SUMMARY = 'SUMMARY' 41 SUMMARY = 'SUMMARY'
@@ -343,6 +344,29 @@ export class AccountModel extends Model<AccountModel> {
343 }) 344 })
344 } 345 }
345 346
347 static loadAccountIdFromVideo (videoId: number): Bluebird<MAccount> {
348 const query = {
349 include: [
350 {
351 attributes: [ 'id', 'accountId' ],
352 model: VideoChannelModel.unscoped(),
353 required: true,
354 include: [
355 {
356 attributes: [ 'id', 'channelId' ],
357 model: VideoModel.unscoped(),
358 where: {
359 id: videoId
360 }
361 }
362 ]
363 }
364 ]
365 }
366
367 return AccountModel.findOne(query)
368 }
369
346 static listLocalsForSitemap (sort: string): Bluebird<MAccountActor[]> { 370 static listLocalsForSitemap (sort: string): Bluebird<MAccountActor[]> {
347 const query = { 371 const query = {
348 attributes: [ ], 372 attributes: [ ],
diff --git a/server/models/utils.ts b/server/models/utils.ts
index b2573cd35..88c9b4adb 100644
--- a/server/models/utils.ts
+++ b/server/models/utils.ts
@@ -136,10 +136,7 @@ function createSimilarityAttribute (col: string, value: string) {
136 ) 136 )
137} 137}
138 138
139function buildBlockedAccountSQL (serverAccountId: number, userAccountId?: number) { 139function buildBlockedAccountSQL (blockerIds: number[]) {
140 const blockerIds = [ serverAccountId ]
141 if (userAccountId) blockerIds.push(userAccountId)
142
143 const blockerIdsString = blockerIds.join(', ') 140 const blockerIdsString = blockerIds.join(', ')
144 141
145 return 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' + 142 return 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' +
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts
index 0844f702d..e0cf50b59 100644
--- a/server/models/video/video-abuse.ts
+++ b/server/models/video/video-abuse.ts
@@ -57,7 +57,7 @@ export enum ScopeNames {
57 }) => { 57 }) => {
58 const where = { 58 const where = {
59 reporterAccountId: { 59 reporterAccountId: {
60 [Op.notIn]: literal('(' + buildBlockedAccountSQL(options.serverAccountId, options.userAccountId) + ')') 60 [Op.notIn]: literal('(' + buildBlockedAccountSQL([ options.serverAccountId, options.userAccountId ]) + ')')
61 } 61 }
62 } 62 }
63 63
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts
index dfeb1c4e7..ba09522cc 100644
--- a/server/models/video/video-comment.ts
+++ b/server/models/video/video-comment.ts
@@ -21,7 +21,8 @@ import {
21 MCommentOwnerReplyVideoLight, 21 MCommentOwnerReplyVideoLight,
22 MCommentOwnerVideo, 22 MCommentOwnerVideo,
23 MCommentOwnerVideoFeed, 23 MCommentOwnerVideoFeed,
24 MCommentOwnerVideoReply 24 MCommentOwnerVideoReply,
25 MVideoImmutable
25} from '../../typings/models/video' 26} from '../../typings/models/video'
26import { AccountModel } from '../account/account' 27import { AccountModel } from '../account/account'
27import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' 28import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor'
@@ -38,14 +39,14 @@ enum ScopeNames {
38} 39}
39 40
40@Scopes(() => ({ 41@Scopes(() => ({
41 [ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => { 42 [ScopeNames.ATTRIBUTES_FOR_API]: (blockerAccountIds: number[]) => {
42 return { 43 return {
43 attributes: { 44 attributes: {
44 include: [ 45 include: [
45 [ 46 [
46 Sequelize.literal( 47 Sequelize.literal(
47 '(' + 48 '(' +
48 'WITH "blocklist" AS (' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' + 49 'WITH "blocklist" AS (' + buildBlockedAccountSQL(blockerAccountIds) + ')' +
49 'SELECT COUNT("replies"."id") - (' + 50 'SELECT COUNT("replies"."id") - (' +
50 'SELECT COUNT("replies"."id") ' + 51 'SELECT COUNT("replies"."id") ' +
51 'FROM "videoComment" AS "replies" ' + 52 'FROM "videoComment" AS "replies" ' +
@@ -276,16 +277,15 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
276 277
277 static async listThreadsForApi (parameters: { 278 static async listThreadsForApi (parameters: {
278 videoId: number 279 videoId: number
280 isVideoOwned: boolean
279 start: number 281 start: number
280 count: number 282 count: number
281 sort: string 283 sort: string
282 user?: MUserAccountId 284 user?: MUserAccountId
283 }) { 285 }) {
284 const { videoId, start, count, sort, user } = parameters 286 const { videoId, isVideoOwned, start, count, sort, user } = parameters
285 287
286 const serverActor = await getServerActor() 288 const blockerAccountIds = await VideoCommentModel.buildBlockerAccountIds({ videoId, user, isVideoOwned })
287 const serverAccountId = serverActor.Account.id
288 const userAccountId = user ? user.Account.id : undefined
289 289
290 const query = { 290 const query = {
291 offset: start, 291 offset: start,
@@ -304,7 +304,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
304 { 304 {
305 accountId: { 305 accountId: {
306 [Op.notIn]: Sequelize.literal( 306 [Op.notIn]: Sequelize.literal(
307 '(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' 307 '(' + buildBlockedAccountSQL(blockerAccountIds) + ')'
308 ) 308 )
309 } 309 }
310 }, 310 },
@@ -320,7 +320,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
320 const scopes: (string | ScopeOptions)[] = [ 320 const scopes: (string | ScopeOptions)[] = [
321 ScopeNames.WITH_ACCOUNT_FOR_API, 321 ScopeNames.WITH_ACCOUNT_FOR_API,
322 { 322 {
323 method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] 323 method: [ ScopeNames.ATTRIBUTES_FOR_API, blockerAccountIds ]
324 } 324 }
325 ] 325 ]
326 326
@@ -334,14 +334,13 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
334 334
335 static async listThreadCommentsForApi (parameters: { 335 static async listThreadCommentsForApi (parameters: {
336 videoId: number 336 videoId: number
337 isVideoOwned: boolean
337 threadId: number 338 threadId: number
338 user?: MUserAccountId 339 user?: MUserAccountId
339 }) { 340 }) {
340 const { videoId, threadId, user } = parameters 341 const { videoId, threadId, user, isVideoOwned } = parameters
341 342
342 const serverActor = await getServerActor() 343 const blockerAccountIds = await VideoCommentModel.buildBlockerAccountIds({ videoId, user, isVideoOwned })
343 const serverAccountId = serverActor.Account.id
344 const userAccountId = user ? user.Account.id : undefined
345 344
346 const query = { 345 const query = {
347 order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ] as Order, 346 order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ] as Order,
@@ -353,7 +352,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
353 ], 352 ],
354 accountId: { 353 accountId: {
355 [Op.notIn]: Sequelize.literal( 354 [Op.notIn]: Sequelize.literal(
356 '(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' 355 '(' + buildBlockedAccountSQL(blockerAccountIds) + ')'
357 ) 356 )
358 } 357 }
359 } 358 }
@@ -362,7 +361,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
362 const scopes: any[] = [ 361 const scopes: any[] = [
363 ScopeNames.WITH_ACCOUNT_FOR_API, 362 ScopeNames.WITH_ACCOUNT_FOR_API,
364 { 363 {
365 method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] 364 method: [ ScopeNames.ATTRIBUTES_FOR_API, blockerAccountIds ]
366 } 365 }
367 ] 366 ]
368 367
@@ -399,13 +398,23 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
399 .findAll(query) 398 .findAll(query)
400 } 399 }
401 400
402 static listAndCountByVideoId (videoId: number, start: number, count: number, t?: Transaction, order: 'ASC' | 'DESC' = 'ASC') { 401 static async listAndCountByVideoForAP (video: MVideoImmutable, start: number, count: number, t?: Transaction) {
402 const blockerAccountIds = await VideoCommentModel.buildBlockerAccountIds({
403 videoId: video.id,
404 isVideoOwned: video.isOwned()
405 })
406
403 const query = { 407 const query = {
404 order: [ [ 'createdAt', order ] ] as Order, 408 order: [ [ 'createdAt', 'ASC' ] ] as Order,
405 offset: start, 409 offset: start,
406 limit: count, 410 limit: count,
407 where: { 411 where: {
408 videoId 412 videoId: video.id,
413 accountId: {
414 [Op.notIn]: Sequelize.literal(
415 '(' + buildBlockedAccountSQL(blockerAccountIds) + ')'
416 )
417 }
409 }, 418 },
410 transaction: t 419 transaction: t
411 } 420 }
@@ -424,7 +433,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
424 deletedAt: null, 433 deletedAt: null,
425 accountId: { 434 accountId: {
426 [Op.notIn]: Sequelize.literal( 435 [Op.notIn]: Sequelize.literal(
427 '(' + buildBlockedAccountSQL(serverActor.Account.id) + ')' 436 '(' + buildBlockedAccountSQL([ serverActor.Account.id, '"Video->VideoChannel"."accountId"' ]) + ')'
428 ) 437 )
429 } 438 }
430 }, 439 },
@@ -435,7 +444,14 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
435 required: true, 444 required: true,
436 where: { 445 where: {
437 privacy: VideoPrivacy.PUBLIC 446 privacy: VideoPrivacy.PUBLIC
438 } 447 },
448 include: [
449 {
450 attributes: [ 'accountId' ],
451 model: VideoChannelModel.unscoped(),
452 required: true
453 }
454 ]
439 } 455 }
440 ] 456 ]
441 } 457 }
@@ -650,4 +666,24 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
650 tag 666 tag
651 } 667 }
652 } 668 }
669
670 private static async buildBlockerAccountIds (options: {
671 videoId: number
672 isVideoOwned: boolean
673 user?: MUserAccountId
674 }) {
675 const { videoId, user, isVideoOwned } = options
676
677 const serverActor = await getServerActor()
678 const blockerAccountIds = [ serverActor.Account.id ]
679
680 if (user) blockerAccountIds.push(user.Account.id)
681
682 if (isVideoOwned) {
683 const videoOwnerAccount = await AccountModel.loadAccountIdFromVideo(videoId)
684 blockerAccountIds.push(videoOwnerAccount.id)
685 }
686
687 return blockerAccountIds
688 }
653} 689}
diff --git a/server/tests/api/users/blocklist.ts b/server/tests/api/users/blocklist.ts
index e37dbb5a4..8c9107a50 100644
--- a/server/tests/api/users/blocklist.ts
+++ b/server/tests/api/users/blocklist.ts
@@ -2,7 +2,7 @@
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { AccountBlock, ServerBlock, Video } from '../../../../shared/index' 5import { AccountBlock, ServerBlock, Video, UserNotification, UserNotificationType } from '../../../../shared/index'
6import { 6import {
7 cleanupTests, 7 cleanupTests,
8 createUser, 8 createUser,
@@ -11,7 +11,9 @@ import {
11 flushAndRunMultipleServers, 11 flushAndRunMultipleServers,
12 ServerInfo, 12 ServerInfo,
13 uploadVideo, 13 uploadVideo,
14 userLogin 14 userLogin,
15 follow,
16 unfollow
15} from '../../../../shared/extra-utils/index' 17} from '../../../../shared/extra-utils/index'
16import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' 18import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
17import { getVideosList, getVideosListWithToken } from '../../../../shared/extra-utils/videos/videos' 19import { getVideosList, getVideosListWithToken } from '../../../../shared/extra-utils/videos/videos'
@@ -19,7 +21,8 @@ import {
19 addVideoCommentReply, 21 addVideoCommentReply,
20 addVideoCommentThread, 22 addVideoCommentThread,
21 getVideoCommentThreads, 23 getVideoCommentThreads,
22 getVideoThreadComments 24 getVideoThreadComments,
25 findCommentId
23} from '../../../../shared/extra-utils/videos/video-comments' 26} from '../../../../shared/extra-utils/videos/video-comments'
24import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 27import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
25import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' 28import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
@@ -45,13 +48,13 @@ async function checkAllVideos (url: string, token: string) {
45 { 48 {
46 const res = await getVideosListWithToken(url, token) 49 const res = await getVideosListWithToken(url, token)
47 50
48 expect(res.body.data).to.have.lengthOf(4) 51 expect(res.body.data).to.have.lengthOf(5)
49 } 52 }
50 53
51 { 54 {
52 const res = await getVideosList(url) 55 const res = await getVideosList(url)
53 56
54 expect(res.body.data).to.have.lengthOf(4) 57 expect(res.body.data).to.have.lengthOf(5)
55 } 58 }
56} 59}
57 60
@@ -76,13 +79,15 @@ async function checkCommentNotification (
76 check: 'presence' | 'absence' 79 check: 'presence' | 'absence'
77) { 80) {
78 const resComment = await addVideoCommentThread(comment.server.url, comment.token, comment.videoUUID, comment.text) 81 const resComment = await addVideoCommentThread(comment.server.url, comment.token, comment.videoUUID, comment.text)
79 const threadId = resComment.body.comment.id 82 const created = resComment.body.comment as VideoComment
83 const threadId = created.id
84 const createdAt = created.createdAt
80 85
81 await waitJobs([ mainServer, comment.server ]) 86 await waitJobs([ mainServer, comment.server ])
82 87
83 const res = await getUserNotifications(mainServer.url, mainServer.accessToken, 0, 30) 88 const res = await getUserNotifications(mainServer.url, mainServer.accessToken, 0, 30)
84 const commentNotifications = res.body.data 89 const commentNotifications = (res.body.data as UserNotification[])
85 .filter(n => n.comment && n.comment.id === threadId) 90 .filter(n => n.comment && n.comment.video.uuid === comment.videoUUID && n.createdAt >= createdAt)
86 91
87 if (check === 'presence') expect(commentNotifications).to.have.lengthOf(1) 92 if (check === 'presence') expect(commentNotifications).to.have.lengthOf(1)
88 else expect(commentNotifications).to.have.lengthOf(0) 93 else expect(commentNotifications).to.have.lengthOf(0)
@@ -96,6 +101,7 @@ describe('Test blocklist', function () {
96 let servers: ServerInfo[] 101 let servers: ServerInfo[]
97 let videoUUID1: string 102 let videoUUID1: string
98 let videoUUID2: string 103 let videoUUID2: string
104 let videoUUID3: string
99 let userToken1: string 105 let userToken1: string
100 let userModeratorToken: string 106 let userModeratorToken: string
101 let userToken2: string 107 let userToken2: string
@@ -103,7 +109,7 @@ describe('Test blocklist', function () {
103 before(async function () { 109 before(async function () {
104 this.timeout(60000) 110 this.timeout(60000)
105 111
106 servers = await flushAndRunMultipleServers(2) 112 servers = await flushAndRunMultipleServers(3)
107 await setAccessTokensToServers(servers) 113 await setAccessTokensToServers(servers)
108 114
109 { 115 {
@@ -139,7 +145,13 @@ describe('Test blocklist', function () {
139 videoUUID2 = res.body.video.uuid 145 videoUUID2 = res.body.video.uuid
140 } 146 }
141 147
148 {
149 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video 2 server 1' })
150 videoUUID3 = res.body.video.uuid
151 }
152
142 await doubleFollow(servers[0], servers[1]) 153 await doubleFollow(servers[0], servers[1])
154 await doubleFollow(servers[0], servers[2])
143 155
144 { 156 {
145 const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID1, 'comment root 1') 157 const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID1, 'comment root 1')
@@ -174,7 +186,7 @@ describe('Test blocklist', function () {
174 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) 186 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
175 187
176 const videos: Video[] = res.body.data 188 const videos: Video[] = res.body.data
177 expect(videos).to.have.lengthOf(3) 189 expect(videos).to.have.lengthOf(4)
178 190
179 const v = videos.find(v => v.name === 'video user 2') 191 const v = videos.find(v => v.name === 'video user 2')
180 expect(v).to.be.undefined 192 expect(v).to.be.undefined
@@ -188,7 +200,7 @@ describe('Test blocklist', function () {
188 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) 200 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
189 201
190 const videos: Video[] = res.body.data 202 const videos: Video[] = res.body.data
191 expect(videos).to.have.lengthOf(2) 203 expect(videos).to.have.lengthOf(3)
192 204
193 const v = videos.find(v => v.name === 'video user 1') 205 const v = videos.find(v => v.name === 'video user 1')
194 expect(v).to.be.undefined 206 expect(v).to.be.undefined
@@ -235,10 +247,6 @@ describe('Test blocklist', function () {
235 return checkAllVideos(servers[0].url, userToken1) 247 return checkAllVideos(servers[0].url, userToken1)
236 }) 248 })
237 249
238 it('Should list all the comments with another user', async function () {
239 return checkAllComments(servers[0].url, userToken1, videoUUID1)
240 })
241
242 it('Should list blocked accounts', async function () { 250 it('Should list blocked accounts', async function () {
243 { 251 {
244 const res = await getAccountBlocklistByAccount(servers[0].url, servers[0].accessToken, 0, 1, 'createdAt') 252 const res = await getAccountBlocklistByAccount(servers[0].url, servers[0].accessToken, 0, 1, 'createdAt')
@@ -269,6 +277,61 @@ describe('Test blocklist', function () {
269 } 277 }
270 }) 278 })
271 279
280 it('Should not allow a remote blocked user to comment my videos', async function () {
281 this.timeout(60000)
282
283 {
284 await addVideoCommentThread(servers[1].url, userToken2, videoUUID3, 'comment user 2')
285 await waitJobs(servers)
286
287 await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID3, 'uploader')
288 await waitJobs(servers)
289
290 const commentId = await findCommentId(servers[1].url, videoUUID3, 'uploader')
291 const message = 'reply by user 2'
292 const resReply = await addVideoCommentReply(servers[1].url, userToken2, videoUUID3, commentId, message)
293 await addVideoCommentReply(servers[1].url, servers[1].accessToken, videoUUID3, resReply.body.comment.id, 'another reply')
294
295 await waitJobs(servers)
296 }
297
298 // Server 2 has all the comments
299 {
300 const resThreads = await getVideoCommentThreads(servers[1].url, videoUUID3, 0, 25, '-createdAt')
301 const threads: VideoComment[] = resThreads.body.data
302
303 expect(threads).to.have.lengthOf(2)
304 expect(threads[0].text).to.equal('uploader')
305 expect(threads[1].text).to.equal('comment user 2')
306
307 const resReplies = await getVideoThreadComments(servers[1].url, videoUUID3, threads[0].id)
308
309 const tree: VideoCommentThreadTree = resReplies.body
310 expect(tree.children).to.have.lengthOf(1)
311 expect(tree.children[0].comment.text).to.equal('reply by user 2')
312 expect(tree.children[0].children).to.have.lengthOf(1)
313 expect(tree.children[0].children[0].comment.text).to.equal('another reply')
314 }
315
316 // Server 1 and 3 should only have uploader comments
317 for (const server of [ servers[0], servers[2] ]) {
318 const resThreads = await getVideoCommentThreads(server.url, videoUUID3, 0, 25, '-createdAt')
319 const threads: VideoComment[] = resThreads.body.data
320
321 expect(threads).to.have.lengthOf(1)
322 expect(threads[0].text).to.equal('uploader')
323
324 const resReplies = await getVideoThreadComments(server.url, videoUUID3, threads[0].id)
325
326 const tree: VideoCommentThreadTree = resReplies.body
327 if (server.serverNumber === 1) {
328 expect(tree.children).to.have.lengthOf(0)
329 } else {
330 expect(tree.children).to.have.lengthOf(1)
331 }
332 }
333 })
334
272 it('Should unblock the remote account', async function () { 335 it('Should unblock the remote account', async function () {
273 await removeAccountFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:' + servers[1].port) 336 await removeAccountFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:' + servers[1].port)
274 }) 337 })
@@ -277,12 +340,37 @@ describe('Test blocklist', function () {
277 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) 340 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
278 341
279 const videos: Video[] = res.body.data 342 const videos: Video[] = res.body.data
280 expect(videos).to.have.lengthOf(3) 343 expect(videos).to.have.lengthOf(4)
281 344
282 const v = videos.find(v => v.name === 'video user 2') 345 const v = videos.find(v => v.name === 'video user 2')
283 expect(v).not.to.be.undefined 346 expect(v).not.to.be.undefined
284 }) 347 })
285 348
349 it('Should display its comments on my video', async function () {
350 for (const server of servers) {
351 const resThreads = await getVideoCommentThreads(server.url, videoUUID3, 0, 25, '-createdAt')
352 const threads: VideoComment[] = resThreads.body.data
353
354 // Server 3 should not have 2 comment threads, because server 1 did not forward the server 2 comment
355 if (server.serverNumber === 3) {
356 expect(threads).to.have.lengthOf(1)
357 continue
358 }
359
360 expect(threads).to.have.lengthOf(2)
361 expect(threads[0].text).to.equal('uploader')
362 expect(threads[1].text).to.equal('comment user 2')
363
364 const resReplies = await getVideoThreadComments(server.url, videoUUID3, threads[0].id)
365
366 const tree: VideoCommentThreadTree = resReplies.body
367 expect(tree.children).to.have.lengthOf(1)
368 expect(tree.children[0].comment.text).to.equal('reply by user 2')
369 expect(tree.children[0].children).to.have.lengthOf(1)
370 expect(tree.children[0].children[0].comment.text).to.equal('another reply')
371 }
372 })
373
286 it('Should unblock the local account', async function () { 374 it('Should unblock the local account', async function () {
287 await removeAccountFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'user1') 375 await removeAccountFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'user1')
288 }) 376 })
@@ -328,7 +416,7 @@ describe('Test blocklist', function () {
328 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) 416 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
329 417
330 const videos: Video[] = res.body.data 418 const videos: Video[] = res.body.data
331 expect(videos).to.have.lengthOf(2) 419 expect(videos).to.have.lengthOf(3)
332 420
333 const v1 = videos.find(v => v.name === 'video user 2') 421 const v1 = videos.find(v => v.name === 'video user 2')
334 const v2 = videos.find(v => v.name === 'video server 2') 422 const v2 = videos.find(v => v.name === 'video server 2')
@@ -442,7 +530,7 @@ describe('Test blocklist', function () {
442 const res = await getVideosListWithToken(servers[0].url, token) 530 const res = await getVideosListWithToken(servers[0].url, token)
443 531
444 const videos: Video[] = res.body.data 532 const videos: Video[] = res.body.data
445 expect(videos).to.have.lengthOf(3) 533 expect(videos).to.have.lengthOf(4)
446 534
447 const v = videos.find(v => v.name === 'video user 2') 535 const v = videos.find(v => v.name === 'video user 2')
448 expect(v).to.be.undefined 536 expect(v).to.be.undefined
@@ -458,7 +546,7 @@ describe('Test blocklist', function () {
458 const res = await getVideosListWithToken(servers[0].url, token) 546 const res = await getVideosListWithToken(servers[0].url, token)
459 547
460 const videos: Video[] = res.body.data 548 const videos: Video[] = res.body.data
461 expect(videos).to.have.lengthOf(2) 549 expect(videos).to.have.lengthOf(3)
462 550
463 const v = videos.find(v => v.name === 'video user 1') 551 const v = videos.find(v => v.name === 'video user 1')
464 expect(v).to.be.undefined 552 expect(v).to.be.undefined
@@ -545,7 +633,7 @@ describe('Test blocklist', function () {
545 const res = await getVideosListWithToken(servers[0].url, token) 633 const res = await getVideosListWithToken(servers[0].url, token)
546 634
547 const videos: Video[] = res.body.data 635 const videos: Video[] = res.body.data
548 expect(videos).to.have.lengthOf(3) 636 expect(videos).to.have.lengthOf(4)
549 637
550 const v = videos.find(v => v.name === 'video user 2') 638 const v = videos.find(v => v.name === 'video user 2')
551 expect(v).not.to.be.undefined 639 expect(v).not.to.be.undefined
@@ -606,7 +694,7 @@ describe('Test blocklist', function () {
606 694
607 for (const res of [ res1, res2 ]) { 695 for (const res of [ res1, res2 ]) {
608 const videos: Video[] = res.body.data 696 const videos: Video[] = res.body.data
609 expect(videos).to.have.lengthOf(2) 697 expect(videos).to.have.lengthOf(3)
610 698
611 const v1 = videos.find(v => v.name === 'video user 2') 699 const v1 = videos.find(v => v.name === 'video user 2')
612 const v2 = videos.find(v => v.name === 'video server 2') 700 const v2 = videos.find(v => v.name === 'video server 2')
@@ -631,7 +719,7 @@ describe('Test blocklist', function () {
631 }) 719 })
632 720
633 it('Should not have notification from blocked instances by instance', async function () { 721 it('Should not have notification from blocked instances by instance', async function () {
634 this.timeout(20000) 722 this.timeout(50000)
635 723
636 { 724 {
637 const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' } 725 const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' }
@@ -647,6 +735,24 @@ describe('Test blocklist', function () {
647 } 735 }
648 await checkCommentNotification(servers[0], comment, 'absence') 736 await checkCommentNotification(servers[0], comment, 'absence')
649 } 737 }
738
739 {
740 const now = new Date()
741 await unfollow(servers[1].url, servers[1].accessToken, servers[0])
742 await waitJobs(servers)
743 await follow(servers[1].url, [ servers[0].host ], servers[1].accessToken)
744
745 await waitJobs(servers)
746
747 const res = await getUserNotifications(servers[0].url, servers[0].accessToken, 0, 30)
748 const commentNotifications = (res.body.data as UserNotification[])
749 .filter(n => {
750 return n.type === UserNotificationType.NEW_INSTANCE_FOLLOWER &&
751 n.createdAt >= now.toISOString()
752 })
753
754 expect(commentNotifications).to.have.lengthOf(0)
755 }
650 }) 756 })
651 757
652 it('Should list blocked servers', async function () { 758 it('Should list blocked servers', async function () {
@@ -678,7 +784,7 @@ describe('Test blocklist', function () {
678 }) 784 })
679 785
680 it('Should have notification from unblocked instances', async function () { 786 it('Should have notification from unblocked instances', async function () {
681 this.timeout(20000) 787 this.timeout(50000)
682 788
683 { 789 {
684 const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' } 790 const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' }
@@ -694,6 +800,24 @@ describe('Test blocklist', function () {
694 } 800 }
695 await checkCommentNotification(servers[0], comment, 'presence') 801 await checkCommentNotification(servers[0], comment, 'presence')
696 } 802 }
803
804 {
805 const now = new Date()
806 await unfollow(servers[1].url, servers[1].accessToken, servers[0])
807 await waitJobs(servers)
808 await follow(servers[1].url, [ servers[0].host ], servers[1].accessToken)
809
810 await waitJobs(servers)
811
812 const res = await getUserNotifications(servers[0].url, servers[0].accessToken, 0, 30)
813 const commentNotifications = (res.body.data as UserNotification[])
814 .filter(n => {
815 return n.type === UserNotificationType.NEW_INSTANCE_FOLLOWER &&
816 n.createdAt >= now.toISOString()
817 })
818
819 expect(commentNotifications).to.have.lengthOf(1)
820 }
697 }) 821 })
698 }) 822 })
699 }) 823 })
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
index e3029f1ae..d7b04373f 100644
--- a/server/tests/api/videos/multiple-servers.ts
+++ b/server/tests/api/videos/multiple-servers.ts
@@ -37,7 +37,8 @@ import {
37 addVideoCommentThread, 37 addVideoCommentThread,
38 deleteVideoComment, 38 deleteVideoComment,
39 getVideoCommentThreads, 39 getVideoCommentThreads,
40 getVideoThreadComments 40 getVideoThreadComments,
41 findCommentId
41} from '../../../../shared/extra-utils/videos/video-comments' 42} from '../../../../shared/extra-utils/videos/video-comments'
42import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 43import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
43 44
@@ -773,8 +774,7 @@ describe('Test multiple servers', function () {
773 await waitJobs(servers) 774 await waitJobs(servers)
774 775
775 { 776 {
776 const res = await getVideoCommentThreads(servers[1].url, videoUUID, 0, 5) 777 const threadId = await findCommentId(servers[1].url, videoUUID, 'my super first comment')
777 const threadId = res.body.data.find(c => c.text === 'my super first comment').id
778 778
779 const text = 'my super answer to thread 1' 779 const text = 'my super answer to thread 1'
780 await addVideoCommentReply(servers[1].url, servers[1].accessToken, videoUUID, threadId, text) 780 await addVideoCommentReply(servers[1].url, servers[1].accessToken, videoUUID, threadId, text)
@@ -783,8 +783,7 @@ describe('Test multiple servers', function () {
783 await waitJobs(servers) 783 await waitJobs(servers)
784 784
785 { 785 {
786 const res1 = await getVideoCommentThreads(servers[2].url, videoUUID, 0, 5) 786 const threadId = await findCommentId(servers[2].url, videoUUID, 'my super first comment')
787 const threadId = res1.body.data.find(c => c.text === 'my super first comment').id
788 787
789 const res2 = await getVideoThreadComments(servers[2].url, videoUUID, threadId) 788 const res2 = await getVideoThreadComments(servers[2].url, videoUUID, threadId)
790 const childCommentId = res2.body.children[0].comment.id 789 const childCommentId = res2.body.children[0].comment.id
diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts
index 7fac921a3..ba961cdba 100644
--- a/server/tests/feeds/feeds.ts
+++ b/server/tests/feeds/feeds.ts
@@ -1,7 +1,14 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai'
4import 'mocha' 3import 'mocha'
4import * as chai from 'chai'
5import * as libxmljs from 'libxmljs'
6import {
7 addAccountToAccountBlocklist,
8 addAccountToServerBlocklist,
9 removeAccountFromServerBlocklist
10} from '@shared/extra-utils/users/blocklist'
11import { VideoPrivacy } from '@shared/models'
5import { 12import {
6 cleanupTests, 13 cleanupTests,
7 createUser, 14 createUser,
@@ -13,14 +20,12 @@ import {
13 ServerInfo, 20 ServerInfo,
14 setAccessTokensToServers, 21 setAccessTokensToServers,
15 uploadVideo, 22 uploadVideo,
23 uploadVideoAndGetId,
16 userLogin 24 userLogin
17} from '../../../shared/extra-utils' 25} from '../../../shared/extra-utils'
18import * as libxmljs from 'libxmljs'
19import { addVideoCommentThread } from '../../../shared/extra-utils/videos/video-comments'
20import { waitJobs } from '../../../shared/extra-utils/server/jobs' 26import { waitJobs } from '../../../shared/extra-utils/server/jobs'
27import { addVideoCommentThread } from '../../../shared/extra-utils/videos/video-comments'
21import { User } from '../../../shared/models/users' 28import { User } from '../../../shared/models/users'
22import { VideoPrivacy } from '@shared/models'
23import { addAccountToServerBlocklist } from '@shared/extra-utils/users/blocklist'
24 29
25chai.use(require('chai-xml')) 30chai.use(require('chai-xml'))
26chai.use(require('chai-json-schema')) 31chai.use(require('chai-json-schema'))
@@ -219,7 +224,11 @@ describe('Test syndication feeds', () => {
219 }) 224 })
220 225
221 it('Should not list comments from muted accounts or instances', async function () { 226 it('Should not list comments from muted accounts or instances', async function () {
222 await addAccountToServerBlocklist(servers[1].url, servers[1].accessToken, 'root@localhost:' + servers[0].port) 227 this.timeout(30000)
228
229 const remoteHandle = 'root@localhost:' + servers[0].port
230
231 await addAccountToServerBlocklist(servers[1].url, servers[1].accessToken, remoteHandle)
223 232
224 { 233 {
225 const json = await getJSONfeed(servers[1].url, 'video-comments', { version: 2 }) 234 const json = await getJSONfeed(servers[1].url, 'video-comments', { version: 2 })
@@ -227,6 +236,26 @@ describe('Test syndication feeds', () => {
227 expect(jsonObj.items.length).to.be.equal(0) 236 expect(jsonObj.items.length).to.be.equal(0)
228 } 237 }
229 238
239 await removeAccountFromServerBlocklist(servers[1].url, servers[1].accessToken, remoteHandle)
240
241 {
242 const videoUUID = (await uploadVideoAndGetId({ server: servers[1], videoName: 'server 2' })).uuid
243 await waitJobs(servers)
244 await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'super comment')
245 await waitJobs(servers)
246
247 const json = await getJSONfeed(servers[1].url, 'video-comments', { version: 3 })
248 const jsonObj = JSON.parse(json.text)
249 expect(jsonObj.items.length).to.be.equal(3)
250 }
251
252 await addAccountToAccountBlocklist(servers[1].url, servers[1].accessToken, remoteHandle)
253
254 {
255 const json = await getJSONfeed(servers[1].url, 'video-comments', { version: 4 })
256 const jsonObj = JSON.parse(json.text)
257 expect(jsonObj.items.length).to.be.equal(2)
258 }
230 }) 259 })
231 }) 260 })
232 261
diff --git a/shared/extra-utils/videos/video-comments.ts b/shared/extra-utils/videos/video-comments.ts
index 81c48412d..831e5e7d4 100644
--- a/shared/extra-utils/videos/video-comments.ts
+++ b/shared/extra-utils/videos/video-comments.ts
@@ -61,6 +61,12 @@ function addVideoCommentReply (
61 .expect(expectedStatus) 61 .expect(expectedStatus)
62} 62}
63 63
64async function findCommentId (url: string, videoId: number | string, text: string) {
65 const res = await getVideoCommentThreads(url, videoId, 0, 25, '-createdAt')
66
67 return res.body.data.find(c => c.text === text).id as number
68}
69
64function deleteVideoComment ( 70function deleteVideoComment (
65 url: string, 71 url: string,
66 token: string, 72 token: string,
@@ -85,5 +91,6 @@ export {
85 getVideoThreadComments, 91 getVideoThreadComments,
86 addVideoCommentThread, 92 addVideoCommentThread,
87 addVideoCommentReply, 93 addVideoCommentReply,
94 findCommentId,
88 deleteVideoComment 95 deleteVideoComment
89} 96}
diff --git a/shared/extra-utils/videos/videos.ts b/shared/extra-utils/videos/videos.ts
index 0d36a38a2..99e591cb2 100644
--- a/shared/extra-utils/videos/videos.ts
+++ b/shared/extra-utils/videos/videos.ts
@@ -95,6 +95,12 @@ function getVideo (url: string, id: number | string, expectedStatus = 200) {
95 .expect(expectedStatus) 95 .expect(expectedStatus)
96} 96}
97 97
98async function getVideoIdFromUUID (url: string, uuid: string) {
99 const res = await getVideo(url, uuid)
100
101 return res.body.id
102}
103
98function getVideoFileMetadataUrl (url: string) { 104function getVideoFileMetadataUrl (url: string) {
99 return request(url) 105 return request(url)
100 .get('/') 106 .get('/')
@@ -669,5 +675,6 @@ export {
669 checkVideoFilesWereRemoved, 675 checkVideoFilesWereRemoved,
670 getPlaylistVideos, 676 getPlaylistVideos,
671 uploadVideoAndGetId, 677 uploadVideoAndGetId,
672 getLocalIdByUUID 678 getLocalIdByUUID,
679 getVideoIdFromUUID
673} 680}