Skip to content

Let writers follow fediverse accounts and read their posts - #495

Merged
laffer1 merged 4 commits into
masterfrom
activitypub-following
Nov 2, 2026
Merged

laffer1 merged 4 commits into
masterfrom
activitypub-following

Conversation

@laffer1

@laffer1 laffer1 commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

Writers whose journals federate can now follow accounts on Mastodon and other fediverse servers. Those accounts' public posts appear on the writer's own friends page.

Following

  • POST /api/federation/following takes @user@host, user@host, acct:user@host or an https profile/actor URL.
    • Handles are looked up with WebFinger over https through SafeHttpFetcher, with every redirect checked.
    • The actor document is fetched signed and must stay on the same host, have a matching id, be an actor type and have an inbox.
    • Plain-http addresses, private/special addresses, odd ports and accounts on this site are refused.
  • A follow is stored as PENDING and a signed Follow is queued.
  • Refused: writers who don't federate, accounts already followed, and more than activitypub.following.max-per-user (default 500) follows.
  • Accept/Reject is matched by the Follow's id or by the embedded Follow, and must be signed by the followed actor. A Reject keeps the row as REJECTED so the writer sees "declined".
  • Unfollow (DELETE /api/federation/following?actor=…) queues an Undo with the original Follow embedded, then removes the row.
  • List: GET returns the writer's follows. All three endpoints need a login.
  • Collection: /users/{u}/following now reports the real count of accepted follows. Like followers, it never lists them.

Incoming posts

  • Stored: Create and Update of a Note or Article that is addressed to Public, comes from an account someone here follows (accepted), and is attributed to the sender, with the object id on the sender's host.
  • Ignored: followers-only and direct posts, boosts, and replies to our own entries (Show fediverse replies to entries as comments #494 handles those).
  • Storage: each post is stored once, keyed by object id. Content is cleaned by jsoup down to text, paragraphs, line breaks and http(s) links. Attachments are kept as links only, and no remote images are shown. A content warning shows as a collapsible <details>.
  • Edits and deletes: Update and Delete are accepted only from the post's author. An Update that makes a post non-public removes it.
  • Retention: an hourly job removes posts older than activitypub.remote-posts.keep-days (default 30) and posts by accounts nobody here follows.

Friends page

Only the writer viewing their own friends page sees fediverse posts. They're merged into the friends' entries by date, marked "from the fediverse" with the author's handle, and link to the original. Other visitors see the page as before.

The same page has a small section to follow an account by @name@server, list follows with their state, and unfollow. If the writer hasn't turned federation on, it links to journal preferences instead.

Cleanup

  • When a writer stops federating or deletes their account, their follows are removed.
  • When a remote actor is deleted, follows of it and its posts are removed.
  • Decision to confirm: the actor Delete from Tell fediverse servers when a journal's actor is gone #491 now also goes to the servers of accounts the writer follows, not only to their followers' servers, so those servers drop the follow too.

Testing

  • New test classes, 62 tests: RemoteAccountResolverTests, FollowingServiceTests, RemotePostServiceTests, FollowingControllerTests, RemotePostCleanupTests. Extended: inbox, actor, actor deletion, users controller and template, and account tests.
  • Affected classes: 213 tests, 0 failures. Full unit suite: 1771 tests, 0 failures.
  • Not verified:
    • the V1_31 migration and the subquery JPQL on MySQL 8;
    • real Mastodon or GoToSocial servers;
    • the UI in a browser.
  • Conflicts: Match an ActivityPub Undo that names the Follow by its id #493 and Show fediverse replies to entries as comments #494 also change InboxService, its constructor and InboxControllerTests. This PR leaves replies to our entries to Show fediverse replies to entries as comments #494, so the two orders of dispatch don't clash. Whichever merges later needs a hand merge.

🤖 Generated with Claude Code

Journals could only be followed: the actor's following collection was
always empty and the inbox understood only Follow, Undo and Delete. A
writer whose journal federates can now follow Mastodon and other
fediverse accounts and see their public posts on their own friends page.

- Following: /api/federation/following lists, follows (by @user@host,
  resolved with WebFinger over https, or by profile/actor URL) and
  unfollows. Every fetch goes through SafeHttpFetcher/UrlPolicy; local
  accounts, non-federating writers, duplicates and more than
  activitypub.following.max-per-user (500) follows are refused. A signed
  Follow is queued; unfollowing queues an Undo embedding it.
- Inbox: Accept and Reject are matched by the Follow's id or the embedded
  Follow and must be signed by the followed actor. Reject keeps the row as
  REJECTED so the writer sees it was declined.
- Posts: public Create/Update/Delete of Notes and Articles from accounts
  someone here follows (accepted) are stored once per object id, content
  cleaned with jsoup to text, paragraphs, breaks and nofollow links,
  attachments kept as links. Update/Delete only from the author; replies
  to our entries are left to the comments path. Posts older than
  activitypub.remote-posts.keep-days (30) or whose author nobody follows
  are purged hourly.
- Friends page: the owner, logged in, sees those posts interleaved by
  date, labelled as from the fediverse and linked to the original, plus a
  small section to manage the follows. Other visitors see neither.
- The following collection counts accepted follows, list kept private.
- Cleanup: stopping federation or deleting the account removes the follows
  and sends the actor Delete to the followed accounts' servers too; a
  deleted remote actor loses its follows and posts.

Migration V1_31 adds activitypub_following and activitypub_remote_post.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Nov 2, 2026

Copy link
Copy Markdown

Sorry @laffer1, your pull request is larger than the review limit of 150,000 diff characters

@laffer1

laffer1 commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator Author

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@deepsource-io

deepsource-io Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

DeepSource Code Review

We reviewed changes in e956390...1c431d2 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
PHP Nov 2, 2026 8:39p.m. Review ↗
Java Nov 2, 2026 8:39p.m. Review ↗
Shell Nov 2, 2026 8:39p.m. Review ↗
JavaScript Nov 2, 2026 8:39p.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

laffer1 and others added 3 commits November 2, 2026 14:27
DeepSource reported "incomplete assertion" on
verify(httpSignatureService).verify(...): a complete Mockito check of a
mocked method that is itself named verify. Marked with skipcq rather
than rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DeepSource fails the check on cyclomatic complexity: store was 23 and
getFriends 30. store now delegates the authorship checks to ownPost and
the field copying to save. getFriends moves the fediverse lookup into
fediversePosts and is marked skipcq, as it was over the limit before
this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both branches dispatched Create, Update and Delete to their own service.
Replies to our entries are now tried first; whatever RemoteReplyService
ignores goes on to RemotePostService, which already skips replies to
our entries. HANDLED and RETRY from the replies stop there. A deleted
or gone actor loses its followers, replies and follows alike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@laffer1
laffer1 merged commit 55c2ba5 into master Nov 2, 2026
11 checks passed
@laffer1
laffer1 deleted the activitypub-following branch November 2, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant