@cexy/hoonfca 1.0.4 → 1.0.5

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 (135) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/DOCS.md +2636 -0
  3. package/LICENSE-MIT +21 -21
  4. package/README.md +632 -0
  5. package/func/checkUpdate.js +222 -0
  6. package/func/logger.js +48 -0
  7. package/index.d.ts +731 -605
  8. package/index.js +6 -774
  9. package/module/config.js +34 -0
  10. package/module/login.js +136 -0
  11. package/module/loginHelper.js +1034 -0
  12. package/module/options.js +45 -0
  13. package/package.json +54 -19
  14. package/src/api/action/addExternalModule.js +25 -0
  15. package/src/api/action/changeAvatar.js +137 -0
  16. package/src/{changeBio.js → api/action/changeBio.js} +75 -76
  17. package/src/api/action/enableAutoSaveAppState.js +73 -0
  18. package/src/{getCurrentUserID.js → api/action/getCurrentUserID.js} +7 -7
  19. package/src/{handleFriendRequest.js → api/action/handleFriendRequest.js} +57 -57
  20. package/src/api/action/logout.js +76 -0
  21. package/src/api/action/refreshFb_dtsg.js +48 -0
  22. package/src/api/action/setPostReaction.js +106 -0
  23. package/src/api/action/unfriend.js +54 -0
  24. package/src/api/http/httpGet.js +46 -0
  25. package/src/api/http/httpPost.js +52 -0
  26. package/src/api/http/postFormData.js +47 -0
  27. package/src/api/messaging/addUserToGroup.js +68 -0
  28. package/src/api/messaging/changeAdminStatus.js +122 -0
  29. package/src/api/messaging/changeArchivedStatus.js +55 -0
  30. package/src/api/messaging/changeBlockedStatus.js +48 -0
  31. package/src/api/messaging/changeGroupImage.js +90 -0
  32. package/src/api/messaging/changeNickname.js +70 -0
  33. package/src/api/messaging/changeThreadColor.js +79 -0
  34. package/src/api/messaging/changeThreadEmoji.js +106 -0
  35. package/src/{createNewGroup.js → api/messaging/createNewGroup.js} +88 -88
  36. package/src/api/messaging/createPoll.js +43 -0
  37. package/src/api/messaging/createThemeAI.js +98 -0
  38. package/src/{deleteMessage.js → api/messaging/deleteMessage.js} +56 -56
  39. package/src/{deleteThread.js → api/messaging/deleteThread.js} +56 -56
  40. package/src/api/messaging/editMessage.js +68 -0
  41. package/src/api/messaging/forwardAttachment.js +51 -0
  42. package/src/{getEmojiUrl.js → api/messaging/getEmojiUrl.js} +8 -8
  43. package/src/{getFriendsList.js → api/messaging/getFriendsList.js} +82 -84
  44. package/src/api/messaging/getMessage.js +829 -0
  45. package/src/api/messaging/getThemePictures.js +62 -0
  46. package/src/api/messaging/handleMessageRequest.js +65 -0
  47. package/src/{markAsDelivered.js → api/messaging/markAsDelivered.js} +57 -58
  48. package/src/{markAsRead.js → api/messaging/markAsRead.js} +88 -88
  49. package/src/{markAsReadAll.js → api/messaging/markAsReadAll.js} +49 -50
  50. package/src/api/messaging/markAsSeen.js +61 -0
  51. package/src/{muteThread.js → api/messaging/muteThread.js} +50 -52
  52. package/src/api/messaging/removeUserFromGroup.js +106 -0
  53. package/src/{resolvePhotoUrl.js → api/messaging/resolvePhotoUrl.js} +43 -45
  54. package/src/api/messaging/scheduler.js +264 -0
  55. package/src/{searchForThread.js → api/messaging/searchForThread.js} +52 -53
  56. package/src/api/messaging/sendMessage.js +272 -0
  57. package/src/api/messaging/sendTypingIndicator.js +67 -0
  58. package/src/api/messaging/setMessageReaction.js +76 -0
  59. package/src/api/messaging/setTitle.js +119 -0
  60. package/src/{shareContact.js → api/messaging/shareContact.js} +49 -53
  61. package/src/api/messaging/threadColors.js +128 -0
  62. package/src/api/messaging/unsendMessage.js +81 -0
  63. package/src/api/messaging/uploadAttachment.js +94 -0
  64. package/src/api/socket/core/connectMqtt.js +255 -0
  65. package/src/api/socket/core/emitAuth.js +106 -0
  66. package/src/api/socket/core/getSeqID.js +40 -0
  67. package/src/api/socket/core/getTaskResponseData.js +22 -0
  68. package/src/api/socket/core/markDelivery.js +12 -0
  69. package/src/api/socket/core/parseDelta.js +391 -0
  70. package/src/api/socket/detail/buildStream.js +208 -0
  71. package/src/api/socket/detail/constants.js +24 -0
  72. package/src/api/socket/listenMqtt.js +364 -0
  73. package/src/api/socket/middleware/index.js +216 -0
  74. package/src/{getThreadHistory.js → api/threads/getThreadHistory.js} +664 -647
  75. package/src/api/threads/getThreadInfo.js +438 -0
  76. package/src/{getThreadList.js → api/threads/getThreadList.js} +293 -292
  77. package/src/{getThreadPictures.js → api/threads/getThreadPictures.js} +78 -79
  78. package/src/{getUserID.js → api/users/getUserID.js} +65 -66
  79. package/src/api/users/getUserInfo.js +327 -0
  80. package/src/api/users/getUserInfoV2.js +134 -0
  81. package/src/core/sendReqMqtt.js +96 -0
  82. package/src/database/models/index.js +87 -0
  83. package/src/database/models/thread.js +45 -0
  84. package/src/database/models/user.js +46 -0
  85. package/src/database/threadData.js +98 -0
  86. package/src/database/userData.js +89 -0
  87. package/src/utils/client.js +320 -0
  88. package/src/utils/constants.js +23 -0
  89. package/{utils.js → src/utils/format.js} +1115 -1447
  90. package/src/utils/headers.js +115 -0
  91. package/src/utils/request.js +305 -0
  92. package/.travis.yml +0 -6
  93. package/src/addExternalModule.js +0 -23
  94. package/src/addUserToGroup.js +0 -113
  95. package/src/changeAdminStatus.js +0 -95
  96. package/src/changeApprovalMode.js +0 -79
  97. package/src/changeArchivedStatus.js +0 -55
  98. package/src/changeBlockedStatus.js +0 -47
  99. package/src/changeBlockedStatusMqtt.js +0 -86
  100. package/src/changeGroupImage.js +0 -133
  101. package/src/changeNickname.js +0 -59
  102. package/src/changeThreadColor.js +0 -71
  103. package/src/changeThreadEmoji.js +0 -55
  104. package/src/createPoll.js +0 -130
  105. package/src/editMessage.js +0 -63
  106. package/src/forwardAttachment.js +0 -60
  107. package/src/forwardMessage.js +0 -63
  108. package/src/getGroupsList.js +0 -83
  109. package/src/getMarketplace.js +0 -84
  110. package/src/getNotifications.js +0 -86
  111. package/src/getPagesList.js +0 -83
  112. package/src/getStories.js +0 -88
  113. package/src/getThreadInfo.js +0 -222
  114. package/src/getUserInfo.js +0 -128
  115. package/src/handleMessageRequest.js +0 -65
  116. package/src/httpGet.js +0 -58
  117. package/src/httpPost.js +0 -58
  118. package/src/listenMqtt.js +0 -1018
  119. package/src/logout.js +0 -75
  120. package/src/markAsSeen.js +0 -61
  121. package/src/pinMessage.js +0 -61
  122. package/src/removeUserFromGroup.js +0 -79
  123. package/src/searchUsers.js +0 -89
  124. package/src/sendComment.js +0 -161
  125. package/src/sendMessage.js +0 -457
  126. package/src/sendTypingIndicator.js +0 -179
  127. package/src/setBio.js +0 -86
  128. package/src/setMessageReaction.js +0 -186
  129. package/src/setPostReaction.js +0 -71
  130. package/src/setTheme.js +0 -313
  131. package/src/setTitle.js +0 -90
  132. package/src/shareLink.js +0 -62
  133. package/src/threadColors.js +0 -57
  134. package/src/unfriend.js +0 -53
  135. package/src/unsendMessage.js +0 -104
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ const { getType } = require("../../../utils/format");
3
+ const { parseAndCheckLogin } = require("../../../utils/client");
4
+ module.exports = function createGetSeqID(deps) {
5
+ const { listenMqtt, logger, emitAuth } = deps;
6
+
7
+ return function getSeqID(defaultFuncs, api, ctx, globalCallback, form) {
8
+ ctx.t_mqttCalled = false;
9
+ return defaultFuncs
10
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
11
+ .then(parseAndCheckLogin(ctx, defaultFuncs))
12
+ .then(resData => {
13
+ if (getType(resData) !== "Array") throw { error: "Not logged in" };
14
+ if (!Array.isArray(resData) || !resData.length) return;
15
+ const lastRes = resData[resData.length - 1];
16
+ if (lastRes && lastRes.successful_results === 0) return;
17
+
18
+ const syncSeqId = resData[0]?.o0?.data?.viewer?.message_threads?.sync_sequence_id;
19
+ if (syncSeqId) {
20
+ ctx.lastSeqId = syncSeqId;
21
+ logger("mqtt getSeqID ok -> listenMqtt()", "info");
22
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
23
+ } else {
24
+ throw { error: "getSeqId: no sync_sequence_id found." };
25
+ }
26
+ })
27
+ .catch(err => {
28
+ const detail = (err && err.detail && err.detail.message) ? ` | detail=${err.detail.message}` : "";
29
+ const msg = ((err && err.error) || (err && err.message) || String(err || "")) + detail;
30
+ if (/Not logged in/i.test(msg)) {
31
+ return emitAuth(ctx, api, globalCallback, "not_logged_in", msg);
32
+ }
33
+ if (/blocked the login/i.test(msg)) {
34
+ return emitAuth(ctx, api, globalCallback, "login_blocked", msg);
35
+ }
36
+ logger(`getSeqID error: ${msg}`, "error");
37
+ return emitAuth(ctx, api, globalCallback, "auth_error", msg);
38
+ });
39
+ };
40
+ };
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ module.exports = function getTaskResponseData(taskType, payload) {
3
+ try {
4
+ switch (taskType) {
5
+ case "send_message_mqtt":
6
+ return {
7
+ type: taskType,
8
+ threadID: payload.step[1][2][2][1][2],
9
+ messageID: payload.step[1][2][2][1][3],
10
+ payload: payload.step[1][2]
11
+ };
12
+ case "set_message_reaction":
13
+ return { mid: payload.step[1][2][2][1][4] };
14
+ case "edit_message":
15
+ return { mid: payload.step[1][2][2][1][2] };
16
+ default:
17
+ return null;
18
+ }
19
+ } catch (e) {
20
+ return null;
21
+ }
22
+ };
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ module.exports = function markDelivery(ctx, api, threadID, messageID) {
3
+ if (threadID && messageID) {
4
+ api.markAsDelivered(threadID, messageID, err => {
5
+ if (err) {} else {
6
+ if (ctx.globalOptions.autoMarkRead) {
7
+ api.markAsRead(threadID, err2 => {});
8
+ }
9
+ }
10
+ });
11
+ }
12
+ };
@@ -0,0 +1,391 @@
1
+ "use strict";
2
+ const { formatDeltaEvent, formatMessage, _formatAttachment, formatDeltaMessage, formatDeltaReadReceipt, formatID, getType, decodeClientPayload } = require("../../../utils/format");
3
+ const logger = require("../../../../func/logger");
4
+ module.exports = function createParseDelta(deps) {
5
+ const { markDelivery, parseAndCheckLogin } = deps;
6
+ return function parseDelta(defaultFuncs, api, ctx, globalCallback, { delta }) {
7
+ if (delta.class === "NewMessage") {
8
+ const resolveAttachmentUrl = i => {
9
+ if (!delta.attachments || i === delta.attachments.length || getType(delta.attachments) !== "Array") {
10
+ let fmtMsg;
11
+ try {
12
+ fmtMsg = formatDeltaMessage(delta);
13
+ } catch (err) {
14
+ return;
15
+ }
16
+ if (fmtMsg) {
17
+ if (ctx.globalOptions.autoMarkDelivery) {
18
+ markDelivery(ctx, api, fmtMsg.threadID, fmtMsg.messageID);
19
+ }
20
+ if (!ctx.globalOptions.selfListen && fmtMsg.senderID === ctx.userID) return;
21
+ globalCallback(null, fmtMsg);
22
+ }
23
+ } else {
24
+ const attachment = delta.attachments[i];
25
+ if (attachment && attachment.mercury && attachment.mercury.attach_type === "photo") {
26
+ api.resolvePhotoUrl(attachment.fbid, (err, url) => {
27
+ if (!err && attachment.mercury && attachment.mercury.metadata) {
28
+ attachment.mercury.metadata.url = url;
29
+ }
30
+ resolveAttachmentUrl(i + 1);
31
+ });
32
+ } else {
33
+ resolveAttachmentUrl(i + 1);
34
+ }
35
+ }
36
+ };
37
+ resolveAttachmentUrl(0);
38
+ } else if (delta.class === "ClientPayload") {
39
+ const clientPayload = decodeClientPayload(delta.payload);
40
+ if (clientPayload && clientPayload.deltas) {
41
+ for (const d of clientPayload.deltas) {
42
+ if (d.deltaMessageReaction && !!ctx.globalOptions.listenEvents) {
43
+ const messageReaction = {
44
+ type: "message_reaction",
45
+ threadID: (d.deltaMessageReaction.threadKey.threadFbId ? d.deltaMessageReaction.threadKey.threadFbId : d.deltaMessageReaction.threadKey.otherUserFbId).toString(),
46
+ messageID: d.deltaMessageReaction.messageId,
47
+ reaction: d.deltaMessageReaction.reaction,
48
+ senderID: d.deltaMessageReaction.senderId.toString(),
49
+ userID: d.deltaMessageReaction.userId.toString()
50
+ };
51
+ globalCallback(null, messageReaction);
52
+ } else if (d.deltaRecallMessageData && !!ctx.globalOptions.listenEvents) {
53
+ const messageUnsend = {
54
+ type: "message_unsend",
55
+ threadID: (d.deltaRecallMessageData.threadKey.threadFbId ? d.deltaRecallMessageData.threadKey.threadFbId : d.deltaRecallMessageData.threadKey.otherUserFbId).toString(),
56
+ messageID: d.deltaRecallMessageData.messageID,
57
+ senderID: d.deltaRecallMessageData.senderID.toString(),
58
+ deletionTimestamp: d.deltaRecallMessageData.deletionTimestamp,
59
+ timestamp: d.deltaRecallMessageData.timestamp
60
+ };
61
+ globalCallback(null, messageUnsend);
62
+ } else if (d.deltaMessageReply) {
63
+ let callbackToReturn;
64
+ try {
65
+ const msg = d.deltaMessageReply.message;
66
+ if (!msg || !msg.messageMetadata) {
67
+ logger("parseDelta: deltaMessageReply.message or messageMetadata is missing", "warn");
68
+ return;
69
+ }
70
+ const mdata = msg === undefined ? [] : msg.data === undefined ? [] : msg.data.prng === undefined ? [] : JSON.parse(msg.data.prng);
71
+ const m_id = mdata.map(u => u.i);
72
+ const m_offset = mdata.map(u => u.o);
73
+ const m_length = mdata.map(u => u.l);
74
+ const mentions = {};
75
+ for (let i = 0; i < m_id.length; i++) {
76
+ mentions[m_id[i]] = (msg.body || "").substring(m_offset[i], m_offset[i] + m_length[i]);
77
+ }
78
+ const msgMetadata = msg.messageMetadata;
79
+ const threadKey = msgMetadata.threadKey || {};
80
+ callbackToReturn = {
81
+ type: "message_reply",
82
+ threadID: (threadKey.threadFbId ? threadKey.threadFbId : threadKey.otherUserFbId || "").toString(),
83
+ messageID: msgMetadata.messageId || "",
84
+ senderID: (msgMetadata.actorFbId || "").toString(),
85
+ attachments: (msg.attachments || []).map(att => {
86
+ try {
87
+ const mercury = JSON.parse(att.mercuryJSON);
88
+ Object.assign(att, mercury);
89
+ } catch (ex) {
90
+ // Ignore parsing errors
91
+ }
92
+ return att;
93
+ }).map(att => {
94
+ let x;
95
+ try {
96
+ x = _formatAttachment(att);
97
+ } catch (ex) {
98
+ x = att;
99
+ x.error = ex;
100
+ x.type = "unknown";
101
+ }
102
+ return x;
103
+ }),
104
+ args: (msg.body || "").trim().split(/\s+/),
105
+ body: msg.body || "",
106
+ isGroup: !!threadKey.threadFbId,
107
+ mentions,
108
+ timestamp: parseInt(msgMetadata.timestamp || 0),
109
+ participantIDs: (msg.participants || []).map(e => e.toString())
110
+ };
111
+ if (d.deltaMessageReply.repliedToMessage) {
112
+ try {
113
+ const repliedTo = d.deltaMessageReply.repliedToMessage;
114
+ const mdata2 = repliedTo === undefined ? [] : repliedTo.data === undefined ? [] : repliedTo.data.prng === undefined ? [] : JSON.parse(repliedTo.data.prng);
115
+ const m_id2 = mdata2.map(u => u.i);
116
+ const m_offset2 = mdata2.map(u => u.o);
117
+ const m_length2 = mdata2.map(u => u.l);
118
+ const rmentions = {};
119
+ for (let i = 0; i < m_id2.length; i++) {
120
+ rmentions[m_id2[i]] = (repliedTo.body || "").substring(m_offset2[i], m_offset2[i] + m_length2[i]);
121
+ }
122
+ const msgMetadata = repliedTo.messageMetadata;
123
+ if (msgMetadata && msgMetadata.threadKey) {
124
+ callbackToReturn.messageReply = {
125
+ threadID: (msgMetadata.threadKey.threadFbId ? msgMetadata.threadKey.threadFbId : msgMetadata.threadKey.otherUserFbId || "").toString(),
126
+ messageID: msgMetadata.messageId || "",
127
+ senderID: (msgMetadata.actorFbId || "").toString(),
128
+ attachments: (repliedTo.attachments || []).map(att => {
129
+ let mercury;
130
+ try {
131
+ mercury = JSON.parse(att.mercuryJSON);
132
+ Object.assign(att, mercury);
133
+ } catch (ex) {
134
+ mercury = {};
135
+ }
136
+ return att;
137
+ }).map(att => {
138
+ let x;
139
+ try {
140
+ x = _formatAttachment(att);
141
+ } catch (ex) {
142
+ x = att;
143
+ x.error = ex;
144
+ x.type = "unknown";
145
+ }
146
+ return x;
147
+ }),
148
+ args: (repliedTo.body || "").trim().split(/\s+/),
149
+ body: repliedTo.body || "",
150
+ isGroup: !!msgMetadata.threadKey.threadFbId,
151
+ mentions: rmentions,
152
+ timestamp: parseInt(msgMetadata.timestamp || 0),
153
+ participantIDs: (repliedTo.participants || []).map(e => e.toString())
154
+ };
155
+ }
156
+ } catch (err) {
157
+ const errMsg = err && err.message ? err.message : String(err || "Unknown error");
158
+ logger(`parseDelta message_reply repliedToMessage error: ${errMsg}`, "warn");
159
+ }
160
+ } else if (d.deltaMessageReply.replyToMessageId) {
161
+ return defaultFuncs.post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, {
162
+ av: ctx.globalOptions.pageID,
163
+ queries: JSON.stringify({
164
+ o0: {
165
+ doc_id: "2848441488556444",
166
+ query_params: {
167
+ thread_and_message_id: {
168
+ thread_id: callbackToReturn.threadID,
169
+ message_id: d.deltaMessageReply.replyToMessageId.id
170
+ }
171
+ }
172
+ }
173
+ })
174
+ }).then(parseAndCheckLogin(ctx, defaultFuncs)).then(resData => {
175
+ if (resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
176
+ if (resData[resData.length - 1].successful_results === 0) throw { error: "forcedFetch: there was no successful_results", res: resData };
177
+ const fetchData = resData[0].o0.data.message;
178
+ const mobj = {};
179
+ for (const n in fetchData.message.ranges) {
180
+ mobj[fetchData.message.ranges[n].entity.id] = (fetchData.message.text || "").substr(fetchData.message.ranges[n].offset, fetchData.message.ranges[n].length);
181
+ }
182
+ callbackToReturn.messageReply = {
183
+ type: "Message",
184
+ threadID: callbackToReturn.threadID,
185
+ messageID: fetchData.message_id,
186
+ senderID: fetchData.message_sender.id.toString(),
187
+ attachments: fetchData.message.blob_attachment.map(att => _formatAttachment({ blob_attachment: att })),
188
+ args: (fetchData.message.text || "").trim().split(/\s+/) || [],
189
+ body: fetchData.message.text || "",
190
+ isGroup: callbackToReturn.isGroup,
191
+ mentions: mobj,
192
+ timestamp: parseInt(fetchData.timestamp_precise)
193
+ };
194
+ }).catch(err => {
195
+ const errMsg = err && err.message ? err.message : String(err || "Unknown error");
196
+ logger(`parseDelta message_reply fetch error: ${errMsg}`, "warn");
197
+ }).finally(() => {
198
+ if (callbackToReturn) {
199
+ if (ctx.globalOptions.autoMarkDelivery) {
200
+ markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
201
+ }
202
+ if (!ctx.globalOptions.selfListen && callbackToReturn.senderID === ctx.userID) return;
203
+ globalCallback(null, callbackToReturn);
204
+ }
205
+ });
206
+ } else {
207
+ if (callbackToReturn) callbackToReturn.delta = d;
208
+ }
209
+ } catch (err) {
210
+ const errMsg = err && err.message ? err.message : String(err || "Unknown error");
211
+ logger(`parseDelta message_reply error: ${errMsg}`, "warn");
212
+ return;
213
+ }
214
+ if (callbackToReturn) {
215
+ if (ctx.globalOptions.autoMarkDelivery) {
216
+ markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
217
+ }
218
+ if (!ctx.globalOptions.selfListen && callbackToReturn.senderID === ctx.userID) return;
219
+ globalCallback(null, callbackToReturn);
220
+ }
221
+ }
222
+ }
223
+ return;
224
+ }
225
+ }
226
+ switch (delta.class) {
227
+ case "ReadReceipt": {
228
+ let fmtMsg;
229
+ try {
230
+ fmtMsg = formatDeltaReadReceipt(delta);
231
+ } catch (err) {
232
+ return;
233
+ }
234
+ globalCallback(null, fmtMsg);
235
+ break;
236
+ }
237
+ case "AdminTextMessage": {
238
+ switch (delta.type) {
239
+ case "instant_game_dynamic_custom_update":
240
+ case "accept_pending_thread":
241
+ case "confirm_friend_request":
242
+ case "shared_album_delete":
243
+ case "shared_album_addition":
244
+ case "pin_messages_v2":
245
+ case "unpin_messages_v2":
246
+ case "change_thread_theme":
247
+ case "change_thread_nickname":
248
+ case "change_thread_icon":
249
+ case "change_thread_quick_reaction":
250
+ case "change_thread_admins":
251
+ case "group_poll":
252
+ case "joinable_group_link_mode_change":
253
+ case "magic_words":
254
+ case "change_thread_approval_mode":
255
+ case "messenger_call_log":
256
+ case "participant_joined_group_call":
257
+ case "rtc_call_log":
258
+ case "update_vote": {
259
+ let fmtMsg;
260
+ try {
261
+ fmtMsg = formatDeltaEvent(delta);
262
+ } catch (err) {
263
+ return;
264
+ }
265
+ globalCallback(null, fmtMsg);
266
+ break;
267
+ }
268
+ }
269
+ break;
270
+ }
271
+ case "ForcedFetch": {
272
+ if (!delta.threadKey) return;
273
+ const mid = delta.messageId;
274
+ const tid = delta.threadKey.threadFbId;
275
+ if (mid && tid) {
276
+ const form = {
277
+ av: ctx.globalOptions.pageID,
278
+ queries: JSON.stringify({
279
+ o0: {
280
+ doc_id: "2848441488556444",
281
+ query_params: {
282
+ thread_and_message_id: {
283
+ thread_id: tid.toString(),
284
+ message_id: mid
285
+ }
286
+ }
287
+ }
288
+ })
289
+ };
290
+ defaultFuncs.post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form).then(parseAndCheckLogin(ctx, defaultFuncs)).then(resData => {
291
+ if (resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
292
+ if (resData[resData.length - 1].successful_results === 0) throw { error: "forcedFetch: there was no successful_results", res: resData };
293
+ const fetchData = resData[0].o0.data.message;
294
+ if (getType(fetchData) === "Object") {
295
+ switch (fetchData.__typename) {
296
+ case "ThreadImageMessage":
297
+ if ((!ctx.globalOptions.selfListen && fetchData.message_sender.id.toString() === ctx.userID) || !ctx.loggedIn) {} else {
298
+ globalCallback(null, {
299
+ type: "event",
300
+ threadID: formatID(tid.toString()),
301
+ logMessageType: "log:thread-image",
302
+ logMessageData: {
303
+ image: {
304
+ attachmentID: fetchData.image_with_metadata && fetchData.image_with_metadata.legacy_attachment_id,
305
+ width: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.x,
306
+ height: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.y,
307
+ url: fetchData.image_with_metadata && fetchData.image_with_metadata.preview.uri
308
+ }
309
+ },
310
+ logMessageBody: fetchData.snippet,
311
+ timestamp: fetchData.timestamp_precise,
312
+ author: fetchData.message_sender.id
313
+ });
314
+ }
315
+ break;
316
+ case "UserMessage": {
317
+ const event = {
318
+ type: "message",
319
+ senderID: formatID(fetchData.message_sender.id),
320
+ body: fetchData.message.text || "",
321
+ threadID: formatID(tid.toString()),
322
+ messageID: fetchData.message_id,
323
+ attachments: [
324
+ {
325
+ type: "share",
326
+ ID: fetchData.extensible_attachment.legacy_attachment_id,
327
+ url: fetchData.extensible_attachment.story_attachment.url,
328
+ title: fetchData.extensible_attachment.story_attachment.title_with_entities.text,
329
+ description: fetchData.extensible_attachment.story_attachment.description.text,
330
+ source: fetchData.extensible_attachment.story_attachment.source,
331
+ image: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).uri,
332
+ width: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).width,
333
+ height: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).height,
334
+ playable: ((fetchData.extensible_attachment.story_attachment.media || {}).is_playable || false),
335
+ duration: ((fetchData.extensible_attachment.story_attachment.media || {}).playable_duration_in_ms || 0),
336
+ subattachments: fetchData.extensible_attachment.subattachments,
337
+ properties: fetchData.extensible_attachment.story_attachment.properties
338
+ }
339
+ ],
340
+ mentions: {},
341
+ timestamp: parseInt(fetchData.timestamp_precise),
342
+ isGroup: fetchData.message_sender.id !== tid.toString()
343
+ };
344
+ globalCallback(null, event);
345
+ break;
346
+ }
347
+ default:
348
+ break;
349
+ }
350
+ } else {
351
+ return;
352
+ }
353
+ }).catch(err => {
354
+ const errMsg = err && err.message ? err.message : String(err || "Unknown error");
355
+ logger(`parseDelta ForcedFetch error: ${errMsg}`, "warn");
356
+ });
357
+ }
358
+ break;
359
+ }
360
+ case "ThreadName":
361
+ case "ParticipantsAddedToGroupThread":
362
+ case "ParticipantLeftGroupThread": {
363
+ let formattedEvent;
364
+ try {
365
+ formattedEvent = formatDeltaEvent(delta);
366
+ } catch (err) {
367
+ return;
368
+ }
369
+ if (!ctx.globalOptions.selfListen && formattedEvent.author.toString() === ctx.userID) return;
370
+ if (!ctx.loggedIn) return;
371
+ globalCallback(null, formattedEvent);
372
+ break;
373
+ }
374
+ case "NewMessage": {
375
+ const hasLiveLocation = d => {
376
+ const attachment = d.attachments && d.attachments[0] && d.attachments[0].mercury && d.attachments[0].mercury.extensible_attachment;
377
+ const storyAttachment = attachment && attachment.story_attachment;
378
+ return storyAttachment && storyAttachment.style_list && storyAttachment.style_list.includes("message_live_location");
379
+ };
380
+ if (delta.attachments && delta.attachments.length === 1 && hasLiveLocation(delta)) {
381
+ delta.class = "UserLocation";
382
+ try {
383
+ const fmtMsg = formatDeltaEvent(delta);
384
+ globalCallback(null, fmtMsg);
385
+ } catch (err) {}
386
+ }
387
+ break;
388
+ }
389
+ }
390
+ };
391
+ };
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+
3
+ const { Writable, PassThrough } = require("stream");
4
+ const Duplexify = require("duplexify");
5
+
6
+ function buildProxy() {
7
+ let target = null;
8
+ let ended = false;
9
+ const Proxy = new Writable({
10
+ autoDestroy: true,
11
+ write(chunk, enc, cb) {
12
+ if (ended || this.destroyed) return cb();
13
+ const ws = target;
14
+ if (ws && ws.readyState === 1) {
15
+ try {
16
+ ws.send(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk), cb);
17
+ } catch (e) {
18
+ cb(e);
19
+ }
20
+ } else cb();
21
+ },
22
+ writev(chunks, cb) {
23
+ if (ended || this.destroyed) return cb();
24
+ const ws = target;
25
+ if (!ws || ws.readyState !== 1) return cb();
26
+ try {
27
+ for (const { chunk } of chunks) {
28
+ ws.send(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
29
+ }
30
+ cb();
31
+ } catch (e) {
32
+ cb(e);
33
+ }
34
+ },
35
+ final(cb) {
36
+ ended = true;
37
+ const ws = target;
38
+ target = null;
39
+ if (ws && (ws.readyState === 0 || ws.readyState === 1)) {
40
+ try {
41
+ typeof ws.terminate === "function" ? ws.terminate() : ws.close();
42
+ } catch { }
43
+ }
44
+ cb();
45
+ }
46
+ });
47
+ Proxy.setTarget = ws => {
48
+ if (ended) return;
49
+ target = ws;
50
+ };
51
+ Proxy.hardEnd = () => {
52
+ ended = true;
53
+ target = null;
54
+ };
55
+ return Proxy;
56
+ }
57
+
58
+ function buildStream(options, WebSocket, Proxy) {
59
+ const readable = new PassThrough();
60
+ const Stream = Duplexify(undefined, undefined, Object.assign({ end: false, autoDestroy: true }, options));
61
+ const NoopWritable = new Writable({ write(_c, _e, cb) { cb(); } });
62
+ let ws = WebSocket;
63
+ let pingTimer = null;
64
+ let livenessTimer = null;
65
+ let lastActivity = Date.now();
66
+ let attached = false;
67
+ let style = "prop";
68
+ let closed = false;
69
+
70
+ const toBuffer = d => {
71
+ if (Buffer.isBuffer(d)) return d;
72
+ if (d instanceof ArrayBuffer) return Buffer.from(d);
73
+ if (ArrayBuffer.isView(d)) return Buffer.from(d.buffer, d.byteOffset, d.byteLength);
74
+ return Buffer.from(String(d));
75
+ };
76
+
77
+ const swapToNoopWritable = () => {
78
+ try { Stream.setWritable(NoopWritable); } catch { }
79
+ };
80
+
81
+ const onOpen = () => {
82
+ if (closed) return;
83
+ Proxy.setTarget(ws);
84
+ Stream.setWritable(Proxy);
85
+ Stream.setReadable(readable);
86
+ Stream.emit("connect");
87
+ lastActivity = Date.now();
88
+ clearInterval(pingTimer);
89
+ clearInterval(livenessTimer);
90
+ pingTimer = setInterval(() => {
91
+ if (!ws || ws.readyState !== 1) return;
92
+ if (typeof ws.ping === "function") {
93
+ try { ws.ping(); } catch { }
94
+ } else {
95
+ try { ws.send("ping"); } catch { }
96
+ }
97
+ }, 30000);
98
+ livenessTimer = setInterval(() => {
99
+ if (!ws || ws.readyState !== 1) return;
100
+ if (Date.now() - lastActivity > 65000) {
101
+ try { typeof ws.terminate === "function" ? ws.terminate() : ws.close(); } catch { }
102
+ }
103
+ }, 10000);
104
+ };
105
+
106
+ const onMessage = data => {
107
+ lastActivity = Date.now();
108
+ readable.write(toBuffer(style === "dom" && data && data.data !== undefined ? data.data : data));
109
+ };
110
+
111
+ const onPong = () => {
112
+ lastActivity = Date.now();
113
+ };
114
+
115
+ const cleanup = () => {
116
+ if (closed) return;
117
+ closed = true;
118
+ clearInterval(pingTimer);
119
+ clearInterval(livenessTimer);
120
+ pingTimer = null;
121
+ livenessTimer = null;
122
+ Proxy.hardEnd();
123
+ swapToNoopWritable();
124
+ if (ws) {
125
+ detach(ws);
126
+ try {
127
+ if (ws.readyState === 1) {
128
+ typeof ws.terminate === "function" ? ws.terminate() : ws.close();
129
+ }
130
+ } catch { }
131
+ ws = null;
132
+ }
133
+ readable.end();
134
+ };
135
+
136
+ const onError = err => {
137
+ cleanup();
138
+ Stream.destroy(err);
139
+ };
140
+
141
+ const onClose = () => {
142
+ cleanup();
143
+ Stream.end();
144
+ if (!Stream.destroyed) Stream.destroy();
145
+ };
146
+
147
+ const attach = w => {
148
+ if (attached || !w) return;
149
+ attached = true;
150
+ if (typeof w.on === "function" && typeof w.off === "function") {
151
+ style = "node";
152
+ w.on("open", onOpen);
153
+ w.on("message", onMessage);
154
+ w.on("error", onError);
155
+ w.on("close", onClose);
156
+ if (typeof w.on === "function") w.on("pong", onPong);
157
+ } else if (typeof w.addEventListener === "function" && typeof w.removeEventListener === "function") {
158
+ style = "dom";
159
+ w.addEventListener("open", onOpen);
160
+ w.addEventListener("message", onMessage);
161
+ w.addEventListener("error", onError);
162
+ w.addEventListener("close", onClose);
163
+ } else {
164
+ style = "prop";
165
+ w.onopen = onOpen;
166
+ w.onmessage = onMessage;
167
+ w.onerror = onError;
168
+ w.onclose = onClose;
169
+ }
170
+ };
171
+
172
+ const detach = w => {
173
+ if (!attached || !w) return;
174
+ attached = false;
175
+ if (style === "node" && typeof w.off === "function") {
176
+ w.off("open", onOpen);
177
+ w.off("message", onMessage);
178
+ w.off("error", onError);
179
+ w.off("close", onClose);
180
+ if (typeof w.off === "function") w.off("pong", onPong);
181
+ } else if (style === "dom" && typeof w.removeEventListener === "function") {
182
+ w.removeEventListener("open", onOpen);
183
+ w.removeEventListener("message", onMessage);
184
+ w.removeEventListener("error", onError);
185
+ w.removeEventListener("close", onClose);
186
+ } else {
187
+ w.onopen = null;
188
+ w.onmessage = null;
189
+ w.onerror = null;
190
+ w.onclose = null;
191
+ }
192
+ };
193
+
194
+ attach(ws);
195
+ if (ws && ws.readyState === 1) onOpen();
196
+
197
+ Stream.on("prefinish", swapToNoopWritable);
198
+ Stream.on("finish", cleanup);
199
+ Stream.on("close", cleanup);
200
+ Proxy.on("close", swapToNoopWritable);
201
+
202
+ return Stream;
203
+ }
204
+
205
+ module.exports = {
206
+ buildProxy,
207
+ buildStream
208
+ };