@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyneoaz/metachat",
3
- "version": "1.1.2",
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.",
@@ -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();
@@ -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/136.0.0.0 Safari/537.36",
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,
@@ -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 - Type of request: 'xhr' for GraphQL/AJAX or 'navigate' for page navigation
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': 'text/html,application/xhtml+xml,application/xml;q=0.9,application/json;q=0.8,*/*;q=0.7',
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
- if (isWindows || isMac || isLinux) {
186
- headers['Sec-Ch-Ua-Arch'] = '"x86"';
187
- headers['Sec-Ch-Ua-Bitness'] = '"64"';
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
+ };
@@ -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: ["139.0.0.0", "131.0.6778.86", "130.0.6723.92", "129.0.6668.101", "128.0.6613.120", "127.0.6533.120"],
8
- edgeVersions: ["139.0.0.0", "131.0.2903.51", "130.0.2849.68", "129.0.2792.89"],
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: ["139.0.0.0", "131.0.6778.86", "130.0.6723.92", "129.0.6668.101", "128.0.6613.120", "127.0.6533.120"],
14
- edgeVersions: ["139.0.0.0", "131.0.2903.51", "130.0.2849.68", "129.0.2792.89"],
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: ["139.0.0.0", "131.0.6778.86", "130.0.6723.92", "129.0.6668.101", "128.0.6613.120"],
20
- edgeVersions: ["139.0.0.0", "131.0.2903.51", "130.0.2849.68"],
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/139.0.0.0 Safari/537.36";
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.7 && data.edgeVersions;
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
- const greeseValue = Math.random() > 0.5 ? '99' : '8';
48
- const brands = useEdge ? [
49
- `"Chromium";v="${majorVersion}"`,
50
- `"Not(A:Brand";v="${greeseValue}"`,
51
- `"${browserName}";v="${majorVersion}"`
52
- ] : [
53
- `"${browserName}";v="${majorVersion}"`,
54
- `"Not;A=Brand";v="${greeseValue}"`,
55
- `"Chromium";v="${majorVersion}"`
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
- const match = b.match(/v="(\d+)"/);
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
+ };