返回 last30days-skill
twitter-client-base.js
根目录 / skills / last30days / scripts / lib / vendor / bird-search / lib / twitter-client-base.js
1 import { randomBytes, randomUUID } from 'node:crypto';
2 import { runtimeQueryIds } from './runtime-query-ids.js';
3 import { QUERY_IDS, TARGET_QUERY_ID_OPERATIONS } from './twitter-client-constants.js';
4 import { normalizeQuoteDepth } from './twitter-client-utils.js';
5 export class TwitterClientBase {
6 authToken;
7 ct0;
8 cookieHeader;
9 userAgent;
10 timeoutMs;
11 quoteDepth;
12 clientUuid;
13 clientDeviceId;
14 clientUserId;
15 constructor(options) {
16 if (!options.cookies.authToken || !options.cookies.ct0) {
17 throw new Error('Both authToken and ct0 cookies are required');
18 }
19 this.authToken = options.cookies.authToken;
20 this.ct0 = options.cookies.ct0;
21 this.cookieHeader = options.cookies.cookieHeader || `auth_token=${this.authToken}; ct0=${this.ct0}`;
22 this.userAgent =
23 options.userAgent ||
24 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
25 this.timeoutMs = options.timeoutMs;
26 this.quoteDepth = normalizeQuoteDepth(options.quoteDepth);
27 this.clientUuid = randomUUID();
28 this.clientDeviceId = randomUUID();
29 }
30 async sleep(ms) {
31 await new Promise((resolve) => setTimeout(resolve, ms));
32 }
33 async getQueryId(operationName) {
34 const cached = await runtimeQueryIds.getQueryId(operationName);
35 return cached ?? QUERY_IDS[operationName];
36 }
37 async refreshQueryIds() {
38 if (process.env.NODE_ENV === 'test') {
39 return;
40 }
41 try {
42 await runtimeQueryIds.refresh(TARGET_QUERY_ID_OPERATIONS, { force: true });
43 }
44 catch {
45 // ignore refresh failures; callers will fall back to baked-in IDs
46 }
47 }
48 async withRefreshedQueryIdsOn404(attempt) {
49 const firstAttempt = await attempt();
50 if (firstAttempt.success || !firstAttempt.had404) {
51 return { result: firstAttempt, refreshed: false };
52 }
53 await this.refreshQueryIds();
54 const secondAttempt = await attempt();
55 return { result: secondAttempt, refreshed: true };
56 }
57 async getTweetDetailQueryIds() {
58 const primary = await this.getQueryId('TweetDetail');
59 return Array.from(new Set([primary, '97JF30KziU00483E_8elBA', 'aFvUsJm2c-oDkJV75blV6g']));
60 }
61 async getSearchTimelineQueryIds() {
62 const primary = await this.getQueryId('SearchTimeline');
63 return Array.from(new Set([primary, 'M1jEez78PEfVfbQLvlWMvQ', '5h0kNbk3ii97rmfY6CdgAA', 'Tp1sewRU1AsZpBWhqCZicQ']));
64 }
65 async fetchWithTimeout(url, init) {
66 if (!this.timeoutMs || this.timeoutMs <= 0) {
67 return fetch(url, init);
68 }
69 const controller = new AbortController();
70 const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
71 try {
72 return await fetch(url, { ...init, signal: controller.signal });
73 }
74 finally {
75 clearTimeout(timeoutId);
76 }
77 }
78 getHeaders() {
79 return this.getJsonHeaders();
80 }
81 createTransactionId() {
82 return randomBytes(16).toString('hex');
83 }
84 getBaseHeaders() {
85 const headers = {
86 accept: '*/*',
87 'accept-language': 'en-US,en;q=0.9',
88 authorization: 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA',
89 'x-csrf-token': this.ct0,
90 'x-twitter-auth-type': 'OAuth2Session',
91 'x-twitter-active-user': 'yes',
92 'x-twitter-client-language': 'en',
93 'x-client-uuid': this.clientUuid,
94 'x-twitter-client-deviceid': this.clientDeviceId,
95 'x-client-transaction-id': this.createTransactionId(),
96 cookie: this.cookieHeader,
97 'user-agent': this.userAgent,
98 origin: 'https://x.com',
99 referer: 'https://x.com/',
100 };
101 if (this.clientUserId) {
102 headers['x-twitter-client-user-id'] = this.clientUserId;
103 }
104 return headers;
105 }
106 getJsonHeaders() {
107 return {
108 ...this.getBaseHeaders(),
109 'content-type': 'application/json',
110 };
111 }
112 getUploadHeaders() {
113 // Note: do not set content-type; URLSearchParams/FormData need to set it (incl boundary) themselves.
114 return this.getBaseHeaders();
115 }
116 async ensureClientUserId() {
117 if (process.env.NODE_ENV === 'test') {
118 return;
119 }
120 if (this.clientUserId) {
121 return;
122 }
123 const result = await this.getCurrentUser();
124 if (result.success && result.user?.id) {
125 this.clientUserId = result.user.id;
126 }
127 }
128 }
129 //# sourceMappingURL=twitter-client-base.js.map
129 lines JAVASCRIPT