emusks 2.0.11 → 2.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emusks",
3
- "version": "2.0.11",
3
+ "version": "2.0.13",
4
4
  "description": "Reverse-engineered Twitter API client. Log in and interact with the unofficial X API using any client identity — web, Android, iOS, or TweetDeck",
5
5
  "keywords": [
6
6
  "client",
package/src/clients.js CHANGED
@@ -4,18 +4,14 @@ const chrome_fingerprint = {
4
4
  ja3: "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,35-5-27-16-0-10-13-23-45-65037-17613-18-65281-51-43-11,4588-29-23-24,0",
5
5
  ja4r: "t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601",
6
6
  };
7
-
8
7
  const android_fingerprint = {
9
8
  ja3: "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,23-0-16-11-17613-27-43-35-13-65037-51-18-65281-45-5-10-41,4588-29-23-24,0",
10
9
  ja4r: "t13d1517h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,0029,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601",
11
-
12
10
  userAgent: "TwitterAndroid/10.93.0-release.00 (Android 14; Google Pixel 7 Pro)",
13
11
  };
14
-
15
12
  const iphone_fingerprint = {
16
13
  ja3: "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,23-0-16-11-17613-27-43-35-13-65037-51-18-65281-45-5-10-41,4588-29-23-24,0",
17
14
  ja4r: "t13d1517h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,0029,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601",
18
-
19
15
  userAgent: "Twitter-iPhone/10.93.0-release.00 (iPhone; iOS 16.5.1; Scale/3.00)",
20
16
  };
21
17
 
@@ -41,14 +37,14 @@ export default {
41
37
  "AAAAAAAAAAAAAAAAAAAAAIWCCAAAAAAA2C25AxqI%2BYCS7pdfJKRH8Xh19zA%3D8vpDZzPHaEJhd20MKVWp3UR38YoPpuTX7UD2cVYo3YNikubuxd",
42
38
  fingerprints: iphone_fingerprint,
43
39
  },
44
-
45
40
  old: {
46
- // doesn't seem to work
41
+ // v1.1 only - doesn't seem to work
47
42
  bearer:
48
43
  "AAAAAAAAAAAAAAAAAAAAAPYXBAAAAAAACLXUNDekMxqa8h%2F40K4moUkGsoc%3DTYfbDKbT3jJPCEVnMYqilB28NHfOPqkca3qaAxGfsyKCs0wRbw",
49
44
  fingerprints: chrome_fingerprint,
50
45
  },
51
46
  web: {
47
+ // requires transaction id
52
48
  bearer:
53
49
  "AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA",
54
50
  fingerprints: chrome_fingerprint,
@@ -59,4 +55,41 @@ export default {
59
55
  "AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF",
60
56
  fingerprints: chrome_fingerprint,
61
57
  },
62
- };
58
+ advertisers: {
59
+ bearer:
60
+ "AAAAAAAAAAAAAAAAAAAAAPnA9gAAAAAAZHpqKYoDdMCaqTUBktzAdK38BGk%3DLNsI9r2BHSjZ7cl5wD6Sh6NhxwZd2j8lXDSd6GDoQVYBlzx5Ff",
61
+ fingerprints: chrome_fingerprint,
62
+ },
63
+ ads_manager_next: {
64
+ bearer:
65
+ "AAAAAAAAAAAAAAAAAAAAAFsXTQEAAAAAGT1ZWWC51IF1Z8pIl9GKdGuGYtY%3Darf4lbyvoKjCUZfpLX90hOjVT1PqmxNH3arG6iOcb4XNlCvQxe",
66
+ fingerprints: chrome_fingerprint,
67
+ },
68
+ advertiser_interface: {
69
+ bearer:
70
+ "AAAAAAAAAAAAAAAAAAAAAFktLQEAAAAAidmFlKJvQR29CW13eg2VolZ3wy4%3DU2ufD1swpIdjuNtaIzloCW8VnUYsmyuTatng49FzHuRwEeDdKH",
71
+ fingerprints: chrome_fingerprint,
72
+ },
73
+ corporate_cms: {
74
+ // known by twitter employees
75
+ bearer:
76
+ "AAAAAAAAAAAAAAAAAAAAACHguwAAAAAAaSlT0G31NDEyg%2BSnBN5JuyKjMCU%3Dlhg0gv0nE7KKyiJNEAojQbn8Y3wJm1xidDK7VnKGBP4ByJwHPb",
77
+ fingerprints: chrome_fingerprint,
78
+ },
79
+ ads_broken: {
80
+ // doesn't work
81
+ bearer:
82
+ "AAAAAAAAAAAAAAAAAAAAAG5LOQEAAAAAbEKsIYYIhrfOQqm4H8u7xcahRkU%3Dz98HKmzbeXdKqBfUDmElcqYl0cmmKY9KdS2UoNIz3Phapgsowi",
83
+ fingerprints: chrome_fingerprint,
84
+ },
85
+ audience_manager_internal: {
86
+ bearer:
87
+ "AAAAAAAAAAAAAAAAAAAAADLR6gAAAAAAVkXGYss9CqHyAQ%2FMecX43S1FhwA%3DYUynbBpK0xqHXNeDxFt3NkoFwkS45LTzvgE3CJTfRbQ3QOSO7f",
88
+ fingerprints: chrome_fingerprint,
89
+ },
90
+ tweets_manager: {
91
+ bearer:
92
+ "AAAAAAAAAAAAAAAAAAAAADiN8wAAAAAAeBRmNg1NMB%2F4KdNvC%2FpQD0jjTEI%3Dosq5IVDr4azvJ2wThXtkMbGu8powFlXBRgQD9yTttGtygMk7ki",
93
+ fingerprints: chrome_fingerprint,
94
+ },
95
+ };
package/src/graphql.js CHANGED
@@ -1,16 +1,60 @@
1
+ import { ClientTransaction, handleXMigration } from "x-client-transaction-id";
1
2
  import getCycleTLS from "./cycletls.js";
2
3
  import graphqlApi from "./static/graphql.js";
3
4
 
5
+ const GRAPHQL_ENDPOINTS = {
6
+ main: {
7
+ base: "https://api.x.com/graphql",
8
+ referrer: "https://x.com/",
9
+ secFetchSite: "same-site",
10
+ },
11
+ main_twitter: {
12
+ base: "https://api.twitter.com/graphql",
13
+ referrer: "https://twitter.com/",
14
+ secFetchSite: "same-site",
15
+ },
16
+ web: {
17
+ base: "https://x.com/i/api/graphql",
18
+ referrer: "https://x.com/",
19
+ secFetchSite: "same-origin",
20
+ },
21
+ web_twitter: {
22
+ base: "https://twitter.com/i/api/graphql",
23
+ referrer: "https://twitter.com/",
24
+ secFetchSite: "same-origin",
25
+ },
26
+ tweetdeck: {
27
+ base: "https://pro.x.com/i/api/graphql",
28
+ referrer: "https://pro.x.com/",
29
+ secFetchSite: "same-origin",
30
+ },
31
+ tweetdeck_twitter: {
32
+ base: "https://pro.twitter.com/i/api/graphql",
33
+ referrer: "https://pro.twitter.com/",
34
+ secFetchSite: "same-origin",
35
+ },
36
+ };
37
+
38
+ export { GRAPHQL_ENDPOINTS };
39
+
4
40
  export default async function graphql(queryName, { variables, fieldToggles, body, headers } = {}) {
5
41
  const entry = graphqlApi[queryName];
6
42
  if (!entry) {
7
43
  throw new Error(`graphql query ${queryName} not found`);
8
44
  }
9
45
 
10
- const [method, baseUrl, features, queryId] = entry;
46
+ const [method, , features, queryId] = entry;
11
47
  const isPost = method.toLowerCase() === "post";
12
48
 
13
- let finalUrl = baseUrl;
49
+ const endpointName = this.graphqlEndpoint || "web";
50
+ const endpoint = GRAPHQL_ENDPOINTS[endpointName];
51
+ if (!endpoint) {
52
+ throw new Error(
53
+ `unknown graphql endpoint "${endpointName}", expected: ${Object.keys(GRAPHQL_ENDPOINTS).join(", ")}`,
54
+ );
55
+ }
56
+
57
+ let finalUrl = `${endpoint.base}/${queryId}/${queryName}`;
14
58
  let requestBody;
15
59
 
16
60
  if (isPost) {
@@ -41,6 +85,9 @@ export default async function graphql(queryName, { variables, fieldToggles, body
41
85
  const url = new URL(finalUrl);
42
86
  const pathname = url.pathname;
43
87
 
88
+ const useTransactionIds =
89
+ this.transactionIds !== undefined ? this.transactionIds : endpointName === "web";
90
+
44
91
  const requestHeaders = {
45
92
  accept: "*/*",
46
93
  "accept-language": "en-US,en;q=0.9",
@@ -50,23 +97,32 @@ export default async function graphql(queryName, { variables, fieldToggles, body
50
97
  "x-twitter-active-user": "yes",
51
98
  "x-twitter-auth-type": "OAuth2Session",
52
99
  "x-twitter-client-language": "en",
53
- "x-client-transaction-id": await this.auth.generateTransactionId(
54
- method.toUpperCase(),
55
- pathname,
56
- ),
57
100
  priority: "u=1, i",
58
101
  "sec-ch-ua": '"Not(A:Brand";v="8", "Chromium";v="144"',
59
102
  "sec-ch-ua-mobile": "?0",
60
103
  "sec-ch-ua-platform": '"macOS"',
61
104
  "sec-fetch-dest": "empty",
62
105
  "sec-fetch-mode": "cors",
63
- "sec-fetch-site": "same-origin",
106
+ "sec-fetch-site": endpoint.secFetchSite,
64
107
  "sec-gpc": "1",
65
108
  cookie:
66
109
  this.auth.client.headers.cookie + (this.elevatedCookies ? `; ${this.elevatedCookies}` : ""),
67
110
  ...headers,
68
111
  };
69
112
 
113
+ if (useTransactionIds) {
114
+ if (!this.auth.generateTransactionId) {
115
+ const document = await handleXMigration();
116
+ const transaction = new ClientTransaction(document);
117
+ await transaction.initialize();
118
+ this.auth.generateTransactionId = transaction.generateTransactionId.bind(transaction);
119
+ }
120
+ requestHeaders["x-client-transaction-id"] = await this.auth.generateTransactionId(
121
+ method.toUpperCase(),
122
+ pathname,
123
+ );
124
+ }
125
+
70
126
  const cycleTLS = await getCycleTLS();
71
127
  const res = await (
72
128
  await cycleTLS(
@@ -78,7 +134,7 @@ export default async function graphql(queryName, { variables, fieldToggles, body
78
134
  ja4r: this.auth.client.fingerprints.ja4r,
79
135
  body: isPost ? JSON.stringify(requestBody) : undefined,
80
136
  proxy: this.proxy || undefined,
81
- referrer: "https://x.com/",
137
+ referrer: endpoint.referrer,
82
138
  },
83
139
  method,
84
140
  )
package/src/index.js CHANGED
@@ -2,7 +2,7 @@ import { ClientTransaction, handleXMigration } from "x-client-transaction-id";
2
2
  import clients from "./clients.js";
3
3
  import getCycleTLS from "./cycletls.js";
4
4
  import flowLogin from "./flow.js";
5
- import graphql from "./graphql.js";
5
+ import graphql, { GRAPHQL_ENDPOINTS } from "./graphql.js";
6
6
  import initHelpers from "./helpers/index.js";
7
7
  import parseUser from "./parsers/user.js";
8
8
  import v1_1 from "./v1.1.js";
@@ -11,6 +11,8 @@ import v2 from "./v2.js";
11
11
  export default class Emusks {
12
12
  auth = null;
13
13
  elevatedCookies = null;
14
+ graphqlEndpoint = "web";
15
+ transactionIds = undefined;
14
16
 
15
17
  async elevate(password) {
16
18
  if (!this.auth) throw new Error("must be logged in before calling elevate");
@@ -76,6 +78,19 @@ export default class Emusks {
76
78
  if (!p.client) throw new Error("invalid client!");
77
79
  if (p.proxy) this.proxy = p.proxy;
78
80
 
81
+ if (p.endpoint) {
82
+ if (!GRAPHQL_ENDPOINTS[p.endpoint]) {
83
+ throw new Error(
84
+ `unknown graphql endpoint "${p.endpoint}", expected: ${Object.keys(GRAPHQL_ENDPOINTS).join(", ")}`,
85
+ );
86
+ }
87
+ this.graphqlEndpoint = p.endpoint;
88
+ }
89
+
90
+ if (p.transactionIds !== undefined) {
91
+ this.transactionIds = p.transactionIds;
92
+ }
93
+
79
94
  if (!p.client.bearer) {
80
95
  throw new Error("client is missing bearer token!");
81
96
  }
@@ -139,10 +154,17 @@ export default class Emusks {
139
154
  }
140
155
  this.auth.client.headers.cookie = cookieParts.join("; ");
141
156
 
142
- const document = await handleXMigration();
143
- const transaction = new ClientTransaction(document);
144
- await transaction.initialize();
145
- this.auth.generateTransactionId = transaction.generateTransactionId.bind(transaction);
157
+ const needsTransactionIds =
158
+ this.transactionIds !== undefined
159
+ ? this.transactionIds
160
+ : this.graphqlEndpoint === "web" || this.graphqlEndpoint === "web_twitter";
161
+
162
+ if (needsTransactionIds) {
163
+ const document = await handleXMigration();
164
+ const transaction = new ClientTransaction(document);
165
+ await transaction.initialize();
166
+ this.auth.generateTransactionId = transaction.generateTransactionId.bind(transaction);
167
+ }
146
168
 
147
169
  const responseText = await res.text();
148
170
  const initialStateMatch = responseText.match(/window\.__INITIAL_STATE__\s*=\s*({.*?});/s);