@lazyneoaz/metachat 1.1.1 → 1.1.3

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/index.js CHANGED
@@ -54,3 +54,7 @@ module.exports.createRequestHelper = createRequestHelper;
54
54
  module.exports.normalizeCookieHeaderString = normalizeCookieHeaderString;
55
55
  module.exports.setJarFromPairs = setJarFromPairs;
56
56
  module.exports.createAuthCore = createAuthCore;
57
+
58
+ module.exports.getVersion = function getVersion() {
59
+ return require('./package.json').version;
60
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyneoaz/metachat",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
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.",
@@ -37,6 +37,7 @@
37
37
  "metachat"
38
38
  ],
39
39
  "dependencies": {
40
+ "@dongdev/fca-unofficial": "^4.0.3",
40
41
  "axios": "^1.13.5",
41
42
  "axios-cookiejar-support": "^4.0.7",
42
43
  "bluebird": "^3.7.2",
@@ -67,7 +67,7 @@ module.exports = function (defaultFuncs, api, ctx) {
67
67
  });
68
68
 
69
69
  const form = JSON.stringify({
70
- app_id: String(ctx.appID || ctx.mqttAppID || "2220391788200892"),
70
+ app_id: "2220391788200892",
71
71
  payload: JSON.stringify({
72
72
  epoch_id: epochID,
73
73
  tasks: tasks,
@@ -114,7 +114,7 @@ module.exports = function (defaultFuncs, api, ctx) {
114
114
  epoch_id: epochID,
115
115
  data_trace_id: null
116
116
  }),
117
- app_id: String(ctx.appID || ctx.mqttAppID || "772021112871879")
117
+ app_id: "2220391788200892"
118
118
  };
119
119
 
120
120
  defaultFuncs
@@ -110,7 +110,7 @@ module.exports = (defaultFuncs, api, ctx) => {
110
110
  };
111
111
 
112
112
  const request = {
113
- app_id: String(ctx.appID || ctx.mqttAppID || "2220391788200892"),
113
+ app_id: "2220391788200892",
114
114
  payload: JSON.stringify(mqttPayload),
115
115
  request_id: reqID,
116
116
  type: 3
@@ -58,7 +58,7 @@ module.exports = (defaultFuncs, api, ctx) => {
58
58
  };
59
59
 
60
60
  const form = JSON.stringify({
61
- app_id: "772021112871879",
61
+ app_id: "2220391788200892",
62
62
  payload: JSON.stringify(payload),
63
63
  request_id: ++ctx.wsReqNumber,
64
64
  type: 3
@@ -1,9 +1,20 @@
1
1
  "use strict";
2
2
 
3
3
  const utils = require('../utils');
4
+ const { publishLsRequestWithAck } = require('../utils/lsRequest');
4
5
 
5
6
  module.exports = (defaultFuncs, api, ctx) => {
6
- return async function deleteMessage(messageID, callback) {
7
+ /**
8
+ * Deletes one or more messages.
9
+ *
10
+ * Uses MQTT label "146" (version "25909428212080747") when the MQTT client is
11
+ * connected — this matches dongdev's deleteMessage implementation.
12
+ * Falls back to the legacy HTTP delete_messages.php endpoint otherwise.
13
+ *
14
+ * @param {string|string[]} messageOrMessages
15
+ * @param {Function} [callback]
16
+ */
17
+ return async function deleteMessage(messageOrMessages, callback) {
7
18
  let resolveFunc = () => {};
8
19
  let rejectFunc = () => {};
9
20
  const returnPromise = new Promise((resolve, reject) => {
@@ -26,17 +37,66 @@ module.exports = (defaultFuncs, api, ctx) => {
26
37
  }
27
38
 
28
39
  try {
29
- const res = await defaultFuncs.post(
30
- "https://www.facebook.com/ajax/mercury/delete_messages.php",
31
- ctx.jar,
32
- { message_id: messageID }
33
- ).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
40
+ const messages = Array.isArray(messageOrMessages)
41
+ ? messageOrMessages
42
+ : [messageOrMessages];
34
43
 
35
- if (res && res.error) {
36
- throw new Error(String(res.error_msg || res.error || "deleteMessage failed"));
44
+ if (messages.length === 0 || messages.some(v => v === null || v === undefined || v === "")) {
45
+ throw new Error("messageOrMessages must contain at least one non-empty message ID");
46
+ }
47
+
48
+ const mqttReady = !!(ctx.mqttClient && ctx.mqttClient.connected);
49
+
50
+ if (mqttReady) {
51
+ // MQTT path: label "146", version "25909428212080747" (dongdev confirmed)
52
+ if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
53
+ if (typeof ctx.wsTaskNumber !== "number") ctx.wsTaskNumber = 0;
54
+
55
+ const requestId = ++ctx.wsReqNumber;
56
+ const tasks = messages.map((messageID) => {
57
+ const queueName = String(messageID);
58
+ const taskId = ++ctx.wsTaskNumber;
59
+ return {
60
+ failure_count: null,
61
+ label: "146",
62
+ payload: JSON.stringify({
63
+ thread_key: queueName,
64
+ remove_type: 0,
65
+ sync_group: 1
66
+ }),
67
+ queue_name: queueName,
68
+ task_id: taskId
69
+ };
70
+ });
71
+
72
+ publishLsRequestWithAck({
73
+ client: ctx.mqttClient,
74
+ requestId,
75
+ timeoutMs: 20000,
76
+ content: {
77
+ app_id: "2220391788200892",
78
+ payload: JSON.stringify({
79
+ epoch_id: parseInt(utils.generateOfflineThreadingID()),
80
+ tasks,
81
+ version_id: "25909428212080747"
82
+ }),
83
+ request_id: requestId,
84
+ type: 3
85
+ },
86
+ extract: (message) => ({ success: true, response: message.payload })
87
+ }).then((result) => {
88
+ callback(null, result);
89
+ }).catch((mqttErr) => {
90
+ utils.error("deleteMessage/mqtt", mqttErr);
91
+ // MQTT failed → fall back to HTTP
92
+ _deleteViaHttp(messages, callback, defaultFuncs, ctx);
93
+ });
94
+
95
+ } else {
96
+ // HTTP fallback
97
+ await _deleteViaHttp(messages, callback, defaultFuncs, ctx);
37
98
  }
38
99
 
39
- callback(null, { success: true });
40
100
  } catch (err) {
41
101
  utils.error("deleteMessage", err);
42
102
  callback(err instanceof Error ? err : new Error(String(err && err.message ? err.message : err)));
@@ -45,3 +105,24 @@ module.exports = (defaultFuncs, api, ctx) => {
45
105
  return returnPromise;
46
106
  };
47
107
  };
108
+
109
+ async function _deleteViaHttp(messages, callback, defaultFuncs, ctx) {
110
+ try {
111
+ // The legacy endpoint only accepts one messageID at a time
112
+ for (const messageID of messages) {
113
+ const res = await defaultFuncs.post(
114
+ "https://www.facebook.com/ajax/mercury/delete_messages.php",
115
+ ctx.jar,
116
+ { message_id: messageID }
117
+ ).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
118
+
119
+ if (res && res.error) {
120
+ throw new Error(String(res.error_msg || res.error || "deleteMessage HTTP failed"));
121
+ }
122
+ }
123
+ callback(null, { success: true });
124
+ } catch (err) {
125
+ utils.error("deleteMessage/http", err);
126
+ callback(err instanceof Error ? err : new Error(String(err && err.message ? err.message : err)));
127
+ }
128
+ }
package/src/apis/emoji.js CHANGED
@@ -92,11 +92,11 @@ module.exports = function (defaultFuncs, api, ctx) {
92
92
  };
93
93
 
94
94
  const context = {
95
- app_id: ctx.appID || '2220391788200892',
95
+ app_id: '2220391788200892',
96
96
  payload: {
97
97
  epoch_id: parseInt(utils.generateOfflineThreadingID()),
98
98
  tasks: [query],
99
- version_id: '24631415369801570',
99
+ version_id: '8798795233522156',
100
100
  },
101
101
  request_id: ctx.wsReqNumber,
102
102
  type: 3,
@@ -1,128 +1,172 @@
1
1
  "use strict";
2
2
 
3
3
  const utils = require('../utils');
4
+ const { publishLsRequestWithAck } = require('../utils/lsRequest');
4
5
 
5
6
  module.exports = function (defaultFuncs, api, ctx) {
6
7
  /**
7
- * Made by ChoruOfficial
8
- * Mqtt
9
- * Adds or removes members from a group chat with pre-checking.
8
+ * Adds or removes members from a group chat.
10
9
  *
11
- * @param {"add" | "remove"} action The action to perform.
12
- * @param {string|string[]} userIDs The user ID or array of user IDs.
13
- * @param {string} threadID The ID of the group chat.
14
- * @param {Function} [callback] Optional callback function.
15
- * @returns {Promise<object>} A promise that resolves with information about the action.
10
+ * Add uses publishLsRequestWithAck (label "23", app_id "772021112871879",
11
+ * version "24502707779384158") matches dongdev addUserToGroup.
12
+ * Remove uses fire-and-forget publish (label "140", app_id "2220391788200892",
13
+ * version "25002366262773827") — matches dongdev removeUserFromGroup.
14
+ *
15
+ * @param {"add"|"remove"} action
16
+ * @param {string|string[]} userIDs
17
+ * @param {string} threadID
18
+ * @param {Function} [callback]
16
19
  */
17
20
  return async function gcmember(action, userIDs, threadID, callback) {
18
21
  let _callback;
19
22
  if (typeof threadID === 'function') {
20
- _callback = threadID;
21
- threadID = null;
23
+ _callback = threadID;
24
+ threadID = null;
22
25
  } else if (typeof callback === 'function') {
23
- _callback = callback;
26
+ _callback = callback;
24
27
  }
25
-
28
+
26
29
  let resolvePromise, rejectPromise;
27
30
  const returnPromise = new Promise((resolve, reject) => {
28
- resolvePromise = resolve;
29
- rejectPromise = reject;
31
+ resolvePromise = resolve;
32
+ rejectPromise = reject;
30
33
  });
31
34
 
32
- if (typeof _callback != "function") {
35
+ if (typeof _callback !== "function") {
33
36
  _callback = (err, data) => {
34
- // Note: We will now rarely use the 'err' parameter for validation errors
35
37
  if (err) return rejectPromise(err);
36
38
  resolvePromise(data);
37
- }
39
+ };
38
40
  }
39
41
 
40
42
  try {
41
- const validActions = ["add", "remove"];
42
- action = action ? action.toLowerCase() : "";
43
+ const validActions = ["add", "remove"];
44
+ action = action ? action.toLowerCase() : "";
43
45
 
46
+ if (!validActions.includes(action)) {
47
+ _callback(null, { type: "error_gc", error: `Invalid action. Must be one of: ${validActions.join(", ")}` });
48
+ return returnPromise;
49
+ }
50
+ if (!userIDs || (Array.isArray(userIDs) ? userIDs.length === 0 : false)) {
51
+ _callback(null, { type: "error_gc", error: "userIDs is required." });
52
+ return returnPromise;
53
+ }
54
+ if (!threadID) {
55
+ _callback(null, { type: "error_gc", error: "threadID is required." });
56
+ return returnPromise;
57
+ }
58
+ if (!ctx.mqttClient || !ctx.mqttClient.connected) {
59
+ _callback(null, { type: "error_gc", error: "Not connected to MQTT" });
60
+ return returnPromise;
61
+ }
44
62
 
45
- if (!validActions.includes(action)) {
46
- _callback(null, { type: "error_gc", error: `Invalid action. Must be one of: ${validActions.join(", ")}` });
47
- return returnPromise;
48
- }
49
- if (!userIDs || userIDs.length === 0) {
50
- _callback(null, { type: "error_gc", error: "userIDs is required." });
51
- return returnPromise;
52
- }
53
- if (!threadID) {
54
- _callback(null, { type: "error_gc", error: "threadID is required." });
55
- return returnPromise;
56
- }
57
- if (!ctx.mqttClient || !ctx.mqttClient.connected) {
58
- _callback(null, { type: "error_gc", error: "Not connected to MQTT" });
59
- return returnPromise;
60
- }
63
+ const threadInfo = await api.getThreadInfo(threadID);
64
+ if (!threadInfo) {
65
+ _callback(null, { type: "error_gc", error: "Could not retrieve thread information." });
66
+ return returnPromise;
67
+ }
68
+ if (threadInfo.isGroup === false) {
69
+ _callback(null, { type: "error_gc", error: "This feature is only for group chats, not private messages." });
70
+ return returnPromise;
71
+ }
61
72
 
62
- const threadInfo = await api.getThreadInfo(threadID);
63
- if (!threadInfo) {
64
- _callback(null, { type: "error_gc", error: "Could not retrieve thread information." });
65
- return returnPromise;
66
- }
67
- if (threadInfo.isGroup === false) {
68
- _callback(null, { type: "error_gc", error: "This feature is only for group chats, not private messages." });
69
- return returnPromise;
73
+ const currentMembers = threadInfo.participantIDs || [];
74
+ const usersToModify = Array.isArray(userIDs) ? userIDs : [userIDs];
75
+
76
+ if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
77
+ if (typeof ctx.wsTaskNumber !== "number") ctx.wsTaskNumber = 0;
78
+
79
+ if (action === 'add') {
80
+ // Filter out users already in the group
81
+ const usersToAdd = usersToModify.filter(id => !currentMembers.includes(String(id)));
82
+ if (usersToAdd.length === 0) {
83
+ _callback(null, { type: "error_gc", error: "All specified users are already in the group." });
84
+ return returnPromise;
70
85
  }
71
-
72
- const currentMembers = threadInfo.participantIDs;
73
- const usersToModify = Array.isArray(userIDs) ? userIDs : [userIDs];
74
- let queryPayload, query;
75
- let finalUsers = usersToModify;
76
-
77
- ctx.wsReqNumber = (ctx.wsReqNumber || 0) + 1;
78
- ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1;
79
-
80
- if (action === 'add') {
81
- const usersToAdd = usersToModify.filter(id => !currentMembers.includes(id));
82
- if (usersToAdd.length === 0) {
83
- _callback(null, { type: "error_gc", error: "All specified users are already in the group." });
84
- return returnPromise;
85
- }
86
- finalUsers = usersToAdd;
87
- queryPayload = { thread_key: parseInt(threadID), contact_ids: finalUsers.map(id => parseInt(id)), sync_group: 1 };
88
- query = { label: "23", payload: JSON.stringify(queryPayload), queue_name: threadID, task_id: ctx.wsTaskNumber };
89
-
90
- } else { // action is 'remove'
91
- const userToRemove = usersToModify[0];
92
- if (!currentMembers.includes(userToRemove)) {
93
- _callback(null, { type: "error_gc", error: `User with ID ${userToRemove} is not in this group chat.` });
94
- return returnPromise;
95
- }
96
- finalUsers = [userToRemove];
97
- queryPayload = { thread_id: threadID, contact_id: userToRemove, sync_group: 1 };
98
- query = { label: "140", payload: JSON.stringify(queryPayload), queue_name: "remove_participant_v2", task_id: ctx.wsTaskNumber };
86
+
87
+ const requestId = ++ctx.wsReqNumber;
88
+ const taskId = ++ctx.wsTaskNumber;
89
+
90
+ // addUserToGroup: app_id "772021112871879", version "24502707779384158" (dongdev confirmed)
91
+ publishLsRequestWithAck({
92
+ client: ctx.mqttClient,
93
+ requestId,
94
+ content: {
95
+ app_id: "772021112871879",
96
+ payload: JSON.stringify({
97
+ epoch_id: parseInt(utils.generateOfflineThreadingID()),
98
+ tasks: [{
99
+ failure_count: null,
100
+ label: "23",
101
+ payload: JSON.stringify({
102
+ thread_key: threadID,
103
+ contact_ids: usersToAdd.map(id => String(id)),
104
+ sync_group: 1
105
+ }),
106
+ queue_name: String(threadID),
107
+ task_id: taskId
108
+ }],
109
+ version_id: "24502707779384158"
110
+ }),
111
+ request_id: requestId,
112
+ type: 3
113
+ },
114
+ extract: (message) => ({ success: true, response: message.payload })
115
+ }).then((result) => {
116
+ _callback(null, {
117
+ type: "gc_member_update",
118
+ threadID, userIDs: usersToAdd, action,
119
+ senderID: ctx.userID, BotID: ctx.userID, timestamp: Date.now()
120
+ });
121
+ }).catch((err) => {
122
+ utils.error("gcmember/add", err);
123
+ _callback(err instanceof Error ? err : new Error(err.message || "gcmember add failed"));
124
+ });
125
+
126
+ } else {
127
+ // action === 'remove'
128
+ const userToRemove = String(usersToModify[0]);
129
+ if (!currentMembers.includes(userToRemove)) {
130
+ _callback(null, { type: "error_gc", error: `User ${userToRemove} is not in this group.` });
131
+ return returnPromise;
99
132
  }
100
133
 
134
+ // removeUserFromGroup: app_id "2220391788200892", version "25002366262773827" (dongdev confirmed)
135
+ // Fire-and-forget (no ACK needed) — matches dongdev's publishRealtimeMessage pattern
101
136
  const context = {
102
- app_id: ctx.appID || '2220391788200892',
103
- payload: { epoch_id: parseInt(utils.generateOfflineThreadingID()), tasks: [query], version_id: "24631415369801570" },
104
- request_id: ctx.wsReqNumber,
137
+ app_id: "2220391788200892",
138
+ payload: JSON.stringify({
139
+ epoch_id: parseInt(utils.generateOfflineThreadingID()),
140
+ tasks: [{
141
+ failure_count: null,
142
+ label: "140",
143
+ payload: JSON.stringify({
144
+ thread_id: threadID,
145
+ contact_id: userToRemove,
146
+ sync_group: 1
147
+ }),
148
+ queue_name: "remove_participant_v2",
149
+ task_id: Math.floor(Math.random() * 1001)
150
+ }],
151
+ version_id: "25002366262773827"
152
+ }),
153
+ request_id: ++ctx.wsReqNumber,
105
154
  type: 3
106
155
  };
107
- context.payload = JSON.stringify(context.payload);
108
156
 
109
157
  ctx.mqttClient.publish('/ls_req', JSON.stringify(context), { qos: 1, retain: false }, (err) => {
110
158
  if (err) return _callback(err);
111
-
112
- const gcmemberInfo = {
159
+ return _callback(null, {
113
160
  type: "gc_member_update",
114
- threadID: threadID,
115
- userIDs: finalUsers,
116
- action: action,
117
- senderID: ctx.userID,
118
- BotID: ctx.userID,
119
- timestamp: Date.now(),
120
- };
121
- return _callback(null, gcmemberInfo);
161
+ threadID, userIDs: [userToRemove], action,
162
+ senderID: ctx.userID, BotID: ctx.userID, timestamp: Date.now()
163
+ });
122
164
  });
165
+ }
166
+
123
167
  } catch (err) {
124
- utils.error("gcmember", err);
125
- _callback(err instanceof Error ? err : new Error(err.message || "An unknown error occurred."));
168
+ utils.error("gcmember", err);
169
+ _callback(err instanceof Error ? err : new Error(err.message || "An unknown error occurred."));
126
170
  }
127
171
 
128
172
  return returnPromise;
@@ -91,7 +91,7 @@ module.exports = function (defaultFuncs, api, ctx) {
91
91
  };
92
92
 
93
93
  const context = {
94
- app_id: ctx.appID || '2220391788200892',
94
+ app_id: '2220391788200892',
95
95
  payload: {
96
96
  epoch_id: parseInt(utils.generateOfflineThreadingID()),
97
97
  tasks: [query],
@@ -74,36 +74,33 @@ module.exports = function (defaultFuncs, api, ctx) {
74
74
  }
75
75
 
76
76
  const isAdminStatus = action === 'admin' ? 1 : 0;
77
- ctx.wsReqNumber = (ctx.wsReqNumber || 0) + 1;
78
- ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1;
77
+ if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
79
78
 
80
- const queryPayload = {
81
- thread_key: parseInt(threadID),
82
- contact_id: parseInt(userID),
83
- is_admin: isAdminStatus
84
- };
85
- const query = {
86
- failure_count: null,
87
- label: "25",
88
- payload: JSON.stringify(queryPayload),
89
- queue_name: "admin_status",
90
- task_id: ctx.wsTaskNumber
91
- };
79
+ // Dongdev pattern: task_id starts at 1 (index + 1), fire-and-forget publish
92
80
  const context = {
93
- app_id: ctx.appID || '2220391788200892',
94
- payload: {
81
+ app_id: '2220391788200892',
82
+ payload: JSON.stringify({
95
83
  epoch_id: parseInt(utils.generateOfflineThreadingID()),
96
- tasks: [query],
97
- version_id: "24631415369801570"
98
- },
99
- request_id: ctx.wsReqNumber,
84
+ tasks: [{
85
+ failure_count: null,
86
+ label: "25",
87
+ payload: JSON.stringify({
88
+ thread_key: threadID,
89
+ contact_id: userID,
90
+ is_admin: isAdminStatus
91
+ }),
92
+ queue_name: "admin_status",
93
+ task_id: 1
94
+ }],
95
+ version_id: "8798795233522156"
96
+ }),
97
+ request_id: ++ctx.wsReqNumber,
100
98
  type: 3
101
99
  };
102
- context.payload = JSON.stringify(context.payload);
103
100
 
104
101
  ctx.mqttClient.publish('/ls_req', JSON.stringify(context), { qos: 1, retain: false }, (err) => {
105
102
  if (err) return _callback(err);
106
- const gcruleInfo = {
103
+ return _callback(null, {
107
104
  type: "gc_rule_update",
108
105
  threadID: threadID,
109
106
  userID: userID,
@@ -111,8 +108,7 @@ module.exports = function (defaultFuncs, api, ctx) {
111
108
  senderID: ctx.userID,
112
109
  BotID: ctx.userID,
113
110
  timestamp: Date.now(),
114
- };
115
- return _callback(null, gcruleInfo);
111
+ });
116
112
  });
117
113
 
118
114
  } catch (err) {
@@ -107,6 +107,12 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
107
107
  const sessionID = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + 1;
108
108
  const cid = ctx.clientID;
109
109
  const cachedUA = ctx.globalOptions.cachedUserAgent || ctx.globalOptions.userAgent;
110
+ // Use app_id from Facebook's own page data (populated by buildAPI) when
111
+ // available. Hard-coded fallback is the Messenger web app ID.
112
+ const rawAppId = ctx.appID || ctx.mqttAppID || ctx.userAppID;
113
+ // Coerce to number safely — Number() of a non-numeric string yields NaN which
114
+ // serialises as null in JSON and breaks the MQTT CONNECT packet.
115
+ const mqttAid = rawAppId && !isNaN(Number(rawAppId)) ? Number(rawAppId) : 219994525426954;
110
116
  const username = {
111
117
  u: ctx.userID,
112
118
  s: sessionID,
@@ -114,7 +120,7 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
114
120
  fg: false,
115
121
  d: cid,
116
122
  ct: 'websocket',
117
- aid: 219994525426954,
123
+ aid: mqttAid,
118
124
  aids: null,
119
125
  mqtt_sid: '',
120
126
  cp: 3,
@@ -140,14 +146,10 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
140
146
 
141
147
  utils.log("Connecting to MQTT...", host);
142
148
 
143
- const cachedSecChUa = ctx.globalOptions.cachedSecChUa || '"Google Chrome";v="136", "Not;A=Brand";v="99", "Chromium";v="136"';
144
- const cachedSecChUaPlatform = ctx.globalOptions.cachedSecChUaPlatform || '"Windows"';
145
149
  const cachedLocale = ctx.globalOptions.cachedLocale || 'en-US,en;q=0.9';
146
150
 
147
- // Generate a unique client ID per session like a real browser would
148
- const mqttClientId = 'mqttwsclient_' + Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
149
151
  const options = {
150
- clientId: mqttClientId,
152
+ clientId: 'mqttwsclient',
151
153
  protocolId: 'MQIsdp',
152
154
  protocolVersion: 3,
153
155
  username: JSON.stringify(username),
@@ -166,18 +168,15 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
166
168
  'Sec-WebSocket-Version': '13',
167
169
  'Accept-Encoding': 'gzip, deflate, br',
168
170
  'Accept-Language': cachedLocale,
169
- 'Sec-Ch-Ua': cachedSecChUa,
170
- 'Sec-Ch-Ua-Mobile': '?0',
171
- 'Sec-Ch-Ua-Platform': cachedSecChUaPlatform,
172
171
  'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits'
173
172
  },
174
173
  origin: 'https://www.facebook.com',
175
174
  protocolVersion: 13,
176
175
  binaryType: 'arraybuffer'
177
176
  },
178
- keepalive: 60,
177
+ keepalive: 30,
179
178
  reschedulePings: true,
180
- connectTimeout: 10000,
179
+ connectTimeout: 12000,
181
180
  reconnectPeriod: 0
182
181
  };
183
182
 
@@ -94,7 +94,7 @@ module.exports = function (defaultFuncs, api, ctx) {
94
94
  });
95
95
 
96
96
  const envelope = {
97
- app_id: String(ctx.appID || ctx.mqttAppID || "2220391788200892"),
97
+ app_id: "2220391788200892",
98
98
  payload: JSON.stringify({
99
99
  epoch_id: generateEpochId(),
100
100
  tasks: [task],
@@ -54,7 +54,7 @@ module.exports = function (defaultFuncs, api, ctx) {
54
54
 
55
55
  const epoch_id = parseInt(utils.generateOfflineThreadingID());
56
56
  const version_id = "9523201934447612";
57
- const app_id = String(ctx.appID || ctx.mqttAppID || "2220391788200892");
57
+ const app_id = "2220391788200892";
58
58
 
59
59
  const createMqttRequest = (tasks, increment = 0) => ({
60
60
  app_id,
@@ -62,9 +62,6 @@ module.exports = (defaultFuncs, api, ctx) => {
62
62
  const batch = attachments.slice(i, i + CONCURRENT_UPLOADS);
63
63
  const results = await Promise.all(batch.map(a => uploadSingleAttachment(a, threadIDHint)));
64
64
  uploads.push(...results);
65
- if (i + CONCURRENT_UPLOADS < attachments.length) {
66
- await globalAntiSuspension.addSmartDelay();
67
- }
68
65
  }
69
66
  return uploads;
70
67
  }
@@ -169,7 +166,7 @@ module.exports = (defaultFuncs, api, ctx) => {
169
166
  const mention = msg.mentions[i];
170
167
  const tag = mention.tag;
171
168
  if (typeof tag !== "string") throw new Error("Mention tags must be strings.");
172
- const offset = msg.body.indexOf(tag, mention.fromIndex || 0);
169
+ const offset = (msg.body || "").indexOf(tag, mention.fromIndex || 0);
173
170
  if (offset < 0) utils.warn("handleMention", 'Mention for "' + tag + '" not found in message string.');
174
171
  const id = mention.id || 0;
175
172
  const emptyChar = '\u200E';
@@ -289,51 +286,83 @@ module.exports = (defaultFuncs, api, ctx) => {
289
286
  }
290
287
 
291
288
  try {
289
+ // Apply anti-suspension delay and volume checks for ALL send paths
290
+ // (both MQTT and HTTP). This must run before transport selection so the
291
+ // circuit-breaker can block sends on either path.
292
292
  try {
293
- const mqttReady = ctx.mqttClient && ctx.mqttClient.connected;
294
- const isMultiRecipient = Array.isArray(threadID);
295
- // Track which transport was used so the catch block can fall back to
296
- // the OTHER transport — retrying the same one that just failed is useless.
297
- const usedMqtt = mqttReady && !isMultiRecipient && api.sendMessageMqtt;
293
+ await globalAntiSuspension.prepareBeforeMessage(
294
+ Array.isArray(threadID) ? threadID[0] : threadID,
295
+ typeof msg === "string" ? msg : (msg.body || "")
296
+ );
297
+ } catch (suspensionErr) {
298
+ // Surface circuit-breaker and volume-limit errors directly to the caller
299
+ // instead of silently swallowing them. Bots MUST handle these to avoid
300
+ // triggering Facebook's automated-behavior detection.
301
+ return callback(suspensionErr);
302
+ }
303
+
304
+ const mqttReady = !!(ctx.mqttClient && ctx.mqttClient.connected);
305
+ const isMultiRecipient = Array.isArray(threadID);
306
+ // Primary transport: MQTT when connected (faster, lower overhead).
307
+ // Multi-recipient sends always use HTTP (MQTT only handles single thread).
308
+ const preferMqtt = mqttReady && !isMultiRecipient && !!api.sendMessageMqtt;
309
+
310
+ let result;
311
+ let primaryErr;
312
+ let primaryOk = false;
298
313
 
299
- let result;
300
- if (usedMqtt) {
314
+ try {
315
+ if (preferMqtt) {
301
316
  result = await api.sendMessageMqtt(msg, threadID, replyToMessage);
302
317
  } else {
303
318
  result = await sendViaHttp(msg, threadID, replyToMessage, isGroup);
304
319
  }
305
- callback(null, result);
306
- } catch (sendErr) {
307
- // Fall back to the OTHER transport, not the one that just failed.
308
- const mqttNowReady = ctx.mqttClient && ctx.mqttClient.connected;
309
- const primaryWasMqtt = mqttNowReady && !Array.isArray(threadID) && api.sendMessageMqtt;
320
+ primaryOk = true;
321
+ } catch (err) {
322
+ primaryErr = err;
323
+ }
324
+
325
+ if (primaryOk) {
326
+ return callback(null, result);
327
+ }
310
328
 
311
- if (primaryWasMqtt) {
312
- // MQTT was used (or is available) — fall back to HTTP
329
+ // Primary transport failed — fall back to the OTHER transport.
330
+ let fallbackOk = false;
331
+ let fallbackErr;
332
+
333
+ if (preferMqtt) {
334
+ // MQTT was primary → fall back to HTTP
335
+ try {
336
+ result = await sendViaHttp(msg, threadID, replyToMessage, isGroup);
337
+ fallbackOk = true;
338
+ } catch (e) {
339
+ fallbackErr = e;
340
+ }
341
+ } else {
342
+ // HTTP was primary → fall back to MQTT if available
343
+ const mqttNow = !!(ctx.mqttClient && ctx.mqttClient.connected);
344
+ if (mqttNow && !isMultiRecipient && api.sendMessageMqtt) {
313
345
  try {
314
- const httpRes = await sendViaHttp(msg, threadID, replyToMessage, isGroup);
315
- callback(null, httpRes);
316
- } catch (_httpErr) {
317
- callback(sendErr);
346
+ result = await api.sendMessageMqtt(msg, threadID, replyToMessage);
347
+ fallbackOk = true;
348
+ } catch (e) {
349
+ fallbackErr = e;
318
350
  }
319
351
  } else {
320
- // HTTP was used — try MQTT if it has since become available
321
- if (mqttNowReady && !Array.isArray(threadID) && api.sendMessageMqtt) {
322
- try {
323
- const mqttRes = await api.sendMessageMqtt(msg, threadID, replyToMessage);
324
- callback(null, mqttRes);
325
- } catch (_mqttErr) {
326
- callback(sendErr);
327
- }
328
- } else {
329
- callback(sendErr);
330
- }
352
+ fallbackErr = primaryErr;
331
353
  }
332
- } finally {
333
- if (typingTimeout) clearTimeout(typingTimeout);
334
- if (typingStarted) {
335
- try { await api.sendTypingIndicator(false, threadID); } catch (_) {}
354
+ }
355
+
356
+ if (fallbackOk) {
357
+ return callback(null, result);
358
+ } else {
359
+ // Both transports failed — report the primary error (most informative)
360
+ // with the fallback error attached for diagnostics.
361
+ const err = primaryErr;
362
+ if (err && fallbackErr && fallbackErr !== primaryErr) {
363
+ err.fallbackError = fallbackErr;
336
364
  }
365
+ return callback(err || fallbackErr);
337
366
  }
338
367
  } catch (err) {
339
368
  callback(err);
@@ -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) {
@@ -22,9 +21,6 @@ module.exports = (defaultFuncs, api, ctx) => {
22
21
  if (!utils.isReadableStream(attachments[i])) {
23
22
  throw new Error("Attachment should be a readable stream and not " + utils.getType(attachments[i]) + ".");
24
23
  }
25
- if (i > 0) {
26
- await globalAntiSuspension.addSmartDelay();
27
- }
28
24
  const form = {
29
25
  upload_1024: attachments[i],
30
26
  ...detectAttachmentType(attachments[i]),
@@ -146,14 +142,22 @@ module.exports = (defaultFuncs, api, ctx) => {
146
142
  replyToMessage = null;
147
143
  }
148
144
 
149
- try {
150
- await globalAntiSuspension.prepareBeforeMessage(String(threadID), typeof msg === "string" ? msg : (msg.body || ""));
151
- } catch (_) {}
145
+ // Anti-suspension preparation (circuit-breaker check, smart delay, thread
146
+ // throttling, and daily-stat tracking) is the responsibility of the CALLER.
147
+ // When invoked through api.sendMessage(), preparation is already done there
148
+ // for ALL transport paths (both MQTT and HTTP).
149
+ //
150
+ // If you call api.sendMessageMqtt() directly (bypassing api.sendMessage()),
151
+ // call globalAntiSuspension.prepareBeforeMessage() yourself first and handle
152
+ // the circuit-breaker error it may throw — otherwise your bot has no protection.
152
153
 
153
154
  const normalized = typeof msg === "string" ? { body: msg } : msg;
154
155
  const baseBody = normalized.body != null ? String(normalized.body) : "";
155
156
  const epoch = (BigInt(Date.now()) << 22n).toString();
156
- const requestId = Math.floor(100 + Math.random() * 900);
157
+ // Use monotonic ctx.wsReqNumber so concurrent sends never share the same
158
+ // request_id, which would cause the ACK listener to match the wrong response.
159
+ if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
160
+ const requestId = ++ctx.wsReqNumber;
157
161
  const otid = utils.generateOfflineThreadingID();
158
162
 
159
163
  const payload0 = {
@@ -225,6 +229,12 @@ module.exports = (defaultFuncs, api, ctx) => {
225
229
  payload0.attachment_fbids = files.map(f => String(Object.values(f)[0]));
226
230
  }
227
231
 
232
+ // Use monotonic task IDs so concurrent sends never reuse the same task_id
233
+ // within a session, which can confuse Facebook's MQTT deduplication.
234
+ if (typeof ctx.wsTaskNumber !== "number") ctx.wsTaskNumber = 0;
235
+ const taskId1 = ++ctx.wsTaskNumber;
236
+ const taskId2 = ++ctx.wsTaskNumber;
237
+
228
238
  const content = {
229
239
  app_id: "2220391788200892",
230
240
  payload: {
@@ -233,7 +243,7 @@ module.exports = (defaultFuncs, api, ctx) => {
233
243
  label: "46",
234
244
  payload: payload0,
235
245
  queue_name: String(threadID),
236
- task_id: 400,
246
+ task_id: taskId1,
237
247
  failure_count: null,
238
248
  },
239
249
  {
@@ -244,7 +254,7 @@ module.exports = (defaultFuncs, api, ctx) => {
244
254
  sync_group: 1,
245
255
  },
246
256
  queue_name: String(threadID),
247
- task_id: 401,
257
+ task_id: taskId2,
248
258
  failure_count: null,
249
259
  },
250
260
  ],
@@ -47,16 +47,17 @@ module.exports = function (defaultFuncs, api, ctx) {
47
47
  });
48
48
 
49
49
  const messageDefs = [
50
- { label: 1013, queue: ['ai_generated_theme', String(threadID)] },
51
- { label: 1037, queue: ['msgr_custom_thread_theme', String(threadID)] },
52
- { label: 1028, queue: ['thread_theme_writer', String(threadID)] },
53
- { label: 43, queue: 'thread_theme', extra: { source: null, payload: null } },
50
+ { label: 1013, queue: ['ai_generated_theme', String(threadID)], app_id: '772021112871879' },
51
+ { label: 1037, queue: ['msgr_custom_thread_theme', String(threadID)], app_id: '772021112871879' },
52
+ { label: 1028, queue: ['thread_theme_writer', String(threadID)], app_id: '772021112871879' },
53
+ // label "43" (thread_theme) is the standard FB operation app_id "2220391788200892" (dongdev confirmed)
54
+ { label: 43, queue: 'thread_theme', extra: { source: null, payload: null }, app_id: '2220391788200892' },
54
55
  ];
55
56
 
56
- const messages = messageDefs.map(({ label, queue, extra }) => {
57
+ const messages = messageDefs.map(({ label, queue, extra, app_id }) => {
57
58
  ctx.wsReqNumber += 1;
58
59
  return {
59
- app_id: '772021112871879',
60
+ app_id,
60
61
  payload: JSON.stringify({
61
62
  epoch_id: parseInt(utils.generateOfflineThreadingID()),
62
63
  tasks: [makeTask(label, queue, extra)],
package/src/apis/theme.js CHANGED
@@ -150,11 +150,11 @@ module.exports = function (defaultFuncs, api, ctx) {
150
150
  };
151
151
 
152
152
  const context = {
153
- app_id: ctx.appID,
153
+ app_id: '2220391788200892',
154
154
  payload: {
155
155
  epoch_id: currentEpochId,
156
156
  tasks: [query],
157
- version_id: '24631415369801570',
157
+ version_id: '8798795233522156',
158
158
  },
159
159
  request_id: request_id,
160
160
  type: 3,
@@ -33,7 +33,7 @@ module.exports = function (defaultFuncs, api, ctx) {
33
33
  const taskId = ++ctx.wsTaskNumber;
34
34
 
35
35
  const content = {
36
- app_id: String(ctx.appID || ctx.mqttAppID || "2220391788200892"),
36
+ app_id: "2220391788200892",
37
37
  payload: JSON.stringify({
38
38
  data_trace_id: null,
39
39
  epoch_id: parseInt(utils.generateOfflineThreadingID()),
@@ -534,4 +534,6 @@ declare module "@lazyneoaz/metachat" {
534
534
  options: LoginOptions | Callback<API>,
535
535
  callback?: Callback<API>
536
536
  ): Promise<API> | void;
537
+
538
+ export function getVersion(): string;
537
539
  }
@@ -461,7 +461,7 @@ class AntiSuspension {
461
461
 
462
462
  /**
463
463
  * Prepare before sending — single delay model.
464
- * Enforces thread throttle and volume limits, respects circuit breaker.
464
+ * Enforces smart delay, thread throttle, and volume limits, respects circuit breaker.
465
465
  * If the circuit breaker is tripped (checkpoint/suspension detected) or
466
466
  * volume limits are reached, throws to protect the account.
467
467
  */
@@ -205,7 +205,10 @@ class AutoReLoginManager {
205
205
 
206
206
  async pauseAPIRequests() {
207
207
  utils.log("AutoReLogin", "Pausing API requests during re-login...");
208
- await new Promise(resolve => setTimeout(resolve, 1000));
208
+ // Wait long enough for in-flight HTTP requests to drain before we start
209
+ // re-logging in. A 1s pause was too short — the session cookies could
210
+ // change mid-request, causing follow-up requests to arrive with stale auth.
211
+ await new Promise(resolve => setTimeout(resolve, 3000));
209
212
  }
210
213
 
211
214
  resolvePendingRequests(success) {
@@ -28,6 +28,9 @@ function sanitizeHeaders(headers) {
28
28
  const out = {};
29
29
  for (const [key, value] of Object.entries(headers)) {
30
30
  if (!key || typeof key !== 'string') continue;
31
+ // Skip undefined/null values — these are intentional omissions (e.g.
32
+ // 'Upgrade-Insecure-Requests' on XHR requests that browsers don't send).
33
+ if (value === undefined || value === null) continue;
31
34
  const cleanKey = key.replace(/[^\x21-\x7E]/g, '').trim();
32
35
  if (!cleanKey) continue;
33
36
  const cleanVal = sanitizeHeaderValue(value);
@@ -113,7 +116,6 @@ function getHeaders(url, options, ctx, customHeader, requestType = 'navigate') {
113
116
  const isXhr = requestType === 'xhr';
114
117
 
115
118
  const locales = options?.cachedLocale || (androidData?.locale ? androidData.locale.replace('_', '-') : getRandomLocale());
116
- const timezone = options?.cachedTimezone || getRandomTimezone();
117
119
 
118
120
  if (isAndroid) {
119
121
  const headers = {
@@ -161,29 +163,31 @@ function getHeaders(url, options, ctx, customHeader, requestType = 'navigate') {
161
163
  const isLinux = secChUaPlatform === '"Linux"';
162
164
 
163
165
  const headers = {
164
- 'Accept': isXhr ? '*/*' : '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',
166
+ 'Accept': isXhr
167
+ ? 'application/json, text/plain, */*'
168
+ : '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',
165
169
  'Accept-Language': locales,
166
170
  'Accept-Encoding': 'gzip, deflate, br',
167
171
  'Cache-Control': 'no-cache',
168
172
  'Connection': 'keep-alive',
169
173
  'DNT': '1',
170
- 'Dpr': '1',
171
174
  'Host': host,
172
175
  'Pragma': 'no-cache',
173
176
  'Referer': referer,
174
- 'Sec-Ch-Prefers-Color-Scheme': 'light',
175
177
  'Sec-Ch-Ua': secChUa,
176
178
  'Sec-Ch-Ua-Full-Version-List': secChUaFullVersionList,
177
179
  'Sec-Ch-Ua-Mobile': '?0',
178
- 'Sec-Ch-Ua-Model': '""',
179
180
  'Sec-Ch-Ua-Platform': secChUaPlatform,
180
181
  'Sec-Ch-Ua-Platform-Version': secChUaPlatformVersion,
181
182
  'Sec-Fetch-Dest': isXhr ? 'empty' : 'document',
182
183
  'Sec-Fetch-Mode': isXhr ? 'cors' : 'navigate',
183
184
  'Sec-Fetch-Site': isXhr ? 'same-origin' : 'none',
184
185
  'User-Agent': userAgent,
185
- 'Viewport-Width': '1920',
186
- 'X-FB-Timezone-Offset': (typeof timezone === 'number' && isFinite(timezone)) ? String(timezone * 60) : '0'
186
+ 'Upgrade-Insecure-Requests': isXhr ? undefined : '1',
187
+ // X-Requested-With is only sent by XHR calls, NOT by page navigations.
188
+ // Sending it on navigate requests is a clear bot-detection fingerprint
189
+ // because real browsers never include it for document loads.
190
+ ...(isXhr ? { 'X-Requested-With': 'XMLHttpRequest' } : {})
187
191
  };
188
192
 
189
193
  if (isWindows || isMac || isLinux) {
@@ -193,10 +197,6 @@ function getHeaders(url, options, ctx, customHeader, requestType = 'navigate') {
193
197
 
194
198
  if (isXhr) {
195
199
  headers['Origin'] = `https://${host}`;
196
- headers['X-Requested-With'] = 'XMLHttpRequest';
197
- } else {
198
- headers['Sec-Fetch-User'] = '?1';
199
- headers['Upgrade-Insecure-Requests'] = '1';
200
200
  }
201
201
 
202
202
  if (ctx) {
@@ -122,7 +122,7 @@ function buildLsEnvelope(ctx, tasks, versionId = "25459622483894963") {
122
122
  const requestId = ++ctx.wsReqNumber;
123
123
 
124
124
  const envelope = {
125
- app_id: String(ctx.appID || ctx.mqttAppID || "2220391788200892"),
125
+ app_id: "2220391788200892",
126
126
  payload: JSON.stringify({
127
127
  epoch_id: generateEpochId(),
128
128
  tasks,
@@ -14,8 +14,12 @@ class RateLimiter {
14
14
 
15
15
  this.ERROR_CACHE_TTL = 300000;
16
16
  this.COOLDOWN_DURATION = 60000;
17
- this.MAX_REQUESTS_PER_MINUTE = 50;
18
- this.MAX_CONCURRENT_REQUESTS = 5;
17
+ // Raised from 50 → 120 req/min. The old limit was too aggressive — it
18
+ // throttled legitimate API calls (thread info lookups, getSeqID, markAsRead
19
+ // etc.) and caused unnecessary delays that looked bot-like when many
20
+ // requests were queued. Facebook's actual rate-limiting kicks in far higher.
21
+ this.MAX_REQUESTS_PER_MINUTE = 120;
22
+ this.MAX_CONCURRENT_REQUESTS = 8;
19
23
 
20
24
  this.activeRequests = 0;
21
25
 
@@ -25,7 +29,9 @@ class RateLimiter {
25
29
 
26
30
  // Per-endpoint sliding windows
27
31
  this._endpointWindows = new Map();
28
- this._MAX_PER_ENDPOINT_PER_MINUTE = 20;
32
+ // Raised from 20 → 40 req/min per endpoint. The old value throttled
33
+ // burst-write scenarios (e.g. thread hydration after reconnect) unnecessarily.
34
+ this._MAX_PER_ENDPOINT_PER_MINUTE = 40;
29
35
  }
30
36
 
31
37
  configure(opts = {}) {
@@ -4,25 +4,44 @@ const { getRandom } = require("./constants");
4
4
  const BROWSER_DATA = {
5
5
  windows: {
6
6
  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"],
7
+ // Ordered from newest to oldest so random selection skews toward current builds
8
+ chromeVersions: [
9
+ "137.0.0.0", "136.0.0.0", "135.0.0.0", "134.0.0.0", "133.0.0.0",
10
+ "132.0.6834.160", "131.0.6778.205", "130.0.6723.119", "129.0.6668.100"
11
+ ],
12
+ edgeVersions: [
13
+ "137.0.0.0", "136.0.0.0", "135.0.0.0", "134.0.0.0",
14
+ "133.0.3065.92", "132.0.2957.140", "131.0.2903.112"
15
+ ],
9
16
  platformVersion: '"15.0.0"'
10
17
  },
11
18
  mac: {
12
19
  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"],
15
- platformVersion: '"14.7.0"'
20
+ chromeVersions: [
21
+ "137.0.0.0", "136.0.0.0", "135.0.0.0", "134.0.0.0", "133.0.0.0",
22
+ "132.0.6834.160", "131.0.6778.205", "130.0.6723.119", "129.0.6668.100"
23
+ ],
24
+ edgeVersions: [
25
+ "137.0.0.0", "136.0.0.0", "135.0.0.0", "134.0.0.0",
26
+ "133.0.3065.92", "132.0.2957.140"
27
+ ],
28
+ platformVersion: '"14.7.1"'
16
29
  },
17
30
  linux: {
18
31
  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"],
32
+ chromeVersions: [
33
+ "137.0.0.0", "136.0.0.0", "135.0.0.0", "134.0.0.0",
34
+ "133.0.0.0", "132.0.6834.160", "131.0.6778.205"
35
+ ],
36
+ edgeVersions: [
37
+ "137.0.0.0", "136.0.0.0", "135.0.0.0",
38
+ "134.0.0.0", "133.0.3065.92"
39
+ ],
21
40
  platformVersion: '""'
22
41
  }
23
42
  };
24
43
 
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";
44
+ const defaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36";
26
45
 
27
46
  /**
28
47
  * Generates a realistic, randomized User-Agent string and related Sec-CH headers.
@@ -105,7 +124,7 @@ function randomFbav() {
105
124
  }
106
125
 
107
126
  function randomOrcaUA() {
108
- const androidVersions = ["8.1.0", "9", "10", "11", "12", "13", "14"];
127
+ const androidVersions = ["10", "11", "12", "13", "14", "15"];
109
128
  const devices = [
110
129
  { brand: "samsung", model: "SM-G996B" },
111
130
  { brand: "samsung", model: "SM-S908E" },