diff --git a/scripts/patch-ap-mastodon-misc.mjs b/scripts/patch-ap-mastodon-misc.mjs index 04115dcd..d33eda1d 100644 --- a/scripts/patch-ap-mastodon-misc.mjs +++ b/scripts/patch-ap-mastodon-misc.mjs @@ -258,6 +258,41 @@ const SEARCH_REMOTE_NEW = [ ` } // [patch] ap-search-url-resolve`, ].join("\n"); +// ── patch-ap-search-remote-uid-guard: skip AP objects with no uid ───────────── +// When extractObjectData fails to resolve the AP object's id (e.g. network +// error or malformed response), it returns uid:"". addTimelineItem({uid:""}) +// then matches/creates a single corrupted document shared by ALL failed lookups. +// Any client that receives this garbage status will get a 500 when liking it +// (likePost calls new URL("") which throws). Guard: reject rData with empty uid. + +const UID_GUARD_MARKER = "// [patch] ap-search-remote-uid-guard"; +// Anchor must be the two consecutive remote-marker lines that exist only here. +const UID_GUARD_OLD = + ` const _rData = await extractObjectData(_rObj, { documentLoader: _rDl }); ${SEARCH_REMOTE_MARKER}\n` + + ` const _rStored = await addTimelineItem(collections, _rData); ${SEARCH_REMOTE_MARKER}`; +const UID_GUARD_NEW = + ` const _rData = await extractObjectData(_rObj, { documentLoader: _rDl }); ${SEARCH_REMOTE_MARKER}\n` + + ` if (!_rData?.uid) throw new Error("remote AP object has no uid"); ${UID_GUARD_MARKER}\n` + + ` const _rStored = await addTimelineItem(collections, _rData); ${SEARCH_REMOTE_MARKER}`; + +let uidGuardDone = false; +for (const f of SEARCH_CANDIDATES) { + if (!(await fileExists(f))) continue; + const src = await readFile(f, "utf8"); + if (src.includes(UID_GUARD_MARKER)) { + console.log(`[postinstall] ${SCRIPT}: ap-search-remote-uid-guard already applied in ${f}`); + uidGuardDone = true; break; + } + if (!src.includes(UID_GUARD_OLD)) { + console.warn(`[postinstall] ${SCRIPT}: ap-search-remote-uid-guard — anchor not found in ${f}`); + continue; + } + await writeFile(f, src.replace(UID_GUARD_OLD, UID_GUARD_NEW), "utf8"); + console.log(`[postinstall] ${SCRIPT}: applied ap-search-remote-uid-guard to ${f}`); + total++; uidGuardDone = true; break; +} +if (!uidGuardDone) console.log(`[postinstall] ${SCRIPT}: ap-search-remote-uid-guard — no target file found`); + let searchRemoteDone = false; for (const f of SEARCH_CANDIDATES) { if (!(await fileExists(f))) continue; diff --git a/scripts/patch-ap-mastodon-statuses.mjs b/scripts/patch-ap-mastodon-statuses.mjs index c700ab29..c85f2d47 100644 --- a/scripts/patch-ap-mastodon-statuses.mjs +++ b/scripts/patch-ap-mastodon-statuses.mjs @@ -206,6 +206,33 @@ const NEW_CTX_STATE = ` // Serialize all items } const serializeOpts = { baseUrl, favouritedIds: ctxFavouritedIds, rebloggedIds: ctxRebloggedIds, bookmarkedIds: ctxBookmarkedIds, pinnedIds: new Set(), replyIdMap, replyAccountIdMap };`; +// ── Patch 6: ap-status-not-found-log ──────────────────────────────────────── +// Add warn logging to findTimelineItemById so 404s on /favourite etc. produce +// a log line showing the requested id and whether it was a bad ObjectId or a +// genuine missing document. Helps diagnose "Record not found" recurrences. + +const MARKER_NOT_FOUND_LOG = "// [patch] ap-status-not-found-log"; + +const OLD_FIND_BY_ID = `async function findTimelineItemById(collection, id) { + try { + return await collection.findOne({ _id: new ObjectId(id) }); + } catch { + return null; + } +}`; + +const NEW_FIND_BY_ID = `async function findTimelineItemById(collection, id) { ${MARKER_NOT_FOUND_LOG} + try { ${MARKER_NOT_FOUND_LOG} + const _oid = new ObjectId(id); ${MARKER_NOT_FOUND_LOG} + const _doc = await collection.findOne({ _id: _oid }); ${MARKER_NOT_FOUND_LOG} + if (!_doc) console.warn(\`[Mastodon API] findTimelineItemById: no item for id=\${id}\`); ${MARKER_NOT_FOUND_LOG} + return _doc; ${MARKER_NOT_FOUND_LOG} + } catch (_fErr) { ${MARKER_NOT_FOUND_LOG} + console.warn(\`[Mastodon API] findTimelineItemById: invalid id=\${id}: \${_fErr.message}\`); ${MARKER_NOT_FOUND_LOG} + return null; ${MARKER_NOT_FOUND_LOG} + } ${MARKER_NOT_FOUND_LOG} +}`; + // ── Runner ─────────────────────────────────────────────────────────────────── const FILES = apPath("lib/mastodon/routes/statuses.js"); @@ -216,6 +243,7 @@ const SINGLE_PATCHES = [ { name: "delete-fix-C", marker: MARKER_DELETE_FIX, oldSnippet: OLD_AFTER_DELETE, newSnippet: NEW_AFTER_DELETE }, { name: "status-reply-id-B", marker: MARKER_REPLY_ID, oldSnippet: OLD_REPLY_INSERT, newSnippet: NEW_REPLY_INSERT }, { name: "interactions-context", marker: MARKER_CTX_STATE, oldSnippet: OLD_CTX_STATE, newSnippet: NEW_CTX_STATE }, + { name: "not-found-log", marker: MARKER_NOT_FOUND_LOG, oldSnippet: OLD_FIND_BY_ID, newSnippet: NEW_FIND_BY_ID }, ]; // Patches that need multiple replacements in one pass (applyMultiPatch)