fix(activitypub) delete request - Added broadcastDelete to mastodonPluginOptions
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m13s
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m13s
This commit is contained in:
158
scripts/patch-ap-mastodon-delete-fix.mjs
Normal file
158
scripts/patch-ap-mastodon-delete-fix.mjs
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* Patch: fix DELETE /api/v1/statuses/:id — two bugs.
|
||||
*
|
||||
* Bug 1 (ReferenceError — primary failure):
|
||||
* Line: await collections.ap_timeline.deleteOne({ _id: objectId });
|
||||
* `objectId` is never defined in the route handler. MongoDB ObjectId is
|
||||
* imported as the class `ObjectId`, not an instance. Every delete request
|
||||
* throws ReferenceError → 500 → the timeline entry is never removed.
|
||||
* Fix: use `item._id` (the document's own _id from findTimelineItemById).
|
||||
*
|
||||
* Bug 2 (AP Delete not broadcast):
|
||||
* The route calls postContent.delete() directly, bypassing the Indiekit
|
||||
* framework that normally invokes syndicator.delete(). No Delete(Note)
|
||||
* activity is ever sent to followers — they keep seeing the post.
|
||||
* Fix:
|
||||
* a) Add broadcastDelete: (url) => pluginRef.broadcastDelete(url) to
|
||||
* mastodonPluginOptions in index.js so the router can reach it.
|
||||
* b) Call req.app.locals.mastodonPluginOptions.broadcastDelete(postUrl)
|
||||
* in the delete route after the timeline entry is removed.
|
||||
*/
|
||||
|
||||
import { access, readFile, writeFile } from "node:fs/promises";
|
||||
|
||||
const MARKER = "// [patch] ap-mastodon-delete-fix";
|
||||
|
||||
const indexCandidates = [
|
||||
"node_modules/@rmdes/indiekit-endpoint-activitypub/index.js",
|
||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/index.js",
|
||||
];
|
||||
|
||||
const statusesCandidates = [
|
||||
"node_modules/@rmdes/indiekit-endpoint-activitypub/lib/mastodon/routes/statuses.js",
|
||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/mastodon/routes/statuses.js",
|
||||
];
|
||||
|
||||
// ── Change A: expose broadcastDelete in mastodonPluginOptions (index.js) ──────
|
||||
|
||||
const OLD_PLUGIN_OPTS = ` loadRsaKey: () => pluginRef._loadRsaPrivateKey(),
|
||||
broadcastActorUpdate: () => pluginRef.broadcastActorUpdate(),`;
|
||||
|
||||
const NEW_PLUGIN_OPTS = ` loadRsaKey: () => pluginRef._loadRsaPrivateKey(),
|
||||
broadcastActorUpdate: () => pluginRef.broadcastActorUpdate(),
|
||||
broadcastDelete: (url) => pluginRef.broadcastDelete(url), ${MARKER}`;
|
||||
|
||||
// ── Change B: fix objectId → item._id (statuses.js) ──────────────────────────
|
||||
|
||||
const OLD_DELETE_ONE = ` // Delete from timeline
|
||||
await collections.ap_timeline.deleteOne({ _id: objectId });`;
|
||||
|
||||
const NEW_DELETE_ONE = ` // Delete from timeline
|
||||
await collections.ap_timeline.deleteOne({ _id: item._id }); ${MARKER}`;
|
||||
|
||||
// ── Change C: call broadcastDelete after timeline removal (statuses.js) ───────
|
||||
|
||||
const OLD_AFTER_DELETE = ` // Delete from timeline
|
||||
await collections.ap_timeline.deleteOne({ _id: item._id }); ${MARKER}
|
||||
|
||||
// Clean up interactions`;
|
||||
|
||||
const NEW_AFTER_DELETE = ` // Delete from timeline
|
||||
await collections.ap_timeline.deleteOne({ _id: item._id }); ${MARKER}
|
||||
|
||||
// Broadcast AP Delete activity to followers ${MARKER}
|
||||
const _pluginOpts = req.app.locals.mastodonPluginOptions || {};
|
||||
if (_pluginOpts.broadcastDelete && postUrl) {
|
||||
_pluginOpts.broadcastDelete(postUrl).catch((err) =>
|
||||
console.warn(\`[Mastodon API] broadcastDelete failed for \${postUrl}: \${err.message}\`),
|
||||
);
|
||||
}
|
||||
|
||||
// Clean up interactions`;
|
||||
|
||||
async function exists(p) {
|
||||
try { await access(p); return true; } catch { return false; }
|
||||
}
|
||||
|
||||
async function patchFile(filePath, replacements) {
|
||||
const source = await readFile(filePath, "utf8");
|
||||
if (source.includes(MARKER)) {
|
||||
console.log(`[postinstall] patch-ap-mastodon-delete-fix: already applied to ${filePath}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
let updated = source;
|
||||
let applied = 0;
|
||||
|
||||
for (const { old: oldSnippet, newSnippet, label } of replacements) {
|
||||
if (!updated.includes(oldSnippet)) {
|
||||
console.warn(`[postinstall] patch-ap-mastodon-delete-fix: snippet "${label}" not found in ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
updated = updated.replace(oldSnippet, newSnippet);
|
||||
applied++;
|
||||
}
|
||||
|
||||
if (applied === 0) return false;
|
||||
|
||||
await writeFile(filePath, updated, "utf8");
|
||||
console.log(`[postinstall] Applied patch-ap-mastodon-delete-fix to ${filePath} (${applied} change(s))`);
|
||||
return true;
|
||||
}
|
||||
|
||||
let totalPatched = 0;
|
||||
let totalChecked = 0;
|
||||
|
||||
// Patch index.js candidates (Change A)
|
||||
for (const filePath of indexCandidates) {
|
||||
if (!(await exists(filePath))) continue;
|
||||
totalChecked++;
|
||||
const ok = await patchFile(filePath, [
|
||||
{ old: OLD_PLUGIN_OPTS, newSnippet: NEW_PLUGIN_OPTS, label: "broadcastDelete in pluginOptions" },
|
||||
]);
|
||||
if (ok) totalPatched++;
|
||||
}
|
||||
|
||||
// Patch statuses.js candidates (Changes B + C together, in sequence)
|
||||
for (const filePath of statusesCandidates) {
|
||||
if (!(await exists(filePath))) continue;
|
||||
totalChecked++;
|
||||
|
||||
const source = await readFile(filePath, "utf8");
|
||||
if (source.includes(MARKER)) {
|
||||
console.log(`[postinstall] patch-ap-mastodon-delete-fix: already applied to ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply B first, then C (C depends on B's output)
|
||||
let updated = source;
|
||||
let applied = 0;
|
||||
|
||||
if (!updated.includes(OLD_DELETE_ONE)) {
|
||||
console.warn(`[postinstall] patch-ap-mastodon-delete-fix: "objectId fix" snippet not found in ${filePath}`);
|
||||
} else {
|
||||
updated = updated.replace(OLD_DELETE_ONE, NEW_DELETE_ONE);
|
||||
applied++;
|
||||
}
|
||||
|
||||
if (!updated.includes(OLD_AFTER_DELETE)) {
|
||||
console.warn(`[postinstall] patch-ap-mastodon-delete-fix: "broadcastDelete call" snippet not found in ${filePath}`);
|
||||
} else {
|
||||
updated = updated.replace(OLD_AFTER_DELETE, NEW_AFTER_DELETE);
|
||||
applied++;
|
||||
}
|
||||
|
||||
if (applied === 0) continue;
|
||||
|
||||
await writeFile(filePath, updated, "utf8");
|
||||
console.log(`[postinstall] Applied patch-ap-mastodon-delete-fix to ${filePath} (${applied}/2 change(s))`);
|
||||
totalPatched++;
|
||||
}
|
||||
|
||||
if (totalChecked === 0) {
|
||||
console.log("[postinstall] patch-ap-mastodon-delete-fix: no target files found");
|
||||
} else if (totalPatched === 0) {
|
||||
console.log("[postinstall] patch-ap-mastodon-delete-fix: already up to date");
|
||||
} else {
|
||||
console.log(`[postinstall] patch-ap-mastodon-delete-fix: patched ${totalPatched} file(s)`);
|
||||
}
|
||||
Reference in New Issue
Block a user