Replace the semantically incorrect fake-Note approach with strict AP protocol compliance:
- patch-ap-like-note-dispatcher: rewritten to revert the fake-Note block
- patch-ap-like-activity-id: adds canonical id URI to Like activities (AP §6.2.1)
- patch-ap-like-activity-dispatcher: registers setObjectDispatcher(Like, ...) so
/activitypub/activities/like/{id} is dereferenceable (AP §3.1)
- patch-ap-url-lookup-api-like: /api/ap-url now returns the likeOf URL for AP-likes
so the "Also on: Fediverse" widget's authorize_interaction flow opens the
original Mastodon post on the remote instance
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
111 lines
4.0 KiB
JavaScript
111 lines
4.0 KiB
JavaScript
/**
|
|
* Patch: make the /api/ap-url endpoint return the liked post URL for AP-likes.
|
|
*
|
|
* Root cause:
|
|
* For like posts where like-of is an ActivityPub URL (e.g. a Mastodon status),
|
|
* the "Also on: Fediverse" widget's authorize_interaction flow needs to send
|
|
* the user to the original AP object, not to a blog-side Note URL.
|
|
*
|
|
* The current handler always returns a /activitypub/objects/note/{id} URL,
|
|
* which 404s for AP-likes (because jf2ToAS2Activity returns a Like activity,
|
|
* not a Create(Note), so the Note dispatcher returns null).
|
|
*
|
|
* Fix:
|
|
* Before building the Note/Article URL, check whether the post is an AP-like
|
|
* (like-of is a URL that responds with application/activity+json). If it is,
|
|
* return { apUrl: likeOf } so that authorize_interaction opens the original
|
|
* AP object on the remote instance, where the user can interact with it.
|
|
*
|
|
* Non-AP likes (like-of is a plain web URL) fall through to the existing
|
|
* Note URL logic unchanged.
|
|
*/
|
|
|
|
import { access, readFile, writeFile } from "node:fs/promises";
|
|
|
|
const candidates = [
|
|
"node_modules/@rmdes/indiekit-endpoint-activitypub/index.js",
|
|
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/index.js",
|
|
];
|
|
|
|
const MARKER = "// ap-url-lookup-api-like patch";
|
|
|
|
const OLD_SNIPPET = ` // Determine the AP object type (mirrors jf2-to-as2.js logic)
|
|
const postType = post.properties?.["post-type"];
|
|
const isArticle = postType === "article" && !!post.properties?.name;
|
|
const objectType = isArticle ? "article" : "note";`;
|
|
|
|
const NEW_SNIPPET = ` // Determine the AP object type (mirrors jf2-to-as2.js logic)
|
|
const postType = post.properties?.["post-type"];
|
|
|
|
// For AP-likes: the widget should open the liked post on the remote instance.
|
|
// We detect AP URLs the same way as jf2-to-as2.js: HEAD with activity+json Accept.
|
|
// ap-url-lookup-api-like patch
|
|
if (postType === "like") {
|
|
const likeOf = post.properties?.["like-of"] || "";
|
|
if (likeOf) {
|
|
let isAp = false;
|
|
try {
|
|
const ctrl = new AbortController();
|
|
const tid = setTimeout(() => ctrl.abort(), 3000);
|
|
const r = await fetch(likeOf, {
|
|
method: "HEAD",
|
|
headers: { Accept: "application/activity+json, application/ld+json" },
|
|
signal: ctrl.signal,
|
|
});
|
|
clearTimeout(tid);
|
|
const ct = r.headers.get("content-type") || "";
|
|
isAp = ct.includes("activity+json") || ct.includes("ld+json");
|
|
} catch { /* network error — treat as non-AP */ }
|
|
if (isAp) {
|
|
res.set("Cache-Control", "public, max-age=60");
|
|
return res.json({ apUrl: likeOf });
|
|
}
|
|
}
|
|
}
|
|
|
|
const isArticle = postType === "article" && !!post.properties?.name;
|
|
const objectType = isArticle ? "article" : "note";`;
|
|
|
|
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;
|
|
let source = await readFile(filePath, "utf8");
|
|
|
|
if (source.includes(MARKER)) {
|
|
continue; // already patched
|
|
}
|
|
|
|
if (!source.includes(OLD_SNIPPET)) {
|
|
console.log(`[postinstall] patch-ap-url-lookup-api-like: snippet not found in ${filePath}`);
|
|
continue;
|
|
}
|
|
|
|
source = source.replace(OLD_SNIPPET, NEW_SNIPPET);
|
|
await writeFile(filePath, source, "utf8");
|
|
patched += 1;
|
|
console.log(`[postinstall] Applied patch-ap-url-lookup-api-like to ${filePath}`);
|
|
}
|
|
|
|
if (checked === 0) {
|
|
console.log("[postinstall] patch-ap-url-lookup-api-like: no target files found");
|
|
} else if (patched === 0) {
|
|
console.log("[postinstall] patch-ap-url-lookup-api-like: already up to date");
|
|
} else {
|
|
console.log(`[postinstall] patch-ap-url-lookup-api-like: patched ${patched}/${checked} file(s)`);
|
|
}
|