kzi 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 (60) hide show
  1. package/index.js +458 -0
  2. package/package.json +11 -0
  3. package/src/Screenshot.js +83 -0
  4. package/src/addExternalModule.js +15 -0
  5. package/src/addUserToGroup.js +77 -0
  6. package/src/changeAdminStatus.js +47 -0
  7. package/src/changeArchivedStatus.js +41 -0
  8. package/src/changeAvt.js +85 -0
  9. package/src/changeBio.js +65 -0
  10. package/src/changeBlockedStatus.js +36 -0
  11. package/src/changeGroupImage.js +106 -0
  12. package/src/changeNickname.js +45 -0
  13. package/src/changeThreadColor.js +61 -0
  14. package/src/changeThreadEmoji.js +41 -0
  15. package/src/createNewGroup.js +70 -0
  16. package/src/createPoll.js +59 -0
  17. package/src/createPost.js +277 -0
  18. package/src/deleteMessage.js +44 -0
  19. package/src/deleteThread.js +42 -0
  20. package/src/forwardAttachment.js +47 -0
  21. package/src/getCurrentUserID.js +7 -0
  22. package/src/getEmojiUrl.js +27 -0
  23. package/src/getFriendsList.js +73 -0
  24. package/src/getThreadHistory.js +537 -0
  25. package/src/getThreadHistoryDeprecated.js +71 -0
  26. package/src/getThreadInfo.js +232 -0
  27. package/src/getThreadInfoDeprecated.js +56 -0
  28. package/src/getThreadList.js +213 -0
  29. package/src/getThreadListDeprecated.js +46 -0
  30. package/src/getThreadPictures.js +59 -0
  31. package/src/getUID.js +119 -0
  32. package/src/getUserID.js +61 -0
  33. package/src/getUserInfo.js +66 -0
  34. package/src/handleFriendRequest.js +46 -0
  35. package/src/handleMessageRequest.js +47 -0
  36. package/src/httpGet.js +49 -0
  37. package/src/httpPost.js +48 -0
  38. package/src/listenMqtt.js +776 -0
  39. package/src/listenMqtt.txt +827 -0
  40. package/src/logout.js +68 -0
  41. package/src/markAsDelivered.js +47 -0
  42. package/src/markAsRead.js +70 -0
  43. package/src/markAsReadAll.js +40 -0
  44. package/src/markAsSeen.js +48 -0
  45. package/src/muteThread.js +45 -0
  46. package/src/postFormData.txt +46 -0
  47. package/src/removeUserFromGroup.js +45 -0
  48. package/src/resolvePhotoUrl.js +36 -0
  49. package/src/searchForThread.js +42 -0
  50. package/src/sendMessage.js +328 -0
  51. package/src/sendTypingIndicator.js +70 -0
  52. package/src/setMessageReaction.js +109 -0
  53. package/src/setPostReaction.js +102 -0
  54. package/src/setTitle.js +70 -0
  55. package/src/shareContact.js +46 -0
  56. package/src/shareLink.js +62 -0
  57. package/src/threadColors.js +41 -0
  58. package/src/unfriend.js +42 -0
  59. package/src/unsendMessage.js +39 -0
  60. package/utils.js +2892 -0
@@ -0,0 +1,827 @@
1
+ /* eslint-disable no-redeclare */
2
+ "use strict";
3
+ var utils = require("../utils");
4
+ var log = require("npmlog");
5
+ var mqtt = require('mqtt');
6
+ var websocket = require('websocket-stream');
7
+ var HttpsProxyAgent = require('https-proxy-agent');
8
+ const EventEmitter = require('events');
9
+ const debugSeq = false;
10
+ var identity = function () { };
11
+ var form = {};
12
+ var getSeqID = function () { };
13
+ var topics = [
14
+ "/legacy_web",
15
+ "/webrtc",
16
+ "/rtc_multi",
17
+ "/onevc",
18
+ "/br_sr", //Notification
19
+ //Need to publish /br_sr right after this
20
+ "/sr_res",
21
+ "/t_ms",
22
+ "/thread_typing",
23
+ "/orca_typing_notifications",
24
+ "/notify_disconnect",
25
+ //Need to publish /messenger_sync_create_queue right after this
26
+ "/orca_presence",
27
+ //Will receive /sr_res right here.
28
+
29
+ "/inbox",
30
+ "/mercury",
31
+ "/messaging_events",
32
+ "/orca_message_notifications",
33
+ "/pp",
34
+ "/webrtc_response",
35
+ ];
36
+
37
+ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
38
+ //Don't really know what this does but I think it's for the active state?
39
+ //TODO: Move to ctx when implemented
40
+ var chatOn = ctx.globalOptions.online;
41
+ var foreground = false;
42
+
43
+ var sessionID = Math.floor(Math.random() * 9007199254740991) + 1;
44
+ var GUID = utils.getGUID();
45
+ const username = {
46
+ u: ctx.userID,
47
+ s: sessionID,
48
+ chat_on: chatOn,
49
+ fg: foreground,
50
+ d: GUID,
51
+ ct: 'websocket',
52
+ aid: '219994525426954',
53
+ aids: null,
54
+ mqtt_sid: '',
55
+ cp: 3,
56
+ ecp: 10,
57
+ st: [],
58
+ pm: [],
59
+ dc: '',
60
+ no_auto_fg: true,
61
+ gas: null,
62
+ pack: [],
63
+ p: null,
64
+ php_override: ""
65
+ };
66
+ var cookies = ctx.jar.getCookies("https://www.facebook.com").join("; ");
67
+
68
+ var host;
69
+ if (ctx.mqttEndpoint) host = `${ctx.mqttEndpoint}&sid=${sessionID}&cid=${GUID}`;
70
+ else if (ctx.region) host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLocaleLowerCase()}&sid=${sessionID}&cid=${GUID}`;
71
+ else host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}&cid=${GUID}`;
72
+
73
+ const options = {
74
+ clientId: 'mqttwsclient',
75
+ protocolId: 'MQIsdp',
76
+ protocolVersion: 3,
77
+ username: JSON.stringify(username),
78
+ clean: true,
79
+ wsOptions: {
80
+ headers: {
81
+ Cookie: cookies,
82
+ Origin: 'https://www.facebook.com',
83
+ 'User-Agent': ctx.globalOptions.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36',
84
+ Referer: 'https://www.facebook.com/',
85
+ Host: new URL(host).hostname,
86
+ },
87
+ origin: 'https://www.facebook.com',
88
+ protocolVersion: 13,
89
+ binaryType: 'arraybuffer',
90
+ },
91
+ keepalive: 60,
92
+ reschedulePings: true,
93
+ reconnectPeriod: 3,
94
+ };
95
+
96
+ if (typeof ctx.globalOptions.proxy != "undefined") {
97
+ var agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
98
+ options.wsOptions.agent = agent;
99
+ }
100
+
101
+ ctx.mqttClient = new mqtt.Client(_ => websocket(host, options.wsOptions), options);
102
+
103
+ global.mqttClient = ctx.mqttClient;
104
+
105
+ mqttClient.on('error', function (err) {
106
+ log.error("listenMqtt", err);
107
+ mqttClient.end();
108
+ if (ctx.globalOptions.autoReconnect) getSeqID();
109
+ else globalCallback({ type: "stop_listen", error: "Connection refused: Server unavailable" }, null);
110
+ });
111
+
112
+ mqttClient.on('connect', function () {
113
+ topics.forEach(topicsub => mqttClient.subscribe(topicsub));
114
+
115
+ var topic;
116
+ var queue = {
117
+ sync_api_version: 10,
118
+ max_deltas_able_to_process: 1000,
119
+ delta_batch_size: 500,
120
+ encoding: "JSON",
121
+ entity_fbid: ctx.userID,
122
+ };
123
+
124
+ if (ctx.syncToken) {
125
+ topic = "/messenger_sync_get_diffs";
126
+ queue.last_seq_id = ctx.lastSeqId;
127
+ queue.sync_token = ctx.syncToken;
128
+ }
129
+ else {
130
+ topic = "/messenger_sync_create_queue";
131
+ queue.initial_titan_sequence_id = ctx.lastSeqId;
132
+ queue.device_params = null;
133
+ }
134
+
135
+ mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
136
+
137
+ var rTimeout = setTimeout(function () {
138
+ mqttClient.end();
139
+ getSeqID();
140
+ }, 5000);
141
+
142
+ ctx.tmsWait = function () {
143
+ clearTimeout(rTimeout);
144
+ ctx.globalOptions.emitReady ? globalCallback({
145
+ type: "ready",
146
+ error: null
147
+ }) : "";
148
+ delete ctx.tmsWait;
149
+ };
150
+ });
151
+
152
+ mqttClient.on('message', function (topic, message, _packet) {
153
+ try {
154
+ var jsonMessage = JSON.parse(message);
155
+ }
156
+ catch (ex) {
157
+ return log.error("listenMqtt", ex);
158
+ }
159
+ if (topic === "/t_ms") {
160
+ if (ctx.tmsWait && typeof ctx.tmsWait == "function") ctx.tmsWait();
161
+
162
+ if (jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
163
+ ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
164
+ ctx.syncToken = jsonMessage.syncToken;
165
+ }
166
+
167
+ if (jsonMessage.lastIssuedSeqId) ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
168
+
169
+ //If it contains more than 1 delta
170
+ for (var i in jsonMessage.deltas) {
171
+ var delta = jsonMessage.deltas[i];
172
+ parseDelta(defaultFuncs, api, ctx, globalCallback, { "delta": delta });
173
+ }
174
+ }
175
+ else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
176
+ var typ = {
177
+ type: "typ",
178
+ isTyping: !!jsonMessage.state,
179
+ from: jsonMessage.sender_fbid.toString(),
180
+ threadID: utils.formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
181
+ };
182
+ (function () { globalCallback(null, typ); })();
183
+ }
184
+ else if (topic === "/orca_presence") {
185
+ if (!ctx.globalOptions.updatePresence) {
186
+ for (var i in jsonMessage.list) {
187
+ var data = jsonMessage.list[i];
188
+ var userID = data["u"];
189
+
190
+ var presence = {
191
+ type: "presence",
192
+ userID: userID.toString(),
193
+ //Convert to ms
194
+ timestamp: data["l"] * 1000,
195
+ statuses: data["p"]
196
+ };
197
+ (function () { globalCallback(null, presence); })();
198
+ }
199
+ }
200
+ }
201
+
202
+ });
203
+
204
+ mqttClient.on('close', function () {});
205
+ }
206
+
207
+ function parseDelta(defaultFuncs, api, ctx, globalCallback, v) {
208
+ if (v.delta.class == "NewMessage") {
209
+ //Not tested for pages
210
+ if (ctx.globalOptions.pageID && ctx.globalOptions.pageID != v.queue) return;
211
+
212
+ (function resolveAttachmentUrl(i) {
213
+ if (i == (v.delta.attachments || []).length) {
214
+ let fmtMsg;
215
+ try {
216
+ fmtMsg = utils.formatDeltaMessage(v);
217
+ } catch (err) {
218
+ return globalCallback({
219
+ error: "Problem parsing message object.",
220
+ detail: err,
221
+ res: v,
222
+ type: "parse_error"
223
+ });
224
+ }
225
+ if (fmtMsg) {
226
+ if (ctx.globalOptions.autoMarkDelivery) {
227
+ markDelivery(ctx, api, fmtMsg.threadID, fmtMsg.messageID);
228
+ }
229
+ }
230
+ return !ctx.globalOptions.selfListen &&
231
+ (fmtMsg.senderID === ctx.i_userID || fmtMsg.senderID === ctx.userID) ?
232
+ undefined :
233
+ (function () { globalCallback(null, fmtMsg); })();
234
+ } else {
235
+ if (v.delta.attachments[i].mercury.attach_type == "photo") {
236
+ api.resolvePhotoUrl(
237
+ v.delta.attachments[i].fbid,
238
+ (err, url) => {
239
+ if (!err)
240
+ v.delta.attachments[
241
+ i
242
+ ].mercury.metadata.url = url;
243
+ return resolveAttachmentUrl(i + 1);
244
+ }
245
+ );
246
+ } else {
247
+ return resolveAttachmentUrl(i + 1);
248
+ }
249
+ }
250
+ })(0);
251
+ }
252
+
253
+ if (v.delta.class == "ClientPayload") {
254
+ var clientPayload = utils.decodeClientPayload(v.delta.payload);
255
+ if (clientPayload && clientPayload.deltas) {
256
+ for (var i in clientPayload.deltas) {
257
+ var delta = clientPayload.deltas[i];
258
+ if (delta.deltaMessageReaction && !!ctx.globalOptions.listenEvents) {
259
+ (function () {
260
+ globalCallback(null, {
261
+ type: "message_reaction",
262
+ threadID: (delta.deltaMessageReaction.threadKey.threadFbId ? delta.deltaMessageReaction.threadKey.threadFbId : delta.deltaMessageReaction.threadKey.otherUserFbId).toString(),
263
+ messageID: delta.deltaMessageReaction.messageId,
264
+ reaction: delta.deltaMessageReaction.reaction,
265
+ senderID: delta.deltaMessageReaction.senderId.toString(),
266
+ userID: delta.deltaMessageReaction.userId.toString()
267
+ });
268
+ })();
269
+ }
270
+ else if (delta.deltaRecallMessageData && !!ctx.globalOptions.listenEvents) {
271
+ (function () {
272
+ globalCallback(null, {
273
+ type: "message_unsend",
274
+ threadID: (delta.deltaRecallMessageData.threadKey.threadFbId ? delta.deltaRecallMessageData.threadKey.threadFbId : delta.deltaRecallMessageData.threadKey.otherUserFbId).toString(),
275
+ messageID: delta.deltaRecallMessageData.messageID,
276
+ senderID: delta.deltaRecallMessageData.senderID.toString(),
277
+ deletionTimestamp: delta.deltaRecallMessageData.deletionTimestamp,
278
+ timestamp: delta.deltaRecallMessageData.timestamp
279
+ });
280
+ })();
281
+ }
282
+ else if (delta.deltaMessageReply) {
283
+ //Mention block - #1
284
+ var mdata = delta.deltaMessageReply.message === undefined ? [] :
285
+ delta.deltaMessageReply.message.data === undefined ? [] :
286
+ delta.deltaMessageReply.message.data.prng === undefined ? [] :
287
+ JSON.parse(delta.deltaMessageReply.message.data.prng);
288
+ var m_id = mdata.map(u => u.i);
289
+ var m_offset = mdata.map(u => u.o);
290
+ var m_length = mdata.map(u => u.l);
291
+
292
+ var mentions = {};
293
+
294
+ for (var i = 0; i < m_id.length; i++) mentions[m_id[i]] = (delta.deltaMessageReply.message.body || "").substring(m_offset[i], m_offset[i] + m_length[i]);
295
+ //Mention block - 1#
296
+ var callbackToReturn = {
297
+ type: "message_reply",
298
+ threadID: (delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId ? delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.message.messageMetadata.threadKey.otherUserFbId).toString(),
299
+ messageID: delta.deltaMessageReply.message.messageMetadata.messageId,
300
+ senderID: delta.deltaMessageReply.message.messageMetadata.actorFbId.toString(),
301
+ attachments: (delta.deltaMessageReply.message.attachments || []).map(function (att) {
302
+ var mercury = JSON.parse(att.mercuryJSON);
303
+ Object.assign(att, mercury);
304
+ return att;
305
+ }).map(att => {
306
+ var x;
307
+ try {
308
+ x = utils._formatAttachment(att);
309
+ }
310
+ catch (ex) {
311
+ x = att;
312
+ x.error = ex;
313
+ x.type = "unknown";
314
+ }
315
+ return x;
316
+ }),
317
+ args: (delta.deltaMessageReply.message.body || "").trim().split(/\s+/),
318
+ body: (delta.deltaMessageReply.message.body || ""),
319
+ isGroup: !!delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId,
320
+ mentions: mentions,
321
+ timestamp: delta.deltaMessageReply.message.messageMetadata.timestamp,
322
+ participantIDs: (delta.deltaMessageReply.message.messageMetadata.cid.canonicalParticipantFbids || delta.deltaMessageReply.message.participants || []).map(e => e.toString())
323
+ };
324
+
325
+ if (delta.deltaMessageReply.repliedToMessage) {
326
+ //Mention block - #2
327
+ mdata = delta.deltaMessageReply.repliedToMessage === undefined ? [] :
328
+ delta.deltaMessageReply.repliedToMessage.data === undefined ? [] :
329
+ delta.deltaMessageReply.repliedToMessage.data.prng === undefined ? [] :
330
+ JSON.parse(delta.deltaMessageReply.repliedToMessage.data.prng);
331
+ m_id = mdata.map(u => u.i);
332
+ m_offset = mdata.map(u => u.o);
333
+ m_length = mdata.map(u => u.l);
334
+
335
+ var rmentions = {};
336
+
337
+ for (var i = 0; i < m_id.length; i++) rmentions[m_id[i]] = (delta.deltaMessageReply.repliedToMessage.body || "").substring(m_offset[i], m_offset[i] + m_length[i]);
338
+ //Mention block - 2#
339
+ callbackToReturn.messageReply = {
340
+ threadID: (delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId ? delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.otherUserFbId).toString(),
341
+ messageID: delta.deltaMessageReply.repliedToMessage.messageMetadata.messageId,
342
+ senderID: delta.deltaMessageReply.repliedToMessage.messageMetadata.actorFbId.toString(),
343
+ attachments: delta.deltaMessageReply.repliedToMessage.attachments.map(function (att) {
344
+ var mercury = JSON.parse(att.mercuryJSON);
345
+ Object.assign(att, mercury);
346
+ return att;
347
+ }).map(att => {
348
+ var x;
349
+ try {
350
+ x = utils._formatAttachment(att);
351
+ }
352
+ catch (ex) {
353
+ x = att;
354
+ x.error = ex;
355
+ x.type = "unknown";
356
+ }
357
+ return x;
358
+ }),
359
+ args: (delta.deltaMessageReply.repliedToMessage.body || "").trim().split(/\s+/),
360
+ body: delta.deltaMessageReply.repliedToMessage.body || "",
361
+ isGroup: !!delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId,
362
+ mentions: rmentions,
363
+ timestamp: delta.deltaMessageReply.repliedToMessage.messageMetadata.timestamp
364
+ };
365
+ }
366
+ else if (delta.deltaMessageReply.replyToMessageId) {
367
+ return defaultFuncs
368
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, {
369
+ "av": ctx.globalOptions.pageID,
370
+ "queries": JSON.stringify({
371
+ "o0": {
372
+ //Using the same doc_id as forcedFetch
373
+ "doc_id": "2848441488556444",
374
+ "query_params": {
375
+ "thread_and_message_id": {
376
+ "thread_id": callbackToReturn.threadID,
377
+ "message_id": delta.deltaMessageReply.replyToMessageId.id,
378
+ }
379
+ }
380
+ }
381
+ })
382
+ })
383
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
384
+ .then((resData) => {
385
+ if (resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
386
+ if (resData[resData.length - 1].successful_results === 0) throw { error: "forcedFetch: there was no successful_results", res: resData };
387
+ var fetchData = resData[0].o0.data.message;
388
+ var mobj = {};
389
+ for (var n in fetchData.message.ranges) mobj[fetchData.message.ranges[n].entity.id] = (fetchData.message.text || "").substr(fetchData.message.ranges[n].offset, fetchData.message.ranges[n].length);
390
+
391
+ callbackToReturn.messageReply = {
392
+ threadID: callbackToReturn.threadID,
393
+ messageID: fetchData.message_id,
394
+ senderID: fetchData.message_sender.id.toString(),
395
+ attachments: fetchData.message.blob_attachment.map(att => {
396
+ var x;
397
+ try {
398
+ x = utils._formatAttachment({ blob_attachment: att });
399
+ }
400
+ catch (ex) {
401
+ x = att;
402
+ x.error = ex;
403
+ x.type = "unknown";
404
+ }
405
+ return x;
406
+ }),
407
+ args: (fetchData.message.text || "").trim().split(/\s+/) || [],
408
+ body: fetchData.message.text || "",
409
+ isGroup: callbackToReturn.isGroup,
410
+ mentions: mobj,
411
+ timestamp: parseInt(fetchData.timestamp_precise)
412
+ };
413
+ })
414
+ .catch(err => log.error("forcedFetch", err))
415
+ .finally(function () {
416
+ if (ctx.globalOptions.autoMarkDelivery) markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
417
+ !ctx.globalOptions.selfListen && callbackToReturn.senderID === ctx.userID ? undefined : (function () { globalCallback(null, callbackToReturn); })();
418
+ });
419
+ }
420
+ else callbackToReturn.delta = delta;
421
+
422
+ if (ctx.globalOptions.autoMarkDelivery) markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
423
+
424
+ return !ctx.globalOptions.selfListen && callbackToReturn.senderID === ctx.userID ? undefined : (function () { globalCallback(null, callbackToReturn); })();
425
+ }
426
+ }
427
+ return;
428
+ }
429
+ }
430
+
431
+ if (v.delta.class !== "NewMessage" && !ctx.globalOptions.listenEvents) return;
432
+ switch (v.delta.class) {
433
+ case "JoinableMode": {
434
+ let fmtMsg;
435
+ try {
436
+ fmtMsg = utils.formatDeltaEvent(v.delta);
437
+ } catch (err) {
438
+ return globalCallback({
439
+ error: "Lỗi gòi!!",
440
+ detail: err,
441
+ res: v.delta,
442
+ type: "parse_error"
443
+ });
444
+ }
445
+ return globalCallback(null, fmtMsg);
446
+ }
447
+ case "AdminTextMessage":
448
+ switch (v.delta.type) {
449
+ case 'confirm_friend_request':
450
+ case 'shared_album_delete':
451
+ case 'shared_album_addition':
452
+ case 'pin_messages_v2':
453
+ case 'unpin_messages_v2':
454
+ case "change_thread_theme":
455
+ case "change_thread_nickname":
456
+ case "change_thread_icon":
457
+ case "change_thread_quick_reaction":
458
+ case "change_thread_admins":
459
+ case "group_poll":
460
+ case "joinable_group_link_mode_change":
461
+ case "magic_words":
462
+ case "change_thread_approval_mode":
463
+ case "messenger_call_log":
464
+ case "participant_joined_group_call":
465
+ var fmtMsg;
466
+ try {
467
+ fmtMsg = utils.formatDeltaEvent(v.delta);
468
+ }
469
+ catch (err) {
470
+ return globalCallback({
471
+ error: "Problem parsing message object. Please open an issue at https://github.com/Schmavery/facebook-chat-api/issues.",
472
+ detail: err,
473
+ res: v.delta,
474
+ type: "parse_error"
475
+ });
476
+ }
477
+ return (function () { globalCallback(null, fmtMsg); })();
478
+ default:
479
+ // console.log(v.delta)
480
+ return;
481
+ }
482
+ //For group images
483
+ case "ForcedFetch":
484
+ if (!v.delta.threadKey) return;
485
+ var mid = v.delta.messageId;
486
+ var tid = v.delta.threadKey.threadFbId;
487
+ if (mid && tid) {
488
+ const form = {
489
+ "av": ctx.globalOptions.pageID,
490
+ "queries": JSON.stringify({
491
+ "o0": {
492
+ //This doc_id is valid as of March 25, 2020
493
+ "doc_id": "2848441488556444",
494
+ "query_params": {
495
+ "thread_and_message_id": {
496
+ "thread_id": tid.toString(),
497
+ "message_id": mid,
498
+ }
499
+ }
500
+ }
501
+ })
502
+ };
503
+
504
+ defaultFuncs
505
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
506
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
507
+ .then((resData) => {
508
+ if (resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
509
+
510
+ if (resData[resData.length - 1].successful_results === 0) throw { error: "forcedFetch: there was no successful_results", res: resData };
511
+
512
+ var fetchData = resData[0].o0.data.message;
513
+
514
+ if (utils.getType(fetchData) == "Object") {
515
+ log.info("forcedFetch", fetchData);
516
+ switch (fetchData.__typename) {
517
+ case "ThreadImageMessage":
518
+ (!ctx.globalOptions.selfListen && fetchData.message_sender.id.toString() === ctx.userID) ||
519
+ !ctx.loggedIn ? undefined : (function () {
520
+ globalCallback(null, {
521
+ type: "change_thread_image",
522
+ threadID: utils.formatID(tid.toString()),
523
+ snippet: fetchData.snippet,
524
+ timestamp: fetchData.timestamp_precise,
525
+ author: fetchData.message_sender.id,
526
+ image: {
527
+ attachmentID: fetchData.image_with_metadata && fetchData.image_with_metadata.legacy_attachment_id,
528
+ width: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.x,
529
+ height: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.y,
530
+ url: fetchData.image_with_metadata && fetchData.image_with_metadata.preview.uri
531
+ }
532
+ });
533
+ })();
534
+ break;
535
+ case "UserMessage":
536
+ log.info("ff-Return", {
537
+ type: "message",
538
+ senderID: utils.formatID(fetchData.message_sender.id),
539
+ body: fetchData.message.text || "",
540
+ threadID: utils.formatID(tid.toString()),
541
+ messageID: fetchData.message_id,
542
+ attachments: [{
543
+ type: "share",
544
+ ID: fetchData.extensible_attachment.legacy_attachment_id,
545
+ url: fetchData.extensible_attachment.story_attachment.url,
546
+
547
+ title: fetchData.extensible_attachment.story_attachment.title_with_entities.text,
548
+ description: fetchData.extensible_attachment.story_attachment.description.text,
549
+ source: fetchData.extensible_attachment.story_attachment.source,
550
+
551
+ image: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).uri,
552
+ width: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).width,
553
+ height: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).height,
554
+ playable: (fetchData.extensible_attachment.story_attachment.media || {}).is_playable || false,
555
+ duration: (fetchData.extensible_attachment.story_attachment.media || {}).playable_duration_in_ms || 0,
556
+
557
+ subattachments: fetchData.extensible_attachment.subattachments,
558
+ properties: fetchData.extensible_attachment.story_attachment.properties,
559
+ }],
560
+ mentions: {},
561
+ timestamp: parseInt(fetchData.timestamp_precise),
562
+ participantIDs: (fetchData.participants || (fetchData.messageMetadata ? fetchData.messageMetadata.cid ? fetchData.messageMetadata.cid.canonicalParticipantFbids : fetchData.messageMetadata.participantIds : []) || []),
563
+ isGroup: (fetchData.message_sender.id != tid.toString())
564
+ });
565
+ globalCallback(null, {
566
+ type: "message",
567
+ senderID: utils.formatID(fetchData.message_sender.id),
568
+ body: fetchData.message.text || "",
569
+ threadID: utils.formatID(tid.toString()),
570
+ messageID: fetchData.message_id,
571
+ attachments: [{
572
+ type: "share",
573
+ ID: fetchData.extensible_attachment.legacy_attachment_id,
574
+ url: fetchData.extensible_attachment.story_attachment.url,
575
+
576
+ title: fetchData.extensible_attachment.story_attachment.title_with_entities.text,
577
+ description: fetchData.extensible_attachment.story_attachment.description.text,
578
+ source: fetchData.extensible_attachment.story_attachment.source,
579
+
580
+ image: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).uri,
581
+ width: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).width,
582
+ height: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).height,
583
+ playable: (fetchData.extensible_attachment.story_attachment.media || {}).is_playable || false,
584
+ duration: (fetchData.extensible_attachment.story_attachment.media || {}).playable_duration_in_ms || 0,
585
+
586
+ subattachments: fetchData.extensible_attachment.subattachments,
587
+ properties: fetchData.extensible_attachment.story_attachment.properties,
588
+ }],
589
+ mentions: {},
590
+ timestamp: parseInt(fetchData.timestamp_precise),
591
+ participantIDs: (fetchData.participants || (fetchData.messageMetadata ? fetchData.messageMetadata.cid ? fetchData.messageMetadata.cid.canonicalParticipantFbids : fetchData.messageMetadata.participantIds : []) || []),
592
+ isGroup: (fetchData.message_sender.id != tid.toString())
593
+ });
594
+ }
595
+ }
596
+ else log.error("forcedFetch", fetchData);
597
+ })
598
+ .catch((err) => log.error("forcedFetch", err));
599
+ }
600
+ break;
601
+ case "ThreadName":
602
+ case "ParticipantsAddedToGroupThread":
603
+ case "ParticipantLeftGroupThread":
604
+ var formattedEvent;
605
+ try {
606
+ formattedEvent = utils.formatDeltaEvent(v.delta);
607
+ }
608
+ catch (err) {
609
+ return globalCallback({
610
+ error: "Problem parsing message object. Please open an issue at https://github.com/Schmavery/facebook-chat-api/issues.",
611
+ detail: err,
612
+ res: v.delta,
613
+ type: "parse_error"
614
+ });
615
+ }
616
+ return (!ctx.globalOptions.selfListen && formattedEvent.author.toString() === ctx.userID) || !ctx.loggedIn ? undefined : (function () { globalCallback(null, formattedEvent); })();
617
+ }
618
+ }
619
+
620
+ function markDelivery(ctx, api, threadID, messageID) {
621
+ if (threadID && messageID) {
622
+ api.markAsDelivered(threadID, messageID, (err) => {
623
+ if (err) log.error("markAsDelivered", err);
624
+ else {
625
+ if (ctx.globalOptions.autoMarkRead) {
626
+ api.markAsRead(threadID, (err) => {
627
+ if (err) log.error("markAsDelivered", err);
628
+ });
629
+ }
630
+ }
631
+ });
632
+ }
633
+ }
634
+
635
+ module.exports = function (defaultFuncs, api, ctx) {
636
+ let globalCallback = identity;
637
+ function getSeqID() {
638
+ ctx.t_mqttCalled = false;
639
+ async function attemptRequest(retries = 3) {
640
+ try {
641
+ if (!ctx.fb_dtsg) {
642
+ const dtsg = await api.getFreshDtsg();
643
+ if (!dtsg) {
644
+ if (retries > 0) {
645
+ logger.Warning("Failed to get fb_dtsg, retrying...");
646
+ await utils.sleep(2000); // Longer delay for token retry
647
+ return attemptRequest(retries - 1);
648
+ }
649
+ throw { error: "Could not obtain fb_dtsg after multiple attempts" };
650
+ }
651
+ ctx.fb_dtsg = dtsg;
652
+ }
653
+
654
+ const form = {
655
+ av: ctx.userID,
656
+ fb_dtsg: ctx.fb_dtsg,
657
+ queries: JSON.stringify({
658
+ o0: {
659
+ doc_id: '3336396659757871',
660
+ query_params: {
661
+ limit: 1,
662
+ before: null,
663
+ tags: ['INBOX'],
664
+ includeDeliveryReceipts: false,
665
+ includeSeqID: true
666
+ }
667
+ }
668
+ }),
669
+ __user: ctx.userID,
670
+ __a: '1',
671
+ __req: '8',
672
+ __hs: '19577.HYP:comet_pkg.2.1..2.1',
673
+ dpr: '1',
674
+ fb_api_caller_class: 'RelayModern',
675
+ fb_api_req_friendly_name: 'MessengerGraphQLThreadlistFetcher'
676
+ };
677
+
678
+ const headers = {
679
+ 'Content-Type': 'application/x-www-form-urlencoded',
680
+ 'Referer': 'https://www.facebook.com/',
681
+ 'Origin': 'https://www.facebook.com',
682
+ 'sec-fetch-site': 'same-origin',
683
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
684
+ 'Cookie': ctx.jar.getCookieString('https://www.facebook.com'),
685
+ 'accept': '*/*',
686
+ 'accept-encoding': 'gzip, deflate, br'
687
+ };
688
+
689
+ const resData = await defaultFuncs
690
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form, { headers })
691
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
692
+
693
+ if (debugSeq) {
694
+ console.log('GraphQL SeqID Response:', JSON.stringify(resData, null, 2));
695
+ }
696
+
697
+ if (resData.error === 1357004 || resData.error === 1357001) {
698
+ if (retries > 0) {
699
+ logger.Warning("Session error, refreshing token and retrying...");
700
+ ctx.fb_dtsg = null; // Force new token
701
+ await utils.sleep(2000);
702
+ return attemptRequest(retries - 1);
703
+ }
704
+ throw { error: "Session refresh failed after retries" };
705
+ }
706
+
707
+ if (!Array.isArray(resData)) {
708
+ throw { error: "Invalid response format", res: resData };
709
+ }
710
+
711
+ const seqID = resData[0]?.o0?.data?.viewer?.message_threads?.sync_sequence_id;
712
+ if (!seqID) {
713
+ throw { error: "Missing sync_sequence_id", res: resData };
714
+ }
715
+
716
+ ctx.lastSeqId = seqID;
717
+ if (debugSeq) {
718
+ console.log('Got SeqID:', ctx.lastSeqId);
719
+ }
720
+
721
+ return listenMqtt(defaultFuncs, api, ctx, globalCallback);
722
+
723
+ } catch (err) {
724
+ if (retries > 0) {
725
+ console.log("Request failed, retrying...");
726
+
727
+ return attemptRequest(retries - 1);
728
+ }
729
+ throw err;
730
+ }
731
+ }
732
+
733
+ return attemptRequest()
734
+ .catch((err) => {
735
+ log.error("getSeqId", err);
736
+ if (utils.getType(err) == "Object" && err.error === "Not logged in") ctx.loggedIn = false;
737
+ return globalCallback(err);
738
+ });
739
+ }
740
+ /*
741
+ getSeqID = function getSeqID() {
742
+ ctx.t_mqttCalled = false;
743
+ defaultFuncs
744
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
745
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
746
+ .then((resData) => {
747
+ if (utils.getType(resData) != "Array") throw { error: "Not logged in", res: resData };
748
+ if (resData && resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
749
+ if (resData[resData.length - 1].successful_results === 0) throw { error: "getSeqId: there was no successful_results", res: resData };
750
+ if (resData[0].o0.data.viewer.message_threads.sync_sequence_id) {
751
+ ctx.lastSeqId = resData[0].o0.data.viewer.message_threads.sync_sequence_id;
752
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
753
+ } else throw { error: "getSeqId: no sync_sequence_id found.", res: resData };
754
+ })
755
+ .catch((err) => {
756
+ log.error("getSeqId", err);
757
+ if (utils.getType(err) == "Object" && err.error === "Not logged in") ctx.loggedIn = false;
758
+ return globalCallback(err);
759
+ });
760
+ };*/
761
+
762
+ return function (callback) {
763
+ class MessageEmitter extends EventEmitter {
764
+ stopListening(callback) {
765
+
766
+ callback = callback || (() => { });
767
+ globalCallback = identity;
768
+ if (ctx.mqttClient) {
769
+ ctx.mqttClient.unsubscribe("/webrtc");
770
+ ctx.mqttClient.unsubscribe("/rtc_multi");
771
+ ctx.mqttClient.unsubscribe("/onevc");
772
+ ctx.mqttClient.publish("/browser_close", "{}");
773
+ ctx.mqttClient.end(false, function (...data) {
774
+ callback(data);
775
+ ctx.mqttClient = undefined;
776
+ });
777
+ }
778
+ }
779
+
780
+ async stopListeningAsync() {
781
+ return new Promise((resolve) => {
782
+ this.stopListening(resolve);
783
+ });
784
+ }
785
+ }
786
+
787
+ const msgEmitter = new MessageEmitter();
788
+ globalCallback = (callback || function (error, message) {
789
+ if (error) {
790
+ return msgEmitter.emit("error", error);
791
+ }
792
+ msgEmitter.emit("message", message);
793
+ });
794
+
795
+ // Reset some stuff
796
+ if (!ctx.firstListen)
797
+ ctx.lastSeqId = null;
798
+ ctx.syncToken = undefined;
799
+ ctx.t_mqttCalled = false;
800
+
801
+ form = {
802
+ "av": ctx.globalOptions.pageID,
803
+ "queries": JSON.stringify({
804
+ "o0": {
805
+ "doc_id": "3336396659757871",
806
+ "query_params": {
807
+ "limit": 1,
808
+ "before": null,
809
+ "tags": ["INBOX"],
810
+ "includeDeliveryReceipts": false,
811
+ "includeSeqID": true
812
+ }
813
+ }
814
+ })
815
+ };
816
+
817
+ if (!ctx.firstListen || !ctx.lastSeqId) {
818
+ getSeqID(defaultFuncs, api, ctx, globalCallback);
819
+ } else {
820
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
821
+ }
822
+
823
+ api.stopListening = msgEmitter.stopListening;
824
+ api.stopListeningAsync = msgEmitter.stopListeningAsync;
825
+ return msgEmitter;
826
+ };
827
+ };