@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,1426 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/client.ts
5
+ import { EventEmitter } from "events";
6
+
7
+ // src/native.ts
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { fileURLToPath } from "url";
11
+ import koffi from "koffi";
12
+ import JSONBig from "yumi-json-bigint";
13
+ var JSONBigNative = JSONBig({
14
+ useNativeBigInt: true,
15
+ parseAsBigInt32: true
16
+ });
17
+ function resolveDirname() {
18
+ return path.dirname(fileURLToPath(import.meta.url));
19
+ }
20
+ __name(resolveDirname, "resolveDirname");
21
+ function libPath() {
22
+ const base = path.join(resolveDirname(), "..", "build");
23
+ if (process.platform === "win32") return path.join(base, "messagix.dll");
24
+ if (process.platform === "darwin") return path.join(base, "messagix.dylib");
25
+ return path.join(base, "messagix.so");
26
+ }
27
+ __name(libPath, "libPath");
28
+ var LIB_FILE = libPath();
29
+ if (!fs.existsSync(LIB_FILE)) {
30
+ throw new Error(`Native library not found at ${LIB_FILE}. Run: npm run build:go`);
31
+ }
32
+ var lib = koffi.load(LIB_FILE);
33
+ var mk = /* @__PURE__ */ __name((ret, name, args) => lib.func(name, ret, args), "mk");
34
+
35
+ // The Go binary heap-allocates every returned JSON string; koffi's plain
36
+ // "str" return type copies it into a JS string but never frees the original
37
+ // C buffer. Without freeing it via MxFreeCString, every single native call
38
+ // (which, for a busy bot, happens very frequently — every message, typing
39
+ // indicator, receipt, send, etc.) leaked native memory that Node's GC can
40
+ // never see, causing slow, steady RSS growth to an eventual OOM kill after
41
+ // several hours of uptime. koffi's disposable() type calls the destructor
42
+ // automatically right after copying the string, fixing this at the root.
43
+ var _rawMxFreeCString = mk("void", "MxFreeCString", ["char*"]);
44
+ var MxString = koffi.disposable("MxString", "str", (ptr) => {
45
+ try { _rawMxFreeCString(ptr); } catch (_) { /* best-effort */ }
46
+ });
47
+
48
+ var fns = {
49
+ MxFreeCString: _rawMxFreeCString,
50
+ MxNewClient: mk(MxString, "MxNewClient", ["str"]),
51
+ MxConnect: mk(MxString, "MxConnect", ["str"]),
52
+ MxConnectE2EE: mk(MxString, "MxConnectE2EE", ["str"]),
53
+ MxDisconnect: mk(MxString, "MxDisconnect", ["str"]),
54
+ MxIsConnected: mk(MxString, "MxIsConnected", ["str"]),
55
+ MxSendMessage: mk(MxString, "MxSendMessage", ["str"]),
56
+ MxSendReaction: mk(MxString, "MxSendReaction", ["str"]),
57
+ MxEditMessage: mk(MxString, "MxEditMessage", ["str"]),
58
+ MxUnsendMessage: mk(MxString, "MxUnsendMessage", ["str"]),
59
+ MxSendTyping: mk(MxString, "MxSendTyping", ["str"]),
60
+ MxMarkRead: mk(MxString, "MxMarkRead", ["str"]),
61
+ MxUploadMedia: mk(MxString, "MxUploadMedia", ["str"]),
62
+ MxSendImage: mk(MxString, "MxSendImage", ["str"]),
63
+ MxSendVideo: mk(MxString, "MxSendVideo", ["str"]),
64
+ MxSendVoice: mk(MxString, "MxSendVoice", ["str"]),
65
+ MxSendFile: mk(MxString, "MxSendFile", ["str"]),
66
+ MxSendSticker: mk(MxString, "MxSendSticker", ["str"]),
67
+ MxCreateThread: mk(MxString, "MxCreateThread", ["str"]),
68
+ MxGetUserInfo: mk(MxString, "MxGetUserInfo", ["str"]),
69
+ MxSetGroupPhoto: mk(MxString, "MxSetGroupPhoto", ["str"]),
70
+ MxRenameThread: mk(MxString, "MxRenameThread", ["str"]),
71
+ MxMuteThread: mk(MxString, "MxMuteThread", ["str"]),
72
+ MxDeleteThread: mk(MxString, "MxDeleteThread", ["str"]),
73
+ MxSearchUsers: mk(MxString, "MxSearchUsers", ["str"]),
74
+ MxPollEvents: mk(MxString, "MxPollEvents", ["str"]),
75
+ MxSendE2EEMessage: mk(MxString, "MxSendE2EEMessage", ["str"]),
76
+ MxSendE2EEReaction: mk(MxString, "MxSendE2EEReaction", ["str"]),
77
+ MxSendE2EETyping: mk(MxString, "MxSendE2EETyping", ["str"]),
78
+ MxEditE2EEMessage: mk(MxString, "MxEditE2EEMessage", ["str"]),
79
+ MxUnsendE2EEMessage: mk(MxString, "MxUnsendE2EEMessage", ["str"]),
80
+ MxGetDeviceData: mk(MxString, "MxGetDeviceData", ["str"]),
81
+ // E2EE Media functions
82
+ MxSendE2EEImage: mk(MxString, "MxSendE2EEImage", ["str"]),
83
+ MxSendE2EEVideo: mk(MxString, "MxSendE2EEVideo", ["str"]),
84
+ MxSendE2EEAudio: mk(MxString, "MxSendE2EEAudio", ["str"]),
85
+ MxSendE2EEDocument: mk(MxString, "MxSendE2EEDocument", ["str"]),
86
+ MxSendE2EESticker: mk(MxString, "MxSendE2EESticker", ["str"]),
87
+ MxDownloadE2EEMedia: mk(MxString, "MxDownloadE2EEMedia", ["str"]),
88
+ // Cookie and push notification functions
89
+ MxGetCookies: mk(MxString, "MxGetCookies", ["str"]),
90
+ MxRegisterPushNotifications: mk(MxString, "MxRegisterPushNotifications", ["str"])
91
+ };
92
+ function call(fn, payload) {
93
+ const input = JSONBigNative.stringify(payload);
94
+ const bound = fns[fn];
95
+ const out = bound(input);
96
+ const data = JSONBigNative.parse(out);
97
+ if (!data.ok) throw new Error(data.error || "Unknown error");
98
+ return data.data;
99
+ }
100
+ __name(call, "call");
101
+ function callAsync(fn, payload) {
102
+ return new Promise((resolve, reject) => {
103
+ setTimeout(() => {
104
+ try {
105
+ const result = call(fn, payload);
106
+ resolve(result);
107
+ } catch (err) {
108
+ reject(err);
109
+ }
110
+ }, 0);
111
+ });
112
+ }
113
+ __name(callAsync, "callAsync");
114
+ var native = {
115
+ newClient: /* @__PURE__ */ __name((cfg) => call("MxNewClient", cfg), "newClient"),
116
+ connect: /* @__PURE__ */ __name((handle) => call("MxConnect", { handle }), "connect"),
117
+ connectE2EE: /* @__PURE__ */ __name((handle) => callAsync("MxConnectE2EE", { handle }), "connectE2EE"),
118
+ disconnect: /* @__PURE__ */ __name((handle) => call("MxDisconnect", { handle }), "disconnect"),
119
+ isConnected: /* @__PURE__ */ __name((handle) => call("MxIsConnected", { handle }), "isConnected"),
120
+ sendMessage: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendMessage", { handle, options }), "sendMessage"),
121
+ sendReaction: /* @__PURE__ */ __name((handle, threadId, messageId, emoji) => callAsync("MxSendReaction", { handle, threadId, messageId, emoji }), "sendReaction"),
122
+ editMessage: /* @__PURE__ */ __name((handle, messageId, newText) => callAsync("MxEditMessage", { handle, messageId, newText }), "editMessage"),
123
+ unsendMessage: /* @__PURE__ */ __name((handle, messageId) => callAsync("MxUnsendMessage", { handle, messageId }), "unsendMessage"),
124
+ sendTyping: /* @__PURE__ */ __name((handle, threadId, isTyping, isGroup, threadType) => callAsync("MxSendTyping", { handle, threadId, isTyping, isGroup, threadType }), "sendTyping"),
125
+ markRead: /* @__PURE__ */ __name((handle, threadId, watermarkTs) => callAsync("MxMarkRead", { handle, threadId, watermarkTs: watermarkTs || 0n }), "markRead"),
126
+ uploadMedia: /* @__PURE__ */ __name((handle, options) => callAsync("MxUploadMedia", { handle, options }), "uploadMedia"),
127
+ sendImage: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendImage", { handle, options }), "sendImage"),
128
+ sendVideo: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendVideo", { handle, options }), "sendVideo"),
129
+ sendVoice: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendVoice", { handle, options }), "sendVoice"),
130
+ sendFile: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendFile", { handle, options }), "sendFile"),
131
+ sendSticker: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendSticker", { handle, options }), "sendSticker"),
132
+ createThread: /* @__PURE__ */ __name((handle, options) => callAsync("MxCreateThread", { handle, options }), "createThread"),
133
+ getUserInfo: /* @__PURE__ */ __name((handle, options) => callAsync("MxGetUserInfo", { handle, options }), "getUserInfo"),
134
+ setGroupPhoto: /* @__PURE__ */ __name((handle, threadId, data, mimeType) => callAsync("MxSetGroupPhoto", { handle, threadId, data, mimeType }), "setGroupPhoto"),
135
+ renameThread: /* @__PURE__ */ __name((handle, options) => callAsync("MxRenameThread", { handle, options }), "renameThread"),
136
+ muteThread: /* @__PURE__ */ __name((handle, options) => callAsync("MxMuteThread", { handle, options }), "muteThread"),
137
+ deleteThread: /* @__PURE__ */ __name((handle, options) => callAsync("MxDeleteThread", { handle, options }), "deleteThread"),
138
+ searchUsers: /* @__PURE__ */ __name((handle, options) => callAsync("MxSearchUsers", {
139
+ handle,
140
+ options
141
+ }), "searchUsers"),
142
+ pollEvents: /* @__PURE__ */ __name((handle, timeoutMs) => callAsync("MxPollEvents", { handle, timeoutMs }), "pollEvents"),
143
+ // E2EE functions
144
+ sendE2EEMessage: /* @__PURE__ */ __name((handle, chatJid, text, replyToId, replyToSenderJid) => callAsync("MxSendE2EEMessage", {
145
+ handle,
146
+ chatJid,
147
+ text,
148
+ replyToId,
149
+ replyToSenderJid
150
+ }), "sendE2EEMessage"),
151
+ sendE2EEReaction: /* @__PURE__ */ __name((handle, chatJid, messageId, senderJid, emoji) => callAsync("MxSendE2EEReaction", { handle, chatJid, messageId, senderJid, emoji }), "sendE2EEReaction"),
152
+ sendE2EETyping: /* @__PURE__ */ __name((handle, chatJid, isTyping) => callAsync("MxSendE2EETyping", { handle, chatJid, isTyping }), "sendE2EETyping"),
153
+ editE2EEMessage: /* @__PURE__ */ __name((handle, chatJid, messageId, newText) => callAsync("MxEditE2EEMessage", { handle, chatJid, messageId, newText }), "editE2EEMessage"),
154
+ unsendE2EEMessage: /* @__PURE__ */ __name((handle, chatJid, messageId) => callAsync("MxUnsendE2EEMessage", { handle, chatJid, messageId }), "unsendE2EEMessage"),
155
+ getDeviceData: /* @__PURE__ */ __name((handle) => call("MxGetDeviceData", { handle }), "getDeviceData"),
156
+ // E2EE Media functions
157
+ sendE2EEImage: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendE2EEImage", { handle, options }), "sendE2EEImage"),
158
+ sendE2EEVideo: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendE2EEVideo", { handle, options }), "sendE2EEVideo"),
159
+ sendE2EEAudio: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendE2EEAudio", { handle, options }), "sendE2EEAudio"),
160
+ sendE2EEDocument: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendE2EEDocument", { handle, options }), "sendE2EEDocument"),
161
+ sendE2EESticker: /* @__PURE__ */ __name((handle, options) => callAsync("MxSendE2EESticker", { handle, options }), "sendE2EESticker"),
162
+ downloadE2EEMedia: /* @__PURE__ */ __name((handle, options) => callAsync("MxDownloadE2EEMedia", { handle, options }), "downloadE2EEMedia"),
163
+ // Cookie and push notification functions
164
+ getCookies: /* @__PURE__ */ __name((handle) => call("MxGetCookies", { handle }), "getCookies"),
165
+ registerPushNotifications: /* @__PURE__ */ __name((handle, options) => callAsync("MxRegisterPushNotifications", { handle, options }), "registerPushNotifications"),
166
+ unload: /* @__PURE__ */ __name(() => lib.unload(), "unload")
167
+ };
168
+
169
+ // src/client.ts
170
+ var Client = class extends EventEmitter {
171
+ static {
172
+ __name(this, "Client");
173
+ }
174
+ handle = null;
175
+ options;
176
+ cookies;
177
+ _user = null;
178
+ _initialData = null;
179
+ eventLoopRunning = false;
180
+ eventLoopAbort = null;
181
+ _socketReady = false;
182
+ _e2eeConnected = false;
183
+ _fullyReadyEmitted = false;
184
+ pendingEvents = [];
185
+ /**
186
+ * Create a new Messenger client
187
+ *
188
+ * @param cookies - Authentication cookies
189
+ * @param options - Client options
190
+ */
191
+ constructor(cookies, options = {}) {
192
+ super();
193
+ this.cookies = cookies;
194
+ this.options = {
195
+ // ! todo: detect platform automatically
196
+ platform: "facebook",
197
+ logLevel: "none",
198
+ enableE2EE: true,
199
+ autoReconnect: true,
200
+ e2eeMemoryOnly: true,
201
+ ...options
202
+ };
203
+ }
204
+ /**
205
+ * Get the current user info
206
+ */
207
+ get user() {
208
+ return this._user;
209
+ }
210
+ /**
211
+ * Get the current user's Facebook ID
212
+ */
213
+ get currentUserId() {
214
+ return this._user?.id ?? null;
215
+ }
216
+ /**
217
+ * Get initial sync data (threads and messages)
218
+ */
219
+ get initialData() {
220
+ return this._initialData;
221
+ }
222
+ /**
223
+ * Check if client is fully ready (socket ready + E2EE connected if enabled)
224
+ */
225
+ isFullyReady() {
226
+ if (!this._socketReady) return false;
227
+ if (this.options.enableE2EE && !this._e2eeConnected) return false;
228
+ return true;
229
+ }
230
+ /**
231
+ * Check if client is connected
232
+ */
233
+ get isConnected() {
234
+ if (!this.handle) return false;
235
+ try {
236
+ const status = native.isConnected(this.handle);
237
+ return status.connected;
238
+ } catch {
239
+ return false;
240
+ }
241
+ }
242
+ /**
243
+ * Check if E2EE is connected
244
+ */
245
+ get isE2EEConnected() {
246
+ if (!this.handle) return false;
247
+ try {
248
+ const status = native.isConnected(this.handle);
249
+ return status.e2eeConnected;
250
+ } catch {
251
+ return false;
252
+ }
253
+ }
254
+ /**
255
+ * Connect to Messenger
256
+ *
257
+ * @returns User info and initial data
258
+ */
259
+ async connect() {
260
+ const { handle } = native.newClient({
261
+ cookies: this.cookies,
262
+ platform: this.options.platform,
263
+ devicePath: this.options.devicePath,
264
+ deviceData: this.options.deviceData,
265
+ e2eeMemoryOnly: this.options.e2eeMemoryOnly,
266
+ logLevel: this.options.logLevel
267
+ });
268
+ this.handle = handle;
269
+ const result = native.connect(handle);
270
+ this._user = result.user;
271
+ this._initialData = result.initialData;
272
+ this.startEventLoop();
273
+ if (this.options.enableE2EE) {
274
+ this.connectE2EE().catch((err) => {
275
+ this.emit("error", err);
276
+ });
277
+ }
278
+ return {
279
+ user: this._user,
280
+ initialData: this._initialData
281
+ };
282
+ }
283
+ /**
284
+ * Connect E2EE (end-to-end encryption)
285
+ * @warn This Promise is not resolved after the connection setup is completed; instead, it is resolved after the function finishes executing.\
286
+ * You should not rely on this Promise to wait for the E2EE connection to be fully established.
287
+ */
288
+ async connectE2EE() {
289
+ if (!this.handle) throw new Error("Not connected");
290
+ await native.connectE2EE(this.handle);
291
+ }
292
+ /**
293
+ * Disconnect from Messenger
294
+ */
295
+ async disconnect() {
296
+ this.stopEventLoop();
297
+ if (this.handle) {
298
+ native.disconnect(this.handle);
299
+ this.handle = null;
300
+ }
301
+ }
302
+ /**
303
+ * Send a text message
304
+ *
305
+ * @param threadId - Thread ID to send to
306
+ * @param options - Message options (text, reply, mentions)
307
+ * @returns Send result with message ID
308
+ */
309
+ async sendMessage(threadId, options) {
310
+ if (!this.handle) throw new Error("Not connected");
311
+ const opts = typeof options === "string" ? { text: options } : options;
312
+ return native.sendMessage(this.handle, {
313
+ threadId,
314
+ text: opts.text,
315
+ replyToId: opts.replyToId,
316
+ attachmentFbIds: opts.attachmentFbIds,
317
+ mentionIds: opts.mentions?.map((m) => m.userId),
318
+ mentionOffsets: opts.mentions?.map((m) => m.offset),
319
+ mentionLengths: opts.mentions?.map((m) => m.length)
320
+ });
321
+ }
322
+ /**
323
+ * Send / Remove a reaction to a message
324
+ *
325
+ * @param threadId - Thread ID
326
+ * @param messageId - Message ID to react to
327
+ * @param emoji - Reaction emoji (to remove, simply omit this parameter)
328
+ */
329
+ async sendReaction(threadId, messageId, emoji) {
330
+ if (!this.handle) throw new Error("Not connected");
331
+ await native.sendReaction(this.handle, threadId, messageId, emoji || "");
332
+ }
333
+ /**
334
+ * Edit a message
335
+ *
336
+ * @param messageId - Message ID to edit
337
+ * @param newText - New text content
338
+ */
339
+ async editMessage(messageId, newText) {
340
+ if (!this.handle) throw new Error("Not connected");
341
+ await native.editMessage(this.handle, messageId, newText);
342
+ }
343
+ /**
344
+ * Unsend/delete a message
345
+ *
346
+ * @param messageId - Message ID to unsend
347
+ */
348
+ async unsendMessage(messageId) {
349
+ if (!this.handle) throw new Error("Not connected");
350
+ await native.unsendMessage(this.handle, messageId);
351
+ }
352
+ /**
353
+ * Send typing indicator
354
+ *
355
+ * @param threadId - Thread ID
356
+ * @param isTyping - Whether typing or not
357
+ * @param isGroup - Whether it's a group chat
358
+ */
359
+ async sendTypingIndicator(threadId, isTyping = true, isGroup = false) {
360
+ if (!this.handle) throw new Error("Not connected");
361
+ await native.sendTyping(this.handle, threadId, isTyping, isGroup, isGroup ? 2 : 1);
362
+ }
363
+ /**
364
+ * Mark messages as read
365
+ *
366
+ * @param threadId - Thread ID
367
+ * @param watermarkTs - Timestamp to mark read up to (optional)
368
+ */
369
+ async markAsRead(threadId, watermarkTs) {
370
+ if (!this.handle) throw new Error("Not connected");
371
+ await native.markRead(this.handle, threadId, watermarkTs);
372
+ }
373
+ /**
374
+ * Upload media to Messenger
375
+ *
376
+ * @param threadId - Thread ID
377
+ * @param data - File data as Buffer
378
+ * @param filename - Filename
379
+ * @param mimeType - MIME type
380
+ * @param isVoice - Whether it's a voice message
381
+ * @returns Upload result with Facebook ID
382
+ */
383
+ async uploadMedia(threadId, data, filename, mimeType, isVoice = false) {
384
+ if (!this.handle) throw new Error("Not connected");
385
+ return native.uploadMedia(this.handle, {
386
+ threadId,
387
+ filename,
388
+ mimeType,
389
+ data: Buffer.isBuffer(data) ? data.toString("base64") : data,
390
+ isVoice
391
+ });
392
+ }
393
+ /**
394
+ * Send an image
395
+ *
396
+ * @param threadId - Thread ID
397
+ * @param data - Image data as Buffer
398
+ * @param filename - Filename
399
+ * @param options - Optional: caption and replyToId
400
+ */
401
+ async sendImage(threadId, data, filename, options) {
402
+ if (!this.handle) throw new Error("Not connected");
403
+ const opts = typeof options === "string" ? { caption: options } : options;
404
+ return native.sendImage(this.handle, {
405
+ threadId,
406
+ data: Buffer.isBuffer(data) ? data.toString("base64") : data,
407
+ filename,
408
+ caption: opts?.caption,
409
+ replyToId: opts?.replyToId
410
+ });
411
+ }
412
+ /**
413
+ * Send a video
414
+ *
415
+ * @param threadId - Thread ID
416
+ * @param data - Video data as Buffer
417
+ * @param filename - Filename
418
+ * @param options - Optional: caption and replyToId
419
+ */
420
+ async sendVideo(threadId, data, filename, options) {
421
+ if (!this.handle) throw new Error("Not connected");
422
+ const opts = typeof options === "string" ? { caption: options } : options;
423
+ return native.sendVideo(this.handle, {
424
+ threadId,
425
+ data: Buffer.isBuffer(data) ? data.toString("base64") : data,
426
+ filename,
427
+ caption: opts?.caption,
428
+ replyToId: opts?.replyToId
429
+ });
430
+ }
431
+ /**
432
+ * Send a voice message
433
+ *
434
+ * @param threadId - Thread ID
435
+ * @param data - Audio data as Buffer
436
+ * @param filename - Filename
437
+ * @param options - Optional: replyToId
438
+ */
439
+ async sendVoice(threadId, data, filename, options) {
440
+ if (!this.handle) throw new Error("Not connected");
441
+ return native.sendVoice(this.handle, {
442
+ threadId,
443
+ data: Buffer.isBuffer(data) ? data.toString("base64") : data,
444
+ filename,
445
+ replyToId: options?.replyToId
446
+ });
447
+ }
448
+ /**
449
+ * Send a file
450
+ *
451
+ * @param threadId - Thread ID
452
+ * @param data - File data as Buffer
453
+ * @param filename - Filename
454
+ * @param mimeType - MIME type
455
+ * @param options - Optional: caption and replyToId
456
+ */
457
+ async sendFile(threadId, data, filename, mimeType, options) {
458
+ if (!this.handle) throw new Error("Not connected");
459
+ const opts = typeof options === "string" ? { caption: options } : options;
460
+ return native.sendFile(this.handle, {
461
+ threadId,
462
+ data: Buffer.isBuffer(data) ? data.toString("base64") : data,
463
+ filename,
464
+ mimeType,
465
+ caption: opts?.caption,
466
+ replyToId: opts?.replyToId
467
+ });
468
+ }
469
+ /**
470
+ * Send a sticker
471
+ *
472
+ * @param threadId - Thread ID
473
+ * @param stickerId - Sticker ID
474
+ * @param options - Optional: replyToId
475
+ */
476
+ async sendSticker(threadId, stickerId, options) {
477
+ if (!this.handle) throw new Error("Not connected");
478
+ return native.sendSticker(this.handle, { threadId, stickerId, replyToId: options?.replyToId });
479
+ }
480
+ /**
481
+ * Create a 1:1 thread with a user
482
+ *
483
+ * @param userId - User ID to create thread with
484
+ * @returns Created thread info
485
+ */
486
+ async createThread(userId) {
487
+ if (!this.handle) throw new Error("Not connected");
488
+ return native.createThread(this.handle, { userId });
489
+ }
490
+ /**
491
+ * Get detailed information about a user
492
+ *
493
+ * @param userId - User ID
494
+ * @returns User info
495
+ */
496
+ async getUserInfo(userId) {
497
+ if (!this.handle) throw new Error("Not connected");
498
+ return native.getUserInfo(this.handle, { userId });
499
+ }
500
+ /**
501
+ * Set group photo/avatar
502
+ *
503
+ * @param threadId - Thread ID
504
+ * @param data - Image data as Buffer or base64 string
505
+ * @param mimeType - MIME type (e.g., 'image/jpeg', 'image/png')
506
+ *
507
+ * @warn Cannot remove group photo. Messenger web doesn't have a remove option?
508
+ */
509
+ async setGroupPhoto(threadId, data, mimeType = "image/jpeg") {
510
+ if (!this.handle) throw new Error("Not connected");
511
+ const base64 = Buffer.isBuffer(data) ? data.toString("base64") : data;
512
+ await native.setGroupPhoto(this.handle, threadId, base64, mimeType);
513
+ }
514
+ /**
515
+ * Rename a group thread
516
+ *
517
+ * @param threadId - Thread ID
518
+ * @param newName - New name
519
+ */
520
+ async renameThread(threadId, newName) {
521
+ if (!this.handle) throw new Error("Not connected");
522
+ native.renameThread(this.handle, { threadId, newName });
523
+ }
524
+ /**
525
+ * Mute a thread
526
+ *
527
+ * @param threadId - Thread ID
528
+ * @param muteSeconds - Duration in seconds (-1 for forever, 0 to unmute)
529
+ */
530
+ async muteThread(threadId, muteSeconds = -1) {
531
+ if (!this.handle) throw new Error("Not connected");
532
+ native.muteThread(this.handle, { threadId, muteSeconds });
533
+ }
534
+ /**
535
+ * Unmute a thread
536
+ *
537
+ * @param threadId - Thread ID
538
+ */
539
+ async unmuteThread(threadId) {
540
+ return this.muteThread(threadId, 0);
541
+ }
542
+ /**
543
+ * Delete a thread
544
+ *
545
+ * @param threadId - Thread ID
546
+ */
547
+ async deleteThread(threadId) {
548
+ if (!this.handle) throw new Error("Not connected");
549
+ native.deleteThread(this.handle, { threadId });
550
+ }
551
+ /**
552
+ * Search for users
553
+ *
554
+ * @param query - Search query
555
+ * @returns List of matching users
556
+ */
557
+ async searchUsers(query) {
558
+ if (!this.handle) throw new Error("Not connected");
559
+ const result = await native.searchUsers(this.handle, { query });
560
+ return result.users;
561
+ }
562
+ // ========== E2EE Methods ==========
563
+ /**
564
+ * Send an E2EE message
565
+ *
566
+ * @param chatJid - Chat JID
567
+ * @param text - Message text
568
+ * @param options - Optional: replyToId and replyToSenderJid for replies
569
+ */
570
+ async sendE2EEMessage(chatJid, text, options) {
571
+ if (!this.handle) throw new Error("Not connected");
572
+ return native.sendE2EEMessage(this.handle, chatJid, text, options?.replyToId, options?.replyToSenderJid);
573
+ }
574
+ /**
575
+ * Send / Remove an E2EE reaction
576
+ *
577
+ * @param chatJid - Chat JID
578
+ * @param messageId - Message ID
579
+ * @param senderJid - Sender JID
580
+ * @param emoji - Reaction emoji (To remove it, simply omit this parameter)
581
+ */
582
+ async sendE2EEReaction(chatJid, messageId, senderJid, emoji) {
583
+ if (!this.handle) throw new Error("Not connected");
584
+ await native.sendE2EEReaction(this.handle, chatJid, messageId, senderJid, emoji || "");
585
+ }
586
+ /**
587
+ * Send E2EE typing indicator
588
+ *
589
+ * @param chatJid - Chat JID
590
+ * @param isTyping - Whether typing
591
+ */
592
+ async sendE2EETyping(chatJid, isTyping = true) {
593
+ if (!this.handle) throw new Error("Not connected");
594
+ await native.sendE2EETyping(this.handle, chatJid, isTyping);
595
+ }
596
+ /**
597
+ * Edit an E2EE message
598
+ *
599
+ * @param chatJid - Chat JID
600
+ * @param messageId - Message ID to edit
601
+ * @param newText - New message text
602
+ */
603
+ async editE2EEMessage(chatJid, messageId, newText) {
604
+ if (!this.handle) throw new Error("Not connected");
605
+ await native.editE2EEMessage(this.handle, chatJid, messageId, newText);
606
+ }
607
+ /**
608
+ * Unsend/delete an E2EE message
609
+ *
610
+ * @param chatJid - Chat JID
611
+ * @param messageId - Message ID to unsend
612
+ */
613
+ async unsendE2EEMessage(chatJid, messageId) {
614
+ if (!this.handle) throw new Error("Not connected");
615
+ await native.unsendE2EEMessage(this.handle, chatJid, messageId);
616
+ }
617
+ // ========== E2EE Media Methods ==========
618
+ /**
619
+ * Send an E2EE image
620
+ *
621
+ * @param chatJid - Chat JID
622
+ * @param data - Image data as Buffer
623
+ * @param mimeType - MIME type (e.g., image/jpeg, image/png)
624
+ * @param options - Optional caption, dimensions, and reply options
625
+ */
626
+ async sendE2EEImage(chatJid, data, mimeType = "image/jpeg", options) {
627
+ if (!this.handle) throw new Error("Not connected");
628
+ return native.sendE2EEImage(this.handle, {
629
+ chatJid,
630
+ data: Buffer.isBuffer(data) ? data.toString("base64") : data,
631
+ mimeType,
632
+ caption: options?.caption,
633
+ width: options?.width,
634
+ height: options?.height,
635
+ replyToId: options?.replyToId,
636
+ replyToSenderJid: options?.replyToSenderJid
637
+ });
638
+ }
639
+ /**
640
+ * Send an E2EE video
641
+ *
642
+ * @param chatJid - Chat JID
643
+ * @param data - Video data as Buffer
644
+ * @param mimeType - MIME type (default: video/mp4)
645
+ * @param options - Optional caption, dimensions, duration, and reply options
646
+ */
647
+ async sendE2EEVideo(chatJid, data, mimeType = "video/mp4", options) {
648
+ if (!this.handle) throw new Error("Not connected");
649
+ return native.sendE2EEVideo(this.handle, {
650
+ chatJid,
651
+ data: Buffer.isBuffer(data) ? data.toString("base64") : data,
652
+ mimeType,
653
+ caption: options?.caption,
654
+ width: options?.width,
655
+ height: options?.height,
656
+ duration: options?.duration,
657
+ replyToId: options?.replyToId,
658
+ replyToSenderJid: options?.replyToSenderJid
659
+ });
660
+ }
661
+ /**
662
+ * Send an E2EE audio/voice message
663
+ *
664
+ * @param chatJid - Chat JID
665
+ * @param data - Audio data as Buffer
666
+ * @param mimeType - MIME type (default: audio/ogg)
667
+ * @param options - Optional PTT (push-to-talk/voice message), duration, and reply options
668
+ */
669
+ async sendE2EEAudio(chatJid, data, mimeType = "audio/ogg", options) {
670
+ if (!this.handle) throw new Error("Not connected");
671
+ return native.sendE2EEAudio(this.handle, {
672
+ chatJid,
673
+ data: Buffer.isBuffer(data) ? data.toString("base64") : data,
674
+ mimeType,
675
+ ptt: options?.ptt ?? false,
676
+ duration: options?.duration,
677
+ replyToId: options?.replyToId,
678
+ replyToSenderJid: options?.replyToSenderJid
679
+ });
680
+ }
681
+ /**
682
+ * Send an E2EE document/file
683
+ *
684
+ * @param chatJid - Chat JID
685
+ * @param data - File data as Buffer
686
+ * @param filename - Filename
687
+ * @param mimeType - MIME type
688
+ * @param options - Optional reply options
689
+ */
690
+ async sendE2EEDocument(chatJid, data, filename, mimeType, options) {
691
+ if (!this.handle) throw new Error("Not connected");
692
+ return native.sendE2EEDocument(this.handle, {
693
+ chatJid,
694
+ data: Buffer.isBuffer(data) ? data.toString("base64") : data,
695
+ filename,
696
+ mimeType,
697
+ replyToId: options?.replyToId,
698
+ replyToSenderJid: options?.replyToSenderJid
699
+ });
700
+ }
701
+ /**
702
+ * Send an E2EE sticker
703
+ *
704
+ * @param chatJid - Chat JID
705
+ * @param data - Sticker data as Buffer (WebP format)
706
+ * @param mimeType - MIME type (default: image/webp)
707
+ * @param options - Optional reply options
708
+ */
709
+ async sendE2EESticker(chatJid, data, mimeType = "image/webp", options) {
710
+ if (!this.handle) throw new Error("Not connected");
711
+ return native.sendE2EESticker(this.handle, {
712
+ chatJid,
713
+ data: Buffer.isBuffer(data) ? data.toString("base64") : data,
714
+ mimeType,
715
+ replyToId: options?.replyToId,
716
+ replyToSenderJid: options?.replyToSenderJid
717
+ });
718
+ }
719
+ /**
720
+ * Get E2EE device data as JSON string
721
+ *
722
+ * Use this to persist device data externally (e.g., in a database)
723
+ *
724
+ * @returns Device data as JSON string
725
+ */
726
+ getDeviceData() {
727
+ if (!this.handle) throw new Error("Not connected");
728
+ const result = native.getDeviceData(this.handle);
729
+ return result.deviceData;
730
+ }
731
+ /**
732
+ * Get the current cookies from the internal client state
733
+ *
734
+ * Use this to export updated cookies after they've been refreshed
735
+ *
736
+ * @returns Current cookies as key-value object
737
+ */
738
+ getCookies() {
739
+ if (!this.handle) throw new Error("Not connected");
740
+ const result = native.getCookies(this.handle);
741
+ return result.cookies;
742
+ }
743
+ /**
744
+ * Register for web push notifications
745
+ *
746
+ * @param endpoint - Push notification endpoint URL
747
+ * @param keys - Push notification keys (p256dh and auth, base64 encoded)
748
+ */
749
+ async registerPushNotifications(endpoint, keys) {
750
+ if (!this.handle) throw new Error("Not connected");
751
+ await native.registerPushNotifications(this.handle, {
752
+ endpoint,
753
+ p256dh: keys.p256dh,
754
+ auth: keys.auth
755
+ });
756
+ }
757
+ /**
758
+ * Download and decrypt E2EE media
759
+ *
760
+ * Use the mediaKey, mediaSha256, and directPath from attachment metadata
761
+ * to download and decrypt encrypted media.
762
+ *
763
+ * @param options - Download options from attachment metadata
764
+ * @returns Decrypted media data as Buffer
765
+ *
766
+ * @example
767
+ * ```typescript
768
+ * const attachment = message.attachments[0];
769
+ * const result = await client.downloadE2EEMedia({
770
+ * directPath: attachment.directPath!,
771
+ * mediaKey: attachment.mediaKey!,
772
+ * mediaSha256: attachment.mediaSha256!,
773
+ * mediaEncSha256: attachment.mediaEncSha256,
774
+ * mediaType: attachment.type,
775
+ * mimeType: attachment.mimeType!,
776
+ * fileSize: attachment.fileSize!,
777
+ * });
778
+ * fs.writeFileSync('downloaded.jpg', result.data);
779
+ * ```
780
+ */
781
+ async downloadE2EEMedia(options) {
782
+ if (!this.handle) throw new Error("Not connected");
783
+ const result = await native.downloadE2EEMedia(this.handle, options);
784
+ return {
785
+ data: Buffer.from(result.data, "base64"),
786
+ mimeType: result.mimeType,
787
+ fileSize: result.fileSize
788
+ };
789
+ }
790
+ startEventLoop() {
791
+ if (this.eventLoopRunning) return;
792
+ this.eventLoopRunning = true;
793
+ this.eventLoopAbort = new AbortController();
794
+ const loop = /* @__PURE__ */ __name(async () => {
795
+ while (this.eventLoopRunning && this.handle) {
796
+ try {
797
+ await new Promise((resolve) => setImmediate(resolve));
798
+ const event = await native.pollEvents(this.handle, 1e3);
799
+ if (!event || event.type === "timeout") continue;
800
+ if (event.type === "closed") {
801
+ this.eventLoopRunning = false;
802
+ break;
803
+ }
804
+ this.handleEvent(event);
805
+ } catch (err) {
806
+ if (this.eventLoopRunning) {
807
+ this.emit("error", err);
808
+ }
809
+ }
810
+ }
811
+ }, "loop");
812
+ setImmediate(loop).unref();
813
+ }
814
+ stopEventLoop() {
815
+ this.eventLoopRunning = false;
816
+ this.eventLoopAbort?.abort();
817
+ this.eventLoopAbort = null;
818
+ }
819
+ checkFullyReady() {
820
+ if (this.isFullyReady() && !this._fullyReadyEmitted) {
821
+ this._fullyReadyEmitted = true;
822
+ this.emit("fullyReady");
823
+ const pending = this.pendingEvents;
824
+ this.pendingEvents = [];
825
+ for (const event of pending) {
826
+ this.emitEvent(event);
827
+ }
828
+ }
829
+ }
830
+ handleEvent(event) {
831
+ switch (event.type) {
832
+ // System events
833
+ case "ready":
834
+ this._socketReady = true;
835
+ this.emit("ready", event.data);
836
+ this.checkFullyReady();
837
+ break;
838
+ case "reconnected":
839
+ this.emit("reconnected");
840
+ break;
841
+ case "disconnected":
842
+ this._socketReady = false;
843
+ this._e2eeConnected = false;
844
+ this._fullyReadyEmitted = false;
845
+ this.pendingEvents = [];
846
+ this.emit("disconnected", event.data || {});
847
+ break;
848
+ case "error":
849
+ this.emit("error", new Error(event.data.message));
850
+ if (event.data.code === 1) {
851
+ this.stopEventLoop();
852
+ this._socketReady = false;
853
+ this._e2eeConnected = false;
854
+ this._fullyReadyEmitted = false;
855
+ this.pendingEvents = [];
856
+ }
857
+ break;
858
+ case "e2eeConnected":
859
+ this._e2eeConnected = true;
860
+ this.emit("e2eeConnected");
861
+ this.checkFullyReady();
862
+ break;
863
+ case "deviceDataChanged":
864
+ this.emit("deviceDataChanged", event.data);
865
+ break;
866
+ case "raw":
867
+ this.emit("raw", event.data);
868
+ break;
869
+ // queue until fullyReady
870
+ case "message":
871
+ case "messageEdit":
872
+ case "messageUnsend":
873
+ case "reaction":
874
+ case "typing":
875
+ case "readReceipt":
876
+ case "e2eeMessage":
877
+ case "e2eeReaction":
878
+ case "e2eeReceipt":
879
+ if (this._fullyReadyEmitted) {
880
+ this.emitEvent(event);
881
+ } else {
882
+ this.pendingEvents.push(event);
883
+ }
884
+ break;
885
+ }
886
+ }
887
+ emitEvent(event) {
888
+ switch (event.type) {
889
+ case "message":
890
+ this.emit("message", event.data);
891
+ break;
892
+ case "messageEdit":
893
+ this.emit("messageEdit", event.data);
894
+ break;
895
+ case "messageUnsend":
896
+ this.emit("messageUnsend", event.data);
897
+ break;
898
+ case "reaction":
899
+ this.emit("reaction", event.data);
900
+ break;
901
+ case "typing":
902
+ this.emit("typing", event.data);
903
+ break;
904
+ case "readReceipt":
905
+ this.emit("readReceipt", event.data);
906
+ break;
907
+ case "e2eeMessage":
908
+ this.emit("e2eeMessage", event.data);
909
+ break;
910
+ case "e2eeReaction":
911
+ this.emit("e2eeReaction", event.data);
912
+ break;
913
+ case "e2eeReceipt":
914
+ this.emit("e2eeReceipt", event.data);
915
+ break;
916
+ }
917
+ }
918
+ /**
919
+ * Unload the native library (for cleanup)
920
+ * @warn Any attempt to find or call a function from this library after unloading it will crash.
921
+ * @returns void
922
+ */
923
+ unloadLibrary() {
924
+ if (this.handle) {
925
+ native.unload();
926
+ }
927
+ }
928
+ };
929
+
930
+ // src/login.ts
931
+ import { randomUUID } from "crypto";
932
+ import { fetch } from "undici";
933
+ var UIDLogin = class extends null {
934
+ static {
935
+ __name(this, "UIDLogin");
936
+ }
937
+ static API_ENDPOINT = "https://b-graph.facebook.com/graphql";
938
+ static USER_AGENT = "[FBAN/FB4A;FBAV/498.1.0.64.74;FBBV/692621185;FBDM/{density=1.5,width=540,height=960};FBLC/vi_VN;FBRV/0;FBCR/MobiFone;FBMF/Xiaomi;FBBD/Xiaomi;FBPN/com.facebook.katana;FBDV/2211133C;FBSV/9;FBOP/1;FBCA/x86_64:arm64-v8a;]";
939
+ static AUTH_TOKEN = "OAuth 350685531728|62f8ce9f74b12f84c123cc23437a4a32";
940
+ static CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
941
+ static randomString(len) {
942
+ let result = "";
943
+ for (let i = 0; i < len; i++) {
944
+ result += this.CHARS[Math.floor(Math.random() * this.CHARS.length)];
945
+ }
946
+ return result;
947
+ }
948
+ static generateNonce(size) {
949
+ const bytes = new Uint8Array(size);
950
+ for (let i = 0; i < size; i++) bytes[i] = Math.floor(Math.random() * 256);
951
+ return Buffer.from(bytes).toString("base64");
952
+ }
953
+ static toBase64(text) {
954
+ return Buffer.from(text).toString("base64");
955
+ }
956
+ static formatPassword(pwd) {
957
+ return `#PWD_FB4A:0:${Math.floor(Date.now() / 1e3)}:${pwd}`;
958
+ }
959
+ static hashData(uid) {
960
+ return this.toBase64(JSON.stringify({ challenge_nonce: this.generateNonce(32), username: uid }));
961
+ }
962
+ static generateVariable(account) {
963
+ const deviceId = randomUUID();
964
+ const variable = {
965
+ params: {
966
+ params: JSON.stringify({
967
+ client_input_params: {
968
+ sim_phones: [],
969
+ secure_family_device_id: randomUUID(),
970
+ /*
971
+ attestation_result: {
972
+ data: this.hashData(account.uid),
973
+ signature:
974
+ "MEYCIQDtz5TqO0pwysy82Ko92FErORasLag9o/pQYlZl8+zaMgIhAKon529upFiPfGgoS6OkPKg0/VahBuSDxwiTgtzpYQA3",
975
+ keyHash: "92398b3e4d9ee926bae93a61fd75e18d750100c1e73fd44d4faa7b9ba9353eee",
976
+ },
977
+ */
978
+ has_granted_read_contacts_permissions: 0,
979
+ auth_secure_device_id: "",
980
+ has_whatsapp_installed: 0,
981
+ password: this.formatPassword(account.password),
982
+ sso_token_map_json_string: "",
983
+ event_flow: "login_manual",
984
+ password_contains_non_ascii: "false",
985
+ sim_serials: [],
986
+ client_known_key_hash: "",
987
+ encrypted_msisdn: "",
988
+ has_granted_read_phone_permissions: 0,
989
+ app_manager_id: "null",
990
+ should_show_nested_nta_from_aymh: 0,
991
+ device_id: deviceId,
992
+ login_attempt_count: 1,
993
+ machine_id: this.randomString(22),
994
+ flash_call_permission_status: {
995
+ READ_PHONE_STATE: "DENIED",
996
+ READ_CALL_LOG: "DENIED",
997
+ ANSWER_PHONE_CALLS: "DENIED"
998
+ },
999
+ accounts_list: [],
1000
+ family_device_id: deviceId,
1001
+ fb_ig_device_id: [],
1002
+ device_emails: [],
1003
+ try_num: 2,
1004
+ lois_settings: { lois_token: "" },
1005
+ event_step: "home_page",
1006
+ headers_infra_flow_id: "",
1007
+ openid_tokens: {},
1008
+ contact_point: account.uid
1009
+ },
1010
+ server_params: {
1011
+ should_trigger_override_login_2fa_action: 0,
1012
+ is_from_logged_out: 0,
1013
+ should_trigger_override_login_success_action: 0,
1014
+ login_credential_type: "none",
1015
+ server_login_source: "login",
1016
+ waterfall_id: randomUUID(),
1017
+ login_source: "Login",
1018
+ is_platform_login: 0,
1019
+ pw_encryption_try_count: 1,
1020
+ INTERNAL__latency_qpl_marker_id: 36707139,
1021
+ offline_experiment_group: "caa_iteration_v6_perf_fb_2",
1022
+ is_from_landing_page: 0,
1023
+ password_text_input_id: "jirv90:99",
1024
+ is_from_empty_password: 0,
1025
+ is_from_msplit_fallback: 0,
1026
+ ar_event_source: "login_home_page",
1027
+ username_text_input_id: "jirv90:98",
1028
+ layered_homepage_experiment_group: null,
1029
+ device_id: deviceId,
1030
+ INTERNAL__latency_qpl_instance_id: 118039064400779,
1031
+ reg_flow_source: "login_home_native_integration_point",
1032
+ is_caa_perf_enabled: 1,
1033
+ credential_type: "password",
1034
+ is_from_password_entry_page: 0,
1035
+ caller: "gslr",
1036
+ family_device_id: deviceId,
1037
+ is_from_assistive_id: 0,
1038
+ access_flow_version: "F2_FLOW",
1039
+ is_from_logged_in_switcher: 0
1040
+ }
1041
+ }),
1042
+ // const
1043
+ bloks_versioning_id: "cb6ac324faea83da28649a4d5046c3a4f0486cb987f8ab769765e316b075a76c",
1044
+ app_id: "com.bloks.www.bloks.caa.login.async.send_login_request"
1045
+ },
1046
+ scale: "1.5",
1047
+ nt_context: {
1048
+ using_white_navbar: true,
1049
+ // const
1050
+ styles_id: "55d2af294359fa6bbdb8e045ff01fc5e",
1051
+ pixel_ratio: 1.5,
1052
+ is_push_on: true,
1053
+ debug_tooling_metadata_token: null,
1054
+ is_flipper_enabled: false,
1055
+ theme_params: [],
1056
+ // can be dynamic
1057
+ bloks_version: "cb6ac324faea83da28649a4d5046c3a4f0486cb987f8ab769765e316b075a76c"
1058
+ }
1059
+ };
1060
+ return JSON.stringify(variable);
1061
+ }
1062
+ static extractCookieToken(data) {
1063
+ const tokenMatch = data.match(/"access_token":"([^"]+)"/);
1064
+ const cookiesMatch = data.match(/"session_cookies":\s*\[([^\]]+)\]/);
1065
+ const cookies = [];
1066
+ if (cookiesMatch) {
1067
+ const pattern = /"name":"([^"]+)","value":"([^"]+)"/g;
1068
+ let m;
1069
+ while (m = pattern.exec(cookiesMatch[1])) cookies.push(`${m[1]}=${m[2]}`);
1070
+ }
1071
+ return {
1072
+ token: tokenMatch?.[1] ?? "Access token not found",
1073
+ cookie: cookies.join("; ")
1074
+ };
1075
+ }
1076
+ /**
1077
+ * Performs Facebook login via Katana API
1078
+ * @param account - Account with UID and password
1079
+ * @returns Cookie and token upon successful authentication
1080
+ * @warn Accounts with 2FA are not supported, and the function will return an error
1081
+ * @deprecated This login method is unstable and may be blocked by Facebook at any time.
1082
+ */
1083
+ static async login(account) {
1084
+ const headers = {
1085
+ "User-Agent": this.USER_AGENT,
1086
+ Authorization: this.AUTH_TOKEN,
1087
+ "Content-Type": "application/x-www-form-urlencoded",
1088
+ "x-fb-sim-hni": "45201",
1089
+ "x-fb-net-hni": "45201",
1090
+ "x-fb-device-group": "2789",
1091
+ "x-fb-connection-type": "WIFI",
1092
+ "x-fb-http-engine": "Tigon/Liger",
1093
+ "x-fb-client-ip": "True",
1094
+ "x-fb-server-cluster": "True",
1095
+ "x-graphql-client-library": "graphservice",
1096
+ "x-graphql-request-purpose": "fetch",
1097
+ "x-fb-friendly-name": "FbBloksActionRootQuery-com.bloks.www.bloks.caa.login.async.send_login_request",
1098
+ "x-tigon-is-retry": "False",
1099
+ "x-zero-eh": "error",
1100
+ "Accept-Encoding": "identity"
1101
+ };
1102
+ const body = new URLSearchParams({
1103
+ method: "post",
1104
+ pretty: "false",
1105
+ format: "json",
1106
+ server_timestamps: "true",
1107
+ locale: "vi_VN",
1108
+ purpose: "fetch",
1109
+ fb_api_req_friendly_name: "FbBloksActionRootQuery-com.bloks.www.bloks.caa.login.async.send_login_request",
1110
+ fb_api_caller_class: "graphservice",
1111
+ client_doc_id: "11994080423986492941384902285",
1112
+ variables: this.generateVariable(account),
1113
+ fb_api_analytics_tags: '["GraphServices"]',
1114
+ client_trace_id: randomUUID()
1115
+ });
1116
+ const res = await fetch(this.API_ENDPOINT, { method: "POST", headers, body: body.toString() });
1117
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1118
+ const result = this.extractCookieToken((await res.text()).replace(/\\/g, ""));
1119
+ return result;
1120
+ }
1121
+ };
1122
+
1123
+ // src/types.ts
1124
+ var ThreadType = /* @__PURE__ */ ((ThreadType2) => {
1125
+ ThreadType2[ThreadType2["ONE_TO_ONE"] = 1] = "ONE_TO_ONE";
1126
+ ThreadType2[ThreadType2["GROUP"] = 2] = "GROUP";
1127
+ ThreadType2[ThreadType2["PAGE"] = 3] = "PAGE";
1128
+ ThreadType2[ThreadType2["MARKETPLACE"] = 4] = "MARKETPLACE";
1129
+ ThreadType2[ThreadType2["ENCRYPTED_ONE_TO_ONE"] = 7] = "ENCRYPTED_ONE_TO_ONE";
1130
+ ThreadType2[ThreadType2["ENCRYPTED_GROUP"] = 8] = "ENCRYPTED_GROUP";
1131
+ return ThreadType2;
1132
+ })(ThreadType || {});
1133
+
1134
+ // src/utils.ts
1135
+ var Utils = class _Utils extends null {
1136
+ static {
1137
+ __name(this, "Utils");
1138
+ }
1139
+ /**
1140
+ * Parse cookies from various formats
1141
+ * Automatically detects the format and parses accordingly
1142
+ *
1143
+ * @param input - Cookie data in any supported format
1144
+ * @returns Parsed cookies object
1145
+ */
1146
+ static parseCookies(input) {
1147
+ if (typeof input === "object" && !Array.isArray(input)) {
1148
+ return input;
1149
+ }
1150
+ if (Array.isArray(input)) {
1151
+ return _Utils.fromCookieArray(input);
1152
+ }
1153
+ if (typeof input === "string") {
1154
+ const trimmed = input.trim();
1155
+ if (_Utils.isBase64(trimmed)) {
1156
+ try {
1157
+ const decoded = Buffer.from(trimmed, "base64").toString("utf-8");
1158
+ return _Utils.parseCookies(decoded);
1159
+ } catch {
1160
+ }
1161
+ }
1162
+ if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
1163
+ try {
1164
+ const parsed = JSON.parse(trimmed);
1165
+ return _Utils.parseCookies(parsed);
1166
+ } catch {
1167
+ }
1168
+ }
1169
+ if (trimmed.includes(" ") && (trimmed.startsWith("#") || trimmed.includes(".facebook.com") || trimmed.includes(".messenger.com"))) {
1170
+ return _Utils.fromNetscape(trimmed);
1171
+ }
1172
+ if (trimmed.includes("=")) {
1173
+ return _Utils.fromCookieString(trimmed);
1174
+ }
1175
+ }
1176
+ throw new Error("Unable to parse cookies: unknown format");
1177
+ }
1178
+ /**
1179
+ * Parse cookies from C3C UFC Utility / EditThisCookie array format
1180
+ *
1181
+ * @param cookies - Array of cookie objects
1182
+ * @returns Parsed cookies object
1183
+ *
1184
+ * @example
1185
+ * ```typescript
1186
+ * const cookies = Utils.fromCookieArray([
1187
+ * { name: 'c_user', value: '123456' },
1188
+ * { name: 'xs', value: 'abc...' }
1189
+ * ])
1190
+ * ```
1191
+ */
1192
+ static fromCookieArray(cookies) {
1193
+ const result = {};
1194
+ for (const cookie of cookies) {
1195
+ if (cookie.name && cookie.value !== void 0) {
1196
+ result[cookie.name] = String(cookie.value);
1197
+ }
1198
+ }
1199
+ return result;
1200
+ }
1201
+ /**
1202
+ * Parse cookies from cookie header string format
1203
+ *
1204
+ * @param cookieString - Cookie string (e.g., "name1=value1; name2=value2")
1205
+ * @returns Parsed cookies object
1206
+ *
1207
+ * @example
1208
+ * ```typescript
1209
+ * const cookies = Utils.fromCookieString('c_user=123456; xs=abc...; datr=xyz...')
1210
+ * ```
1211
+ */
1212
+ static fromCookieString(cookieString) {
1213
+ const result = {};
1214
+ const pairs = cookieString.split(/;\s*/);
1215
+ for (const pair of pairs) {
1216
+ const [name, ...valueParts] = pair.split("=");
1217
+ if (name && valueParts.length > 0) {
1218
+ const trimmedName = name.trim();
1219
+ const value = valueParts.join("=").trim();
1220
+ if (trimmedName && value) {
1221
+ result[trimmedName] = value;
1222
+ }
1223
+ }
1224
+ }
1225
+ return result;
1226
+ }
1227
+ /**
1228
+ * Parse cookies from Netscape/HTTP cookie file format
1229
+ *
1230
+ * @param content - Netscape cookie file content
1231
+ * @returns Parsed cookies object
1232
+ *
1233
+ * @example
1234
+ * ```typescript
1235
+ * const cookies = Utils.fromNetscape(`
1236
+ * # Netscape HTTP Cookie File
1237
+ * .facebook.com TRUE / TRUE 1234567890 c_user 123456
1238
+ * .facebook.com TRUE / TRUE 1234567890 xs abc...
1239
+ * `)
1240
+ * ```
1241
+ */
1242
+ static fromNetscape(content) {
1243
+ const result = {};
1244
+ const lines = content.split("\n");
1245
+ for (const line of lines) {
1246
+ const trimmed = line.trim();
1247
+ if (!trimmed || trimmed.startsWith("#")) {
1248
+ continue;
1249
+ }
1250
+ const parts = trimmed.split(" ");
1251
+ if (parts.length >= 7) {
1252
+ const name = parts[5];
1253
+ const value = parts[6];
1254
+ if (name && value) {
1255
+ result[name] = value;
1256
+ }
1257
+ }
1258
+ }
1259
+ return result;
1260
+ }
1261
+ /**
1262
+ * Parse cookies from Base64 encoded string
1263
+ *
1264
+ * @param base64 - Base64 encoded cookie data
1265
+ * @returns Parsed cookies object
1266
+ */
1267
+ static fromBase64(base64) {
1268
+ const decoded = Buffer.from(base64, "base64").toString("utf-8");
1269
+ return _Utils.parseCookies(decoded);
1270
+ }
1271
+ /**
1272
+ * Convert cookies object to cookie header string
1273
+ *
1274
+ * @param cookies - Cookies object
1275
+ * @returns Cookie header string
1276
+ *
1277
+ * @example
1278
+ * ```typescript
1279
+ * const header = Utils.toCookieString({ c_user: '123456', xs: 'abc...' })
1280
+ * // Returns: "c_user=123456; xs=abc..."
1281
+ * ```
1282
+ */
1283
+ static toCookieString(cookies) {
1284
+ return Object.entries(cookies).map(([name, value]) => `${name}=${value}`).join("; ");
1285
+ }
1286
+ /**
1287
+ * Convert cookies object to array format (C3C UFC Utility style)
1288
+ *
1289
+ * @param cookies - Cookies object
1290
+ * @param domain - Cookie domain (default: .facebook.com)
1291
+ * @returns Array of cookie objects
1292
+ *
1293
+ * @example
1294
+ * ```typescript
1295
+ * const arr = Utils.toCookieArray({ c_user: '123456', xs: 'abc...' })
1296
+ * ```
1297
+ */
1298
+ static toCookieArray(cookies, domain = ".facebook.com") {
1299
+ return Object.entries(cookies).filter(([, value]) => value !== void 0).map(([name, value]) => ({
1300
+ name,
1301
+ value,
1302
+ domain,
1303
+ path: "/",
1304
+ secure: true,
1305
+ httpOnly: true
1306
+ }));
1307
+ }
1308
+ /**
1309
+ * Convert cookies object to Netscape format
1310
+ *
1311
+ * @param cookies - Cookies object
1312
+ * @param domain - Cookie domain (default: .facebook.com)
1313
+ * @returns Netscape cookie file content
1314
+ */
1315
+ static toNetscape(cookies, domain = ".facebook.com") {
1316
+ const lines = ["# Netscape HTTP Cookie File", "# Generated by meta-messenger.js", ""];
1317
+ for (const [name, value] of Object.entries(cookies)) {
1318
+ const expiration = Math.floor(Date.now() / 1e3) + 365 * 24 * 60 * 60;
1319
+ lines.push(`${domain} TRUE / TRUE ${expiration} ${name} ${value}`);
1320
+ }
1321
+ return lines.join("\n");
1322
+ }
1323
+ /**
1324
+ * Convert cookies to Base64 encoded JSON
1325
+ *
1326
+ * @param cookies - Cookies object
1327
+ * @returns Base64 encoded string
1328
+ */
1329
+ static toBase64(cookies) {
1330
+ return Buffer.from(JSON.stringify(cookies)).toString("base64");
1331
+ }
1332
+ /**
1333
+ * Filter cookies to only essential ones for Facebook/Messenger
1334
+ *
1335
+ * @param cookies - Cookies object
1336
+ * @returns Filtered cookies with only essential keys
1337
+ */
1338
+ static filterEssential(cookies) {
1339
+ const essential = ["c_user", "xs", "datr", "fr", "sb", "wd", "presence"];
1340
+ const result = {};
1341
+ for (const key of essential) {
1342
+ if (cookies[key]) {
1343
+ result[key] = cookies[key];
1344
+ }
1345
+ }
1346
+ return result;
1347
+ }
1348
+ /**
1349
+ * Validate that cookies contain required fields
1350
+ *
1351
+ * @param cookies - Cookies object
1352
+ * @returns True if cookies are valid
1353
+ */
1354
+ static validate(cookies) {
1355
+ const required = ["c_user", "xs"];
1356
+ return required.every((key) => cookies[key] && cookies[key].length > 0);
1357
+ }
1358
+ /**
1359
+ * Get missing required cookies
1360
+ *
1361
+ * @param cookies - Cookies object
1362
+ * @returns Array of missing cookie names
1363
+ */
1364
+ static getMissing(cookies) {
1365
+ const required = ["c_user", "xs"];
1366
+ return required.filter((key) => !cookies[key] || cookies[key].length === 0);
1367
+ }
1368
+ /**
1369
+ * Check if a string is valid Base64
1370
+ */
1371
+ static isBase64(str) {
1372
+ if (str.length < 4) return false;
1373
+ const base64Regex = /^[A-Za-z0-9+/]+=*$/;
1374
+ if (!base64Regex.test(str)) return false;
1375
+ return str.length > 20 && str.length % 4 === 0;
1376
+ }
1377
+ };
1378
+ var THUMBS_UP_STICKER_IDS = {
1379
+ SMALL: 369239263222822,
1380
+ MEDIUM: 369239343222814,
1381
+ LARGE: 369239383222810
1382
+ };
1383
+ function isThumbsUpSticker(stickerId) {
1384
+ if (!stickerId) return false;
1385
+ return stickerId === THUMBS_UP_STICKER_IDS.SMALL || stickerId === THUMBS_UP_STICKER_IDS.MEDIUM || stickerId === THUMBS_UP_STICKER_IDS.LARGE;
1386
+ }
1387
+ __name(isThumbsUpSticker, "isThumbsUpSticker");
1388
+ function extractUrlFromLPHP(url) {
1389
+ if (!url) return url;
1390
+ try {
1391
+ const parsed = new URL(url);
1392
+ if (parsed.pathname === "/l.php" || parsed.pathname.endsWith("/l.php")) {
1393
+ const actualUrl = parsed.searchParams.get("u");
1394
+ if (actualUrl) return actualUrl;
1395
+ }
1396
+ } catch {
1397
+ }
1398
+ return url;
1399
+ }
1400
+ __name(extractUrlFromLPHP, "extractUrlFromLPHP");
1401
+
1402
+ // src/index.ts
1403
+ async function login(cookies, options) {
1404
+ const client = new Client(cookies, options);
1405
+ await client.connect();
1406
+ return client;
1407
+ }
1408
+ __name(login, "login");
1409
+ function createClient(cookies, options) {
1410
+ return new Client(cookies, options);
1411
+ }
1412
+ __name(createClient, "createClient");
1413
+ var index_default = { Client, login, createClient };
1414
+ export {
1415
+ Client,
1416
+ THUMBS_UP_STICKER_IDS,
1417
+ ThreadType,
1418
+ UIDLogin,
1419
+ Utils,
1420
+ createClient,
1421
+ index_default as default,
1422
+ extractUrlFromLPHP,
1423
+ isThumbsUpSticker,
1424
+ login
1425
+ };
1426
+ //# sourceMappingURL=index.js.map