Files
indiekit-server/scripts/patch-ap-mastodon-status-id.mjs
Sven f28552a5b2
All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m17s
fix: activitypub -> time confusion created / published times
2026-04-01 14:17:42 +02:00

100 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Patch: fix POST /api/v1/statuses response ID to match ap_timeline published date.
*
* Root cause:
* The POST /api/v1/statuses handler returns `id: String(Date.now())` — the
* wall-clock time when the response is sent. The ap_timeline item inserted by
* patch-ap-mastodon-reply-threading uses `published: data.properties.published`,
* which is set BEFORE postContent.create() runs (before the Gitea write, which
* can take several seconds).
*
* When Phanpy/Elk receives the creation response and the user then replies to that
* post, the client sends `in_reply_to_id: <id from creation response>`. The handler
* calls findTimelineItemById with that id. The ±1 second range query looks for
* published ≈ Date.now(), but the stored item has published = postCreationTime
* (potentially 515 seconds earlier). The range misses → inReplyTo = null →
* jf2["in-reply-to"] not set → getPostType returns "note" instead of "reply".
*
* Fix:
* 1. Also import encodeCursor alongside decodeCursor.
* 2. Use encodeCursor(data.properties.published) as the status ID in the response.
* Falls back to String(Date.now()) if published is missing/invalid.
*
* This ensures the creation response ID matches what findTimelineItemById will
* resolve in subsequent in_reply_to_id lookups.
*/
import { access, readFile, writeFile } from "node:fs/promises";
const MARKER = "// [patch] ap-mastodon-status-id";
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",
];
// Change 1: add encodeCursor to the decodeCursor import
const OLD_IMPORT = `import { decodeCursor } from "../helpers/pagination.js";`;
const NEW_IMPORT = `import { decodeCursor, encodeCursor } from "../helpers/pagination.js"; ${MARKER}`;
// Change 2: replace String(Date.now()) with encodeCursor(data.properties.published)
const OLD_ID = ` res.json({
id: String(Date.now()),`;
const NEW_ID = ` res.json({
id: encodeCursor(data.properties.published) || String(Date.now()), ${MARKER}`;
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-mastodon-status-id: already applied to ${filePath}`);
continue;
}
if (!source.includes(OLD_IMPORT)) {
console.warn(`[postinstall] patch-ap-mastodon-status-id: import snippet not found in ${filePath}`);
continue;
}
if (!source.includes(OLD_ID)) {
console.warn(`[postinstall] patch-ap-mastodon-status-id: response id snippet not found in ${filePath}`);
continue;
}
let updated = source
.replace(OLD_IMPORT, NEW_IMPORT)
.replace(OLD_ID, NEW_ID);
if (updated === source) {
console.log(`[postinstall] patch-ap-mastodon-status-id: no changes in ${filePath}`);
continue;
}
await writeFile(filePath, updated, "utf8");
patched += 1;
console.log(`[postinstall] Applied patch-ap-mastodon-status-id to ${filePath}`);
}
if (checked === 0) {
console.log("[postinstall] patch-ap-mastodon-status-id: no target files found");
} else if (patched === 0) {
console.log("[postinstall] patch-ap-mastodon-status-id: already up to date");
} else {
console.log(`[postinstall] patch-ap-mastodon-status-id: patched ${patched} file(s)`);
}