fix: make interaction retrieval robust against three failure modes
Deploy Indiekit Server / deploy (push) Successful in 1m24s
Deploy Indiekit Server / deploy (push) Successful in 1m24s
- patch-ap-interactions-cleanup-preserve: daily timeline cleanup was silently deleting liked/bookmarked posts from ap_timeline along with their ap_interactions records; exempts interacted items from cleanup - patch-ap-interactions-accounts-uid: account statuses route added ix.objectUrl to interaction Sets instead of item.uid, causing wrong favourited/reblogged state when url ≠ uid - patch-ap-interactions-context-state: thread context (ancestors/ descendants) used empty interaction Sets; now batch-loads real state Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -178,6 +178,9 @@ npm install git+https://gitea.giersig.eu/svemagie/indiekit-endpoint-activitypub
|
||||
| AP lookup returns 302 / auth redirect | nginx not forwarding `Host`/`X-Forwarded-Proto` — see `patch-ap-federation-bridge-base-url` |
|
||||
| `findTimelineItemById` returns null | Item not yet in `ap_timeline` (build not finished) or TZ-offset date mismatch — `$dateFromString` range query should catch offsets |
|
||||
| Favourite/reblog hangs in Mastodon client | `resolveAuthor` timeout — `Promise.race` 5 s cap should prevent this |
|
||||
| Liked/bookmarked posts disappear from Favourites/Bookmarks over time | Daily timeline cleanup was deleting them — fixed by `patch-ap-interactions-cleanup-preserve` (exempts items present in `ap_interactions`) |
|
||||
| Favourited/reblogged state wrong on account statuses timeline | `accounts.js` added `ix.objectUrl` to the Sets instead of `item.uid` — fixed by `patch-ap-interactions-accounts-uid` |
|
||||
| Liked posts show as not-liked in thread context (ancestors/descendants) | Context endpoint used empty interaction Sets — fixed by `patch-ap-interactions-context-state` |
|
||||
| "Empty reply from server" on webmention poller | Poller routing through nginx (returns 444 for wrong Host) — must use `INDIEKIT_DIRECT_URL` |
|
||||
| HTTP Signature 401 errors on all inbound activities | nginx forwarding wrong `Host` header — fixed by `patch-ap-signature-host-header` (overrides to `blog.giersig.eu`) |
|
||||
| HTTP Signature verify errors flooding logs for deleted/migrated actors | Expected noise — `patch-ap-inbox-delivery-debug` suppresses to `fatal`; real errors surface at `error` level |
|
||||
|
||||
@@ -218,6 +218,34 @@ non-followers not stored in `ap_timeline`.
|
||||
Also added else-if branch in `handleCreate` to store replies to own posts in `ap_timeline` even
|
||||
when sender is not in `ap_following`.
|
||||
|
||||
### `patch-ap-interactions-cleanup-preserve` *(2026-04-10)*
|
||||
**File:** `lib/timeline-cleanup.js` → `cleanupTimeline()`
|
||||
**Problem:** The daily cleanup deleted remote posts from `ap_timeline` (beyond `retentionLimit`) AND
|
||||
deleted their corresponding `ap_interactions` entries. Any post the user had liked, bookmarked, or
|
||||
boosted would silently disappear from `GET /api/v1/favourites` and `GET /api/v1/bookmarks` after the
|
||||
next cleanup run. This was intermittent — only triggered once per day.
|
||||
**Fix:** Before deleting, call `ap_interactions.distinct("objectUrl")` to get all URLs the user has
|
||||
interacted with. Filter those UIDs out of `toDelete` so they are never removed from `ap_timeline`
|
||||
(and their `ap_interactions` entries remain intact).
|
||||
|
||||
### `patch-ap-interactions-accounts-uid` *(2026-04-10)*
|
||||
**File:** `lib/mastodon/routes/accounts.js` → account statuses route
|
||||
**Problem:** When loading interaction state for `GET /api/v1/accounts/:id/statuses`, the code built
|
||||
`lookupUrls` from both `item.uid` and `item.url`, found matching interactions, but then added
|
||||
`ix.objectUrl` directly to `favouritedIds`/`rebloggedIds`/`bookmarkedIds`. `serializeStatus` checks
|
||||
`favouritedIds.has(item.uid)` — so when `ix.objectUrl === item.url` and `item.url !== item.uid` (common
|
||||
for remote posts), the interaction state showed wrong (not-liked even though liked).
|
||||
**Fix:** Build a `urlToUid` map before the lookup and resolve `ix.objectUrl` to the canonical `item.uid`
|
||||
before adding to the Sets (same approach as `loadInteractionState` in `timelines.js`).
|
||||
|
||||
### `patch-ap-interactions-context-state` *(2026-04-10)*
|
||||
**File:** `lib/mastodon/routes/statuses.js` → `GET /api/v1/statuses/:id/context`
|
||||
**Problem:** Thread ancestors and descendants were serialized with `emptyInteractions` (all empty Sets).
|
||||
Any post the user had liked or bookmarked showed as not-liked/not-bookmarked when viewing thread context.
|
||||
**Fix:** Replace `emptyInteractions` with a batch `ap_interactions` lookup for all context items
|
||||
(ancestors + descendants), using a `urlToUid` map to resolve to canonical UIDs. One MongoDB query
|
||||
for the entire thread instead of N+1 calls.
|
||||
|
||||
### `patch-ap-status-reply-id` *(updated 2026-04-09)*
|
||||
**Files:** `lib/mastodon/entities/status.js` + `lib/mastodon/routes/statuses.js`
|
||||
**Original problem (2026-04-01):** `in_reply_to_id` was tautological `item.inReplyTo ? null : null`.
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"preinstall": "node scripts/setup-gitea-url-rewrite.mjs",
|
||||
"postinstall": "xattr -w com.apple.fileprovider.ignore#P 1 node_modules 2>/dev/null || true && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-github-contributions-log.mjs && node scripts/patch-store-github-error-message.mjs && node scripts/patch-store-github-update-fallback.mjs && node scripts/patch-store-github-gitea-methods.mjs && node scripts/patch-store-github-content-type.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-syndicate-normalize-syndication-array.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-micropub-session-token.mjs && node scripts/patch-indiekit-endpoint-urls-protocol.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-reset-stale.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node scripts/patch-bluesky-syndicator-media-type-guard.mjs && node scripts/patch-bluesky-og-own-post-title.mjs && node scripts/patch-ap-skip-draft-syndication.mjs && node scripts/patch-ap-og-image.mjs && node scripts/patch-ap-webfinger-before-auth.mjs && node scripts/patch-ap-federation-bridge-base-url.mjs && node scripts/patch-ap-signature-host-header.mjs && node scripts/patch-ap-compose-default-checked.mjs && node scripts/patch-ap-mastodon-reply-threading.mjs && node scripts/patch-ap-mastodon-status-id.mjs && node scripts/patch-ap-interactions-send-guard.mjs && node scripts/patch-ap-syndicate-dedup.mjs && node scripts/patch-ap-mastodon-delete-fix.mjs && node scripts/patch-ap-status-reply-id.mjs && node scripts/patch-ap-inbox-publication-url.mjs && node scripts/patch-bluesky-syndicator-delete.mjs && node scripts/patch-micropub-delete-propagation.mjs && node scripts/patch-ap-inbox-delivery-debug.mjs && node scripts/patch-ap-repost-announce-fix.mjs && node scripts/patch-inbox-ignore-view-activity.mjs && node scripts/patch-inbox-skip-view-activity-parse.mjs && node scripts/patch-ap-oauth-token-expiry-fix.mjs && node scripts/patch-ap-startup-gate-bypass.mjs && node scripts/patch-micropub-category-from-posts.mjs && node scripts/patch-tag-input-autocomplete.mjs",
|
||||
"serve": "export NODE_ENV=${NODE_ENV:-production} INDIEKIT_DEBUG=${INDIEKIT_DEBUG:-0} && node scripts/preflight-production-security.mjs && node scripts/preflight-mongo-connection.mjs && node scripts/preflight-activitypub-rsa-key.mjs && node scripts/preflight-activitypub-profile-urls.mjs && node scripts/preflight-startup-gate.mjs && node scripts/patch-ap-startup-gate-bypass.mjs && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-github-contributions-log.mjs && node scripts/patch-store-github-error-message.mjs && node scripts/patch-store-github-update-fallback.mjs && node scripts/patch-store-github-gitea-methods.mjs && node scripts/patch-store-github-content-type.mjs && node scripts/patch-microsub-reader-ap-dispatch.mjs && node scripts/patch-microsub-compose-draft-guard.mjs && node scripts/patch-microsub-no-bookmark-autofollow.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-syndicate-normalize-syndication-array.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-micropub-session-token.mjs && node scripts/patch-indiekit-endpoint-urls-protocol.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-reset-stale.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node scripts/patch-bluesky-syndicator-media-type-guard.mjs && node scripts/patch-bluesky-og-own-post-title.mjs && node scripts/patch-ap-skip-draft-syndication.mjs && node scripts/patch-ap-og-image.mjs && node scripts/patch-ap-webfinger-before-auth.mjs && node scripts/patch-ap-federation-bridge-base-url.mjs && node scripts/patch-ap-signature-host-header.mjs && node scripts/patch-ap-compose-default-checked.mjs && node scripts/patch-ap-mastodon-reply-threading.mjs && node scripts/patch-ap-mastodon-status-id.mjs && node scripts/patch-ap-interactions-send-guard.mjs && node scripts/patch-ap-syndicate-dedup.mjs && node scripts/patch-ap-mastodon-delete-fix.mjs && node scripts/patch-ap-status-reply-id.mjs && node scripts/patch-ap-inbox-publication-url.mjs && node scripts/patch-bluesky-syndicator-delete.mjs && node scripts/patch-micropub-delete-propagation.mjs && node scripts/patch-ap-inbox-delivery-debug.mjs && node scripts/patch-ap-repost-announce-fix.mjs && node scripts/patch-inbox-ignore-view-activity.mjs && node scripts/patch-inbox-skip-view-activity-parse.mjs && node scripts/patch-ap-oauth-token-expiry-fix.mjs && node scripts/patch-micropub-category-from-posts.mjs && node scripts/patch-tag-input-autocomplete.mjs && node --require ./metrics-shim.cjs node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs",
|
||||
"postinstall": "xattr -w com.apple.fileprovider.ignore#P 1 node_modules 2>/dev/null || true && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-github-contributions-log.mjs && node scripts/patch-store-github-error-message.mjs && node scripts/patch-store-github-update-fallback.mjs && node scripts/patch-store-github-gitea-methods.mjs && node scripts/patch-store-github-content-type.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-syndicate-normalize-syndication-array.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-micropub-session-token.mjs && node scripts/patch-indiekit-endpoint-urls-protocol.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-reset-stale.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node scripts/patch-bluesky-syndicator-media-type-guard.mjs && node scripts/patch-bluesky-og-own-post-title.mjs && node scripts/patch-ap-skip-draft-syndication.mjs && node scripts/patch-ap-og-image.mjs && node scripts/patch-ap-webfinger-before-auth.mjs && node scripts/patch-ap-federation-bridge-base-url.mjs && node scripts/patch-ap-signature-host-header.mjs && node scripts/patch-ap-compose-default-checked.mjs && node scripts/patch-ap-mastodon-reply-threading.mjs && node scripts/patch-ap-mastodon-status-id.mjs && node scripts/patch-ap-interactions-send-guard.mjs && node scripts/patch-ap-interactions-cleanup-preserve.mjs && node scripts/patch-ap-interactions-accounts-uid.mjs && node scripts/patch-ap-interactions-context-state.mjs && node scripts/patch-ap-syndicate-dedup.mjs && node scripts/patch-ap-mastodon-delete-fix.mjs && node scripts/patch-ap-status-reply-id.mjs && node scripts/patch-ap-inbox-publication-url.mjs && node scripts/patch-bluesky-syndicator-delete.mjs && node scripts/patch-micropub-delete-propagation.mjs && node scripts/patch-ap-inbox-delivery-debug.mjs && node scripts/patch-ap-repost-announce-fix.mjs && node scripts/patch-inbox-ignore-view-activity.mjs && node scripts/patch-inbox-skip-view-activity-parse.mjs && node scripts/patch-ap-oauth-token-expiry-fix.mjs && node scripts/patch-ap-startup-gate-bypass.mjs && node scripts/patch-micropub-category-from-posts.mjs && node scripts/patch-tag-input-autocomplete.mjs",
|
||||
"serve": "export NODE_ENV=${NODE_ENV:-production} INDIEKIT_DEBUG=${INDIEKIT_DEBUG:-0} && node scripts/preflight-production-security.mjs && node scripts/preflight-mongo-connection.mjs && node scripts/preflight-activitypub-rsa-key.mjs && node scripts/preflight-activitypub-profile-urls.mjs && node scripts/preflight-startup-gate.mjs && node scripts/patch-ap-startup-gate-bypass.mjs && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-github-contributions-log.mjs && node scripts/patch-store-github-error-message.mjs && node scripts/patch-store-github-update-fallback.mjs && node scripts/patch-store-github-gitea-methods.mjs && node scripts/patch-store-github-content-type.mjs && node scripts/patch-microsub-reader-ap-dispatch.mjs && node scripts/patch-microsub-compose-draft-guard.mjs && node scripts/patch-microsub-no-bookmark-autofollow.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-syndicate-normalize-syndication-array.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-micropub-session-token.mjs && node scripts/patch-indiekit-endpoint-urls-protocol.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-reset-stale.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node scripts/patch-bluesky-syndicator-media-type-guard.mjs && node scripts/patch-bluesky-og-own-post-title.mjs && node scripts/patch-ap-skip-draft-syndication.mjs && node scripts/patch-ap-og-image.mjs && node scripts/patch-ap-webfinger-before-auth.mjs && node scripts/patch-ap-federation-bridge-base-url.mjs && node scripts/patch-ap-signature-host-header.mjs && node scripts/patch-ap-compose-default-checked.mjs && node scripts/patch-ap-mastodon-reply-threading.mjs && node scripts/patch-ap-mastodon-status-id.mjs && node scripts/patch-ap-interactions-send-guard.mjs && node scripts/patch-ap-interactions-cleanup-preserve.mjs && node scripts/patch-ap-interactions-accounts-uid.mjs && node scripts/patch-ap-interactions-context-state.mjs && node scripts/patch-ap-syndicate-dedup.mjs && node scripts/patch-ap-mastodon-delete-fix.mjs && node scripts/patch-ap-status-reply-id.mjs && node scripts/patch-ap-inbox-publication-url.mjs && node scripts/patch-bluesky-syndicator-delete.mjs && node scripts/patch-micropub-delete-propagation.mjs && node scripts/patch-ap-inbox-delivery-debug.mjs && node scripts/patch-ap-repost-announce-fix.mjs && node scripts/patch-inbox-ignore-view-activity.mjs && node scripts/patch-inbox-skip-view-activity-parse.mjs && node scripts/patch-ap-oauth-token-expiry-fix.mjs && node scripts/patch-micropub-category-from-posts.mjs && node scripts/patch-tag-input-autocomplete.mjs && node --require ./metrics-shim.cjs node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Patch: fix interaction state in GET /api/v1/accounts/:id/statuses.
|
||||
*
|
||||
* Root cause:
|
||||
* The account statuses route loads interaction state (liked/boosted/bookmarked)
|
||||
* for the returned timeline items. It builds the lookup URL list correctly
|
||||
* (both item.uid and item.url), but then adds ix.objectUrl directly to the
|
||||
* favouritedIds/rebloggedIds/bookmarkedIds Sets. serializeStatus() checks
|
||||
* favouritedIds.has(item.uid) — so when ix.objectUrl === item.url and
|
||||
* item.url !== item.uid, the check fails and the post shows as not-liked.
|
||||
*
|
||||
* Fix:
|
||||
* Build a urlToUid map (same approach as loadInteractionState in timelines.js)
|
||||
* and resolve ix.objectUrl to the canonical uid before adding to the Sets.
|
||||
*/
|
||||
|
||||
import { access, readFile, writeFile } from "node:fs/promises";
|
||||
|
||||
const MARKER = "// [patch] ap-interactions-accounts-uid";
|
||||
|
||||
const candidates = [
|
||||
"node_modules/@rmdes/indiekit-endpoint-activitypub/lib/mastodon/routes/accounts.js",
|
||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/mastodon/routes/accounts.js",
|
||||
];
|
||||
|
||||
const OLD_SNIPPET = ` const lookupUrls = items.flatMap((i) => [i.uid, i.url].filter(Boolean));
|
||||
if (lookupUrls.length > 0) {
|
||||
const interactions = await collections.ap_interactions
|
||||
.find({ objectUrl: { $in: lookupUrls } })
|
||||
.toArray();
|
||||
for (const ix of interactions) {
|
||||
if (ix.type === "like") favouritedIds.add(ix.objectUrl);
|
||||
else if (ix.type === "boost") rebloggedIds.add(ix.objectUrl);
|
||||
else if (ix.type === "bookmark") bookmarkedIds.add(ix.objectUrl);
|
||||
}
|
||||
}`;
|
||||
|
||||
const NEW_SNIPPET = ` const urlToUid = new Map(); ${MARKER}
|
||||
for (const i of items) {
|
||||
if (i.uid) {
|
||||
urlToUid.set(i.uid, i.uid);
|
||||
if (i.url && i.url !== i.uid) urlToUid.set(i.url, i.uid);
|
||||
}
|
||||
}
|
||||
const lookupUrls = [...urlToUid.keys()];
|
||||
if (lookupUrls.length > 0) {
|
||||
const interactions = await collections.ap_interactions
|
||||
.find({ objectUrl: { $in: lookupUrls } })
|
||||
.toArray();
|
||||
for (const ix of interactions) {
|
||||
const uid = urlToUid.get(ix.objectUrl) || ix.objectUrl;
|
||||
if (ix.type === "like") favouritedIds.add(uid);
|
||||
else if (ix.type === "boost") rebloggedIds.add(uid);
|
||||
else if (ix.type === "bookmark") bookmarkedIds.add(uid);
|
||||
}
|
||||
}`;
|
||||
|
||||
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-interactions-accounts-uid: already applied to ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!source.includes(OLD_SNIPPET)) {
|
||||
console.warn(`[postinstall] patch-ap-interactions-accounts-uid: snippet not found in ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const updated = source.replace(OLD_SNIPPET, NEW_SNIPPET);
|
||||
await writeFile(filePath, updated, "utf8");
|
||||
patched += 1;
|
||||
console.log(`[postinstall] Applied patch-ap-interactions-accounts-uid to ${filePath}`);
|
||||
}
|
||||
|
||||
if (checked === 0) {
|
||||
console.log("[postinstall] patch-ap-interactions-accounts-uid: no target files found");
|
||||
} else if (patched === 0) {
|
||||
console.log("[postinstall] patch-ap-interactions-accounts-uid: already up to date");
|
||||
} else {
|
||||
console.log(`[postinstall] patch-ap-interactions-accounts-uid: patched ${patched} file(s)`);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Patch: load real interaction state for thread context ancestors/descendants.
|
||||
*
|
||||
* Root cause:
|
||||
* GET /api/v1/statuses/:id/context serializes all ancestors and descendants
|
||||
* using emptyInteractions (all empty Sets). Any post in the thread that the
|
||||
* user has liked/bookmarked/boosted shows as not-liked/not-bookmarked/not-boosted.
|
||||
*
|
||||
* Fix:
|
||||
* Replace emptyInteractions with a batch lookup against ap_interactions for
|
||||
* all items in the thread (same approach as loadInteractionState in timelines.js).
|
||||
*/
|
||||
|
||||
import { access, readFile, writeFile } from "node:fs/promises";
|
||||
|
||||
const MARKER = "// [patch] ap-interactions-context-state";
|
||||
|
||||
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",
|
||||
];
|
||||
|
||||
const OLD_SNIPPET = ` // Serialize all items
|
||||
const emptyInteractions = {
|
||||
favouritedIds: new Set(),
|
||||
rebloggedIds: new Set(),
|
||||
bookmarkedIds: new Set(),
|
||||
pinnedIds: new Set(),
|
||||
};
|
||||
|
||||
const allItems = [...ancestors, ...descendants];
|
||||
const { replyIdMap, replyAccountIdMap } = await resolveReplyIds(collections.ap_timeline, allItems);
|
||||
const serializeOpts = { baseUrl, ...emptyInteractions, replyIdMap, replyAccountIdMap };`;
|
||||
|
||||
const NEW_SNIPPET = ` // Serialize all items
|
||||
const allItems = [...ancestors, ...descendants];
|
||||
const { replyIdMap, replyAccountIdMap } = await resolveReplyIds(collections.ap_timeline, allItems);
|
||||
|
||||
// Load real interaction state for thread context ${MARKER}
|
||||
const ctxFavouritedIds = new Set();
|
||||
const ctxRebloggedIds = new Set();
|
||||
const ctxBookmarkedIds = new Set();
|
||||
if (allItems.length > 0 && collections.ap_interactions) {
|
||||
const ctxUrlToUid = new Map();
|
||||
for (const ci of allItems) {
|
||||
if (ci.uid) { ctxUrlToUid.set(ci.uid, ci.uid); }
|
||||
if (ci.url && ci.url !== ci.uid) { ctxUrlToUid.set(ci.url, ci.uid || ci.url); }
|
||||
}
|
||||
const ctxLookupUrls = [...ctxUrlToUid.keys()];
|
||||
if (ctxLookupUrls.length > 0) {
|
||||
const ctxInteractions = await collections.ap_interactions
|
||||
.find({ objectUrl: { $in: ctxLookupUrls } })
|
||||
.toArray();
|
||||
for (const ci of ctxInteractions) {
|
||||
const uid = ctxUrlToUid.get(ci.objectUrl) || ci.objectUrl;
|
||||
if (ci.type === "like") ctxFavouritedIds.add(uid);
|
||||
else if (ci.type === "boost") ctxRebloggedIds.add(uid);
|
||||
else if (ci.type === "bookmark") ctxBookmarkedIds.add(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
const serializeOpts = { baseUrl, favouritedIds: ctxFavouritedIds, rebloggedIds: ctxRebloggedIds, bookmarkedIds: ctxBookmarkedIds, pinnedIds: new Set(), replyIdMap, replyAccountIdMap };`;
|
||||
|
||||
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-interactions-context-state: already applied to ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!source.includes(OLD_SNIPPET)) {
|
||||
console.warn(`[postinstall] patch-ap-interactions-context-state: snippet not found in ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const updated = source.replace(OLD_SNIPPET, NEW_SNIPPET);
|
||||
await writeFile(filePath, updated, "utf8");
|
||||
patched += 1;
|
||||
console.log(`[postinstall] Applied patch-ap-interactions-context-state to ${filePath}`);
|
||||
}
|
||||
|
||||
if (checked === 0) {
|
||||
console.log("[postinstall] patch-ap-interactions-context-state: no target files found");
|
||||
} else if (patched === 0) {
|
||||
console.log("[postinstall] patch-ap-interactions-context-state: already up to date");
|
||||
} else {
|
||||
console.log(`[postinstall] patch-ap-interactions-context-state: patched ${patched} file(s)`);
|
||||
}
|
||||
Reference in New Issue
Block a user