From 010439998e8eb9008e1499e51607cf07ddf6cbdb Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sun, 26 Oct 2025 11:34:36 -0400 Subject: [PATCH] Sort recent feed by original posting time --- js/feedEngine/sorters.js | 32 +++++++++++++++++++++++++-- tests/feed-engine.test.mjs | 44 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/js/feedEngine/sorters.js b/js/feedEngine/sorters.js index 17418d66..43b5303a 100644 --- a/js/feedEngine/sorters.js +++ b/js/feedEngine/sorters.js @@ -9,6 +9,34 @@ export function createChronologicalSorter({ direction = "desc" } = {}) { } const copy = [...items]; + const getTimestamp = (entry) => { + if (!entry || typeof entry !== "object") { + return Number.NEGATIVE_INFINITY; + } + + const video = entry.video; + if (!video || typeof video !== "object") { + return Number.NEGATIVE_INFINITY; + } + + const rootCreatedAt = Number(video.rootCreatedAt); + if (Number.isFinite(rootCreatedAt)) { + return Math.floor(rootCreatedAt); + } + + const nip71CreatedAt = Number(video?.nip71Source?.created_at); + if (Number.isFinite(nip71CreatedAt)) { + return Math.floor(nip71CreatedAt); + } + + const createdAt = Number(video.created_at); + if (Number.isFinite(createdAt)) { + return Math.floor(createdAt); + } + + return Number.NEGATIVE_INFINITY; + }; + copy.sort((a, b) => { const aMuted = a?.metadata?.moderation?.trustedMuted === true || @@ -21,8 +49,8 @@ export function createChronologicalSorter({ direction = "desc" } = {}) { return aMuted ? 1 : -1; } - const aTs = Number(a?.video?.created_at); - const bTs = Number(b?.video?.created_at); + const aTs = getTimestamp(a); + const bTs = getTimestamp(b); const normalizedATs = Number.isFinite(aTs) ? aTs : Number.NEGATIVE_INFINITY; const normalizedBTs = Number.isFinite(bTs) ? bTs : Number.NEGATIVE_INFINITY; diff --git a/tests/feed-engine.test.mjs b/tests/feed-engine.test.mjs index f69efe1a..9087a1c4 100644 --- a/tests/feed-engine.test.mjs +++ b/tests/feed-engine.test.mjs @@ -70,6 +70,49 @@ async function testDedupeOrdering() { assert.equal(dedupeReasons[0].rootId, "rootA"); } +async function testRootCreatedAtSorting() { + const engine = createFeedEngine(); + const feedName = "root-created-sort"; + + const originalVideo = { + id: "video-original", + videoRootId: "root-1", + created_at: 100, + rootCreatedAt: 100, + }; + + const updatedVideo = { + id: "video-updated", + videoRootId: "root-1", + created_at: 400, + rootCreatedAt: 100, + }; + + const newerRootVideo = { + id: "video-newer-root", + videoRootId: "root-2", + created_at: 300, + rootCreatedAt: 300, + }; + + engine.registerFeed(feedName, { + source: async () => [ + { video: originalVideo }, + { video: updatedVideo }, + { video: newerRootVideo }, + ], + stages: [createDedupeByRootStage()], + sorter: createChronologicalSorter(), + }); + + const result = await engine.runFeed(feedName); + assert.deepEqual( + result.videos.map((video) => video.id), + ["video-newer-root", "video-updated"], + "videos should be sorted by original posting time, not last edit", + ); +} + async function testBlacklistFiltering() { const engine = createFeedEngine(); const feedName = "blacklist"; @@ -240,6 +283,7 @@ async function testTrustedMuteDownrank() { } await testDedupeOrdering(); +await testRootCreatedAtSorting(); await testBlacklistFiltering(); await testWatchHistoryHookIsolation(); await testBlacklistOrderingWithRuntimeChanges();