Skip to content

Commit e6e2acb

Browse files
committed
fix(twitter): add X-Client-Transaction-Id
1 parent 6a664a8 commit e6e2acb

8 files changed

Lines changed: 122 additions & 19 deletions

File tree

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
package-lock=true
22
package-manager-strict=false
3+
@jsr:registry=https://npm.jsr.io

lib/routes/twitter/api/developer-api/api.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,14 +230,16 @@ const cacheTryGet = async (_id: string, params: Record<string, any> | undefined,
230230

231231
const getUserTimeline = async (id: string, params?: Record<string, any>, options: Record<string, any> = {}) => {
232232
const client = await getAppClient();
233-
const response = await client.v2.get(`users/${id}/tweets`, {
233+
const { ...requestOptions } = options;
234+
const requestParams = {
234235
max_results: params?.count ?? 20,
235236
expansions: 'author_id,attachments.media_keys,referenced_tweets.id,referenced_tweets.id.author_id',
236237
'tweet.fields': 'created_at,entities,conversation_id,referenced_tweets,author_id,in_reply_to_user_id',
237238
'user.fields': 'username,name,profile_image_url,description',
238239
'media.fields': 'preview_image_url,url,type,width,height,variants',
239-
...options,
240-
});
240+
...requestOptions,
241+
};
242+
const response = await client.v2.get(`users/${id}/tweets`, requestParams);
241243
return mapTweetResponseToLegacy(response);
242244
};
243245

lib/routes/twitter/api/mobile-api/api.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,14 @@ const twitterGot = async (url, params) => {
4949
};
5050

5151
const paginationTweets = async (endpoint, userId, variables, path) => {
52-
const { data } = await twitterGot(baseUrl + endpoint, {
52+
const params = {
5353
variables: JSON.stringify({
5454
...variables,
5555
rest_id: userId,
5656
}),
5757
features: gqlFeatures,
58-
});
58+
};
59+
const { data } = await twitterGot(baseUrl + endpoint, params);
5960

6061
let instructions;
6162
if (path) {

lib/routes/twitter/api/web-api/api.ts

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,22 @@ const getUserData = (id) =>
4141
});
4242
});
4343

44-
const cacheTryGet = async (_id, params, func) => {
44+
const cacheTryGet = async (_id, params, func, cacheKey = func.name) => {
4545
const userData: any = await getUserData(_id);
4646
const id = (userData.data?.user || userData.data?.user_result)?.result?.rest_id;
4747
if (id === undefined) {
4848
cache.set(`twitter-userdata-${_id}`, '', config.cache.contentExpire);
4949
throw new InvalidParameterError('User not found');
5050
}
51-
const funcName = func.name;
5251
const paramsString = JSON.stringify(params);
53-
return cache.tryGet(`twitter:${id}:${funcName}:${paramsString}`, () => func(id, params), config.cache.routeExpire, false);
52+
return cache.tryGet(`twitter:${id}:${cacheKey}:${paramsString}`, () => func(id, params), config.cache.routeExpire, false);
5453
};
5554

5655
const getUserTweets = (id: string, params?: Record<string, any>) =>
57-
cacheTryGet(id, params, async (id, params = {}) =>
56+
cacheTryGet(
57+
id,
58+
params,
59+
async (id, params = {}) =>
5860
gatherLegacyFromData(
5961
await paginationTweets('UserTweets', id, {
6062
...params,
@@ -64,11 +66,15 @@ const getUserTweets = (id: string, params?: Record<string, any>) =>
6466
withVoice: true,
6567
withV2Timeline: true,
6668
})
67-
)
69+
),
70+
'getUserTweets'
6871
);
6972

7073
const getUserTweetsAndReplies = (id: string, params?: Record<string, any>) =>
71-
cacheTryGet(id, params, async (id, params = {}) =>
74+
cacheTryGet(
75+
id,
76+
params,
77+
async (id, params = {}) =>
7278
gatherLegacyFromData(
7379
await paginationTweets('UserTweetsAndReplies', id, {
7480
...params,
@@ -80,11 +86,15 @@ const getUserTweetsAndReplies = (id: string, params?: Record<string, any>) =>
8086
}),
8187
['profile-conversation-'],
8288
id
83-
)
89+
),
90+
'getUserTweetsAndReplies'
8491
);
8592

8693
const getUserMedia = (id: string, params?: Record<string, any>) =>
87-
cacheTryGet(id, params, async (id, params = {}) =>
94+
cacheTryGet(
95+
id,
96+
params,
97+
async (id, params = {}) =>
8898
gatherLegacyFromData(
8999
await paginationTweets('UserMedia', id, {
90100
...params,
@@ -95,11 +105,15 @@ const getUserMedia = (id: string, params?: Record<string, any>) =>
95105
withVoice: true,
96106
withV2Timeline: true,
97107
})
98-
)
108+
),
109+
'getUserMedia'
99110
);
100111

101112
const getUserLikes = (id: string, params?: Record<string, any>) =>
102-
cacheTryGet(id, params, async (id, params = {}) =>
113+
cacheTryGet(
114+
id,
115+
params,
116+
async (id, params = {}) =>
103117
gatherLegacyFromData(
104118
await paginationTweets('Likes', id, {
105119
...params,
@@ -109,11 +123,15 @@ const getUserLikes = (id: string, params?: Record<string, any>) =>
109123
withVoice: false,
110124
withV2Timeline: true,
111125
})
112-
)
126+
),
127+
'getUserLikes'
113128
);
114129

115130
const getUserTweet = (id: string, params?: Record<string, any>) =>
116-
cacheTryGet(id, params, async (id, params = {}) =>
131+
cacheTryGet(
132+
id,
133+
params,
134+
async (id, params = {}) =>
117135
gatherLegacyFromData(
118136
await paginationTweets(
119137
'TweetDetail',
@@ -129,7 +147,8 @@ const getUserTweet = (id: string, params?: Record<string, any>) =>
129147
['threaded_conversation_with_injections_v2']
130148
),
131149
['homeConversation-', 'conversationthread-']
132-
)
150+
),
151+
'getUserTweet'
133152
);
134153

135154
const getSearch = async (keywords: string, params?: Record<string, any>) =>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { ClientTransaction, handleXMigration } from '@lami/x-client-transaction-id';
2+
3+
let clientTransactionPromise: Promise<ClientTransaction> | undefined;
4+
5+
const buildClientTransaction = async () => ClientTransaction.create(await handleXMigration());
6+
7+
const getClientTransaction = () => {
8+
clientTransactionPromise ??= buildClientTransaction();
9+
return clientTransactionPromise;
10+
};
11+
12+
export const getClientTransactionId = async (method: string, path: string) => {
13+
try {
14+
const clientTransaction = await getClientTransaction();
15+
return await clientTransaction.generateTransactionId(method, path);
16+
} catch {
17+
clientTransactionPromise = undefined;
18+
}
19+
};

lib/routes/twitter/api/web-api/utils.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import logger from '@/utils/logger';
1010
import ofetch from '@/utils/ofetch';
1111
import proxy from '@/utils/proxy';
1212

13+
import { getClientTransactionId } from './client-transaction';
1314
import { baseUrl, bearerToken, gqlFeatures, gqlMap, thirdPartySupportedAPI } from './constants';
1415
import login from './login';
1516

@@ -80,6 +81,7 @@ export const twitterGot = async (
8081
params,
8182
options?: {
8283
allowNoAuth?: boolean;
84+
headers?: Record<string, string>;
8385
}
8486
) => {
8587
const auth = await getAuth(30);
@@ -171,6 +173,7 @@ export const twitterGot = async (
171173
: {
172174
'x-guest-token': jsonCookie.gt,
173175
}),
176+
...options?.headers,
174177
},
175178
dispatcher: dispatchers?.agent,
176179
});
@@ -263,7 +266,14 @@ export const paginationTweets = async (endpoint: string, userId: number | undefi
263266
});
264267
return data;
265268
}
266-
const { data } = await twitterGot(baseUrl + gqlMap[endpoint], params);
269+
const transactionId = endpoint === 'UserTweetsAndReplies' ? await getClientTransactionId('GET', new URL(baseUrl + gqlMap[endpoint]).pathname) : undefined;
270+
const { data } = await twitterGot(baseUrl + gqlMap[endpoint], params, {
271+
headers: transactionId
272+
? {
273+
'x-client-transaction-id': transactionId,
274+
}
275+
: undefined,
276+
});
267277
return data;
268278
};
269279

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"@hono/node-server": "1.19.14",
6464
"@hono/zod-openapi": "1.3.0",
6565
"@jocmp/mercury-parser": "3.0.7",
66+
"@lami/x-client-transaction-id": "npm:@jsr/lami__x-client-transaction-id@^0.2.0",
6667
"@notionhq/client": "5.18.0",
6768
"@opentelemetry/api": "1.9.1",
6869
"@opentelemetry/exporter-prometheus": "0.214.0",

pnpm-lock.yaml

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)