Changeset eb852c5 in Klonkt


Ignore:
Timestamp:
06/24/2026 11:22:49 AM (3 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
c16e0a5
Parents:
5a93ac0
Message:

feat(activitypub): federate post deletions (Delete + Tombstone)

Deleting a published, non-fan-only post now sends a Delete activity (Tombstone)
to followers' inboxes, so it's removed from their Mastodon feeds instead of being
orphaned.

Co-Authored-By: Claude <noreply@…>

Location:
src
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/routes/posts.js

    r5a93ac0 reb852c5  
    369369  if (!PermissionsService.canDeletePost(req.session.user, post, site)) {
    370370    return res.status(403).send('No permission');
     371  }
     372
     373  // ActivityPub: tell followers the post is gone (Delete + Tombstone), but only
     374  // if it was actually federated (published + not fan-only). Fire before the row
     375  // is removed — we still have post.id (= the Note id).
     376  if (post.status === 'published' && !post.fan_only) {
     377    ActivityPubService.deliverDelete(site, post).catch(() => { /* best-effort */ });
    371378  }
    372379
  • src/services/ActivityPubService.js

    r5a93ac0 reb852c5  
    288288}
    289289
     290// Tell followers a post is gone (Delete + Tombstone) so it's removed from their feeds.
     291export async function deliverDelete(site, post) {
     292  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
     293  if (!base || !site || !site.slug || !post || !post.id) return;
     294  const followers = fStmts().list.all(site.slug);
     295  if (!followers.length) return;
     296  const inboxes = [...new Set(followers.map((f) => f.shared_inbox || f.inbox).filter(Boolean))];
     297  const keys = getOrCreateKeys(site.slug);
     298  const me = actorId(base, site.slug);
     299  const nid = noteId(base, post.id);
     300  const del = {
     301    '@context': 'https://www.w3.org/ns/activitystreams',
     302    id: `${nid}#delete-${Date.now()}`,
     303    type: 'Delete',
     304    actor: me,
     305    to: [PUBLIC],
     306    object: { id: nid, type: 'Tombstone' },
     307  };
     308  for (const inbox of inboxes) deliver(inbox, del, `${me}#main-key`, keys.private_pem).catch(() => { /* best-effort */ });
     309}
     310
    290311export default {
    291312  getOrCreateKeys, apWants, sendAP, actorId, noteId,
    292313  buildActor, buildNote, buildCreate, buildOutbox, buildFollowers,
    293   followerCount, deliver, fetchActor, verifyRequest, handleInbox, deliverCreate,
     314  followerCount, deliver, fetchActor, verifyRequest, handleInbox, deliverCreate, deliverDelete,
    294315};
Note: See TracChangeset for help on using the changeset viewer.