fix: add cursor pagination support to REST fallback for followers/following

When GraphQL endpoints return 404 and fall back to REST API v1.1,
pagination now works correctly by:
- Accepting cursor parameter in getFollowersViaRest and getFollowingViaRest
- Passing cursor to REST API requests
- Extracting next_cursor_str from REST responses
- Returning nextCursor in the result for proper --all pagination
This commit is contained in:
Henry Lee
2026-06-24 18:42:16 +09:00
committed by yuta
parent df449ea736
commit 058aa0e3c3
+18 -8
View File
@@ -65,7 +65,7 @@ export function withUsers<TBase extends AbstractConstructor<TwitterClientBase>>(
.filter((u) => u !== null);
}
private async getFollowersViaRest(userId: string, count: number): Promise<FollowingResult> {
private async getFollowersViaRest(userId: string, count: number, cursor?: string): Promise<FollowingResult> {
const params = new URLSearchParams({
user_id: userId,
count: String(count),
@@ -73,6 +73,10 @@ export function withUsers<TBase extends AbstractConstructor<TwitterClientBase>>(
include_user_entities: 'false',
});
if (cursor) {
params.set('cursor', cursor);
}
const urls = [
`https://x.com/i/api/1.1/followers/list.json?${params.toString()}`,
`https://api.twitter.com/1.1/followers/list.json?${params.toString()}`,
@@ -95,11 +99,13 @@ export function withUsers<TBase extends AbstractConstructor<TwitterClientBase>>(
const data = (await response.json()) as {
users?: RestUser[];
next_cursor_str?: string;
};
const users = this.parseUsersFromRestResponse(data.users);
const nextCursor = data.next_cursor_str && data.next_cursor_str !== '0' ? data.next_cursor_str : undefined;
return { success: true, users };
return { success: true, users, nextCursor };
} catch (error) {
lastError = error instanceof Error ? error.message : String(error);
}
@@ -108,7 +114,7 @@ export function withUsers<TBase extends AbstractConstructor<TwitterClientBase>>(
return { success: false, error: lastError ?? 'Unknown error fetching followers' };
}
private async getFollowingViaRest(userId: string, count: number): Promise<FollowingResult> {
private async getFollowingViaRest(userId: string, count: number, cursor?: string): Promise<FollowingResult> {
const params = new URLSearchParams({
user_id: userId,
count: String(count),
@@ -116,6 +122,10 @@ export function withUsers<TBase extends AbstractConstructor<TwitterClientBase>>(
include_user_entities: 'false',
});
if (cursor) {
params.set('cursor', cursor);
}
const urls = [
`https://x.com/i/api/1.1/friends/list.json?${params.toString()}`,
`https://api.twitter.com/1.1/friends/list.json?${params.toString()}`,
@@ -138,11 +148,13 @@ export function withUsers<TBase extends AbstractConstructor<TwitterClientBase>>(
const data = (await response.json()) as {
users?: RestUser[];
next_cursor_str?: string;
};
const users = this.parseUsersFromRestResponse(data.users);
const nextCursor = data.next_cursor_str && data.next_cursor_str !== '0' ? data.next_cursor_str : undefined;
return { success: true, users };
return { success: true, users, nextCursor };
} catch (error) {
lastError = error instanceof Error ? error.message : String(error);
}
@@ -333,8 +345,7 @@ export function withUsers<TBase extends AbstractConstructor<TwitterClientBase>>(
// GraphQL Following can also return 404 (queryId churn / endpoint flakiness).
// Fallback to the internal v1.1 REST endpoint used by the web client through the relay.
// Note: REST fallback does not support cursor pagination.
const restAttempt = await this.getFollowingViaRest(userId, count);
const restAttempt = await this.getFollowingViaRest(userId, count, cursor);
if (restAttempt.success) {
return restAttempt;
}
@@ -439,8 +450,7 @@ export function withUsers<TBase extends AbstractConstructor<TwitterClientBase>>(
// GraphQL Followers regularly returns 404 (queryId churn / endpoint flakiness).
// Fallback to the internal v1.1 REST endpoint used by the web client through the relay.
// Note: REST fallback does not support cursor pagination.
const restAttempt = await this.getFollowersViaRest(userId, count);
const restAttempt = await this.getFollowersViaRest(userId, count, cursor);
if (restAttempt.success) {
return restAttempt;
}