@lazyneoaz/nkxchat 1.0.0
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/LICENSE +3 -0
- package/README.md +199 -0
- package/examples/login-with-cookies.js +102 -0
- package/examples/verify.js +70 -0
- package/index.js +2 -0
- package/package.json +84 -0
- package/src/apis/addExternalModule.js +24 -0
- package/src/apis/addUserToGroup.js +108 -0
- package/src/apis/changeAdminStatus.js +148 -0
- package/src/apis/changeArchivedStatus.js +61 -0
- package/src/apis/changeAvatar.js +103 -0
- package/src/apis/changeBio.js +69 -0
- package/src/apis/changeBlockedStatus.js +54 -0
- package/src/apis/changeGroupImage.js +136 -0
- package/src/apis/changeThreadColor.js +116 -0
- package/src/apis/changeThreadEmoji.js +53 -0
- package/src/apis/comment.js +207 -0
- package/src/apis/createAITheme.js +129 -0
- package/src/apis/createNewGroup.js +79 -0
- package/src/apis/createPoll.js +73 -0
- package/src/apis/deleteMessage.js +44 -0
- package/src/apis/deleteThread.js +52 -0
- package/src/apis/editMessage.js +70 -0
- package/src/apis/emoji.js +124 -0
- package/src/apis/enableAutoSaveAppState.js +69 -0
- package/src/apis/fetchThemeData.js +113 -0
- package/src/apis/follow.js +81 -0
- package/src/apis/forwardAttachment.js +178 -0
- package/src/apis/forwardMessage.js +52 -0
- package/src/apis/friend.js +243 -0
- package/src/apis/gcmember.js +122 -0
- package/src/apis/gcname.js +123 -0
- package/src/apis/gcrule.js +119 -0
- package/src/apis/getAccess.js +111 -0
- package/src/apis/getBotInfo.js +88 -0
- package/src/apis/getBotInitialData.js +43 -0
- package/src/apis/getEmojiUrl.js +40 -0
- package/src/apis/getFriendsList.js +79 -0
- package/src/apis/getMessage.js +423 -0
- package/src/apis/getTheme.js +123 -0
- package/src/apis/getThemeInfo.js +116 -0
- package/src/apis/getThemePictures.js +87 -0
- package/src/apis/getThreadColors.js +119 -0
- package/src/apis/getThreadHistory.js +239 -0
- package/src/apis/getThreadInfo.js +267 -0
- package/src/apis/getThreadList.js +232 -0
- package/src/apis/getThreadPictures.js +58 -0
- package/src/apis/getUserID.js +117 -0
- package/src/apis/getUserInfo.js +513 -0
- package/src/apis/getUserInfoV2.js +146 -0
- package/src/apis/handleFriendRequest.js +66 -0
- package/src/apis/handleMessageRequest.js +50 -0
- package/src/apis/httpGet.js +63 -0
- package/src/apis/httpPost.js +89 -0
- package/src/apis/httpPostFormData.js +69 -0
- package/src/apis/listenMqtt.js +924 -0
- package/src/apis/listenSpeed.js +178 -0
- package/src/apis/logout.js +63 -0
- package/src/apis/markAsDelivered.js +47 -0
- package/src/apis/markAsRead.js +95 -0
- package/src/apis/markAsReadAll.js +40 -0
- package/src/apis/markAsSeen.js +70 -0
- package/src/apis/mqttDeltaValue.js +252 -0
- package/src/apis/muteThread.js +45 -0
- package/src/apis/nickname.js +132 -0
- package/src/apis/notes.js +163 -0
- package/src/apis/pinMessage.js +150 -0
- package/src/apis/produceMetaTheme.js +160 -0
- package/src/apis/realtime.js +182 -0
- package/src/apis/refreshFb_dtsg.js +94 -0
- package/src/apis/removeUserFromGroup.js +117 -0
- package/src/apis/resolvePhotoUrl.js +58 -0
- package/src/apis/searchForThread.js +154 -0
- package/src/apis/sendEffect.js +306 -0
- package/src/apis/sendMessage.js +353 -0
- package/src/apis/sendMessageMqtt.js +255 -0
- package/src/apis/sendTypingIndicator.js +40 -0
- package/src/apis/setMessageReaction.js +27 -0
- package/src/apis/setMessageReactionMqtt.js +61 -0
- package/src/apis/setPostReaction.js +118 -0
- package/src/apis/setThreadTheme.js +210 -0
- package/src/apis/setThreadThemeMqtt.js +94 -0
- package/src/apis/setTitle.js +26 -0
- package/src/apis/share.js +106 -0
- package/src/apis/shareContact.js +66 -0
- package/src/apis/stickers.js +257 -0
- package/src/apis/story.js +181 -0
- package/src/apis/theme.js +233 -0
- package/src/apis/unfriend.js +47 -0
- package/src/apis/unsendMessage.js +17 -0
- package/src/apis/uploadAttachment.js +87 -0
- package/src/database/appStateBackup.js +189 -0
- package/src/database/models/index.js +56 -0
- package/src/database/models/thread.js +31 -0
- package/src/database/models/user.js +32 -0
- package/src/database/threadData.js +101 -0
- package/src/database/userData.js +90 -0
- package/src/engine/client.js +92 -0
- package/src/engine/models/buildAPI.js +118 -0
- package/src/engine/models/loginHelper.js +492 -0
- package/src/engine/models/setOptions.js +88 -0
- package/src/types/index.d.ts +498 -0
- package/src/utils/antiSuspension.js +516 -0
- package/src/utils/auth-helpers.js +149 -0
- package/src/utils/autoReLogin.js +237 -0
- package/src/utils/axios.js +368 -0
- package/src/utils/cache.js +54 -0
- package/src/utils/clients.js +279 -0
- package/src/utils/constants.js +525 -0
- package/src/utils/formatters/data/formatAttachment.js +370 -0
- package/src/utils/formatters/data/formatDelta.js +109 -0
- package/src/utils/formatters/index.js +159 -0
- package/src/utils/formatters/value/formatCookie.js +91 -0
- package/src/utils/formatters/value/formatDate.js +36 -0
- package/src/utils/formatters/value/formatID.js +16 -0
- package/src/utils/formatters.js +1369 -0
- package/src/utils/headers.js +235 -0
- package/src/utils/index.js +152 -0
- package/src/utils/monitoring.js +333 -0
- package/src/utils/rateLimiter.js +251 -0
- package/src/utils/tokenRefresh.js +285 -0
- package/src/utils/user-agents.js +238 -0
- package/src/utils/validation.js +157 -0
|
@@ -0,0 +1,924 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const utils = require('../utils');
|
|
3
|
+
const mqtt = require('mqtt');
|
|
4
|
+
const WebSocket = require('ws');
|
|
5
|
+
const duplexify = require('duplexify');
|
|
6
|
+
const HttpsProxyAgent = require('https-proxy-agent');
|
|
7
|
+
const EventEmitter = require('events');
|
|
8
|
+
const { parseDelta } = require('./mqttDeltaValue');
|
|
9
|
+
const { globalAutoReLoginManager } = require('../utils/autoReLogin');
|
|
10
|
+
|
|
11
|
+
let form = {};
|
|
12
|
+
let getSeqID;
|
|
13
|
+
|
|
14
|
+
const topics = [
|
|
15
|
+
"/ls_req", "/ls_resp", "/legacy_web", "/webrtc", "/rtc_multi", "/onevc", "/br_sr", "/sr_res",
|
|
16
|
+
"/t_ms", "/thread_typing", "/orca_typing_notifications", "/notify_disconnect",
|
|
17
|
+
"/orca_presence", "/inbox", "/mercury", "/messaging_events",
|
|
18
|
+
"/orca_message_notifications", "/pp", "/webrtc_response"
|
|
19
|
+
];
|
|
20
|
+
const MQTT_MAX_BACKOFF = 30000;
|
|
21
|
+
const MQTT_JITTER_MAX = 1000;
|
|
22
|
+
const MQTT_QUICK_CLOSE_WINDOW_MS = 2000;
|
|
23
|
+
const MQTT_QUICK_CLOSE_THRESHOLD = 3;
|
|
24
|
+
|
|
25
|
+
function generateUUID() {
|
|
26
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
27
|
+
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
|
28
|
+
return v.toString(16);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getRandomReconnectTime() {
|
|
33
|
+
const min = 26 * 60 * 1000;
|
|
34
|
+
const max = 60 * 60 * 1000;
|
|
35
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function calculate(previousTimestamp, currentTimestamp){
|
|
39
|
+
return Math.floor(previousTimestamp + (currentTimestamp - previousTimestamp) + 300);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function computeBackoffDelay(ctx, baseDelay, maxBackoff, jitterMax) {
|
|
43
|
+
const attempt = ctx._reconnectAttempts || 0;
|
|
44
|
+
const base = Number.isFinite(baseDelay) && baseDelay > 0 ? baseDelay : 2000;
|
|
45
|
+
const max = Number.isFinite(maxBackoff) && maxBackoff > 0 ? maxBackoff : MQTT_MAX_BACKOFF;
|
|
46
|
+
const jitterCap = Number.isFinite(jitterMax) && jitterMax >= 0 ? jitterMax : MQTT_JITTER_MAX;
|
|
47
|
+
const backoff = Math.min(base * Math.pow(1.6, attempt), max);
|
|
48
|
+
const jitter = Math.floor(Math.random() * jitterCap);
|
|
49
|
+
return Math.round(backoff + jitter);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {Object} ctx
|
|
54
|
+
* @param {Object} api
|
|
55
|
+
* @param {string} threadID
|
|
56
|
+
*/
|
|
57
|
+
function markAsRead(ctx, api, threadID) {
|
|
58
|
+
if (ctx.globalOptions.autoMarkRead && threadID) {
|
|
59
|
+
api.markAsRead(threadID, (err) => {
|
|
60
|
+
if (err) utils.error("autoMarkRead", err);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {Object} defaultFuncs
|
|
67
|
+
* @param {Object} api
|
|
68
|
+
* @param {Object} ctx
|
|
69
|
+
* @param {Function} globalCallback
|
|
70
|
+
* @param {Function} scheduleReconnect
|
|
71
|
+
* @param {Function} emitAuthError - Passed from the factory closure so auth errors can be emitted correctly
|
|
72
|
+
*/
|
|
73
|
+
async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconnect, emitAuthError) {
|
|
74
|
+
function isEndingLikeError(msg) {
|
|
75
|
+
return /No subscription existed|client disconnecting|socket hang up|ECONNRESET/i.test(msg || "");
|
|
76
|
+
}
|
|
77
|
+
function guard(label, fn) {
|
|
78
|
+
return (...args) => {
|
|
79
|
+
try {
|
|
80
|
+
return fn(...args);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
utils.error("MQTT", `${label} handler error:`, err && err.message ? err.message : err);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (ctx._reconnectTimer) {
|
|
88
|
+
clearTimeout(ctx._reconnectTimer);
|
|
89
|
+
ctx._reconnectTimer = null;
|
|
90
|
+
}
|
|
91
|
+
if (ctx._tmsTimeout) {
|
|
92
|
+
clearTimeout(ctx._tmsTimeout);
|
|
93
|
+
ctx._tmsTimeout = null;
|
|
94
|
+
}
|
|
95
|
+
if (ctx._mqttWatchdog) {
|
|
96
|
+
clearInterval(ctx._mqttWatchdog);
|
|
97
|
+
ctx._mqttWatchdog = null;
|
|
98
|
+
}
|
|
99
|
+
if (ctx.mqttClient) {
|
|
100
|
+
try { ctx.mqttClient.removeAllListeners(); } catch (_) { }
|
|
101
|
+
try { ctx.mqttClient.end(true); } catch (_) { }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const chatOn = ctx.globalOptions.online;
|
|
105
|
+
const region = ctx.region;
|
|
106
|
+
const foreground = false;
|
|
107
|
+
const sessionID = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + 1;
|
|
108
|
+
const cid = ctx.clientID;
|
|
109
|
+
const cachedUA = ctx.globalOptions.cachedUserAgent || ctx.globalOptions.userAgent;
|
|
110
|
+
const username = {
|
|
111
|
+
u: ctx.userID,
|
|
112
|
+
s: sessionID,
|
|
113
|
+
chat_on: chatOn,
|
|
114
|
+
fg: false,
|
|
115
|
+
d: cid,
|
|
116
|
+
ct: 'websocket',
|
|
117
|
+
aid: 219994525426954,
|
|
118
|
+
aids: null,
|
|
119
|
+
mqtt_sid: '',
|
|
120
|
+
cp: 3,
|
|
121
|
+
ecp: 10,
|
|
122
|
+
st: [],
|
|
123
|
+
pm: [],
|
|
124
|
+
dc: '',
|
|
125
|
+
no_auto_fg: true,
|
|
126
|
+
gas: null,
|
|
127
|
+
pack: [],
|
|
128
|
+
p: null,
|
|
129
|
+
php_override: ""
|
|
130
|
+
};
|
|
131
|
+
const cookies = ctx.jar.getCookiesSync('https://www.facebook.com').join('; ');
|
|
132
|
+
let host;
|
|
133
|
+
if (ctx.mqttEndpoint) {
|
|
134
|
+
host = `${ctx.mqttEndpoint}&sid=${sessionID}&cid=${cid}`;
|
|
135
|
+
} else if (region) {
|
|
136
|
+
host = `wss://edge-chat.facebook.com/chat?region=${region.toLowerCase()}&sid=${sessionID}&cid=${cid}`;
|
|
137
|
+
} else {
|
|
138
|
+
host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}&cid=${cid}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
utils.log("Connecting to MQTT...", host);
|
|
142
|
+
|
|
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
|
+
const cachedLocale = ctx.globalOptions.cachedLocale || 'en-US,en;q=0.9';
|
|
146
|
+
|
|
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
|
+
const options = {
|
|
150
|
+
clientId: mqttClientId,
|
|
151
|
+
protocolId: 'MQIsdp',
|
|
152
|
+
protocolVersion: 3,
|
|
153
|
+
username: JSON.stringify(username),
|
|
154
|
+
clean: true,
|
|
155
|
+
wsOptions: {
|
|
156
|
+
headers: {
|
|
157
|
+
'Cookie': cookies,
|
|
158
|
+
'Origin': 'https://www.facebook.com',
|
|
159
|
+
'User-Agent': cachedUA,
|
|
160
|
+
'Referer': 'https://www.facebook.com/',
|
|
161
|
+
'Host': 'edge-chat.facebook.com',
|
|
162
|
+
'Connection': 'Upgrade',
|
|
163
|
+
'Pragma': 'no-cache',
|
|
164
|
+
'Cache-Control': 'no-cache',
|
|
165
|
+
'Upgrade': 'websocket',
|
|
166
|
+
'Sec-WebSocket-Version': '13',
|
|
167
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
168
|
+
'Accept-Language': cachedLocale,
|
|
169
|
+
'Sec-Ch-Ua': cachedSecChUa,
|
|
170
|
+
'Sec-Ch-Ua-Mobile': '?0',
|
|
171
|
+
'Sec-Ch-Ua-Platform': cachedSecChUaPlatform,
|
|
172
|
+
'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits'
|
|
173
|
+
},
|
|
174
|
+
origin: 'https://www.facebook.com',
|
|
175
|
+
protocolVersion: 13,
|
|
176
|
+
binaryType: 'arraybuffer'
|
|
177
|
+
},
|
|
178
|
+
keepalive: 60,
|
|
179
|
+
reschedulePings: true,
|
|
180
|
+
connectTimeout: 10000,
|
|
181
|
+
reconnectPeriod: 0
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
if (ctx.globalOptions.proxy) options.wsOptions.agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
|
|
185
|
+
ctx._mqttLastConnectAttemptAt = Date.now();
|
|
186
|
+
|
|
187
|
+
function buildMqttStream() {
|
|
188
|
+
const wsOpts = {
|
|
189
|
+
headers: options.wsOptions.headers,
|
|
190
|
+
origin: options.wsOptions.origin,
|
|
191
|
+
perMessageDeflate: false,
|
|
192
|
+
};
|
|
193
|
+
if (options.wsOptions.agent) wsOpts.agent = options.wsOptions.agent;
|
|
194
|
+
const ws = new WebSocket(host, wsOpts);
|
|
195
|
+
const d = duplexify();
|
|
196
|
+
ws.once('open', () => {
|
|
197
|
+
const s = WebSocket.createWebSocketStream(ws, { objectMode: false });
|
|
198
|
+
d.setReadable(s);
|
|
199
|
+
d.setWritable(s);
|
|
200
|
+
});
|
|
201
|
+
ws.on('error', (err) => d.destroy(err));
|
|
202
|
+
ws.on('unexpected-response', (_req, res) => {
|
|
203
|
+
d.destroy(new Error(`WebSocket unexpected response: ${res.statusCode}`));
|
|
204
|
+
});
|
|
205
|
+
return d;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const mqttClient = new mqtt.Client(buildMqttStream, options);
|
|
209
|
+
mqttClient.publishSync = mqttClient.publish.bind(mqttClient);
|
|
210
|
+
mqttClient.publish = (topic, message, opts = {}, callback = () => {}) => new Promise((resolve, reject) => {
|
|
211
|
+
mqttClient.publishSync(topic, message, opts, (err, data) => {
|
|
212
|
+
if (err) {
|
|
213
|
+
callback(err);
|
|
214
|
+
reject(err);
|
|
215
|
+
}
|
|
216
|
+
callback(null, data);
|
|
217
|
+
resolve(data);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
ctx.mqttClient = mqttClient;
|
|
221
|
+
|
|
222
|
+
mqttClient.on('error', guard("error", (err) => {
|
|
223
|
+
const msg = String(err && err.message ? err.message : err || "");
|
|
224
|
+
|
|
225
|
+
if ((ctx._ending || ctx._cycling) && isEndingLikeError(msg)) {
|
|
226
|
+
utils.log("MQTT", "Expected error during shutdown: " + msg);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (ctx._tmsTimeout) {
|
|
231
|
+
clearTimeout(ctx._tmsTimeout);
|
|
232
|
+
ctx._tmsTimeout = null;
|
|
233
|
+
}
|
|
234
|
+
if (ctx._mqttWatchdog) {
|
|
235
|
+
clearInterval(ctx._mqttWatchdog);
|
|
236
|
+
ctx._mqttWatchdog = null;
|
|
237
|
+
}
|
|
238
|
+
ctx._mqttConnected = false;
|
|
239
|
+
|
|
240
|
+
if (/Not logged in|Not logged in\.|blocked the login|checkpoint|401|403/i.test(msg)) {
|
|
241
|
+
try { mqttClient.end(true); } catch (_) { }
|
|
242
|
+
try { if (ctx._autoCycleTimer) clearInterval(ctx._autoCycleTimer); } catch (_) { }
|
|
243
|
+
emitAuthError(/blocked|checkpoint/i.test(msg) ? "login_blocked" : "not_logged_in", msg);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
utils.error("MQTT error:", msg);
|
|
248
|
+
try { mqttClient.end(true); } catch (_) { }
|
|
249
|
+
|
|
250
|
+
if (ctx._ending || ctx._cycling) return;
|
|
251
|
+
|
|
252
|
+
if (ctx.globalOptions.autoReconnect) {
|
|
253
|
+
const baseDelay = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
|
|
254
|
+
ctx._reconnectAttempts = (ctx._reconnectAttempts || 0) + 1;
|
|
255
|
+
const d = computeBackoffDelay(ctx, baseDelay, MQTT_MAX_BACKOFF, MQTT_JITTER_MAX);
|
|
256
|
+
utils.warn("MQTT", `Auto-reconnecting in ${d}ms (attempt ${ctx._reconnectAttempts}) due to error`);
|
|
257
|
+
scheduleReconnect(d);
|
|
258
|
+
} else {
|
|
259
|
+
globalCallback({ type: "stop_listen", error: msg || "Connection refused" });
|
|
260
|
+
}
|
|
261
|
+
}));
|
|
262
|
+
|
|
263
|
+
mqttClient.on('connect', guard("connect", async () => {
|
|
264
|
+
if (!ctx._mqttConnected) {
|
|
265
|
+
utils.log("MQTT connected successfully");
|
|
266
|
+
ctx._mqttConnected = true;
|
|
267
|
+
}
|
|
268
|
+
ctx._cycling = false;
|
|
269
|
+
ctx._reconnectAttempts = 0;
|
|
270
|
+
ctx._mqttQuickCloseCount = 0;
|
|
271
|
+
if (ctx._reconnectTimer) {
|
|
272
|
+
clearTimeout(ctx._reconnectTimer);
|
|
273
|
+
ctx._reconnectTimer = null;
|
|
274
|
+
}
|
|
275
|
+
ctx.loggedIn = true;
|
|
276
|
+
ctx._lastMqttMessageAt = Date.now();
|
|
277
|
+
if (ctx._mqttWatchdog) {
|
|
278
|
+
clearInterval(ctx._mqttWatchdog);
|
|
279
|
+
ctx._mqttWatchdog = null;
|
|
280
|
+
}
|
|
281
|
+
const watchdogInterval = (ctx._mqttOpt && ctx._mqttOpt.watchdogIntervalMs) || 60000;
|
|
282
|
+
const staleMs = (ctx._mqttOpt && ctx._mqttOpt.staleMs) || 300000;
|
|
283
|
+
ctx._mqttWatchdog = setInterval(() => {
|
|
284
|
+
if (ctx._ending || ctx._cycling || !ctx.globalOptions.autoReconnect) return;
|
|
285
|
+
const last = ctx._lastMqttMessageAt || 0;
|
|
286
|
+
if (last && Date.now() - last > staleMs) {
|
|
287
|
+
utils.warn("MQTT", `No MQTT activity for ${Date.now() - last}ms, cycling connection`);
|
|
288
|
+
try { mqttClient.end(true); } catch (_) { }
|
|
289
|
+
scheduleReconnect((ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000);
|
|
290
|
+
}
|
|
291
|
+
}, watchdogInterval);
|
|
292
|
+
|
|
293
|
+
mqttClient.subscribe(topics, { qos: 1 });
|
|
294
|
+
|
|
295
|
+
const queue = {
|
|
296
|
+
sync_api_version: 11,
|
|
297
|
+
max_deltas_able_to_process: 200,
|
|
298
|
+
delta_batch_size: 200,
|
|
299
|
+
encoding: "JSON",
|
|
300
|
+
entity_fbid: ctx.userID,
|
|
301
|
+
initial_titan_sequence_id: ctx.lastSeqId,
|
|
302
|
+
device_params: null
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
let topic;
|
|
306
|
+
if (ctx.syncToken) {
|
|
307
|
+
topic = "/messenger_sync_get_diffs";
|
|
308
|
+
queue.last_seq_id = ctx.lastSeqId;
|
|
309
|
+
queue.sync_token = ctx.syncToken;
|
|
310
|
+
} else {
|
|
311
|
+
topic = "/messenger_sync_create_queue";
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
|
|
315
|
+
mqttClient.publish("/foreground_state", JSON.stringify({ foreground: chatOn }), { qos: 1 });
|
|
316
|
+
mqttClient.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 });
|
|
317
|
+
|
|
318
|
+
const tmsTimeoutDelay = 10000;
|
|
319
|
+
ctx._tmsTimeout = setTimeout(() => {
|
|
320
|
+
ctx._tmsTimeout = null;
|
|
321
|
+
if (ctx._ending || ctx._cycling) return;
|
|
322
|
+
if (!ctx.globalOptions.autoReconnect) {
|
|
323
|
+
utils.warn("MQTT", "t_ms timeout but autoReconnect is disabled");
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
utils.warn("MQTT", `t_ms timeout after ${tmsTimeoutDelay}ms, will cycle connection`);
|
|
327
|
+
try { mqttClient.end(true); } catch (_) { }
|
|
328
|
+
const baseDelay = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
|
|
329
|
+
scheduleReconnect(baseDelay);
|
|
330
|
+
}, tmsTimeoutDelay);
|
|
331
|
+
|
|
332
|
+
ctx.tmsWait = function() {
|
|
333
|
+
if (ctx._tmsTimeout) {
|
|
334
|
+
clearTimeout(ctx._tmsTimeout);
|
|
335
|
+
ctx._tmsTimeout = null;
|
|
336
|
+
}
|
|
337
|
+
if (ctx.globalOptions.emitReady) {
|
|
338
|
+
globalCallback(null, { type: "ready", timestamp: Date.now() });
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
if (ctx._emitter) ctx._emitter.emit('ready', { timestamp: Date.now() });
|
|
342
|
+
} catch (_) {}
|
|
343
|
+
delete ctx.tmsWait;
|
|
344
|
+
};
|
|
345
|
+
}));
|
|
346
|
+
|
|
347
|
+
mqttClient.on('message', guard("message", async (topic, message, _packet) => {
|
|
348
|
+
try {
|
|
349
|
+
ctx._lastMqttMessageAt = Date.now();
|
|
350
|
+
let jsonMessage = Buffer.isBuffer(message) ? Buffer.from(message).toString() : message;
|
|
351
|
+
try { jsonMessage = JSON.parse(jsonMessage); } catch (_) { jsonMessage = {}; }
|
|
352
|
+
|
|
353
|
+
if (jsonMessage.type === "jewel_requests_add") {
|
|
354
|
+
globalCallback(null, {
|
|
355
|
+
type: "friend_request_received",
|
|
356
|
+
actorFbId: jsonMessage.from.toString(),
|
|
357
|
+
timestamp: Date.now().toString()
|
|
358
|
+
});
|
|
359
|
+
} else if (jsonMessage.type === "jewel_requests_remove_old") {
|
|
360
|
+
globalCallback(null, {
|
|
361
|
+
type: "friend_request_cancel",
|
|
362
|
+
actorFbId: jsonMessage.from.toString(),
|
|
363
|
+
timestamp: Date.now().toString()
|
|
364
|
+
});
|
|
365
|
+
} else if (topic === "/t_ms") {
|
|
366
|
+
if (ctx.tmsWait && typeof ctx.tmsWait === "function") ctx.tmsWait();
|
|
367
|
+
|
|
368
|
+
if (jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
|
|
369
|
+
ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
|
|
370
|
+
ctx.syncToken = jsonMessage.syncToken;
|
|
371
|
+
}
|
|
372
|
+
if (jsonMessage.lastIssuedSeqId) {
|
|
373
|
+
ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (jsonMessage.deltas) {
|
|
377
|
+
for (const delta of jsonMessage.deltas) {
|
|
378
|
+
parseDelta(defaultFuncs, api, ctx, globalCallback, { delta });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
} else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
|
|
382
|
+
const typ = {
|
|
383
|
+
type: "typ",
|
|
384
|
+
isTyping: !!jsonMessage.state,
|
|
385
|
+
from: jsonMessage.sender_fbid.toString(),
|
|
386
|
+
threadID: utils.formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
|
|
387
|
+
};
|
|
388
|
+
globalCallback(null, typ);
|
|
389
|
+
} else if (topic === "/orca_presence") {
|
|
390
|
+
if (!ctx.globalOptions.updatePresence && jsonMessage.list) {
|
|
391
|
+
for (const data of jsonMessage.list) {
|
|
392
|
+
globalCallback(null, {
|
|
393
|
+
type: "presence",
|
|
394
|
+
userID: String(data.u),
|
|
395
|
+
timestamp: data.l * 1000,
|
|
396
|
+
statuses: data.p
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
} catch (ex) {
|
|
402
|
+
utils.error("MQTT message parse error:", ex && ex.message ? ex.message : ex);
|
|
403
|
+
}
|
|
404
|
+
}));
|
|
405
|
+
|
|
406
|
+
mqttClient.on('close', guard("close", () => {
|
|
407
|
+
utils.warn("MQTT", "Connection closed");
|
|
408
|
+
if (ctx._tmsTimeout) {
|
|
409
|
+
clearTimeout(ctx._tmsTimeout);
|
|
410
|
+
ctx._tmsTimeout = null;
|
|
411
|
+
}
|
|
412
|
+
if (ctx._mqttWatchdog) {
|
|
413
|
+
clearInterval(ctx._mqttWatchdog);
|
|
414
|
+
ctx._mqttWatchdog = null;
|
|
415
|
+
}
|
|
416
|
+
// Save connected state BEFORE clearing it — used for quick-close detection.
|
|
417
|
+
const wasConnected = ctx._mqttConnected;
|
|
418
|
+
ctx._mqttConnected = false;
|
|
419
|
+
if (ctx._ending || ctx._cycling) return;
|
|
420
|
+
|
|
421
|
+
// Quick-close detection: only relevant when we closed before a 'connect'
|
|
422
|
+
// event ever fired (wasConnected is still false from initialization).
|
|
423
|
+
if (!wasConnected) {
|
|
424
|
+
const now = Date.now();
|
|
425
|
+
const lastAttempt = ctx._mqttLastConnectAttemptAt || 0;
|
|
426
|
+
if (lastAttempt && now - lastAttempt <= MQTT_QUICK_CLOSE_WINDOW_MS) {
|
|
427
|
+
ctx._mqttQuickCloseCount = (ctx._mqttQuickCloseCount || 0) + 1;
|
|
428
|
+
} else {
|
|
429
|
+
ctx._mqttQuickCloseCount = 0;
|
|
430
|
+
}
|
|
431
|
+
if (ctx._mqttQuickCloseCount >= MQTT_QUICK_CLOSE_THRESHOLD) {
|
|
432
|
+
ctx._mqttQuickCloseCount = 0;
|
|
433
|
+
if (!ctx._mqttReauthing && globalAutoReLoginManager && globalAutoReLoginManager.isEnabled && globalAutoReLoginManager.isEnabled()) {
|
|
434
|
+
ctx._mqttReauthing = true;
|
|
435
|
+
globalAutoReLoginManager.handleSessionExpiry(api, 'https://www.facebook.com', "MQTT quick close loop")
|
|
436
|
+
.then((ok) => {
|
|
437
|
+
ctx._mqttReauthing = false;
|
|
438
|
+
if (ok && ctx.globalOptions.autoReconnect) {
|
|
439
|
+
ctx._reconnectAttempts = 0;
|
|
440
|
+
scheduleReconnect((ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000);
|
|
441
|
+
}
|
|
442
|
+
})
|
|
443
|
+
.catch(() => {
|
|
444
|
+
ctx._mqttReauthing = false;
|
|
445
|
+
if (ctx.globalOptions.autoReconnect) {
|
|
446
|
+
scheduleReconnect((ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
// Re-auth handles reconnect in its .then() — do not schedule a
|
|
450
|
+
// second reconnect here or both will race.
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (ctx.globalOptions.autoReconnect) {
|
|
457
|
+
ctx._reconnectAttempts = (ctx._reconnectAttempts || 0) + 1;
|
|
458
|
+
const maxAttempts = (ctx._mqttOpt && ctx._mqttOpt.maxReconnectAttempts) || 100;
|
|
459
|
+
if (ctx._reconnectAttempts > maxAttempts) {
|
|
460
|
+
utils.warn("MQTT", `Max reconnect attempts (${maxAttempts}) reached. Pausing for 10 minutes before retrying.`);
|
|
461
|
+
ctx._reconnectAttempts = 0;
|
|
462
|
+
scheduleReconnect(10 * 60 * 1000);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const baseDelay = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
|
|
466
|
+
const d = computeBackoffDelay(ctx, baseDelay, MQTT_MAX_BACKOFF, MQTT_JITTER_MAX);
|
|
467
|
+
utils.warn("MQTT", `Reconnecting in ${d}ms (attempt ${ctx._reconnectAttempts}/${maxAttempts})`);
|
|
468
|
+
scheduleReconnect(d);
|
|
469
|
+
}
|
|
470
|
+
}));
|
|
471
|
+
|
|
472
|
+
mqttClient.on('disconnect', guard("disconnect", () => {
|
|
473
|
+
utils.log("MQTT", "Disconnected");
|
|
474
|
+
if (ctx._tmsTimeout) {
|
|
475
|
+
clearTimeout(ctx._tmsTimeout);
|
|
476
|
+
ctx._tmsTimeout = null;
|
|
477
|
+
}
|
|
478
|
+
if (ctx._mqttWatchdog) {
|
|
479
|
+
clearInterval(ctx._mqttWatchdog);
|
|
480
|
+
ctx._mqttWatchdog = null;
|
|
481
|
+
}
|
|
482
|
+
ctx._mqttConnected = false;
|
|
483
|
+
}));
|
|
484
|
+
|
|
485
|
+
mqttClient.on('offline', guard("offline", () => {
|
|
486
|
+
utils.warn("MQTT", "Connection went offline");
|
|
487
|
+
if (ctx._tmsTimeout) {
|
|
488
|
+
clearTimeout(ctx._tmsTimeout);
|
|
489
|
+
ctx._tmsTimeout = null;
|
|
490
|
+
}
|
|
491
|
+
if (ctx._mqttWatchdog) {
|
|
492
|
+
clearInterval(ctx._mqttWatchdog);
|
|
493
|
+
ctx._mqttWatchdog = null;
|
|
494
|
+
}
|
|
495
|
+
ctx._mqttConnected = false;
|
|
496
|
+
if (!ctx._ending && !ctx._cycling && ctx.globalOptions.autoReconnect) {
|
|
497
|
+
try { mqttClient.end(true); } catch (_) { }
|
|
498
|
+
// Schedule a reconnect — without this the bot silently stays offline.
|
|
499
|
+
const baseDelay = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
|
|
500
|
+
ctx._reconnectAttempts = (ctx._reconnectAttempts || 0) + 1;
|
|
501
|
+
const d = computeBackoffDelay(ctx, baseDelay, MQTT_MAX_BACKOFF, MQTT_JITTER_MAX);
|
|
502
|
+
utils.warn("MQTT", `Offline — reconnecting in ${d}ms`);
|
|
503
|
+
scheduleReconnect(d);
|
|
504
|
+
}
|
|
505
|
+
}));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const MQTT_DEFAULTS = {
|
|
509
|
+
cycleMs: 60 * 60 * 1000,
|
|
510
|
+
reconnectDelayMs: 2000,
|
|
511
|
+
autoReconnect: true,
|
|
512
|
+
watchdogIntervalMs: 60000,
|
|
513
|
+
staleMs: 300000,
|
|
514
|
+
reconnectAfterStop: false
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
function mqttConf(ctx, overrides) {
|
|
518
|
+
ctx._mqttOpt = Object.assign({}, MQTT_DEFAULTS, ctx._mqttOpt || {}, overrides || {});
|
|
519
|
+
if (typeof ctx._mqttOpt.autoReconnect === "boolean") {
|
|
520
|
+
ctx.globalOptions.autoReconnect = ctx._mqttOpt.autoReconnect;
|
|
521
|
+
}
|
|
522
|
+
return ctx._mqttOpt;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
module.exports = (defaultFuncs, api, ctx, opts) => {
|
|
526
|
+
const identity = () => {};
|
|
527
|
+
let globalCallback = identity;
|
|
528
|
+
|
|
529
|
+
function emitAuthError(reason, detail) {
|
|
530
|
+
try { if (ctx._autoCycleTimer) clearTimeout(ctx._autoCycleTimer); } catch (_) { }
|
|
531
|
+
try { if (ctx._reconnectTimer) clearTimeout(ctx._reconnectTimer); } catch (_) { }
|
|
532
|
+
try { ctx._ending = true; } catch (_) { }
|
|
533
|
+
try { if (ctx.mqttClient) ctx.mqttClient.end(true); } catch (_) { }
|
|
534
|
+
ctx.mqttClient = undefined;
|
|
535
|
+
ctx.loggedIn = false;
|
|
536
|
+
|
|
537
|
+
const msg = detail || reason;
|
|
538
|
+
utils.error("AUTH", `Authentication error -> ${reason}: ${msg}`);
|
|
539
|
+
|
|
540
|
+
// Emit typed events so consumer bots can react without parsing error strings
|
|
541
|
+
try {
|
|
542
|
+
if (ctx._emitter) {
|
|
543
|
+
const eventName = /checkpoint/i.test(reason) ? 'checkpoint' : 'sessionExpired';
|
|
544
|
+
ctx._emitter.emit(eventName, { reason, detail: msg, timestamp: Date.now() });
|
|
545
|
+
ctx._emitter.emit('account_inactive', { reason, detail: msg, timestamp: Date.now() });
|
|
546
|
+
}
|
|
547
|
+
} catch (_) {}
|
|
548
|
+
|
|
549
|
+
if (typeof globalCallback === "function") {
|
|
550
|
+
globalCallback({
|
|
551
|
+
type: "account_inactive",
|
|
552
|
+
reason: reason,
|
|
553
|
+
error: msg,
|
|
554
|
+
requiresReLogin: true,
|
|
555
|
+
timestamp: Date.now()
|
|
556
|
+
}, null);
|
|
557
|
+
}
|
|
558
|
+
try {
|
|
559
|
+
if (globalAutoReLoginManager && globalAutoReLoginManager.isEnabled && globalAutoReLoginManager.isEnabled()) {
|
|
560
|
+
globalAutoReLoginManager.handleSessionExpiry(api, 'https://www.facebook.com', "Session expired").then((ok) => {
|
|
561
|
+
if (ok && ctx._listeningActive && typeof api.listenMqtt === 'function') {
|
|
562
|
+
try {
|
|
563
|
+
if (typeof api.stopListening === 'function') {
|
|
564
|
+
try { api.stopListening(); } catch (_) {}
|
|
565
|
+
}
|
|
566
|
+
const cb = ctx._lastListenCallback || null;
|
|
567
|
+
if (cb) {
|
|
568
|
+
api.listenMqtt(cb);
|
|
569
|
+
} else {
|
|
570
|
+
api.listenMqtt();
|
|
571
|
+
}
|
|
572
|
+
} catch (_) {}
|
|
573
|
+
}
|
|
574
|
+
}).catch(() => {});
|
|
575
|
+
}
|
|
576
|
+
} catch (_) {}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function installPostGuard() {
|
|
580
|
+
if (ctx._postGuarded) return defaultFuncs.post;
|
|
581
|
+
const rawPost = defaultFuncs.post && defaultFuncs.post.bind(defaultFuncs);
|
|
582
|
+
if (!rawPost) return defaultFuncs.post;
|
|
583
|
+
|
|
584
|
+
function postSafe(...args) {
|
|
585
|
+
const lastArg = args[args.length - 1];
|
|
586
|
+
const hasCallback = typeof lastArg === 'function';
|
|
587
|
+
|
|
588
|
+
if (hasCallback) {
|
|
589
|
+
const originalCallback = args[args.length - 1];
|
|
590
|
+
args[args.length - 1] = function(err, ...cbArgs) {
|
|
591
|
+
if (err) {
|
|
592
|
+
const msg = (err && err.error) || (err && err.message) || String(err || "");
|
|
593
|
+
if (/Not logged in|Not logged in\.|blocked the login|checkpoint|security check|session.*expir|invalid.*session|authentication.*fail|auth.*fail/i.test(msg)) {
|
|
594
|
+
emitAuthError(
|
|
595
|
+
/blocked|checkpoint|security/i.test(msg) ? "login_blocked" : "not_logged_in",
|
|
596
|
+
msg
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return originalCallback(err, ...cbArgs);
|
|
601
|
+
};
|
|
602
|
+
return rawPost(...args);
|
|
603
|
+
} else {
|
|
604
|
+
const result = rawPost(...args);
|
|
605
|
+
if (result && typeof result.catch === 'function') {
|
|
606
|
+
return result.catch(err => {
|
|
607
|
+
const msg = (err && err.error) || (err && err.message) || String(err || "");
|
|
608
|
+
if (/Not logged in|Not logged in\.|blocked the login|checkpoint|security check|session.*expir|invalid.*session|authentication.*fail|auth.*fail/i.test(msg)) {
|
|
609
|
+
emitAuthError(
|
|
610
|
+
/blocked|checkpoint|security/i.test(msg) ? "login_blocked" : "not_logged_in",
|
|
611
|
+
msg
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
throw err;
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
return result;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
defaultFuncs.post = postSafe;
|
|
621
|
+
ctx._postGuarded = true;
|
|
622
|
+
utils.log("MQTT", "PostSafe guard installed for anti-automation detection");
|
|
623
|
+
return postSafe;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function scheduleReconnect(delayMs) {
|
|
627
|
+
const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
|
|
628
|
+
const ms = typeof delayMs === "number" ? delayMs : d;
|
|
629
|
+
if (ctx._ending) return;
|
|
630
|
+
if (ctx._reconnectTimer) return;
|
|
631
|
+
utils.warn("MQTT", `Will reconnect in ${ms}ms`);
|
|
632
|
+
ctx._reconnectTimer = setTimeout(() => {
|
|
633
|
+
ctx._reconnectTimer = null;
|
|
634
|
+
getSeqIDWrapper();
|
|
635
|
+
}, ms);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
let conf = mqttConf(ctx, opts);
|
|
639
|
+
installPostGuard();
|
|
640
|
+
|
|
641
|
+
getSeqID = async () => {
|
|
642
|
+
try {
|
|
643
|
+
form = {
|
|
644
|
+
av: ctx.globalOptions.pageID,
|
|
645
|
+
queries: JSON.stringify({
|
|
646
|
+
o0: {
|
|
647
|
+
doc_id: "3336396659757871",
|
|
648
|
+
query_params: {
|
|
649
|
+
limit: 1,
|
|
650
|
+
before: null,
|
|
651
|
+
tags: ["INBOX"],
|
|
652
|
+
includeDeliveryReceipts: false,
|
|
653
|
+
includeSeqID: true
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
})
|
|
657
|
+
};
|
|
658
|
+
utils.log("MQTT", "Getting sequence ID...");
|
|
659
|
+
ctx.t_mqttCalled = false;
|
|
660
|
+
const resData = await defaultFuncs.post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
661
|
+
|
|
662
|
+
if (utils.getType(resData) !== "Array") {
|
|
663
|
+
throw { error: "Not logged in" };
|
|
664
|
+
}
|
|
665
|
+
if (!Array.isArray(resData) || !resData.length) {
|
|
666
|
+
throw { error: "getSeqID: empty response" };
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const lastRes = resData[resData.length - 1];
|
|
670
|
+
if (lastRes && lastRes.successful_results === 0) {
|
|
671
|
+
throw { error: "getSeqID: no successful results" };
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const syncSeqId = resData[0] && resData[0].o0 && resData[0].o0.data && resData[0].o0.data.viewer && resData[0].o0.data.viewer.message_threads && resData[0].o0.data.viewer.message_threads.sync_sequence_id;
|
|
675
|
+
if (syncSeqId) {
|
|
676
|
+
ctx.lastSeqId = syncSeqId;
|
|
677
|
+
ctx._cycling = false;
|
|
678
|
+
utils.log("MQTT", "getSeqID ok -> listenMqtt()");
|
|
679
|
+
listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconnect, emitAuthError);
|
|
680
|
+
} else {
|
|
681
|
+
throw { error: "getSeqID: no sync_sequence_id found" };
|
|
682
|
+
}
|
|
683
|
+
} catch (err) {
|
|
684
|
+
const detail = (err && err.detail && err.detail.message) ? ` | detail=${err.detail.message}` : "";
|
|
685
|
+
const msg = ((err && err.error) || (err && err.message) || String(err || "")) + detail;
|
|
686
|
+
|
|
687
|
+
if (/Not logged in/i.test(msg)) {
|
|
688
|
+
utils.error("MQTT", "Auth error in getSeqID: Not logged in");
|
|
689
|
+
return emitAuthError("not_logged_in", msg);
|
|
690
|
+
}
|
|
691
|
+
if (/blocked the login|checkpoint|security check|session.*expir|invalid.*session|authentication.*fail|auth.*fail|login.*block|account.*lock|verification.*requir/i.test(msg)) {
|
|
692
|
+
utils.error("MQTT", "Auth error in getSeqID: Session/Login blocked");
|
|
693
|
+
return emitAuthError("login_blocked", msg);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
utils.error("MQTT", "getSeqID error:", msg);
|
|
697
|
+
if (ctx.globalOptions.autoReconnect) {
|
|
698
|
+
const baseDelay = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
|
|
699
|
+
ctx._reconnectAttempts = (ctx._reconnectAttempts || 0) + 1;
|
|
700
|
+
const d = computeBackoffDelay(ctx, baseDelay, MQTT_MAX_BACKOFF, MQTT_JITTER_MAX);
|
|
701
|
+
utils.warn("MQTT", `getSeqID failed, will retry in ${d}ms`);
|
|
702
|
+
scheduleReconnect(d);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
function getSeqIDWrapper() {
|
|
708
|
+
utils.log("MQTT", "getSeqID call");
|
|
709
|
+
return getSeqID()
|
|
710
|
+
.then(() => {
|
|
711
|
+
utils.log("MQTT", "getSeqID done");
|
|
712
|
+
ctx._cycling = false;
|
|
713
|
+
})
|
|
714
|
+
.catch(e => {
|
|
715
|
+
utils.error("MQTT", `getSeqID error: ${e && e.message ? e.message : e}`);
|
|
716
|
+
if (ctx.globalOptions.autoReconnect) {
|
|
717
|
+
ctx._reconnectAttempts = (ctx._reconnectAttempts || 0) + 1;
|
|
718
|
+
const baseDelay = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
|
|
719
|
+
scheduleReconnect(computeBackoffDelay(ctx, baseDelay, MQTT_MAX_BACKOFF, MQTT_JITTER_MAX));
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function isConnected() {
|
|
725
|
+
return !!(ctx.mqttClient && ctx.mqttClient.connected);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function unsubAll(cb) {
|
|
729
|
+
if (!isConnected()) return cb && cb();
|
|
730
|
+
let pending = topics.length;
|
|
731
|
+
if (!pending) return cb && cb();
|
|
732
|
+
let fired = false;
|
|
733
|
+
topics.forEach(t => {
|
|
734
|
+
ctx.mqttClient.unsubscribe(t, () => {
|
|
735
|
+
if (--pending === 0 && !fired) {
|
|
736
|
+
fired = true;
|
|
737
|
+
cb && cb();
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function endQuietly(next) {
|
|
744
|
+
const finish = () => {
|
|
745
|
+
try {
|
|
746
|
+
ctx.mqttClient && ctx.mqttClient.removeAllListeners();
|
|
747
|
+
} catch (_) { }
|
|
748
|
+
if (ctx._tmsTimeout) {
|
|
749
|
+
clearTimeout(ctx._tmsTimeout);
|
|
750
|
+
ctx._tmsTimeout = null;
|
|
751
|
+
}
|
|
752
|
+
if (ctx._reconnectTimer) {
|
|
753
|
+
clearTimeout(ctx._reconnectTimer);
|
|
754
|
+
ctx._reconnectTimer = null;
|
|
755
|
+
}
|
|
756
|
+
if (ctx._mqttWatchdog) {
|
|
757
|
+
clearInterval(ctx._mqttWatchdog);
|
|
758
|
+
ctx._mqttWatchdog = null;
|
|
759
|
+
}
|
|
760
|
+
ctx.mqttClient = undefined;
|
|
761
|
+
ctx.lastSeqId = null;
|
|
762
|
+
ctx.syncToken = undefined;
|
|
763
|
+
ctx.t_mqttCalled = false;
|
|
764
|
+
ctx._ending = false;
|
|
765
|
+
ctx._mqttConnected = false;
|
|
766
|
+
next && next();
|
|
767
|
+
};
|
|
768
|
+
try {
|
|
769
|
+
if (ctx.mqttClient) {
|
|
770
|
+
if (isConnected()) {
|
|
771
|
+
try {
|
|
772
|
+
ctx.mqttClient.publish("/browser_close", "{}");
|
|
773
|
+
} catch (_) { }
|
|
774
|
+
}
|
|
775
|
+
ctx.mqttClient.end(true, finish);
|
|
776
|
+
} else finish();
|
|
777
|
+
} catch (_) {
|
|
778
|
+
finish();
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function delayedReconnect() {
|
|
783
|
+
const d = conf.reconnectDelayMs;
|
|
784
|
+
utils.log("MQTT", `Reconnect in ${d}ms`);
|
|
785
|
+
setTimeout(() => getSeqIDWrapper(), d);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function forceCycle() {
|
|
789
|
+
if (ctx._cycling) return;
|
|
790
|
+
ctx._cycling = true;
|
|
791
|
+
ctx._ending = true;
|
|
792
|
+
utils.warn("MQTT", "Force cycle begin");
|
|
793
|
+
unsubAll(() => endQuietly(() => delayedReconnect()));
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return (callback) => {
|
|
797
|
+
class MessageEmitter extends EventEmitter {
|
|
798
|
+
stopListening(callback2) {
|
|
799
|
+
const cb = callback2 || function() {};
|
|
800
|
+
utils.log("MQTT", "Stop requested");
|
|
801
|
+
globalCallback = identity;
|
|
802
|
+
ctx._listeningActive = false;
|
|
803
|
+
|
|
804
|
+
if (ctx._autoCycleTimer) {
|
|
805
|
+
clearInterval(ctx._autoCycleTimer);
|
|
806
|
+
ctx._autoCycleTimer = null;
|
|
807
|
+
utils.log("MQTT", "Auto-cycle cleared");
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (ctx._reconnectTimer) {
|
|
811
|
+
clearTimeout(ctx._reconnectTimer);
|
|
812
|
+
ctx._reconnectTimer = null;
|
|
813
|
+
utils.log("MQTT", "Reconnect timer cleared");
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (ctx._tmsTimeout) {
|
|
817
|
+
clearTimeout(ctx._tmsTimeout);
|
|
818
|
+
ctx._tmsTimeout = null;
|
|
819
|
+
utils.log("MQTT", "TMS timeout cleared");
|
|
820
|
+
}
|
|
821
|
+
if (ctx._mqttWatchdog) {
|
|
822
|
+
clearInterval(ctx._mqttWatchdog);
|
|
823
|
+
ctx._mqttWatchdog = null;
|
|
824
|
+
utils.log("MQTT", "Watchdog cleared");
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
ctx._ending = true;
|
|
828
|
+
ctx._reconnectAttempts = 0;
|
|
829
|
+
|
|
830
|
+
// Stop background timers that would keep making requests
|
|
831
|
+
// to Facebook after the bot is supposed to be idle.
|
|
832
|
+
try {
|
|
833
|
+
if (api.tokenRefreshManager && typeof api.tokenRefreshManager.stopAutoRefresh === 'function') {
|
|
834
|
+
api.tokenRefreshManager.stopAutoRefresh();
|
|
835
|
+
utils.log("MQTT", "Token refresh stopped");
|
|
836
|
+
}
|
|
837
|
+
} catch (_) {}
|
|
838
|
+
try {
|
|
839
|
+
if (globalAutoReLoginManager && typeof globalAutoReLoginManager.stopSessionMonitoring === 'function') {
|
|
840
|
+
globalAutoReLoginManager.stopSessionMonitoring();
|
|
841
|
+
utils.log("MQTT", "Session monitoring stopped");
|
|
842
|
+
}
|
|
843
|
+
} catch (_) {}
|
|
844
|
+
|
|
845
|
+
unsubAll(() => endQuietly(() => {
|
|
846
|
+
utils.log("MQTT", "Stopped successfully");
|
|
847
|
+
cb();
|
|
848
|
+
conf = mqttConf(ctx, conf);
|
|
849
|
+
if (conf.reconnectAfterStop) delayedReconnect();
|
|
850
|
+
}));
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
async stopListeningAsync() {
|
|
854
|
+
return new Promise(resolve => {
|
|
855
|
+
this.stopListening(resolve);
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const msgEmitter = new MessageEmitter();
|
|
861
|
+
|
|
862
|
+
globalCallback = callback || function(error, message) {
|
|
863
|
+
if (error) {
|
|
864
|
+
utils.error("MQTT", "Emit error");
|
|
865
|
+
return msgEmitter.emit("error", error);
|
|
866
|
+
}
|
|
867
|
+
if (message && (message.type === "message" || message.type === "message_reply")) {
|
|
868
|
+
markAsRead(ctx, api, message.threadID);
|
|
869
|
+
}
|
|
870
|
+
msgEmitter.emit("message", message);
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
ctx._listeningActive = true;
|
|
874
|
+
ctx._lastListenCallback = callback || null;
|
|
875
|
+
|
|
876
|
+
conf = mqttConf(ctx, conf);
|
|
877
|
+
|
|
878
|
+
if (!ctx.firstListen) ctx.lastSeqId = null;
|
|
879
|
+
ctx.syncToken = undefined;
|
|
880
|
+
ctx.t_mqttCalled = false;
|
|
881
|
+
|
|
882
|
+
if (ctx._autoCycleTimer) {
|
|
883
|
+
clearTimeout(ctx._autoCycleTimer);
|
|
884
|
+
ctx._autoCycleTimer = null;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function scheduleAutoCycle() {
|
|
888
|
+
const base = conf.cycleMs;
|
|
889
|
+
if (!base || base <= 0) return;
|
|
890
|
+
const jitter = Math.floor(base * (0.2 + Math.random() * 0.4));
|
|
891
|
+
const next = base + (Math.random() > 0.5 ? jitter : -jitter);
|
|
892
|
+
ctx._autoCycleTimer = setTimeout(() => {
|
|
893
|
+
ctx._autoCycleTimer = null;
|
|
894
|
+
forceCycle();
|
|
895
|
+
scheduleAutoCycle();
|
|
896
|
+
}, next);
|
|
897
|
+
utils.log("MQTT", `Auto-cycle scheduled: ${next}ms`);
|
|
898
|
+
}
|
|
899
|
+
if (conf.cycleMs && conf.cycleMs > 0) {
|
|
900
|
+
scheduleAutoCycle();
|
|
901
|
+
} else {
|
|
902
|
+
utils.log("MQTT", "Auto-cycle disabled");
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
if (!ctx.firstListen || !ctx.lastSeqId) {
|
|
906
|
+
getSeqIDWrapper();
|
|
907
|
+
} else {
|
|
908
|
+
utils.log("MQTT", "Starting listenMqtt");
|
|
909
|
+
listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconnect, emitAuthError);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
if (ctx.firstListen) {
|
|
913
|
+
api.markAsReadAll().catch(err => {
|
|
914
|
+
utils.error("Failed to mark all messages as read on startup:", err);
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
ctx.firstListen = false;
|
|
919
|
+
|
|
920
|
+
api.stopListening = msgEmitter.stopListening;
|
|
921
|
+
api.stopListeningAsync = msgEmitter.stopListeningAsync;
|
|
922
|
+
return msgEmitter;
|
|
923
|
+
};
|
|
924
|
+
};
|