From 1cf5b01788e23d5f059717149c1fed14fcf12c89 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Mar 2026 14:39:21 +0000 Subject: [PATCH 1/2] fix(ap): fix OG image not included in ActivityPub activities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: jf2-to-as2.js (both 842fc5af and 45f8ba9) uses a regex expecting date-based URLs like /articles/YYYY/MM/DD/slug/ to extract the post date for the OG image filename. This blog uses flat URLs like /articles/slug/ so the regex never matches — no `image` property is set on Note/Article ActivityPub objects and fediverse clients show no preview card. Fix: - Add scripts/patch-ap-og-image.mjs: replaces the URL-pattern regex with slug-from-last-path-segment + date-from-properties.published, producing /og/{year}-{month}-{day}-{slug}.png matching the Eleventy OG filenames. Handles both 842fc5af (slug-only URL) and 45f8ba9 (date+slug) variants. Applied to both jf2ToActivityStreams() and jf2ToAS2Activity(). - Register patch in package.json postinstall and serve after patch-ap-repost-commentary. - Bump package-lock.json to 45f8ba9 fork commit (correct date+slug filename format, likes-as-bookmarks, announce cc reverted). https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX --- README.md | 17 +++- package-lock.json | 2 +- package.json | 4 +- scripts/patch-ap-og-image.mjs | 141 ++++++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 scripts/patch-ap-og-image.mjs diff --git a/README.md b/README.md index ef7135a7..5a34da7c 100644 --- a/README.md +++ b/README.md @@ -160,15 +160,25 @@ Posts are converted from Indiekit's JF2 format to ActivityStreams 2.0 in two mod ### AP-specific patches -These patches are applied to `node_modules` via postinstall and at serve startup. They're needed because the lockfile pins the fork to v2.10.1 which predates some fixes, and because some fixes cannot be upstreamed. +These patches are applied to `node_modules` via postinstall and at serve startup. They're needed because some fixes cannot be upstreamed or because they adapt upstream behaviour to this blog's specific URL structure. | Patch | Target | What it does | |---|---|---| | `patch-ap-allow-private-address` | federation-setup.js | Adds `signatureTimeWindow` and `allowPrivateAddress` to `createFederation()` | | `patch-ap-url-lookup-api` | Adds new route | Public `GET /activitypub/api/ap-url` resolves blog URL → AP object URL | +| `patch-ap-og-image` | jf2-to-as2.js | Fixes OG image URL generation — see below | | `patch-federation-unlisted-guards` | endpoint-syndicate | Prevents unlisted posts from being re-syndicated (AP fork has this natively) | | `patch-endpoint-activitypub-locales` | locales | Injects German (`de`) translations for the AP endpoint UI | +**`patch-ap-og-image.mjs`** +The fork (both 842fc5af and 45f8ba9) attempts to derive the OG image path by matching a date-based URL pattern like `/articles/2024/01/15/slug/`. This blog uses flat URLs (`/articles/slug/`) with no date component, so the regex never matches and no `image` property is set on ActivityPub objects — Mastodon and other clients never show a preview card. + +The patch replaces the URL-pattern extraction with: +1. Slug from the last URL path segment. +2. Date from `properties.published` (ISO-8601 string). + +This produces the correct `/og/{year}-{month}-{day}-{slug}.png` filename that the Eleventy build generates for per-post OG images. Applied to both `jf2ToActivityStreams()` (plain JSON-LD) and `jf2ToAS2Activity()` (Fedify vocab objects). + ### AP environment variables | Variable | Default | Purpose | @@ -648,6 +658,11 @@ Environment variables are loaded from `.env` via `dotenv`. See `indiekit.config. ## Changelog +### 2026-03-20 + +**fix(ap): fix OG image not included in ActivityPub activities** +The fork's OG image code expected date-based URLs (`/articles/YYYY/MM/DD/slug/`) but this blog uses flat URLs (`/articles/slug/`). The regex never matched so no `image` property was set and Mastodon/fediverse clients showed no preview card. Added `patch-ap-og-image.mjs` which extracts the slug from the URL's last path segment and the date from `properties.published`, producing the correct `/og/{year}-{month}-{day}-{slug}.png` filename. Also updated `package-lock.json` to pull the `45f8ba9` fork commit (likes-as-bookmarks + correct date+slug OG filename format). + ### 2026-03-19 **feat: deliver likes as bookmarks, revert announce cc, add OG images** (`45f8ba9` in svemagie/indiekit-endpoint-activitypub) diff --git a/package-lock.json b/package-lock.json index 013fafd7..b5ed206a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2357,7 +2357,7 @@ }, "node_modules/@rmdes/indiekit-endpoint-activitypub": { "version": "2.15.4", - "resolved": "git+ssh://git@github.com/svemagie/indiekit-endpoint-activitypub.git#842fc5af2ab946d6cc2f2abfbe4728da3edaf3da", + "resolved": "git+ssh://git@github.com/svemagie/indiekit-endpoint-activitypub.git#45f8ba93c0202fabf460bdd03e9ee758faa0a457", "license": "MIT", "dependencies": { "@fedify/debugger": "^2.0.0", diff --git a/package.json b/package.json index 9c4c09e8..d9a08a7d 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "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-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-ap-url-lookup-api.mjs && node scripts/patch-ap-allow-private-address.mjs && node scripts/patch-ap-repost-commentary.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.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", - "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-microsub-reader-ap-dispatch.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-ap-url-lookup-api.mjs && node scripts/patch-ap-allow-private-address.mjs && node scripts/patch-ap-repost-commentary.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.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 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-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-ap-url-lookup-api.mjs && node scripts/patch-ap-allow-private-address.mjs && node scripts/patch-ap-repost-commentary.mjs && node scripts/patch-ap-og-image.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.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", + "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-microsub-reader-ap-dispatch.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-ap-url-lookup-api.mjs && node scripts/patch-ap-allow-private-address.mjs && node scripts/patch-ap-repost-commentary.mjs && node scripts/patch-ap-og-image.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.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 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-og-image.mjs b/scripts/patch-ap-og-image.mjs new file mode 100644 index 00000000..89b2675a --- /dev/null +++ b/scripts/patch-ap-og-image.mjs @@ -0,0 +1,141 @@ +/** + * Patch: fix OG image URL generation in ActivityPub jf2-to-as2.js. + * + * Root cause: + * Both 842fc5af and 45f8ba9 versions of jf2-to-as2.js try to extract the + * post date from the URL using a regex that expects date-based URLs like + * /articles/2024/01/15/slug/ but this blog uses flat URLs like /articles/slug/. + * The regex never matches so the `image` property is never set — no OG image + * preview card reaches Mastodon or other fediverse servers. + * + * Fix: + * Replace the date-from-URL regex with an approach that: + * 1. Extracts the slug from the last path segment of the post URL. + * 2. Reads the date from properties.published (ISO-8601 string). + * Constructs /og/{year}-{month}-{day}-{slug}.png — the filename pattern that + * the Eleventy build generates for static OG preview images. + * + * Both jf2ToActivityStreams() (plain JSON-LD) and jf2ToAS2Activity() (Fedify + * vocab objects) are patched. Both 842fc5af and 45f8ba9 variants are handled + * so the patch works regardless of which commit npm install resolved. + */ + +import { access, readFile, writeFile } from "node:fs/promises"; + +const candidates = [ + "node_modules/@rmdes/indiekit-endpoint-activitypub/lib/jf2-to-as2.js", + "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/jf2-to-as2.js", +]; + +const MARKER = "// og-image fix"; + +// --------------------------------------------------------------------------- +// Use JS regex patterns to locate the OG image blocks. +// Both 842fc5af and 45f8ba9 share the same variable names (ogMatch / ogMatchF) +// and the same if-block structure, differing only in the URL construction. +// +// Pattern: matches from "const ogMatch[F] = postUrl && postUrl.match(" to the +// closing "}" (2-space indent) of the if block. +// --------------------------------------------------------------------------- +const CN_BLOCK_RE = + / const ogMatch = postUrl && postUrl\.match\([^\n]+\n if \(ogMatch\) \{[\s\S]*?\n \}/; + +const AS2_BLOCK_RE = + / const ogMatchF = postUrl && postUrl\.match\([^\n]+\n if \(ogMatchF\) \{[\s\S]*?\n \}/; + +// --------------------------------------------------------------------------- +// Replacement: extract slug from URL last segment, date from published ISO string. +// Build /og/{year}-{month}-{day}-{slug}.png to match the Eleventy OG filenames. +// +// Template literal note: backslashes in regex literals inside the injected code +// are doubled here so they survive the template literal → string conversion: +// \\\/ → \/ (escaped slash in regex) +// \\d → \d (digit class) +// [\\\w-] → [\w-] (word char class) +// --------------------------------------------------------------------------- +const NEW_CN = ` const ogSlug = postUrl && postUrl.match(/\\/([\\\w-]+)\\/?$/)?.[1]; // og-image fix + const ogPub = properties.published && properties.published.match(/^(\\d{4})-(\\d{2})-(\\d{2})/); // og-image fix + if (ogSlug && ogPub) { // og-image fix + object.image = { + type: "Image", + url: \`\${publicationUrl.replace(/\\/$/, "")}/og/\${ogPub[1]}-\${ogPub[2]}-\${ogPub[3]}-\${ogSlug}.png\`, // og-image fix + mediaType: "image/png", + }; + }`; + +const NEW_AS2 = ` const ogSlugF = postUrl && postUrl.match(/\\/([\\\w-]+)\\/?$/)?.[1]; // og-image fix + const ogPubF = properties.published && properties.published.match(/^(\\d{4})-(\\d{2})-(\\d{2})/); // og-image fix + if (ogSlugF && ogPubF) { // og-image fix + noteOptions.image = new Image({ + url: new URL(\`\${publicationUrl.replace(/\\/$/, "")}/og/\${ogPubF[1]}-\${ogPubF[2]}-\${ogPubF[3]}-\${ogSlugF}.png\`), // og-image fix + mediaType: "image/png", + }); + }`; + +// --------------------------------------------------------------------------- + +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-og-image: already applied to ${filePath}`); + continue; + } + + let updated = source; + let changed = false; + + // Fix the jf2ToActivityStreams OG block + if (CN_BLOCK_RE.test(updated)) { + updated = updated.replace(CN_BLOCK_RE, NEW_CN); + changed = true; + } else { + console.warn( + `[postinstall] patch-ap-og-image: jf2ToActivityStreams OG block not found in ${filePath} — skipping`, + ); + } + + // Fix the jf2ToAS2Activity OG block + if (AS2_BLOCK_RE.test(updated)) { + updated = updated.replace(AS2_BLOCK_RE, NEW_AS2); + changed = true; + } else { + console.warn( + `[postinstall] patch-ap-og-image: jf2ToAS2Activity OG block not found in ${filePath} — skipping`, + ); + } + + if (!changed || updated === source) { + console.log(`[postinstall] patch-ap-og-image: no changes applied to ${filePath}`); + continue; + } + + await writeFile(filePath, updated, "utf8"); + patched += 1; + console.log(`[postinstall] Applied patch-ap-og-image to ${filePath}`); +} + +if (checked === 0) { + console.log("[postinstall] patch-ap-og-image: no target files found"); +} else if (patched === 0) { + console.log("[postinstall] patch-ap-og-image: already up to date"); +} else { + console.log(`[postinstall] patch-ap-og-image: patched ${patched}/${checked} file(s)`); +} From 769720b33f9e371a04c9664212832c93707bc3c0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Mar 2026 20:04:19 +0000 Subject: [PATCH 2/2] fix(ap): use slug-only OG image path /og/{slug}.png The previous fix incorrectly generated /og/{year}-{month}-{day}-{slug}.png but the Eleventy blog generates OG images at /og/{slug}.png (e.g. /og/2615b.png). Remove the unnecessary date extraction and simplify to slug-only. https://claude.ai/code/session_0124D41vdLYE3DkJxhPqYthX --- README.md | 10 +++------- scripts/patch-ap-og-image.mjs | 29 ++++++++++++----------------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 5a34da7c..7a14dfad 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ Posts are converted from Indiekit's JF2 format to ActivityStreams 2.0 in two mod - Permalink appended to content body - Nested hashtags normalized: `on/art/music` → `#music` (Mastodon doesn't support path-style tags) - Sensitive posts flagged with `sensitive: true`; summary doubles as CW text for notes -- Per-post OG image added to Note/Article objects (`/og/{year}-{month}-{day}-{slug}.png`) for fediverse preview cards +- Per-post OG image added to Note/Article objects (`/og/{slug}.png`) for fediverse preview cards ### Express ↔ Fedify bridge @@ -173,11 +173,7 @@ These patches are applied to `node_modules` via postinstall and at serve startup **`patch-ap-og-image.mjs`** The fork (both 842fc5af and 45f8ba9) attempts to derive the OG image path by matching a date-based URL pattern like `/articles/2024/01/15/slug/`. This blog uses flat URLs (`/articles/slug/`) with no date component, so the regex never matches and no `image` property is set on ActivityPub objects — Mastodon and other clients never show a preview card. -The patch replaces the URL-pattern extraction with: -1. Slug from the last URL path segment. -2. Date from `properties.published` (ISO-8601 string). - -This produces the correct `/og/{year}-{month}-{day}-{slug}.png` filename that the Eleventy build generates for per-post OG images. Applied to both `jf2ToActivityStreams()` (plain JSON-LD) and `jf2ToAS2Activity()` (Fedify vocab objects). +The patch replaces the broken date-from-URL regex with a simple last-path-segment extraction, producing `/og/{slug}.png` — the actual filename the Eleventy build generates (e.g. `/og/2615b.png`). Applied to both `jf2ToActivityStreams()` (plain JSON-LD) and `jf2ToAS2Activity()` (Fedify vocab objects). ### AP environment variables @@ -661,7 +657,7 @@ Environment variables are loaded from `.env` via `dotenv`. See `indiekit.config. ### 2026-03-20 **fix(ap): fix OG image not included in ActivityPub activities** -The fork's OG image code expected date-based URLs (`/articles/YYYY/MM/DD/slug/`) but this blog uses flat URLs (`/articles/slug/`). The regex never matched so no `image` property was set and Mastodon/fediverse clients showed no preview card. Added `patch-ap-og-image.mjs` which extracts the slug from the URL's last path segment and the date from `properties.published`, producing the correct `/og/{year}-{month}-{day}-{slug}.png` filename. Also updated `package-lock.json` to pull the `45f8ba9` fork commit (likes-as-bookmarks + correct date+slug OG filename format). +The fork's OG image code expected date-based URLs (`/articles/YYYY/MM/DD/slug/`) but this blog uses flat URLs (`/articles/slug/`). The regex never matched so no `image` property was set and Mastodon/fediverse clients showed no preview card. Added `patch-ap-og-image.mjs` which extracts the slug from the URL's last path segment and constructs `/og/{slug}.png` — the actual Eleventy OG filename format (e.g. `/og/2615b.png`). Also updated `package-lock.json` to pull the `45f8ba9` fork commit (likes-as-bookmarks, announce cc reverted). ### 2026-03-19 diff --git a/scripts/patch-ap-og-image.mjs b/scripts/patch-ap-og-image.mjs index 89b2675a..8874707e 100644 --- a/scripts/patch-ap-og-image.mjs +++ b/scripts/patch-ap-og-image.mjs @@ -3,17 +3,15 @@ * * Root cause: * Both 842fc5af and 45f8ba9 versions of jf2-to-as2.js try to extract the - * post date from the URL using a regex that expects date-based URLs like + * post slug from the URL using a regex that expects date-based URLs like * /articles/2024/01/15/slug/ but this blog uses flat URLs like /articles/slug/. * The regex never matches so the `image` property is never set — no OG image * preview card reaches Mastodon or other fediverse servers. * * Fix: - * Replace the date-from-URL regex with an approach that: - * 1. Extracts the slug from the last path segment of the post URL. - * 2. Reads the date from properties.published (ISO-8601 string). - * Constructs /og/{year}-{month}-{day}-{slug}.png — the filename pattern that - * the Eleventy build generates for static OG preview images. + * Replace the date-from-URL regex with a simple last-path-segment extraction. + * Constructs /og/{slug}.png — the actual filename pattern the Eleventy build + * generates for static OG preview images (e.g. /og/2615b.png). * * Both jf2ToActivityStreams() (plain JSON-LD) and jf2ToAS2Activity() (Fedify * vocab objects) are patched. Both 842fc5af and 45f8ba9 variants are handled @@ -44,30 +42,27 @@ const AS2_BLOCK_RE = / const ogMatchF = postUrl && postUrl\.match\([^\n]+\n if \(ogMatchF\) \{[\s\S]*?\n \}/; // --------------------------------------------------------------------------- -// Replacement: extract slug from URL last segment, date from published ISO string. -// Build /og/{year}-{month}-{day}-{slug}.png to match the Eleventy OG filenames. +// Replacement: extract slug from last URL path segment. +// Build /og/{slug}.png to match the Eleventy OG filenames (e.g. /og/2615b.png). // -// Template literal note: backslashes in regex literals inside the injected code -// are doubled here so they survive the template literal → string conversion: +// Template literal note: backslashes inside the injected regex are doubled so +// they survive the template literal → string conversion: // \\\/ → \/ (escaped slash in regex) -// \\d → \d (digit class) // [\\\w-] → [\w-] (word char class) // --------------------------------------------------------------------------- const NEW_CN = ` const ogSlug = postUrl && postUrl.match(/\\/([\\\w-]+)\\/?$/)?.[1]; // og-image fix - const ogPub = properties.published && properties.published.match(/^(\\d{4})-(\\d{2})-(\\d{2})/); // og-image fix - if (ogSlug && ogPub) { // og-image fix + if (ogSlug) { // og-image fix object.image = { type: "Image", - url: \`\${publicationUrl.replace(/\\/$/, "")}/og/\${ogPub[1]}-\${ogPub[2]}-\${ogPub[3]}-\${ogSlug}.png\`, // og-image fix + url: \`\${publicationUrl.replace(/\\/$/, "")}/og/\${ogSlug}.png\`, // og-image fix mediaType: "image/png", }; }`; const NEW_AS2 = ` const ogSlugF = postUrl && postUrl.match(/\\/([\\\w-]+)\\/?$/)?.[1]; // og-image fix - const ogPubF = properties.published && properties.published.match(/^(\\d{4})-(\\d{2})-(\\d{2})/); // og-image fix - if (ogSlugF && ogPubF) { // og-image fix + if (ogSlugF) { // og-image fix noteOptions.image = new Image({ - url: new URL(\`\${publicationUrl.replace(/\\/$/, "")}/og/\${ogPubF[1]}-\${ogPubF[2]}-\${ogPubF[3]}-\${ogSlugF}.png\`), // og-image fix + url: new URL(\`\${publicationUrl.replace(/\\/$/, "")}/og/\${ogSlugF}.png\`), // og-image fix mediaType: "image/png", }); }`;