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:
@@ -65,7 +65,7 @@ export function withUsers<TBase extends AbstractConstructor<TwitterClientBase>>(
|
|||||||
.filter((u) => u !== null);
|
.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({
|
const params = new URLSearchParams({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
count: String(count),
|
count: String(count),
|
||||||
@@ -73,6 +73,10 @@ export function withUsers<TBase extends AbstractConstructor<TwitterClientBase>>(
|
|||||||
include_user_entities: 'false',
|
include_user_entities: 'false',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (cursor) {
|
||||||
|
params.set('cursor', cursor);
|
||||||
|
}
|
||||||
|
|
||||||
const urls = [
|
const urls = [
|
||||||
`https://x.com/i/api/1.1/followers/list.json?${params.toString()}`,
|
`https://x.com/i/api/1.1/followers/list.json?${params.toString()}`,
|
||||||
`https://api.twitter.com/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 {
|
const data = (await response.json()) as {
|
||||||
users?: RestUser[];
|
users?: RestUser[];
|
||||||
|
next_cursor_str?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const users = this.parseUsersFromRestResponse(data.users);
|
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) {
|
} catch (error) {
|
||||||
lastError = error instanceof Error ? error.message : String(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' };
|
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({
|
const params = new URLSearchParams({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
count: String(count),
|
count: String(count),
|
||||||
@@ -116,6 +122,10 @@ export function withUsers<TBase extends AbstractConstructor<TwitterClientBase>>(
|
|||||||
include_user_entities: 'false',
|
include_user_entities: 'false',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (cursor) {
|
||||||
|
params.set('cursor', cursor);
|
||||||
|
}
|
||||||
|
|
||||||
const urls = [
|
const urls = [
|
||||||
`https://x.com/i/api/1.1/friends/list.json?${params.toString()}`,
|
`https://x.com/i/api/1.1/friends/list.json?${params.toString()}`,
|
||||||
`https://api.twitter.com/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 {
|
const data = (await response.json()) as {
|
||||||
users?: RestUser[];
|
users?: RestUser[];
|
||||||
|
next_cursor_str?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const users = this.parseUsersFromRestResponse(data.users);
|
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) {
|
} catch (error) {
|
||||||
lastError = error instanceof Error ? error.message : String(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).
|
// 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.
|
// 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, cursor);
|
||||||
const restAttempt = await this.getFollowingViaRest(userId, count);
|
|
||||||
if (restAttempt.success) {
|
if (restAttempt.success) {
|
||||||
return restAttempt;
|
return restAttempt;
|
||||||
}
|
}
|
||||||
@@ -439,8 +450,7 @@ export function withUsers<TBase extends AbstractConstructor<TwitterClientBase>>(
|
|||||||
|
|
||||||
// GraphQL Followers regularly returns 404 (queryId churn / endpoint flakiness).
|
// 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.
|
// 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, cursor);
|
||||||
const restAttempt = await this.getFollowersViaRest(userId, count);
|
|
||||||
if (restAttempt.success) {
|
if (restAttempt.success) {
|
||||||
return restAttempt;
|
return restAttempt;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user