@lazyneoaz/metachat 1.1.2 → 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/package.json +2 -1
- package/src/apis/changeAdminStatus.js +2 -2
- package/src/apis/changeGroupImage.js +1 -1
- package/src/apis/createPoll.js +1 -1
- package/src/apis/deleteMessage.js +90 -9
- package/src/apis/emoji.js +2 -2
- package/src/apis/gcmember.js +131 -87
- package/src/apis/gcname.js +1 -1
- package/src/apis/gcrule.js +20 -24
- package/src/apis/listenMqtt.js +7 -1
- package/src/apis/nickname.js +1 -1
- package/src/apis/pinMessage.js +1 -1
- package/src/apis/sendMessage.js +66 -34
- package/src/apis/sendMessageMqtt.js +20 -7
- package/src/apis/setThreadThemeMqtt.js +7 -6
- package/src/apis/theme.js +2 -2
- package/src/apis/unsendMessage.js +1 -1
- package/src/utils/antiSuspension.js +1 -1
- package/src/utils/autoReLogin.js +4 -1
- package/src/utils/headers.js +11 -3
- package/src/utils/lsRequest.js +1 -1
- package/src/utils/rateLimiter.js +9 -3
- package/src/utils/user-agents.js +28 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyneoaz/metachat",
|
|
3
|
-
"version": "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:
|
|
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:
|
|
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:
|
|
113
|
+
app_id: "2220391788200892",
|
|
114
114
|
payload: JSON.stringify(mqttPayload),
|
|
115
115
|
request_id: reqID,
|
|
116
116
|
type: 3
|
package/src/apis/createPoll.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{ message_id: messageID }
|
|
33
|
-
).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
40
|
+
const messages = Array.isArray(messageOrMessages)
|
|
41
|
+
? messageOrMessages
|
|
42
|
+
: [messageOrMessages];
|
|
34
43
|
|
|
35
|
-
if (
|
|
36
|
-
throw new Error(
|
|
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:
|
|
95
|
+
app_id: '2220391788200892',
|
|
96
96
|
payload: {
|
|
97
97
|
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
98
98
|
tasks: [query],
|
|
99
|
-
version_id: '
|
|
99
|
+
version_id: '8798795233522156',
|
|
100
100
|
},
|
|
101
101
|
request_id: ctx.wsReqNumber,
|
|
102
102
|
type: 3,
|
package/src/apis/gcmember.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
-
|
|
21
|
-
|
|
23
|
+
_callback = threadID;
|
|
24
|
+
threadID = null;
|
|
22
25
|
} else if (typeof callback === 'function') {
|
|
23
|
-
|
|
26
|
+
_callback = callback;
|
|
24
27
|
}
|
|
25
|
-
|
|
28
|
+
|
|
26
29
|
let resolvePromise, rejectPromise;
|
|
27
30
|
const returnPromise = new Promise((resolve, reject) => {
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
resolvePromise = resolve;
|
|
32
|
+
rejectPromise = reject;
|
|
30
33
|
});
|
|
31
34
|
|
|
32
|
-
if (typeof _callback
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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:
|
|
103
|
-
payload:
|
|
104
|
-
|
|
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:
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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;
|
package/src/apis/gcname.js
CHANGED
package/src/apis/gcrule.js
CHANGED
|
@@ -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
|
|
78
|
-
ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1;
|
|
77
|
+
if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
|
|
79
78
|
|
|
80
|
-
|
|
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:
|
|
94
|
-
payload: {
|
|
81
|
+
app_id: '2220391788200892',
|
|
82
|
+
payload: JSON.stringify({
|
|
95
83
|
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
96
|
-
tasks: [
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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) {
|
package/src/apis/listenMqtt.js
CHANGED
|
@@ -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:
|
|
123
|
+
aid: mqttAid,
|
|
118
124
|
aids: null,
|
|
119
125
|
mqtt_sid: '',
|
|
120
126
|
cp: 3,
|
package/src/apis/nickname.js
CHANGED
|
@@ -94,7 +94,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
const envelope = {
|
|
97
|
-
app_id:
|
|
97
|
+
app_id: "2220391788200892",
|
|
98
98
|
payload: JSON.stringify({
|
|
99
99
|
epoch_id: generateEpochId(),
|
|
100
100
|
tasks: [task],
|
package/src/apis/pinMessage.js
CHANGED
|
@@ -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 =
|
|
57
|
+
const app_id = "2220391788200892";
|
|
58
58
|
|
|
59
59
|
const createMqttRequest = (tasks, increment = 0) => ({
|
|
60
60
|
app_id,
|
package/src/apis/sendMessage.js
CHANGED
|
@@ -166,7 +166,7 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
166
166
|
const mention = msg.mentions[i];
|
|
167
167
|
const tag = mention.tag;
|
|
168
168
|
if (typeof tag !== "string") throw new Error("Mention tags must be strings.");
|
|
169
|
-
const offset = msg.body.indexOf(tag, mention.fromIndex || 0);
|
|
169
|
+
const offset = (msg.body || "").indexOf(tag, mention.fromIndex || 0);
|
|
170
170
|
if (offset < 0) utils.warn("handleMention", 'Mention for "' + tag + '" not found in message string.');
|
|
171
171
|
const id = mention.id || 0;
|
|
172
172
|
const emptyChar = '\u200E';
|
|
@@ -286,51 +286,83 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
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.
|
|
289
292
|
try {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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;
|
|
295
309
|
|
|
296
|
-
|
|
297
|
-
|
|
310
|
+
let result;
|
|
311
|
+
let primaryErr;
|
|
312
|
+
let primaryOk = false;
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
if (preferMqtt) {
|
|
298
316
|
result = await api.sendMessageMqtt(msg, threadID, replyToMessage);
|
|
299
317
|
} else {
|
|
300
318
|
result = await sendViaHttp(msg, threadID, replyToMessage, isGroup);
|
|
301
319
|
}
|
|
302
|
-
|
|
303
|
-
} catch (
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
320
|
+
primaryOk = true;
|
|
321
|
+
} catch (err) {
|
|
322
|
+
primaryErr = err;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (primaryOk) {
|
|
326
|
+
return callback(null, result);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Primary transport failed — fall back to the OTHER transport.
|
|
330
|
+
let fallbackOk = false;
|
|
331
|
+
let fallbackErr;
|
|
307
332
|
|
|
308
|
-
|
|
309
|
-
|
|
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) {
|
|
310
345
|
try {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
} catch (
|
|
314
|
-
|
|
346
|
+
result = await api.sendMessageMqtt(msg, threadID, replyToMessage);
|
|
347
|
+
fallbackOk = true;
|
|
348
|
+
} catch (e) {
|
|
349
|
+
fallbackErr = e;
|
|
315
350
|
}
|
|
316
351
|
} else {
|
|
317
|
-
|
|
318
|
-
if (mqttNowReady && !Array.isArray(threadID) && api.sendMessageMqtt) {
|
|
319
|
-
try {
|
|
320
|
-
const mqttRes = await api.sendMessageMqtt(msg, threadID, replyToMessage);
|
|
321
|
-
callback(null, mqttRes);
|
|
322
|
-
} catch (_mqttErr) {
|
|
323
|
-
callback(sendErr);
|
|
324
|
-
}
|
|
325
|
-
} else {
|
|
326
|
-
callback(sendErr);
|
|
327
|
-
}
|
|
352
|
+
fallbackErr = primaryErr;
|
|
328
353
|
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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;
|
|
333
364
|
}
|
|
365
|
+
return callback(err || fallbackErr);
|
|
334
366
|
}
|
|
335
367
|
} catch (err) {
|
|
336
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) {
|
|
@@ -143,14 +142,22 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
143
142
|
replyToMessage = null;
|
|
144
143
|
}
|
|
145
144
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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.
|
|
149
153
|
|
|
150
154
|
const normalized = typeof msg === "string" ? { body: msg } : msg;
|
|
151
155
|
const baseBody = normalized.body != null ? String(normalized.body) : "";
|
|
152
156
|
const epoch = (BigInt(Date.now()) << 22n).toString();
|
|
153
|
-
|
|
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;
|
|
154
161
|
const otid = utils.generateOfflineThreadingID();
|
|
155
162
|
|
|
156
163
|
const payload0 = {
|
|
@@ -222,6 +229,12 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
222
229
|
payload0.attachment_fbids = files.map(f => String(Object.values(f)[0]));
|
|
223
230
|
}
|
|
224
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
|
+
|
|
225
238
|
const content = {
|
|
226
239
|
app_id: "2220391788200892",
|
|
227
240
|
payload: {
|
|
@@ -230,7 +243,7 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
230
243
|
label: "46",
|
|
231
244
|
payload: payload0,
|
|
232
245
|
queue_name: String(threadID),
|
|
233
|
-
task_id:
|
|
246
|
+
task_id: taskId1,
|
|
234
247
|
failure_count: null,
|
|
235
248
|
},
|
|
236
249
|
{
|
|
@@ -241,7 +254,7 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
241
254
|
sync_group: 1,
|
|
242
255
|
},
|
|
243
256
|
queue_name: String(threadID),
|
|
244
|
-
task_id:
|
|
257
|
+
task_id: taskId2,
|
|
245
258
|
failure_count: null,
|
|
246
259
|
},
|
|
247
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
|
-
|
|
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
|
|
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:
|
|
153
|
+
app_id: '2220391788200892',
|
|
154
154
|
payload: {
|
|
155
155
|
epoch_id: currentEpochId,
|
|
156
156
|
tasks: [query],
|
|
157
|
-
version_id: '
|
|
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:
|
|
36
|
+
app_id: "2220391788200892",
|
|
37
37
|
payload: JSON.stringify({
|
|
38
38
|
data_trace_id: null,
|
|
39
39
|
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
@@ -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
|
*/
|
package/src/utils/autoReLogin.js
CHANGED
|
@@ -205,7 +205,10 @@ class AutoReLoginManager {
|
|
|
205
205
|
|
|
206
206
|
async pauseAPIRequests() {
|
|
207
207
|
utils.log("AutoReLogin", "Pausing API requests during re-login...");
|
|
208
|
-
|
|
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) {
|
package/src/utils/headers.js
CHANGED
|
@@ -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);
|
|
@@ -160,7 +163,9 @@ function getHeaders(url, options, ctx, customHeader, requestType = 'navigate') {
|
|
|
160
163
|
const isLinux = secChUaPlatform === '"Linux"';
|
|
161
164
|
|
|
162
165
|
const headers = {
|
|
163
|
-
'Accept':
|
|
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',
|
|
164
169
|
'Accept-Language': locales,
|
|
165
170
|
'Accept-Encoding': 'gzip, deflate, br',
|
|
166
171
|
'Cache-Control': 'no-cache',
|
|
@@ -178,8 +183,11 @@ function getHeaders(url, options, ctx, customHeader, requestType = 'navigate') {
|
|
|
178
183
|
'Sec-Fetch-Mode': isXhr ? 'cors' : 'navigate',
|
|
179
184
|
'Sec-Fetch-Site': isXhr ? 'same-origin' : 'none',
|
|
180
185
|
'User-Agent': userAgent,
|
|
181
|
-
'Upgrade-Insecure-Requests': '1',
|
|
182
|
-
|
|
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' } : {})
|
|
183
191
|
};
|
|
184
192
|
|
|
185
193
|
if (isWindows || isMac || isLinux) {
|
package/src/utils/lsRequest.js
CHANGED
|
@@ -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:
|
|
125
|
+
app_id: "2220391788200892",
|
|
126
126
|
payload: JSON.stringify({
|
|
127
127
|
epoch_id: generateEpochId(),
|
|
128
128
|
tasks,
|
package/src/utils/rateLimiter.js
CHANGED
|
@@ -14,8 +14,12 @@ class RateLimiter {
|
|
|
14
14
|
|
|
15
15
|
this.ERROR_CACHE_TTL = 300000;
|
|
16
16
|
this.COOLDOWN_DURATION = 60000;
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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 = {}) {
|
package/src/utils/user-agents.js
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
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: [
|
|
14
|
-
|
|
15
|
-
|
|
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: [
|
|
20
|
-
|
|
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/
|
|
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 = ["
|
|
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" },
|