From 71a8b60d962ae6f33948d4ef09e49c688905dc3d Mon Sep 17 00:00:00 2001 From: Sven Date: Wed, 1 Apr 2026 14:46:56 +0200 Subject: [PATCH] fix(activitypub) delete request - Added broadcastDelete to mastodonPluginOptions --- package.json | 4 +- scripts/patch-ap-mastodon-delete-fix.mjs | 158 +++++++++++++++++++++++ 2 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 scripts/patch-ap-mastodon-delete-fix.mjs diff --git a/package.json b/package.json index 97865740..86645264 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "index.js", "scripts": { "preinstall": "node scripts/setup-gitea-url-rewrite.mjs", - "postinstall": "xattr -w com.apple.fileprovider.ignore#P 1 node_modules 2>/dev/null || true && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-github-contributions-log.mjs && node scripts/patch-store-github-error-message.mjs && node scripts/patch-store-github-update-fallback.mjs && node scripts/patch-store-github-gitea-methods.mjs && node scripts/patch-store-github-content-type.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-syndicate-normalize-syndication-array.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-micropub-session-token.mjs && node scripts/patch-indiekit-endpoint-urls-protocol.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node scripts/patch-bluesky-syndicator-media-type-guard.mjs && node scripts/patch-ap-skip-draft-syndication.mjs && node scripts/patch-ap-og-image.mjs && node scripts/patch-ap-webfinger-before-auth.mjs && node scripts/patch-ap-federation-bridge-base-url.mjs && node scripts/patch-ap-compose-default-checked.mjs && node scripts/patch-ap-mastodon-reply-threading.mjs && node scripts/patch-ap-mastodon-status-id.mjs && node scripts/patch-ap-interactions-send-guard.mjs && node scripts/patch-ap-syndicate-dedup.mjs && node scripts/patch-micropub-gitea-dispatch-conditional.mjs", - "serve": "export NODE_ENV=${NODE_ENV:-production} INDIEKIT_DEBUG=${INDIEKIT_DEBUG:-0} && node scripts/preflight-production-security.mjs && node scripts/preflight-mongo-connection.mjs && node scripts/preflight-activitypub-rsa-key.mjs && node scripts/preflight-activitypub-profile-urls.mjs && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-github-contributions-log.mjs && node scripts/patch-store-github-error-message.mjs && node scripts/patch-store-github-update-fallback.mjs && node scripts/patch-store-github-gitea-methods.mjs && node scripts/patch-store-github-content-type.mjs && node scripts/patch-microsub-reader-ap-dispatch.mjs && node scripts/patch-microsub-compose-draft-guard.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-syndicate-normalize-syndication-array.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-micropub-session-token.mjs && node scripts/patch-indiekit-endpoint-urls-protocol.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node scripts/patch-bluesky-syndicator-media-type-guard.mjs && node scripts/patch-ap-skip-draft-syndication.mjs && node scripts/patch-ap-og-image.mjs && node scripts/patch-ap-webfinger-before-auth.mjs && node scripts/patch-ap-federation-bridge-base-url.mjs && node scripts/patch-ap-compose-default-checked.mjs && node scripts/patch-ap-mastodon-reply-threading.mjs && node scripts/patch-ap-mastodon-status-id.mjs && node scripts/patch-ap-interactions-send-guard.mjs && node scripts/patch-ap-syndicate-dedup.mjs && node --require ./metrics-shim.cjs node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs", + "postinstall": "xattr -w com.apple.fileprovider.ignore#P 1 node_modules 2>/dev/null || true && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-github-contributions-log.mjs && node scripts/patch-store-github-error-message.mjs && node scripts/patch-store-github-update-fallback.mjs && node scripts/patch-store-github-gitea-methods.mjs && node scripts/patch-store-github-content-type.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-syndicate-normalize-syndication-array.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-micropub-session-token.mjs && node scripts/patch-indiekit-endpoint-urls-protocol.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node scripts/patch-bluesky-syndicator-media-type-guard.mjs && node scripts/patch-ap-skip-draft-syndication.mjs && node scripts/patch-ap-og-image.mjs && node scripts/patch-ap-webfinger-before-auth.mjs && node scripts/patch-ap-federation-bridge-base-url.mjs && node scripts/patch-ap-compose-default-checked.mjs && node scripts/patch-ap-mastodon-reply-threading.mjs && node scripts/patch-ap-mastodon-status-id.mjs && node scripts/patch-ap-interactions-send-guard.mjs && node scripts/patch-ap-syndicate-dedup.mjs && node scripts/patch-ap-mastodon-delete-fix.mjs && node scripts/patch-micropub-gitea-dispatch-conditional.mjs", + "serve": "export NODE_ENV=${NODE_ENV:-production} INDIEKIT_DEBUG=${INDIEKIT_DEBUG:-0} && node scripts/preflight-production-security.mjs && node scripts/preflight-mongo-connection.mjs && node scripts/preflight-activitypub-rsa-key.mjs && node scripts/preflight-activitypub-profile-urls.mjs && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-github-contributions-log.mjs && node scripts/patch-store-github-error-message.mjs && node scripts/patch-store-github-update-fallback.mjs && node scripts/patch-store-github-gitea-methods.mjs && node scripts/patch-store-github-content-type.mjs && node scripts/patch-microsub-reader-ap-dispatch.mjs && node scripts/patch-microsub-compose-draft-guard.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-syndicate-normalize-syndication-array.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-micropub-session-token.mjs && node scripts/patch-indiekit-endpoint-urls-protocol.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node scripts/patch-bluesky-syndicator-media-type-guard.mjs && node scripts/patch-ap-skip-draft-syndication.mjs && node scripts/patch-ap-og-image.mjs && node scripts/patch-ap-webfinger-before-auth.mjs && node scripts/patch-ap-federation-bridge-base-url.mjs && node scripts/patch-ap-compose-default-checked.mjs && node scripts/patch-ap-mastodon-reply-threading.mjs && node scripts/patch-ap-mastodon-status-id.mjs && node scripts/patch-ap-interactions-send-guard.mjs && node scripts/patch-ap-syndicate-dedup.mjs && node scripts/patch-ap-mastodon-delete-fix.mjs && node --require ./metrics-shim.cjs node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], diff --git a/scripts/patch-ap-mastodon-delete-fix.mjs b/scripts/patch-ap-mastodon-delete-fix.mjs new file mode 100644 index 00000000..b51ef1ea --- /dev/null +++ b/scripts/patch-ap-mastodon-delete-fix.mjs @@ -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)`); +}