From f1ad18d92d68151a99a4dc542a3a13e6d3fc8069 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Fri, 27 Mar 2026 15:32:15 +0100 Subject: [PATCH] fix: synthesize timeline content for likes/bookmarks/reposts Interaction types (likes, bookmarks, reposts) have no body content in their JF2 properties. The timeline entry was created with empty content, showing blank posts in Phanpy/Moshidon. Now synthesizes display content (e.g. "Liked: https://...") matching backfill-timeline.js behavior. --- lib/syndicator.js | 54 ++++++++++++++++++++++++++++++++++++++++------- package.json | 2 +- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/lib/syndicator.js b/lib/syndicator.js index 4c7c960..8bf29c3 100644 --- a/lib/syndicator.js +++ b/lib/syndicator.js @@ -224,7 +224,7 @@ export function createSyndicator(plugin) { // timelines (Phanpy/Moshidon). Uses $setOnInsert — idempotent. try { const profile = await plugin._collections.ap_profile?.findOne({}); - const content = normalizeContent(properties.content); + const content = buildTimelineContent(properties); const timelineItem = { uid: properties.url, url: properties.url, @@ -289,13 +289,51 @@ export function createSyndicator(plugin) { // ─── Timeline helpers ─────────────────────────────────────────────────────── -function normalizeContent(content) { - if (!content) return { text: "", html: "" }; - if (typeof content === "string") return { text: content, html: `

${content}

` }; - return { - text: content.text || content.value || "", - html: content.html || content.text || content.value || "", - }; +/** + * Build content from JF2 properties. Synthesizes content for interaction + * types (likes, bookmarks, reposts) that have no body text. + */ +function buildTimelineContent(properties) { + const content = properties.content; + if (content) { + if (typeof content === "string") return { text: content, html: `

${content}

` }; + if (content.text || content.html) { + return { + text: content.text || content.value || "", + html: content.html || content.text || content.value || "", + }; + } + } + + // Synthesize for interaction types + const esc = (s) => s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); + const likeOf = properties["like-of"]; + if (likeOf) { + return { + text: `Liked: ${likeOf}`, + html: `

Liked: ${esc(likeOf)}

`, + }; + } + const bookmarkOf = properties["bookmark-of"]; + if (bookmarkOf) { + const label = properties.name || bookmarkOf; + return { + text: `Bookmarked: ${label}`, + html: `

Bookmarked: ${esc(label)}

`, + }; + } + const repostOf = properties["repost-of"]; + if (repostOf) { + const label = properties.name || repostOf; + return { + text: `Reposted: ${label}`, + html: `

Reposted: ${esc(label)}

`, + }; + } + if (properties.name) { + return { text: properties.name, html: `

${esc(properties.name)}

` }; + } + return { text: "", html: "" }; } function mapPostType(postType) { diff --git a/package.json b/package.json index 3db4f2a..f08d620 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rmdes/indiekit-endpoint-activitypub", - "version": "3.10.1", + "version": "3.10.2", "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.", "keywords": [ "indiekit",