feat: add AboutAccountQuery client support

This commit is contained in:
Pablo Tovar
2026-06-24 18:40:30 +09:00
committed by yuta
parent 083c8cd007
commit 769245503c
6 changed files with 117 additions and 2 deletions
+1
View File
@@ -23,6 +23,7 @@ const TARGET_OPERATIONS = [
'ExplorePage', 'ExplorePage',
'GenericTimelineById', 'GenericTimelineById',
'TrendHistory', 'TrendHistory',
'AboutAccountQuery',
] as const; ] as const;
type OperationName = (typeof TARGET_OPERATIONS)[number]; type OperationName = (typeof TARGET_OPERATIONS)[number];
+6 -1
View File
@@ -14,4 +14,9 @@ export type { ExploreTab, NewsFetchOptions, NewsItem, NewsResult } from './twitt
export type { SearchFetchOptions } from './twitter-client-search.js'; export type { SearchFetchOptions } from './twitter-client-search.js';
export type { TimelineFetchOptions } from './twitter-client-timelines.js'; export type { TimelineFetchOptions } from './twitter-client-timelines.js';
export type { TweetFetchOptions } from './twitter-client-tweet-detail.js'; export type { TweetFetchOptions } from './twitter-client-tweet-detail.js';
export type { TweetResult, UploadMediaResult } from './twitter-client-types.js'; export type {
AboutAccountProfile,
AboutAccountResult,
TweetResult,
UploadMediaResult,
} from './twitter-client-types.js';
+2 -1
View File
@@ -13,5 +13,6 @@
"ExploreSidebar": "lpSN4M6qpimkF4nRFPE3nQ", "ExploreSidebar": "lpSN4M6qpimkF4nRFPE3nQ",
"ExplorePage": "kheAINB_4pzRDqkzG3K-ng", "ExplorePage": "kheAINB_4pzRDqkzG3K-ng",
"GenericTimelineById": "uGSr7alSjR9v6QJAIaqSKQ", "GenericTimelineById": "uGSr7alSjR9v6QJAIaqSKQ",
"TrendHistory": "Sj4T-jSB9pr0Mxtsc1UKZQ" "TrendHistory": "Sj4T-jSB9pr0Mxtsc1UKZQ",
"AboutAccountQuery": "zs_jFPFT78rBpXv9Z3U2YQ"
} }
+1
View File
@@ -33,6 +33,7 @@ export const FALLBACK_QUERY_IDS = {
ExplorePage: 'kheAINB_4pzRDqkzG3K-ng', ExplorePage: 'kheAINB_4pzRDqkzG3K-ng',
GenericTimelineById: 'uGSr7alSjR9v6QJAIaqSKQ', GenericTimelineById: 'uGSr7alSjR9v6QJAIaqSKQ',
TrendHistory: 'Sj4T-jSB9pr0Mxtsc1UKZQ', TrendHistory: 'Sj4T-jSB9pr0Mxtsc1UKZQ',
AboutAccountQuery: 'zs_jFPFT78rBpXv9Z3U2YQ',
} as const; } as const;
export type OperationName = keyof typeof FALLBACK_QUERY_IDS; export type OperationName = keyof typeof FALLBACK_QUERY_IDS;
+14
View File
@@ -324,6 +324,20 @@ export interface FollowingResult {
nextCursor?: string; nextCursor?: string;
} }
export interface AboutAccountProfile {
accountBasedIn?: string;
source?: string;
createdCountryAccurate?: boolean;
locationAccurate?: boolean;
learnMoreUrl?: string;
}
export interface AboutAccountResult {
success: boolean;
aboutProfile?: AboutAccountProfile;
error?: string;
}
export interface TwitterClientOptions { export interface TwitterClientOptions {
relayBaseUrl?: string; relayBaseUrl?: string;
profileName?: string; profileName?: string;
+93
View File
@@ -1,6 +1,7 @@
import { normalizeHandle } from './normalize-handle.js'; import { normalizeHandle } from './normalize-handle.js';
import type { AbstractConstructor, Mixin, TwitterClientBase } from './twitter-client-base.js'; import type { AbstractConstructor, Mixin, TwitterClientBase } from './twitter-client-base.js';
import { TWITTER_API_BASE } from './twitter-client-constants.js'; import { TWITTER_API_BASE } from './twitter-client-constants.js';
import type { AboutAccountResult } from './twitter-client-types.js';
/** Result of username to userId lookup */ /** Result of username to userId lookup */
export interface UserLookupResult { export interface UserLookupResult {
@@ -13,6 +14,7 @@ export interface UserLookupResult {
export interface TwitterClientUserLookupMethods { export interface TwitterClientUserLookupMethods {
getUserIdByUsername(username: string): Promise<UserLookupResult>; getUserIdByUsername(username: string): Promise<UserLookupResult>;
getUserAboutAccount(username: string): Promise<AboutAccountResult>;
} }
export function withUserLookup<TBase extends AbstractConstructor<TwitterClientBase>>( export function withUserLookup<TBase extends AbstractConstructor<TwitterClientBase>>(
@@ -205,6 +207,97 @@ export function withUserLookup<TBase extends AbstractConstructor<TwitterClientBa
return { success: false, error: lastError ?? 'Unknown error looking up user' }; return { success: false, error: lastError ?? 'Unknown error looking up user' };
} }
private async getAboutAccountQueryIds(): Promise<string[]> {
const primary = await this.getQueryId('AboutAccountQuery');
return Array.from(new Set([primary, 'zs_jFPFT78rBpXv9Z3U2YQ']));
}
/**
* Get account origin and location information for a user.
* Returns data from Twitter's "About this account" feature.
*/
async getUserAboutAccount(username: string): Promise<AboutAccountResult> {
const cleanUsername = normalizeHandle(username);
if (!cleanUsername) {
return { success: false, error: `Invalid username: ${username}` };
}
const variables = {
screenName: cleanUsername,
};
const params = new URLSearchParams({
variables: JSON.stringify(variables),
});
let lastError: string | undefined;
const queryIds = await this.getAboutAccountQueryIds();
for (const queryId of queryIds) {
const url = `${TWITTER_API_BASE}/${queryId}/AboutAccountQuery?${params.toString()}`;
try {
const response = await this.fetchWithTimeout(url, {
method: 'GET',
headers: this.getHeaders(),
});
if (!response.ok) {
const text = await response.text();
if (response.status === 404) {
lastError = `HTTP ${response.status}`;
continue;
}
lastError = `HTTP ${response.status}: ${text.slice(0, 200)}`;
continue;
}
const data = (await response.json()) as {
data?: {
user_result_by_screen_name?: {
result?: {
about_profile?: {
account_based_in?: string;
source?: string;
created_country_accurate?: boolean;
location_accurate?: boolean;
learn_more_url?: string;
};
};
};
};
errors?: Array<{ message: string }>;
};
if (data.errors && data.errors.length > 0) {
lastError = data.errors.map((e) => e.message).join(', ');
continue;
}
const aboutProfile = data.data?.user_result_by_screen_name?.result?.about_profile;
if (!aboutProfile) {
lastError = 'Missing about_profile in response';
continue;
}
return {
success: true,
aboutProfile: {
accountBasedIn: aboutProfile.account_based_in,
source: aboutProfile.source,
createdCountryAccurate: aboutProfile.created_country_accurate,
locationAccurate: aboutProfile.location_accurate,
learnMoreUrl: aboutProfile.learn_more_url,
},
};
} catch (error) {
lastError = error instanceof Error ? error.message : String(error);
}
}
return { success: false, error: lastError ?? 'Unknown error fetching account details' };
}
} }
return TwitterClientUserLookup; return TwitterClientUserLookup;