P7: feeds read as accounts, and PieFed's post Move
Build / Build (push) Successful in 7m50s

Lemmy 1.0's multi-communities and PieFed's feeds (`type: Feed`) resolve as accounts: the feed's `following` is kept
once a day with its counts, each community in it is read in turn, and `/api/v1/accounts/:id/following` lists them.
Lemmy's feed carries the instance's key and its text in `description`; both are taken as they are. Following a feed
answers 422 until the owner decides how a feed is followed.

A post moved to another community (PieFed's Move{object: post, origin, target}), relayed by the community it leaves,
goes there with its thread; a move into a community we host is not taken.

Checked in the pasture: a Lemmy 1.0 feed and a PieFed feed list their communities (ours among them); a PieFed post moved
from its web code arrives moved, with its comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-07 21:42:54 +02:00
1 parent 62189bf11d
commit 3b3f1f6b93
10 files changed
+226 -25

No files matched your search

@@ -59,7 +59,7 @@ namespace PrivaPub.Federation.Inbox.Handlers
public async Task Handle(JsonNode activity, ForeignAvatar actor, CancellationToken token)
{
var inner = activity["object"];
if (inner is JsonObject && Value(inner, "type") is "Create" or "Update" or "Delete" or "Like" or "Dislike" or "Undo" or "Add" or "Remove" or "Block" or "Lock")
if (inner is JsonObject && Value(inner, "type") is "Create" or "Update" or "Delete" or "Like" or "Dislike" or "Undo" or "Add" or "Remove" or "Block" or "Lock" or "Move")
{
await GroupActivity(inner, actor, token);
return;
@@ -242,6 +242,9 @@ namespace PrivaPub.Federation.Inbox.Handlers
case "Lock" when objectUri != default:
await Lock(objectUri, group, true, token);
break;
case "Move" when objectUri != default && Id(inner["origin"]) == group.ActorURI:
await Move(objectUri, Id(inner["target"]), group, token);
break;
case "Undo" when Value(inner["object"], "type") == "Lock":
await Lock(Id(inner["object"]?["object"]), group, false, token);
break;
@@ -305,6 +308,42 @@ namespace PrivaPub.Federation.Inbox.Handlers
Arrival.Accept(locked ? "locked" : "unlocked");
}
// A post moved to another community (PieFed's Move{object: post, origin, target}), relayed by the community it leaves,
// which vouches for what is done to its own posts as for a lock; its thread goes with it. A move into a community we
// host is not taken: it would be ours to announce.
async Task Move(string objectUri, string targetUri, ForeignAvatar group, CancellationToken token)
{
var post = await _dbEntities.Posts.Match(p => p.ObjectURI == objectUri && p.AudienceURI == group.ActorURI && !p.DeletedAt.HasValue)
.ExecuteFirstAsync(token);
if (post == default)
{
Arrival.Drop("unknown-object");
return;
}
Arrival.About(visibility: post.Visibility, created: post.CreationDate);
var target = targetUri == default || targetUri == group.ActorURI || await _localActors.FindByUri(targetUri, token) != default
? default
: await _remoteActors.GetActor(targetUri, refresh: false, token);
if (target is not { AvatarType: AvatarType.Group })
{
Arrival.Drop("unknown-target");
return;
}
var thread = new List<string> { post.ID };
var level = thread.ToList();
while (level.Count > 0 && thread.Count < MaxMoved)
{
var parents = level;
level = (await _dbEntities.Posts.Match(p => parents.Contains(p.AnsweringToPostId) && p.AudienceURI == group.ActorURI)
.Project(p => new PostEntity { ID = p.ID }).ExecuteAsync(token)).Select(p => p.ID).ToList();
thread.AddRange(level);
}
await DB.Default.Update<PostEntity>().Match(p => thread.Contains(p.ID)).Modify(p => p.AudienceURI, target.ActorURI).ExecuteAsync(token);
Arrival.Accept("moved");
}
const int MaxMoved = 1000;
// A community bans one of our personas (Lemmy's Block with the community as its target): kept as the community
// blocking the persona, so its relationship says blocked_by and nothing of the persona's is posted there until the
// Undo. A ban of someone from another server is no business of ours.