nexus-fca 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.
Files changed (180) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/DOCS.md +2047 -0
  3. package/Fca_Database/database.sqlite +0 -0
  4. package/LICENSE-MIT +21 -0
  5. package/README.md +240 -0
  6. package/docs/README.md +9 -0
  7. package/docs/addExternalModule.md +15 -0
  8. package/docs/addUserToGroup.md +20 -0
  9. package/docs/changeAdminStatus.md +21 -0
  10. package/docs/changeArchivedStatus.md +19 -0
  11. package/docs/changeAvatar.md +21 -0
  12. package/docs/changeAvatarV2.md +18 -0
  13. package/docs/changeBio.md +15 -0
  14. package/docs/changeBlockedStatus.md +16 -0
  15. package/docs/changeBlockedStatusMqtt.md +16 -0
  16. package/docs/changeCover.md +15 -0
  17. package/docs/changeGroupImage.md +16 -0
  18. package/docs/changeName.md +18 -0
  19. package/docs/changeNickname.md +17 -0
  20. package/docs/changeThreadColor.md +16 -0
  21. package/docs/changeThreadEmoji.md +16 -0
  22. package/docs/changeUsername.md +15 -0
  23. package/docs/createCommentPost.md +16 -0
  24. package/docs/createNewGroup.md +16 -0
  25. package/docs/createPoll.md +17 -0
  26. package/docs/createPost.md +15 -0
  27. package/docs/deleteMessage.md +15 -0
  28. package/docs/deleteThread.md +15 -0
  29. package/docs/editMessage.md +16 -0
  30. package/docs/follow.md +15 -0
  31. package/docs/forwardAttachment.md +16 -0
  32. package/docs/getAccess.md +17 -0
  33. package/docs/getAvatarUser.md +15 -0
  34. package/docs/getBotInitialData.md +14 -0
  35. package/docs/getCtx.md +14 -0
  36. package/docs/getCurrentUserID.md +12 -0
  37. package/docs/getEmojiUrl.md +17 -0
  38. package/docs/getFriendsList.md +14 -0
  39. package/docs/getMessage.md +15 -0
  40. package/docs/getOptions.md +14 -0
  41. package/docs/getRegion.md +14 -0
  42. package/docs/getThreadHistory.md +17 -0
  43. package/docs/getThreadHistoryDeprecated.md +17 -0
  44. package/docs/getThreadInfo.md +15 -0
  45. package/docs/getThreadInfoDeprecated.md +17 -0
  46. package/docs/getThreadList.md +17 -0
  47. package/docs/getThreadListDeprecated.md +17 -0
  48. package/docs/getThreadPictures.md +17 -0
  49. package/docs/getUID.md +15 -0
  50. package/docs/getUserID.md +15 -0
  51. package/docs/getUserInfo.md +15 -0
  52. package/docs/handleFriendRequest.md +16 -0
  53. package/docs/handleMessageRequest.md +16 -0
  54. package/docs/httpGet.md +15 -0
  55. package/docs/httpPost.md +16 -0
  56. package/docs/httpPostFormData.md +16 -0
  57. package/docs/listenMqtt.md +17 -0
  58. package/docs/listenNotification.md +14 -0
  59. package/docs/logout.md +14 -0
  60. package/docs/markAsDelivered.md +16 -0
  61. package/docs/markAsRead.md +16 -0
  62. package/docs/markAsReadAll.md +14 -0
  63. package/docs/markAsSeen.md +15 -0
  64. package/docs/muteThread.md +16 -0
  65. package/docs/pinMessage.md +17 -0
  66. package/docs/postFormData.md +16 -0
  67. package/docs/refreshFb_dtsg.md +14 -0
  68. package/docs/removeUserFromGroup.md +16 -0
  69. package/docs/resolvePhotoUrl.md +15 -0
  70. package/docs/searchForThread.md +15 -0
  71. package/docs/searchStickers.md +15 -0
  72. package/docs/sendComment.md +16 -0
  73. package/docs/sendMessage.md +16 -0
  74. package/docs/sendMessageMqtt.md +16 -0
  75. package/docs/sendTypingIndicator.md +15 -0
  76. package/docs/setMessageReaction.md +17 -0
  77. package/docs/setMessageReactionMqtt.md +16 -0
  78. package/docs/setPostReaction.md +16 -0
  79. package/docs/setProfileGuard.md +15 -0
  80. package/docs/setStoryReaction.md +16 -0
  81. package/docs/setTitle.md +16 -0
  82. package/docs/shareContact.md +16 -0
  83. package/docs/shareLink.md +16 -0
  84. package/docs/stopListenMqtt.md +8 -0
  85. package/docs/threadColors.md +11 -0
  86. package/docs/unfriend.md +15 -0
  87. package/docs/unsendMessage.md +15 -0
  88. package/docs/uploadAttachment.md +15 -0
  89. package/fca-config.json +7 -0
  90. package/index.d.ts +618 -0
  91. package/index.js +361 -0
  92. package/lib/database/models/index.js +47 -0
  93. package/lib/database/models/thread.js +31 -0
  94. package/lib/database/threadData.js +93 -0
  95. package/lib/logger.js +24 -0
  96. package/lib/login.js +0 -0
  97. package/package.json +90 -0
  98. package/src/addExternalModule.js +19 -0
  99. package/src/addUserToGroup.js +113 -0
  100. package/src/changeAdminStatus.js +79 -0
  101. package/src/changeArchivedStatus.js +55 -0
  102. package/src/changeAvatar.js +126 -0
  103. package/src/changeAvatarV2.js +77 -0
  104. package/src/changeBio.js +77 -0
  105. package/src/changeBlockedStatus.js +47 -0
  106. package/src/changeBlockedStatusMqtt.js +71 -0
  107. package/src/changeCover.js +72 -0
  108. package/src/changeGroupImage.js +132 -0
  109. package/src/changeName.js +79 -0
  110. package/src/changeNickname.js +59 -0
  111. package/src/changeThreadColor.js +65 -0
  112. package/src/changeThreadEmoji.js +55 -0
  113. package/src/changeUsername.js +58 -0
  114. package/src/createCommentPost.js +225 -0
  115. package/src/createNewGroup.js +86 -0
  116. package/src/createPoll.js +71 -0
  117. package/src/createPost.js +276 -0
  118. package/src/deleteMessage.js +56 -0
  119. package/src/deleteThread.js +56 -0
  120. package/src/editMessage.js +57 -0
  121. package/src/follow.js +54 -0
  122. package/src/forwardAttachment.js +60 -0
  123. package/src/getAccess.js +67 -0
  124. package/src/getAvatarUser.js +56 -0
  125. package/src/getBotInitialData.js +37 -0
  126. package/src/getCtx.js +6 -0
  127. package/src/getCurrentUserID.js +7 -0
  128. package/src/getEmojiUrl.js +29 -0
  129. package/src/getFriendsList.js +83 -0
  130. package/src/getMessage.js +796 -0
  131. package/src/getOptions.js +6 -0
  132. package/src/getRegion.js +8 -0
  133. package/src/getThreadHistory.js +666 -0
  134. package/src/getThreadHistoryDeprecated.js +55 -0
  135. package/src/getThreadInfo.js +535 -0
  136. package/src/getThreadInfoDeprecated.js +49 -0
  137. package/src/getThreadList.js +192 -0
  138. package/src/getThreadListDeprecated.js +54 -0
  139. package/src/getThreadPictures.js +79 -0
  140. package/src/getUID.js +67 -0
  141. package/src/getUserID.js +66 -0
  142. package/src/getUserInfo.js +80 -0
  143. package/src/handleFriendRequest.js +61 -0
  144. package/src/handleMessageRequest.js +65 -0
  145. package/src/httpGet.js +57 -0
  146. package/src/httpPost.js +57 -0
  147. package/src/httpPostFormData.js +63 -0
  148. package/src/listenMqtt.js +1039 -0
  149. package/src/listenNotification.js +65 -0
  150. package/src/logout.js +75 -0
  151. package/src/markAsDelivered.js +58 -0
  152. package/src/markAsRead.js +80 -0
  153. package/src/markAsReadAll.js +50 -0
  154. package/src/markAsSeen.js +59 -0
  155. package/src/muteThread.js +52 -0
  156. package/src/pinMessage.js +59 -0
  157. package/src/postFormData.js +46 -0
  158. package/src/refreshFb_dtsg.js +66 -0
  159. package/src/removeUserFromGroup.js +79 -0
  160. package/src/resolvePhotoUrl.js +45 -0
  161. package/src/searchForThread.js +53 -0
  162. package/src/searchStickers.js +51 -0
  163. package/src/sendComment.js +63 -0
  164. package/src/sendMessage.js +328 -0
  165. package/src/sendMessageMqtt.js +316 -0
  166. package/src/sendTypingIndicator.js +103 -0
  167. package/src/setMessageReaction.js +119 -0
  168. package/src/setMessageReactionMqtt.js +61 -0
  169. package/src/setPostReaction.js +109 -0
  170. package/src/setProfileGuard.js +45 -0
  171. package/src/setStoryReaction.js +62 -0
  172. package/src/setTitle.js +86 -0
  173. package/src/shareContact.js +49 -0
  174. package/src/shareLink.js +59 -0
  175. package/src/stopListenMqtt.js +21 -0
  176. package/src/threadColors.js +131 -0
  177. package/src/unfriend.js +52 -0
  178. package/src/unsendMessage.js +49 -0
  179. package/src/uploadAttachment.js +95 -0
  180. package/utils.js +1432 -0
@@ -0,0 +1,1039 @@
1
+ "use strict";
2
+ const utils = require("../utils");
3
+ const log = require("npmlog");
4
+ const mqtt = require("mqtt");
5
+ const WebSocket = require("ws");
6
+ const HttpsProxyAgent = require("https-proxy-agent");
7
+ const EventEmitter = require("events");
8
+ const Duplexify = require("duplexify");
9
+ const { Transform } = require("stream");
10
+ var identity = function () {};
11
+ var form = {};
12
+ var getSeqID = function () {};
13
+ const logger = require("../lib/logger.js");
14
+ const topics = [
15
+ "/ls_req",
16
+ "/ls_resp",
17
+ "/legacy_web",
18
+ "/webrtc",
19
+ "/rtc_multi",
20
+ "/onevc",
21
+ "/br_sr",
22
+ "/sr_res",
23
+ "/t_ms",
24
+ "/thread_typing",
25
+ "/orca_typing_notifications",
26
+ "/notify_disconnect",
27
+ "/orca_presence",
28
+ "/inbox",
29
+ "/mercury",
30
+ "/messaging_events",
31
+ "/orca_message_notifications",
32
+ "/pp",
33
+ "/webrtc_response",
34
+ ];
35
+ let WebSocket_Global;
36
+ function buildProxy() {
37
+ const Proxy = new Transform({
38
+ objectMode: false,
39
+ transform(chunk, enc, next) {
40
+ if (WebSocket_Global.readyState !== WebSocket.OPEN) {
41
+ return next();
42
+ }
43
+ const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf8");
44
+ try {
45
+ WebSocket_Global.send(data);
46
+ next();
47
+ } catch (err) {
48
+ console.error("WebSocket send error:", err);
49
+ next(err);
50
+ }
51
+ },
52
+ flush(done) {
53
+ if (WebSocket_Global.readyState === WebSocket.OPEN) {
54
+ WebSocket_Global.close();
55
+ }
56
+ done();
57
+ },
58
+ writev(chunks, cb) {
59
+ try {
60
+ for (const { chunk } of chunks) {
61
+ this.push(
62
+ Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf8")
63
+ );
64
+ }
65
+ cb();
66
+ } catch (err) {
67
+ console.error("Writev error:", err);
68
+ cb(err);
69
+ }
70
+ },
71
+ });
72
+
73
+ return Proxy;
74
+ }
75
+ function buildStream(options, WebSocket, Proxy) {
76
+ const Stream = Duplexify(undefined, undefined, options);
77
+ Stream.socket = WebSocket;
78
+ let pingInterval;
79
+ let reconnectTimeout;
80
+ const clearTimers = () => {
81
+ clearInterval(pingInterval);
82
+ clearTimeout(reconnectTimeout);
83
+ };
84
+ WebSocket.onclose = () => {
85
+ clearTimers();
86
+ Stream.end();
87
+ Stream.destroy();
88
+ };
89
+ WebSocket.onerror = (err) => {
90
+ clearTimers();
91
+ Stream.destroy(err);
92
+ };
93
+ WebSocket.onmessage = (event) => {
94
+ clearTimeout(reconnectTimeout);
95
+ const data =
96
+ event.data instanceof ArrayBuffer
97
+ ? Buffer.from(event.data)
98
+ : Buffer.from(event.data, "utf8");
99
+ Stream.push(data);
100
+ };
101
+ WebSocket.onopen = () => {
102
+ Stream.setReadable(Proxy);
103
+ Stream.setWritable(Proxy);
104
+ Stream.emit("connect");
105
+ pingInterval = setInterval(() => {
106
+ if (WebSocket.readyState === WebSocket.OPEN) {
107
+ WebSocket.ping();
108
+ }
109
+ }, 30000);
110
+ reconnectTimeout = setTimeout(() => {
111
+ if (WebSocket.readyState === WebSocket.OPEN) {
112
+ WebSocket.close();
113
+ Stream.end();
114
+ Stream.destroy();
115
+ }
116
+ }, 60000);
117
+ };
118
+ WebSocket_Global = WebSocket;
119
+ Proxy.on("close", () => {
120
+ clearTimers();
121
+ WebSocket.close();
122
+ });
123
+ return Stream;
124
+ }
125
+ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
126
+ const chatOn = ctx.globalOptions.online;
127
+ const foreground = false;
128
+ const sessionID = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + 1;
129
+ const GUID = utils.getGUID();
130
+ const username = {
131
+ u: ctx.userID,
132
+ s: sessionID,
133
+ chat_on: chatOn,
134
+ fg: foreground,
135
+ d: GUID,
136
+ ct: "websocket",
137
+ aid: 219994525426954,
138
+ aids: null,
139
+ mqtt_sid: "",
140
+ cp: 3,
141
+ ecp: 10,
142
+ st: [],
143
+ pm: [],
144
+ dc: "",
145
+ no_auto_fg: true,
146
+ gas: null,
147
+ pack: [],
148
+ p: null,
149
+ php_override: ""
150
+ };
151
+ const cookies = ctx.jar.getCookies("https://www.facebook.com").join("; ");
152
+ let host;
153
+ if (ctx.mqttEndpoint) {
154
+ host = `${ctx.mqttEndpoint}&sid=${sessionID}&cid=${GUID}`;
155
+ } else if (ctx.region) {
156
+ host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLowerCase()}&sid=${sessionID}&cid=${GUID}`;
157
+ } else {
158
+ host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}&cid=${GUID}`;
159
+ }
160
+ const options = {
161
+ clientId: "mqttwsclient",
162
+ protocolId: "MQIsdp",
163
+ protocolVersion: 3,
164
+ username: JSON.stringify(username),
165
+ clean: true,
166
+ wsOptions: {
167
+ headers: {
168
+ Cookie: cookies,
169
+ Origin: "https://www.facebook.com",
170
+ "User-Agent":
171
+ ctx.globalOptions.userAgent ||
172
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
173
+ Referer: "https://www.facebook.com/",
174
+ Host: "edge-chat.facebook.com",
175
+ Connection: "Upgrade",
176
+ Pragma: "no-cache",
177
+ "Cache-Control": "no-cache",
178
+ Upgrade: "websocket",
179
+ "Sec-WebSocket-Version": "13",
180
+ "Accept-Encoding": "gzip, deflate, br",
181
+ "Accept-Language": "vi,en;q=0.9",
182
+ "Sec-WebSocket-Extensions":
183
+ "permessage-deflate; client_max_window_bits",
184
+ },
185
+ origin: "https://www.facebook.com",
186
+ protocolVersion: 13,
187
+ binaryType: "arraybuffer",
188
+ },
189
+ keepalive: 30,
190
+ reschedulePings: true,
191
+ reconnectPeriod: 1000,
192
+ connectTimeout: 5000,
193
+ };
194
+ if (ctx.globalOptions.proxy !== undefined) {
195
+ const agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
196
+ options.wsOptions.agent = agent;
197
+ }
198
+ ctx.mqttClient = new mqtt.Client(
199
+ () =>
200
+ buildStream(
201
+ options,
202
+ new WebSocket(host, options.wsOptions),
203
+ buildProxy()
204
+ ),
205
+ options
206
+ );
207
+ const mqttClient = ctx.mqttClient;
208
+ global.mqttClient = mqttClient;
209
+ mqttClient.on('error', function (err) {
210
+ log.error("listenMqtt", err);
211
+ mqttClient.end();
212
+ if (ctx.globalOptions.autoReconnect) {
213
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
214
+ } else {
215
+ utils.checkLiveCookie(ctx, defaultFuncs)
216
+ .then(res => {
217
+ globalCallback({
218
+ type: "stop_listen",
219
+ error: "Connection refused: Server unavailable"
220
+ }, null);
221
+ })
222
+ .catch(err => {
223
+ globalCallback({
224
+ type: "account_inactive",
225
+ error: "Maybe your account is blocked by facebook, please login and check at https://facebook.com"
226
+ }, null);
227
+ });
228
+ }
229
+ });
230
+ mqttClient.on("connect", function () {
231
+ if (process.env.OnStatus === undefined) {
232
+ logger("Nexus-FCA premium features loaded", "info");
233
+ process.env.OnStatus = true;
234
+ }
235
+ topics.forEach((topicsub) => mqttClient.subscribe(topicsub));
236
+ var topic;
237
+ const queue = {
238
+ sync_api_version: 11,
239
+ max_deltas_able_to_process: 100,
240
+ delta_batch_size: 500,
241
+ encoding: "JSON",
242
+ entity_fbid: ctx.userID,
243
+ initial_titan_sequence_id: ctx.lastSeqId,
244
+ device_params: null,
245
+ };
246
+ if (ctx.syncToken) {
247
+ topic = "/messenger_sync_get_diffs";
248
+ queue.last_seq_id = ctx.lastSeqId;
249
+ queue.sync_token = ctx.syncToken;
250
+ } else {
251
+ topic = "/messenger_sync_create_queue";
252
+ queue.initial_titan_sequence_id = ctx.lastSeqId;
253
+ queue.device_params = null;
254
+ }
255
+ mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
256
+ mqttClient.publish(
257
+ "/foreground_state",
258
+ JSON.stringify({ foreground: chatOn }),
259
+ { qos: 1 }
260
+ );
261
+ mqttClient.publish(
262
+ "/set_client_settings",
263
+ JSON.stringify({ make_user_available_when_in_foreground: true }),
264
+ { qos: 1 }
265
+ );
266
+ const rTimeout = setTimeout(function () {
267
+ mqttClient.end();
268
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
269
+ }, 5000);
270
+ ctx.tmsWait = function () {
271
+ clearTimeout(rTimeout);
272
+ ctx.globalOptions.emitReady
273
+ ? globalCallback({
274
+ type: "ready",
275
+ error: null,
276
+ })
277
+ : "";
278
+ delete ctx.tmsWait;
279
+ };
280
+ });
281
+ mqttClient.on("message", function (topic, message, _packet) {
282
+ try {
283
+ let jsonMessage = Buffer.isBuffer(message)
284
+ ? Buffer.from(message).toString()
285
+ : message;
286
+ try {
287
+ jsonMessage = JSON.parse(jsonMessage);
288
+ } catch (e) {
289
+ jsonMessage = {};
290
+ }
291
+ if (jsonMessage.type === "jewel_requests_add") {
292
+ globalCallback(null, {
293
+ type: "friend_request_received",
294
+ actorFbId: jsonMessage.from.toString(),
295
+ timestamp: Date.now().toString(),
296
+ });
297
+ } else if (jsonMessage.type === "jewel_requests_remove_old") {
298
+ globalCallback(null, {
299
+ type: "friend_request_cancel",
300
+ actorFbId: jsonMessage.from.toString(),
301
+ timestamp: Date.now().toString(),
302
+ });
303
+ } else if (topic === "/t_ms") {
304
+ if (ctx.tmsWait && typeof ctx.tmsWait == "function") {
305
+ ctx.tmsWait();
306
+ }
307
+ if (jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
308
+ ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
309
+ ctx.syncToken = jsonMessage.syncToken;
310
+ }
311
+ if (jsonMessage.lastIssuedSeqId) {
312
+ ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
313
+ }
314
+ for (const i in jsonMessage.deltas) {
315
+ const delta = jsonMessage.deltas[i];
316
+ parseDelta(defaultFuncs, api, ctx, globalCallback, {
317
+ delta: delta,
318
+ });
319
+ }
320
+ } else if (
321
+ topic === "/thread_typing" ||
322
+ topic === "/orca_typing_notifications"
323
+ ) {
324
+ const typ = {
325
+ type: "typ",
326
+ isTyping: !!jsonMessage.state,
327
+ from: jsonMessage.sender_fbid.toString(),
328
+ threadID: utils.formatID(
329
+ (jsonMessage.thread || jsonMessage.sender_fbid).toString()
330
+ ),
331
+ };
332
+ (function () {
333
+ globalCallback(null, typ);
334
+ })();
335
+ } else if (topic === "/orca_presence") {
336
+ if (!ctx.globalOptions.updatePresence) {
337
+ for (const i in jsonMessage.list) {
338
+ const data = jsonMessage.list[i];
339
+ const userID = data["u"];
340
+ const presence = {
341
+ type: "presence",
342
+ userID: userID.toString(),
343
+ timestamp: data["l"] * 1000,
344
+ statuses: data["p"],
345
+ };
346
+ (function () {
347
+ globalCallback(null, presence);
348
+ })();
349
+ }
350
+ }
351
+ } else if (topic == "/ls_resp") {
352
+ const parsedPayload = JSON.parse(jsonMessage.payload);
353
+ const reqID = jsonMessage.request_id;
354
+ if (ctx["tasks"].has(reqID)) {
355
+ const taskData = ctx["tasks"].get(reqID);
356
+ const { type: taskType, callback: taskCallback } = taskData;
357
+ const taskRespData = getTaskResponseData(taskType, parsedPayload);
358
+ if (taskRespData == null) {
359
+ taskCallback("error", null);
360
+ } else {
361
+ taskCallback(null, {
362
+ type: taskType,
363
+ reqID: reqID,
364
+ ...taskRespData,
365
+ });
366
+ }
367
+ }
368
+ }
369
+ } catch (ex) {
370
+ console.error("Message parsing error:", ex);
371
+ if (ex.stack) console.error(ex.stack);
372
+ return;
373
+ }
374
+ });
375
+ mqttClient.on("close", function () {});
376
+ mqttClient.on("disconnect", () => {});
377
+ }
378
+ function getTaskResponseData(taskType, payload) {
379
+ try {
380
+ switch (taskType) {
381
+ case "send_message_mqtt": {
382
+ return {
383
+ type: taskType,
384
+ threadID: payload.step[1][2][2][1][2],
385
+ messageID: payload.step[1][2][2][1][3],
386
+ payload: payload.step[1][2],
387
+ };
388
+ }
389
+ case "set_message_reaction": {
390
+ return {
391
+ mid: payload.step[1][2][2][1][4],
392
+ };
393
+ }
394
+ case "edit_message": {
395
+ return {
396
+ mid: payload.step[1][2][2][1][2],
397
+ };
398
+ }
399
+ }
400
+ } catch (error) {
401
+ return null;
402
+ }
403
+ }
404
+ function parseDelta(defaultFuncs, api, ctx, globalCallback, { delta }) {
405
+ if (delta.class === "NewMessage") {
406
+ if (ctx.globalOptions.pageID && ctx.globalOptions.pageID !== delta.queue)
407
+ return;
408
+ const resolveAttachmentUrl = (i) => {
409
+ if (
410
+ !delta.attachments ||
411
+ i === delta.attachments.length ||
412
+ utils.getType(delta.attachments) !== "Array"
413
+ ) {
414
+ let fmtMsg;
415
+ try {
416
+ fmtMsg = utils.formatDeltaMessage(delta);
417
+ } catch (err) {
418
+ return log.error("Lỗi Nhẹ", err);
419
+ }
420
+ if (fmtMsg) {
421
+ if (ctx.globalOptions.autoMarkDelivery) {
422
+ markDelivery(ctx, api, fmtMsg.threadID, fmtMsg.messageID);
423
+ }
424
+ if (!ctx.globalOptions.selfListen && fmtMsg.senderID === ctx.userID)
425
+ return;
426
+ globalCallback(null, fmtMsg);
427
+ }
428
+ } else {
429
+ const attachment = delta.attachments[i];
430
+ if (attachment.mercury.attach_type === "photo") {
431
+ api.resolvePhotoUrl(attachment.fbid, (err, url) => {
432
+ if (!err) attachment.mercury.metadata.url = url;
433
+ resolveAttachmentUrl(i + 1);
434
+ });
435
+ } else {
436
+ resolveAttachmentUrl(i + 1);
437
+ }
438
+ }
439
+ };
440
+ resolveAttachmentUrl(0);
441
+ } else if (delta.class === "ClientPayload") {
442
+ const clientPayload = utils.decodeClientPayload(delta.payload);
443
+ if (clientPayload && clientPayload.deltas) {
444
+ for (const delta of clientPayload.deltas) {
445
+ if (delta.deltaMessageReaction && !!ctx.globalOptions.listenEvents) {
446
+ const messageReaction = {
447
+ type: "message_reaction",
448
+ threadID: (delta.deltaMessageReaction.threadKey.threadFbId
449
+ ? delta.deltaMessageReaction.threadKey.threadFbId
450
+ : delta.deltaMessageReaction.threadKey.otherUserFbId
451
+ ).toString(),
452
+ messageID: delta.deltaMessageReaction.messageId,
453
+ reaction: delta.deltaMessageReaction.reaction,
454
+ senderID: delta.deltaMessageReaction.senderId.toString(),
455
+ userID: delta.deltaMessageReaction.userId.toString(),
456
+ };
457
+ globalCallback(null, messageReaction);
458
+ } else if (
459
+ delta.deltaRecallMessageData &&
460
+ !!ctx.globalOptions.listenEvents
461
+ ) {
462
+ const messageUnsend = {
463
+ type: "message_unsend",
464
+ threadID: (delta.deltaRecallMessageData.threadKey.threadFbId
465
+ ? delta.deltaRecallMessageData.threadKey.threadFbId
466
+ : delta.deltaRecallMessageData.threadKey.otherUserFbId
467
+ ).toString(),
468
+ messageID: delta.deltaRecallMessageData.messageID,
469
+ senderID: delta.deltaRecallMessageData.senderID.toString(),
470
+ deletionTimestamp: delta.deltaRecallMessageData.deletionTimestamp,
471
+ timestamp: delta.deltaRecallMessageData.timestamp,
472
+ };
473
+ globalCallback(null, messageUnsend);
474
+ } else if (delta.deltaMessageReply) {
475
+ const mdata =
476
+ delta.deltaMessageReply.message === undefined
477
+ ? []
478
+ : delta.deltaMessageReply.message.data === undefined
479
+ ? []
480
+ : delta.deltaMessageReply.message.data.prng === undefined
481
+ ? []
482
+ : JSON.parse(delta.deltaMessageReply.message.data.prng);
483
+
484
+ const m_id = mdata.map((u) => u.i);
485
+ const m_offset = mdata.map((u) => u.o);
486
+ const m_length = mdata.map((u) => u.l);
487
+ const mentions = {};
488
+ for (let i = 0; i < m_id.length; i++) {
489
+ mentions[m_id[i]] = (
490
+ delta.deltaMessageReply.message.body || ""
491
+ ).substring(m_offset[i], m_offset[i] + m_length[i]);
492
+ }
493
+ const callbackToReturn = {
494
+ type: "message_reply",
495
+ threadID: (delta.deltaMessageReply.message.messageMetadata.threadKey
496
+ .threadFbId
497
+ ? delta.deltaMessageReply.message.messageMetadata.threadKey
498
+ .threadFbId
499
+ : delta.deltaMessageReply.message.messageMetadata.threadKey
500
+ .otherUserFbId
501
+ ).toString(),
502
+ messageID:
503
+ delta.deltaMessageReply.message.messageMetadata.messageId,
504
+ senderID:
505
+ delta.deltaMessageReply.message.messageMetadata.actorFbId.toString(),
506
+ attachments: (delta.deltaMessageReply.message.attachments || [])
507
+ .map((att) => {
508
+ const mercury = JSON.parse(att.mercuryJSON);
509
+ Object.assign(att, mercury);
510
+ return att;
511
+ })
512
+ .map((att) => {
513
+ let x;
514
+ try {
515
+ x = utils._formatAttachment(att);
516
+ } catch (ex) {
517
+ x = att;
518
+ x.error = ex;
519
+ x.type = "unknown";
520
+ }
521
+ return x;
522
+ }),
523
+ args: (delta.deltaMessageReply.message.body || "")
524
+ .trim()
525
+ .split(/\s+/),
526
+ body: delta.deltaMessageReply.message.body || "",
527
+ isGroup:
528
+ !!delta.deltaMessageReply.message.messageMetadata.threadKey
529
+ .threadFbId,
530
+ mentions,
531
+ timestamp: parseInt(
532
+ delta.deltaMessageReply.message.messageMetadata.timestamp
533
+ ),
534
+ participantIDs: (
535
+ delta.deltaMessageReply.message.participants || []
536
+ ).map((e) => e.toString()),
537
+ };
538
+ if (delta.deltaMessageReply.repliedToMessage) {
539
+ const mdata =
540
+ delta.deltaMessageReply.repliedToMessage === undefined
541
+ ? []
542
+ : delta.deltaMessageReply.repliedToMessage.data === undefined
543
+ ? []
544
+ : delta.deltaMessageReply.repliedToMessage.data.prng ===
545
+ undefined
546
+ ? []
547
+ : JSON.parse(
548
+ delta.deltaMessageReply.repliedToMessage.data.prng
549
+ );
550
+ const m_id = mdata.map((u) => u.i);
551
+ const m_offset = mdata.map((u) => u.o);
552
+ const m_length = mdata.map((u) => u.l);
553
+ const rmentions = {};
554
+ for (let i = 0; i < m_id.length; i++) {
555
+ rmentions[m_id[i]] = (
556
+ delta.deltaMessageReply.repliedToMessage.body || ""
557
+ ).substring(m_offset[i], m_offset[i] + m_length[i]);
558
+ }
559
+ callbackToReturn.messageReply = {
560
+ threadID: (delta.deltaMessageReply.repliedToMessage
561
+ .messageMetadata.threadKey.threadFbId
562
+ ? delta.deltaMessageReply.repliedToMessage.messageMetadata
563
+ .threadKey.threadFbId
564
+ : delta.deltaMessageReply.repliedToMessage.messageMetadata
565
+ .threadKey.otherUserFbId
566
+ ).toString(),
567
+ messageID:
568
+ delta.deltaMessageReply.repliedToMessage.messageMetadata
569
+ .messageId,
570
+ senderID:
571
+ delta.deltaMessageReply.repliedToMessage.messageMetadata.actorFbId.toString(),
572
+ attachments: delta.deltaMessageReply.repliedToMessage.attachments
573
+ .map((att) => {
574
+ let mercury;
575
+ try {
576
+ mercury = JSON.parse(att.mercuryJSON);
577
+ Object.assign(att, mercury);
578
+ } catch (ex) {
579
+ mercury = {};
580
+ }
581
+ return att;
582
+ })
583
+ .map((att) => {
584
+ let x;
585
+ try {
586
+ x = utils._formatAttachment(att);
587
+ } catch (ex) {
588
+ x = att;
589
+ x.error = ex;
590
+ x.type = "unknown";
591
+ }
592
+ return x;
593
+ }),
594
+ args: (delta.deltaMessageReply.repliedToMessage.body || "")
595
+ .trim()
596
+ .split(/\s+/),
597
+ body: delta.deltaMessageReply.repliedToMessage.body || "",
598
+ isGroup:
599
+ !!delta.deltaMessageReply.repliedToMessage.messageMetadata
600
+ .threadKey.threadFbId,
601
+ mentions: rmentions,
602
+ timestamp: parseInt(
603
+ delta.deltaMessageReply.repliedToMessage.messageMetadata
604
+ .timestamp
605
+ ),
606
+ participantIDs: (
607
+ delta.deltaMessageReply.repliedToMessage.participants || []
608
+ ).map((e) => e.toString()),
609
+ };
610
+ } else if (delta.deltaMessageReply.replyToMessageId) {
611
+ return defaultFuncs
612
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, {
613
+ av: ctx.globalOptions.pageID,
614
+ queries: JSON.stringify({
615
+ o0: {
616
+ doc_id: "2848441488556444",
617
+ query_params: {
618
+ thread_and_message_id: {
619
+ thread_id: callbackToReturn.threadID,
620
+ message_id: delta.deltaMessageReply.replyToMessageId.id,
621
+ },
622
+ },
623
+ },
624
+ }),
625
+ })
626
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
627
+ .then((resData) => {
628
+ if (resData[resData.length - 1].error_results > 0)
629
+ throw resData[0].o0.errors;
630
+ if (resData[resData.length - 1].successful_results === 0)
631
+ throw {
632
+ error: "forcedFetch: there was no successful_results",
633
+ res: resData,
634
+ };
635
+ const fetchData = resData[0].o0.data.message;
636
+ const mobj = {};
637
+ for (const n in fetchData.message.ranges) {
638
+ mobj[fetchData.message.ranges[n].entity.id] = (
639
+ fetchData.message.text || ""
640
+ ).substr(
641
+ fetchData.message.ranges[n].offset,
642
+ fetchData.message.ranges[n].length
643
+ );
644
+ }
645
+ callbackToReturn.messageReply = {
646
+ type: "Message",
647
+ threadID: callbackToReturn.threadID,
648
+ messageID: fetchData.message_id,
649
+ senderID: fetchData.message_sender.id.toString(),
650
+ attachments: fetchData.message.blob_attachment.map((att) =>
651
+ utils._formatAttachment({
652
+ blob_attachment: att,
653
+ })
654
+ ),
655
+ args:
656
+ (fetchData.message.text || "").trim().split(/\s+/) || [],
657
+ body: fetchData.message.text || "",
658
+ isGroup: callbackToReturn.isGroup,
659
+ mentions: mobj,
660
+ timestamp: parseInt(fetchData.timestamp_precise),
661
+ };
662
+ })
663
+ .catch((err) => log.error("forcedFetch", err))
664
+ .finally(() => {
665
+ if (ctx.globalOptions.autoMarkDelivery) {
666
+ markDelivery(
667
+ ctx,
668
+ api,
669
+ callbackToReturn.threadID,
670
+ callbackToReturn.messageID
671
+ );
672
+ }
673
+ if (
674
+ !ctx.globalOptions.selfListen &&
675
+ callbackToReturn.senderID === ctx.userID
676
+ )
677
+ return;
678
+ globalCallback(null, callbackToReturn);
679
+ });
680
+ } else {
681
+ callbackToReturn.delta = delta;
682
+ }
683
+ if (ctx.globalOptions.autoMarkDelivery) {
684
+ markDelivery(
685
+ ctx,
686
+ api,
687
+ callbackToReturn.threadID,
688
+ callbackToReturn.messageID
689
+ );
690
+ }
691
+ if (
692
+ !ctx.globalOptions.selfListen &&
693
+ callbackToReturn.senderID === ctx.userID
694
+ )
695
+ return;
696
+ globalCallback(null, callbackToReturn);
697
+ }
698
+ }
699
+ return;
700
+ }
701
+ }
702
+ switch (delta.class) {
703
+ case "ReadReceipt": {
704
+ let fmtMsg;
705
+ try {
706
+ fmtMsg = utils.formatDeltaReadReceipt(delta);
707
+ } catch (err) {
708
+ return log.error("Lỗi Nhẹ", err);
709
+ }
710
+ globalCallback(null, fmtMsg);
711
+ break;
712
+ }
713
+ case "AdminTextMessage": {
714
+ switch (delta.type) {
715
+ case "instant_game_dynamic_custom_update":
716
+ case "accept_pending_thread":
717
+ case "confirm_friend_request":
718
+ case "shared_album_delete":
719
+ case "shared_album_addition":
720
+ case "pin_messages_v2":
721
+ case "unpin_messages_v2":
722
+ case "change_thread_theme":
723
+ case "change_thread_nickname":
724
+ case "change_thread_icon":
725
+ case "change_thread_quick_reaction":
726
+ case "change_thread_admins":
727
+ case "group_poll":
728
+ case "joinable_group_link_mode_change":
729
+ case "magic_words":
730
+ case "change_thread_approval_mode":
731
+ case "messenger_call_log":
732
+ case "participant_joined_group_call":
733
+ case "rtc_call_log":
734
+ case "update_vote": {
735
+ let fmtMsg;
736
+ try {
737
+ fmtMsg = utils.formatDeltaEvent(delta);
738
+ } catch (err) {
739
+ console.log(delta);
740
+ return log.error("Lỗi Nhẹ", err);
741
+ }
742
+ globalCallback(null, fmtMsg);
743
+ break;
744
+ }
745
+ }
746
+ break;
747
+ }
748
+ case "ForcedFetch": {
749
+ if (!delta.threadKey) return;
750
+ const mid = delta.messageId;
751
+ const tid = delta.threadKey.threadFbId;
752
+ if (mid && tid) {
753
+ const form = {
754
+ av: ctx.globalOptions.pageID,
755
+ queries: JSON.stringify({
756
+ o0: {
757
+ doc_id: "2848441488556444",
758
+ query_params: {
759
+ thread_and_message_id: {
760
+ thread_id: tid.toString(),
761
+ message_id: mid,
762
+ },
763
+ },
764
+ },
765
+ }),
766
+ };
767
+ defaultFuncs
768
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
769
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
770
+ .then((resData) => {
771
+ if (resData[resData.length - 1].error_results > 0)
772
+ throw resData[0].o0.errors;
773
+ if (resData[resData.length - 1].successful_results === 0)
774
+ throw {
775
+ error: "forcedFetch: there was no successful_results",
776
+ res: resData,
777
+ };
778
+ const fetchData = resData[0].o0.data.message;
779
+ if (utils.getType(fetchData) === "Object") {
780
+ log.info("forcedFetch", fetchData);
781
+ switch (fetchData.__typename) {
782
+ case "ThreadImageMessage":
783
+ (!ctx.globalOptions.selfListen &&
784
+ fetchData.message_sender.id.toString() === ctx.userID) ||
785
+ !ctx.loggedIn
786
+ ? undefined
787
+ : (function () {
788
+ globalCallback(null, {
789
+ type: "event",
790
+ threadID: utils.formatID(tid.toString()),
791
+ logMessageType: "log:thread-image",
792
+ logMessageData: {
793
+ image: {
794
+ attachmentID:
795
+ fetchData.image_with_metadata &&
796
+ fetchData.image_with_metadata
797
+ .legacy_attachment_id,
798
+ width:
799
+ fetchData.image_with_metadata &&
800
+ fetchData.image_with_metadata
801
+ .original_dimensions.x,
802
+ height:
803
+ fetchData.image_with_metadata &&
804
+ fetchData.image_with_metadata
805
+ .original_dimensions.y,
806
+ url:
807
+ fetchData.image_with_metadata &&
808
+ fetchData.image_with_metadata.preview.uri,
809
+ },
810
+ },
811
+ logMessageBody: fetchData.snippet,
812
+ timestamp: fetchData.timestamp_precise,
813
+ author: fetchData.message_sender.id,
814
+ });
815
+ })();
816
+ break;
817
+ case "UserMessage": {
818
+ const event = {
819
+ type: "message",
820
+ senderID: utils.formatID(fetchData.message_sender.id),
821
+ body: fetchData.message.text || "",
822
+ threadID: utils.formatID(tid.toString()),
823
+ messageID: fetchData.message_id,
824
+ attachments: [
825
+ {
826
+ type: "share",
827
+ ID: fetchData.extensible_attachment
828
+ .legacy_attachment_id,
829
+ url: fetchData.extensible_attachment.story_attachment
830
+ .url,
831
+ title:
832
+ fetchData.extensible_attachment.story_attachment
833
+ .title_with_entities.text,
834
+ description:
835
+ fetchData.extensible_attachment.story_attachment
836
+ .description.text,
837
+ source:
838
+ fetchData.extensible_attachment.story_attachment
839
+ .source,
840
+ image: (
841
+ (
842
+ fetchData.extensible_attachment.story_attachment
843
+ .media || {}
844
+ ).image || {}
845
+ ).uri,
846
+ width: (
847
+ (
848
+ fetchData.extensible_attachment.story_attachment
849
+ .media || {}
850
+ ).image || {}
851
+ ).width,
852
+ height: (
853
+ (
854
+ fetchData.extensible_attachment.story_attachment
855
+ .media || {}
856
+ ).image || {}
857
+ ).height,
858
+ playable:
859
+ (
860
+ fetchData.extensible_attachment.story_attachment
861
+ .media || {}
862
+ ).is_playable || false,
863
+ duration:
864
+ (
865
+ fetchData.extensible_attachment.story_attachment
866
+ .media || {}
867
+ ).playable_duration_in_ms || 0,
868
+ subattachments:
869
+ fetchData.extensible_attachment.subattachments,
870
+ properties:
871
+ fetchData.extensible_attachment.story_attachment
872
+ .properties,
873
+ },
874
+ ],
875
+ mentions: {},
876
+ timestamp: parseInt(fetchData.timestamp_precise),
877
+ isGroup: fetchData.message_sender.id !== tid.toString(),
878
+ };
879
+ log.info("ff-Return", event);
880
+ globalCallback(null, event);
881
+ break;
882
+ }
883
+ default:
884
+ log.error("forcedFetch", fetchData);
885
+ }
886
+ } else {
887
+ log.error("forcedFetch", fetchData);
888
+ }
889
+ })
890
+ .catch((err) => log.error("forcedFetch", err));
891
+ }
892
+ break;
893
+ }
894
+ case "ThreadName":
895
+ case "ParticipantsAddedToGroupThread":
896
+ case "ParticipantLeftGroupThread": {
897
+ let formattedEvent;
898
+ try {
899
+ formattedEvent = utils.formatDeltaEvent(delta);
900
+ } catch (err) {
901
+ console.log(err);
902
+ return log.error("Lỗi Nhẹ", err);
903
+ }
904
+ if (
905
+ !ctx.globalOptions.selfListen &&
906
+ formattedEvent.author.toString() === ctx.userID
907
+ )
908
+ return;
909
+ if (!ctx.loggedIn) return;
910
+ globalCallback(null, formattedEvent);
911
+ break;
912
+ }
913
+ case "NewMessage": {
914
+ const hasLiveLocation = (delta) => {
915
+ const attachment =
916
+ delta.attachments?.[0]?.mercury?.extensible_attachment;
917
+ const storyAttachment = attachment?.story_attachment;
918
+ return storyAttachment?.style_list?.includes("message_live_location");
919
+ };
920
+ if (delta.attachments?.length === 1 && hasLiveLocation(delta)) {
921
+ delta.class = "UserLocation";
922
+ try {
923
+ const fmtMsg = utils.formatDeltaEvent(delta);
924
+ globalCallback(null, fmtMsg);
925
+ } catch (err) {
926
+ console.log(delta);
927
+ log.error("Lỗi Nhẹ", err);
928
+ }
929
+ }
930
+ break;
931
+ }
932
+ }
933
+ }
934
+ function markDelivery(ctx, api, threadID, messageID) {
935
+ if (threadID && messageID) {
936
+ api.markAsDelivered(threadID, messageID, (err) => {
937
+ if (err) log.error("markAsDelivered", err);
938
+ else {
939
+ if (ctx.globalOptions.autoMarkRead) {
940
+ api.markAsRead(threadID, (err) => {
941
+ if (err) log.error("markAsDelivered", err);
942
+ });
943
+ }
944
+ }
945
+ });
946
+ }
947
+ }
948
+ module.exports = function (defaultFuncs, api, ctx) {
949
+ let globalCallback = identity;
950
+ getSeqID = function getSeqID() {
951
+ ctx.t_mqttCalled = false;
952
+ defaultFuncs
953
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
954
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
955
+ .then((resData) => {
956
+ if (utils.getType(resData) !== "Array") throw { error: "Not logged in", res: resData };
957
+ if (resData && resData[resData.length - 1].error_results > 0)
958
+ throw resData[0].o0.errors;
959
+ if (resData[resData.length - 1].successful_results === 0)
960
+ throw {
961
+ error: "getSeqId: there was no successful_results",
962
+ res: resData,
963
+ };
964
+ if (resData[0].o0.data.viewer.message_threads.sync_sequence_id) {
965
+ ctx.lastSeqId =
966
+ resData[0].o0.data.viewer.message_threads.sync_sequence_id;
967
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
968
+ } else
969
+ throw {
970
+ error: "getSeqId: no sync_sequence_id found.",
971
+ res: resData,
972
+ };
973
+ })
974
+ .catch((err) => {
975
+ log.error("getSeqId", err);
976
+ if (utils.getType(err) === "Object" && err.error === "Not logged in")
977
+ ctx.loggedIn = false;
978
+ return globalCallback(err);
979
+ });
980
+ };
981
+ return function (callback) {
982
+ class MessageEmitter extends EventEmitter {
983
+ stopListening(callback) {
984
+ callback = callback || (() => {});
985
+ globalCallback = identity;
986
+ if (ctx.mqttClient) {
987
+ ctx.mqttClient.unsubscribe("/webrtc");
988
+ ctx.mqttClient.unsubscribe("/rtc_multi");
989
+ ctx.mqttClient.unsubscribe("/onevc");
990
+ ctx.mqttClient.publish("/browser_close", "{}");
991
+ ctx.mqttClient.end(false, function (...data) {
992
+ callback(data);
993
+ ctx.mqttClient = undefined;
994
+ });
995
+ }
996
+ }
997
+ async stopListeningAsync() {
998
+ return new Promise((resolve) => {
999
+ this.stopListening(resolve);
1000
+ });
1001
+ }
1002
+ }
1003
+ const msgEmitter = new MessageEmitter();
1004
+ globalCallback =
1005
+ callback ||
1006
+ function (error, message) {
1007
+ if (error) {
1008
+ return msgEmitter.emit("error", error);
1009
+ }
1010
+ msgEmitter.emit("message", message);
1011
+ };
1012
+ if (!ctx.firstListen) ctx.lastSeqId = null;
1013
+ ctx.syncToken = undefined;
1014
+ ctx.t_mqttCalled = false;
1015
+ form = {
1016
+ av: ctx.globalOptions.pageID,
1017
+ queries: JSON.stringify({
1018
+ o0: {
1019
+ doc_id: "3336396659757871",
1020
+ query_params: {
1021
+ limit: 1,
1022
+ before: null,
1023
+ tags: ["INBOX"],
1024
+ includeDeliveryReceipts: false,
1025
+ includeSeqID: true,
1026
+ },
1027
+ },
1028
+ }),
1029
+ };
1030
+ if (!ctx.firstListen || !ctx.lastSeqId) {
1031
+ getSeqID(defaultFuncs, api, ctx, globalCallback);
1032
+ } else {
1033
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
1034
+ }
1035
+ api.stopListening = msgEmitter.stopListening;
1036
+ api.stopListeningAsync = msgEmitter.stopListeningAsync;
1037
+ return msgEmitter;
1038
+ };
1039
+ };