@ivncotacte/fca-ivan 1.5.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of @ivncotacte/fca-ivan might be problematic. Click here for more details.

Files changed (116) hide show
  1. package/.gitattributes +2 -0
  2. package/Extra/Database/index.js +469 -0
  3. package/Extra/ExtraAddons.js +82 -0
  4. package/Extra/ExtraFindUID.js +62 -0
  5. package/Extra/ExtraGetThread.js +340 -0
  6. package/Extra/ExtraScreenShot.js +430 -0
  7. package/Extra/ExtraUptimeRobot.js +38 -0
  8. package/Extra/Html/Classic/script.js +119 -0
  9. package/Extra/Html/Classic/style.css +8 -0
  10. package/Extra/Security/AES_256_GCM/index.js +0 -0
  11. package/Extra/Security/Base/Step_1.js +6 -0
  12. package/Extra/Security/Base/Step_2.js +22 -0
  13. package/Extra/Security/Base/Step_3.js +22 -0
  14. package/Extra/Security/Base/index.js +173 -0
  15. package/Extra/Security/Index.js +5 -0
  16. package/Extra/Security/Step_1.js +6 -0
  17. package/Extra/Security/Step_2.js +22 -0
  18. package/Extra/Security/Step_3.js +22 -0
  19. package/Extra/Src/Change_Environment.js +24 -0
  20. package/Extra/Src/Check_Update.js +67 -0
  21. package/Extra/Src/History.js +115 -0
  22. package/Extra/Src/Instant_Update.js +65 -0
  23. package/Extra/Src/Last-Run.js +65 -0
  24. package/Extra/Src/Premium.js +81 -0
  25. package/Extra/Src/Release_Memory.js +41 -0
  26. package/Extra/Src/Websocket.js +213 -0
  27. package/Extra/Src/image/checkmate.jpg +0 -0
  28. package/Extra/Src/uuid.js +137 -0
  29. package/Func/AcceptAgreement.js +31 -0
  30. package/Func/ClearCache.js +64 -0
  31. package/Func/ReportV1.js +54 -0
  32. package/LICENSE +21 -0
  33. package/Language/index.json +435 -0
  34. package/Main.js +1211 -0
  35. package/README.md +129 -0
  36. package/SECURITY.md +18 -0
  37. package/broadcast.js +40 -0
  38. package/index.js +386 -0
  39. package/logger.js +66 -0
  40. package/package.json +87 -0
  41. package/src/Dev_Horizon_Data.js +125 -0
  42. package/src/Premium.js +25 -0
  43. package/src/Screenshot.js +83 -0
  44. package/src/addExternalModule.js +16 -0
  45. package/src/addUserToGroup.js +79 -0
  46. package/src/changeAdminStatus.js +79 -0
  47. package/src/changeArchivedStatus.js +41 -0
  48. package/src/changeAvt.js +85 -0
  49. package/src/changeBio.js +65 -0
  50. package/src/changeBlockedStatus.js +36 -0
  51. package/src/changeGroupImage.js +106 -0
  52. package/src/changeNickname.js +45 -0
  53. package/src/changeThreadColor.js +62 -0
  54. package/src/changeThreadEmoji.js +42 -0
  55. package/src/createNewGroup.js +70 -0
  56. package/src/createPoll.js +60 -0
  57. package/src/deleteMessage.js +45 -0
  58. package/src/deleteThread.js +43 -0
  59. package/src/forwardAttachment.js +48 -0
  60. package/src/getAccessToken.js +28 -0
  61. package/src/getCurrentUserID.js +7 -0
  62. package/src/getEmojiUrl.js +27 -0
  63. package/src/getFriendsList.js +73 -0
  64. package/src/getMessage.js +80 -0
  65. package/src/getThreadHistory.js +537 -0
  66. package/src/getThreadInfo.js +425 -0
  67. package/src/getThreadList.js +213 -0
  68. package/src/getThreadMain.js +220 -0
  69. package/src/getThreadPictures.js +59 -0
  70. package/src/getUID.js +59 -0
  71. package/src/getUserID.js +62 -0
  72. package/src/getUserInfo.js +113 -0
  73. package/src/getUserInfoMain.js +65 -0
  74. package/src/getUserInfoV2.js +32 -0
  75. package/src/getUserInfoV3.js +63 -0
  76. package/src/getUserInfoV4.js +55 -0
  77. package/src/getUserInfoV5.js +61 -0
  78. package/src/handleFriendRequest.js +46 -0
  79. package/src/handleMessageRequest.js +49 -0
  80. package/src/httpGet.js +49 -0
  81. package/src/httpPost.js +48 -0
  82. package/src/httpPostFormData.js +41 -0
  83. package/src/listenMqtt.js +787 -0
  84. package/src/logout.js +68 -0
  85. package/src/markAsDelivered.js +48 -0
  86. package/src/markAsRead.js +70 -0
  87. package/src/markAsReadAll.js +43 -0
  88. package/src/markAsSeen.js +51 -0
  89. package/src/muteThread.js +47 -0
  90. package/src/removeUserFromGroup.js +49 -0
  91. package/src/resolvePhotoUrl.js +37 -0
  92. package/src/searchForThread.js +43 -0
  93. package/src/sendMessage.js +379 -0
  94. package/src/sendTypingIndicator.js +80 -0
  95. package/src/setMessageReaction.js +109 -0
  96. package/src/setPostReaction.js +102 -0
  97. package/src/setTitle.js +74 -0
  98. package/src/threadColors.js +39 -0
  99. package/src/unfriend.js +43 -0
  100. package/src/unsendMessage.js +40 -0
  101. package/test/Database_Test.js +4 -0
  102. package/test/Db2.js +530 -0
  103. package/test/Horizon_Database/A_README.md +1 -0
  104. package/test/Horizon_Database/Database.db +0 -0
  105. package/test/data/shareAttach.js +146 -0
  106. package/test/data/something.mov +0 -0
  107. package/test/data/test.png +0 -0
  108. package/test/data/test.txt +7 -0
  109. package/test/env/.env +0 -0
  110. package/test/example-config.json +18 -0
  111. package/test/example-db.db +0 -0
  112. package/test/memoryleak.js +18 -0
  113. package/test/test-page.js +140 -0
  114. package/test/test.js +385 -0
  115. package/test/testv2.js +18 -0
  116. package/utils.js +1683 -0
@@ -0,0 +1,425 @@
1
+ "use strict";
2
+
3
+ var utils = require("../utils");
4
+ var log = require("npmlog");
5
+ // tương lai đi rồi fix ahahha
6
+ function formatEventReminders(reminder) {
7
+ return {
8
+ reminderID: reminder.id,
9
+ eventCreatorID: reminder.lightweight_event_creator.id,
10
+ time: reminder.time,
11
+ eventType: reminder.lightweight_event_type.toLowerCase(),
12
+ locationName: reminder.location_name,
13
+ // @TODO verify this
14
+ locationCoordinates: reminder.location_coordinates,
15
+ locationPage: reminder.location_page,
16
+ eventStatus: reminder.lightweight_event_status.toLowerCase(),
17
+ note: reminder.note,
18
+ repeatMode: reminder.repeat_mode.toLowerCase(),
19
+ eventTitle: reminder.event_title,
20
+ triggerMessage: reminder.trigger_message,
21
+ secondsToNotifyBefore: reminder.seconds_to_notify_before,
22
+ allowsRsvp: reminder.allows_rsvp,
23
+ relatedEvent: reminder.related_event,
24
+ members: reminder.event_reminder_members.edges.map(function(member) {
25
+ return {
26
+ memberID: member.node.id,
27
+ state: member.guest_list_state.toLowerCase()
28
+ };
29
+ })
30
+ };
31
+ }
32
+
33
+ function formatThreadGraphQLResponse(data) {
34
+ try{
35
+ var messageThread = data.message_thread;
36
+ } catch (err){
37
+ console.error("GetThreadInfoGraphQL", "Can't get this thread info!");
38
+ return {err: err};
39
+ }
40
+ var threadID = messageThread.thread_key.thread_fbid
41
+ ? messageThread.thread_key.thread_fbid
42
+ : messageThread.thread_key.other_user_id;
43
+
44
+ // Remove me
45
+ var lastM = messageThread.last_message;
46
+ var snippetID =
47
+ lastM &&
48
+ lastM.nodes &&
49
+ lastM.nodes[0] &&
50
+ lastM.nodes[0].message_sender &&
51
+ lastM.nodes[0].message_sender.messaging_actor
52
+ ? lastM.nodes[0].message_sender.messaging_actor.id
53
+ : null;
54
+ var snippetText =
55
+ lastM && lastM.nodes && lastM.nodes[0] ? lastM.nodes[0].snippet : null;
56
+ var lastR = messageThread.last_read_receipt;
57
+ var lastReadTimestamp =
58
+ lastR && lastR.nodes && lastR.nodes[0] && lastR.nodes[0].timestamp_precise
59
+ ? lastR.nodes[0].timestamp_precise
60
+ : null;
61
+
62
+ return {
63
+ threadID: threadID,
64
+ threadName: messageThread.name,
65
+ participantIDs: messageThread.all_participants.edges.map(d => d.node.messaging_actor.id),
66
+ userInfo: messageThread.all_participants.edges.map(d => ({
67
+ id: d.node.messaging_actor.id,
68
+ name: d.node.messaging_actor.name,
69
+ firstName: d.node.messaging_actor.short_name,
70
+ vanity: d.node.messaging_actor.username,
71
+ thumbSrc: d.node.messaging_actor.big_image_src.uri,
72
+ profileUrl: d.node.messaging_actor.big_image_src.uri,
73
+ gender: d.node.messaging_actor.gender,
74
+ type: d.node.messaging_actor.__typename,
75
+ isFriend: d.node.messaging_actor.is_viewer_friend,
76
+ isBirthday: !!d.node.messaging_actor.is_birthday //not sure?
77
+ })),
78
+ unreadCount: messageThread.unread_count,
79
+ messageCount: messageThread.messages_count,
80
+ timestamp: messageThread.updated_time_precise,
81
+ muteUntil: messageThread.mute_until,
82
+ isGroup: messageThread.thread_type == "GROUP",
83
+ isSubscribed: messageThread.is_viewer_subscribed,
84
+ isArchived: messageThread.has_viewer_archived,
85
+ folder: messageThread.folder,
86
+ cannotReplyReason: messageThread.cannot_reply_reason,
87
+ eventReminders: messageThread.event_reminders
88
+ ? messageThread.event_reminders.nodes.map(formatEventReminders)
89
+ : null,
90
+ emoji: messageThread.customization_info
91
+ ? messageThread.customization_info.emoji
92
+ : null,
93
+ color:
94
+ messageThread.customization_info &&
95
+ messageThread.customization_info.outgoing_bubble_color
96
+ ? messageThread.customization_info.outgoing_bubble_color.slice(2)
97
+ : null,
98
+ nicknames:
99
+ messageThread.customization_info &&
100
+ messageThread.customization_info.participant_customizations
101
+ ? messageThread.customization_info.participant_customizations.reduce(
102
+ function(res, val) {
103
+ if (val.nickname) res[val.participant_id] = val.nickname;
104
+ return res;
105
+ },
106
+ {}
107
+ )
108
+ : {},
109
+ adminIDs: messageThread.thread_admins,
110
+ approvalMode: Boolean(messageThread.approval_mode),
111
+ approvalQueue: messageThread.group_approval_queue.nodes.map(a => ({
112
+ inviterID: a.inviter.id,
113
+ requesterID: a.requester.id,
114
+ timestamp: a.request_timestamp,
115
+ request_source: a.request_source // @Undocumented
116
+ })),
117
+
118
+ // @Undocumented
119
+ reactionsMuteMode: messageThread.reactions_mute_mode.toLowerCase(),
120
+ mentionsMuteMode: messageThread.mentions_mute_mode.toLowerCase(),
121
+ isPinProtected: messageThread.is_pin_protected,
122
+ relatedPageThread: messageThread.related_page_thread,
123
+
124
+ // @Legacy
125
+ name: messageThread.name,
126
+ snippet: snippetText,
127
+ snippetSender: snippetID,
128
+ snippetAttachments: [],
129
+ serverTimestamp: messageThread.updated_time_precise,
130
+ imageSrc: messageThread.image ? messageThread.image.uri : null,
131
+ isCanonicalUser: messageThread.is_canonical_neo_user,
132
+ isCanonical: messageThread.thread_type != "GROUP",
133
+ recipientsLoadable: true,
134
+ hasEmailParticipant: false,
135
+ readOnly: false,
136
+ canReply: messageThread.cannot_reply_reason == null,
137
+ lastMessageTimestamp: messageThread.last_message
138
+ ? messageThread.last_message.timestamp_precise
139
+ : null,
140
+ lastMessageType: "message",
141
+ lastReadTimestamp: lastReadTimestamp,
142
+ threadType: messageThread.thread_type == "GROUP" ? 2 : 1,
143
+ TimeCreate: Date.now(),
144
+ TimeUpdate: Date.now()
145
+ };
146
+ }
147
+
148
+ module.exports = function(defaultFuncs, api, ctx) {
149
+
150
+ var { createData,getData,hasData,setLastRun,updateData, getAll } = require('../Extra/ExtraGetThread');
151
+ var { capture } = require('../Extra/Src/Last-Run');
152
+ var Database = require('../Extra/Database');
153
+ global.Fca.Data.Userinfo = [];
154
+
155
+ return function getThreadInfoGraphQL(threadID, callback) {
156
+ var resolveFunc = function(){};
157
+ var rejectFunc = function(){};
158
+ var returnPromise = new Promise(function (resolve, reject) {
159
+ resolveFunc = resolve;
160
+ rejectFunc = reject;
161
+ });
162
+
163
+ if (utils.getType(callback) != "Function" && utils.getType(callback) != "AsyncFunction") {
164
+ callback = function (err, data) {
165
+ if (err) {
166
+ return rejectFunc(err);
167
+ }
168
+ resolveFunc(data);
169
+ };
170
+ }
171
+
172
+ // được tìm thấy vào giữa tháng 8/2022 bởi @KanzuWakazaki - đã được chia sẻ cho @D-Jukie và Horizon Team Public group 🤴
173
+ // những code tương tự muliti thread như này đều có thể là copy idea 🐧
174
+ // đã áp dụng vào fca mới(cloud - fca(private)) vào cuối tháng 8/2022 bởi @IteralingCode(Hidden Member( always :) )) - Synthetic 4 - @Horizon Team
175
+ //cập nhật dự án bị bỏ rơi này vào ngày 19/11/2022 bởi @KanzuWakazaki(Owner) - Synthetic 1 - @Horizon Team nhằm đáp ứng nhu cầu của client !
176
+
177
+ if (utils.getType(threadID) !== "Array") threadID = [threadID];
178
+
179
+
180
+ var SpecialMethod = function(TID) {
181
+ const All = getAll();
182
+ const Real = [];
183
+ const Average = [];
184
+ for (let i of All) {
185
+ if (i.data.threadID != undefined) {
186
+ if (i.data.TimeCreate + 900 * 1000 <= Date.now()) {
187
+ Real.push(i.data.threadID);
188
+ }
189
+ else {
190
+ Average.push({
191
+ threadID: i.data.threadID,
192
+ TimeCreate: i.data.TimeCreate
193
+ });
194
+ continue;
195
+ }
196
+ } else continue;
197
+ }
198
+ const AllofThread = [];
199
+ if (Average.length > 0) {
200
+ var Time = 0;
201
+ for (let i of Average) {
202
+ Time += i.TimeCreate;
203
+ }
204
+ Time = Time / Average.length;
205
+ if (Time + 900 * 1000 <= Date.now()) {
206
+ for (let i of Average) {
207
+ Real.push(i.threadID);
208
+ }
209
+ } //can't =))
210
+ else {
211
+ setTimeout(function () {
212
+ SpecialMethod(TID);
213
+ }, Time + 900 * 1000 - Date.now());
214
+ }
215
+ }
216
+ else {
217
+ setTimeout(function () {
218
+ SpecialMethod(TID);
219
+ }, 900 * 1000);
220
+ }
221
+ if (Real.length == 0) return;
222
+ else if (Real.length == 1) {
223
+ return DefaultMethod(TID);
224
+ }
225
+ else if (All.length > 1) {
226
+ for (let i of All) {
227
+ if (i.data.threadID != undefined) {
228
+ AllofThread.push(i.data.threadID);
229
+ } else continue;
230
+ }
231
+ var Form = {};
232
+ var ThreadInfo = [];
233
+
234
+ AllofThread.map(function (x,y) {
235
+ Form["o" + y] = {
236
+ doc_id: "3449967031715030",
237
+ query_params: {
238
+ id: x,
239
+ message_limit: 0,
240
+ load_messages: false,
241
+ load_read_receipts: false,
242
+ before: null
243
+ }
244
+ };
245
+ });
246
+
247
+ var form = {
248
+ queries: JSON.stringify(Form),
249
+ batch_name: "MessengerGraphQLThreadFetcher"
250
+ };
251
+
252
+ defaultFuncs
253
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
254
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
255
+ .then(function(resData) {
256
+ if (resData.error) {
257
+ throw "Lỗi: getThreadInfoGraphQL Có Thể Do Bạn Spam Quá Nhiều";
258
+ }
259
+ if (resData[resData.length - 1].error_results !== 0) {
260
+ throw "Lỗi: getThreadInfoGraphQL Có Thể Do Bạn Spam Quá Nhiều";
261
+ }
262
+ resData = resData.splice(0, resData.length - 1);
263
+ resData.sort((a, b) => { return Object.keys(a)[0].localeCompare(Object.keys(b)[0]); });
264
+ resData.map(function (x,y) {
265
+ ThreadInfo.push(formatThreadGraphQLResponse(x["o"+y].data));
266
+ });
267
+ try {
268
+ if (Object.keys(resData).length == 1) {
269
+ updateData(threadID,ThreadInfo[0]);
270
+ if (utils.getType(ThreadInfo[0].userInfo) == "Array") {
271
+ for (let i of ThreadInfo[0].userInfo) {
272
+ if (global.Fca.Data.Userinfo.some(ii => ii.id == i.id)) {
273
+ global.Fca.Data.Userinfo.splice(global.Fca.Data.Userinfo.findIndex(ii => ii.id == i.id), 1);
274
+ }
275
+ global.Fca.Data.Userinfo.push(i);
276
+ }
277
+ }
278
+ } else {
279
+ for (let i of ThreadInfo) {
280
+ updateData(i.threadID,i);
281
+ if (utils.getType(i.userInfo) == "Array") {
282
+ for (let ii of i.userInfo) {
283
+ if (global.Fca.Data.Userinfo.some(iii => iii.id == ii.id)) {
284
+ global.Fca.Data.Userinfo.splice(global.Fca.Data.Userinfo.findIndex(iii => iii.id == ii.id), 1);
285
+ }
286
+ global.Fca.Data.Userinfo.push(ii);
287
+ }
288
+ }
289
+ }
290
+ }
291
+ }
292
+ catch (e) {
293
+ console.log(e);
294
+ }
295
+ })
296
+ .catch(function(err){
297
+ throw "Lỗi: getThreadInfoGraphQL Có Thể Do Bạn Spam Quá Nhiều";
298
+ });
299
+ }
300
+ };
301
+
302
+ var DefaultMethod = function(TID) {
303
+ var ThreadInfo = [];
304
+ for (let i of TID) {
305
+ ThreadInfo.push(getData(i));
306
+ }
307
+ if (ThreadInfo.length == 1) {
308
+ callback(null,ThreadInfo[0]);
309
+ if (utils.getType(ThreadInfo[0].userInfo) == "Array") {
310
+ for (let i of ThreadInfo[0].userInfo) {
311
+ if (global.Fca.Data.Userinfo.some(ii => ii.id == i.id)) {
312
+ global.Fca.Data.Userinfo.splice(global.Fca.Data.Userinfo.findIndex(ii => ii.id == i.id), 1);
313
+ }
314
+ global.Fca.Data.Userinfo.push(i);
315
+ }
316
+ } else {
317
+ for (let i of ThreadInfo) {
318
+ if (utils.getType(i.userInfo) == "Array") {
319
+ for (let ii of i.userInfo) {
320
+ if (global.Fca.Data.Userinfo.some(iii => iii.id == ii.id)) {
321
+ global.Fca.Data.Userinfo.splice(global.Fca.Data.Userinfo.findIndex(iii => iii.id == ii.id), 1);
322
+ }
323
+ global.Fca.Data.Userinfo.push(ii);
324
+ }
325
+ }
326
+ }
327
+ callback(null,ThreadInfo);
328
+ }
329
+ }
330
+ };
331
+ var CreateMethod = function(TID) {
332
+ var Form = {};
333
+ var ThreadInfo = [];
334
+
335
+ TID.map(function (x,y) {
336
+ Form["o" + y] = {
337
+ doc_id: "3449967031715030",
338
+ query_params: {
339
+ id: x,
340
+ message_limit: 0,
341
+ load_messages: false,
342
+ load_read_receipts: false,
343
+ before: null
344
+ }
345
+ };
346
+ });
347
+
348
+ var form = {
349
+ queries: JSON.stringify(Form),
350
+ batch_name: "MessengerGraphQLThreadFetcher"
351
+ };
352
+
353
+ defaultFuncs
354
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
355
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
356
+ .then(function(resData) {
357
+ if (resData.error) {
358
+ throw resData.error;
359
+ }
360
+ if (resData[resData.length - 1].error_results !== 0) {
361
+ throw resData.error;
362
+ }
363
+ resData = resData.splice(0, resData.length - 1);
364
+ resData.sort((a, b) => { return Object.keys(a)[0].localeCompare(Object.keys(b)[0]); });
365
+ resData.map(function (x,y) {
366
+ ThreadInfo.push(formatThreadGraphQLResponse(x["o"+y].data));
367
+ });
368
+ if (Object.keys(resData).length == 1) {
369
+ createData(threadID,ThreadInfo[0]);
370
+ callback(null, ThreadInfo[0]);
371
+ capture(callback);
372
+ setLastRun('LastUpdate', callback);
373
+ if (utils.getType(ThreadInfo[0].userInfo) == "Array") {
374
+ for (let i of ThreadInfo[0].userInfo) {
375
+ if (global.Fca.Data.Userinfo.some(ii => ii.id == i.id)) {
376
+ global.Fca.Data.Userinfo.splice(global.Fca.Data.Userinfo.findIndex(ii => ii.id == i.id), 1);
377
+ }
378
+ global.Fca.Data.Userinfo.push(i);
379
+ }
380
+ }
381
+ } else {
382
+ // api.Horizon_Data([ThreadInfo], "Threads", "Post");
383
+ for (let i of ThreadInfo) {
384
+ createData(i.threadID,i);
385
+ if (utils.getType(i.userInfo) == "Array") {
386
+ for (let ii of i.userInfo) {
387
+ if (global.Fca.Data.Userinfo.some(iii => iii.id == ii.id)) {
388
+ global.Fca.Data.Userinfo.splice(global.Fca.Data.Userinfo.findIndex(iii => iii.id == ii.id), 1);
389
+ }
390
+ global.Fca.Data.Userinfo.push(ii);
391
+ }
392
+ }
393
+ }
394
+ callback(null, ThreadInfo);
395
+ }
396
+ })
397
+ .catch(function(err){
398
+ throw err;
399
+ });
400
+ };
401
+ if (global.Fca.Data.Already != true) { SpecialMethod(threadID); global.Fca.Data.Already = true; }
402
+
403
+
404
+
405
+ setInterval(function(){Database(true).set('UserInfo', global.Fca.Data.Userinfo);global.Fca.Data.Userinfo = [];}, 900 * 1000);
406
+ try {
407
+ for (let i of threadID) {
408
+ switch (hasData(i)) {
409
+ case true: {
410
+ DefaultMethod(threadID);
411
+ break;
412
+ }
413
+ case false: {
414
+ CreateMethod(threadID);
415
+ break;
416
+ }
417
+ }
418
+ }
419
+ }
420
+ catch (err) {
421
+ console.log(err);
422
+ }
423
+ return returnPromise;
424
+ };
425
+ };
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+
3
+ const utils = require("../utils");
4
+ const log = require("npmlog");
5
+
6
+ function createProfileUrl(url, username, id) {
7
+ if (url) return url;
8
+ return "https://www.facebook.com/" + (username || utils.formatID(id.toString()));
9
+ }
10
+
11
+ function formatParticipants(participants) {
12
+ return participants.edges.map((p) => {
13
+ p = p.node.messaging_actor;
14
+ switch (p["__typename"]) {
15
+ case "User":
16
+ return {
17
+ accountType: p["__typename"],
18
+ userID: utils.formatID(p.id.toString()), // do we need .toString()? when it is not a string?
19
+ name: p.name,
20
+ shortName: p.short_name,
21
+ gender: p.gender,
22
+ url: p.url, // how about making it profileURL
23
+ profilePicture: p.big_image_src.uri,
24
+ username: (p.username || null),
25
+ // TODO: maybe better names for these?
26
+ isViewerFriend: p.is_viewer_friend, // true/false
27
+ isMessengerUser: p.is_messenger_user, // true/false
28
+ isVerified: p.is_verified, // true/false
29
+ isMessageBlockedByViewer: p.is_message_blocked_by_viewer, // true/false
30
+ isViewerCoworker: p.is_viewer_coworker, // true/false
31
+ isEmployee: p.is_employee // null? when it is something other? can someone check?
32
+ };
33
+ case "Page":
34
+ return {
35
+ accountType: p["__typename"],
36
+ userID: utils.formatID(p.id.toString()), // or maybe... pageID?
37
+ name: p.name,
38
+ url: p.url,
39
+ profilePicture: p.big_image_src.uri,
40
+ username: (p.username || null),
41
+ // uhm... better names maybe?
42
+ acceptsMessengerUserFeedback: p.accepts_messenger_user_feedback, // true/false
43
+ isMessengerUser: p.is_messenger_user, // true/false
44
+ isVerified: p.is_verified, // true/false
45
+ isMessengerPlatformBot: p.is_messenger_platform_bot, // true/false
46
+ isMessageBlockedByViewer: p.is_message_blocked_by_viewer, // true/false
47
+ };
48
+ case "ReducedMessagingActor":
49
+ case "UnavailableMessagingActor":
50
+ return {
51
+ accountType: p["__typename"],
52
+ userID: utils.formatID(p.id.toString()),
53
+ name: p.name,
54
+ url: createProfileUrl(p.url, p.username, p.id), // in this case p.url is null all the time
55
+ profilePicture: p.big_image_src.uri, // in this case it is default facebook photo, we could determine gender using it
56
+ username: (p.username || null), // maybe we could use it to generate profile URL?
57
+ isMessageBlockedByViewer: p.is_message_blocked_by_viewer, // true/false
58
+ };
59
+ default:
60
+ log.warn("getThreadList", "Found participant with unsupported typename. Please open an issue at https://github.com/Schmavery/facebook-chat-api/issues\n" + JSON.stringify(p, null, 2));
61
+ return {
62
+ accountType: p["__typename"],
63
+ userID: utils.formatID(p.id.toString()),
64
+ name: p.name || `[unknown ${p["__typename"]}]`, // probably it will always be something... but fallback to [unknown], just in case
65
+ };
66
+ }
67
+ });
68
+ }
69
+
70
+ // "FF8C0077" -> "8C0077"
71
+ function formatColor(color) {
72
+ if (color && color.match(/^(?:[0-9a-fA-F]{8})$/g)) return color.slice(2);
73
+ return color;
74
+ }
75
+
76
+ function getThreadName(t) {
77
+ if (t.name || t.thread_key.thread_fbid) return t.name;
78
+
79
+ for (let po of t.all_participants.edges) {
80
+ let p = po.node;
81
+ if (p.messaging_actor.id === t.thread_key.other_user_id) return p.messaging_actor.name;
82
+ }
83
+ }
84
+
85
+ function mapNicknames(customizationInfo) {
86
+ return (customizationInfo && customizationInfo.participant_customizations) ? customizationInfo.participant_customizations.map(u => {
87
+ return {
88
+ "userID": u.participant_id,
89
+ "nickname": u.nickname
90
+ };
91
+ }) : [];
92
+ }
93
+
94
+ function formatThreadList(data) {
95
+ return data.map(t => {
96
+ let lastMessageNode = (t.last_message && t.last_message.nodes && t.last_message.nodes.length > 0) ? t.last_message.nodes[0] : null;
97
+ return {
98
+ threadID: t.thread_key ? utils.formatID(t.thread_key.thread_fbid || t.thread_key.other_user_id) : null, // shall never be null
99
+ name: getThreadName(t),
100
+ unreadCount: t.unread_count,
101
+ messageCount: t.messages_count,
102
+ imageSrc: t.image ? t.image.uri : null,
103
+ emoji: t.customization_info ? t.customization_info.emoji : null,
104
+ color: formatColor(t.customization_info ? t.customization_info.outgoing_bubble_color : null),
105
+ nicknames: mapNicknames(t.customization_info),
106
+ muteUntil: t.mute_until,
107
+ participants: formatParticipants(t.all_participants),
108
+ adminIDs: t.thread_admins.map(a => a.id),
109
+ folder: t.folder,
110
+ isGroup: t.thread_type === "GROUP",
111
+ // rtc_call_data: t.rtc_call_data, // TODO: format and document this
112
+ // isPinProtected: t.is_pin_protected, // feature from future? always false (2018-04-04)
113
+ customizationEnabled: t.customization_enabled, // false for ONE_TO_ONE with Page or ReducedMessagingActor
114
+ participantAddMode: t.participant_add_mode_as_string, // "ADD" if "GROUP" and null if "ONE_TO_ONE"
115
+ montageThread: t.montage_thread ? Buffer.from(t.montage_thread.id, "base64").toString() : null, // base64 encoded string "message_thread:0000000000000000"
116
+ // it is not userID nor any other ID known to me...
117
+ // can somebody inspect it? where is it used?
118
+ // probably Messenger Day uses it
119
+ reactionsMuteMode: t.reactions_mute_mode,
120
+ mentionsMuteMode: t.mentions_mute_mode,
121
+ isArchived: t.has_viewer_archived,
122
+ isSubscribed: t.is_viewer_subscribed,
123
+ timestamp: t.updated_time_precise, // in miliseconds
124
+ // isCanonicalUser: t.is_canonical_neo_user, // is it always false?
125
+ // TODO: how about putting snippet in another object? current implementation does not handle every possibile message type etc.
126
+ snippet: lastMessageNode ? lastMessageNode.snippet : null,
127
+ snippetAttachments: lastMessageNode ? lastMessageNode.extensible_attachment : null, // TODO: not sure if it works
128
+ snippetSender: lastMessageNode ? utils.formatID((lastMessageNode.message_sender.messaging_actor.id || "").toString()) : null,
129
+ lastMessageTimestamp: lastMessageNode ? lastMessageNode.timestamp_precise : null, // timestamp in miliseconds
130
+ lastReadTimestamp: (t.last_read_receipt && t.last_read_receipt.nodes.length > 0)
131
+ ? (t.last_read_receipt.nodes[0] ? t.last_read_receipt.nodes[0].timestamp_precise : null)
132
+ : null, // timestamp in miliseconds
133
+ cannotReplyReason: t.cannot_reply_reason, // TODO: inspect possible values
134
+ approvalMode: Boolean(t.approval_mode),
135
+
136
+ // @Legacy
137
+ participantIDs: formatParticipants(t.all_participants).map(participant => participant.userID),
138
+ threadType: t.thread_type === "GROUP" ? 2 : 1 // "GROUP" or "ONE_TO_ONE"
139
+ };
140
+ });
141
+ }
142
+
143
+ module.exports = function (defaultFuncs, api, ctx) {
144
+ return function getThreadList(limit, timestamp, tags, callback) {
145
+ if (!callback && (utils.getType(tags) === "Function" || utils.getType(tags) === "AsyncFunction")) {
146
+ callback = tags;
147
+ tags = [""];
148
+ }
149
+ if (utils.getType(limit) !== "Number" || !Number.isInteger(limit) || limit <= 0) throw { error: "getThreadList: limit must be a positive integer" };
150
+
151
+ if (utils.getType(timestamp) !== "Null" && (utils.getType(timestamp) !== "Number" || !Number.isInteger(timestamp))) throw { error: "getThreadList: timestamp must be an integer or null" };
152
+
153
+ if (utils.getType(tags) === "String") tags = [tags];
154
+ if (utils.getType(tags) !== "Array") throw { error: "getThreadList: tags must be an array" };
155
+
156
+ var resolveFunc = function () { };
157
+ var rejectFunc = function () { };
158
+ var returnPromise = new Promise(function (resolve, reject) {
159
+ resolveFunc = resolve;
160
+ rejectFunc = reject;
161
+ });
162
+
163
+ if (utils.getType(callback) !== "Function" && utils.getType(callback) !== "AsyncFunction") {
164
+ callback = function (err, data) {
165
+ if (err) return rejectFunc(err);
166
+ resolveFunc(data);
167
+ };
168
+ }
169
+
170
+ const form = {
171
+ "av": ctx.globalOptions.pageID,
172
+ "queries": JSON.stringify({
173
+ "o0": {
174
+ // This doc_id was valid on 2020-07-20
175
+ "doc_id": "3336396659757871",
176
+ "query_params": {
177
+ "limit": limit + (timestamp ? 1 : 0),
178
+ "before": timestamp,
179
+ "tags": tags,
180
+ "includeDeliveryReceipts": true,
181
+ "includeSeqID": false
182
+ }
183
+ }
184
+ }),
185
+ "batch_name": "MessengerGraphQLThreadlistFetcher"
186
+ };
187
+
188
+ defaultFuncs
189
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
190
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
191
+ .then((resData) => {
192
+ if (resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
193
+
194
+ if (resData[resData.length - 1].successful_results === 0) throw { error: "getThreadList: there was no successful_results", res: resData };
195
+
196
+ // When we ask for threads using timestamp from the previous request,
197
+ // we are getting the last thread repeated as the first thread in this response.
198
+ // .shift() gets rid of it
199
+ // It is also the reason for increasing limit by 1 when timestamp is set
200
+ // this way user asks for 10 threads, we are asking for 11,
201
+ // but after removing the duplicated one, it is again 10
202
+ if (timestamp) resData[0].o0.data.viewer.message_threads.nodes.shift();
203
+
204
+ callback(null, formatThreadList(resData[0].o0.data.viewer.message_threads.nodes));
205
+ })
206
+ .catch((err) => {
207
+ log.error("getThreadList", "Lỗi: getThreadList Có Thể Do Bạn Spam Quá Nhiều, Hãy Thử Lại !");
208
+ return callback(err);
209
+ });
210
+
211
+ return returnPromise;
212
+ };
213
+ };