@lazyneoaz/metachat 1.1.4 → 1.1.6
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 +1 -1
- package/src/engine/models/buildAPI.js +16 -0
- package/src/utils/headers.js +17 -26
- package/src/utils/index.js +13 -2
- package/src/utils/tokenRefresh.js +9 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyneoaz/metachat",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"types": "src/types/index.d.ts",
|
|
6
6
|
"description": "Advanced Facebook Chat API client for building Messenger bots — real-time messaging, thread management, MQTT, and session stability.",
|
|
@@ -74,6 +74,16 @@ async function buildAPI(html, jar, netData, globalOptions, fbLinkFunc, errorRetr
|
|
|
74
74
|
let region = mqttEndpoint ? new URL(mqttEndpoint).searchParams.get("region")?.toUpperCase() : undefined;
|
|
75
75
|
const irisSeqIDMatch = html.match(/irisSeqID:"(.+?)"/);
|
|
76
76
|
const irisSeqID = irisSeqIDMatch ? irisSeqIDMatch[1] : null;
|
|
77
|
+
|
|
78
|
+
// Extract Facebook client-state tokens that are embedded in every page load.
|
|
79
|
+
// Real browsers include these in every subsequent request to prove the request
|
|
80
|
+
// originated from a genuine page load. Missing them is a strong bot signal.
|
|
81
|
+
const spinRMatch = html.match(/"__spin_r"\s*:\s*(\d+)/);
|
|
82
|
+
const spinTMatch = html.match(/"__spin_t"\s*:\s*(\d+)/);
|
|
83
|
+
const __spin_r = spinRMatch ? parseInt(spinRMatch[1], 10) : null;
|
|
84
|
+
const __spin_t = spinTMatch ? parseInt(spinTMatch[1], 10) : null;
|
|
85
|
+
const __spin_b = 'trunk';
|
|
86
|
+
|
|
77
87
|
if (globalOptions.bypassRegion && mqttEndpoint) {
|
|
78
88
|
const currentEndpoint = new URL(mqttEndpoint);
|
|
79
89
|
currentEndpoint.searchParams.set('region', globalOptions.bypassRegion.toLowerCase());
|
|
@@ -116,6 +126,12 @@ async function buildAPI(html, jar, netData, globalOptions, fbLinkFunc, errorRetr
|
|
|
116
126
|
validator: globalValidator,
|
|
117
127
|
_emitter: emitter,
|
|
118
128
|
ttstamp,
|
|
129
|
+
// Client-state spin tokens extracted from the page HTML.
|
|
130
|
+
// These are included in every subsequent request body to prove the
|
|
131
|
+
// request originated from a real page load (not a standalone script).
|
|
132
|
+
__spin_r,
|
|
133
|
+
__spin_t,
|
|
134
|
+
__spin_b,
|
|
119
135
|
...dtsgResult,
|
|
120
136
|
};
|
|
121
137
|
const defaultFuncs = utils.makeDefaults(html, userID, ctx);
|
package/src/utils/headers.js
CHANGED
|
@@ -62,13 +62,12 @@ function getRandomTimezone() {
|
|
|
62
62
|
*
|
|
63
63
|
* Key fingerprint rules enforced here:
|
|
64
64
|
* - Navigate (page load): Accept = full HTML accept, Upgrade-Insecure-Requests = 1,
|
|
65
|
-
* Sec-Fetch-Dest/Mode/Site = document/navigate/none. NO
|
|
65
|
+
* Sec-Fetch-Dest/Mode/Site = document/navigate/none. NO Origin.
|
|
66
66
|
* - XHR (AJAX/API call): Accept = *\/*, Sec-Fetch-Dest/Mode/Site = empty/cors/same-origin,
|
|
67
|
-
* Origin = host. NO Upgrade-Insecure-Requests.
|
|
67
|
+
* Origin = host, X-Requested-With = XMLHttpRequest. NO Upgrade-Insecure-Requests.
|
|
68
68
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* Facebook's bot-detection system flags when seen on navigation requests.
|
|
69
|
+
* X-Requested-With is sent consistently on all XHR/API calls. Consistency matters —
|
|
70
|
+
* sending it sometimes but not others is itself a detectable pattern.
|
|
72
71
|
*
|
|
73
72
|
* @param {string} url - The target URL.
|
|
74
73
|
* @param {object} options - Global options from context.
|
|
@@ -147,12 +146,9 @@ function getHeaders(url, options, ctx, customHeader, requestType = 'navigate') {
|
|
|
147
146
|
if (ctx.region) {
|
|
148
147
|
headers['X-MSGR-Region'] = ctx.region;
|
|
149
148
|
}
|
|
150
|
-
if (ctx.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (__spin_b) headers['X-Fb-Spin-B'] = String(__spin_b);
|
|
154
|
-
if (__spin_t) headers['X-Fb-Spin-T'] = String(__spin_t);
|
|
155
|
-
}
|
|
149
|
+
if (ctx.__spin_r != null) headers['X-Fb-Spin-R'] = String(ctx.__spin_r);
|
|
150
|
+
if (ctx.__spin_b) headers['X-Fb-Spin-B'] = String(ctx.__spin_b);
|
|
151
|
+
if (ctx.__spin_t != null) headers['X-Fb-Spin-T'] = String(ctx.__spin_t);
|
|
156
152
|
}
|
|
157
153
|
|
|
158
154
|
if (customHeader) {
|
|
@@ -181,6 +177,7 @@ function getHeaders(url, options, ctx, customHeader, requestType = 'navigate') {
|
|
|
181
177
|
'Accept-Encoding': 'gzip, deflate, br',
|
|
182
178
|
'Cache-Control': 'no-cache',
|
|
183
179
|
'Connection': 'keep-alive',
|
|
180
|
+
'DNT': '1',
|
|
184
181
|
'Host': host,
|
|
185
182
|
'Pragma': 'no-cache',
|
|
186
183
|
'Referer': referer,
|
|
@@ -196,23 +193,18 @@ function getHeaders(url, options, ctx, customHeader, requestType = 'navigate') {
|
|
|
196
193
|
'User-Agent': userAgent,
|
|
197
194
|
};
|
|
198
195
|
|
|
199
|
-
// Upgrade-Insecure-Requests is a navigation-only hint — browsers never
|
|
200
|
-
// include it on XHR/fetch calls. Sending it on XHR is a bot fingerprint.
|
|
201
196
|
if (!isXhr) {
|
|
202
197
|
headers['Upgrade-Insecure-Requests'] = '1';
|
|
203
198
|
}
|
|
204
199
|
|
|
205
|
-
// Origin is only present on cross-origin or same-origin XHR/fetch requests.
|
|
206
|
-
// Real browsers do NOT send Origin on top-level page navigations (Sec-Fetch-Mode: navigate).
|
|
207
200
|
if (isXhr) {
|
|
208
201
|
headers['Origin'] = `https://${host}`;
|
|
202
|
+
// X-Requested-With is sent consistently on all XHR/API requests.
|
|
203
|
+
// Consistency matters — sending it sometimes but not others is itself
|
|
204
|
+
// a detectable pattern. Keep it always-on for API calls.
|
|
205
|
+
headers['X-Requested-With'] = 'XMLHttpRequest';
|
|
209
206
|
}
|
|
210
207
|
|
|
211
|
-
// X-Requested-With is NOT a browser-native header — it is a jQuery/XHR
|
|
212
|
-
// convention that Facebook's own modern web client does not set.
|
|
213
|
-
// Sending it (especially on navigate requests) is a well-known bot fingerprint
|
|
214
|
-
// that Facebook's detection system keys on. We never send it.
|
|
215
|
-
|
|
216
208
|
if (isWindows || isMac || isLinux) {
|
|
217
209
|
headers['Sec-Ch-Ua-Arch'] = '"x86"';
|
|
218
210
|
headers['Sec-Ch-Ua-Bitness'] = '"64"';
|
|
@@ -225,12 +217,11 @@ function getHeaders(url, options, ctx, customHeader, requestType = 'navigate') {
|
|
|
225
217
|
if (ctx.region) {
|
|
226
218
|
headers['X-MSGR-Region'] = ctx.region;
|
|
227
219
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
220
|
+
// Spin tokens are stored directly on ctx by buildAPI — ctx.master is
|
|
221
|
+
// never populated and reading from it was dead code that never fired.
|
|
222
|
+
if (ctx.__spin_r != null) headers['X-Fb-Spin-R'] = String(ctx.__spin_r);
|
|
223
|
+
if (ctx.__spin_b) headers['X-Fb-Spin-B'] = String(ctx.__spin_b);
|
|
224
|
+
if (ctx.__spin_t != null) headers['X-Fb-Spin-T'] = String(ctx.__spin_t);
|
|
234
225
|
}
|
|
235
226
|
|
|
236
227
|
if (customHeader) {
|
package/src/utils/index.js
CHANGED
|
@@ -59,7 +59,6 @@ function makeDefaults(html, userID, ctx) {
|
|
|
59
59
|
const network = require("./axios");
|
|
60
60
|
const constants = require("./constants");
|
|
61
61
|
|
|
62
|
-
let reqCounter = 1;
|
|
63
62
|
const revision = constants.getFrom(html, 'revision":', ",");
|
|
64
63
|
|
|
65
64
|
/**
|
|
@@ -68,10 +67,13 @@ function makeDefaults(html, userID, ctx) {
|
|
|
68
67
|
* @returns {object} The merged object.
|
|
69
68
|
*/
|
|
70
69
|
function mergeWithDefaults(obj) {
|
|
70
|
+
// __req must be random, not a sequential counter. A counter that resets to 1
|
|
71
|
+
// on every session start is a textbook bot fingerprint that Facebook's systems
|
|
72
|
+
// detect. Random values match real Chrome behaviour.
|
|
71
73
|
const newObj = {
|
|
72
74
|
av: userID,
|
|
73
75
|
__user: userID,
|
|
74
|
-
__req: (
|
|
76
|
+
__req: Math.floor(Math.random() * 36 ** 2).toString(36),
|
|
75
77
|
__rev: revision,
|
|
76
78
|
__a: 1,
|
|
77
79
|
...(ctx && {
|
|
@@ -80,6 +82,15 @@ function makeDefaults(html, userID, ctx) {
|
|
|
80
82
|
}),
|
|
81
83
|
};
|
|
82
84
|
|
|
85
|
+
// Include Facebook's client-state spin tokens when present. These are
|
|
86
|
+
// extracted from the page HTML at login time and prove each request
|
|
87
|
+
// originated from a real page load, not a standalone script.
|
|
88
|
+
if (ctx) {
|
|
89
|
+
if (ctx.__spin_r != null) newObj.__spin_r = ctx.__spin_r;
|
|
90
|
+
if (ctx.__spin_t != null) newObj.__spin_t = ctx.__spin_t;
|
|
91
|
+
if (ctx.__spin_b) newObj.__spin_b = ctx.__spin_b;
|
|
92
|
+
}
|
|
93
|
+
|
|
83
94
|
if (!obj) return newObj;
|
|
84
95
|
|
|
85
96
|
for (const prop in obj) {
|
|
@@ -111,7 +111,7 @@ class TokenRefreshManager {
|
|
|
111
111
|
const resp = await utils.get(
|
|
112
112
|
probeUrl,
|
|
113
113
|
ctx.jar,
|
|
114
|
-
{ reason: 'reconnect', __a: 1, __req:
|
|
114
|
+
{ reason: 'reconnect', __a: 1, __req: Math.floor(Math.random() * 36 ** 2).toString(36) },
|
|
115
115
|
ctx.globalOptions,
|
|
116
116
|
probeCtx
|
|
117
117
|
);
|
|
@@ -236,6 +236,14 @@ class TokenRefreshManager {
|
|
|
236
236
|
ctx.__rev = revisionMatch[1];
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
// Refresh spin tokens alongside fb_dtsg — they are page-state values
|
|
240
|
+
// embedded in every HTML response and must stay current so requests
|
|
241
|
+
// continue to pass Facebook's client-state validation.
|
|
242
|
+
const spinRMatch = html.match(/"__spin_r"\s*:\s*(\d+)/);
|
|
243
|
+
const spinTMatch = html.match(/"__spin_t"\s*:\s*(\d+)/);
|
|
244
|
+
if (spinRMatch) ctx.__spin_r = parseInt(spinRMatch[1], 10);
|
|
245
|
+
if (spinTMatch) ctx.__spin_t = parseInt(spinTMatch[1], 10);
|
|
246
|
+
|
|
239
247
|
this.lastRefresh = Date.now();
|
|
240
248
|
this.failureCount = 0;
|
|
241
249
|
|