shadowx-fca 8.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.
Files changed (174) hide show
  1. package/README.md +1066 -0
  2. package/build/messagix.dll +0 -0
  3. package/build/messagix.so +0 -0
  4. package/checkUpdate.js +393 -0
  5. package/config.json +17 -0
  6. package/e2ee.js +563 -0
  7. package/e2eetest.js +356 -0
  8. package/index.js +611 -0
  9. package/lib/index.mjs +1412 -0
  10. package/logger.js +500 -0
  11. package/package.json +65 -0
  12. package/src/GetBotInfo.js +66 -0
  13. package/src/OldMessage.js +182 -0
  14. package/src/Screenshot.js +83 -0
  15. package/src/addExternalModule.js +13 -0
  16. package/src/addUserToGroup.js +33 -0
  17. package/src/approveGroupJoinRequests.js +18 -0
  18. package/src/changeAdminStatus.js +16 -0
  19. package/src/changeArchivedStatus.js +17 -0
  20. package/src/changeAvatar.js +136 -0
  21. package/src/changeAvatarV2.js +86 -0
  22. package/src/changeAvt.js +85 -0
  23. package/src/changeBio.js +76 -0
  24. package/src/changeBlockedStatus.js +20 -0
  25. package/src/changeBlockedStatusMqtt.js +80 -0
  26. package/src/changeCover.js +72 -0
  27. package/src/changeGroupImage.js +16 -0
  28. package/src/changeName.js +79 -0
  29. package/src/changeNickname.js +16 -0
  30. package/src/changeThreadColor.js +15 -0
  31. package/src/changeThreadEmoji.js +15 -0
  32. package/src/changeThreadMemberNickname.js +6 -0
  33. package/src/changeUsername.js +59 -0
  34. package/src/createCommentPost.js +230 -0
  35. package/src/createNewGroup.js +38 -0
  36. package/src/createNote.js +35 -0
  37. package/src/createPoll.js +27 -0
  38. package/src/createPost.js +276 -0
  39. package/src/createThemeAI.js +129 -0
  40. package/src/data/cache/system/data.json +4 -0
  41. package/src/data/cache/system/datahandle.js +21 -0
  42. package/src/data/getThreadInfo.json +1 -0
  43. package/src/deleteComment.js +23 -0
  44. package/src/deleteMessage.js +15 -0
  45. package/src/deleteThread.js +15 -0
  46. package/src/denyGroupJoinRequests.js +18 -0
  47. package/src/e2ee/crypto.js +173 -0
  48. package/src/e2ee/index.js +144 -0
  49. package/src/e2ee/proto/ArmadilloApplication.proto +281 -0
  50. package/src/e2ee/proto/ArmadilloICDC.proto +14 -0
  51. package/src/e2ee/proto/ConsumerApplication.proto +232 -0
  52. package/src/e2ee/proto/MessageApplication.proto +82 -0
  53. package/src/e2ee/proto/MessageTransport.proto +77 -0
  54. package/src/e2ee/proto/WACommon.proto +66 -0
  55. package/src/e2ee/proto/WAMediaTransport.proto +176 -0
  56. package/src/e2ee/proto/proto-writer.ts +76 -0
  57. package/src/e2ee/protocol.js +196 -0
  58. package/src/e2ee/ratchet.js +219 -0
  59. package/src/e2ee/store.js +182 -0
  60. package/src/e2ee.js +8 -0
  61. package/src/editMessage.js +56 -0
  62. package/src/editMessageOld.js +67 -0
  63. package/src/enableReactions.js +24 -0
  64. package/src/follow.js +74 -0
  65. package/src/followUser.js +23 -0
  66. package/src/forwardAttachment.js +16 -0
  67. package/src/friendList.js +98 -0
  68. package/src/getAccess.js +112 -0
  69. package/src/getAppState.js +13 -0
  70. package/src/getAvatarUser.js +11 -0
  71. package/src/getBio.js +24 -0
  72. package/src/getBotInitialData.js +42 -0
  73. package/src/getCtx.js +5 -0
  74. package/src/getCurrentUserID.js +6 -0
  75. package/src/getEmojiUrl.js +29 -0
  76. package/src/getFriendsList.js +36 -0
  77. package/src/getMessage.js +37 -0
  78. package/src/getNotes.js +17 -0
  79. package/src/getOptions.js +5 -0
  80. package/src/getPinnedMessages.js +33 -0
  81. package/src/getPostInfo.js +17 -0
  82. package/src/getProfileInfo.js +17 -0
  83. package/src/getPublicData.js +25 -0
  84. package/src/getRegion.js +7 -0
  85. package/src/getRepInfo.js +17 -0
  86. package/src/getStickerPacks.js +25 -0
  87. package/src/getStickers.js +39 -0
  88. package/src/getStoryReactions.js +18 -0
  89. package/src/getThreadHistory.js +45 -0
  90. package/src/getThreadHistoryDeprecated.js +71 -0
  91. package/src/getThreadInfo.js +73 -0
  92. package/src/getThreadInfoDeprecated.js +56 -0
  93. package/src/getThreadList.js +76 -0
  94. package/src/getThreadListDeprecated.js +46 -0
  95. package/src/getThreadPictures.js +59 -0
  96. package/src/getThreadTheme.js +77 -0
  97. package/src/getUID.js +17 -0
  98. package/src/getUserID.js +17 -0
  99. package/src/getUserInfo.js +28 -0
  100. package/src/handleFriendRequest.js +21 -0
  101. package/src/handleMessageRequest.js +15 -0
  102. package/src/httpGet.js +13 -0
  103. package/src/httpPost.js +12 -0
  104. package/src/httpPostFormData.js +12 -0
  105. package/src/listenE2EE.js +75 -0
  106. package/src/listenMqtt.js +802 -0
  107. package/src/listenNotification.js +85 -0
  108. package/src/logout.js +22 -0
  109. package/src/markAsDelivered.js +17 -0
  110. package/src/markAsRead.js +14 -0
  111. package/src/markAsReadAll.js +15 -0
  112. package/src/markAsSeen.js +15 -0
  113. package/src/metaTheme.js +185 -0
  114. package/src/muteThread.js +52 -0
  115. package/src/note.js +228 -0
  116. package/src/pin.js +53 -0
  117. package/src/pinMessage.js +6 -0
  118. package/src/postComment.js +29 -0
  119. package/src/postFormData.js +46 -0
  120. package/src/reactToComment.js +31 -0
  121. package/src/reactToPost.js +32 -0
  122. package/src/refreshFb_dtsg.js +31 -0
  123. package/src/removeSuspiciousAccount.js +74 -0
  124. package/src/removeUserFromGroup.js +15 -0
  125. package/src/reply.js +442 -0
  126. package/src/resolvePhotoUrl.js +15 -0
  127. package/src/searchForThread.js +20 -0
  128. package/src/searchFriends.js +28 -0
  129. package/src/searchStickers.js +53 -0
  130. package/src/send.js +46 -0
  131. package/src/sendAudio.js +8 -0
  132. package/src/sendBroadcast.js +93 -0
  133. package/src/sendButtons.js +161 -0
  134. package/src/sendComment.js +159 -0
  135. package/src/sendEmoji.js +10 -0
  136. package/src/sendFile.js +9 -0
  137. package/src/sendFriendRequest.js +33 -0
  138. package/src/sendGif.js +24 -0
  139. package/src/sendImage.js +9 -0
  140. package/src/sendLocation.js +9 -0
  141. package/src/sendMessage.js +487 -0
  142. package/src/sendMessage1.js +309 -0
  143. package/src/sendMessageMqtt.js +68 -0
  144. package/src/sendSticker.js +8 -0
  145. package/src/sendTypingIndicator.js +36 -0
  146. package/src/sendTypingIndicatorV2.js +28 -0
  147. package/src/sendVideo.js +9 -0
  148. package/src/sessionGuard.js +130 -0
  149. package/src/setActiveStatus.js +16 -0
  150. package/src/setMessageReaction.js +61 -0
  151. package/src/setMessageReactionMqtt.js +62 -0
  152. package/src/setOptions.js +22 -0
  153. package/src/setPollVote.js +17 -0
  154. package/src/setPostReaction.js +112 -0
  155. package/src/setProfileGuard.js +44 -0
  156. package/src/setProfileLock.js +93 -0
  157. package/src/setStoryReaction.js +129 -0
  158. package/src/setStorySeen.js +99 -0
  159. package/src/setThreadTheme.js +17 -0
  160. package/src/setTitle.js +15 -0
  161. package/src/shareContact.js +33 -0
  162. package/src/shareLink.js +8 -0
  163. package/src/sharePost.js +31 -0
  164. package/src/stopListenMqtt.js +23 -0
  165. package/src/storyManager.js +353 -0
  166. package/src/suggestFriend.js +128 -0
  167. package/src/threadColors.js +131 -0
  168. package/src/unfollowUser.js +23 -0
  169. package/src/unfriend.js +15 -0
  170. package/src/unpinMessage.js +6 -0
  171. package/src/unsendMessage.js +14 -0
  172. package/src/uploadAttachment.js +58 -0
  173. package/src/uploadImageToImgbb.js +29 -0
  174. package/utils.js +2945 -0
@@ -0,0 +1,802 @@
1
+ "use strict";
2
+
3
+ var utils = require("../utils");
4
+ var logger = require("../logger");
5
+ var mqtt = require('mqtt');
6
+ var WebSocket = require('ws');
7
+ var Transform = require('stream').Transform;
8
+ var EventEmitter = require('events');
9
+ var zlib = require('zlib');
10
+
11
+ var identity = function () { };
12
+ var form = {};
13
+ var getSeqID = function () { };
14
+
15
+ var MQTT_TOPICS = [
16
+ "/legacy_web", "/webrtc", "/rtc_multi", "/onevc",
17
+ "/br_sr", "/sr_res", "/t_ms", "/thread_typing",
18
+ "/orca_typing_notifications", "/notify_disconnect",
19
+ "/orca_presence", "/inbox", "/mercury",
20
+ "/messaging_events", "/orca_message_notifications",
21
+ "/pp", "/webrtc_response", "/ls_resp"
22
+ ];
23
+
24
+ function decompressResponse(data) {
25
+ return new Promise((resolve, reject) => {
26
+ if (!Buffer.isBuffer(data)) {
27
+ resolve(String(data));
28
+ return;
29
+ }
30
+
31
+ // Check for gzip magic numbers (0x1F 0x8B)
32
+ if (data.length > 2 && data[0] === 0x1F && data[1] === 0x8B) {
33
+ zlib.gunzip(data, (err, decoded) => {
34
+ if (err) {
35
+ zlib.inflate(data, (err2, decoded2) => {
36
+ if (err2) reject(err);
37
+ else resolve(decoded2.toString('utf8'));
38
+ });
39
+ } else {
40
+ resolve(decoded.toString('utf8'));
41
+ }
42
+ });
43
+ }
44
+ // Check for zlib magic numbers (0x78 0x9C, 0x78 0xDA, 0x78 0x01)
45
+ else if (data.length > 2 && data[0] === 0x78 && (data[1] === 0x9C || data[1] === 0xDA || data[1] === 0x01)) {
46
+ zlib.inflate(data, (err, decoded) => {
47
+ if (err) reject(err);
48
+ else resolve(decoded.toString('utf8'));
49
+ });
50
+ }
51
+ else {
52
+ // Brotli has no magic bytes — try brotliDecompress on any unrecognized binary,
53
+ // then fall back to plain UTF-8 if decompression fails.
54
+ zlib.brotliDecompress(data, (err, decoded) => {
55
+ if (!err) {
56
+ resolve(decoded.toString('utf8'));
57
+ } else {
58
+ resolve(data.toString('utf8'));
59
+ }
60
+ });
61
+ }
62
+ });
63
+ }
64
+
65
+ function createMqttPatchStream() {
66
+ var buf = null;
67
+ return new Transform({
68
+ transform(chunk, encoding, callback) {
69
+ if (!Buffer.isBuffer(chunk)) chunk = Buffer.from(chunk, encoding);
70
+ var out = buf ? Buffer.concat([buf, chunk]) : Buffer.from(chunk);
71
+ buf = null;
72
+ var i = 0;
73
+ while (i < out.length) {
74
+ var b = out[i];
75
+ var type = (b >> 4) & 0x0F;
76
+ var flags = b & 0x0F;
77
+ if (flags !== 0 && (type === 4 || type === 9 || type === 11 || type === 13 || type === 2)) {
78
+ out[i] = (b & 0xF0);
79
+ }
80
+ i++;
81
+ var multiplier = 1, frameLen = 0, lenOk = false;
82
+ while (i < out.length) {
83
+ var lb = out[i++];
84
+ frameLen += (lb & 0x7F) * multiplier;
85
+ multiplier *= 128;
86
+ if ((lb & 0x80) === 0) { lenOk = true; break; }
87
+ if (multiplier > 128 * 128 * 128) break;
88
+ }
89
+ if (!lenOk) {
90
+ buf = out.slice(i - 1);
91
+ out = out.slice(0, i - 1);
92
+ break;
93
+ }
94
+ i += frameLen;
95
+ }
96
+ callback(null, out);
97
+ },
98
+ flush(callback) {
99
+ if (buf && buf.length > 0) callback(null, buf);
100
+ else callback();
101
+ buf = null;
102
+ }
103
+ });
104
+ }
105
+
106
+ function attachImageUrlToAttachment(api, attachment) {
107
+ if (!attachment || attachment.type !== "photo" || !attachment.url) return;
108
+ if (api && api._imgUpload) {
109
+ api._imgUpload(attachment.url).then(url => {
110
+ if (url) attachment.imgUrl = url;
111
+ }).catch(() => { });
112
+ }
113
+ }
114
+
115
+ function markDelivery(ctx, api, threadID, messageID) {
116
+ if (!threadID || !messageID) return;
117
+ if (api.markAsDelivered) {
118
+ api.markAsDelivered(threadID, messageID, err => {
119
+ if (err) logger.error("markAsDelivered", err);
120
+ else if (ctx.globalOptions.autoMarkRead && api.markAsRead) {
121
+ api.markAsRead(threadID, err2 => { if (err2) logger.error("markAsRead", err2); });
122
+ }
123
+ });
124
+ }
125
+ }
126
+
127
+ function parseDelta(defaultFuncs, api, ctx, globalCallback, v) {
128
+ var delta = v.delta;
129
+
130
+ if (delta.class === "NewMessage") {
131
+ if (ctx.globalOptions.pageID && ctx.globalOptions.pageID != v.queue) return;
132
+
133
+ (function resolveAttachmentUrl(i) {
134
+ if (i === (delta.attachments || []).length) {
135
+ var fmtMsg;
136
+ try {
137
+ fmtMsg = utils.formatDeltaMessage(v);
138
+ var tk = delta.messageMetadata && delta.messageMetadata.threadKey || {};
139
+ fmtMsg.isSingleUser = !!tk.otherUserFbId && !tk.threadFbId;
140
+ fmtMsg.isGroup = !!tk.threadFbId;
141
+ if (!ctx.threadTypes) ctx.threadTypes = {};
142
+ ctx.threadTypes[fmtMsg.threadID] = fmtMsg.isSingleUser ? 'dm' : 'group';
143
+ if (fmtMsg.attachments && Array.isArray(fmtMsg.attachments)) {
144
+ fmtMsg.attachments.forEach(att => attachImageUrlToAttachment(api, att));
145
+ }
146
+ } catch (err) {
147
+ return globalCallback({ error: "Problem parsing message object.", detail: err, res: v, type: "parse_error" }, null);
148
+ }
149
+
150
+ if (fmtMsg && fmtMsg.messageID) {
151
+ if (!ctx._msgCache) ctx._msgCache = {};
152
+ if (!ctx._msgCacheKeys) ctx._msgCacheKeys = [];
153
+ ctx._msgCache[fmtMsg.messageID] = {
154
+ body: fmtMsg.body || "",
155
+ attachments: fmtMsg.attachments || [],
156
+ senderID: fmtMsg.senderID,
157
+ threadID: fmtMsg.threadID
158
+ };
159
+ ctx._msgCacheKeys.push(fmtMsg.messageID);
160
+ if (ctx._msgCacheKeys.length > 500) {
161
+ var evict = ctx._msgCacheKeys.shift();
162
+ delete ctx._msgCache[evict];
163
+ }
164
+ }
165
+
166
+ if (fmtMsg && ctx.globalOptions.autoMarkDelivery) {
167
+ markDelivery(ctx, api, fmtMsg.threadID, fmtMsg.messageID);
168
+ }
169
+ if (!ctx.globalOptions.selfListen &&
170
+ (fmtMsg.senderID === ctx.userID || fmtMsg.senderID === ctx.i_userID)) return;
171
+ return globalCallback(null, fmtMsg);
172
+ } else {
173
+ if ((delta.attachments[i].mercury || {}).attach_type === "photo" && api.resolvePhotoUrl) {
174
+ api.resolvePhotoUrl(delta.attachments[i].fbid, (err, url) => {
175
+ if (!err) delta.attachments[i].mercury.metadata.url = url;
176
+ return resolveAttachmentUrl(i + 1);
177
+ });
178
+ } else {
179
+ return resolveAttachmentUrl(i + 1);
180
+ }
181
+ }
182
+ })(0);
183
+ }
184
+
185
+ if (delta.class === "ClientPayload") {
186
+ var clientPayload = utils.decodeClientPayload(delta.payload);
187
+ if (clientPayload && clientPayload.deltas) {
188
+ for (var i in clientPayload.deltas) {
189
+ var d = clientPayload.deltas[i];
190
+ if (d.deltaMessageReaction && ctx.globalOptions.listenEvents) {
191
+ var dr = d.deltaMessageReaction;
192
+ globalCallback(null, {
193
+ type: "message_reaction",
194
+ threadID: (dr.threadKey.threadFbId || dr.threadKey.otherUserFbId).toString(),
195
+ messageID: dr.messageId,
196
+ reaction: dr.reaction,
197
+ senderID: dr.senderId.toString(),
198
+ userID: dr.userId.toString()
199
+ });
200
+ } else if (d.deltaRecallMessageData && ctx.globalOptions.listenEvents) {
201
+ var drm = d.deltaRecallMessageData;
202
+ var unsendEvt = {
203
+ type: "message_unsend",
204
+ threadID: (drm.threadKey.threadFbId || drm.threadKey.otherUserFbId).toString(),
205
+ messageID: drm.messageID,
206
+ senderID: drm.senderID.toString(),
207
+ deletionTimestamp: drm.deletionTimestamp,
208
+ timestamp: drm.timestamp,
209
+ body: "",
210
+ attachments: []
211
+ };
212
+ var cached = ctx._msgCache && ctx._msgCache[drm.messageID];
213
+ if (cached) {
214
+ unsendEvt.body = cached.body;
215
+ unsendEvt.attachments = cached.attachments;
216
+ if (cached.attachments && cached.attachments.length > 0) {
217
+ unsendEvt.attachmentType = cached.attachments[0].type || "unknown";
218
+ } else if (cached.body) {
219
+ unsendEvt.attachmentType = "text";
220
+ } else {
221
+ unsendEvt.attachmentType = "unknown";
222
+ }
223
+ } else {
224
+ unsendEvt.attachmentType = "unknown";
225
+ }
226
+ globalCallback(null, unsendEvt);
227
+ } else if (d.deltaMessageReply) {
228
+ var mdata = [];
229
+ try { mdata = JSON.parse((d.deltaMessageReply.message.data || {}).prng || "[]"); } catch (_) { }
230
+ var m_id = mdata.map(u => u.i);
231
+ var m_offset = mdata.map(u => u.o);
232
+ var m_length = mdata.map(u => u.l);
233
+ var mentions = {};
234
+ for (var j = 0; j < m_id.length; j++) {
235
+ mentions[m_id[j]] = (d.deltaMessageReply.message.body || "").substring(m_offset[j], m_offset[j] + m_length[j]);
236
+ }
237
+ var msg = d.deltaMessageReply.message;
238
+ var tk = msg.messageMetadata.threadKey;
239
+ var callbackToReturn = {
240
+ type: "message_reply",
241
+ threadID: (tk.threadFbId || tk.otherUserFbId).toString(),
242
+ messageID: msg.messageMetadata.messageId,
243
+ senderID: msg.messageMetadata.actorFbId.toString(),
244
+ body: msg.body || "",
245
+ args: (msg.body || "").trim().split(/\s+/),
246
+ isGroup: !!tk.threadFbId,
247
+ mentions,
248
+ timestamp: msg.messageMetadata.timestamp,
249
+ attachments: (msg.attachments || []).map(att => {
250
+ var mercury = {};
251
+ try { Object.assign(mercury, att.mercury || JSON.parse(att.mercuryJSON || '{}')); } catch (_) { }
252
+ try { return utils._formatAttachment(att, mercury); }
253
+ catch (ex) { return { type: "unknown", error: ex }; }
254
+ })
255
+ };
256
+ if (callbackToReturn.attachments) {
257
+ callbackToReturn.attachments.forEach(att => attachImageUrlToAttachment(api, att));
258
+ }
259
+
260
+ if (d.deltaMessageReply.repliedToMessage) {
261
+ var rtm = d.deltaMessageReply.repliedToMessage;
262
+ var rtmdata = [];
263
+ try { rtmdata = JSON.parse((rtm.data || {}).prng || "[]"); } catch (_) {}
264
+ var rt_id = rtmdata.map(function(u) { return u.i; });
265
+ var rt_offset = rtmdata.map(function(u) { return u.o; });
266
+ var rt_length = rtmdata.map(function(u) { return u.l; });
267
+ var rmentions = {};
268
+ for (var rj = 0; rj < rt_id.length; rj++) {
269
+ rmentions[rt_id[rj]] = (rtm.body || "").substring(rt_offset[rj], rt_offset[rj] + rt_length[rj]);
270
+ }
271
+ var rtk = rtm.messageMetadata.threadKey;
272
+ callbackToReturn.messageReply = {
273
+ threadID: (rtk.threadFbId || rtk.otherUserFbId).toString(),
274
+ messageID: rtm.messageMetadata.messageId,
275
+ senderID: rtm.messageMetadata.actorFbId.toString(),
276
+ attachments: (rtm.attachments || []).map(function(att) {
277
+ var mercury = {};
278
+ try { Object.assign(mercury, att.mercury || JSON.parse(att.mercuryJSON || '{}')); } catch (_) {}
279
+ try { return utils._formatAttachment(att, mercury); }
280
+ catch (ex) { return { type: "unknown", error: ex }; }
281
+ }),
282
+ args: (rtm.body || "").trim().split(/\s+/),
283
+ body: rtm.body || "",
284
+ isGroup: !!rtk.threadFbId,
285
+ mentions: rmentions,
286
+ timestamp: rtm.messageMetadata.timestamp
287
+ };
288
+ } else if (d.deltaMessageReply.replyToMessageId) {
289
+ return defaultFuncs.post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, {
290
+ av: ctx.globalOptions.pageID,
291
+ queries: JSON.stringify({
292
+ o0: {
293
+ doc_id: "2848441488556444",
294
+ query_params: {
295
+ thread_and_message_id: {
296
+ thread_id: callbackToReturn.threadID,
297
+ message_id: d.deltaMessageReply.replyToMessageId.id
298
+ }
299
+ }
300
+ }
301
+ })
302
+ })
303
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
304
+ .then(function(resData) {
305
+ if (!resData || !resData[0]) return;
306
+ var fetchData = resData[0].o0 && resData[0].o0.data && resData[0].o0.data.message;
307
+ if (!fetchData) return;
308
+ var mobj = {};
309
+ if (fetchData.message && fetchData.message.ranges) {
310
+ for (var n in fetchData.message.ranges) {
311
+ var range = fetchData.message.ranges[n];
312
+ mobj[range.entity.id] = (fetchData.message.text || "").substr(range.offset, range.length);
313
+ }
314
+ }
315
+ callbackToReturn.messageReply = {
316
+ threadID: callbackToReturn.threadID,
317
+ messageID: fetchData.message_id,
318
+ senderID: fetchData.message_sender.id.toString(),
319
+ attachments: ((fetchData.message && fetchData.message.blob_attachment) || []).map(function(att) {
320
+ try { return utils._formatAttachment({ blob_attachment: att }); }
321
+ catch (ex) { return { type: "unknown", error: ex }; }
322
+ }),
323
+ args: ((fetchData.message && fetchData.message.text) || "").trim().split(/\s+/),
324
+ body: (fetchData.message && fetchData.message.text) || "",
325
+ isGroup: callbackToReturn.isGroup,
326
+ mentions: mobj,
327
+ timestamp: parseInt(fetchData.timestamp_precise)
328
+ };
329
+ })
330
+ .catch(function() {})
331
+ .then(function() {
332
+ if (ctx.globalOptions.autoMarkDelivery) markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
333
+ if (!ctx.globalOptions.selfListen && callbackToReturn.senderID === ctx.userID) return;
334
+ globalCallback(null, callbackToReturn);
335
+ });
336
+ }
337
+
338
+ if (ctx.globalOptions.autoMarkDelivery) {
339
+ markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
340
+ }
341
+ if (!ctx.globalOptions.selfListen && callbackToReturn.senderID === ctx.userID) return;
342
+ globalCallback(null, callbackToReturn);
343
+ }
344
+ }
345
+ return;
346
+ }
347
+ }
348
+
349
+ if (delta.class !== "NewMessage" && !ctx.globalOptions.listenEvents) return;
350
+
351
+ switch (delta.class) {
352
+ case "AdminTextMessage":
353
+ case "ThreadName":
354
+ case "ParticipantsAddedToGroupThread":
355
+ case "ParticipantLeftGroupThread":
356
+ case "JoinableMode": {
357
+ var fmtEvt;
358
+ try { fmtEvt = utils.formatDeltaEvent(delta); }
359
+ catch (err) {
360
+ return globalCallback({ error: "Problem parsing event.", detail: err, res: delta, type: "parse_error" }, null);
361
+ }
362
+ if (delta.class === "AdminTextMessage") {
363
+ var allowedTypes = [
364
+ 'confirm_friend_request', 'shared_album_delete', 'shared_album_addition',
365
+ 'pin_messages_v2', 'unpin_messages_v2', 'change_thread_theme',
366
+ 'change_thread_nickname', 'change_thread_icon', 'change_thread_quick_reaction',
367
+ 'change_thread_admins', 'group_poll', 'joinable_group_link_mode_change',
368
+ 'magic_words', 'change_thread_approval_mode', 'messenger_call_log',
369
+ 'participant_joined_group_call'
370
+ ];
371
+ if (!allowedTypes.includes(delta.type)) return;
372
+ }
373
+ if (!ctx.globalOptions.selfListen && fmtEvt.author && fmtEvt.author.toString() === ctx.userID) {
374
+ if (delta.class === "ParticipantsAddedToGroupThread" || delta.class === "ParticipantLeftGroupThread") return;
375
+ }
376
+ return globalCallback(null, fmtEvt);
377
+ }
378
+
379
+ case "ForcedFetch": {
380
+ if (!delta.threadKey) return;
381
+ var mid = delta.messageId;
382
+ var tid = delta.threadKey.threadFbId;
383
+ if (mid && tid) {
384
+ defaultFuncs.post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, {
385
+ av: ctx.globalOptions.pageID,
386
+ queries: JSON.stringify({
387
+ o0: {
388
+ doc_id: "2848441488556444",
389
+ query_params: {
390
+ thread_and_message_id: {
391
+ thread_id: tid.toString(),
392
+ message_id: mid
393
+ }
394
+ }
395
+ }
396
+ })
397
+ })
398
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
399
+ .then(resData => {
400
+ if (!resData || !resData[0]) return;
401
+ var fetchData = resData[0].o0 && resData[0].o0.data && resData[0].o0.data.message;
402
+ if (!fetchData) return;
403
+ if (fetchData.__typename === "ThreadImageMessage") {
404
+ if (!ctx.loggedIn) return;
405
+ if (!ctx.globalOptions.selfListen && fetchData.message_sender.id.toString() === ctx.userID) return;
406
+ globalCallback(null, {
407
+ type: "change_thread_image",
408
+ threadID: utils.formatID(tid.toString()),
409
+ timestamp: fetchData.timestamp_precise,
410
+ author: fetchData.message_sender.id,
411
+ image: {
412
+ attachmentID: fetchData.image_with_metadata && fetchData.image_with_metadata.legacy_attachment_id,
413
+ url: fetchData.image_with_metadata && fetchData.image_with_metadata.preview && fetchData.image_with_metadata.preview.uri
414
+ }
415
+ });
416
+ } else if (fetchData.__typename === "UserMessage") {
417
+ globalCallback(null, {
418
+ type: "message",
419
+ senderID: utils.formatID(fetchData.message_sender.id),
420
+ body: (fetchData.message && fetchData.message.text) || "",
421
+ threadID: utils.formatID(tid.toString()),
422
+ messageID: fetchData.message_id,
423
+ timestamp: parseInt(fetchData.timestamp_precise),
424
+ isGroup: true,
425
+ attachments: [],
426
+ mentions: {}
427
+ });
428
+ }
429
+ })
430
+ .catch(err => logger.error("ForcedFetch", err));
431
+ }
432
+ break;
433
+ }
434
+ }
435
+ }
436
+
437
+ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
438
+ var chatOn = ctx.globalOptions.online;
439
+ var foreground = false;
440
+ var sessionID = Math.floor(Math.random() * 9007199254740991) + 1;
441
+ var GUID = utils.getGUID();
442
+
443
+ var username = {
444
+ u: ctx.userID,
445
+ s: sessionID,
446
+ chat_on: chatOn,
447
+ fg: foreground,
448
+ d: GUID,
449
+ ct: 'websocket',
450
+ aid: '219994525426954',
451
+ aids: null,
452
+ mqtt_sid: '',
453
+ cp: 3,
454
+ ecp: 10,
455
+ st: [],
456
+ pm: [],
457
+ dc: '',
458
+ no_auto_fg: true,
459
+ gas: null,
460
+ pack: [],
461
+ p: null,
462
+ php_override: ""
463
+ };
464
+
465
+ var cookies = ctx.jar.getCookies("https://www.facebook.com").join("; ");
466
+ var baseEndpoint = (ctx.mqttEndpoint || "wss://edge-chat.facebook.com/chat")
467
+ .replace(/[?&]sid=[^&]*/g, '')
468
+ .replace(/[?&]cid=[^&]*/g, '');
469
+ if (baseEndpoint.indexOf('?') === -1 && (ctx.mqttEndpoint || '').indexOf('?') !== -1) {
470
+ baseEndpoint = baseEndpoint.replace(/&/, '?');
471
+ }
472
+ var sep = baseEndpoint.indexOf('?') === -1 ? '?' : '&';
473
+ var host = baseEndpoint + sep + "sid=" + sessionID + "&cid=" + GUID;
474
+
475
+ var ua = ctx.globalOptions.userAgent ||
476
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15";
477
+
478
+ var wsHeaders = {
479
+ Cookie: cookies.replace(/[\r\n\[\]]/g, '').trim(),
480
+ Origin: "https://www.facebook.com",
481
+ "User-Agent": ua,
482
+ Referer: "https://www.facebook.com/",
483
+ Host: "edge-chat.facebook.com",
484
+ Connection: "Upgrade",
485
+ Upgrade: "websocket",
486
+ "Sec-WebSocket-Version": "13",
487
+ "Accept-Language": "en-US,en;q=0.9",
488
+ "Cache-Control": "no-cache",
489
+ Pragma: "no-cache"
490
+ };
491
+ if (ctx.region) wsHeaders["X-MSGR-Region"] = ctx.region;
492
+
493
+ var wsOptions = { headers: wsHeaders, origin: "https://www.facebook.com", protocolVersion: 13 };
494
+ if (ctx.globalOptions.proxy) {
495
+ try {
496
+ var { HttpsProxyAgent } = require('https-proxy-agent');
497
+ wsOptions.agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
498
+ } catch (_) { }
499
+ }
500
+
501
+ var mqttOptions = {
502
+ clientId: "mqttwsclient",
503
+ protocolId: "MQIsdp",
504
+ protocolVersion: 3,
505
+ username: JSON.stringify(username),
506
+ clean: true,
507
+ keepalive: 30,
508
+ reschedulePings: true,
509
+ reconnectPeriod: 0,
510
+ connectTimeout: 12000
511
+ };
512
+
513
+ function buildStream() {
514
+ var Duplex = require('stream').Duplex;
515
+ var ws = new WebSocket(host, wsOptions);
516
+ ws.on('error', () => { });
517
+ var wsStream = WebSocket.createWebSocketStream(ws, { objectMode: false });
518
+ var patcher = createMqttPatchStream();
519
+ wsStream.pipe(patcher);
520
+ var duplex = new Duplex({
521
+ read() { },
522
+ write(chunk, enc, cb) { wsStream.write(chunk, enc, cb); },
523
+ final(cb) { wsStream.end(cb); },
524
+ destroy(err, cb) { try { wsStream.destroy(err); } catch (_) { } cb(err); }
525
+ });
526
+ patcher.on('data', data => { if (!duplex.destroyed) duplex.push(data); });
527
+ patcher.on('end', () => { if (!duplex.destroyed) duplex.push(null); });
528
+ patcher.on('error', e => { if (!duplex.destroyed) duplex.destroy(e); });
529
+ wsStream.on('error', e => { if (!duplex.destroyed) duplex.destroy(e); });
530
+ return duplex;
531
+ }
532
+
533
+ logger.startSpinner(ctx.region);
534
+ ctx.mqttClient = new mqtt.MqttClient(buildStream, mqttOptions);
535
+ global.mqttClient = ctx.mqttClient;
536
+ var mqttClient = ctx.mqttClient;
537
+
538
+ mqttClient.on('error', err => {
539
+ logger.stopSpinner(false);
540
+ logger.error("MQTT", err.message || err);
541
+ mqttClient.end();
542
+ if (ctx.globalOptions.autoReconnect) {
543
+ logger.info("MQTT", "🔄 Auto-reconnecting in 3s...");
544
+ setTimeout(() => getSeqID(), 3000);
545
+ } else {
546
+ globalCallback({ type: "stop_listen", error: "MQTT connection refused" }, null);
547
+ }
548
+ });
549
+
550
+ mqttClient.on('connect', () => {
551
+ MQTT_TOPICS.forEach(t => mqttClient.subscribe(t));
552
+ logger.stopSpinner(true);
553
+ logger.success("MQTT", `⚡ Connected • Region: ${ctx.region || 'AUTO'} • Auto-reconnect: ${ctx.globalOptions.autoReconnect ? '✅' : '❌'}`);
554
+
555
+ var topic;
556
+ var queue = {
557
+ sync_api_version: 10,
558
+ max_deltas_able_to_process: 1000,
559
+ delta_batch_size: 500,
560
+ encoding: "JSON",
561
+ entity_fbid: ctx.userID,
562
+ };
563
+ if (ctx.syncToken) {
564
+ topic = "/messenger_sync_get_diffs";
565
+ queue.last_seq_id = ctx.lastSeqId;
566
+ queue.sync_token = ctx.syncToken;
567
+ } else {
568
+ topic = "/messenger_sync_create_queue";
569
+ queue.initial_titan_sequence_id = ctx.lastSeqId;
570
+ queue.device_params = null;
571
+ }
572
+ mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
573
+
574
+ var rTimeout = setTimeout(() => { mqttClient.end(); getSeqID(); }, 5000);
575
+ ctx.tmsWait = () => {
576
+ clearTimeout(rTimeout);
577
+ if (ctx.globalOptions.emitReady) globalCallback({ type: "ready", error: null }, null);
578
+ delete ctx.tmsWait;
579
+ };
580
+ });
581
+
582
+ mqttClient.on('message', (topic, message) => {
583
+ if (Buffer.isBuffer(message)) {
584
+ if (message.length > 0 && message[0] === 0x07) {
585
+ return;
586
+ }
587
+ const strMessage = message.toString('utf8');
588
+ if (strMessage.trim().startsWith('{') || strMessage.trim().startsWith('[')) {
589
+ try {
590
+ var jsonMessage = JSON.parse(strMessage);
591
+ } catch (ex) {
592
+ return;
593
+ }
594
+ } else {
595
+ return;
596
+ }
597
+ } else if (typeof message === 'string') {
598
+ try {
599
+ var jsonMessage = JSON.parse(message);
600
+ } catch (ex) {
601
+ logger.error("MQTT parse", ex.message);
602
+ return;
603
+ }
604
+ } else {
605
+ return;
606
+ }
607
+
608
+ if (topic === "/t_ms") {
609
+ if (ctx.tmsWait && typeof ctx.tmsWait === "function") ctx.tmsWait();
610
+ if (jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
611
+ ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
612
+ ctx.syncToken = jsonMessage.syncToken;
613
+ }
614
+ if (jsonMessage.lastIssuedSeqId) ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
615
+ for (var i in jsonMessage.deltas) {
616
+ parseDelta(defaultFuncs, api, ctx, globalCallback, { delta: jsonMessage.deltas[i] });
617
+ }
618
+ } else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
619
+ if (!ctx.globalOptions.listenTyping) return;
620
+ globalCallback(null, {
621
+ type: "typ",
622
+ isTyping: !!jsonMessage.state,
623
+ from: (jsonMessage.sender_fbid || "").toString(),
624
+ threadID: utils.formatID((jsonMessage.thread || jsonMessage.sender_fbid || "").toString())
625
+ });
626
+ } else if (topic === "/orca_presence") {
627
+ if (!ctx.globalOptions.updatePresence) return;
628
+ for (var j in jsonMessage.list) {
629
+ var data = jsonMessage.list[j];
630
+ globalCallback(null, {
631
+ type: "presence",
632
+ userID: data["u"].toString(),
633
+ timestamp: data["l"] * 1000,
634
+ statuses: data["p"]
635
+ });
636
+ }
637
+ } else if (topic === "/ls_resp") {
638
+ if (jsonMessage.request_id && ctx.reqCallbacks[jsonMessage.request_id]) {
639
+ ctx.reqCallbacks[jsonMessage.request_id](null, jsonMessage);
640
+ delete ctx.reqCallbacks[jsonMessage.request_id];
641
+ }
642
+ }
643
+ });
644
+
645
+ mqttClient.on('close', () => { logger.warn("MQTT", "Connection closed"); });
646
+ mqttClient.on('offline', () => { logger.warn("MQTT", "Client went offline"); });
647
+ }
648
+
649
+ module.exports = function (defaultFuncs, api, ctx) {
650
+ var globalCallback = identity;
651
+ var retryCount = 0;
652
+ var maxRetries = 3;
653
+
654
+ getSeqID = async function () {
655
+ ctx.t_mqttCalled = false;
656
+
657
+ // Refresh fb_dtsg before GraphQL call — modern Facebook doesn't embed it
658
+ // in the initial HTML, so we must fetch it fresh every time.
659
+ if (api.getFreshDtsg) {
660
+ try { await api.getFreshDtsg(); } catch (_) {}
661
+ }
662
+
663
+ defaultFuncs.post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, {
664
+ av: ctx.globalOptions.pageID || ctx.userID,
665
+ queries: JSON.stringify({
666
+ o0: {
667
+ doc_id: "3336396659757871",
668
+ query_params: {
669
+ limit: 1,
670
+ before: null,
671
+ tags: ["INBOX", "OTHER", "PENDING"],
672
+ includeDeliveryReceipts: false,
673
+ includeSeqID: true
674
+ }
675
+ }
676
+ })
677
+ })
678
+ .then(async function(res) {
679
+ let body = res.body;
680
+
681
+ // Decompress if needed
682
+ if (Buffer.isBuffer(body)) {
683
+ body = await decompressResponse(body);
684
+ res.body = body;
685
+ } else if (typeof body === 'string') {
686
+ try {
687
+ JSON.parse(body);
688
+ } catch (e) {
689
+ body = await decompressResponse(Buffer.from(body));
690
+ res.body = body;
691
+ }
692
+ }
693
+
694
+ return res;
695
+ })
696
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
697
+ .then(resData => {
698
+ retryCount = 0;
699
+ if (!Array.isArray(resData)) throw { error: "Not logged in", res: resData };
700
+ if (resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
701
+ if (resData[resData.length - 1].successful_results === 0) throw { error: "getSeqId: no successful_results" };
702
+ var threads = resData[0].o0.data && resData[0].o0.data.viewer && resData[0].o0.data.viewer.message_threads;
703
+ if (threads && threads.sync_sequence_id) {
704
+ ctx.lastSeqId = threads.sync_sequence_id;
705
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
706
+ } else {
707
+ throw { error: "getSeqId: no sync_sequence_id found." };
708
+ }
709
+ })
710
+ .catch(async err => {
711
+ logger.error("getSeqID", err.error || err.message || err);
712
+
713
+ if (err.error === "Not logged in") {
714
+ ctx.loggedIn = false;
715
+ return globalCallback(err, null);
716
+ }
717
+
718
+ // Check if this is a binary/gzip or JSON parse error — retry instead of crashing
719
+ if (err.isBinaryResponse === true ||
720
+ (err.res && Buffer.isBuffer(err.res)) ||
721
+ err.error === "JSON.parse error" ||
722
+ (err.message && err.message.includes("JSON.parse")) ||
723
+ (err.detail && err.detail.message && err.detail.message.includes("JSON.parse"))) {
724
+ if (err.res && typeof err.res === "string") {
725
+ logger.warn("getSeqID", `Response preview: ${err.res.substring(0, 200)}`);
726
+ }
727
+ retryCount++;
728
+ if (retryCount <= maxRetries) {
729
+ logger.warn("getSeqID", `Parse error detected, retrying (${retryCount}/${maxRetries}) in 5 seconds...`);
730
+ await new Promise(resolve => setTimeout(resolve, 5000));
731
+ getSeqID();
732
+ return;
733
+ } else {
734
+ logger.warn("getSeqID", `Failed after ${maxRetries} retries, will retry again in 30 seconds...`);
735
+ retryCount = 0;
736
+ await new Promise(resolve => setTimeout(resolve, 30000));
737
+ getSeqID();
738
+ return;
739
+ }
740
+ }
741
+
742
+ return globalCallback(err, null);
743
+ });
744
+ };
745
+
746
+ return function (callback) {
747
+ class MessageEmitter extends EventEmitter {
748
+ stopListening(cb) {
749
+ cb = cb || (() => { });
750
+ globalCallback = identity;
751
+ if (ctx.mqttClient) {
752
+ ctx.mqttClient.unsubscribe("/webrtc");
753
+ ctx.mqttClient.unsubscribe("/rtc_multi");
754
+ ctx.mqttClient.unsubscribe("/onevc");
755
+ ctx.mqttClient.publish("/browser_close", "{}");
756
+ ctx.mqttClient.end(false, (...data) => { cb(data); ctx.mqttClient = undefined; });
757
+ } else {
758
+ cb([]);
759
+ }
760
+ }
761
+ stopListeningAsync() {
762
+ return new Promise(res => this.stopListening(res));
763
+ }
764
+ }
765
+
766
+ var msgEmitter = new MessageEmitter();
767
+ globalCallback = callback || ((error, message) => {
768
+ if (error) return msgEmitter.emit("error", error);
769
+ msgEmitter.emit("message", message);
770
+ });
771
+
772
+ if (!ctx.firstListen) ctx.lastSeqId = null;
773
+ ctx.syncToken = undefined;
774
+
775
+ form = {
776
+ av: ctx.globalOptions.pageID,
777
+ queries: JSON.stringify({
778
+ o0: {
779
+ doc_id: "3336396659757871",
780
+ query_params: {
781
+ limit: 1,
782
+ before: null,
783
+ tags: ["INBOX", "OTHER", "PENDING"],
784
+ includeDeliveryReceipts: false,
785
+ includeSeqID: true
786
+ }
787
+ }
788
+ })
789
+ };
790
+
791
+ if (!ctx.firstListen || !ctx.lastSeqId) {
792
+ getSeqID();
793
+ } else {
794
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
795
+ }
796
+
797
+ ctx.firstListen = false;
798
+ api.stopListening = msgEmitter.stopListening.bind(msgEmitter);
799
+ api.stopListeningAsync = msgEmitter.stopListeningAsync.bind(msgEmitter);
800
+ return msgEmitter;
801
+ };
802
+ };