diff --git a/scripts/patch-actor-aliases-successor.mjs b/scripts/patch-actor-aliases-successor.mjs
deleted file mode 100644
index 23a283ec..00000000
--- a/scripts/patch-actor-aliases-successor.mjs
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * Patch @rmdes/indiekit-endpoint-activitypub federation-setup.js:
- * - personOptions.alsoKnownAs → personOptions.aliases (Fedify uses 'aliases')
- * - Add movedTo → personOptions.successor (Fedify uses 'successor')
- */
-
-import { access, readFile, writeFile } from "node:fs/promises";
-
-const candidates = [
- "node_modules/@rmdes/indiekit-endpoint-activitypub/lib/federation-setup.js",
- "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/federation-setup.js",
-];
-
-const MARKER = "// patch-actor-aliases-successor: applied";
-
-const OLD = ` if (profile.alsoKnownAs?.length > 0) {
- personOptions.alsoKnownAs = profile.alsoKnownAs.map((u) => new URL(u));
- }`;
-
-const NEW = ` if (profile.alsoKnownAs?.length > 0) {
- personOptions.aliases = profile.alsoKnownAs.map((u) => new URL(u));
- }
- if (profile.movedTo) {
- personOptions.successor = new URL(profile.movedTo);
- }
- ${MARKER}`;
-
-async function exists(p) {
- try { await access(p); return true; } catch { return false; }
-}
-
-let patched = false;
-for (const filePath of candidates) {
- if (!(await exists(filePath))) continue;
- const src = await readFile(filePath, "utf8");
- if (src.includes(MARKER)) {
- console.log(`[postinstall] patch-actor-aliases-successor: already applied in ${filePath}`);
- patched = true;
- break;
- }
- if (!src.includes(OLD)) {
- console.log(`[postinstall] patch-actor-aliases-successor: target not found in ${filePath}`);
- continue;
- }
- await writeFile(filePath, src.replace(OLD, NEW), "utf8");
- console.log(`[postinstall] patch-actor-aliases-successor: applied to ${filePath}`);
- patched = true;
- break;
-}
-
-if (!patched) {
- console.log("[postinstall] patch-actor-aliases-successor: no target file found");
-}
diff --git a/scripts/patch-ap-collection-count-estimate.mjs b/scripts/patch-ap-collection-count-estimate.mjs
deleted file mode 100644
index aeb567af..00000000
--- a/scripts/patch-ap-collection-count-estimate.mjs
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Patch: replace countDocuments() with estimatedDocumentCount() in
- * federation-setup.js followers/following collection dispatchers.
- *
- * All four calls are already wrapped in cachedQuery(), but on cache miss
- * countDocuments() runs a full aggregate scan. estimatedDocumentCount()
- * reads collection metadata — O(1).
- *
- * Covers: ap_followers (×2) and ap_following (×2) in collection pagination
- * and counter dispatchers.
- */
-
-import { access, readFile, writeFile } from "node:fs/promises";
-
-const candidates = [
- "node_modules/@rmdes/indiekit-endpoint-activitypub/lib/federation-setup.js",
- "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/federation-setup.js",
-];
-
-const MARKER = "// [patch] collection-count-estimate";
-
-const replacements = [
- {
- old: ` const t = await collections.ap_followers.countDocuments();
- return [d, t];`,
- new: ` const t = await collections.ap_followers.estimatedDocumentCount(); ${MARKER}
- return [d, t];`,
- },
- {
- old: ` return await collections.ap_followers.countDocuments();`,
- new: ` return await collections.ap_followers.estimatedDocumentCount(); ${MARKER}`,
- },
- {
- old: ` const t = await collections.ap_following.countDocuments();
- return [d, t];`,
- new: ` const t = await collections.ap_following.estimatedDocumentCount(); ${MARKER}
- return [d, t];`,
- },
- {
- old: ` return await collections.ap_following.countDocuments();`,
- new: ` return await collections.ap_following.estimatedDocumentCount(); ${MARKER}`,
- },
-];
-
-async function exists(p) {
- try { await access(p); return true; } catch { return false; }
-}
-
-let patched = false;
-for (const filePath of candidates) {
- if (!(await exists(filePath))) continue;
- let src = await readFile(filePath, "utf8");
- if (src.includes(MARKER)) {
- console.log(`[postinstall] patch-ap-collection-count-estimate: already applied in ${filePath}`);
- patched = true;
- break;
- }
- let changed = 0;
- for (const { old, new: replacement } of replacements) {
- if (src.includes(old)) {
- src = src.replace(old, replacement);
- changed++;
- }
- }
- if (changed === 0) {
- console.log(`[postinstall] patch-ap-collection-count-estimate: target snippets not found in ${filePath}`);
- continue;
- }
- await writeFile(filePath, src, "utf8");
- console.log(`[postinstall] patch-ap-collection-count-estimate: applied ${changed}/4 replacements in ${filePath}`);
- patched = true;
- break;
-}
-
-if (!patched) {
- console.log("[postinstall] patch-ap-collection-count-estimate: no target file found");
-}
diff --git a/scripts/patch-ap-dm-bsky-source-content.mjs b/scripts/patch-ap-dm-bsky-source-content.mjs
deleted file mode 100644
index 129fd74e..00000000
--- a/scripts/patch-ap-dm-bsky-source-content.mjs
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * Patch: extract DM content from object.source.content when object.content is empty.
- *
- * Bluesky DMs bridged via bsky.brid.gy arrive as Create activities wrapping a
- * ChatMessage object. Bridgy Fed sends the message text in `source.content`
- * (plain text, mediaType: text/plain) without a `content` field (HTML).
- *
- * The DM handler only checked `object.content`, so all bsky.brid.gy DMs were
- * stored with empty content, making them unviewable in Mona.
- *
- * Fix: after extracting `rawHtml` from `object.content`, also read
- * `object.source?.content` as a plain-text fallback for `contentText` when
- * `rawHtml` is empty.
- */
-
-import { access, readFile, writeFile } from "node:fs/promises";
-
-const candidates = [
- "node_modules/@rmdes/indiekit-endpoint-activitypub/lib/inbox-handlers.js",
- "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/inbox-handlers.js",
-];
-
-const MARKER = "// [patch] bsky-source-content-fallback";
-
-const OLD = ` const rawHtml = object.content?.toString() || "";
- const contentHtml = sanitizeContent(rawHtml);
- const contentText = rawHtml.replace(/<[^>]*>/g, "").substring(0, 500);`;
-
-const NEW = ` const rawHtml = object.content?.toString() || "";
- const sourceText = object.source?.content?.toString() || ""; // [patch] bsky-source-content-fallback
- const contentHtml = sanitizeContent(rawHtml);
- const contentText = rawHtml
- ? rawHtml.replace(/<[^>]*>/g, "").substring(0, 500)
- : sourceText.substring(0, 500);`;
-
-async function exists(p) {
- try { await access(p); return true; } catch { return false; }
-}
-
-let patched = false;
-for (const filePath of candidates) {
- if (!(await exists(filePath))) continue;
- const src = await readFile(filePath, "utf8");
- if (src.includes(MARKER)) {
- console.log(`[postinstall] patch-ap-dm-bsky-source-content: already applied in ${filePath}`);
- patched = true;
- break;
- }
- if (!src.includes(OLD)) {
- console.log(`[postinstall] patch-ap-dm-bsky-source-content: target snippet not found in ${filePath}`);
- continue;
- }
- await writeFile(filePath, src.replace(OLD, NEW), "utf8");
- console.log(`[postinstall] patch-ap-dm-bsky-source-content: applied to ${filePath}`);
- patched = true;
- break;
-}
-
-if (!patched) {
- console.log("[postinstall] patch-ap-dm-bsky-source-content: no target file found");
-}
diff --git a/scripts/patch-ap-dm-send-serialize-id.mjs b/scripts/patch-ap-dm-send-serialize-id.mjs
deleted file mode 100644
index d195eefd..00000000
--- a/scripts/patch-ap-dm-send-serialize-id.mjs
+++ /dev/null
@@ -1,66 +0,0 @@
-/**
- * Patch: capture addTimelineItem return value for DM serializeStatus.
- *
- * When sending an outbound DM via the Mastodon API, addTimelineItem returns
- * the stored MongoDB document (which has _id). The return value was ignored,
- * so the original timelineItem object had no _id. serializeStatus then crashed
- * on item._id.toString() → "Cannot read properties of undefined (reading 'toString')".
- *
- * Fix: capture the addTimelineItem result and pass it to serializeStatus.
- */
-
-import { access, readFile, writeFile } from "node:fs/promises";
-
-const candidates = [
- "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",
-];
-
-const MARKER = "// [patch] dm-send-serialize-id";
-
-const OLD = ` try {
- await addTimelineItem(collections, timelineItem);
- } catch (storeError) {
- console.warn("[Mastodon API] Failed to store outbound DM in timeline:", storeError.message);
- }
-
- // Return a full serialized status so clients (Phanpy, Elk) can render it
- const status = serializeStatus(timelineItem, {`;
-
-const NEW = ` let storedDmItem = timelineItem; // [patch] dm-send-serialize-id
- try {
- const _dmResult = await addTimelineItem(collections, timelineItem);
- if (_dmResult?._id) storedDmItem = _dmResult;
- } catch (storeError) {
- console.warn("[Mastodon API] Failed to store outbound DM in timeline:", storeError.message);
- }
-
- // Return a full serialized status so clients (Phanpy, Elk) can render it
- const status = serializeStatus(storedDmItem, {`;
-
-async function exists(p) {
- try { await access(p); return true; } catch { return false; }
-}
-
-let patched = false;
-for (const filePath of candidates) {
- if (!(await exists(filePath))) continue;
- const src = await readFile(filePath, "utf8");
- if (src.includes(MARKER)) {
- console.log(`[postinstall] patch-ap-dm-send-serialize-id: already applied in ${filePath}`);
- patched = true;
- break;
- }
- if (!src.includes(OLD)) {
- console.log(`[postinstall] patch-ap-dm-send-serialize-id: target snippet not found in ${filePath}`);
- continue;
- }
- await writeFile(filePath, src.replace(OLD, NEW), "utf8");
- console.log(`[postinstall] patch-ap-dm-send-serialize-id: applied to ${filePath}`);
- patched = true;
- break;
-}
-
-if (!patched) {
- console.log("[postinstall] patch-ap-dm-send-serialize-id: no target file found");
-}
diff --git a/scripts/patch-ap-followers-count-estimate.mjs b/scripts/patch-ap-followers-count-estimate.mjs
deleted file mode 100644
index 3cd1d095..00000000
--- a/scripts/patch-ap-followers-count-estimate.mjs
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Patch: replace ap_followers.countDocuments() with estimatedDocumentCount()
- * in syndicator.js log statement.
- *
- * countDocuments() runs an aggregate({$match:{}, $group:{n:$sum:1}}) scan.
- * For a logging call this is unnecessary — estimatedDocumentCount() reads
- * collection metadata (O(1), no lock contention).
- */
-
-import { access, readFile, writeFile } from "node:fs/promises";
-
-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 MARKER = "// [patch] followers-count-estimate";
-
-const OLD = ` // Count followers for logging
- const followerCount =
- await plugin._collections.ap_followers.countDocuments();`;
-
-const NEW = ` // Count followers for logging
- const followerCount =
- await plugin._collections.ap_followers.estimatedDocumentCount(); ${MARKER}`;
-
-async function exists(p) {
- try { await access(p); return true; } catch { return false; }
-}
-
-let patched = false;
-for (const filePath of candidates) {
- if (!(await exists(filePath))) continue;
- const src = await readFile(filePath, "utf8");
- if (src.includes(MARKER)) {
- console.log(`[postinstall] patch-ap-followers-count-estimate: already applied in ${filePath}`);
- patched = true;
- break;
- }
- if (!src.includes(OLD)) {
- console.log(`[postinstall] patch-ap-followers-count-estimate: target snippet not found in ${filePath}`);
- continue;
- }
- await writeFile(filePath, src.replace(OLD, NEW), "utf8");
- console.log(`[postinstall] patch-ap-followers-count-estimate: applied to ${filePath}`);
- patched = true;
- break;
-}
-
-if (!patched) {
- console.log("[postinstall] patch-ap-followers-count-estimate: no target file found");
-}
diff --git a/scripts/patch-ap-key-freshness-nonblocking.mjs b/scripts/patch-ap-key-freshness-nonblocking.mjs
deleted file mode 100644
index cd78807d..00000000
--- a/scripts/patch-ap-key-freshness-nonblocking.mjs
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * Patch: make touchKeyFreshness() fire-and-forget in inbox-listeners.js.
- *
- * touchKeyFreshness() is non-critical tracking (records lastSeenAt on
- * ap_key_freshness). Under burst AP traffic, 11 parallel awaited upserts
- * compete for MongoDB write locks — profiling shows 8-second delays on
- * this single call while holding up inbox processing.
- *
- * Removing the await means inbox listeners don't block on the tracking
- * write. The .catch(() => {}) mirrors the existing error handling inside
- * the function itself.
- */
-
-import { access, readFile, writeFile } from "node:fs/promises";
-
-const candidates = [
- "node_modules/@rmdes/indiekit-endpoint-activitypub/lib/inbox-listeners.js",
- "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/inbox-listeners.js",
-];
-
-const MARKER = "// [patch] key-freshness-nonblocking";
-
-async function exists(p) {
- try { await access(p); return true; } catch { return false; }
-}
-
-let patched = false;
-for (const filePath of candidates) {
- if (!(await exists(filePath))) continue;
- let src = await readFile(filePath, "utf8");
- if (src.includes(MARKER)) {
- console.log(`[postinstall] patch-ap-key-freshness-nonblocking: already applied in ${filePath}`);
- patched = true;
- break;
- }
- const before = src;
- // Replace all: await touchKeyFreshness(...) → touchKeyFreshness(...).catch(() => {})
- src = src.replace(
- /await touchKeyFreshness\(([^)]+)\);/g,
- `touchKeyFreshness($1).catch(() => {}); ${MARKER}`,
- );
- const count = (src.match(/key-freshness-nonblocking/g) || []).length;
- if (count === 0) {
- console.log(`[postinstall] patch-ap-key-freshness-nonblocking: target not found in ${filePath}`);
- continue;
- }
- await writeFile(filePath, src, "utf8");
- console.log(`[postinstall] patch-ap-key-freshness-nonblocking: applied to ${count} calls in ${filePath}`);
- patched = true;
- break;
-}
-
-if (!patched) {
- console.log("[postinstall] patch-ap-key-freshness-nonblocking: no target file found");
-}
diff --git a/scripts/patch-ap-mastodon-short-post-bypass.mjs b/scripts/patch-ap-mastodon-short-post-bypass.mjs
deleted file mode 100644
index 491be99a..00000000
--- a/scripts/patch-ap-mastodon-short-post-bypass.mjs
+++ /dev/null
@@ -1,157 +0,0 @@
-/**
- * Patch: skip Micropub blog post creation for short standalone Mastodon posts.
- *
- * Very short status texts (≤ 280 chars, no media, no reply) are fediverse-only
- * interactions — they federate via AP but must NOT create a permanent blog entry.
- * Posts with in_reply_to, media, or text > 280 characters go through the full
- * Micropub pipeline as before.
- *
- * Short posts get a canonical URL at /activitypub/objects/note/{uuid} (same as
- * DMs), are stored in ap_timeline, and delivered to followers via Fedify's
- * ctx.sendActivity({ "followers" }) — the same mechanism the AP syndicator uses.
- */
-
-import { access, readFile, writeFile } from "node:fs/promises";
-
-const candidates = [
- "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",
-];
-
-const MARKER = "// [patch] short-post-bypass";
-
-const OLD = ` // Resolve in_reply_to URL from status ID (cursor or ObjectId)
- let inReplyTo = null;
- if (inReplyToId) {`;
-
-const NEW = ` // Short standalone posts (≤ 280 chars, no media, no reply) skip Micropub
- // and go directly to AP — no blog entry is created. // [patch] short-post-bypass
- const _shortPostLimit = 280;
- const _isShortPost =
- !inReplyToId &&
- (!mediaIds || mediaIds.length === 0) &&
- visibility !== "direct" &&
- (statusText || "").trim().length > 0 &&
- (statusText || "").trim().length <= _shortPostLimit;
-
- if (_isShortPost) {
- const _spPluginOptions = req.app.locals.mastodonPluginOptions || {};
- const _spPublicationUrl = (_spPluginOptions.publicationUrl || baseUrl).replace(/\\/+$/, "");
- const _spFederation = _spPluginOptions.federation;
- const _spHandle = _spPluginOptions.handle || "user";
- const _spHostname = (() => { try { return new URL(_spPublicationUrl).hostname; } catch { return ""; } })();
- const _spProfile = await collections.ap_profile.findOne({});
-
- const { randomUUID } = await import("node:crypto");
- const _spUuid = randomUUID();
- const _spNoteId = \`\${_spPublicationUrl}/activitypub/objects/note/\${_spUuid}\`;
- const _spNow = new Date().toISOString();
-
- const _spText = (statusText || "").trim();
- const _spHtml = _spText
- .replace(/&/g, "&")
- .replace(//g, ">")
- .replace(/(https?:\\/\\/[^\\s<>&"')\\]]+)/g, '$1')
- .replace(/\\n/g, "
");
-
- const _spTlItem = await addTimelineItem(collections, {
- uid: _spNoteId,
- url: _spNoteId,
- type: "note",
- content: { text: _spText, html: \`
\${_spHtml}
\` }, - author: { - name: _spProfile?.name || _spHandle, - url: _spProfile?.url || _spPublicationUrl, - photo: _spProfile?.icon || "", - handle: \`@\${_spHandle}@\${_spHostname}\`, - emojis: [], - bot: false, - }, - published: _spNow, - createdAt: _spNow, - inReplyTo: null, - visibility: visibility || "public", - sensitive: sensitive === true || sensitive === "true", - category: [], - counts: { likes: 0, boosts: 0, replies: 0 }, - }); - - if (_spFederation) { - try { - const { Note, Create } = await import("@fedify/fedify/vocab"); - const _spCtx = _spFederation.createContext( - new URL(_spPublicationUrl + "/"), - { handle: _spHandle, publicationUrl: _spPublicationUrl }, - ); - const _spActorUri = _spCtx.getActorUri(_spHandle); - const _spNote = new Note({ - id: new URL(_spNoteId), - attributedTo: _spActorUri, - content: \`\${_spHtml}
\`, - published: new Date(_spNow), - }); - const _spCreate = new Create({ - id: new URL(\`\${_spNoteId}#create\`), - actor: _spActorUri, - object: _spNote, - }); - await _spCtx.sendActivity( - { identifier: _spHandle }, - "followers", - _spCreate, - { preferSharedInbox: true, syncCollection: true, orderingKey: _spNoteId }, - ); - console.info(\`[Mastodon API] Short post federated to followers (no blog entry): \${_spNoteId}\`); - } catch (_spApErr) { - console.warn(\`[Mastodon API] Short post AP federation error: \${_spApErr.message}\`); - } - } - - const _spStatus = serializeStatus(_spTlItem, { - baseUrl, - favouritedIds: new Set(), - rebloggedIds: new Set(), - bookmarkedIds: new Set(), - pinnedIds: new Set(), - }); - - if (idempotencyKey && collections.ap_idempotency) { - const { createHash } = await import("node:crypto"); - const _spKey = createHash("sha256").update(\`\${baseUrl}:\${idempotencyKey}\`).digest("hex"); - await collections.ap_idempotency.insertOne({ key: _spKey, response: _spStatus, createdAt: new Date() }).catch(() => {}); - } - - return res.json(_spStatus); - } - - // Resolve in_reply_to URL from status ID (cursor or ObjectId) - let inReplyTo = null; - if (inReplyToId) {`; - -async function exists(p) { - try { await access(p); return true; } catch { return false; } -} - -let patched = false; -for (const filePath of candidates) { - if (!(await exists(filePath))) continue; - const src = await readFile(filePath, "utf8"); - if (src.includes(MARKER)) { - console.log(`[postinstall] patch-ap-mastodon-short-post-bypass: already applied in ${filePath}`); - patched = true; - break; - } - if (!src.includes(OLD)) { - console.log(`[postinstall] patch-ap-mastodon-short-post-bypass: target snippet not found in ${filePath}`); - continue; - } - await writeFile(filePath, src.replace(OLD, NEW), "utf8"); - console.log(`[postinstall] patch-ap-mastodon-short-post-bypass: applied to ${filePath}`); - patched = true; - break; -} - -if (!patched) { - console.log("[postinstall] patch-ap-mastodon-short-post-bypass: no target file found"); -} diff --git a/scripts/patch-ap-syndicate-deploy-delay.mjs b/scripts/patch-ap-syndicate-deploy-delay.mjs deleted file mode 100644 index 182ecd41..00000000 --- a/scripts/patch-ap-syndicate-deploy-delay.mjs +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Patch: add a 2-minute deploy delay before ActivityPub syndication. - * - * When a post is created (via Micropub or Mastodon-API compose), the - * blog's deploy pipeline (Gitea → Eleventy → rsync) takes ~1-2 minutes. - * If ActivityPub fires immediately, followers receive a Create activity - * whose `url` still 404s — Mastodon clients show a broken link. - * - * This patch inserts a `setTimeout`-based delay at the top of the - * `syndicate()` function. The delay is configurable via the - * AP_SYNDICATE_DELAY_MS environment variable (default: 120000 = 2 min). - */ - -import { access, readFile, writeFile } from "node:fs/promises"; - -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 MARKER = "// [patch] ap-syndicate-deploy-delay"; - -// The line right after the early-exit guards — insert before the -// try { const actorUrl = ... } block. -const INSERT_AFTER = ` try { - const actorUrl = plugin._getActorUrl();`; - -const DELAY_BLOCK = ` // [patch] ap-syndicate-deploy-delay — wait for deploy pipeline before federating - const _apDelay = Number.parseInt(process.env.AP_SYNDICATE_DELAY_MS ?? "120000", 10); - if (_apDelay > 0) { - console.info(\`[ActivityPub] Delaying syndication by \${_apDelay}ms for \${properties.url}\`); - await new Promise((resolve) => setTimeout(resolve, _apDelay)); - } - ${MARKER} - try { - const actorUrl = plugin._getActorUrl();`; - -async function exists(p) { - try { await access(p); return true; } catch { return false; } -} - -let patched = false; -for (const filePath of candidates) { - if (!(await exists(filePath))) continue; - let src = await readFile(filePath, "utf8"); - if (src.includes(MARKER)) { - console.log(`[postinstall] patch-ap-syndicate-deploy-delay: already applied in ${filePath}`); - patched = true; - break; - } - if (!src.includes(INSERT_AFTER)) { - console.log(`[postinstall] patch-ap-syndicate-deploy-delay: insertion point not found in ${filePath}`); - continue; - } - src = src.replace(INSERT_AFTER, DELAY_BLOCK); - await writeFile(filePath, src, "utf8"); - console.log(`[postinstall] patch-ap-syndicate-deploy-delay: applied in ${filePath}`); - patched = true; - break; -} - -if (!patched) { - console.log("[postinstall] patch-ap-syndicate-deploy-delay: no target file found"); -} diff --git a/scripts/patch-federation-unlisted-guards.mjs b/scripts/patch-federation-unlisted-guards.mjs deleted file mode 100644 index 39c8b05e..00000000 --- a/scripts/patch-federation-unlisted-guards.mjs +++ /dev/null @@ -1,143 +0,0 @@ -import { access, readFile, writeFile } from "node:fs/promises"; - -// activitypub index.js and federation-setup.js unlisted guards are now -// built into the fork — only endpoint-syndicate (separate package) needs patching. -// backfill-timeline.js also needs patching — it runs on every startup and -// backfills ap_timeline from posts without filtering unlisted posts. - -const endpointSyndicateCandidates = [ - "node_modules/@indiekit/endpoint-syndicate/lib/utils.js", - "node_modules/@indiekit/indiekit/node_modules/@indiekit/endpoint-syndicate/lib/utils.js", -]; - -const backfillTimelineCandidates = [ - "node_modules/@rmdes/indiekit-endpoint-activitypub/lib/mastodon/backfill-timeline.js", -]; - -const patchSpecs = [ - { - name: "backfill-timeline-unlisted-guard", - candidates: backfillTimelineCandidates, - oldSnippet: ` const allPosts = await posts - .find({ - "properties.post-status": { $ne: "draft" }, - "properties.deleted": { $exists: false }, - "properties.url": { $exists: true }, - }) - .toArray();`, - newSnippet: ` const allPosts = await posts - .find({ - "properties.post-status": { $ne: "draft" }, - "properties.deleted": { $exists: false }, - "properties.url": { $exists: true }, - // Exclude unlisted posts — they must not appear in ap_timeline (Mastodon API). - "properties.visibility": { $ne: "unlisted" }, - }) - .toArray();`, - }, - { - name: "endpoint-syndicate-source-url-unlisted-guard", - candidates: endpointSyndicateCandidates, - oldSnippet: ` postData = await postsCollection.findOne({ - "properties.url": url, - });`, - newSnippet: ` postData = await postsCollection.findOne({ - "properties.url": url, - "properties.post-status": { - $ne: "draft", - }, - // Exclude unlisted posts from automatic syndication/federation. - "properties.visibility": { - $ne: "unlisted", - }, - });`, - }, - { - name: "endpoint-syndicate-get-post-data-pending-unlisted-guard", - candidates: endpointSyndicateCandidates, - oldSnippet: ` "properties.post-status": { - $ne: "draft", - }, - })`, - newSnippet: ` "properties.post-status": { - $ne: "draft", - }, - // Exclude unlisted posts from automatic syndication/federation. - "properties.visibility": { - $ne: "unlisted", - }, - })`, - }, - { - name: "endpoint-syndicate-get-all-post-data-unlisted-guard", - candidates: endpointSyndicateCandidates, - oldSnippet: ` "properties.post-status": { - $ne: "draft", - }, - })`, - newSnippet: ` "properties.post-status": { - $ne: "draft", - }, - // Exclude unlisted posts from automatic syndication/federation. - "properties.visibility": { - $ne: "unlisted", - }, - })`, - }, -]; - -async function exists(filePath) { - try { - await access(filePath); - return true; - } catch { - return false; - } -} - -const checkedFiles = new Set(); -const patchedFiles = new Set(); - -for (const spec of patchSpecs) { - let foundAnyTarget = false; - - for (const filePath of spec.candidates) { - if (!(await exists(filePath))) { - continue; - } - - foundAnyTarget = true; - checkedFiles.add(filePath); - - const source = await readFile(filePath, "utf8"); - - let updated = source; - let replacements = 0; - - if (source.includes(spec.oldSnippet)) { - updated = source.replace(spec.oldSnippet, spec.newSnippet); - replacements = 1; - } - - if (replacements === 0 || updated === source) { - continue; - } - - await writeFile(filePath, updated, "utf8"); - patchedFiles.add(filePath); - } - - if (!foundAnyTarget) { - console.log(`[postinstall] ${spec.name}: no target files found`); - } -} - -if (checkedFiles.size === 0) { - console.log("[postinstall] No federation patch targets found"); -} else if (patchedFiles.size === 0) { - console.log("[postinstall] federation unlisted guards already patched"); -} else { - console.log( - `[postinstall] Patched federation unlisted guards in ${patchedFiles.size}/${checkedFiles.size} file(s)`, - ); -}