diff --git a/scripts/patch-ap-mastodon-misc.mjs b/scripts/patch-ap-mastodon-misc.mjs index 4f8de1ab..3d87590a 100644 --- a/scripts/patch-ap-mastodon-misc.mjs +++ b/scripts/patch-ap-mastodon-misc.mjs @@ -152,4 +152,71 @@ for (const f of ACCOUNTS_CANDIDATES) { } if (!hashDone) console.log(`[postinstall] ${SCRIPT}: ap-accounts-id-hash — no target file found or no changes`); +// ── patch-ap-search-url-resolve: resolve posts by URL when resolve=true ──────── +// Phanpy calls GET /api/v2/search?q=&resolve=true&type=statuses before +// liking/boosting a remote post. The search endpoint only did content-text regex +// search — never found posts by URL → statuses:[] → "Failed to load post" error. +// Fix: when resolve=true and query is a URL, look up ap_timeline by uid/url first. +// Two targeted replacements: +// A) inject URL-resolve block before the content search (unique anchor: "results.statuses = items.map") +// B) change assignment to push so existing array stays intact + +const SEARCH_MARKER = "// [patch] ap-search-url-resolve"; +const SEARCH_CANDIDATES = apPath("lib/mastodon/routes/search.js"); + +// Replacement A: inject URL-resolve block + switch assignment to push +// Anchor chosen because it is unique in search.js. +const SEARCH_OLD_A = " results.statuses = items.map((item) =>"; +const SEARCH_NEW_A = [ + " // URL resolve: find post by AP URL before content search. " + SEARCH_MARKER, + " if (resolve && query.startsWith(\"http\")) { " + SEARCH_MARKER, + " const resolvedItem = await collections.ap_timeline.findOne({ " + SEARCH_MARKER, + " isContext: { $ne: true }, " + SEARCH_MARKER, + " $or: [{ uid: query }, { url: query }], " + SEARCH_MARKER, + " }); " + SEARCH_MARKER, + " if (resolvedItem) { " + SEARCH_MARKER, + " results.statuses.push(serializeStatus(resolvedItem, { " + SEARCH_MARKER, + " baseUrl, favouritedIds: new Set(), rebloggedIds: new Set(), " + SEARCH_MARKER, + " bookmarkedIds: new Set(), pinnedIds: new Set(), " + SEARCH_MARKER, + " })); " + SEARCH_MARKER, + " } " + SEARCH_MARKER, + " } " + SEARCH_MARKER, + " results.statuses.push(...items.map((item) =>", +].join("\n"); + +// Replacement B: close the push call (was ` );` → ` ));`) +// Unique because it is immediately followed by the closing brace + Hashtag comment. +const SEARCH_OLD_B = " }),\n );\n }\n\n // ─── Hashtag"; +const SEARCH_NEW_B = " }),\n ));\n }\n\n // ─── Hashtag"; + +let searchDone = false; +for (const f of SEARCH_CANDIDATES) { + if (!(await fileExists(f))) continue; + const src = await readFile(f, "utf8"); + if (src.includes(SEARCH_MARKER)) { + console.log(`[postinstall] ${SCRIPT}: ap-search-url-resolve already applied in ${f}`); + searchDone = true; break; + } + let updated = src; + let changed = false; + if (updated.includes(SEARCH_OLD_A)) { + updated = updated.replace(SEARCH_OLD_A, SEARCH_NEW_A); + changed = true; + } else { + console.warn(`[postinstall] ${SCRIPT}: ap-search-url-resolve A — anchor not found in ${f}`); + } + if (updated.includes(SEARCH_OLD_B)) { + updated = updated.replace(SEARCH_OLD_B, SEARCH_NEW_B); + } else { + console.warn(`[postinstall] ${SCRIPT}: ap-search-url-resolve B — closing anchor not found in ${f}`); + changed = false; // both must succeed + } + if (changed && updated !== src) { + await writeFile(f, updated, "utf8"); + console.log(`[postinstall] ${SCRIPT}: applied ap-search-url-resolve to ${f}`); + total++; searchDone = true; break; + } +} +if (!searchDone) console.log(`[postinstall] ${SCRIPT}: ap-search-url-resolve — no target file found or no changes`); + console.log(`[postinstall] ${SCRIPT}: done (${total} patch(es) applied)`);