diff --git a/CHANGELOG.md b/CHANGELOG.md index f12d8e7..1826e55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Long-form article tweets now render rich Draft.js content blocks/entities (#36) — thanks @crcatala. - `news`/`trending` command for Explore tabs with AI-curated headlines (#39) — thanks @aavetis. - `search` now supports pagination (`--all`, `--max-pages`, `--cursor`) (#42) — thanks @pjtf93. +- `likes` now supports pagination (`--all`, `--max-pages`, `--cursor`) (#44) — thanks @jsholmes. ### Changed - Library typing: `SearchResult` is now a discriminated union (so `error` only exists when `success: false`). diff --git a/src/lib/twitter-client-lists.ts b/src/lib/twitter-client-lists.ts index d1f1e33..877d5a3 100644 --- a/src/lib/twitter-client-lists.ts +++ b/src/lib/twitter-client-lists.ts @@ -449,7 +449,7 @@ export function withLists>( return { success: false as const, error: firstAttempt.error }; }; - const unlimited = !Number.isFinite(limit); + const unlimited = limit === Number.POSITIVE_INFINITY; while (unlimited || tweets.length < limit) { const pageCount = unlimited ? pageSize : Math.min(pageSize, limit - tweets.length); const page = await fetchWithRefresh(pageCount, cursor); diff --git a/src/lib/twitter-client-search.ts b/src/lib/twitter-client-search.ts index 5c87d17..7848d69 100644 --- a/src/lib/twitter-client-search.ts +++ b/src/lib/twitter-client-search.ts @@ -209,7 +209,7 @@ export function withSearch> return { success: false as const, error: firstAttempt.error }; }; - const unlimited = !Number.isFinite(limit); + const unlimited = limit === Number.POSITIVE_INFINITY; while (unlimited || tweets.length < limit) { const pageCount = unlimited ? pageSize : Math.min(pageSize, limit - tweets.length); const page = await fetchWithRefresh(pageCount, cursor); diff --git a/src/lib/twitter-client-timelines.ts b/src/lib/twitter-client-timelines.ts index 557f6cd..9494312 100644 --- a/src/lib/twitter-client-timelines.ts +++ b/src/lib/twitter-client-timelines.ts @@ -204,7 +204,7 @@ export function withTimelines { expect(secondVars.cursor).toBe('cursor-1'); }); + it('respects maxPages when fetching all likes', async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => ({ + data: { + user: { + result: { + timeline: { + timeline: { + instructions: [ + { + entries: [ + { + content: { + itemContent: { + tweet_results: { + result: { + rest_id: '1', + legacy: { + full_text: 'liked page 1', + created_at: '2024-01-01T00:00:00Z', + reply_count: 0, + retweet_count: 0, + favorite_count: 0, + conversation_id_str: '1', + }, + core: { + user_results: { + result: { + rest_id: 'u1', + legacy: { screen_name: 'root', name: 'Root' }, + }, + }, + }, + }, + }, + }, + }, + }, + { + content: { + cursorType: 'Bottom', + value: 'cursor-1', + }, + }, + ], + }, + ], + }, + }, + }, + }, + }, + }), + }); + + const client = new TwitterClient({ cookies: validCookies }); + const clientPrivate = client as unknown as TwitterClientPrivate; + clientPrivate.getCurrentUser = async () => ({ + success: true, + user: { id: '42', username: 'tester', name: 'Tester' }, + }); + clientPrivate.getLikesQueryIds = async () => ['test']; + + const result = await client.getAllLikes({ maxPages: 1 }); + + expect(result.success).toBe(true); + expect(result.tweets?.map((tweet) => tweet.id)).toEqual(['1']); + expect(result.nextCursor).toBe('cursor-1'); + expect(mockFetch).toHaveBeenCalledTimes(1); + }); + it('returns an error when current user is unavailable', async () => { const client = new TwitterClient({ cookies: validCookies }); const clientPrivate = client as unknown as TwitterClientPrivate;