diff --git a/js/webmentions.js b/js/webmentions.js index 3e3bade..89f70c9 100644 --- a/js/webmentions.js +++ b/js/webmentions.js @@ -225,6 +225,25 @@ const wmItems = [...(wmData1.children || []), ...(wmData2.children || [])]; const convItems = [...(convData1.children || []), ...(convData2.children || [])]; + // Collect syndication URLs from owner-enriched replies (is_owner items). + // When the owner replies from the frontend, Micropub creates the post + // and syndicates it (e.g., to Bluesky). The syndicated copy's URL + // (e.g., bsky.app/profile/.../post/...) is stored in the post's + // syndication property. Bridgy/webmention.io picks up that same URL + // and sends it back as a regular webmention. By collecting these + // syndication URLs, we can precisely match and suppress the echo + // without affecting direct platform replies that have no Micropub post. + var ownerReplySyndicationUrls = new Set(); + for (var k = 0; k < convItems.length; k++) { + var c = convItems[k]; + if (c.is_owner && c.syndication) { + var syns = Array.isArray(c.syndication) ? c.syndication : [c.syndication]; + for (var s = 0; s < syns.length; s++) { + if (syns[s]) ownerReplySyndicationUrls.add(syns[s].replace(/\/$/, '').toLowerCase()); + } + } + } + // Build dedup sets from conversations items (richer metadata, take priority) const convUrls = new Set(convItems.map(c => c.url).filter(Boolean)); const seen = new Set(); @@ -257,6 +276,21 @@ const authorUrl = (wm.author && wm.author.url) || wm.url || ''; const action = wm['wm-property'] || 'mention'; if (authorUrl && authorActions.has(authorUrl + '::' + action)) continue; + // Skip webmention.io items that are syndicated echoes of owner replies. + // When the owner replies from the frontend, Micropub creates the post + // and syndicates it (e.g., to Bluesky at bsky.app/profile/.../post/xyz). + // The conversations API returns the reply as an is_owner enriched item + // with syndication: ["https://bsky.app/profile/.../post/xyz"]. + // Bridgy/webmention.io also picks up that exact syndicated URL and + // sends it back as a regular webmention. By matching the webmention's + // source URL against known owner reply syndication URLs, we precisely + // filter the echo without affecting: + // - Direct platform replies (no Micropub post, no syndication URL match) + // - Owner likes/reposts from webmention.io + // - Owner replies on a different thread on the same page + if (wm.url && ownerReplySyndicationUrls.has(wm.url.replace(/\/$/, '').toLowerCase())) { + continue; + } seen.add(key); allChildren.push(wm); }