@eryxenx/fca 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/CHANGELOG.md +325 -0
  2. package/DOCS.md +2712 -0
  3. package/README.md +455 -0
  4. package/func/checkUpdate.js +7 -0
  5. package/func/logAdapter.js +33 -0
  6. package/func/logger.js +48 -0
  7. package/index.d.ts +751 -0
  8. package/index.js +22 -0
  9. package/module/config.js +40 -0
  10. package/module/login.js +133 -0
  11. package/module/loginHelper.js +1365 -0
  12. package/module/options.js +44 -0
  13. package/package.json +98 -0
  14. package/src/api/action/addExternalModule.js +25 -0
  15. package/src/api/action/changeAvatar.js +137 -0
  16. package/src/api/action/changeBio.js +75 -0
  17. package/src/api/action/enableAutoSaveAppState.js +73 -0
  18. package/src/api/action/getCurrentUserID.js +7 -0
  19. package/src/api/action/handleFriendRequest.js +57 -0
  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 +126 -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 +91 -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 +111 -0
  35. package/src/api/messaging/createNewGroup.js +88 -0
  36. package/src/api/messaging/createPoll.js +46 -0
  37. package/src/api/messaging/createThemeAI.js +98 -0
  38. package/src/api/messaging/deleteMessage.js +136 -0
  39. package/src/api/messaging/deleteThread.js +56 -0
  40. package/src/api/messaging/editMessage.js +105 -0
  41. package/src/api/messaging/forwardAttachment.js +57 -0
  42. package/src/api/messaging/forwardMessage.js +134 -0
  43. package/src/api/messaging/getEmojiUrl.js +29 -0
  44. package/src/api/messaging/getFriendsList.js +82 -0
  45. package/src/api/messaging/getMessage.js +829 -0
  46. package/src/api/messaging/getThemePictures.js +62 -0
  47. package/src/api/messaging/handleMessageRequest.js +65 -0
  48. package/src/api/messaging/markAsDelivered.js +57 -0
  49. package/src/api/messaging/markAsRead.js +88 -0
  50. package/src/api/messaging/markAsReadAll.js +56 -0
  51. package/src/api/messaging/markAsSeen.js +68 -0
  52. package/src/api/messaging/muteThread.js +50 -0
  53. package/src/api/messaging/pinMessage.js +115 -0
  54. package/src/api/messaging/removeUserFromGroup.js +62 -0
  55. package/src/api/messaging/resolvePhotoUrl.js +43 -0
  56. package/src/api/messaging/scheduler.js +264 -0
  57. package/src/api/messaging/searchForThread.js +53 -0
  58. package/src/api/messaging/sendBroadcast.js +93 -0
  59. package/src/api/messaging/sendMessage.js +269 -0
  60. package/src/api/messaging/sendTypingIndicator.js +90 -0
  61. package/src/api/messaging/sessionGuard.js +130 -0
  62. package/src/api/messaging/setMessageReaction.js +109 -0
  63. package/src/api/messaging/setTitle.js +124 -0
  64. package/src/api/messaging/shareContact.js +98 -0
  65. package/src/api/messaging/threadColors.js +128 -0
  66. package/src/api/messaging/unsendMessage.js +105 -0
  67. package/src/api/messaging/uploadAttachment.js +492 -0
  68. package/src/api/socket/OldMessage.js +186 -0
  69. package/src/api/socket/core/connectMqtt.js +269 -0
  70. package/src/api/socket/core/emitAuth.js +103 -0
  71. package/src/api/socket/core/getSeqID.js +321 -0
  72. package/src/api/socket/core/getTaskResponseData.js +25 -0
  73. package/src/api/socket/core/parseDelta.js +387 -0
  74. package/src/api/socket/detail/buildStream.js +215 -0
  75. package/src/api/socket/detail/constants.js +28 -0
  76. package/src/api/socket/e2ee/crypto.js +173 -0
  77. package/src/api/socket/e2ee/index.js +925 -0
  78. package/src/api/socket/e2ee/localMediaServer.js +59 -0
  79. package/src/api/socket/e2ee/mediaDecode.js +155 -0
  80. package/src/api/socket/e2ee/native/NOTICE.md +29 -0
  81. package/src/api/socket/e2ee/native/build/messagix.dll +0 -0
  82. package/src/api/socket/e2ee/native/build/messagix.so +0 -0
  83. package/src/api/socket/e2ee/native/lib/index.mjs +1426 -0
  84. package/src/api/socket/e2ee/native/nativeMediaBridge.js +233 -0
  85. package/src/api/socket/e2ee/proto/ArmadilloApplication.proto +281 -0
  86. package/src/api/socket/e2ee/proto/ArmadilloICDC.proto +14 -0
  87. package/src/api/socket/e2ee/proto/ConsumerApplication.proto +232 -0
  88. package/src/api/socket/e2ee/proto/MessageApplication.proto +82 -0
  89. package/src/api/socket/e2ee/proto/MessageTransport.proto +77 -0
  90. package/src/api/socket/e2ee/proto/WACommon.proto +66 -0
  91. package/src/api/socket/e2ee/proto/WAMediaTransport.proto +176 -0
  92. package/src/api/socket/e2ee/proto/proto-writer.ts +76 -0
  93. package/src/api/socket/e2ee/protocol.js +196 -0
  94. package/src/api/socket/e2ee/ratchet.js +219 -0
  95. package/src/api/socket/e2ee/store.js +182 -0
  96. package/src/api/socket/e2ee/vendor/fme/dist/index.cjs +6477 -0
  97. package/src/api/socket/e2ee/vendor/fme/proto/ArmadilloApplication.proto +281 -0
  98. package/src/api/socket/e2ee/vendor/fme/proto/ArmadilloICDC.proto +14 -0
  99. package/src/api/socket/e2ee/vendor/fme/proto/ConsumerApplication.proto +232 -0
  100. package/src/api/socket/e2ee/vendor/fme/proto/MessageApplication.proto +82 -0
  101. package/src/api/socket/e2ee/vendor/fme/proto/MessageTransport.proto +77 -0
  102. package/src/api/socket/e2ee/vendor/fme/proto/WACommon.proto +66 -0
  103. package/src/api/socket/e2ee/vendor/fme/proto/WAMediaTransport.proto +176 -0
  104. package/src/api/socket/listenE2EE.js +75 -0
  105. package/src/api/socket/listenMqtt.js +436 -0
  106. package/src/api/socket/middleware/index.js +216 -0
  107. package/src/api/socket/sendMessage.js +314 -0
  108. package/src/api/socket/sendMessageMqtt.js +69 -0
  109. package/src/api/threads/getThreadHistory.js +664 -0
  110. package/src/api/threads/getThreadInfo.js +329 -0
  111. package/src/api/threads/getThreadList.js +293 -0
  112. package/src/api/threads/getThreadPictures.js +78 -0
  113. package/src/api/users/getUserID.js +65 -0
  114. package/src/api/users/getUserInfo.js +402 -0
  115. package/src/api/users/getUserInfoV2.js +134 -0
  116. package/src/app/MessengerBot.js +209 -0
  117. package/src/app/MessengerContext.js +32 -0
  118. package/src/app/createFcaClient.js +136 -0
  119. package/src/app/threadInfoRealtimeSync.js +284 -0
  120. package/src/core/sendReqMqtt.js +96 -0
  121. package/src/database/helpers.js +53 -0
  122. package/src/database/models/index.js +88 -0
  123. package/src/database/models/thread.js +50 -0
  124. package/src/database/models/user.js +46 -0
  125. package/src/database/threadData.js +94 -0
  126. package/src/database/userData.js +98 -0
  127. package/src/remote/remoteClient.js +123 -0
  128. package/src/utils/broadcast.js +51 -0
  129. package/src/utils/client.js +10 -0
  130. package/src/utils/constants.js +23 -0
  131. package/src/utils/cookies.js +68 -0
  132. package/src/utils/format/attachment.js +357 -0
  133. package/src/utils/format/cookie.js +9 -0
  134. package/src/utils/format/date.js +50 -0
  135. package/src/utils/format/decode.js +44 -0
  136. package/src/utils/format/delta.js +194 -0
  137. package/src/utils/format/ids.js +64 -0
  138. package/src/utils/format/index.js +64 -0
  139. package/src/utils/format/message.js +88 -0
  140. package/src/utils/format/presence.js +132 -0
  141. package/src/utils/format/readTyp.js +44 -0
  142. package/src/utils/format/thread.js +42 -0
  143. package/src/utils/format/utils.js +141 -0
  144. package/src/utils/headers.js +128 -0
  145. package/src/utils/loginParser/autoLogin.js +125 -0
  146. package/src/utils/loginParser/helpers.js +43 -0
  147. package/src/utils/loginParser/index.js +10 -0
  148. package/src/utils/loginParser/parseAndCheckLogin.js +220 -0
  149. package/src/utils/loginParser/textUtils.js +28 -0
  150. package/src/utils/nexca-logger.js +144 -0
  151. package/src/utils/nexca-utils.js +686 -0
  152. package/src/utils/request/client.js +26 -0
  153. package/src/utils/request/config.js +23 -0
  154. package/src/utils/request/defaults.js +46 -0
  155. package/src/utils/request/helpers.js +46 -0
  156. package/src/utils/request/index.js +17 -0
  157. package/src/utils/request/methods.js +163 -0
  158. package/src/utils/request/proxy.js +21 -0
  159. package/src/utils/request/retry.js +77 -0
  160. package/src/utils/request/sanitize.js +49 -0
  161. package/src/utils/versionCheck.js +47 -0
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+
3
+ var utils = require("../../utils/nexca-utils");
4
+ var logger = require("../../utils/nexca-logger");
5
+ var bluebird = require("bluebird");
6
+
7
+ var allowedProperties = {
8
+ attachment: true, url: true, sticker: true, emoji: true,
9
+ emojiSize: true, body: true, mentions: true, location: true,
10
+ };
11
+
12
+ module.exports = function (defaultFuncs, api, ctx) {
13
+
14
+ function uploadAttachment(attachments, callback) {
15
+ var uploads = [];
16
+ for (var i = 0; i < attachments.length; i++) {
17
+ if (!utils.isReadableStream(attachments[i])) {
18
+ return callback({ error: "Attachment must be a readable stream." });
19
+ }
20
+ uploads.push(
21
+ defaultFuncs.postFormData("https://upload.facebook.com/ajax/mercury/upload.php", ctx.jar, {
22
+ upload_1024: attachments[i],
23
+ voice_clip: "true"
24
+ })
25
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
26
+ .then(resData => {
27
+ if (resData.error) throw resData;
28
+ return resData.payload.metadata[0];
29
+ })
30
+ );
31
+ }
32
+ bluebird.all(uploads)
33
+ .then(resData => callback(null, resData))
34
+ .catch(err => { logger.error("OldMessage.upload", err); callback(err); });
35
+ }
36
+
37
+ function getUrl(url, callback) {
38
+ defaultFuncs.post("https://www.facebook.com/message_share_attachment/fromURI/", ctx.jar, {
39
+ image_height: 960, image_width: 960, uri: url
40
+ })
41
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
42
+ .then(resData => {
43
+ if (resData.error) return callback(resData);
44
+ if (!resData.payload) return callback({ error: "Invalid URL" });
45
+ callback(null, resData.payload.share_data.share_params);
46
+ })
47
+ .catch(err => { logger.error("OldMessage.getUrl", err); callback(err); });
48
+ }
49
+
50
+ function sendContent(form, threadID, isSingleUser, messageAndOTID, callback) {
51
+ if (utils.getType(threadID) === "Array") {
52
+ for (var i = 0; i < threadID.length; i++) form["specific_to_list[" + i + "]"] = "fbid:" + threadID[i];
53
+ form["specific_to_list[" + threadID.length + "]"] = "fbid:" + ctx.userID;
54
+ form["client_thread_id"] = "root:" + messageAndOTID;
55
+ } else {
56
+ if (isSingleUser) {
57
+ form["specific_to_list[0]"] = "fbid:" + threadID;
58
+ form["specific_to_list[1]"] = "fbid:" + ctx.userID;
59
+ form["other_user_fbid"] = threadID;
60
+ } else {
61
+ form["thread_fbid"] = threadID;
62
+ }
63
+ }
64
+
65
+ if (ctx.globalOptions.pageID) {
66
+ form["author"] = "fbid:" + ctx.globalOptions.pageID;
67
+ form["specific_to_list[1]"] = "fbid:" + ctx.globalOptions.pageID;
68
+ form["creator_info[creatorID]"] = ctx.userID;
69
+ form["creator_info[creatorType]"] = "direct_admin";
70
+ }
71
+
72
+ defaultFuncs.post("https://www.facebook.com/messaging/send/", ctx.jar, form)
73
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
74
+ .then(resData => {
75
+ if (!resData.payload) throw resData;
76
+ var messageID = (resData.payload.actions && resData.payload.actions[0] &&
77
+ resData.payload.actions[0].message_id) || null;
78
+ var threadID2 = resData.payload.thread_fbid ||
79
+ (resData.payload.actions && resData.payload.actions[0] && resData.payload.actions[0].thread_fbid);
80
+ callback(null, {
81
+ threadID: threadID2 ? String(threadID2) : String(threadID),
82
+ messageID: messageID ? String(messageID) : null,
83
+ timestamp: resData.payload.timestamp
84
+ });
85
+ })
86
+ .catch(err => { logger.error("OldMessage.send", err); callback(err); });
87
+ }
88
+
89
+ function send(form, threadID, isSingleUser, callback) {
90
+ var messageAndOTID = utils.generateOfflineThreadingID();
91
+ form.client = "mercury";
92
+ form.action_type = "ma-type:user-generated-message";
93
+ form.timestamp = Date.now();
94
+ form.timestamp_absolute = "Today";
95
+ form.timestamp_relative = "12:00";
96
+ form.timestamp_time_passed = "0";
97
+ form.is_unread = false;
98
+ form.is_cleared = false;
99
+ form.is_forward = false;
100
+ form.is_filtered_content = false;
101
+ form.is_filtered_content_bec = false;
102
+ form.is_filtered_content_account = false;
103
+ form.is_filtered_content_quasar = false;
104
+ form.is_filtered_content_invalid_app = false;
105
+ form.is_not_supported = false;
106
+ form.message_id = messageAndOTID;
107
+ form.offline_threading_id = messageAndOTID;
108
+ form.ephemeral_ttl_mode = 0;
109
+ form.manual_retry_cnt = 0;
110
+ form.has_attachment = false;
111
+ form.signatureID = utils.getGUID().replace(/-/g, "").slice(0, 8);
112
+ sendContent(form, threadID, isSingleUser, messageAndOTID, callback);
113
+ }
114
+
115
+ return function OldMessage(msg, threadID, callback, replyToMessage, isSingleUser) {
116
+ if (typeof msg === "string") msg = { body: msg };
117
+
118
+ if (msg.body && /(https?:\/\/|www\.|t\.me\/|fb\.me\/|youtu\.be\/|facebook\.com\/|youtube\.com\/)/i.test(msg.body)) {
119
+ return require('./sendMessage')(defaultFuncs, api, ctx)(msg, threadID, callback, replyToMessage, false);
120
+ }
121
+
122
+ var resolveFunc = () => { };
123
+ var rejectFunc = () => { };
124
+ var promise = new Promise((res, rej) => { resolveFunc = res; rejectFunc = rej; });
125
+ if (!callback) {
126
+ callback = (err, data) => err ? rejectFunc(err) : resolveFunc(data);
127
+ }
128
+
129
+ for (var key in msg) {
130
+ if (!allowedProperties[key]) {
131
+ return callback({ error: "OldMessage: Unknown property '" + key + "'" });
132
+ }
133
+ }
134
+
135
+ var form = {};
136
+ if (msg.body) form.body = msg.body;
137
+
138
+ if (msg.mentions && Array.isArray(msg.mentions)) {
139
+ form.body = msg.body || "";
140
+ form['profile_to_mention[0]'] = msg.mentions.map(m => m.id).join(",");
141
+ }
142
+
143
+ if (replyToMessage) form.replied_to_message_id = replyToMessage;
144
+
145
+ if (msg.sticker) {
146
+ form["has_attachment"] = true;
147
+ form["sticker_id"] = msg.sticker;
148
+ return send(form, threadID, isSingleUser, callback);
149
+ }
150
+
151
+ if (msg.emoji) {
152
+ form.body = msg.emoji;
153
+ var size = msg.emojiSize || "small";
154
+ form["emoji_size"] = size;
155
+ return send(form, threadID, isSingleUser, callback);
156
+ }
157
+
158
+ if (msg.url) {
159
+ return getUrl(msg.url, (err, shareData) => {
160
+ if (err) return callback(err);
161
+ form["has_attachment"] = true;
162
+ form["image_ids[0]"] = shareData.image_ids && shareData.image_ids[0];
163
+ form["share_params"] = JSON.stringify(shareData);
164
+ send(form, threadID, isSingleUser, callback);
165
+ });
166
+ }
167
+
168
+ if (msg.attachment) {
169
+ var attachments = Array.isArray(msg.attachment) ? msg.attachment : [msg.attachment];
170
+ return uploadAttachment(attachments, (err, uploaded) => {
171
+ if (err) return callback(err);
172
+ form["has_attachment"] = true;
173
+ uploaded.forEach((u, i) => {
174
+ if (u.image_id) form["image_ids[" + i + "]"] = u.image_id;
175
+ else if (u.video_id) form["video_ids[" + i + "]"] = u.video_id;
176
+ else if (u.file_id) form["file_ids[" + i + "]"] = u.file_id;
177
+ else if (u.audio_id) form["audio_ids[" + i + "]"] = u.audio_id;
178
+ });
179
+ send(form, threadID, isSingleUser, callback);
180
+ });
181
+ }
182
+
183
+ send(form, threadID, isSingleUser, callback);
184
+ return promise;
185
+ };
186
+ };
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ /**
3
+ * MQTT/WebSocket listener for Facebook Messenger real-time events.
4
+ * Connects to edge-chat.facebook.com, subscribes to topics, parses deltas and typing/presence.
5
+ */
6
+ const { formatID } = require("../../../utils/format");
7
+
8
+ const DEFAULT_RECONNECT_DELAY_MS = 2000;
9
+ const T_MS_WAIT_TIMEOUT_MS = 5000;
10
+
11
+ module.exports = function createListenMqtt(deps) {
12
+ const { WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy,
13
+ topics, parseDelta, getTaskResponseData, logger, emitAuth
14
+ } = deps;
15
+
16
+ return function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
17
+
18
+ function scheduleReconnect(delayMs) {
19
+ const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || DEFAULT_RECONNECT_DELAY_MS;
20
+ const ms = typeof delayMs === "number" ? delayMs : d;
21
+ if (ctx._reconnectTimer) {
22
+ logger("mqtt reconnect already scheduled", "warn");
23
+ return; // debounce
24
+ }
25
+ if (ctx._ending) {
26
+ logger("mqtt reconnect skipped - ending", "warn");
27
+ return;
28
+ }
29
+ logger(`mqtt will reconnect in ${ms}ms`, "warn");
30
+ ctx._reconnectTimer = setTimeout(() => {
31
+ ctx._reconnectTimer = null;
32
+ if (!ctx._ending) {
33
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
34
+ }
35
+ }, ms);
36
+ }
37
+ function isEndingLikeError(msg) {
38
+ return /No subscription existed|client disconnecting|socket hang up|ECONNRESET/i.test(msg || "");
39
+ }
40
+
41
+ const chatOn = ctx.globalOptions.online;
42
+ const sessionID = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + 1;
43
+ const username = {
44
+ u: ctx.userID, s: sessionID, chat_on: chatOn, fg: false, d: ctx.clientId,
45
+ ct: "websocket", aid: 219994525426954, aids: null, mqtt_sid: "",
46
+ cp: 3, ecp: 10, st: [], pm: [], dc: "", no_auto_fg: true, gas: null, pack: [], p: null, php_override: ""
47
+ };
48
+
49
+ const cookies = api.getCookies();
50
+ let host;
51
+ if (ctx.mqttEndpoint) host = `${ctx.mqttEndpoint}&sid=${sessionID}&cid=${ctx.clientId}`;
52
+ else if (ctx.region) host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLowerCase()}&sid=${sessionID}&cid=${ctx.clientId}`;
53
+ else host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}&cid=${ctx.clientId}`;
54
+
55
+ const options = {
56
+ clientId: "mqttwsclient",
57
+ protocolId: "MQIsdp",
58
+ protocolVersion: 3,
59
+ username: JSON.stringify(username),
60
+ clean: true,
61
+ wsOptions: {
62
+ headers: {
63
+ Cookie: cookies,
64
+ Origin: "https://www.facebook.com",
65
+ "User-Agent": ctx.globalOptions.userAgent || "Mozilla/5.0",
66
+ Referer: "https://www.facebook.com/",
67
+ Host: "edge-chat.facebook.com",
68
+ Connection: "Upgrade",
69
+ Pragma: "no-cache",
70
+ "Cache-Control": "no-cache",
71
+ Upgrade: "websocket",
72
+ "Sec-WebSocket-Version": "13",
73
+ "Accept-Encoding": "gzip, deflate, br",
74
+ "Accept-Language": "vi,en;q=0.9",
75
+ "Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits"
76
+ },
77
+ origin: "https://www.facebook.com",
78
+ protocolVersion: 13,
79
+ binaryType: "arraybuffer"
80
+ },
81
+ keepalive: 30,
82
+ reschedulePings: true,
83
+ reconnectPeriod: 0,
84
+ connectTimeout: 5000
85
+ };
86
+ if (ctx.globalOptions.proxy !== undefined) {
87
+ const agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
88
+ options.wsOptions.agent = agent;
89
+ }
90
+
91
+ ctx.mqttClient = new mqtt.Client(
92
+ () => buildStream(options, new WebSocket(host, options.wsOptions), buildProxy()),
93
+ options
94
+ );
95
+ const mqttClient = ctx.mqttClient;
96
+
97
+ mqttClient.on("error", function (err) {
98
+ const msg = String(err && err.message ? err.message : err || "");
99
+ if ((ctx._ending || ctx._cycling) && /No subscription existed|client disconnecting/i.test(msg)) {
100
+ logger(`mqtt expected during shutdown: ${msg}`, "info");
101
+ return;
102
+ }
103
+
104
+ if (/Invalid header flag bits|must be 0x0 for puback/i.test(msg)) {
105
+ logger(`mqtt puback ignored: ${msg}`, "warn");
106
+ return;
107
+ }
108
+
109
+ if (/Not logged in|Not logged in.|blocked the login|401|403/i.test(msg)) {
110
+ try {
111
+ if (mqttClient && mqttClient.connected) {
112
+ mqttClient.end(true);
113
+ }
114
+ } catch (_) { }
115
+ return emitAuth(ctx, api, globalCallback,
116
+ /blocked/i.test(msg) ? "login_blocked" : "not_logged_in",
117
+ msg
118
+ );
119
+ }
120
+ logger(`mqtt error: ${msg}`, "error");
121
+ try {
122
+ if (mqttClient && mqttClient.connected) {
123
+ mqttClient.end(true);
124
+ }
125
+ } catch (_) { }
126
+ if (ctx._ending || ctx._cycling) return;
127
+
128
+ if (ctx.globalOptions.autoReconnect && !ctx._ending) {
129
+ const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || DEFAULT_RECONNECT_DELAY_MS;
130
+ logger(`mqtt autoReconnect listenMqtt() in ${d}ms`, "warn");
131
+ scheduleReconnect(d);
132
+ } else {
133
+ globalCallback({ type: "stop_listen", error: msg || "Connection refused" }, null);
134
+ }
135
+ });
136
+
137
+ mqttClient.on("connect", function () {
138
+ if (process.env.OnStatus === undefined) {
139
+ logger("fca-unofficial", "info");
140
+ process.env.OnStatus = true;
141
+ }
142
+ ctx._cycling = false;
143
+
144
+ topics.forEach(t => mqttClient.subscribe(t));
145
+
146
+
147
+ const queue = {
148
+ sync_api_version: 11, max_deltas_able_to_process: 100, delta_batch_size: 500,
149
+ encoding: "JSON", entity_fbid: ctx.userID, initial_titan_sequence_id: ctx.lastSeqId, device_params: null
150
+ };
151
+ const topic = ctx.syncToken ? "/messenger_sync_get_diffs" : "/messenger_sync_create_queue";
152
+ if (ctx.syncToken) { queue.last_seq_id = ctx.lastSeqId; queue.sync_token = ctx.syncToken; }
153
+ mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
154
+ mqttClient.publish("/foreground_state", JSON.stringify({ foreground: chatOn }), { qos: 1 });
155
+ mqttClient.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 });
156
+ const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || DEFAULT_RECONNECT_DELAY_MS;
157
+ let rTimeout = setTimeout(function () {
158
+ rTimeout = null;
159
+ if (ctx._ending) {
160
+ logger("mqtt t_ms timeout skipped - ending", "warn");
161
+ return;
162
+ }
163
+ logger(`mqtt t_ms timeout, cycling in ${d}ms`, "warn");
164
+ try {
165
+ if (mqttClient && mqttClient.connected) {
166
+ mqttClient.end(true);
167
+ }
168
+ } catch (_) { }
169
+ scheduleReconnect(d);
170
+ }, T_MS_WAIT_TIMEOUT_MS);
171
+
172
+ // Store timeout reference for cleanup
173
+ ctx._rTimeout = rTimeout;
174
+
175
+ ctx.tmsWait = function () {
176
+ if (rTimeout) {
177
+ clearTimeout(rTimeout);
178
+ rTimeout = null;
179
+ }
180
+ if (ctx._rTimeout) {
181
+ delete ctx._rTimeout;
182
+ }
183
+ if (ctx.globalOptions.emitReady) globalCallback({ type: "ready", error: null });
184
+ delete ctx.tmsWait;
185
+ };
186
+ });
187
+
188
+ mqttClient.on("message", function (topic, message) {
189
+ if (ctx._ending) return; // Ignore messages if ending
190
+ try {
191
+ let jsonMessage = Buffer.isBuffer(message) ? Buffer.from(message).toString() : message;
192
+ try {
193
+ jsonMessage = JSON.parse(jsonMessage);
194
+ } catch (parseErr) {
195
+ logger(`mqtt message parse error for topic ${topic}: ${parseErr && parseErr.message ? parseErr.message : String(parseErr)}`, "warn");
196
+ jsonMessage = {};
197
+ }
198
+
199
+ if (jsonMessage.type === "jewel_requests_add") {
200
+ globalCallback(null, { type: "friend_request_received", actorFbId: jsonMessage.from.toString(), timestamp: Date.now().toString() });
201
+ } else if (jsonMessage.type === "jewel_requests_remove_old") {
202
+ globalCallback(null, { type: "friend_request_cancel", actorFbId: jsonMessage.from.toString(), timestamp: Date.now().toString() });
203
+ } else if (topic === "/t_ms") {
204
+ if (ctx.tmsWait && typeof ctx.tmsWait == "function") ctx.tmsWait();
205
+ if (jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
206
+ ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
207
+ ctx.syncToken = jsonMessage.syncToken;
208
+ }
209
+ if (jsonMessage.lastIssuedSeqId) ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
210
+ for (const dlt of (jsonMessage.deltas || [])) {
211
+ parseDelta(defaultFuncs, api, ctx, globalCallback, { delta: dlt });
212
+ }
213
+ } else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
214
+ const typ = {
215
+ type: "typ",
216
+ isTyping: !!jsonMessage.state,
217
+ from: jsonMessage.sender_fbid.toString(),
218
+ threadID: formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
219
+ };
220
+ globalCallback(null, typ);
221
+ } else if (topic === "/orca_presence") {
222
+ if (!ctx.globalOptions.updatePresence) {
223
+ for (const data of (jsonMessage.list || [])) {
224
+ const presence = { type: "presence", userID: String(data.u), timestamp: data.l * 1000, statuses: data.p };
225
+ globalCallback(null, presence);
226
+ }
227
+ }
228
+ } else if (topic === "/ls_resp") {
229
+ const parsedPayload = JSON.parse(jsonMessage.payload);
230
+ const reqID = jsonMessage.request_id;
231
+ const tasks = ctx.tasks;
232
+ if (tasks && tasks instanceof Map && tasks.has(reqID)) {
233
+ const taskData = tasks.get(reqID);
234
+ const { type: taskType, callback: taskCallback } = taskData;
235
+ const taskRespData = getTaskResponseData(taskType, parsedPayload);
236
+ if (taskRespData == null) taskCallback("error", null);
237
+ else taskCallback(null, Object.assign({ type: taskType, reqID }, taskRespData));
238
+ }
239
+ }
240
+ } catch (ex) {
241
+ const errMsg = ex && ex.message ? ex.message : String(ex || "Unknown error");
242
+ logger(`mqtt message handler error: ${errMsg}`, "error");
243
+ // Don't crash on message parsing errors, just log and continue
244
+ }
245
+ });
246
+
247
+ mqttClient.on("close", function () {
248
+ if (ctx._ending || ctx._cycling) {
249
+ logger("mqtt close expected", "info");
250
+ return;
251
+ }
252
+ logger("mqtt connection closed", "warn");
253
+ if (ctx.globalOptions.autoReconnect && !ctx._ending) {
254
+ scheduleReconnect();
255
+ }
256
+ });
257
+
258
+ mqttClient.on("disconnect", () => {
259
+ if (ctx._ending || ctx._cycling) {
260
+ logger("mqtt disconnect expected", "info");
261
+ return;
262
+ }
263
+ logger("mqtt disconnected", "warn");
264
+ if (ctx.globalOptions.autoReconnect && !ctx._ending) {
265
+ scheduleReconnect();
266
+ }
267
+ });
268
+ };
269
+ };
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ /**
3
+ * Emits account_inactive to the global callback and cleans up MQTT/timers/scheduler.
4
+ * Used when login is invalid, blocked, or session is lost.
5
+ */
6
+ module.exports = function createEmitAuth({ logger }) {
7
+ return function emitAuth(ctx, api, globalCallback, reason, detail) {
8
+ // Clean up all timers
9
+ try {
10
+ if (ctx._autoCycleTimer) {
11
+ clearInterval(ctx._autoCycleTimer);
12
+ ctx._autoCycleTimer = null;
13
+ }
14
+ } catch (_) { }
15
+ try {
16
+ if (ctx._reconnectTimer) {
17
+ clearTimeout(ctx._reconnectTimer);
18
+ ctx._reconnectTimer = null;
19
+ }
20
+ } catch (_) { }
21
+
22
+ try {
23
+ ctx._ending = true;
24
+ ctx._cycling = false;
25
+ } catch (_) { }
26
+
27
+ // Clean up MQTT client
28
+ try {
29
+ if (ctx.mqttClient) {
30
+ ctx.mqttClient.removeAllListeners();
31
+ if (ctx.mqttClient.connected) {
32
+ ctx.mqttClient.end(true);
33
+ }
34
+ }
35
+ } catch (_) { }
36
+
37
+ ctx.mqttClient = undefined;
38
+ ctx.loggedIn = false;
39
+
40
+ // Clean up timeout references
41
+ try {
42
+ if (ctx._rTimeout) {
43
+ clearTimeout(ctx._rTimeout);
44
+ ctx._rTimeout = null;
45
+ }
46
+ } catch (_) { }
47
+
48
+ // Clean up tasks Map to prevent memory leak
49
+ try {
50
+ if (ctx.tasks && ctx.tasks instanceof Map) {
51
+ ctx.tasks.clear();
52
+ }
53
+ } catch (_) { }
54
+
55
+ // Clean up userInfo intervals
56
+ try {
57
+ if (ctx._userInfoIntervals && Array.isArray(ctx._userInfoIntervals)) {
58
+ ctx._userInfoIntervals.forEach(interval => {
59
+ try {
60
+ clearInterval(interval);
61
+ } catch (_) { }
62
+ });
63
+ ctx._userInfoIntervals = [];
64
+ }
65
+ } catch (_) { }
66
+
67
+ // Clean up autoSave intervals
68
+ try {
69
+ if (ctx._autoSaveInterval && Array.isArray(ctx._autoSaveInterval)) {
70
+ ctx._autoSaveInterval.forEach(interval => {
71
+ try {
72
+ clearInterval(interval);
73
+ } catch (_) { }
74
+ });
75
+ ctx._autoSaveInterval = [];
76
+ }
77
+ } catch (_) { }
78
+
79
+ // Clean up scheduler
80
+ try {
81
+ if (ctx._scheduler && typeof ctx._scheduler.destroy === "function") {
82
+ ctx._scheduler.destroy();
83
+ ctx._scheduler = undefined;
84
+ }
85
+ } catch (_) { }
86
+
87
+ const msg = detail || reason;
88
+ logger(`auth change -> ${reason}: ${msg}`, "error");
89
+
90
+ if (typeof globalCallback === "function") {
91
+ try {
92
+ globalCallback({
93
+ type: "account_inactive",
94
+ reason,
95
+ error: msg,
96
+ timestamp: Date.now()
97
+ }, null);
98
+ } catch (cbErr) {
99
+ logger(`emitAuth callback error: ${cbErr && cbErr.message ? cbErr.message : String(cbErr)}`, "error");
100
+ }
101
+ }
102
+ };
103
+ };