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
+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 { TimelineFetchOptions } from './twitter-client-timelines.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",
"ExplorePage": "kheAINB_4pzRDqkzG3K-ng",
"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',
GenericTimelineById: 'uGSr7alSjR9v6QJAIaqSKQ',
TrendHistory: 'Sj4T-jSB9pr0Mxtsc1UKZQ',
AboutAccountQuery: 'zs_jFPFT78rBpXv9Z3U2YQ',
} as const;
export type OperationName = keyof typeof FALLBACK_QUERY_IDS;
+14
View File
@@ -324,6 +324,20 @@ export interface FollowingResult {
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 {
relayBaseUrl?: string;
profileName?: string;
+93
View File
@@ -1,6 +1,7 @@
import { normalizeHandle } from './normalize-handle.js';
import type { AbstractConstructor, Mixin, TwitterClientBase } from './twitter-client-base.js';
import { TWITTER_API_BASE } from './twitter-client-constants.js';
import type { AboutAccountResult } from './twitter-client-types.js';
/** Result of username to userId lookup */
export interface UserLookupResult {
@@ -13,6 +14,7 @@ export interface UserLookupResult {
export interface TwitterClientUserLookupMethods {
getUserIdByUsername(username: string): Promise<UserLookupResult>;
getUserAboutAccount(username: string): Promise<AboutAccountResult>;
}
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' };
}
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;