@lazyneoaz/metachat 1.1.2 → 1.1.4
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/apis/sendMessage.js +25 -0
- package/src/apis/sendMessageMqtt.js +0 -5
- package/src/engine/client.js +1 -1
- package/src/utils/antiSuspension.js +0 -29
- package/src/utils/headers.js +38 -12
- package/src/utils/user-agents.js +86 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyneoaz/metachat",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
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.",
|
package/src/apis/sendMessage.js
CHANGED
|
@@ -285,7 +285,29 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
285
285
|
return callback(new Error("Dissallowed props: `" + disallowedProperties.join(", ") + "`"));
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
// Declare typing state here so the finally block never hits a ReferenceError
|
|
289
|
+
// in strict mode. These are set below if simulateTyping is enabled.
|
|
290
|
+
let typingTimeout = null;
|
|
291
|
+
let typingStarted = false;
|
|
292
|
+
|
|
288
293
|
try {
|
|
294
|
+
// Simulate human typing delay when the option is enabled and there is
|
|
295
|
+
// text to type. The indicator is sent first, we wait the computed delay,
|
|
296
|
+
// then the actual send follows. The finally block stops the indicator.
|
|
297
|
+
if (ctx.globalOptions.simulateTyping && msg.body && typeof api.sendTypingIndicator === 'function') {
|
|
298
|
+
try {
|
|
299
|
+
const typingDelay = await globalAntiSuspension.simulateTyping(threadID, msg.body.length);
|
|
300
|
+
await api.sendTypingIndicator(true, threadID);
|
|
301
|
+
typingStarted = true;
|
|
302
|
+
await new Promise(resolve => {
|
|
303
|
+
typingTimeout = setTimeout(resolve, typingDelay);
|
|
304
|
+
});
|
|
305
|
+
} catch (_) {
|
|
306
|
+
// Typing simulation is best-effort — a failure here must never
|
|
307
|
+
// block the actual message send.
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
289
311
|
try {
|
|
290
312
|
const mqttReady = ctx.mqttClient && ctx.mqttClient.connected;
|
|
291
313
|
const isMultiRecipient = Array.isArray(threadID);
|
|
@@ -327,6 +349,9 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
327
349
|
}
|
|
328
350
|
}
|
|
329
351
|
} finally {
|
|
352
|
+
// Stop typing indicator regardless of success or failure.
|
|
353
|
+
// typingTimeout and typingStarted are always declared above so this
|
|
354
|
+
// block can never throw a ReferenceError in strict mode.
|
|
330
355
|
if (typingTimeout) clearTimeout(typingTimeout);
|
|
331
356
|
if (typingStarted) {
|
|
332
357
|
try { await api.sendTypingIndicator(false, threadID); } catch (_) {}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const utils = require('../utils');
|
|
4
|
-
const { globalAntiSuspension } = require('../utils/antiSuspension');
|
|
5
4
|
|
|
6
5
|
module.exports = (defaultFuncs, api, ctx) => {
|
|
7
6
|
function detectAttachmentType(attachment) {
|
|
@@ -143,10 +142,6 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
143
142
|
replyToMessage = null;
|
|
144
143
|
}
|
|
145
144
|
|
|
146
|
-
try {
|
|
147
|
-
await globalAntiSuspension.prepareBeforeMessage(String(threadID), typeof msg === "string" ? msg : (msg.body || ""));
|
|
148
|
-
} catch (_) {}
|
|
149
|
-
|
|
150
145
|
const normalized = typeof msg === "string" ? { body: msg } : msg;
|
|
151
146
|
const baseBody = normalized.body != null ? String(normalized.body) : "";
|
|
152
147
|
const epoch = (BigInt(Date.now()) << 22n).toString();
|
package/src/engine/client.js
CHANGED
|
@@ -26,7 +26,7 @@ const DEFAULT_OPTIONS = {
|
|
|
26
26
|
autoReconnect: true,
|
|
27
27
|
online: true,
|
|
28
28
|
emitReady: false,
|
|
29
|
-
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
29
|
+
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.182 Safari/537.36",
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
/**
|
|
@@ -459,35 +459,6 @@ class AntiSuspension {
|
|
|
459
459
|
return results;
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
-
/**
|
|
463
|
-
* Prepare before sending — single delay model.
|
|
464
|
-
* Enforces thread throttle and volume limits, respects circuit breaker.
|
|
465
|
-
* If the circuit breaker is tripped (checkpoint/suspension detected) or
|
|
466
|
-
* volume limits are reached, throws to protect the account.
|
|
467
|
-
*/
|
|
468
|
-
async prepareBeforeMessage(threadID, message) {
|
|
469
|
-
if (this.isCircuitBreakerTripped()) {
|
|
470
|
-
const remaining = this.getCircuitBreakerRemainingMs();
|
|
471
|
-
const { utils } = this._getUtils();
|
|
472
|
-
utils && utils.warn && utils.warn("AntiSuspension",
|
|
473
|
-
`Circuit breaker is tripped. Blocking message send to protect account. ` +
|
|
474
|
-
`Cooldown remaining: ${Math.ceil(remaining / 1000)}s`);
|
|
475
|
-
const err = new Error(`Circuit breaker is tripped. Pausing sends to protect account. Retry in ${Math.ceil(remaining / 1000)}s.`);
|
|
476
|
-
err.error = 'circuit_breaker_tripped';
|
|
477
|
-
err.remainingMs = remaining;
|
|
478
|
-
throw err;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const volumeWarning = this.checkVolumeLimit(threadID);
|
|
482
|
-
if (volumeWarning) {
|
|
483
|
-
const { utils } = this._getUtils();
|
|
484
|
-
utils && utils.warn && utils.warn("AntiSuspension", volumeWarning);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
await this.enforceThreadThrottling(threadID);
|
|
488
|
-
this._incrementDailyStats(threadID);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
462
|
getConfig() {
|
|
492
463
|
return {
|
|
493
464
|
messageDelayMs: this.messageDelayMs,
|
package/src/utils/headers.js
CHANGED
|
@@ -59,16 +59,28 @@ function getRandomTimezone() {
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Generates a comprehensive and realistic set of headers for requests to Facebook.
|
|
62
|
+
*
|
|
63
|
+
* Key fingerprint rules enforced here:
|
|
64
|
+
* - Navigate (page load): Accept = full HTML accept, Upgrade-Insecure-Requests = 1,
|
|
65
|
+
* Sec-Fetch-Dest/Mode/Site = document/navigate/none. NO X-Requested-With, NO Origin.
|
|
66
|
+
* - XHR (AJAX/API call): Accept = *\/*, Sec-Fetch-Dest/Mode/Site = empty/cors/same-origin,
|
|
67
|
+
* Origin = host. NO Upgrade-Insecure-Requests.
|
|
68
|
+
*
|
|
69
|
+
* Real Chrome never sends X-Requested-With automatically — it is a jQuery/XHR
|
|
70
|
+
* legacy header that Facebook's own modern client does NOT include and that
|
|
71
|
+
* Facebook's bot-detection system flags when seen on navigation requests.
|
|
72
|
+
*
|
|
62
73
|
* @param {string} url - The target URL.
|
|
63
74
|
* @param {object} options - Global options from context.
|
|
64
75
|
* @param {object} ctx - The application context (containing fb_dtsg, lsd, etc.).
|
|
65
76
|
* @param {object} customHeader - Any extra headers to merge.
|
|
66
|
-
* @param {string} requestType -
|
|
77
|
+
* @param {string} requestType - 'xhr' for GraphQL/AJAX or 'navigate' for page navigation.
|
|
67
78
|
* @returns {object} A complete headers object.
|
|
68
79
|
*/
|
|
69
80
|
function getHeaders(url, options, ctx, customHeader, requestType = 'navigate') {
|
|
70
81
|
const persona = options?.persona || 'desktop';
|
|
71
82
|
const isAndroid = persona === 'android' || persona === 'mobile';
|
|
83
|
+
const isXhr = requestType === 'xhr';
|
|
72
84
|
|
|
73
85
|
let userAgent, secChUa, secChUaFullVersionList, secChUaPlatform, secChUaPlatformVersion;
|
|
74
86
|
let androidData = null;
|
|
@@ -110,8 +122,6 @@ function getHeaders(url, options, ctx, customHeader, requestType = 'navigate') {
|
|
|
110
122
|
const host = new URL(url).hostname;
|
|
111
123
|
const referer = `https://${host}/`;
|
|
112
124
|
|
|
113
|
-
const isXhr = requestType === 'xhr';
|
|
114
|
-
|
|
115
125
|
const locales = options?.cachedLocale || (androidData?.locale ? androidData.locale.replace('_', '-') : getRandomLocale());
|
|
116
126
|
|
|
117
127
|
if (isAndroid) {
|
|
@@ -159,38 +169,55 @@ function getHeaders(url, options, ctx, customHeader, requestType = 'navigate') {
|
|
|
159
169
|
const isMac = secChUaPlatform === '"macOS"';
|
|
160
170
|
const isLinux = secChUaPlatform === '"Linux"';
|
|
161
171
|
|
|
172
|
+
// Navigate (page load) Accept mirrors real Chrome — includes avif/webp/apng
|
|
173
|
+
// XHR Accept is simply */* as sent by Chrome's fetch/XHR APIs
|
|
174
|
+
const acceptHeader = isXhr
|
|
175
|
+
? '*/*'
|
|
176
|
+
: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7';
|
|
177
|
+
|
|
162
178
|
const headers = {
|
|
163
|
-
'Accept':
|
|
179
|
+
'Accept': acceptHeader,
|
|
164
180
|
'Accept-Language': locales,
|
|
165
181
|
'Accept-Encoding': 'gzip, deflate, br',
|
|
166
182
|
'Cache-Control': 'no-cache',
|
|
167
183
|
'Connection': 'keep-alive',
|
|
168
|
-
'DNT': '1',
|
|
169
184
|
'Host': host,
|
|
170
185
|
'Pragma': 'no-cache',
|
|
171
186
|
'Referer': referer,
|
|
172
187
|
'Sec-Ch-Ua': secChUa,
|
|
173
188
|
'Sec-Ch-Ua-Full-Version-List': secChUaFullVersionList,
|
|
174
189
|
'Sec-Ch-Ua-Mobile': '?0',
|
|
190
|
+
'Sec-Ch-Ua-Model': '""',
|
|
175
191
|
'Sec-Ch-Ua-Platform': secChUaPlatform,
|
|
176
192
|
'Sec-Ch-Ua-Platform-Version': secChUaPlatformVersion,
|
|
177
193
|
'Sec-Fetch-Dest': isXhr ? 'empty' : 'document',
|
|
178
194
|
'Sec-Fetch-Mode': isXhr ? 'cors' : 'navigate',
|
|
179
195
|
'Sec-Fetch-Site': isXhr ? 'same-origin' : 'none',
|
|
180
196
|
'User-Agent': userAgent,
|
|
181
|
-
'Upgrade-Insecure-Requests': '1',
|
|
182
|
-
'X-Requested-With': 'XMLHttpRequest'
|
|
183
197
|
};
|
|
184
198
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
+
if (!isXhr) {
|
|
202
|
+
headers['Upgrade-Insecure-Requests'] = '1';
|
|
188
203
|
}
|
|
189
204
|
|
|
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).
|
|
190
207
|
if (isXhr) {
|
|
191
208
|
headers['Origin'] = `https://${host}`;
|
|
192
209
|
}
|
|
193
210
|
|
|
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
|
+
if (isWindows || isMac || isLinux) {
|
|
217
|
+
headers['Sec-Ch-Ua-Arch'] = '"x86"';
|
|
218
|
+
headers['Sec-Ch-Ua-Bitness'] = '"64"';
|
|
219
|
+
}
|
|
220
|
+
|
|
194
221
|
if (ctx) {
|
|
195
222
|
if (ctx.lsd || ctx.fb_dtsg) {
|
|
196
223
|
headers['X-Fb-Lsd'] = ctx.lsd || ctx.fb_dtsg;
|
|
@@ -206,7 +233,6 @@ function getHeaders(url, options, ctx, customHeader, requestType = 'navigate') {
|
|
|
206
233
|
}
|
|
207
234
|
}
|
|
208
235
|
|
|
209
|
-
|
|
210
236
|
if (customHeader) {
|
|
211
237
|
Object.assign(headers, customHeader);
|
|
212
238
|
if (customHeader.noRef) {
|
|
@@ -224,4 +250,4 @@ module.exports = {
|
|
|
224
250
|
meta,
|
|
225
251
|
getRandomLocale,
|
|
226
252
|
getRandomTimezone
|
|
227
|
-
};
|
|
253
|
+
};
|
package/src/utils/user-agents.js
CHANGED
|
@@ -1,28 +1,81 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const { getRandom } = require("./constants");
|
|
3
3
|
|
|
4
|
+
// Chrome versions ordered newest-first. Keep this list current — Facebook's
|
|
5
|
+
// bot-detection compares claimed UA versions against known release timelines.
|
|
6
|
+
// Versions that are too old (pre-120) or too new (unreleased) are flagged.
|
|
4
7
|
const BROWSER_DATA = {
|
|
5
8
|
windows: {
|
|
6
9
|
platform: "Windows NT 10.0; Win64; x64",
|
|
7
|
-
chromeVersions: [
|
|
8
|
-
|
|
10
|
+
chromeVersions: [
|
|
11
|
+
"143.0.7499.182", "142.0.7410.114", "141.0.7358.100",
|
|
12
|
+
"140.0.7294.114", "139.0.7258.100", "138.0.7204.92",
|
|
13
|
+
"137.0.7151.68", "136.0.7103.113", "135.0.7049.96",
|
|
14
|
+
"134.0.6998.165"
|
|
15
|
+
],
|
|
16
|
+
edgeVersions: [
|
|
17
|
+
"143.0.7499.182", "142.0.7410.114", "141.0.7358.100",
|
|
18
|
+
"140.0.7294.114", "139.0.7258.100"
|
|
19
|
+
],
|
|
9
20
|
platformVersion: '"15.0.0"'
|
|
10
21
|
},
|
|
11
22
|
mac: {
|
|
12
23
|
platform: "Macintosh; Intel Mac OS X 10_15_7",
|
|
13
|
-
chromeVersions: [
|
|
14
|
-
|
|
24
|
+
chromeVersions: [
|
|
25
|
+
"143.0.7499.182", "142.0.7410.114", "141.0.7358.100",
|
|
26
|
+
"140.0.7294.114", "139.0.7258.100", "138.0.7204.92",
|
|
27
|
+
"137.0.7151.68", "136.0.7103.113", "135.0.7049.96",
|
|
28
|
+
"134.0.6998.165"
|
|
29
|
+
],
|
|
30
|
+
edgeVersions: [
|
|
31
|
+
"143.0.7499.182", "142.0.7410.114", "141.0.7358.100",
|
|
32
|
+
"140.0.7294.114", "139.0.7258.100"
|
|
33
|
+
],
|
|
15
34
|
platformVersion: '"14.7.0"'
|
|
16
35
|
},
|
|
17
36
|
linux: {
|
|
18
37
|
platform: "X11; Linux x86_64",
|
|
19
|
-
chromeVersions: [
|
|
20
|
-
|
|
38
|
+
chromeVersions: [
|
|
39
|
+
"143.0.7499.182", "142.0.7410.114", "141.0.7358.100",
|
|
40
|
+
"140.0.7294.114", "139.0.7258.100", "138.0.7204.92",
|
|
41
|
+
"137.0.7151.68", "136.0.7103.113"
|
|
42
|
+
],
|
|
43
|
+
edgeVersions: [
|
|
44
|
+
"143.0.7499.182", "142.0.7410.114", "141.0.7358.100",
|
|
45
|
+
"140.0.7294.114"
|
|
46
|
+
],
|
|
21
47
|
platformVersion: '""'
|
|
22
48
|
}
|
|
23
49
|
};
|
|
24
50
|
|
|
25
|
-
const defaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
51
|
+
const defaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.182 Safari/537.36";
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Builds the correct GREASE token for a given major Chrome version.
|
|
55
|
+
*
|
|
56
|
+
* Chrome picks one of several "not a brand" placeholder strings via a
|
|
57
|
+
* deterministic algorithm keyed to the major version. Using the wrong
|
|
58
|
+
* GREASE value is a detectable fingerprint mismatch.
|
|
59
|
+
*
|
|
60
|
+
* The set used by Chrome 100+ rotates through these four forms based on
|
|
61
|
+
* (major % 4):
|
|
62
|
+
* 0 → "Not)A;Brand"
|
|
63
|
+
* 1 → "Not A(Brand"
|
|
64
|
+
* 2 → "Not;A=Brand"
|
|
65
|
+
* 3 → "Not A Brand" (older alias)
|
|
66
|
+
*
|
|
67
|
+
* @param {number} major - Chrome major version number.
|
|
68
|
+
* @returns {string} - GREASE brand string without the surrounding quotes.
|
|
69
|
+
*/
|
|
70
|
+
function greaseForVersion(major) {
|
|
71
|
+
const forms = [
|
|
72
|
+
"Not)A;Brand",
|
|
73
|
+
"Not A(Brand",
|
|
74
|
+
"Not;A=Brand",
|
|
75
|
+
"Not A Brand"
|
|
76
|
+
];
|
|
77
|
+
return forms[major % 4];
|
|
78
|
+
}
|
|
26
79
|
|
|
27
80
|
/**
|
|
28
81
|
* Generates a realistic, randomized User-Agent string and related Sec-CH headers.
|
|
@@ -33,32 +86,42 @@ function randomUserAgent() {
|
|
|
33
86
|
const os = getRandom(Object.keys(BROWSER_DATA));
|
|
34
87
|
const data = BROWSER_DATA[os];
|
|
35
88
|
|
|
36
|
-
const useEdge = Math.random() > 0.
|
|
89
|
+
const useEdge = Math.random() > 0.75 && data.edgeVersions && data.edgeVersions.length > 0;
|
|
37
90
|
const versions = useEdge ? data.edgeVersions : data.chromeVersions;
|
|
38
91
|
const version = getRandom(versions);
|
|
39
92
|
const majorVersion = version.split('.')[0];
|
|
93
|
+
const major = parseInt(majorVersion, 10);
|
|
40
94
|
const browserName = useEdge ? 'Microsoft Edge' : 'Google Chrome';
|
|
41
|
-
const browserIdentifier = useEdge ? 'Edg' : 'Chrome';
|
|
42
95
|
|
|
43
|
-
const userAgent = useEdge
|
|
96
|
+
const userAgent = useEdge
|
|
44
97
|
? `Mozilla/5.0 (${data.platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36 Edg/${version}`
|
|
45
98
|
: `Mozilla/5.0 (${data.platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36`;
|
|
46
99
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
100
|
+
// Use the version-correct GREASE placeholder — mismatched GREASE is a
|
|
101
|
+
// detectable fingerprint that Facebook's bot detection system checks.
|
|
102
|
+
const greaseStr = greaseForVersion(major);
|
|
103
|
+
const greaseVersion = "99";
|
|
104
|
+
|
|
105
|
+
let brands;
|
|
106
|
+
if (useEdge) {
|
|
107
|
+
brands = [
|
|
108
|
+
`"Chromium";v="${majorVersion}"`,
|
|
109
|
+
`"${browserName}";v="${majorVersion}"`,
|
|
110
|
+
`"${greaseStr}";v="${greaseVersion}"`
|
|
111
|
+
];
|
|
112
|
+
} else {
|
|
113
|
+
brands = [
|
|
114
|
+
`"${browserName}";v="${majorVersion}"`,
|
|
115
|
+
`"Chromium";v="${majorVersion}"`,
|
|
116
|
+
`"${greaseStr}";v="${greaseVersion}"`
|
|
117
|
+
];
|
|
118
|
+
}
|
|
57
119
|
|
|
58
120
|
const secChUa = brands.join(', ');
|
|
121
|
+
|
|
122
|
+
// Full version list replaces the major-only version with the full string
|
|
59
123
|
const secChUaFullVersionList = brands.map(b => {
|
|
60
|
-
|
|
61
|
-
if (match && match[1] === majorVersion) {
|
|
124
|
+
if (b.includes(`v="${majorVersion}"`)) {
|
|
62
125
|
return b.replace(`v="${majorVersion}"`, `v="${version}"`);
|
|
63
126
|
}
|
|
64
127
|
return b;
|
|
@@ -235,4 +298,4 @@ module.exports = {
|
|
|
235
298
|
randomOrcaUA,
|
|
236
299
|
generateUserAgentByPersona,
|
|
237
300
|
cachePersonaData,
|
|
238
|
-
};
|
|
301
|
+
};
|