A community's vote on our reply counts, followed or not

Lemmy sends a vote to the community alone, which relays it to the post's
server. A persona's reply in a thread of a community nobody here follows
lost its Lemmy upvotes: the relay was dropped as "not followed". Such a
relay is now taken when the vote (or its undo) is on one of our posts in a
thread rooted in that community; from any other unfollowed group it is
still dropped. For that, a post keeps the community its `audience` names
(FEP-1b12) however it arrived, fetched for a thread as well as announced.

Found by the town's village (p191: 1 like counted of 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-05 07:29:12 +02:00
1 parent 8686275b69
commit 7e37f56db6
4 files changed
+62 -2

No files matched your search

@@ -143,7 +143,8 @@ namespace PrivaPub.Federation.Inbox.Handlers
{
Arrival.About(Value(inner, "type"));
if (group.AvatarType != AvatarType.Group
|| !await _dbEntities.Followings.Match(f => f.TargetActorURI == group.ActorURI && f.State == FollowState.Accepted).ExecuteAnyAsync(token))
|| !await _dbEntities.Followings.Match(f => f.TargetActorURI == group.ActorURI && f.State == FollowState.Accepted).ExecuteAnyAsync(token)
&& !await OurPostInItsThread(inner, group, token))
{
Arrival.Drop("not-followed");
return;
@@ -230,6 +231,28 @@ namespace PrivaPub.Federation.Inbox.Handlers
}
}
// A vote (or its undoing) on one of our posts in a thread the community holds: the community relays it to the post's
// server whether or not anyone here follows the community (Lemmy sends a vote to the community alone)
async Task<bool> OurPostInItsThread(JsonNode inner, ForeignAvatar group, CancellationToken token)
{
var target = Value(inner, "type") switch
{
"Like" or "Dislike" => Id(inner["object"]),
"Undo" when (inner["object"] as JsonObject)?["type"]?.GetValue<string>() is "Like" or "Dislike" => Id(inner["object"]?["object"]),
_ => default
};
var post = target == default
? default
: await _dbEntities.Posts.Match(p => p.ObjectURI == target && !p.IsFederatedCopy && !p.DeletedAt.HasValue).ExecuteFirstAsync(token);
for (var depth = 0; post != default && depth < RemotePosts.MaxDepth; depth++)
{
if (post.AudienceURI == group.ActorURI)
return true;
post = string.IsNullOrEmpty(post.AnsweringToPostId) ? default : await _dbEntities.Posts.MatchID(post.AnsweringToPostId).ExecuteFirstAsync(token);
}
return false;
}
// A community's moderators lock one of its posts, or unlock it: no more replies, ours included. The community vouches
// for what is done to its own posts.
async Task Lock(string objectUri, ForeignAvatar group, bool locked, CancellationToken token)