Changeset 55bba23 in Klonkt
- Timestamp:
- 06/25/2026 09:13:53 AM (3 months ago)
- Branches:
- main
- Children:
- 4b5223f
- Parents:
- f9f3312
- Location:
- src
- Files:
-
- 2 edited
-
routes/posts.js (modified) (1 diff)
-
services/ActivityPubService.js (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/posts.js
rf9f3312 r55bba23 367 367 } 368 368 369 // Pin/unpin changed → push an actor Update so Mastodon re-fetches the featured370 // (pinned) collection promptly instead of waiting for its own actor refresh.369 // Pin/unpin/reorder → push Add/Remove activities so followers' instances update the 370 // pinned order immediately (reliable, unlike re-fetching the cached featured collection). 371 371 if ((post.pinned || 0) !== parsePinnedRank(pinned)) { 372 ActivityPubService.deliverActorUpdate(site).catch(() => { /* best-effort */ }); 372 const unpinned = (post.pinned || 0) > 0 && parsePinnedRank(pinned) === 0 ? [post.id] : []; 373 ActivityPubService.resyncFeaturedPins(site, unpinned).catch(() => { /* best-effort */ }); 373 374 } 374 375 -
src/services/ActivityPubService.js
rf9f3312 r55bba23 706 706 }; 707 707 for (const inbox of inboxes) deliverWithRetry(site.slug, inbox, update, `${me}#main-key`, keys.private_pem); 708 } 709 710 // Reliably set the pinned order on followers' instances via Add/Remove activities 711 // (how Mastodon itself federates pins) — pushed to the inbox + processed immediately, 712 // unlike the featured COLLECTION which Mastodon caches with sticky StatusPins. 713 // Mastodon's Add skips an already-pinned status, so we REMOVE every pin first, wait, 714 // then ADD in rank-DESCENDING order (rank 1 added LAST → newest StatusPin → shown first, 715 // because Mastodon displays pins newest-first). `alsoRemove` = ids to unpin too. 716 export async function resyncFeaturedPins(site, alsoRemove = []) { 717 const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, ''); 718 if (!base || !site || !site.slug) return; 719 const followers = fStmts().list.all(site.slug); 720 if (!followers.length) return; 721 const inboxes = [...new Set(followers.map((f) => f.shared_inbox || f.inbox).filter(Boolean))]; 722 const keys = getOrCreateKeys(site.slug); 723 const me = actorId(base, site.slug); 724 const keyId = `${me}#main-key`; 725 const featured = `${me}/featured`; 726 const AS = 'https://www.w3.org/ns/activitystreams'; 727 const note = (id) => noteId(base, id); 728 const pinned = db.prepare( 729 `SELECT id FROM posts WHERE site_id = ? AND status = 'published' AND (fan_only IS NULL OR fan_only = 0) 730 AND pinned IS NOT NULL AND pinned > 0 731 ORDER BY pinned DESC, COALESCE(published_at, created_at) ASC LIMIT 20` 732 ).all(site.id); 733 const removeIds = [...new Set([...pinned.map((p) => p.id), ...alsoRemove])]; 734 // 1. Remove every current pin so Mastodon can recreate them in order. 735 for (const id of removeIds) { 736 const rm = { '@context': AS, id: `${me}#rm-${id}-${Date.now()}`, type: 'Remove', actor: me, object: note(id), target: featured, to: [PUBLIC] }; 737 for (const inbox of inboxes) deliver(inbox, rm, keyId, keys.private_pem).catch(() => { /* best-effort */ }); 738 } 739 if (!pinned.length) { console.log('[AP] unpinned all featured for', site.slug); return; } 740 await new Promise((r) => setTimeout(r, 5000)); // let the Removes land first 741 // 2. Add in rank-DESC order, gaps so each StatusPin gets an increasing created_at. 742 for (const p of pinned) { 743 const add = { '@context': AS, id: `${me}#add-${p.id}-${Date.now()}`, type: 'Add', actor: me, object: note(p.id), target: featured, to: [PUBLIC], cc: [`${me}/followers`] }; 744 for (const inbox of inboxes) deliver(inbox, add, keyId, keys.private_pem).catch(() => { /* best-effort */ }); 745 await new Promise((r) => setTimeout(r, 2000)); 746 } 747 console.log('[AP] resynced', pinned.length, 'featured pins for', site.slug); 708 748 } 709 749 … … 1038 1078 getOrCreateKeys, apWants, sendAP, actorId, noteId, 1039 1079 buildActor, buildNote, buildCreate, buildOutbox, buildFollowers, buildFeatured, 1040 followerCount, deliver, fetchActor, verifyRequest, handleInbox, deliverCreate, deliverDelete, deliverUpdate, deliverActorUpdate, 1080 followerCount, deliver, fetchActor, verifyRequest, handleInbox, deliverCreate, deliverDelete, deliverUpdate, deliverActorUpdate, resyncFeaturedPins, 1041 1081 getInteractions, getInteractionById, buildReplyNote, getOutboxNote, deliverReply, resolveRemoteNote, 1042 1082 listOutbox, deliverOutboxDelete,
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)