All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m11s
- status.js: in_reply_to_id was always null (both branches of ternary returned null — TODO left unfilled). Changed to item.inReplyToId || null. - statuses.js POST handler: timeline insert now stores inReplyToId from the in_reply_to_id cursor the client already sent, so own replies are threaded correctly in Phanpy/Elk. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
97 lines
3.9 KiB
JavaScript
97 lines
3.9 KiB
JavaScript
/**
|
|
* Patch: fix in_reply_to_id always being null in Mastodon status serializer.
|
|
*
|
|
* Bug:
|
|
* status.js line 207:
|
|
* in_reply_to_id: item.inReplyTo ? null : null, // TODO: resolve to local ID
|
|
*
|
|
* Both branches of the ternary return null, so in_reply_to_id is ALWAYS null.
|
|
* Mastodon clients (Phanpy, Elk) use this field to display reply threading —
|
|
* without it, replies appear as standalone posts with no thread context.
|
|
*
|
|
* Fix (two changes):
|
|
*
|
|
* A) status.js — use item.inReplyToId (the encoded cursor of the parent post)
|
|
* instead of the tautological null.
|
|
*
|
|
* B) statuses.js POST /api/v1/statuses handler — when pre-inserting own posts
|
|
* into ap_timeline (reply-threading patch), also store
|
|
* inReplyToId: inReplyToId || null
|
|
* (inReplyToId is already in scope as the raw in_reply_to_id param from the
|
|
* client, which IS a valid encodeCursor value.)
|
|
*
|
|
* Note: inbound AP replies from remote servers will still have inReplyToId = null
|
|
* until a separate patch populates it from ap_timeline lookups. Own replies via
|
|
* the Mastodon client API are fully fixed by this patch.
|
|
*/
|
|
|
|
import { access, readFile, writeFile } from "node:fs/promises";
|
|
|
|
const MARKER = "// [patch] ap-status-reply-id";
|
|
|
|
// ── Change A: fix tautological null in status.js ──────────────────────────────
|
|
|
|
const statusEntityCandidates = [
|
|
"node_modules/@rmdes/indiekit-endpoint-activitypub/lib/mastodon/entities/status.js",
|
|
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/mastodon/entities/status.js",
|
|
];
|
|
|
|
const OLD_TAUTOLOGY = ` in_reply_to_id: item.inReplyTo ? null : null, // TODO: resolve to local ID`;
|
|
const NEW_REPLY_ID = ` in_reply_to_id: item.inReplyToId || null, ${MARKER}`;
|
|
|
|
// ── Change B: store inReplyToId in the Mastodon API timeline insert ───────────
|
|
|
|
const statusesRouteCandidates = [
|
|
"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 OLD_REPLY_INSERT = ` inReplyTo: inReplyTo || null, // [patch] ap-mastodon-reply-threading`;
|
|
const NEW_REPLY_INSERT = ` inReplyTo: inReplyTo || null, // [patch] ap-mastodon-reply-threading
|
|
inReplyToId: inReplyToId || null, ${MARKER}`;
|
|
|
|
async function exists(p) {
|
|
try { await access(p); return true; } catch { return false; }
|
|
}
|
|
|
|
async function applyPatch(candidates, oldSnippet, newSnippet, label) {
|
|
let checked = 0;
|
|
let patched = 0;
|
|
|
|
for (const filePath of candidates) {
|
|
if (!(await exists(filePath))) continue;
|
|
checked++;
|
|
|
|
const source = await readFile(filePath, "utf8");
|
|
if (source.includes(MARKER)) {
|
|
console.log(`[postinstall] patch-ap-status-reply-id: ${label} already applied to ${filePath}`);
|
|
continue;
|
|
}
|
|
|
|
if (!source.includes(oldSnippet)) {
|
|
console.warn(`[postinstall] patch-ap-status-reply-id: ${label} snippet not found in ${filePath}`);
|
|
continue;
|
|
}
|
|
|
|
await writeFile(filePath, source.replace(oldSnippet, newSnippet), "utf8");
|
|
patched++;
|
|
console.log(`[postinstall] Applied patch-ap-status-reply-id (${label}) to ${filePath}`);
|
|
}
|
|
|
|
return { checked, patched };
|
|
}
|
|
|
|
const a = await applyPatch(statusEntityCandidates, OLD_TAUTOLOGY, NEW_REPLY_ID, "status entity");
|
|
const b = await applyPatch(statusesRouteCandidates, OLD_REPLY_INSERT, NEW_REPLY_INSERT, "timeline insert");
|
|
|
|
const totalChecked = a.checked + b.checked;
|
|
const totalPatched = a.patched + b.patched;
|
|
|
|
if (totalChecked === 0) {
|
|
console.log("[postinstall] patch-ap-status-reply-id: no target files found");
|
|
} else if (totalPatched === 0) {
|
|
console.log("[postinstall] patch-ap-status-reply-id: already up to date");
|
|
} else {
|
|
console.log(`[postinstall] patch-ap-status-reply-id: patched ${totalPatched} file(s)`);
|
|
}
|