Files
indiekit-server/scripts/patch-bluesky-og-own-post-title.mjs
Sven 82319432db
Deploy Indiekit Server / deploy (push) Successful in 1m18s
Fix Bluesky card showing "Indiekit Authenticate" instead of post title
fetchOpenGraphData() used _toInternalUrl() to rewrite own-blog URLs to
the Node.js jail, which serves Indiekit admin (requiring auth) rather than
the static Eleventy output. This caused og:title "Indiekit Authenticate"
to end up as the Bluesky link card title.

New patch (patch-bluesky-og-own-post-title) skips fetchOpenGraphData()
for own-domain post URLs and instead derives title/description from the
JF2 properties passed by the caller.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 17:10:35 +02:00

127 lines
4.7 KiB
JavaScript

/**
* Patch @rmdes/indiekit-syndicator-bluesky so that own-domain post URLs
* don't fetch OG metadata via the internal URL.
*
* Problem: fetchOpenGraphData() rewrites own-blog URLs to the Node.js
* internal address (INTERNAL_FETCH_URL). But static Eleventy pages are
* served by nginx on the web jail — not by the Node.js process. The
* internal fetch therefore hits Indiekit's CMS which responds with an
* authentication page whose og:title is "Indiekit Authenticate", so
* Bluesky cards end up titled "Indiekit Authenticate".
*
* Fix:
* 1. createExternalEmbed() accepts optional `title`/`description` in
* options and skips fetchOpenGraphData() when they are provided for
* own-domain URLs.
* 2. post() passes title/description derived from JF2 properties when
* the embed URL is an own-domain post permalink.
*/
import { access, readFile, writeFile } from "node:fs/promises";
const MARKER = "// [patch] bluesky-og-own-post-title";
const TARGET =
"node_modules/@rmdes/indiekit-syndicator-bluesky/lib/bluesky.js";
// ── Replacement 1: createExternalEmbed — skip fetchOpenGraphData for own posts
const OLD_OG_FETCH = ` // Fetch OG metadata from HTML (needed for title/description,
// and as image fallback for external URLs)
const ogData = await fetchOpenGraphData(url);`;
const NEW_OG_FETCH = ` // Fetch OG metadata from HTML (needed for title/description,
// and as image fallback for external URLs).
// For own-domain posts skip the HTML fetch: _toInternalUrl() rewrites
// to the Node.js jail which serves Indiekit admin (auth page), not the
// static Eleventy output. Use caller-supplied title/description instead.
${MARKER}
const ogData =
isOwnPost && (options.title || options.description)
? {
title: options.title || url,
description: options.description || "",
imageUrl: null,
}
: await fetchOpenGraphData(url);`;
// ── Replacement 2: post() — pass title/description for own-domain embed URLs
const OLD_EMBED_CALL = ` // Create OG embed:
// - External URL exists → use it as OG card (permalink is in text)
// - No external URL → use permalink as OG card
let externalEmbed = null;
if (!images?.length) {
const embedUrl = externalUrl || properties.url;
if (embedUrl) {
externalEmbed = await this.createExternalEmbed(embedUrl, { me });
}
}`;
const NEW_EMBED_CALL = ` // Create OG embed:
// - External URL exists → use it as OG card (permalink is in text)
// - No external URL → use permalink as OG card
let externalEmbed = null;
if (!images?.length) {
const embedUrl = externalUrl || properties.url;
if (embedUrl) {
// For own-domain post URLs the internal fetch would hit the Indiekit
// auth page instead of the static HTML. Supply title/description from
// JF2 properties so fetchOpenGraphData() is bypassed. ${MARKER}
const _meNorm = me?.replace(/\\/+$/, "");
const _isOwnEmbed = _meNorm && embedUrl.startsWith(_meNorm);
let _embedOpts = { me };
if (_isOwnEmbed) {
const _rawText =
properties.content?.text ||
(properties.content?.html
? properties.content.html
.replace(/<[^>]+>/g, " ")
.replace(/\\s+/g, " ")
.trim()
: "");
_embedOpts.title =
(properties.name || _rawText.slice(0, 100).trim() || embedUrl);
_embedOpts.description = _rawText.slice(0, 300).trim();
}
externalEmbed = await this.createExternalEmbed(embedUrl, _embedOpts);
}
}`;
async function exists(p) {
try {
await access(p);
return true;
} catch {
return false;
}
}
if (!(await exists(TARGET))) {
console.log(`[postinstall] bluesky-og-own-post-title: ${TARGET} not found — skipping`);
process.exit(0);
}
const source = await readFile(TARGET, "utf8");
if (source.includes(MARKER)) {
console.log("[postinstall] bluesky-og-own-post-title: already applied");
process.exit(0);
}
const missing = [];
if (!source.includes(OLD_OG_FETCH)) missing.push("OG fetch snippet");
if (!source.includes(OLD_EMBED_CALL)) missing.push("embed call snippet");
if (missing.length) {
console.warn(
`[postinstall] bluesky-og-own-post-title: snippets not found — skipping (${missing.join(", ")})`,
);
process.exit(0);
}
const updated = source
.replace(OLD_OG_FETCH, NEW_OG_FETCH)
.replace(OLD_EMBED_CALL, NEW_EMBED_CALL);
await writeFile(TARGET, updated, "utf8");
console.log(`[postinstall] Patched bluesky-og-own-post-title in ${TARGET}`);