fix(activitypub) dedup: query ap_activities for an existing outbound Create/Announce/Update
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m18s
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m18s
This commit is contained in:
95
scripts/patch-ap-syndicate-dedup.mjs
Normal file
95
scripts/patch-ap-syndicate-dedup.mjs
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Patch: dedup guard in AP syndicator.syndicate() to prevent double-posting.
|
||||
*
|
||||
* Root cause:
|
||||
* The build CI calls POST /syndicate?source_url=X (force=true) after every
|
||||
* Eleventy build. When syndicateToTargets() records the first syndication, it
|
||||
* issues a Micropub update to save the syndication URL — which commits a new
|
||||
* file to Gitea, triggering another build. That second build's CI call also
|
||||
* hits the syndicate endpoint with force=true.
|
||||
*
|
||||
* In force mode with no mp-syndicate-to, syndicateToTargets() re-selects
|
||||
* targets whose origin matches any existing syndication URL. Since the AP
|
||||
* syndicator's UID (publicationUrl, e.g. "https://blog.giersig.eu/") and the
|
||||
* first syndication return value (properties.url, e.g.
|
||||
* "https://blog.giersig.eu/notes/my-post/") share the same origin,
|
||||
* the AP syndicator is matched and called a second time → duplicate Create(Note).
|
||||
*
|
||||
* Fix:
|
||||
* At the start of syndicate(), query ap_activities for an existing outbound
|
||||
* Create/Announce/Update for properties.url. If found, log and return the
|
||||
* existing URL without re-federating.
|
||||
*
|
||||
* This is self-contained (no CI or force-mode changes needed) and correct
|
||||
* regardless of how syndication is triggered.
|
||||
*/
|
||||
|
||||
import { access, readFile, writeFile } from "node:fs/promises";
|
||||
|
||||
const MARKER = "// [patch] ap-syndicate-dedup";
|
||||
|
||||
const candidates = [
|
||||
"node_modules/@rmdes/indiekit-endpoint-activitypub/lib/syndicator.js",
|
||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/syndicator.js",
|
||||
];
|
||||
|
||||
const OLD = ` try {
|
||||
const actorUrl = plugin._getActorUrl();`;
|
||||
|
||||
const NEW = ` // Dedup: skip re-federation if we've already sent an activity for this URL. ${MARKER}
|
||||
// ap_activities is the authoritative record of "already federated".
|
||||
try {
|
||||
const existingActivity = await plugin._collections.ap_activities?.findOne({
|
||||
direction: "outbound",
|
||||
type: { $in: ["Create", "Announce", "Update"] },
|
||||
objectUrl: properties.url,
|
||||
});
|
||||
if (existingActivity) {
|
||||
console.info(\`[ActivityPub] Skipping duplicate syndication for \${properties.url} — already sent (\${existingActivity.type})\`);
|
||||
return properties.url || undefined;
|
||||
}
|
||||
} catch { /* DB unavailable — proceed */ }
|
||||
|
||||
try {
|
||||
const actorUrl = plugin._getActorUrl();`;
|
||||
|
||||
async function exists(filePath) {
|
||||
try {
|
||||
await access(filePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let checked = 0;
|
||||
let patched = 0;
|
||||
|
||||
for (const filePath of candidates) {
|
||||
if (!(await exists(filePath))) continue;
|
||||
checked += 1;
|
||||
|
||||
const source = await readFile(filePath, "utf8");
|
||||
|
||||
if (source.includes(MARKER)) {
|
||||
console.log(`[postinstall] patch-ap-syndicate-dedup: already applied to ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!source.includes(OLD)) {
|
||||
console.warn(`[postinstall] patch-ap-syndicate-dedup: snippet not found in ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await writeFile(filePath, source.replace(OLD, NEW), "utf8");
|
||||
patched += 1;
|
||||
console.log(`[postinstall] Applied patch-ap-syndicate-dedup to ${filePath}`);
|
||||
}
|
||||
|
||||
if (checked === 0) {
|
||||
console.log("[postinstall] patch-ap-syndicate-dedup: no target files found");
|
||||
} else if (patched === 0) {
|
||||
console.log("[postinstall] patch-ap-syndicate-dedup: already up to date");
|
||||
} else {
|
||||
console.log(`[postinstall] patch-ap-syndicate-dedup: patched ${patched} file(s)`);
|
||||
}
|
||||
Reference in New Issue
Block a user