fix: guard pagination limits (#44) (thanks @jsholmes)
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
- Long-form article tweets now render rich Draft.js content blocks/entities (#36) — thanks @crcatala.
|
- 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.
|
- `news`/`trending` command for Explore tabs with AI-curated headlines (#39) — thanks @aavetis.
|
||||||
- `search` now supports pagination (`--all`, `--max-pages`, `--cursor`) (#42) — thanks @pjtf93.
|
- `search` now supports pagination (`--all`, `--max-pages`, `--cursor`) (#42) — thanks @pjtf93.
|
||||||
|
- `likes` now supports pagination (`--all`, `--max-pages`, `--cursor`) (#44) — thanks @jsholmes.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Library typing: `SearchResult` is now a discriminated union (so `error` only exists when `success: false`).
|
- Library typing: `SearchResult` is now a discriminated union (so `error` only exists when `success: false`).
|
||||||
|
|||||||
@@ -449,7 +449,7 @@ export function withLists<TBase extends AbstractConstructor<TwitterClientBase>>(
|
|||||||
return { success: false as const, error: firstAttempt.error };
|
return { success: false as const, error: firstAttempt.error };
|
||||||
};
|
};
|
||||||
|
|
||||||
const unlimited = !Number.isFinite(limit);
|
const unlimited = limit === Number.POSITIVE_INFINITY;
|
||||||
while (unlimited || tweets.length < limit) {
|
while (unlimited || tweets.length < limit) {
|
||||||
const pageCount = unlimited ? pageSize : Math.min(pageSize, limit - tweets.length);
|
const pageCount = unlimited ? pageSize : Math.min(pageSize, limit - tweets.length);
|
||||||
const page = await fetchWithRefresh(pageCount, cursor);
|
const page = await fetchWithRefresh(pageCount, cursor);
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ export function withSearch<TBase extends AbstractConstructor<TwitterClientBase>>
|
|||||||
return { success: false as const, error: firstAttempt.error };
|
return { success: false as const, error: firstAttempt.error };
|
||||||
};
|
};
|
||||||
|
|
||||||
const unlimited = !Number.isFinite(limit);
|
const unlimited = limit === Number.POSITIVE_INFINITY;
|
||||||
while (unlimited || tweets.length < limit) {
|
while (unlimited || tweets.length < limit) {
|
||||||
const pageCount = unlimited ? pageSize : Math.min(pageSize, limit - tweets.length);
|
const pageCount = unlimited ? pageSize : Math.min(pageSize, limit - tweets.length);
|
||||||
const page = await fetchWithRefresh(pageCount, cursor);
|
const page = await fetchWithRefresh(pageCount, cursor);
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ export function withTimelines<TBase extends AbstractConstructor<TwitterClientBas
|
|||||||
return { success: false as const, error: firstAttempt.error };
|
return { success: false as const, error: firstAttempt.error };
|
||||||
};
|
};
|
||||||
|
|
||||||
const unlimited = !Number.isFinite(limit);
|
const unlimited = limit === Number.POSITIVE_INFINITY;
|
||||||
while (unlimited || tweets.length < limit) {
|
while (unlimited || tweets.length < limit) {
|
||||||
const pageCount = unlimited ? pageSize : Math.min(pageSize, limit - tweets.length);
|
const pageCount = unlimited ? pageSize : Math.min(pageSize, limit - tweets.length);
|
||||||
const page = await fetchWithRefresh(pageCount, cursor);
|
const page = await fetchWithRefresh(pageCount, cursor);
|
||||||
@@ -382,7 +382,7 @@ export function withTimelines<TBase extends AbstractConstructor<TwitterClientBas
|
|||||||
return { success: false as const, error: firstAttempt.error };
|
return { success: false as const, error: firstAttempt.error };
|
||||||
};
|
};
|
||||||
|
|
||||||
const unlimited = !Number.isFinite(limit);
|
const unlimited = limit === Number.POSITIVE_INFINITY;
|
||||||
while (unlimited || tweets.length < limit) {
|
while (unlimited || tweets.length < limit) {
|
||||||
const pageCount = unlimited ? pageSize : Math.min(pageSize, limit - tweets.length);
|
const pageCount = unlimited ? pageSize : Math.min(pageSize, limit - tweets.length);
|
||||||
const page = await fetchWithRefresh(pageCount, cursor);
|
const page = await fetchWithRefresh(pageCount, cursor);
|
||||||
@@ -565,7 +565,7 @@ export function withTimelines<TBase extends AbstractConstructor<TwitterClientBas
|
|||||||
return { success: false as const, error: firstAttempt.error };
|
return { success: false as const, error: firstAttempt.error };
|
||||||
};
|
};
|
||||||
|
|
||||||
const unlimited = !Number.isFinite(limit);
|
const unlimited = limit === Number.POSITIVE_INFINITY;
|
||||||
while (unlimited || tweets.length < limit) {
|
while (unlimited || tweets.length < limit) {
|
||||||
const pageCount = unlimited ? pageSize : Math.min(pageSize, limit - tweets.length);
|
const pageCount = unlimited ? pageSize : Math.min(pageSize, limit - tweets.length);
|
||||||
const page = await fetchWithRefresh(pageCount, cursor);
|
const page = await fetchWithRefresh(pageCount, cursor);
|
||||||
|
|||||||
@@ -218,6 +218,79 @@ describe('TwitterClient likes', () => {
|
|||||||
expect(secondVars.cursor).toBe('cursor-1');
|
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 () => {
|
it('returns an error when current user is unavailable', async () => {
|
||||||
const client = new TwitterClient({ cookies: validCookies });
|
const client = new TwitterClient({ cookies: validCookies });
|
||||||
const clientPrivate = client as unknown as TwitterClientPrivate;
|
const clientPrivate = client as unknown as TwitterClientPrivate;
|
||||||
|
|||||||
Reference in New Issue
Block a user