fix: harden home timeline pagination (#31) (thanks @odysseus0)
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `list-timeline` now supports pagination (`--all`, `--max-pages`, `--cursor`) (#30) — thanks @zheli.
|
- `list-timeline` now supports pagination (`--all`, `--max-pages`, `--cursor`) (#30) — thanks @zheli.
|
||||||
|
- `home` command for the "For You" and "Following" home timelines (#31) — thanks @odysseus0.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- macOS cookie extraction now supports Brave keychain storage (#40) — thanks @gakonst.
|
- macOS cookie extraction now supports Brave keychain storage (#40) — thanks @gakonst.
|
||||||
|
|||||||
@@ -163,18 +163,20 @@ export function withHome<TBase extends AbstractConstructor<TwitterClientBase>>(
|
|||||||
return { success: false, error: page.error };
|
return { success: false, error: page.error };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let added = 0;
|
||||||
for (const tweet of page.tweets) {
|
for (const tweet of page.tweets) {
|
||||||
if (seen.has(tweet.id)) {
|
if (seen.has(tweet.id)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
seen.add(tweet.id);
|
seen.add(tweet.id);
|
||||||
tweets.push(tweet);
|
tweets.push(tweet);
|
||||||
|
added += 1;
|
||||||
if (tweets.length >= count) {
|
if (tweets.length >= count) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!page.cursor || page.cursor === cursor || page.tweets.length === 0) {
|
if (!page.cursor || page.cursor === cursor || page.tweets.length === 0 || added === 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
cursor = page.cursor;
|
cursor = page.cursor;
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ describe('TwitterClient home timeline', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockFetch = vi.fn();
|
mockFetch = vi.fn();
|
||||||
global.fetch = mockFetch;
|
global.fetch = mockFetch as unknown as typeof fetch;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getHomeTimeline', () => {
|
describe('getHomeTimeline', () => {
|
||||||
@@ -249,5 +249,96 @@ describe('TwitterClient home timeline', () => {
|
|||||||
expect(result.tweets?.[0].id).toBe('tweet1');
|
expect(result.tweets?.[0].id).toBe('tweet1');
|
||||||
expect(result.tweets?.[1].id).toBe('tweet2');
|
expect(result.tweets?.[1].id).toBe('tweet2');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('stops when a page only returns duplicates', async () => {
|
||||||
|
mockFetch.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
data: {
|
||||||
|
home: {
|
||||||
|
home_timeline_urt: {
|
||||||
|
instructions: [
|
||||||
|
{
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
content: {
|
||||||
|
itemContent: {
|
||||||
|
tweet_results: {
|
||||||
|
result: {
|
||||||
|
rest_id: 'tweet1',
|
||||||
|
legacy: { full_text: 'First tweet', created_at: 'Mon Jan 06 00:00:00 +0000 2025' },
|
||||||
|
core: {
|
||||||
|
user_results: {
|
||||||
|
result: { rest_id: 'u1', legacy: { screen_name: 'user1', name: 'User 1' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: {
|
||||||
|
cursorType: 'Bottom',
|
||||||
|
value: 'cursor123',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
data: {
|
||||||
|
home: {
|
||||||
|
home_timeline_urt: {
|
||||||
|
instructions: [
|
||||||
|
{
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
content: {
|
||||||
|
itemContent: {
|
||||||
|
tweet_results: {
|
||||||
|
result: {
|
||||||
|
rest_id: 'tweet1',
|
||||||
|
legacy: { full_text: 'First tweet', created_at: 'Mon Jan 06 00:00:00 +0000 2025' },
|
||||||
|
core: {
|
||||||
|
user_results: {
|
||||||
|
result: { rest_id: 'u1', legacy: { screen_name: 'user1', name: 'User 1' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: {
|
||||||
|
cursorType: 'Bottom',
|
||||||
|
value: 'cursor456',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const client = new TwitterClient({ cookies: validCookies });
|
||||||
|
const result = await client.getHomeTimeline(5);
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.tweets).toHaveLength(1);
|
||||||
|
expect(mockFetch).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user