From 2660a1a604461412683bafcf6cdfe03982796c66 Mon Sep 17 00:00:00 2001 From: svemagie <869694+svemagie@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:53:20 +0100 Subject: [PATCH] fix(mastodon-api): favourite fails for pre-UTC-normalization timeline items findTimelineItemById's string range query used $gte/$lte on ISO strings, which fails for items stored with timezone offsets (e.g. "+01:00"): the string "2026-03-21T16:33:50+01:00" is lexicographically outside the range ["2026-03-21T15:33:50Z", "2026-03-21T15:33:51Z"]. Replace with a $or that covers both cases: - BSON Date stored (Micropub): direct Date range comparison - String stored with any timezone: $dateFromString parses the offset correctly, $toLong converts to ms-since-epoch, numeric range always matches regardless of timezone format Items received before extractObjectData's UTC normalization (a259c79) was deployed are stored with the original server's timezone offset and now resolve correctly through this fallback. Co-Authored-By: Claude Sonnet 4.6 --- lib/mastodon/routes/statuses.js | 34 ++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/mastodon/routes/statuses.js b/lib/mastodon/routes/statuses.js index 6fec78c..267bd29 100644 --- a/lib/mastodon/routes/statuses.js +++ b/lib/mastodon/routes/statuses.js @@ -749,16 +749,36 @@ async function findTimelineItemById(collection, id) { item = await collection.findOne({ published: new Date(publishedDate) }); if (item) return item; - // Try date-range lookup for timezone-offset stored strings (+01:00 etc.) - // Some AP servers emit non-UTC dates; decodeCursor normalizes to UTC but - // the stored string may differ. Search a ±1 s window using a regex on the - // ms value, or simply try the raw numeric id as a direct uid/url lookup. + // Try ±1 s range lookup for timezone-offset stored strings (+01:00 etc.) + // and BSON Date fields. The UTC-ISO string range query used above fails when + // the stored value has a non-UTC timezone — "2026-03-21T16:33:50+01:00" is + // lexicographically outside ["2026-03-21T15:33:50Z", "2026-03-21T15:33:51Z"]. + // $dateFromString parses any ISO 8601 format (including offsets) to a Date, + // $toLong converts it to ms-since-epoch, and the numeric range always matches. const ms = Number.parseInt(id, 10); if (ms > 0) { - const lo = new Date(ms - 999).toISOString().replace(/\.999Z$/, "Z"); - const hi = new Date(ms + 999).toISOString().replace(/\.999Z$/, "Z"); + const lo = new Date(ms - 999); + const hi = new Date(ms + 999); item = await collection.findOne({ - published: { $gte: lo, $lte: hi }, + $or: [ + // BSON Date stored (Micropub pipeline) — direct Date range comparison + { published: { $gte: lo, $lte: hi } }, + // String stored with any timezone format — parse via $dateFromString + { + $expr: { + $and: [ + { $gte: [ + { $toLong: { $dateFromString: { dateString: "$published", onError: 0, onNull: 0 } } }, + ms - 999, + ] }, + { $lte: [ + { $toLong: { $dateFromString: { dateString: "$published", onError: 0, onNull: 0 } } }, + ms + 999, + ] }, + ], + }, + }, + ], }); if (item) return item; }