shadowx-fca 8.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 (174) hide show
  1. package/README.md +1066 -0
  2. package/build/messagix.dll +0 -0
  3. package/build/messagix.so +0 -0
  4. package/checkUpdate.js +393 -0
  5. package/config.json +17 -0
  6. package/e2ee.js +563 -0
  7. package/e2eetest.js +356 -0
  8. package/index.js +611 -0
  9. package/lib/index.mjs +1412 -0
  10. package/logger.js +500 -0
  11. package/package.json +65 -0
  12. package/src/GetBotInfo.js +66 -0
  13. package/src/OldMessage.js +182 -0
  14. package/src/Screenshot.js +83 -0
  15. package/src/addExternalModule.js +13 -0
  16. package/src/addUserToGroup.js +33 -0
  17. package/src/approveGroupJoinRequests.js +18 -0
  18. package/src/changeAdminStatus.js +16 -0
  19. package/src/changeArchivedStatus.js +17 -0
  20. package/src/changeAvatar.js +136 -0
  21. package/src/changeAvatarV2.js +86 -0
  22. package/src/changeAvt.js +85 -0
  23. package/src/changeBio.js +76 -0
  24. package/src/changeBlockedStatus.js +20 -0
  25. package/src/changeBlockedStatusMqtt.js +80 -0
  26. package/src/changeCover.js +72 -0
  27. package/src/changeGroupImage.js +16 -0
  28. package/src/changeName.js +79 -0
  29. package/src/changeNickname.js +16 -0
  30. package/src/changeThreadColor.js +15 -0
  31. package/src/changeThreadEmoji.js +15 -0
  32. package/src/changeThreadMemberNickname.js +6 -0
  33. package/src/changeUsername.js +59 -0
  34. package/src/createCommentPost.js +230 -0
  35. package/src/createNewGroup.js +38 -0
  36. package/src/createNote.js +35 -0
  37. package/src/createPoll.js +27 -0
  38. package/src/createPost.js +276 -0
  39. package/src/createThemeAI.js +129 -0
  40. package/src/data/cache/system/data.json +4 -0
  41. package/src/data/cache/system/datahandle.js +21 -0
  42. package/src/data/getThreadInfo.json +1 -0
  43. package/src/deleteComment.js +23 -0
  44. package/src/deleteMessage.js +15 -0
  45. package/src/deleteThread.js +15 -0
  46. package/src/denyGroupJoinRequests.js +18 -0
  47. package/src/e2ee/crypto.js +173 -0
  48. package/src/e2ee/index.js +144 -0
  49. package/src/e2ee/proto/ArmadilloApplication.proto +281 -0
  50. package/src/e2ee/proto/ArmadilloICDC.proto +14 -0
  51. package/src/e2ee/proto/ConsumerApplication.proto +232 -0
  52. package/src/e2ee/proto/MessageApplication.proto +82 -0
  53. package/src/e2ee/proto/MessageTransport.proto +77 -0
  54. package/src/e2ee/proto/WACommon.proto +66 -0
  55. package/src/e2ee/proto/WAMediaTransport.proto +176 -0
  56. package/src/e2ee/proto/proto-writer.ts +76 -0
  57. package/src/e2ee/protocol.js +196 -0
  58. package/src/e2ee/ratchet.js +219 -0
  59. package/src/e2ee/store.js +182 -0
  60. package/src/e2ee.js +8 -0
  61. package/src/editMessage.js +56 -0
  62. package/src/editMessageOld.js +67 -0
  63. package/src/enableReactions.js +24 -0
  64. package/src/follow.js +74 -0
  65. package/src/followUser.js +23 -0
  66. package/src/forwardAttachment.js +16 -0
  67. package/src/friendList.js +98 -0
  68. package/src/getAccess.js +112 -0
  69. package/src/getAppState.js +13 -0
  70. package/src/getAvatarUser.js +11 -0
  71. package/src/getBio.js +24 -0
  72. package/src/getBotInitialData.js +42 -0
  73. package/src/getCtx.js +5 -0
  74. package/src/getCurrentUserID.js +6 -0
  75. package/src/getEmojiUrl.js +29 -0
  76. package/src/getFriendsList.js +36 -0
  77. package/src/getMessage.js +37 -0
  78. package/src/getNotes.js +17 -0
  79. package/src/getOptions.js +5 -0
  80. package/src/getPinnedMessages.js +33 -0
  81. package/src/getPostInfo.js +17 -0
  82. package/src/getProfileInfo.js +17 -0
  83. package/src/getPublicData.js +25 -0
  84. package/src/getRegion.js +7 -0
  85. package/src/getRepInfo.js +17 -0
  86. package/src/getStickerPacks.js +25 -0
  87. package/src/getStickers.js +39 -0
  88. package/src/getStoryReactions.js +18 -0
  89. package/src/getThreadHistory.js +45 -0
  90. package/src/getThreadHistoryDeprecated.js +71 -0
  91. package/src/getThreadInfo.js +73 -0
  92. package/src/getThreadInfoDeprecated.js +56 -0
  93. package/src/getThreadList.js +76 -0
  94. package/src/getThreadListDeprecated.js +46 -0
  95. package/src/getThreadPictures.js +59 -0
  96. package/src/getThreadTheme.js +77 -0
  97. package/src/getUID.js +17 -0
  98. package/src/getUserID.js +17 -0
  99. package/src/getUserInfo.js +28 -0
  100. package/src/handleFriendRequest.js +21 -0
  101. package/src/handleMessageRequest.js +15 -0
  102. package/src/httpGet.js +13 -0
  103. package/src/httpPost.js +12 -0
  104. package/src/httpPostFormData.js +12 -0
  105. package/src/listenE2EE.js +75 -0
  106. package/src/listenMqtt.js +802 -0
  107. package/src/listenNotification.js +85 -0
  108. package/src/logout.js +22 -0
  109. package/src/markAsDelivered.js +17 -0
  110. package/src/markAsRead.js +14 -0
  111. package/src/markAsReadAll.js +15 -0
  112. package/src/markAsSeen.js +15 -0
  113. package/src/metaTheme.js +185 -0
  114. package/src/muteThread.js +52 -0
  115. package/src/note.js +228 -0
  116. package/src/pin.js +53 -0
  117. package/src/pinMessage.js +6 -0
  118. package/src/postComment.js +29 -0
  119. package/src/postFormData.js +46 -0
  120. package/src/reactToComment.js +31 -0
  121. package/src/reactToPost.js +32 -0
  122. package/src/refreshFb_dtsg.js +31 -0
  123. package/src/removeSuspiciousAccount.js +74 -0
  124. package/src/removeUserFromGroup.js +15 -0
  125. package/src/reply.js +442 -0
  126. package/src/resolvePhotoUrl.js +15 -0
  127. package/src/searchForThread.js +20 -0
  128. package/src/searchFriends.js +28 -0
  129. package/src/searchStickers.js +53 -0
  130. package/src/send.js +46 -0
  131. package/src/sendAudio.js +8 -0
  132. package/src/sendBroadcast.js +93 -0
  133. package/src/sendButtons.js +161 -0
  134. package/src/sendComment.js +159 -0
  135. package/src/sendEmoji.js +10 -0
  136. package/src/sendFile.js +9 -0
  137. package/src/sendFriendRequest.js +33 -0
  138. package/src/sendGif.js +24 -0
  139. package/src/sendImage.js +9 -0
  140. package/src/sendLocation.js +9 -0
  141. package/src/sendMessage.js +487 -0
  142. package/src/sendMessage1.js +309 -0
  143. package/src/sendMessageMqtt.js +68 -0
  144. package/src/sendSticker.js +8 -0
  145. package/src/sendTypingIndicator.js +36 -0
  146. package/src/sendTypingIndicatorV2.js +28 -0
  147. package/src/sendVideo.js +9 -0
  148. package/src/sessionGuard.js +130 -0
  149. package/src/setActiveStatus.js +16 -0
  150. package/src/setMessageReaction.js +61 -0
  151. package/src/setMessageReactionMqtt.js +62 -0
  152. package/src/setOptions.js +22 -0
  153. package/src/setPollVote.js +17 -0
  154. package/src/setPostReaction.js +112 -0
  155. package/src/setProfileGuard.js +44 -0
  156. package/src/setProfileLock.js +93 -0
  157. package/src/setStoryReaction.js +129 -0
  158. package/src/setStorySeen.js +99 -0
  159. package/src/setThreadTheme.js +17 -0
  160. package/src/setTitle.js +15 -0
  161. package/src/shareContact.js +33 -0
  162. package/src/shareLink.js +8 -0
  163. package/src/sharePost.js +31 -0
  164. package/src/stopListenMqtt.js +23 -0
  165. package/src/storyManager.js +353 -0
  166. package/src/suggestFriend.js +128 -0
  167. package/src/threadColors.js +131 -0
  168. package/src/unfollowUser.js +23 -0
  169. package/src/unfriend.js +15 -0
  170. package/src/unpinMessage.js +6 -0
  171. package/src/unsendMessage.js +14 -0
  172. package/src/uploadAttachment.js +58 -0
  173. package/src/uploadImageToImgbb.js +29 -0
  174. package/utils.js +2945 -0
@@ -0,0 +1,487 @@
1
+ "use strict";
2
+
3
+ var utils = require("../utils");
4
+ var logger = require("../logger");
5
+
6
+ var ALLOWED = {
7
+ attachment: true, url: true, sticker: true, emoji: true,
8
+ emojiSize: true, body: true, mentions: true, location: true,
9
+ replyToMessage: true, forwardAttachmentIds: true
10
+ };
11
+
12
+ var EMOJI_SIZES = { small: 1, medium: 2, large: 3 };
13
+
14
+ function toEmojiSize(size) {
15
+ if (typeof size === "number" && !isNaN(size)) return Math.min(3, Math.max(1, size));
16
+ if (typeof size === "string" && size in EMOJI_SIZES) return EMOJI_SIZES[size];
17
+ return 1;
18
+ }
19
+
20
+ function hasLinks(text) {
21
+ return /(https?:\/\/|www\.|t\.me\/|fb\.me\/|youtu\.be\/|facebook\.com\/|youtube\.com\/)/i.test(text);
22
+ }
23
+
24
+ function buildMentionData(msg, baseBody) {
25
+ if (!Array.isArray(msg.mentions) || !msg.mentions.length) return null;
26
+ var ids = [], offsets = [], lengths = [], types = [];
27
+ for (var i = 0; i < msg.mentions.length; i++) {
28
+ var mention = msg.mentions[i];
29
+ var tag = String(mention.tag || "");
30
+ if (tag && !tag.startsWith("@")) tag = "@" + tag;
31
+ var fromIndex = Number.isInteger(mention.fromIndex) ? mention.fromIndex : 0;
32
+ var offset = baseBody.indexOf(tag, fromIndex);
33
+ if (offset === -1) {
34
+ offset = baseBody.indexOf(tag.slice(1), fromIndex);
35
+ }
36
+ if (offset < 0) offset = 0;
37
+ ids.push(String(mention.id || 0));
38
+ offsets.push(offset);
39
+ lengths.push(tag.length);
40
+ types.push("p");
41
+ }
42
+ return {
43
+ mention_ids: ids.join(","),
44
+ mention_offsets: offsets.join(","),
45
+ mention_lengths: lengths.join(","),
46
+ mention_types: types.join(",")
47
+ };
48
+ }
49
+
50
+ function extractIdsFromPayload(payload) {
51
+ var messageID = null, threadID = null;
52
+ function walk(node) {
53
+ if (!Array.isArray(node)) return;
54
+ if (node[0] === 5 && (node[1] === "replaceOptimsiticMessage" || node[1] === "replaceOptimisticMessage")) {
55
+ messageID = String(node[3]);
56
+ }
57
+ if (node[0] === 5 && node[1] === "writeCTAIdToThreadsTable") {
58
+ var candidate = node[2];
59
+ if (Array.isArray(candidate) && candidate[0] === 19) threadID = String(candidate[1]);
60
+ }
61
+ for (var i = 0; i < node.length; i++) walk(node[i]);
62
+ }
63
+ try { walk(payload && payload.step); } catch (_) { }
64
+ return { threadID, messageID };
65
+ }
66
+
67
+ function publishLsRequestWithAck(mqttClient, content, requestId, timeout) {
68
+ timeout = timeout || 15000;
69
+ return new Promise((resolve, reject) => {
70
+ var timer = setTimeout(() => {
71
+ mqttClient.removeListener('message', onMessage);
72
+ reject(new Error('MQTT sendMessage timed out after ' + timeout + 'ms'));
73
+ }, timeout);
74
+
75
+ function onMessage(topic, message) {
76
+ if (topic !== '/ls_resp') return;
77
+ try {
78
+ var data = JSON.parse(message.toString());
79
+ if (String(data.request_id) === String(requestId)) {
80
+ clearTimeout(timer);
81
+ mqttClient.removeListener('message', onMessage);
82
+ var extracted = extractIdsFromPayload(data.payload ? JSON.parse(data.payload) : {});
83
+ resolve({ threadID: extracted.threadID, messageID: extracted.messageID });
84
+ }
85
+ } catch (_) { }
86
+ }
87
+
88
+ mqttClient.on('message', onMessage);
89
+ mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1 }, err => {
90
+ if (err) {
91
+ clearTimeout(timer);
92
+ mqttClient.removeListener('message', onMessage);
93
+ reject(err);
94
+ }
95
+ });
96
+ });
97
+ }
98
+
99
+ module.exports = function (defaultFuncs, api, ctx) {
100
+ // HTTP-based upload for OldMessage fallback
101
+ async function uploadAttachmentHTTP(attachments) {
102
+ var uploads = [];
103
+ for (var i = 0; i < attachments.length; i++) {
104
+ if (!utils.isReadableStream(attachments[i])) {
105
+ throw new Error("Attachment should be a readable stream and not " + utils.getType(attachments[i]) + ".");
106
+ }
107
+ var formData = { upload_1024: attachments[i], voice_clip: "true" };
108
+ var resData = await defaultFuncs
109
+ .postFormData("https://upload.facebook.com/ajax/mercury/upload.php", ctx.jar, formData, {})
110
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
111
+ if (!resData || resData.error) {
112
+ throw new Error(resData ? resData.error : "Upload failed");
113
+ }
114
+ uploads.push(resData.payload.metadata[0]);
115
+ }
116
+ return uploads;
117
+ }
118
+
119
+ // MQTT-based upload using uploadAttachment module
120
+ var uploadAttachmentMQTT = require('./uploadAttachment')(defaultFuncs, api, ctx);
121
+
122
+ async function uploadAttachmentsMQTT(attachments) {
123
+ if (!Array.isArray(attachments)) attachments = [attachments];
124
+ return uploadAttachmentMQTT(attachments);
125
+ }
126
+
127
+ // URL sharing helper for HTTP path
128
+ async function getShareUrl(url) {
129
+ var resData = await defaultFuncs
130
+ .post("https://www.facebook.com/message_share_attachment/fromURI/", ctx.jar, {
131
+ image_height: 960,
132
+ image_width: 960,
133
+ uri: url
134
+ })
135
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
136
+ if (!resData || resData.error || !resData.payload) {
137
+ throw new Error(resData ? resData.error : "Failed to get share URL");
138
+ }
139
+ return resData.payload.share_params;
140
+ }
141
+
142
+ // MQTT send path
143
+ async function sendViaMqtt(msg, threadID, replyToMessage) {
144
+ var mqttClient = ctx.mqttClient || global.mqttClient;
145
+ if (!mqttClient) throw new Error('MQTT client not available');
146
+
147
+ var baseBody = msg.body != null ? String(msg.body) : "";
148
+ var requestId = Math.floor(100 + Math.random() * 900);
149
+ var epoch = (BigInt(Date.now()) << 22n).toString();
150
+
151
+ var payload0 = {
152
+ thread_id: String(threadID),
153
+ otid: utils.generateOfflineThreadingID(),
154
+ source: 2097153,
155
+ send_type: 1,
156
+ sync_group: 1,
157
+ mark_thread_read: 1,
158
+ text: baseBody === "" ? null : baseBody,
159
+ initiating_source: 0,
160
+ skip_url_preview_gen: 0,
161
+ text_has_links: hasLinks(baseBody) ? 1 : 0,
162
+ multitab_env: 0,
163
+ metadata_dataclass: JSON.stringify({ media_accessibility_metadata: { alt_text: null } })
164
+ };
165
+
166
+ var mentionData = buildMentionData(msg, baseBody);
167
+ if (mentionData) payload0.mention_data = mentionData;
168
+
169
+ if (msg.sticker) {
170
+ payload0.send_type = 2;
171
+ payload0.sticker_id = msg.sticker;
172
+ }
173
+ if (msg.emoji) {
174
+ payload0.send_type = 1;
175
+ payload0.text = msg.emoji;
176
+ payload0.hot_emoji_size = toEmojiSize(msg.emojiSize);
177
+ }
178
+
179
+ if (msg.location && msg.location.latitude != null && msg.location.longitude != null) {
180
+ payload0.send_type = 1;
181
+ payload0.location_data = {
182
+ coordinates: { latitude: msg.location.latitude, longitude: msg.location.longitude },
183
+ is_current_location: Boolean(msg.location.current),
184
+ is_live_location: Boolean(msg.location.live)
185
+ };
186
+ }
187
+
188
+ var effectiveReplyTo = replyToMessage || msg.replyToMessage;
189
+ if (effectiveReplyTo) {
190
+ payload0.reply_metadata = {
191
+ reply_source_id: effectiveReplyTo,
192
+ reply_source_type: 1,
193
+ reply_type: 0
194
+ };
195
+ }
196
+
197
+ if (msg.url) {
198
+ // URL sharing via MQTT
199
+ payload0.send_type = 1;
200
+ payload0.link_share = {
201
+ share_type: 100,
202
+ share_params: await getShareUrl(msg.url)
203
+ };
204
+ }
205
+
206
+ if (msg.attachment) {
207
+ payload0.send_type = 3;
208
+ if (payload0.text === "") payload0.text = null;
209
+ payload0.attachment_fbids = [];
210
+ var list = Array.isArray(msg.attachment) ? msg.attachment : [msg.attachment];
211
+ var preuploaded = [], toUpload = [];
212
+ for (var item of list) {
213
+ if (Array.isArray(item) && item.length >= 2 && typeof item[0] === "string") {
214
+ preuploaded.push(String(item[1]));
215
+ } else if (utils.isReadableStream(item)) {
216
+ toUpload.push(item);
217
+ }
218
+ }
219
+ if (preuploaded.length) payload0.attachment_fbids = payload0.attachment_fbids.concat(preuploaded);
220
+ if (msg.forwardAttachmentIds && msg.forwardAttachmentIds.length) {
221
+ payload0.attachment_fbids = payload0.attachment_fbids.concat(msg.forwardAttachmentIds.map(String));
222
+ }
223
+
224
+ if (toUpload.length) {
225
+ var uploaded = await uploadAttachmentsMQTT(toUpload);
226
+ for (var u of uploaded) {
227
+ if (!u) continue;
228
+ var fbid = u.image_id || u.video_id || u.audio_id || u.file_id || u.sticker_id || u.gif_id;
229
+ if (!fbid) {
230
+ var firstKey = Object.keys(u).find(function(k) { return u[k] && /^\d+$/.test(String(u[k])); });
231
+ if (firstKey) fbid = u[firstKey];
232
+ }
233
+ if (fbid) payload0.attachment_fbids.push(String(fbid));
234
+ }
235
+ }
236
+ if (!payload0.attachment_fbids.length) delete payload0.attachment_fbids;
237
+ }
238
+
239
+ var tasks = [
240
+ {
241
+ label: '46',
242
+ payload: JSON.stringify(payload0),
243
+ queue_name: String(threadID),
244
+ task_id: 400,
245
+ failure_count: null
246
+ },
247
+ {
248
+ label: '21',
249
+ payload: JSON.stringify({
250
+ thread_id: String(threadID),
251
+ last_read_watermark_ts: Date.now(),
252
+ sync_group: 1
253
+ }),
254
+ queue_name: String(threadID),
255
+ task_id: 401,
256
+ failure_count: null
257
+ }
258
+ ];
259
+
260
+ var content = {
261
+ app_id: '2220391788200892',
262
+ payload: JSON.stringify({
263
+ tasks: tasks,
264
+ epoch_id: epoch,
265
+ version_id: '24804310205905615',
266
+ data_trace_id: '#' + Buffer.from(String(Math.random())).toString('base64').replace(/=+$/, '')
267
+ }),
268
+ request_id: requestId,
269
+ type: 3
270
+ };
271
+
272
+ return publishLsRequestWithAck(mqttClient, content, requestId, 15000);
273
+ }
274
+
275
+ // HTTP send path (OldMessage style)
276
+ async function sendViaHTTP(msg, threadID, isSingleUser, replyToMessage) {
277
+ var messageAndOTID = utils.generateOfflineThreadingID();
278
+ var form = {
279
+ client: "mercury",
280
+ action_type: "ma-type:user-generated-message",
281
+ author: "fbid:" + ctx.userID,
282
+ timestamp: Date.now(),
283
+ timestamp_absolute: "Today",
284
+ timestamp_relative: utils.generateTimestampRelative(),
285
+ timestamp_time_passed: "0",
286
+ is_unread: false,
287
+ is_cleared: false,
288
+ is_forward: false,
289
+ is_filtered_content: false,
290
+ is_filtered_content_bh: false,
291
+ is_filtered_content_account: false,
292
+ is_filtered_content_quasar: false,
293
+ is_filtered_content_invalid_app: false,
294
+ is_spoof_warning: false,
295
+ source: "source:chat:web",
296
+ "source_tags[0]": "source:chat",
297
+ body: msg.body || "",
298
+ html_body: false,
299
+ ui_push_phase: "V3",
300
+ status: "0",
301
+ offline_threading_id: messageAndOTID,
302
+ message_id: messageAndOTID,
303
+ threading_id: utils.generateThreadingID(ctx.clientID),
304
+ "ephemeral_ttl_mode:": "0",
305
+ manual_retry_cnt: "0",
306
+ has_attachment: !!(msg.attachment || msg.url || msg.sticker),
307
+ signatureID: utils.getSignatureID(),
308
+ replied_to_message_id: replyToMessage || ""
309
+ };
310
+
311
+ var threadIDStr = threadID.toString();
312
+ if (utils.getType(threadID) === "Array") {
313
+ for (var i = 0; i < threadID.length; i++) {
314
+ form["specific_to_list[" + i + "]"] = "fbid:" + threadID[i];
315
+ }
316
+ form["specific_to_list[" + threadID.length + "]"] = "fbid:" + ctx.userID;
317
+ form["client_thread_id"] = "root:" + messageAndOTID;
318
+ } else {
319
+ if (isSingleUser) {
320
+ form["specific_to_list[0]"] = "fbid:" + threadIDStr;
321
+ form["specific_to_list[1]"] = "fbid:" + ctx.userID;
322
+ form["other_user_fbid"] = threadIDStr;
323
+ } else {
324
+ form["thread_fbid"] = threadIDStr;
325
+ }
326
+ }
327
+
328
+ if (ctx.globalOptions.pageID) {
329
+ form["author"] = "fbid:" + ctx.globalOptions.pageID;
330
+ form["specific_to_list[1]"] = "fbid:" + ctx.globalOptions.pageID;
331
+ form["creator_info[creatorID]"] = ctx.userID;
332
+ form["creator_info[creatorType]"] = "direct_admin";
333
+ form["creator_info[labelType]"] = "sent_message";
334
+ form["creator_info[pageID]"] = ctx.globalOptions.pageID;
335
+ form["request_user_id"] = ctx.globalOptions.pageID;
336
+ form["creator_info[profileURI]"] = "https://www.facebook.com/profile.php?id=" + ctx.userID;
337
+ }
338
+
339
+ if (msg.location) {
340
+ form["location_attachment[coordinates][latitude]"] = msg.location.latitude;
341
+ form["location_attachment[coordinates][longitude]"] = msg.location.longitude;
342
+ form["location_attachment[is_current_location]"] = !!msg.location.current;
343
+ }
344
+
345
+ if (msg.sticker) {
346
+ form["sticker_id"] = msg.sticker;
347
+ }
348
+
349
+ if (msg.emoji) {
350
+ form.body = msg.emoji;
351
+ form["tags[0]"] = "hot_emoji_size:" + (msg.emojiSize || "medium");
352
+ }
353
+
354
+ if (msg.mentions) {
355
+ for (var j = 0; j < msg.mentions.length; j++) {
356
+ var mention = msg.mentions[j];
357
+ var tag = mention.tag;
358
+ var offset = (msg.body || "").indexOf(tag, mention.fromIndex || 0);
359
+ if (offset < 0) {
360
+ logger.warn("sendMessage", 'Mention for "' + tag + '" not found in message string.');
361
+ }
362
+ form["profile_xmd[" + j + "][offset]"] = offset + 1;
363
+ form["profile_xmd[" + j + "][length]"] = tag.length;
364
+ form["profile_xmd[" + j + "][id]"] = mention.id || 0;
365
+ form["profile_xmd[" + j + "][type]"] = "p";
366
+ }
367
+ }
368
+
369
+ if (msg.attachment) {
370
+ form.image_ids = [];
371
+ form.gif_ids = [];
372
+ form.file_ids = [];
373
+ form.video_ids = [];
374
+ form.audio_ids = [];
375
+ var attachments = utils.getType(msg.attachment) !== "Array" ? [msg.attachment] : msg.attachment;
376
+ var files = await uploadAttachmentHTTP(attachments);
377
+ files.forEach(function(file) {
378
+ var type = Object.keys(file)[0];
379
+ form[type + "s"].push(file[type]);
380
+ });
381
+ }
382
+
383
+ if (msg.url) {
384
+ form["shareable_attachment[share_type]"] = "100";
385
+ form["shareable_attachment[share_params]"] = await getShareUrl(msg.url);
386
+ }
387
+
388
+ var resData = await defaultFuncs
389
+ .post("https://www.facebook.com/messaging/send/", ctx.jar, form)
390
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
391
+
392
+ if (!resData) throw new Error("Send message failed.");
393
+ if (resData.error) {
394
+ if (resData.error === 1545012) {
395
+ throw new Error("Cannot send message to thread " + threadID + ": Bot is not part of this conversation (Error 1545012)");
396
+ }
397
+ throw new Error(resData.error);
398
+ }
399
+
400
+ return resData.payload.actions.reduce(function(p, v) {
401
+ return { threadID: v.thread_fbid, messageID: v.message_id, timestamp: v.timestamp } || p;
402
+ }, null);
403
+ }
404
+
405
+ return async function sendMessage(msg, threadID, callback, replyToMessage, isSingleUser) {
406
+ // Normalize parameters
407
+ if (typeof callback === "string" || (callback && typeof callback === "object")) {
408
+ isSingleUser = replyToMessage;
409
+ replyToMessage = callback;
410
+ callback = function() {};
411
+ }
412
+ if (typeof msg === "string") msg = { body: msg };
413
+ if (typeof callback !== "function") callback = null;
414
+
415
+ var resolve, reject;
416
+ var promise = new Promise((res, rej) => { resolve = res; reject = rej; });
417
+
418
+ // Validate properties
419
+ for (var key in msg) {
420
+ if (!ALLOWED[key]) {
421
+ var err = { error: "sendMessage: Unknown property '" + key + "'" };
422
+ if (callback) callback(err);
423
+ else reject(err);
424
+ return promise;
425
+ }
426
+ }
427
+
428
+ // Auto-detect isSingleUser from ctx.threadTypes if not explicitly provided
429
+ if (isSingleUser === undefined && ctx.threadTypes) {
430
+ isSingleUser = ctx.threadTypes[String(threadID)] === 'dm';
431
+ }
432
+ if (isSingleUser === undefined) {
433
+ var threadIDStr = threadID.toString();
434
+ isSingleUser = threadIDStr.length === 15 || !threadIDStr.match(/^\d{16,}$/);
435
+ }
436
+
437
+ // DM attachment sends — route appropriately
438
+ if (isSingleUser && msg.attachment) {
439
+ var useE2EE = api.e2ee && typeof api.e2ee.isConnected === "function" && api.e2ee.isConnected();
440
+ if (useE2EE) {
441
+ try {
442
+ var e2eeResult = await api.e2ee.sendMessage(String(threadID), msg, replyToMessage);
443
+ var wrapped = e2eeResult && e2eeResult.messageId
444
+ ? { threadID: String(threadID), messageID: String(e2eeResult.messageId) }
445
+ : e2eeResult;
446
+ if (callback) callback(null, wrapped);
447
+ else resolve(wrapped);
448
+ } catch (e2eeErr) {
449
+ logger.error("sendMessage", "E2EE DM attachment send failed: " + (e2eeErr.message || e2eeErr));
450
+ if (callback) callback(e2eeErr);
451
+ else reject(e2eeErr);
452
+ }
453
+ } else {
454
+ try {
455
+ var httpResult = await sendViaHTTP(msg, threadID, true, replyToMessage);
456
+ if (callback) callback(null, httpResult);
457
+ else resolve(httpResult);
458
+ } catch (httpErr) {
459
+ logger.error("sendMessage", "DM attachment via HTTP failed: " + (httpErr.error || httpErr.message || httpErr));
460
+ if (callback) callback(httpErr);
461
+ else reject(httpErr);
462
+ }
463
+ }
464
+ return promise;
465
+ }
466
+
467
+ // Try MQTT first, fallback to HTTP
468
+ try {
469
+ var mqttResult = await sendViaMqtt(msg, threadID, replyToMessage);
470
+ if (callback) callback(null, mqttResult);
471
+ else resolve(mqttResult);
472
+ } catch (mqttErr) {
473
+ logger.warn("sendMessage", "MQTT failed, falling back to HTTP: " + (mqttErr.message || mqttErr));
474
+ try {
475
+ var httpResult = await sendViaHTTP(msg, threadID, isSingleUser, replyToMessage);
476
+ if (callback) callback(null, httpResult);
477
+ else resolve(httpResult);
478
+ } catch (httpErr) {
479
+ logger.error("sendMessage", "HTTP fallback also failed: " + (httpErr.error || httpErr.message || httpErr));
480
+ if (callback) callback(httpErr);
481
+ else reject(httpErr);
482
+ }
483
+ }
484
+
485
+ return promise;
486
+ };
487
+ };