Changeset ca25f360 in Klonkt


Ignore:
Timestamp:
06/25/2026 05:37:12 AM (3 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
2923a95
Parents:
4c9c60b
Message:

feat(fediverse): federate post edits as Update (Mastodon refreshes cached copy)

Editing an already-published, federated post now sends Update(Note) to followers
(with an 'updated' timestamp), so Mastodon refreshes the cached post instead of
keeping the stale original — e.g. fixes a post that federated before content
handling existed. New-publish still sends Create; fan_only never federates.

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

Location:
src
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/routes/posts.js

    r4c9c60b rca25f360  
    354354  } catch (e) { /* FTS issues non-fatal */ }
    355355
    356   // ActivityPub: federate when a post BECOMES published (draft/scheduled → published
    357   // via the editor). A brand-new published post is handled in the create route.
    358   if (finalStatus === 'published' && post.status !== 'published' && !fanOnly) {
    359     ActivityPubService.deliverCreate(site, {
     356  // ActivityPub: federate edits to followers. A post that BECOMES published →
     357  // Create (new post); an already-published post that's edited → Update (so
     358  // Mastodon refreshes its cached copy). fan_only posts don't federate.
     359  if (finalStatus === 'published' && !fanOnly) {
     360    const apPost = {
    360361      id: post.id, slug: finalSlug, title: title || finalSlug,
    361362      content: cleanContent, cover_image_url: cover_image_url || null,
    362363      published_at: publishedAt, created_at: post.created_at,
    363     }).catch(() => { /* best-effort */ });
     364    };
     365    if (post.status !== 'published') ActivityPubService.deliverCreate(site, apPost).catch(() => { /* best-effort */ });
     366    else ActivityPubService.deliverUpdate(site, apPost).catch(() => { /* best-effort */ });
    364367  }
    365368
  • src/services/ActivityPubService.js

    r4c9c60b rca25f360  
    598598  };
    599599  for (const inbox of inboxes) deliverWithRetry(site.slug, inbox, del, `${me}#main-key`, keys.private_pem);
     600}
     601
     602// Tell followers an already-published post changed (Update + edited Note) so
     603// Mastodon refreshes the cached copy (e.g. after fixing content).
     604export async function deliverUpdate(site, post) {
     605  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
     606  if (!base || !site || !site.slug || !post || !post.id) return;
     607  const followers = fStmts().list.all(site.slug);
     608  if (!followers.length) return;
     609  const inboxes = [...new Set(followers.map((f) => f.shared_inbox || f.inbox).filter(Boolean))];
     610  const keys = getOrCreateKeys(site.slug);
     611  const me = actorId(base, site.slug);
     612  const note = buildNote(base, site, post);
     613  note.updated = new Date().toISOString();
     614  const update = {
     615    '@context': 'https://www.w3.org/ns/activitystreams',
     616    id: `${noteId(base, post.id)}#update-${Date.now()}`,
     617    type: 'Update', actor: me, to: [PUBLIC], cc: [`${me}/followers`],
     618    object: note,
     619  };
     620  for (const inbox of inboxes) deliverWithRetry(site.slug, inbox, update, `${me}#main-key`, keys.private_pem);
    600621}
    601622
     
    930951  getOrCreateKeys, apWants, sendAP, actorId, noteId,
    931952  buildActor, buildNote, buildCreate, buildOutbox, buildFollowers,
    932   followerCount, deliver, fetchActor, verifyRequest, handleInbox, deliverCreate, deliverDelete,
     953  followerCount, deliver, fetchActor, verifyRequest, handleInbox, deliverCreate, deliverDelete, deliverUpdate,
    933954  getInteractions, getInteractionById, buildReplyNote, getOutboxNote, deliverReply, resolveRemoteNote,
    934955  listOutbox, deliverOutboxDelete,
Note: See TracChangeset for help on using the changeset viewer.