fca-riyad 37.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 (242) hide show
  1. package/CHANGELOG.md +325 -0
  2. package/DOCS.md +2712 -0
  3. package/README.md +410 -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 +1359 -0
  12. package/module/options.js +44 -0
  13. package/package.json +95 -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 +90 -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 +49 -0
  51. package/src/api/messaging/markAsSeen.js +61 -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 +74 -0
  61. package/src/api/messaging/sessionGuard.js +130 -0
  62. package/src/api/messaging/setMessageReaction.js +95 -0
  63. package/src/api/messaging/setTitle.js +124 -0
  64. package/src/api/messaging/shareContact.js +49 -0
  65. package/src/api/messaging/threadColors.js +128 -0
  66. package/src/api/messaging/unsendMessage.js +81 -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/e2ee/crypto.js +173 -0
  78. package/src/api/socket/e2ee/e2ee/index.js +421 -0
  79. package/src/api/socket/e2ee/e2ee/proto/ArmadilloApplication.proto +281 -0
  80. package/src/api/socket/e2ee/e2ee/proto/ArmadilloICDC.proto +14 -0
  81. package/src/api/socket/e2ee/e2ee/proto/ConsumerApplication.proto +232 -0
  82. package/src/api/socket/e2ee/e2ee/proto/MessageApplication.proto +82 -0
  83. package/src/api/socket/e2ee/e2ee/proto/MessageTransport.proto +77 -0
  84. package/src/api/socket/e2ee/e2ee/proto/WACommon.proto +66 -0
  85. package/src/api/socket/e2ee/e2ee/proto/WAMediaTransport.proto +176 -0
  86. package/src/api/socket/e2ee/e2ee/proto/proto-writer.ts +76 -0
  87. package/src/api/socket/e2ee/e2ee/protocol.js +196 -0
  88. package/src/api/socket/e2ee/e2ee/ratchet.js +219 -0
  89. package/src/api/socket/e2ee/e2ee/store.js +182 -0
  90. package/src/api/socket/e2ee/e2ee/vendor/fb-e2ee.cjs +9162 -0
  91. package/src/api/socket/e2ee/e2ee/vendor/proto/ArmadilloApplication.proto +281 -0
  92. package/src/api/socket/e2ee/e2ee/vendor/proto/ArmadilloICDC.proto +14 -0
  93. package/src/api/socket/e2ee/e2ee/vendor/proto/ConsumerApplication.proto +232 -0
  94. package/src/api/socket/e2ee/e2ee/vendor/proto/MessageApplication.proto +82 -0
  95. package/src/api/socket/e2ee/e2ee/vendor/proto/MessageTransport.proto +77 -0
  96. package/src/api/socket/e2ee/e2ee/vendor/proto/WACommon.proto +66 -0
  97. package/src/api/socket/e2ee/e2ee/vendor/proto/WAMediaTransport.proto +176 -0
  98. package/src/api/socket/e2ee/e2ee/vendor/proto/proto-writer.ts +76 -0
  99. package/src/api/socket/e2ee/index.js +437 -0
  100. package/src/api/socket/e2ee/proto/ArmadilloApplication.proto +281 -0
  101. package/src/api/socket/e2ee/proto/ArmadilloICDC.proto +14 -0
  102. package/src/api/socket/e2ee/proto/ConsumerApplication.proto +232 -0
  103. package/src/api/socket/e2ee/proto/MessageApplication.proto +82 -0
  104. package/src/api/socket/e2ee/proto/MessageTransport.proto +77 -0
  105. package/src/api/socket/e2ee/proto/WACommon.proto +66 -0
  106. package/src/api/socket/e2ee/proto/WAMediaTransport.proto +176 -0
  107. package/src/api/socket/e2ee/proto/proto-writer.ts +76 -0
  108. package/src/api/socket/e2ee/protocol.js +196 -0
  109. package/src/api/socket/e2ee/ratchet.js +219 -0
  110. package/src/api/socket/e2ee/store.js +182 -0
  111. package/src/api/socket/e2ee/vendor/fb-e2ee.cjs +9162 -0
  112. package/src/api/socket/e2ee/vendor/proto/ArmadilloApplication.proto +281 -0
  113. package/src/api/socket/e2ee/vendor/proto/ArmadilloICDC.proto +14 -0
  114. package/src/api/socket/e2ee/vendor/proto/ConsumerApplication.proto +232 -0
  115. package/src/api/socket/e2ee/vendor/proto/MessageApplication.proto +82 -0
  116. package/src/api/socket/e2ee/vendor/proto/MessageTransport.proto +77 -0
  117. package/src/api/socket/e2ee/vendor/proto/WACommon.proto +66 -0
  118. package/src/api/socket/e2ee/vendor/proto/WAMediaTransport.proto +176 -0
  119. package/src/api/socket/e2ee/vendor/proto/proto-writer.ts +76 -0
  120. package/src/api/socket/listenE2EE.js +75 -0
  121. package/src/api/socket/listenMqtt.js +388 -0
  122. package/src/api/socket/middleware/index.js +216 -0
  123. package/src/api/socket/sendMessage.js +309 -0
  124. package/src/api/socket/sendMessageMqtt.js +69 -0
  125. package/src/api/threads/getThreadHistory.js +664 -0
  126. package/src/api/threads/getThreadInfo.js +296 -0
  127. package/src/api/threads/getThreadList.js +293 -0
  128. package/src/api/threads/getThreadPictures.js +78 -0
  129. package/src/api/users/getUserID.js +65 -0
  130. package/src/api/users/getUserInfo.js +402 -0
  131. package/src/api/users/getUserInfoV2.js +134 -0
  132. package/src/app/MessengerBot.js +209 -0
  133. package/src/app/MessengerContext.js +32 -0
  134. package/src/app/createFcaClient.js +136 -0
  135. package/src/app/threadInfoRealtimeSync.js +284 -0
  136. package/src/core/sendReqMqtt.js +96 -0
  137. package/src/database/helpers.js +53 -0
  138. package/src/database/models/index.js +88 -0
  139. package/src/database/models/thread.js +50 -0
  140. package/src/database/models/user.js +46 -0
  141. package/src/database/threadData.js +94 -0
  142. package/src/database/userData.js +98 -0
  143. package/src/remote/remoteClient.js +123 -0
  144. package/src/utils/broadcast.js +51 -0
  145. package/src/utils/client.js +10 -0
  146. package/src/utils/constants.js +23 -0
  147. package/src/utils/cookies.js +68 -0
  148. package/src/utils/format/attachment.js +357 -0
  149. package/src/utils/format/cookie.js +9 -0
  150. package/src/utils/format/date.js +50 -0
  151. package/src/utils/format/decode.js +44 -0
  152. package/src/utils/format/delta.js +194 -0
  153. package/src/utils/format/ids.js +64 -0
  154. package/src/utils/format/index.js +64 -0
  155. package/src/utils/format/message.js +88 -0
  156. package/src/utils/format/presence.js +132 -0
  157. package/src/utils/format/readTyp.js +44 -0
  158. package/src/utils/format/thread.js +42 -0
  159. package/src/utils/format/utils.js +141 -0
  160. package/src/utils/headers.js +115 -0
  161. package/src/utils/loginParser/autoLogin.js +125 -0
  162. package/src/utils/loginParser/helpers.js +43 -0
  163. package/src/utils/loginParser/index.js +10 -0
  164. package/src/utils/loginParser/parseAndCheckLogin.js +220 -0
  165. package/src/utils/loginParser/textUtils.js +28 -0
  166. package/src/utils/nexca-logger.js +144 -0
  167. package/src/utils/nexca-utils.js +686 -0
  168. package/src/utils/request/client.js +26 -0
  169. package/src/utils/request/config.js +23 -0
  170. package/src/utils/request/defaults.js +46 -0
  171. package/src/utils/request/helpers.js +46 -0
  172. package/src/utils/request/index.js +17 -0
  173. package/src/utils/request/methods.js +163 -0
  174. package/src/utils/request/proxy.js +21 -0
  175. package/src/utils/request/retry.js +77 -0
  176. package/src/utils/request/sanitize.js +49 -0
  177. package/src/utils/versionCheck.js +47 -0
  178. package/src/vendor/fca-unofficial/.github/workflows/nodejs.yml +26 -0
  179. package/src/vendor/fca-unofficial/.github/workflows/npmpublish.yml +30 -0
  180. package/src/vendor/fca-unofficial/.travis.yml +6 -0
  181. package/src/vendor/fca-unofficial/DOCS.md +1731 -0
  182. package/src/vendor/fca-unofficial/LICENSE-MIT +21 -0
  183. package/src/vendor/fca-unofficial/README.md +225 -0
  184. package/src/vendor/fca-unofficial/index.js +619 -0
  185. package/src/vendor/fca-unofficial/package.json +76 -0
  186. package/src/vendor/fca-unofficial/src/addExternalModule.js +19 -0
  187. package/src/vendor/fca-unofficial/src/addUserToGroup.js +113 -0
  188. package/src/vendor/fca-unofficial/src/changeAdminStatus.js +79 -0
  189. package/src/vendor/fca-unofficial/src/changeArchivedStatus.js +55 -0
  190. package/src/vendor/fca-unofficial/src/changeBio.js +77 -0
  191. package/src/vendor/fca-unofficial/src/changeBlockedStatus.js +47 -0
  192. package/src/vendor/fca-unofficial/src/changeGroupImage.js +129 -0
  193. package/src/vendor/fca-unofficial/src/changeNickname.js +59 -0
  194. package/src/vendor/fca-unofficial/src/changeThreadColor.js +71 -0
  195. package/src/vendor/fca-unofficial/src/changeThreadEmoji.js +55 -0
  196. package/src/vendor/fca-unofficial/src/createNewGroup.js +86 -0
  197. package/src/vendor/fca-unofficial/src/createPoll.js +71 -0
  198. package/src/vendor/fca-unofficial/src/deleteMessage.js +56 -0
  199. package/src/vendor/fca-unofficial/src/deleteThread.js +56 -0
  200. package/src/vendor/fca-unofficial/src/forwardAttachment.js +60 -0
  201. package/src/vendor/fca-unofficial/src/getCurrentUserID.js +7 -0
  202. package/src/vendor/fca-unofficial/src/getEmojiUrl.js +29 -0
  203. package/src/vendor/fca-unofficial/src/getFriendsList.js +84 -0
  204. package/src/vendor/fca-unofficial/src/getThreadHistory.js +645 -0
  205. package/src/vendor/fca-unofficial/src/getThreadHistoryDeprecated.js +93 -0
  206. package/src/vendor/fca-unofficial/src/getThreadInfo.js +201 -0
  207. package/src/vendor/fca-unofficial/src/getThreadInfoDeprecated.js +80 -0
  208. package/src/vendor/fca-unofficial/src/getThreadList.js +238 -0
  209. package/src/vendor/fca-unofficial/src/getThreadListDeprecated.js +75 -0
  210. package/src/vendor/fca-unofficial/src/getThreadPictures.js +79 -0
  211. package/src/vendor/fca-unofficial/src/getUserID.js +66 -0
  212. package/src/vendor/fca-unofficial/src/getUserInfo.js +72 -0
  213. package/src/vendor/fca-unofficial/src/handleFriendRequest.js +61 -0
  214. package/src/vendor/fca-unofficial/src/handleMessageRequest.js +65 -0
  215. package/src/vendor/fca-unofficial/src/httpGet.js +52 -0
  216. package/src/vendor/fca-unofficial/src/httpPost.js +52 -0
  217. package/src/vendor/fca-unofficial/src/listenMqtt.js +788 -0
  218. package/src/vendor/fca-unofficial/src/logout.js +75 -0
  219. package/src/vendor/fca-unofficial/src/markAsDelivered.js +58 -0
  220. package/src/vendor/fca-unofficial/src/markAsRead.js +80 -0
  221. package/src/vendor/fca-unofficial/src/markAsReadAll.js +50 -0
  222. package/src/vendor/fca-unofficial/src/markAsSeen.js +59 -0
  223. package/src/vendor/fca-unofficial/src/muteThread.js +52 -0
  224. package/src/vendor/fca-unofficial/src/removeUserFromGroup.js +79 -0
  225. package/src/vendor/fca-unofficial/src/resolvePhotoUrl.js +45 -0
  226. package/src/vendor/fca-unofficial/src/searchForThread.js +53 -0
  227. package/src/vendor/fca-unofficial/src/sendMessage.js +459 -0
  228. package/src/vendor/fca-unofficial/src/sendTypingIndicator.js +103 -0
  229. package/src/vendor/fca-unofficial/src/setMessageReaction.js +117 -0
  230. package/src/vendor/fca-unofficial/src/setPostReaction.js +76 -0
  231. package/src/vendor/fca-unofficial/src/setTitle.js +86 -0
  232. package/src/vendor/fca-unofficial/src/threadColors.js +57 -0
  233. package/src/vendor/fca-unofficial/src/unfriend.js +52 -0
  234. package/src/vendor/fca-unofficial/src/unsendMessage.js +49 -0
  235. package/src/vendor/fca-unofficial/test/data/shareAttach.js +146 -0
  236. package/src/vendor/fca-unofficial/test/data/something.mov +0 -0
  237. package/src/vendor/fca-unofficial/test/data/test.png +0 -0
  238. package/src/vendor/fca-unofficial/test/data/test.txt +7 -0
  239. package/src/vendor/fca-unofficial/test/example-config.json +18 -0
  240. package/src/vendor/fca-unofficial/test/test-page.js +140 -0
  241. package/src/vendor/fca-unofficial/test/test.js +385 -0
  242. package/src/vendor/fca-unofficial/utils.js +1358 -0
@@ -0,0 +1,437 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * NEXCA E2EE Bridge — Signal Protocol + Noise WebSocket
5
+ *
6
+ * Uses NEXCA's own native E2EE engine bundled at ./vendor/fb-e2ee.cjs.
7
+ * No external npm package needed — all E2EE code is owned by NEXCA.
8
+ *
9
+ * Direct deps (fca/package.json): @signalapp/libsignal-client, protobufjs,
10
+ * @noble/curves, @noble/hashes
11
+ *
12
+ * Full protocol stack:
13
+ * • @signalapp/libsignal-client — Signal Protocol (Double Ratchet)
14
+ * • Noise_XX_25519_AESGCM_SHA256 WebSocket handshake
15
+ * • WA-binary + Protobuf message encoding
16
+ * • ICDC device registration with Facebook
17
+ */
18
+
19
+ const path = require("path");
20
+ const logger = require("../../../utils/nexca-logger");
21
+
22
+ function loadFBClient() {
23
+ try {
24
+ const vendorPath = path.join(__dirname, "vendor", "fb-e2ee.cjs");
25
+ return require(vendorPath).FBClient;
26
+ } catch (err) {
27
+ throw new Error(
28
+ "NEXCA E2EE engine failed to load.\n" +
29
+ " Expected at: fca/src/e2ee/vendor/fb-e2ee.cjs\n" +
30
+ " Cause: " + err.message
31
+ );
32
+ }
33
+ }
34
+
35
+ class E2EEBridge {
36
+ constructor(ctx, api, defaultFuncs) {
37
+ this.ctx = ctx;
38
+ this.api = api;
39
+ this.defaultFuncs = defaultFuncs || null;
40
+ this.client = null;
41
+ this.connected = false;
42
+ this._messageCallback = null;
43
+ }
44
+
45
+ // ─────────────────────────────────────────────────────────────────────────
46
+ // Setup
47
+ // ─────────────────────────────────────────────────────────────────────────
48
+
49
+ async connect(deviceStorePath, userId) {
50
+ // Guard against concurrent/duplicate connect() calls (e.g. bot code
51
+ // calling api.connectE2EE() explicitly while auto-connect is also
52
+ // running) — without this, two parallel connects race and corrupt
53
+ // `this.client`, causing undefined responses downstream.
54
+ if (this._connectPromise) return this._connectPromise;
55
+ if (this.connected) return Promise.resolve({ userId: this.ctx.userID });
56
+
57
+ this._connectPromise = this._doConnect(deviceStorePath, userId)
58
+ .catch((err) => {
59
+ this._connectPromise = null;
60
+ throw err;
61
+ });
62
+ return this._connectPromise;
63
+ }
64
+
65
+ async _doConnect(deviceStorePath, userId) {
66
+ const fs = require("fs");
67
+
68
+ userId = userId || this.ctx.userID;
69
+
70
+ // Default to .nexca/e2ee_device.json in the user's project directory
71
+ if (!deviceStorePath) {
72
+ deviceStorePath = path.join(process.cwd(), ".nexca", "e2ee_device.json");
73
+ }
74
+
75
+ // Auto-create parent directory — users don't need to create it manually
76
+ try {
77
+ fs.mkdirSync(path.dirname(deviceStorePath), { recursive: true });
78
+ } catch (_) {}
79
+
80
+ logger.info("E2EE", "Device store: " + deviceStorePath);
81
+
82
+ const FBClient = loadFBClient();
83
+
84
+ // Re-use the already-loaded session from NEXCA's cookie jar
85
+ const appState = this.api.getAppState();
86
+
87
+ this.client = new FBClient({
88
+ appState,
89
+ platform: "facebook",
90
+ });
91
+
92
+ // Build the NEXCA adapter that replaces fb-messenger-e2ee's internal
93
+ // fca-unofficial login. fb-messenger-e2ee calls
94
+ // this.gateway.login(appState)
95
+ // inside connect() which triggers a *second* fca-unofficial login and
96
+ // crashes when the network call returns undefined.
97
+ // By replacing gateway.login BEFORE connect() we prevent that entirely.
98
+ const _ctx = this.ctx;
99
+ const _nexcaApi = this.api;
100
+ const _defaultFuncs = this.defaultFuncs;
101
+
102
+ const _nexcaAdapter = {
103
+ fb_dtsg: _ctx.fb_dtsg,
104
+ getCurrentUserID: () => _nexcaApi.getCurrentUserID(),
105
+ getAppState: () => _nexcaApi.getAppState(),
106
+ httpPost: async function (url, form) {
107
+ var merged = Object.assign({}, form);
108
+ if (!merged.fb_dtsg && _ctx.fb_dtsg) merged.fb_dtsg = _ctx.fb_dtsg;
109
+ if (!merged.__user) merged.__user = _ctx.userID;
110
+ const res = _defaultFuncs
111
+ ? await _defaultFuncs.post(url, _ctx.jar, merged)
112
+ : await new Promise(function (resolve, reject) {
113
+ require("request")({ method: "POST", url: url, jar: _ctx.jar, form: merged, gzip: true },
114
+ function (err, r) { if (err) return reject(err); resolve(r); });
115
+ });
116
+ return res && res.body;
117
+ },
118
+ // listenMqtt — fb-e2ee.cjs calls this to route non-E2EE MQTT events.
119
+ // NEXCA has its own separate MQTT listener so we provide a no-op stub
120
+ // to prevent a competing second listener from being started.
121
+ listenMqtt: function () { return; },
122
+ stopListenMqtt: function () {},
123
+ setOptions: function () {},
124
+ // Delegate messaging methods to NEXCA's real api
125
+ sendMessage: function (msg, threadID, callback, replyToMessage) {
126
+ return _nexcaApi.sendMessage(msg, threadID, callback, replyToMessage);
127
+ },
128
+ setMessageReaction: function (reaction, messageID, callback, force) {
129
+ if (typeof _nexcaApi.setMessageReaction === "function")
130
+ return _nexcaApi.setMessageReaction(reaction, messageID, callback, force);
131
+ },
132
+ unsendMessage: function (messageID, callback) {
133
+ if (typeof _nexcaApi.unsendMessage === "function")
134
+ return _nexcaApi.unsendMessage(messageID, callback);
135
+ },
136
+ sendTypingIndicator: function (isTyping, threadID, callback) {
137
+ if (typeof _nexcaApi.sendTypingIndicator === "function")
138
+ return _nexcaApi.sendTypingIndicator(isTyping, threadID, callback);
139
+ },
140
+ markAsRead: function (threadID, read, callback) {
141
+ if (typeof _nexcaApi.markAsRead === "function")
142
+ return _nexcaApi.markAsRead(threadID, read, callback);
143
+ },
144
+ muteThread: function (threadID, muteSeconds, callback) {
145
+ if (typeof _nexcaApi.muteThread === "function")
146
+ return _nexcaApi.muteThread(threadID, muteSeconds, callback);
147
+ },
148
+ setTitle: function (newTitle, threadID, callback) {
149
+ if (typeof _nexcaApi.setTitle === "function")
150
+ return _nexcaApi.setTitle(newTitle, threadID, callback);
151
+ },
152
+ changeGroupImage: function (image, threadID, callback) {
153
+ if (typeof _nexcaApi.changeGroupImage === "function")
154
+ return _nexcaApi.changeGroupImage(image, threadID, callback);
155
+ },
156
+ };
157
+
158
+ // Inject the adapter as a global so that fb-e2ee.cjs's internal
159
+ // require("../../vendor/fca-unofficial") login() intercepts it and
160
+ // skips the real Facebook login, returning NEXCA's session instead.
161
+ global._nexcaE2EEAdapter = _nexcaAdapter;
162
+
163
+ logger.info("E2EE", "Bootstrapping auth via vendored E2EE engine (NEXCA session)...");
164
+ let resolvedUserId;
165
+ try {
166
+ const connectResult = await this.client.connect();
167
+ resolvedUserId = connectResult && connectResult.userId;
168
+ } finally {
169
+ delete global._nexcaE2EEAdapter;
170
+ }
171
+
172
+ // Also patch controller.api (used for CAT/ICDC calls after connect)
173
+ if (this.client.controller) {
174
+ this.client.controller.api = _nexcaAdapter;
175
+ }
176
+
177
+ logger.info("E2EE", "Opening Noise WebSocket (Signal Protocol)...");
178
+ const _e2eeDevicePath = deviceStorePath;
179
+ const _e2eeUserId = resolvedUserId || userId;
180
+ await this.client.connectE2EE(_e2eeDevicePath, _e2eeUserId);
181
+
182
+ // Auto-reconnect the Noise WebSocket whenever it drops unexpectedly.
183
+ // `this.connected` stays true through reconnect cycles; it's only set
184
+ // false by an explicit api.e2ee.disconnect() call.
185
+ const _self = this;
186
+ this.client.onEvent("disconnected", function _onE2EEDisconnected() {
187
+ if (!_self.connected) return; // intentional disconnect — skip
188
+ logger.warn("E2EE", "Noise WebSocket disconnected — reconnecting in 5s...");
189
+ setTimeout(async function () {
190
+ if (!_self.connected) return;
191
+ try {
192
+ await _self.client.connectE2EE(_e2eeDevicePath, _e2eeUserId);
193
+ logger.success("E2EE", "E2EE WebSocket reconnected.");
194
+ } catch (err) {
195
+ logger.error("E2EE", "E2EE reconnect failed: " + (err && err.message ? err.message : String(err)));
196
+ // Retry again after another 10s if reconnect failed
197
+ if (_self.connected) {
198
+ setTimeout(async function () {
199
+ if (!_self.connected) return;
200
+ try {
201
+ await _self.client.connectE2EE(_e2eeDevicePath, _e2eeUserId);
202
+ logger.success("E2EE", "E2EE WebSocket reconnected (retry).");
203
+ } catch (err2) {
204
+ logger.error("E2EE", "E2EE reconnect retry failed: " + (err2 && err2.message ? err2.message : String(err2)));
205
+ }
206
+ }, 10000);
207
+ }
208
+ }
209
+ }, 5000);
210
+ });
211
+
212
+ // Forward incoming E2EE messages to the registered callback.
213
+ this.client.onEvent("e2ee_message", (msg) => {
214
+ if (!this._messageCallback) return;
215
+
216
+ const senderID =
217
+ msg.senderId ||
218
+ (typeof msg.senderJid === "string" ? msg.senderJid.split(".")[0] : "");
219
+
220
+ // Build mentions: vendor surfaces an array [{ id, text }] or object
221
+ var mentions = {};
222
+ if (Array.isArray(msg.mentions)) {
223
+ msg.mentions.forEach(function(m) {
224
+ if (m && m.id) mentions[m.id] = m.text || "@" + m.id;
225
+ });
226
+ } else if (msg.mentions && typeof msg.mentions === "object") {
227
+ mentions = msg.mentions;
228
+ }
229
+
230
+ const isReply = !!(msg.replyTo && msg.replyTo.messageId);
231
+
232
+ const event = {
233
+ type: isReply ? "message_reply" : "message",
234
+ senderID,
235
+ threadID: msg.threadId,
236
+ body: msg.text || "",
237
+ isE2EE: true,
238
+ isGroup: !!msg.isGroup,
239
+ timestamp: msg.timestampMs || Date.now(),
240
+ messageID: msg.id || "",
241
+ attachments: [],
242
+ mentions,
243
+ args: (msg.text || "").trim().split(/\s+/).filter(Boolean),
244
+ };
245
+
246
+ // Populate ctx.threadTypes so that sendMessage.js can detect this
247
+ // as a DM and route attachments through OldMessage (not MQTT).
248
+ // E2EE DM messages arrive via the Noise WebSocket — not MQTT — so
249
+ // parseDelta never sees them and ctx.threadTypes stays empty unless
250
+ // we populate it here.
251
+ if (!event.isGroup && msg.threadId) {
252
+ this.ctx.threadTypes = this.ctx.threadTypes || {};
253
+ this.ctx.threadTypes[String(msg.threadId)] = 'dm';
254
+ }
255
+
256
+ // Populate messageReply so reply handlers work the same as in MQTT
257
+ if (isReply) {
258
+ event.messageReply = {
259
+ messageID: msg.replyTo.messageId,
260
+ senderID: msg.replyTo.senderId || "",
261
+ threadID: msg.threadId,
262
+ body: msg.replyTo.text || "",
263
+ args: (msg.replyTo.text || "").trim().split(/\s+/).filter(Boolean),
264
+ isE2EE: true,
265
+ isGroup: !!msg.isGroup,
266
+ mentions: {},
267
+ attachments: []
268
+ };
269
+ }
270
+
271
+ this._messageCallback(null, event);
272
+ });
273
+
274
+ // Surface connection errors so the bot log shows them.
275
+ // Silently ignore DuplicatedMessage errors — these are harmless replays on reconnect.
276
+ this.client.onEvent("error", (err) => {
277
+ if (err && (err.code === 1 || (err.message && err.message.includes("old counter")))) return;
278
+ logger.error("E2EE", "E2EE error: " + (err && err.message ? err.message : String(err)));
279
+ });
280
+
281
+ this.connected = true;
282
+ logger.success("E2EE", "E2EE active — Signal Protocol / Noise WebSocket (vendored)");
283
+ return this;
284
+ }
285
+
286
+ ensureConnected() {
287
+ if (!this.connected || !this.client) {
288
+ throw new Error("E2EE not connected. Call api.connectE2EE() first.");
289
+ }
290
+ }
291
+
292
+ isConnected() {
293
+ return this.connected;
294
+ }
295
+
296
+ // ─────────────────────────────────────────────────────────────────────────
297
+ // Send API (all go through the Noise WebSocket)
298
+ // ─────────────────────────────────────────────────────────────────────────
299
+
300
+ /**
301
+ * Send a message on an E2EE (or non-E2EE) thread.
302
+ *
303
+ * msg can be:
304
+ * - string → plain text
305
+ * - { body, attachment} → text + one or more readable streams
306
+ *
307
+ * Attachments on E2EE threads are encrypted and sent via the Noise WebSocket.
308
+ * Attachments on non-E2EE threads fall back to api.sendMessage (NEXCA's own).
309
+ */
310
+ async sendMessage(threadId, msg, replyToMessageId) {
311
+ this.ensureConnected();
312
+
313
+ const text = typeof msg === "string" ? msg : (msg && msg.body != null ? String(msg.body) : "");
314
+ const attachment = (msg && typeof msg === "object") ? (msg.attachment || null) : null;
315
+
316
+ if (!attachment) {
317
+ return this.client.sendMessage({ threadId, text, replyToMessageId });
318
+ }
319
+
320
+ // Always use the vendor's Noise WebSocket path for attachments.
321
+ // The isE2EEThreadId() check was removed because:
322
+ // 1. It frequently returns false for valid E2EE DM thread IDs (user-id format),
323
+ // which caused fallback to api.sendMessage → MQTT, where Facebook strips
324
+ // attachment_fbids from the E2EE envelope → attachment silently dropped.
325
+ // 2. This method is only called when the thread is KNOWN to be an E2EE DM
326
+ // (sendMessage.js routes here only when isSingleUser=true AND e2ee.isConnected()).
327
+ // 3. client.sendImage/sendVideo/sendAudio handle the JID conversion internally
328
+ // (100055943906136 → 100055943906136.0@msgr) so the threadId format is fine.
329
+
330
+ // E2EE path: read stream(s) → Buffer, detect type, send via vendor
331
+ const path = require("path");
332
+ let mime;
333
+ try { mime = require("mime"); } catch (_) {}
334
+
335
+ const list = Array.isArray(attachment) ? attachment : [attachment];
336
+ const results = [];
337
+
338
+ for (const stream of list) {
339
+ const data = await _streamToBuffer(stream);
340
+ const fileName = (stream.path ? path.basename(String(stream.path)) : "file.bin");
341
+ const mimeType = (mime && mime.getType(fileName)) || _guessMime(fileName);
342
+
343
+ const input = { threadId, data, fileName, mimeType, caption: text || undefined, replyToMessageId };
344
+
345
+ console.log(`[E2EEBridge] sendMessage attachment: fileName=${fileName}, mimeType=${mimeType}, size=${data.length} bytes, threadId=${threadId}`);
346
+ let result;
347
+ if (mimeType.startsWith("image/")) {
348
+ result = await this.client.sendImage(input);
349
+ } else if (mimeType.startsWith("video/")) {
350
+ result = await this.client.sendVideo(input);
351
+ } else if (mimeType.startsWith("audio/")) {
352
+ result = await this.client.sendAudio(input);
353
+ } else {
354
+ result = await this.client.sendFile(input);
355
+ }
356
+ console.log(`[E2EEBridge] send result:`, JSON.stringify(result));
357
+ results.push(result);
358
+ }
359
+
360
+ return results.length === 1 ? results[0] : results;
361
+ }
362
+
363
+ async sendReaction(threadId, messageId, reaction, senderJid) {
364
+ this.ensureConnected();
365
+ return this.client.sendReaction({ threadId, messageId, reaction, senderJid });
366
+ }
367
+
368
+ async sendTyping(threadId, isTyping) {
369
+ this.ensureConnected();
370
+ return this.client.sendTyping({ threadId, isTyping: isTyping !== false });
371
+ }
372
+
373
+ async unsendMessage(messageId, threadId) {
374
+ this.ensureConnected();
375
+ return this.client.unsendMessage({ messageId, threadId });
376
+ }
377
+
378
+ async editMessage(threadId, messageId, newText) {
379
+ this.ensureConnected();
380
+ return this.client.editMessage({ threadId, messageId, newText });
381
+ }
382
+
383
+ // ─────────────────────────────────────────────────────────────────────────
384
+ // Receive API
385
+ // ─────────────────────────────────────────────────────────────────────────
386
+
387
+ onMessage(callback) {
388
+ this._messageCallback = callback;
389
+ }
390
+
391
+ // ─────────────────────────────────────────────────────────────────────────
392
+ // Info / lifecycle
393
+ // ─────────────────────────────────────────────────────────────────────────
394
+
395
+ getPublicKeys() {
396
+ return {
397
+ info: "Keys managed by vendored E2EE engine (Signal Protocol / Noise handshake).",
398
+ note: "Identity + device keys are stored in the device-store file. Do NOT delete it.",
399
+ };
400
+ }
401
+
402
+ async disconnect() {
403
+ if (this.client) {
404
+ try { await this.client.disconnect(); } catch (_) {}
405
+ }
406
+ this.connected = false;
407
+ logger.info("E2EE", "E2EE disconnected.");
408
+ }
409
+ }
410
+
411
+ module.exports = { E2EEBridge };
412
+
413
+ // ─────────────────────────────────────────────────────────────────────────────
414
+ // Private helpers
415
+ // ─────────────────────────────────────────────────────────────────────────────
416
+
417
+ function _streamToBuffer(stream) {
418
+ return new Promise(function (resolve, reject) {
419
+ var chunks = [];
420
+ stream.on("data", function (c) { chunks.push(c); });
421
+ stream.on("end", function () { resolve(Buffer.concat(chunks)); });
422
+ stream.on("error", reject);
423
+ });
424
+ }
425
+
426
+ function _guessMime(fileName) {
427
+ var ext = (fileName || "").split(".").pop().toLowerCase();
428
+ var map = {
429
+ jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif",
430
+ webp: "image/webp", mp4: "video/mp4", mov: "video/quicktime",
431
+ avi: "video/x-msvideo", mkv: "video/x-matroska",
432
+ mp3: "audio/mpeg", ogg: "audio/ogg", wav: "audio/wav",
433
+ m4a: "audio/mp4", aac: "audio/aac", opus: "audio/ogg; codecs=opus",
434
+ pdf: "application/pdf", zip: "application/zip"
435
+ };
436
+ return map[ext] || "application/octet-stream";
437
+ }