hridoy-ultimate 8.0.2 → 9.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 (281) hide show
  1. package/CHANGELOG.md +68 -40
  2. package/LICENSE +23 -0
  3. package/MERGE_REPORT.md +54 -0
  4. package/README.md +158 -113
  5. package/THIRD_PARTY_NOTICES.md +36 -0
  6. package/docs/TEST_RESULTS.md +68 -0
  7. package/docs/UPSTREAM_API_DOCS.md +2712 -0
  8. package/func/checkUpdate.js +6 -0
  9. package/func/logAdapter.js +33 -0
  10. package/func/logger.js +48 -0
  11. package/index.d.ts +812 -0
  12. package/index.js +30 -824
  13. package/module/config.js +46 -0
  14. package/module/login.js +136 -0
  15. package/module/loginHelper.js +1413 -0
  16. package/module/options.js +55 -0
  17. package/package.json +75 -22
  18. package/scripts/run-tests.js +19 -0
  19. package/scripts/syntax-check.js +22 -0
  20. package/src/{addExternalModule.js → api/action/addExternalModule.js} +8 -8
  21. package/src/{changeAvatar.js → api/action/changeAvatar.js} +32 -33
  22. package/src/{changeBio.js → api/action/changeBio.js} +19 -22
  23. package/src/api/action/dismissScrapingWarning.js +50 -0
  24. package/src/api/action/enableAutoSaveAppState.js +73 -0
  25. package/src/{getCurrentUserID.js → api/action/getCurrentUserID.js} +1 -1
  26. package/src/api/action/handleFriendRequest.js +57 -0
  27. package/src/api/action/logout.js +95 -0
  28. package/src/api/action/refreshFb_dtsg.js +47 -0
  29. package/src/{setPostReaction.js → api/action/setPostReaction.js} +23 -31
  30. package/src/{unfriend.js → api/action/unfriend.js} +14 -14
  31. package/src/api/http/httpGet.js +46 -0
  32. package/src/api/http/httpPost.js +52 -0
  33. package/src/api/http/postFormData.js +47 -0
  34. package/src/api/messaging/addUserToGroup.js +68 -0
  35. package/src/api/messaging/changeAdminStatus.js +126 -0
  36. package/src/{changeArchivedStatus.js → api/messaging/changeArchivedStatus.js} +15 -17
  37. package/src/{changeBlockedStatus.js → api/messaging/changeBlockedStatus.js} +13 -16
  38. package/src/api/messaging/changeGroupImage.js +91 -0
  39. package/src/api/messaging/changeNickname.js +70 -0
  40. package/src/api/messaging/changeThreadColor.js +79 -0
  41. package/src/api/messaging/changeThreadEmoji.js +111 -0
  42. package/src/{createNewGroup.js → api/messaging/createNewGroup.js} +24 -26
  43. package/src/api/messaging/createPoll.js +46 -0
  44. package/src/api/messaging/createThemeAI.js +98 -0
  45. package/src/api/messaging/deleteMessage.js +136 -0
  46. package/src/{deleteThread.js → api/messaging/deleteThread.js} +14 -16
  47. package/src/api/messaging/editMessage.js +105 -0
  48. package/src/api/messaging/forwardAttachment.js +57 -0
  49. package/src/api/messaging/forwardMessage.js +134 -0
  50. package/src/{getEmojiUrl.js → api/messaging/getEmojiUrl.js} +2 -2
  51. package/src/{getFriendsList.js → api/messaging/getFriendsList.js} +16 -19
  52. package/src/{getMessage.js → api/messaging/getMessage.js} +160 -180
  53. package/src/api/messaging/getThemePictures.js +62 -0
  54. package/src/api/messaging/handleMessageRequest.js +65 -0
  55. package/src/api/messaging/markAsDelivered.js +69 -0
  56. package/src/api/messaging/markAsRead.js +110 -0
  57. package/src/api/messaging/markAsReadAll.js +56 -0
  58. package/src/api/messaging/markAsSeen.js +68 -0
  59. package/src/api/messaging/muteThread.js +50 -0
  60. package/src/api/messaging/pinMessage.js +115 -0
  61. package/src/api/messaging/removeUserFromGroup.js +62 -0
  62. package/src/{resolvePhotoUrl.js → api/messaging/resolvePhotoUrl.js} +11 -15
  63. package/src/api/messaging/scheduler.js +264 -0
  64. package/src/{searchForThread.js → api/messaging/searchForThread.js} +12 -12
  65. package/src/api/messaging/sendBroadcast.js +93 -0
  66. package/src/api/messaging/sendMessage.js +269 -0
  67. package/src/api/messaging/sendTypingIndicator.js +90 -0
  68. package/src/api/messaging/sessionGuard.js +130 -0
  69. package/src/api/messaging/setMessageReaction.js +114 -0
  70. package/src/api/messaging/setTitle.js +124 -0
  71. package/src/api/messaging/shareContact.js +98 -0
  72. package/src/{threadColors.js → api/messaging/threadColors.js} +2 -5
  73. package/src/api/messaging/unsendMessage.js +105 -0
  74. package/src/api/messaging/uploadAttachment.js +493 -0
  75. package/src/api/socket/OldMessage.js +199 -0
  76. package/src/api/socket/core/connectMqtt.js +304 -0
  77. package/src/api/socket/core/emitAuth.js +95 -0
  78. package/src/api/socket/core/getSeqID.js +331 -0
  79. package/src/api/socket/core/getTaskResponseData.js +25 -0
  80. package/src/api/socket/core/lifecycle.js +117 -0
  81. package/src/api/socket/core/parseDelta.js +387 -0
  82. package/src/api/socket/detail/buildStream.js +215 -0
  83. package/src/api/socket/detail/constants.js +28 -0
  84. package/src/api/socket/e2ee/crypto.js +173 -0
  85. package/src/api/socket/e2ee/index.js +977 -0
  86. package/src/api/socket/e2ee/localMediaServer.js +59 -0
  87. package/src/api/socket/e2ee/mediaDecode.js +155 -0
  88. package/src/api/socket/e2ee/native/NOTICE.md +29 -0
  89. package/{lib → src/api/socket/e2ee/native/lib}/index.mjs +109 -66
  90. package/src/api/socket/e2ee/native/nativeMediaBridge.js +273 -0
  91. package/src/api/socket/e2ee/proto/ArmadilloApplication.proto +281 -0
  92. package/src/api/socket/e2ee/proto/ArmadilloICDC.proto +14 -0
  93. package/src/api/socket/e2ee/proto/ConsumerApplication.proto +232 -0
  94. package/src/api/socket/e2ee/proto/MessageApplication.proto +82 -0
  95. package/src/api/socket/e2ee/proto/MessageTransport.proto +77 -0
  96. package/src/api/socket/e2ee/proto/WACommon.proto +66 -0
  97. package/src/api/socket/e2ee/proto/WAMediaTransport.proto +176 -0
  98. package/src/api/socket/e2ee/proto/proto-writer.ts +76 -0
  99. package/src/api/socket/e2ee/protocol.js +196 -0
  100. package/src/api/socket/e2ee/ratchet.js +219 -0
  101. package/src/api/socket/e2ee/store.js +182 -0
  102. package/src/api/socket/e2ee/vendor/fme/dist/index.cjs +6477 -0
  103. package/src/api/socket/e2ee/vendor/fme/proto/ArmadilloApplication.proto +281 -0
  104. package/src/api/socket/e2ee/vendor/fme/proto/ArmadilloICDC.proto +14 -0
  105. package/src/api/socket/e2ee/vendor/fme/proto/ConsumerApplication.proto +232 -0
  106. package/src/api/socket/e2ee/vendor/fme/proto/MessageApplication.proto +82 -0
  107. package/src/api/socket/e2ee/vendor/fme/proto/MessageTransport.proto +77 -0
  108. package/src/api/socket/e2ee/vendor/fme/proto/WACommon.proto +66 -0
  109. package/src/api/socket/e2ee/vendor/fme/proto/WAMediaTransport.proto +176 -0
  110. package/src/api/socket/listenE2EE.js +87 -0
  111. package/src/api/socket/listenMqtt.js +499 -0
  112. package/src/api/socket/middleware/index.js +216 -0
  113. package/src/api/socket/sendMessage.js +331 -0
  114. package/src/api/socket/sendMessageMqtt.js +69 -0
  115. package/src/{getThreadHistory.js → api/threads/getThreadHistory.js} +114 -132
  116. package/src/api/threads/getThreadInfo.js +329 -0
  117. package/src/api/threads/getThreadList.js +293 -0
  118. package/src/api/threads/getThreadPictures.js +78 -0
  119. package/src/{getUserID.js → api/users/getUserID.js} +22 -18
  120. package/src/api/users/getUserInfo.js +402 -0
  121. package/src/api/users/getUserInfoV2.js +134 -0
  122. package/src/app/MessengerBot.js +209 -0
  123. package/src/app/MessengerContext.js +32 -0
  124. package/src/app/createFcaClient.js +136 -0
  125. package/src/app/threadInfoRealtimeSync.js +284 -0
  126. package/src/core/sendReqMqtt.js +96 -0
  127. package/src/database/helpers.js +53 -0
  128. package/src/database/models/index.js +88 -0
  129. package/src/database/models/thread.js +50 -0
  130. package/src/database/models/user.js +46 -0
  131. package/src/database/threadData.js +94 -0
  132. package/src/database/userData.js +98 -0
  133. package/src/remote/remoteClient.js +123 -0
  134. package/src/utils/antiSuspension.js +219 -0
  135. package/src/utils/broadcast.js +51 -0
  136. package/src/utils/client.js +10 -0
  137. package/src/utils/constants.js +23 -0
  138. package/src/utils/cookies.js +85 -0
  139. package/src/utils/fingerprint.js +110 -0
  140. package/src/utils/format/attachment.js +357 -0
  141. package/src/utils/format/cookie.js +9 -0
  142. package/src/utils/format/date.js +50 -0
  143. package/src/utils/format/decode.js +44 -0
  144. package/src/utils/format/delta.js +195 -0
  145. package/src/utils/format/ids.js +64 -0
  146. package/src/utils/format/index.js +64 -0
  147. package/src/utils/format/message.js +88 -0
  148. package/src/utils/format/presence.js +132 -0
  149. package/src/utils/format/readTyp.js +44 -0
  150. package/src/utils/format/thread.js +42 -0
  151. package/src/utils/format/utils.js +141 -0
  152. package/src/utils/headers.js +125 -0
  153. package/src/utils/humanizer.js +256 -0
  154. package/src/utils/loginParser/autoLogin.js +125 -0
  155. package/src/utils/loginParser/extractDtsg.js +44 -0
  156. package/src/utils/loginParser/helpers.js +43 -0
  157. package/src/utils/loginParser/index.js +10 -0
  158. package/src/utils/loginParser/parseAndCheckLogin.js +241 -0
  159. package/src/utils/loginParser/textUtils.js +28 -0
  160. package/src/utils/nexca-logger.js +144 -0
  161. package/src/utils/nexca-utils.js +687 -0
  162. package/src/utils/request/client.js +26 -0
  163. package/src/utils/request/config.js +23 -0
  164. package/src/utils/request/defaults.js +46 -0
  165. package/src/utils/request/helpers.js +46 -0
  166. package/src/utils/request/index.js +17 -0
  167. package/src/utils/request/methods.js +163 -0
  168. package/src/utils/request/proxy.js +21 -0
  169. package/src/utils/request/retry.js +77 -0
  170. package/src/utils/request/sanitize.js +49 -0
  171. package/src/utils/typingIndicator.js +202 -0
  172. package/test/helpers/stub-modules.js +122 -0
  173. package/test/integration/logout.test.js +69 -0
  174. package/test/integration/mqtt-lifecycle.test.js +195 -0
  175. package/test/integration/package.test.js +116 -0
  176. package/test/integration/typing-wiring.test.js +71 -0
  177. package/test/unit/fingerprint.test.js +19 -0
  178. package/test/unit/humanizer.test.js +32 -0
  179. package/test/unit/lifecycle.test.js +92 -0
  180. package/test/unit/parsers.test.js +43 -0
  181. package/test/unit/typing.test.js +136 -0
  182. package/ANALYSIS.md +0 -31
  183. package/AUDIT_REPORT.md +0 -57
  184. package/advancedSystem.js +0 -95
  185. package/checkUpdate.js +0 -47
  186. package/config.json +0 -51
  187. package/e2ee.js +0 -738
  188. package/examples/basic-bot.js +0 -122
  189. package/examples/e2ee-bot.js +0 -141
  190. package/fca-config.example.json +0 -73
  191. package/scripts/check.js +0 -38
  192. package/src/OldMessage.js +0 -357
  193. package/src/addUserToGroup.js +0 -117
  194. package/src/changeAdminStatus.js +0 -105
  195. package/src/changeAvatarV2.js +0 -86
  196. package/src/changeAvt.js +0 -85
  197. package/src/changeBlockedStatusMqtt.js +0 -75
  198. package/src/changeCover.js +0 -73
  199. package/src/changeGroupImage.js +0 -137
  200. package/src/changeName.js +0 -79
  201. package/src/changeNickname.js +0 -61
  202. package/src/changeThreadColor.js +0 -67
  203. package/src/changeThreadEmoji.js +0 -57
  204. package/src/changeUsername.js +0 -59
  205. package/src/createCommentPost.js +0 -232
  206. package/src/createPoll.js +0 -73
  207. package/src/createPost.js +0 -276
  208. package/src/deleteMessage.js +0 -58
  209. package/src/editMessage.js +0 -76
  210. package/src/editMessageOld.js +0 -62
  211. package/src/follow.js +0 -74
  212. package/src/forwardAttachment.js +0 -62
  213. package/src/friendList.js +0 -104
  214. package/src/getAccess.js +0 -112
  215. package/src/getAvatarUser.js +0 -78
  216. package/src/getBotInitialData.js +0 -42
  217. package/src/getCtx.js +0 -5
  218. package/src/getOptions.js +0 -5
  219. package/src/getRegion.js +0 -7
  220. package/src/getThreadHistoryDeprecated.js +0 -71
  221. package/src/getThreadInfo.js +0 -232
  222. package/src/getThreadInfoDeprecated.js +0 -56
  223. package/src/getThreadList.js +0 -213
  224. package/src/getThreadListDeprecated.js +0 -46
  225. package/src/getThreadPictures.js +0 -59
  226. package/src/getThreadTheme.js +0 -81
  227. package/src/getUID.js +0 -119
  228. package/src/getUserInfo.js +0 -66
  229. package/src/handleFriendRequest.js +0 -46
  230. package/src/handleMessageRequest.js +0 -47
  231. package/src/httpGet.js +0 -49
  232. package/src/httpPost.js +0 -48
  233. package/src/httpPostFormData.js +0 -70
  234. package/src/listenMqtt.js +0 -1081
  235. package/src/listenNotification.js +0 -85
  236. package/src/logout.js +0 -18
  237. package/src/markAsDelivered.js +0 -47
  238. package/src/markAsRead.js +0 -72
  239. package/src/markAsReadAll.js +0 -40
  240. package/src/markAsSeen.js +0 -48
  241. package/src/metaTheme.js +0 -189
  242. package/src/muteThread.js +0 -45
  243. package/src/note.js +0 -228
  244. package/src/pinMessage.js +0 -54
  245. package/src/refreshFb_dtsg.js +0 -49
  246. package/src/removeSuspiciousAccount.js +0 -78
  247. package/src/removeUserFromGroup.js +0 -81
  248. package/src/reply.js +0 -442
  249. package/src/searchFriends.js +0 -140
  250. package/src/searchStickers.js +0 -53
  251. package/src/send.js +0 -46
  252. package/src/sendButtons.js +0 -163
  253. package/src/sendComment.js +0 -159
  254. package/src/sendFriendRequest.js +0 -112
  255. package/src/sendMessage.js +0 -441
  256. package/src/sendMessageMqtt.js +0 -323
  257. package/src/sendTypingIndicator.js +0 -68
  258. package/src/sendTypingIndicatorV2.js +0 -22
  259. package/src/setActiveStatus.js +0 -92
  260. package/src/setMessageReaction.js +0 -150
  261. package/src/setMessageReactionMqtt.js +0 -104
  262. package/src/setMessageReactionSafe.js +0 -44
  263. package/src/setProfileGuard.js +0 -44
  264. package/src/setProfileLock.js +0 -97
  265. package/src/setStoryReaction.js +0 -133
  266. package/src/setStorySeen.js +0 -108
  267. package/src/setThreadTheme.js +0 -102
  268. package/src/setTitle.js +0 -92
  269. package/src/shareContact.js +0 -110
  270. package/src/shareLink.js +0 -59
  271. package/src/stopListenMqtt.js +0 -60
  272. package/src/storyManager.js +0 -357
  273. package/src/suggestFriend.js +0 -132
  274. package/src/unsendMessage.js +0 -68
  275. package/src/uploadAttachment.js +0 -115
  276. package/test/fixes-1_2_4.test.js +0 -118
  277. package/test/mqtt-stop.test.js +0 -32
  278. package/test/reaction-mqtt.test.js +0 -57
  279. package/utils.js +0 -3056
  280. /package/{build → src/api/socket/e2ee/native/build}/messagix.dll +0 -0
  281. /package/{build → src/api/socket/e2ee/native/build}/messagix.so +0 -0
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+ const logger = require("../../../../func/logger");
3
+
4
+ /**
5
+ * Middleware system for filtering and processing events before they are emitted
6
+ *
7
+ * Middleware functions receive (event, next) where:
8
+ * - event: The event object (can be modified)
9
+ * - next: Function to call to continue to next middleware
10
+ * - next() - continue to next middleware
11
+ * - next(false) or next(null) - stop processing, don't emit event
12
+ * - next(error) - emit error instead
13
+ */
14
+ module.exports = function createMiddlewareSystem() {
15
+ const middlewareStack = [];
16
+
17
+ /**
18
+ * Add middleware to the stack
19
+ * @param {Function|string} middleware - Middleware function or name for named middleware
20
+ * @param {Function} [fn] - Middleware function (if first param is name)
21
+ * @returns {Function} Unsubscribe function
22
+ */
23
+ function use(middleware, fn) {
24
+ let middlewareFn, name;
25
+
26
+ if (typeof middleware === "string" && typeof fn === "function") {
27
+ name = middleware;
28
+ middlewareFn = fn;
29
+ } else if (typeof middleware === "function") {
30
+ middlewareFn = middleware;
31
+ name = `middleware_${middlewareStack.length}`;
32
+ } else {
33
+ throw new Error("Middleware must be a function or (name, function)");
34
+ }
35
+
36
+ const wrapped = {
37
+ name,
38
+ fn: middlewareFn,
39
+ enabled: true
40
+ };
41
+
42
+ middlewareStack.push(wrapped);
43
+ logger(`Middleware "${name}" added`, "info");
44
+
45
+ // Return unsubscribe function
46
+ return function remove() {
47
+ const index = middlewareStack.indexOf(wrapped);
48
+ if (index !== -1) {
49
+ middlewareStack.splice(index, 1);
50
+ logger(`Middleware "${name}" removed`, "info");
51
+ }
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Remove middleware by name or function
57
+ * @param {string|Function} identifier - Name or function to remove
58
+ * @returns {boolean} True if removed
59
+ */
60
+ function remove(identifier) {
61
+ if (typeof identifier === "string") {
62
+ const index = middlewareStack.findIndex(m => m.name === identifier);
63
+ if (index !== -1) {
64
+ const removed = middlewareStack.splice(index, 1)[0];
65
+ logger(`Middleware "${removed.name}" removed`, "info");
66
+ return true;
67
+ }
68
+ return false;
69
+ } else if (typeof identifier === "function") {
70
+ const index = middlewareStack.findIndex(m => m.fn === identifier);
71
+ if (index !== -1) {
72
+ const removed = middlewareStack.splice(index, 1)[0];
73
+ logger(`Middleware "${removed.name}" removed`, "info");
74
+ return true;
75
+ }
76
+ return false;
77
+ }
78
+ return false;
79
+ }
80
+
81
+ /**
82
+ * Remove all middleware
83
+ */
84
+ function clear() {
85
+ const count = middlewareStack.length;
86
+ middlewareStack.length = 0;
87
+ logger(`All middleware cleared (${count} removed)`, "info");
88
+ }
89
+
90
+ /**
91
+ * Get list of middleware names
92
+ * @returns {string[]} Array of middleware names
93
+ */
94
+ function list() {
95
+ return middlewareStack.filter(m => m.enabled).map(m => m.name);
96
+ }
97
+
98
+ /**
99
+ * Enable/disable middleware by name
100
+ * @param {string} name - Middleware name
101
+ * @param {boolean} enabled - Enable or disable
102
+ * @returns {boolean} True if found and updated
103
+ */
104
+ function setEnabled(name, enabled) {
105
+ const middleware = middlewareStack.find(m => m.name === name);
106
+ if (middleware) {
107
+ middleware.enabled = enabled;
108
+ logger(`Middleware "${name}" ${enabled ? "enabled" : "disabled"}`, "info");
109
+ return true;
110
+ }
111
+ return false;
112
+ }
113
+
114
+ /**
115
+ * Process event through middleware stack
116
+ * @param {*} event - Event object
117
+ * @param {Function} finalCallback - Callback to call after all middleware
118
+ * @returns {Promise} Promise that resolves when processing is complete
119
+ */
120
+ function process(event, finalCallback) {
121
+ if (!middlewareStack.length) {
122
+ return finalCallback(null, event);
123
+ }
124
+
125
+ let index = 0;
126
+ const enabledMiddleware = middlewareStack.filter(m => m.enabled);
127
+
128
+ function next(err) {
129
+ // Error occurred, stop processing
130
+ if (err && err !== false && err !== null) {
131
+ return finalCallback(err, null);
132
+ }
133
+
134
+ // Explicitly stopped (next(false) or next(null))
135
+ if (err === false || err === null) {
136
+ return finalCallback(null, null); // null event means don't emit
137
+ }
138
+
139
+ // No more middleware, call final callback
140
+ if (index >= enabledMiddleware.length) {
141
+ return finalCallback(null, event);
142
+ }
143
+
144
+ // Get next middleware
145
+ const middleware = enabledMiddleware[index++];
146
+
147
+ try {
148
+ // Call middleware with event and next
149
+ const result = middleware.fn(event, next);
150
+
151
+ // If middleware returns a promise, handle it
152
+ if (result && typeof result.then === "function") {
153
+ result
154
+ .then(() => next())
155
+ .catch(err => next(err));
156
+ } else if (result === false || result === null) {
157
+ // Middleware returned false/null, stop processing
158
+ finalCallback(null, null);
159
+ }
160
+ // If middleware called next() synchronously, it will continue
161
+ } catch (err) {
162
+ logger(`Middleware "${middleware.name}" error: ${err && err.message ? err.message : String(err)}`, "error");
163
+ next(err);
164
+ }
165
+ }
166
+
167
+ // Start processing
168
+ next();
169
+ }
170
+
171
+ /**
172
+ * Wrap a callback with middleware processing
173
+ * @param {Function} callback - Original callback (err, event)
174
+ * @returns {Function} Wrapped callback
175
+ */
176
+ function wrapCallback(callback) {
177
+ return function wrappedCallback(err, event) {
178
+ if (err) {
179
+ // Errors bypass middleware
180
+ return callback(err, null);
181
+ }
182
+
183
+ if (!event) {
184
+ return callback(null, null);
185
+ }
186
+
187
+ // Process event through middleware
188
+ process(event, (middlewareErr, processedEvent) => {
189
+ if (middlewareErr) {
190
+ return callback(middlewareErr, null);
191
+ }
192
+
193
+ // If processedEvent is null, middleware blocked the event
194
+ if (processedEvent === null) {
195
+ return; // Don't emit
196
+ }
197
+
198
+ // Emit processed event
199
+ callback(null, processedEvent);
200
+ });
201
+ };
202
+ }
203
+
204
+ return {
205
+ use,
206
+ remove,
207
+ clear,
208
+ list,
209
+ setEnabled,
210
+ process,
211
+ wrapCallback,
212
+ get count() {
213
+ return middlewareStack.filter(m => m.enabled).length;
214
+ }
215
+ };
216
+ };
@@ -0,0 +1,331 @@
1
+ "use strict";
2
+
3
+ var utils = require("../../utils/nexca-utils");
4
+ var logger = require("../../utils/nexca-logger");
5
+ var { globalAntiSuspension } = require("../../utils/antiSuspension");
6
+
7
+ var ALLOWED = {
8
+ attachment: true, url: true, sticker: true, emoji: true,
9
+ emojiSize: true, body: true, mentions: true, location: true,
10
+ replyToMessage: true, forwardAttachmentIds: true
11
+ };
12
+
13
+ var EMOJI_SIZES = { small: 1, medium: 2, large: 3 };
14
+
15
+ function toEmojiSize(size) {
16
+ if (typeof size === "number" && !isNaN(size)) return Math.min(3, Math.max(1, size));
17
+ if (typeof size === "string" && size in EMOJI_SIZES) return EMOJI_SIZES[size];
18
+ return 1;
19
+ }
20
+
21
+ function hasLinks(text) {
22
+ return /(https?:\/\/|www\.|t\.me\/|fb\.me\/|youtu\.be\/|facebook\.com\/|youtube\.com\/)/i.test(text);
23
+ }
24
+
25
+ function buildMentionData(msg, baseBody) {
26
+ if (!Array.isArray(msg.mentions) || !msg.mentions.length) return null;
27
+ var ids = [], offsets = [], lengths = [], types = [];
28
+ for (var i = 0; i < msg.mentions.length; i++) {
29
+ var mention = msg.mentions[i];
30
+ // Ensure tag always has @ prefix — Facebook counts @ in both offset and length
31
+ var tag = String(mention.tag || "");
32
+ if (tag && !tag.startsWith("@")) tag = "@" + tag;
33
+ var fromIndex = Number.isInteger(mention.fromIndex) ? mention.fromIndex : 0;
34
+ var offset = baseBody.indexOf(tag, fromIndex);
35
+ if (offset === -1) {
36
+ // Fallback: search for bare name without @
37
+ offset = baseBody.indexOf(tag.slice(1), fromIndex);
38
+ }
39
+ if (offset < 0) offset = 0;
40
+ ids.push(String(mention.id || 0));
41
+ offsets.push(offset);
42
+ lengths.push(tag.length); // includes @ — matches Facebook's expectation
43
+ types.push("p");
44
+ }
45
+ return {
46
+ mention_ids: ids.join(","),
47
+ mention_offsets: offsets.join(","),
48
+ mention_lengths: lengths.join(","),
49
+ mention_types: types.join(",")
50
+ };
51
+ }
52
+
53
+ function extractIdsFromPayload(payload) {
54
+ var messageID = null, threadID = null;
55
+ function walk(node) {
56
+ if (!Array.isArray(node)) return;
57
+ if (node[0] === 5 && (node[1] === "replaceOptimsiticMessage" || node[1] === "replaceOptimisticMessage")) {
58
+ messageID = String(node[3]);
59
+ }
60
+ if (node[0] === 5 && node[1] === "writeCTAIdToThreadsTable") {
61
+ var candidate = node[2];
62
+ if (Array.isArray(candidate) && candidate[0] === 19) threadID = String(candidate[1]);
63
+ }
64
+ for (var i = 0; i < node.length; i++) walk(node[i]);
65
+ }
66
+ try { walk(payload && payload.step); } catch (_) { }
67
+ return { threadID, messageID };
68
+ }
69
+
70
+ function publishLsRequestWithAck(mqttClient, content, requestId, timeout) {
71
+ timeout = timeout || 15000;
72
+ return new Promise((resolve, reject) => {
73
+ var timer = setTimeout(() => {
74
+ mqttClient.removeListener('message', onMessage);
75
+ reject(new Error('MQTT sendMessage timed out after ' + timeout + 'ms'));
76
+ }, timeout);
77
+
78
+ function onMessage(topic, message) {
79
+ if (topic !== '/ls_resp') return;
80
+ try {
81
+ var data = JSON.parse(message.toString());
82
+ if (String(data.request_id) === String(requestId)) {
83
+ clearTimeout(timer);
84
+ mqttClient.removeListener('message', onMessage);
85
+ var extracted = extractIdsFromPayload(data.payload ? JSON.parse(data.payload) : {});
86
+ resolve({ threadID: extracted.threadID, messageID: extracted.messageID });
87
+ }
88
+ } catch (_) { }
89
+ }
90
+
91
+ mqttClient.on('message', onMessage);
92
+ mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1 }, err => {
93
+ if (err) {
94
+ clearTimeout(timer);
95
+ mqttClient.removeListener('message', onMessage);
96
+ reject(err);
97
+ }
98
+ });
99
+ });
100
+ }
101
+
102
+ module.exports = function (defaultFuncs, api, ctx) {
103
+ var uploadAttachmentFn = require('../messaging/uploadAttachment')(defaultFuncs, api, ctx);
104
+
105
+ async function uploadAttachments(attachments) {
106
+ if (!Array.isArray(attachments)) attachments = [attachments];
107
+ return uploadAttachmentFn(attachments);
108
+ }
109
+
110
+ async function sendViaMqtt(msg, threadID, replyToMessage) {
111
+ var mqttClient = ctx.mqttClient || global.mqttClient;
112
+ if (!mqttClient) throw new Error('MQTT client not available');
113
+
114
+ var baseBody = msg.body != null ? String(msg.body) : "";
115
+ var requestId = Math.floor(100 + Math.random() * 900);
116
+ var epoch = (BigInt(Date.now()) << 22n).toString();
117
+
118
+ var payload0 = {
119
+ thread_id: String(threadID),
120
+ otid: utils.generateOfflineThreadingID(),
121
+ source: 2097153,
122
+ send_type: 1,
123
+ sync_group: 1,
124
+ mark_thread_read: 1,
125
+ text: baseBody === "" ? null : baseBody,
126
+ initiating_source: 0,
127
+ skip_url_preview_gen: 1,
128
+ text_has_links: hasLinks(baseBody) ? 1 : 0,
129
+ multitab_env: 0,
130
+ metadata_dataclass: JSON.stringify({ media_accessibility_metadata: { alt_text: null } })
131
+ };
132
+
133
+ var mentionData = buildMentionData(msg, baseBody);
134
+ if (mentionData) payload0.mention_data = mentionData;
135
+
136
+ if (msg.sticker) { payload0.send_type = 2; payload0.sticker_id = msg.sticker; }
137
+ if (msg.emoji) { payload0.send_type = 1; payload0.text = msg.emoji; payload0.hot_emoji_size = toEmojiSize(msg.emojiSize); }
138
+
139
+ if (msg.location && msg.location.latitude != null && msg.location.longitude != null) {
140
+ payload0.send_type = 1;
141
+ payload0.location_data = {
142
+ coordinates: { latitude: msg.location.latitude, longitude: msg.location.longitude },
143
+ is_current_location: Boolean(msg.location.current),
144
+ is_live_location: Boolean(msg.location.live)
145
+ };
146
+ }
147
+
148
+ var effectiveReplyTo = replyToMessage || msg.replyToMessage;
149
+ if (effectiveReplyTo) {
150
+ payload0.reply_metadata = {
151
+ reply_source_id: effectiveReplyTo,
152
+ reply_source_type: 1,
153
+ reply_type: 0
154
+ };
155
+ }
156
+
157
+ if (msg.attachment) {
158
+ payload0.send_type = 3;
159
+ if (payload0.text === "") payload0.text = null;
160
+ payload0.attachment_fbids = [];
161
+ var list = Array.isArray(msg.attachment) ? msg.attachment : [msg.attachment];
162
+ var preuploaded = [], toUpload = [];
163
+ for (var item of list) {
164
+ if (Array.isArray(item) && item.length >= 2 && typeof item[0] === "string") {
165
+ preuploaded.push(String(item[1]));
166
+ } else if (utils.isReadableStream(item)) {
167
+ toUpload.push(item);
168
+ }
169
+ }
170
+ if (preuploaded.length) payload0.attachment_fbids = payload0.attachment_fbids.concat(preuploaded);
171
+ if (msg.forwardAttachmentIds && msg.forwardAttachmentIds.length) {
172
+ payload0.attachment_fbids = payload0.attachment_fbids.concat(msg.forwardAttachmentIds.map(String));
173
+ }
174
+
175
+ if (toUpload.length) {
176
+ var uploaded = await uploadAttachments(toUpload);
177
+ for (var u of uploaded) {
178
+ if (!u) continue;
179
+ var fbid = u.image_id || u.video_id || u.audio_id || u.file_id || u.sticker_id || u.gif_id;
180
+ if (!fbid) {
181
+ var firstKey = Object.keys(u).find(function(k) { return u[k] && /^\d+$/.test(String(u[k])); });
182
+ if (firstKey) fbid = u[firstKey];
183
+ }
184
+ if (fbid) payload0.attachment_fbids.push(String(fbid));
185
+ }
186
+ }
187
+ if (!payload0.attachment_fbids.length) delete payload0.attachment_fbids;
188
+ }
189
+
190
+ var tasks = [
191
+ {
192
+ label: '46',
193
+ payload: JSON.stringify(payload0),
194
+ queue_name: String(threadID),
195
+ task_id: 400,
196
+ failure_count: null
197
+ },
198
+ {
199
+ label: '21',
200
+ payload: JSON.stringify({
201
+ thread_id: String(threadID),
202
+ last_read_watermark_ts: Date.now(),
203
+ sync_group: 1
204
+ }),
205
+ queue_name: String(threadID),
206
+ task_id: 401,
207
+ failure_count: null
208
+ }
209
+ ];
210
+
211
+ var content = {
212
+ app_id: '2220391788200892',
213
+ payload: JSON.stringify({
214
+ tasks: tasks,
215
+ epoch_id: epoch,
216
+ version_id: '24804310205905615',
217
+ data_trace_id: '#' + Buffer.from(String(Math.random())).toString('base64').replace(/=+$/, '')
218
+ }),
219
+ request_id: requestId,
220
+ type: 3
221
+ };
222
+
223
+ return publishLsRequestWithAck(mqttClient, content, requestId, 15000);
224
+ }
225
+
226
+ return async function sendMessage(msg, threadID, callback, replyToMessage, isSingleUser) {
227
+ if (typeof msg === "string") msg = { body: msg };
228
+ if (typeof callback !== "function") { callback = null; }
229
+
230
+ var resolve, reject;
231
+ var promise = new Promise((res, rej) => { resolve = res; reject = rej; });
232
+
233
+ for (var key in msg) {
234
+ if (!ALLOWED[key]) {
235
+ var err = { error: "sendMessage: Unknown property '" + key + "'" };
236
+ if (callback) callback(err);
237
+ else reject(err);
238
+ return promise;
239
+ }
240
+ }
241
+
242
+ // Auto-detect isSingleUser from ctx.threadTypes if not explicitly provided.
243
+ // parseDelta in listenMqtt.js populates ctx.threadTypes[senderID] = 'dm' | 'group'
244
+ if (isSingleUser === undefined && ctx.threadTypes) {
245
+ isSingleUser = ctx.threadTypes[String(threadID)] === 'dm';
246
+ }
247
+
248
+ // Rate-limit / pace / circuit-break outbound sends to avoid looking
249
+ // like automated bulk traffic to Facebook. Opt-out via
250
+ // globalOptions.antiBan === false; on by default. This never throws
251
+ // in normal operation (it only adds delay), the try/catch is
252
+ // defensive so a bug in here can never block a real send.
253
+ if (!ctx.globalOptions || ctx.globalOptions.antiBan !== false) {
254
+ try {
255
+ await globalAntiSuspension.prepareBeforeMessage(threadID, msg.body || "");
256
+ } catch (err) {
257
+ logger("AntiSuspension prepareBeforeMessage error: " + (err && err.message ? err.message : err), "warn");
258
+ }
259
+ }
260
+
261
+ // DM sends on E2EE threads — skip MQTT entirely, always.
262
+ // Facebook does not deliver plaintext MQTT bodies into an E2EE (Signal Protocol)
263
+ // thread — this applies to TEXT replies just as much as attachments. Previously
264
+ // only attachment sends were routed through the E2EE bridge, so every plain-text
265
+ // bot reply inside an E2EE DM silently vanished (sent over MQTT, never delivered).
266
+ // Now: if the DM is E2EE-connected, ALL sends (text or attachment) go through the
267
+ // Noise WebSocket / Signal Protocol bridge. Only non-E2EE DM attachments fall back
268
+ // to OldMessage; everything else falls through to the normal MQTT path below.
269
+ var useE2EE = isSingleUser && api.e2ee && typeof api.e2ee.isConnected === "function" && api.e2ee.isConnected();
270
+ var useE2EEGroup = !isSingleUser && api.e2ee && typeof api.e2ee.isKnownE2EEGroup === "function" &&
271
+ api.e2ee.isKnownE2EEGroup(String(threadID)) && typeof api.e2ee.isConnected === "function" && api.e2ee.isConnected();
272
+ useE2EE = useE2EE || useE2EEGroup;
273
+
274
+ if (useE2EE) {
275
+ try {
276
+ var e2eeResult = await api.e2ee.sendMessage(String(threadID), msg, replyToMessage);
277
+ var wrapped = e2eeResult && e2eeResult.messageId
278
+ ? { threadID: String(threadID), messageID: String(e2eeResult.messageId) }
279
+ : e2eeResult;
280
+ if (callback) callback(null, wrapped);
281
+ else resolve(wrapped);
282
+ } catch (e2eeErr) {
283
+ globalAntiSuspension.checkAccountHealth(e2eeErr);
284
+ logger.error("sendMessage", "E2EE DM send failed: " + (e2eeErr.message || e2eeErr));
285
+ if (callback) callback(e2eeErr);
286
+ else reject(e2eeErr);
287
+ }
288
+ return promise;
289
+ }
290
+
291
+ if (isSingleUser && msg.attachment) {
292
+ // Non-E2EE DM with attachment.
293
+ // /messaging/send/ with other_user_fbid routing works for plain DMs.
294
+ try {
295
+ var omResult = await new Promise((res2, rej2) => {
296
+ api.OldMessage(msg, threadID, (err2, data2) => err2 ? rej2(err2) : res2(data2), replyToMessage, true);
297
+ });
298
+ if (callback) callback(null, omResult);
299
+ else resolve(omResult);
300
+ } catch (omErr) {
301
+ globalAntiSuspension.checkAccountHealth(omErr);
302
+ logger.error("sendMessage", "DM attachment via OldMessage failed: " + (omErr.error || omErr.message || omErr));
303
+ if (callback) callback(omErr);
304
+ else reject(omErr);
305
+ }
306
+ return promise;
307
+ }
308
+
309
+ try {
310
+ var result = await sendViaMqtt(msg, threadID, replyToMessage);
311
+ if (callback) callback(null, result);
312
+ else resolve(result);
313
+ } catch (mqttErr) {
314
+ logger.warn("sendMessage", "MQTT failed, falling back to OldMessage: " + (mqttErr.message || mqttErr));
315
+ try {
316
+ var fbResult = await new Promise((res2, rej2) => {
317
+ api.OldMessage(msg, threadID, (err2, data2) => err2 ? rej2(err2) : res2(data2), replyToMessage, isSingleUser);
318
+ });
319
+ if (callback) callback(null, fbResult);
320
+ else resolve(fbResult);
321
+ } catch (fbErr) {
322
+ globalAntiSuspension.checkAccountHealth(fbErr);
323
+ logger.error("sendMessage", fbErr.error || fbErr.message || fbErr);
324
+ if (callback) callback(fbErr);
325
+ else reject(fbErr);
326
+ }
327
+ }
328
+
329
+ return promise;
330
+ };
331
+ };
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+
3
+ var utils = require("../../utils/nexca-utils");
4
+
5
+ module.exports = function (defaultFuncs, api, ctx) {
6
+ return function sendMessageMqtt(msg, threadID, callback, replyToMessage) {
7
+ if (typeof msg === "string") msg = { body: msg };
8
+ var mqttClient = ctx.mqttClient || global.mqttClient;
9
+ if (!mqttClient) {
10
+ var err = { error: "MQTT not connected. Call listenMqtt first." };
11
+ if (typeof callback === "function") return callback(err);
12
+ return Promise.reject(err);
13
+ }
14
+
15
+ ctx.wsReqNumber = (ctx.wsReqNumber || 0) + 1;
16
+ ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1;
17
+
18
+ var baseBody = msg.body != null ? String(msg.body) : "";
19
+ var hasLinks = typeof baseBody === "string" && /(https?:\/\/|www\.|t\.me\/|fb\.me\/|youtu\.be\/|facebook\.com\/|youtube\.com\/)/i.test(baseBody);
20
+ var requestId = ctx.wsReqNumber;
21
+
22
+ var payload0 = {
23
+ thread_id: String(threadID),
24
+ otid: utils.generateOfflineThreadingID(),
25
+ source: 2097153,
26
+ send_type: 1,
27
+ sync_group: 1,
28
+ mark_thread_read: 1,
29
+ text: baseBody === "" ? null : baseBody,
30
+ initiating_source: 0,
31
+ skip_url_preview_gen: 1,
32
+ text_has_links: hasLinks ? 1 : 0,
33
+ multitab_env: 0
34
+ };
35
+
36
+ if (msg.sticker) { payload0.send_type = 2; payload0.sticker_id = msg.sticker; }
37
+ if (replyToMessage || msg.replyToMessage) {
38
+ payload0.reply_metadata = {
39
+ reply_source_id: replyToMessage || msg.replyToMessage,
40
+ reply_source_type: 1,
41
+ reply_type: 0
42
+ };
43
+ }
44
+
45
+ var content = {
46
+ app_id: '2220391788200892',
47
+ payload: JSON.stringify({
48
+ tasks: [{
49
+ failure_count: null,
50
+ label: '46',
51
+ payload: JSON.stringify(payload0),
52
+ queue_name: String(threadID),
53
+ task_id: ctx.wsTaskNumber
54
+ }],
55
+ epoch_id: utils.generateOfflineThreadingID(),
56
+ version_id: '7214102258676893'
57
+ }),
58
+ request_id: requestId,
59
+ type: 3
60
+ };
61
+
62
+ if (typeof callback === "function") {
63
+ ctx.reqCallbacks[requestId] = callback;
64
+ }
65
+
66
+ mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1 });
67
+ return Promise.resolve({ requestId });
68
+ };
69
+ };