From 1033e8e57f76ae51142eb4142b3c8e4178bbd3a1 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sat, 2 Apr 2022 08:16:35 +0200
Subject: [PATCH] fix(federation): avoid duplicate activity delivery (#8429)

* prefer shared inbox over individual inbox

* no new shared inbox for direct recipes

* fix type error
---
 .../src/remote/activitypub/deliver-manager.ts | 63 +++++++++++--------
 1 file changed, 36 insertions(+), 27 deletions(-)

diff --git a/packages/backend/src/remote/activitypub/deliver-manager.ts b/packages/backend/src/remote/activitypub/deliver-manager.ts
index 9f21dc4cc6..c63437116b 100644
--- a/packages/backend/src/remote/activitypub/deliver-manager.ts
+++ b/packages/backend/src/remote/activitypub/deliver-manager.ts
@@ -79,37 +79,46 @@ export default class DeliverManager {
 
 		const inboxes = new Set<string>();
 
-		// build inbox list
-		for (const recipe of this.recipes) {
-			if (isFollowers(recipe)) {
-				// followers deliver
-				// TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう
-				// ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう?
-				const followers = await Followings.find({
-					where: {
-						followeeId: this.actor.id,
-						followerHost: Not(IsNull()),
-					},
-					select: {
-						followerSharedInbox: true,
-						followerInbox: true,
-					},
-				}) as {
-					followerSharedInbox: string | null;
-					followerInbox: string;
-				}[];
+		/*
+		build inbox list
 
-				for (const following of followers) {
-					const inbox = following.followerSharedInbox || following.followerInbox;
-					inboxes.add(inbox);
-				}
-			} else if (isDirect(recipe)) {
-				// direct deliver
-				const inbox = recipe.to.inbox;
-				if (inbox) inboxes.add(inbox);
+		Process follower recipes first to avoid duplication when processing
+		direct recipes later.
+		*/
+		if (this.recipes.some(r => isFollowers(r)) {
+			// followers deliver
+			// TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう
+			// ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう?
+			const followers = await Followings.find({
+				where: {
+					followeeId: this.actor.id,
+					followerHost: Not(IsNull()),
+				},
+				select: {
+					followerSharedInbox: true,
+					followerInbox: true,
+				},
+			}) as {
+				followerSharedInbox: string | null;
+				followerInbox: string;
+			}[];
+
+			for (const following of followers) {
+				const inbox = following.followerSharedInbox || following.followerInbox;
+				inboxes.add(inbox);
 			}
 		}
 
+		this.recipes.filter((recipe): recipe is IDirectRecipe => {
+			// followers recipes have already been processed
+			isDirect(recipe)
+			// check that shared inbox has not been added yet
+			&& !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox))
+			// check that they actually have an inbox
+			&& recipe.to.inbox
+		})
+		.forEach(recipe => inboxes.add(recipe.to.inbox));
+
 		// deliver
 		for (const inbox of inboxes) {
 			deliver(this.actor, this.activity, inbox);