fix: deduplicate syndicated owner reply echoes from webmention.io

When the owner replies from the frontend, Micropub creates the post and
syndicates it (e.g., to Bluesky). Bridgy picks up the syndicated copy
and sends it back as a webmention, creating a duplicate — the reply
appears both as a threaded owner reply and as a standalone root card.

Fix: collect syndication URLs from is_owner enriched items returned by
the conversations API, then skip any webmention.io item whose URL
exactly matches. This precisely filters only Bridgy echoes without
affecting direct platform replies (no Micropub post = no syndication
URL = no match = kept).
This commit is contained in:
Ricardo
2026-04-05 19:46:28 +02:00
parent 49d8083025
commit b80d43facb
+34
View File
@@ -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);
}