toru-fca 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +319 -0
  3. package/advancedSystem.js +95 -0
  4. package/build/messagix.dll +0 -0
  5. package/build/messagix.so +0 -0
  6. package/checkUpdate.js +37 -0
  7. package/config.json +45 -0
  8. package/e2ee.js +719 -0
  9. package/index.js +715 -0
  10. package/lib/index.mjs +1412 -0
  11. package/package.json +69 -0
  12. package/src/OldMessage.js +357 -0
  13. package/src/addExternalModule.js +25 -0
  14. package/src/addUserToGroup.js +117 -0
  15. package/src/changeAdminStatus.js +105 -0
  16. package/src/changeArchivedStatus.js +57 -0
  17. package/src/changeAvatar.js +138 -0
  18. package/src/changeAvatarV2.js +86 -0
  19. package/src/changeAvt.js +85 -0
  20. package/src/changeBio.js +78 -0
  21. package/src/changeBlockedStatus.js +51 -0
  22. package/src/changeBlockedStatusMqtt.js +80 -0
  23. package/src/changeCover.js +72 -0
  24. package/src/changeGroupImage.js +137 -0
  25. package/src/changeName.js +79 -0
  26. package/src/changeNickname.js +61 -0
  27. package/src/changeThreadColor.js +67 -0
  28. package/src/changeThreadEmoji.js +57 -0
  29. package/src/changeUsername.js +59 -0
  30. package/src/createCommentPost.js +230 -0
  31. package/src/createNewGroup.js +90 -0
  32. package/src/createPoll.js +73 -0
  33. package/src/createPost.js +276 -0
  34. package/src/data/getThreadInfo.json +1 -0
  35. package/src/deleteMessage.js +58 -0
  36. package/src/deleteThread.js +58 -0
  37. package/src/editMessage.js +81 -0
  38. package/src/editMessageOld.js +67 -0
  39. package/src/follow.js +74 -0
  40. package/src/forwardAttachment.js +62 -0
  41. package/src/friendList.js +104 -0
  42. package/src/getAccess.js +112 -0
  43. package/src/getAvatarUser.js +78 -0
  44. package/src/getBotInitialData.js +42 -0
  45. package/src/getCtx.js +5 -0
  46. package/src/getCurrentUserID.js +7 -0
  47. package/src/getEmojiUrl.js +29 -0
  48. package/src/getFriendsList.js +85 -0
  49. package/src/getMessage.js +849 -0
  50. package/src/getOptions.js +5 -0
  51. package/src/getRegion.js +7 -0
  52. package/src/getThreadHistory.js +682 -0
  53. package/src/getThreadHistoryDeprecated.js +71 -0
  54. package/src/getThreadInfo.js +232 -0
  55. package/src/getThreadInfoDeprecated.js +56 -0
  56. package/src/getThreadList.js +213 -0
  57. package/src/getThreadListDeprecated.js +46 -0
  58. package/src/getThreadPictures.js +59 -0
  59. package/src/getThreadTheme.js +81 -0
  60. package/src/getUID.js +119 -0
  61. package/src/getUserID.js +61 -0
  62. package/src/getUserInfo.js +66 -0
  63. package/src/handleFriendRequest.js +46 -0
  64. package/src/handleMessageRequest.js +47 -0
  65. package/src/httpGet.js +49 -0
  66. package/src/httpPost.js +48 -0
  67. package/src/httpPostFormData.js +70 -0
  68. package/src/listenMqtt.js +955 -0
  69. package/src/listenNotification.js +85 -0
  70. package/src/logout.js +19 -0
  71. package/src/markAsDelivered.js +47 -0
  72. package/src/markAsRead.js +72 -0
  73. package/src/markAsReadAll.js +40 -0
  74. package/src/markAsSeen.js +48 -0
  75. package/src/metaTheme.js +189 -0
  76. package/src/muteThread.js +45 -0
  77. package/src/note.js +228 -0
  78. package/src/pinMessage.js +59 -0
  79. package/src/refreshFb_dtsg.js +91 -0
  80. package/src/removeSuspiciousAccount.js +78 -0
  81. package/src/removeUserFromGroup.js +81 -0
  82. package/src/reply.js +442 -0
  83. package/src/resolvePhotoUrl.js +47 -0
  84. package/src/searchForThread.js +53 -0
  85. package/src/searchFriends.js +140 -0
  86. package/src/searchStickers.js +53 -0
  87. package/src/send.js +46 -0
  88. package/src/sendButtons.js +163 -0
  89. package/src/sendComment.js +159 -0
  90. package/src/sendFriendRequest.js +112 -0
  91. package/src/sendMessage.js +440 -0
  92. package/src/sendMessageMqtt.js +323 -0
  93. package/src/sendTypingIndicator.js +54 -0
  94. package/src/setActiveStatus.js +92 -0
  95. package/src/setMessageReaction.js +149 -0
  96. package/src/setMessageReactionMqtt.js +62 -0
  97. package/src/setPostReaction.js +114 -0
  98. package/src/setProfileGuard.js +44 -0
  99. package/src/setProfileLock.js +97 -0
  100. package/src/setStoryReaction.js +133 -0
  101. package/src/setStorySeen.js +108 -0
  102. package/src/setThreadTheme.js +102 -0
  103. package/src/setTitle.js +92 -0
  104. package/src/shareContact.js +110 -0
  105. package/src/shareLink.js +59 -0
  106. package/src/stopListenMqtt.js +37 -0
  107. package/src/storyManager.js +357 -0
  108. package/src/suggestFriend.js +132 -0
  109. package/src/threadColors.js +131 -0
  110. package/src/unfriend.js +54 -0
  111. package/src/unsendMessage.js +68 -0
  112. package/src/uploadAttachment.js +115 -0
  113. package/utils.js +3044 -0
@@ -0,0 +1,440 @@
1
+ "use strict";
2
+
3
+ const utils = require('../utils');
4
+ const log = require('npmlog');
5
+
6
+ const allowedProperties = {
7
+ attachment: true,
8
+ url: true,
9
+ sticker: true,
10
+ emoji: true,
11
+ emojiSize: true,
12
+ body: true,
13
+ mentions: true,
14
+ location: true,
15
+ replyToMessage: true,
16
+ forwardAttachmentIds: true,
17
+ };
18
+
19
+ const EMOJI_SIZES = { small: 1, medium: 2, large: 3 };
20
+
21
+ function toEmojiSize(size) {
22
+ if (typeof size === "number" && !isNaN(size)) return Math.min(3, Math.max(1, size));
23
+ if (typeof size === "string" && size in EMOJI_SIZES) return EMOJI_SIZES[size];
24
+ return 1;
25
+ }
26
+
27
+ function hasLinks(text) {
28
+ return /(https?:\/\/|www\.|t\.me\/|fb\.me\/|youtu\.be\/|facebook\.com\/|youtube\.com\/)/i.test(text);
29
+ }
30
+
31
+ function buildMentionData(msg, baseBody) {
32
+ if (!Array.isArray(msg.mentions) || !msg.mentions.length) return null;
33
+ var ids = [], offsets = [], lengths = [], types = [];
34
+ var cursor = 0;
35
+ for (var i = 0; i < msg.mentions.length; i++) {
36
+ var mention = msg.mentions[i];
37
+ var rawTag = String(mention.tag || "");
38
+ var displayName = rawTag.replace(/^@+/, "");
39
+ var start = Number.isInteger(mention.fromIndex) ? mention.fromIndex : cursor;
40
+ var index = baseBody.indexOf(rawTag, start);
41
+ var adjustment = 0;
42
+ if (index === -1) {
43
+ index = baseBody.indexOf(displayName, start);
44
+ } else {
45
+ adjustment = rawTag.length - displayName.length;
46
+ }
47
+ if (index < 0) { index = 0; adjustment = 0; }
48
+ var offset = index + adjustment;
49
+ ids.push(String(mention.id || 0));
50
+ offsets.push(offset);
51
+ lengths.push(displayName.length);
52
+ types.push("p");
53
+ cursor = offset + displayName.length;
54
+ }
55
+ return {
56
+ mention_ids: ids.join(","),
57
+ mention_offsets: offsets.join(","),
58
+ mention_lengths: lengths.join(","),
59
+ mention_types: types.join(",")
60
+ };
61
+ }
62
+
63
+ function extractIdsFromPayload(payload) {
64
+ var messageID = null, threadID = null;
65
+ function walk(node) {
66
+ if (!Array.isArray(node)) return;
67
+ if (node[0] === 5 && (node[1] === "replaceOptimsiticMessage" || node[1] === "replaceOptimisticMessage")) {
68
+ messageID = String(node[3]);
69
+ }
70
+ if (node[0] === 5 && node[1] === "writeCTAIdToThreadsTable") {
71
+ var candidate = node[2];
72
+ if (Array.isArray(candidate) && candidate[0] === 19) threadID = String(candidate[1]);
73
+ }
74
+ for (var i = 0; i < node.length; i++) walk(node[i]);
75
+ }
76
+ try { walk(payload && payload.step); } catch (_) { }
77
+ return { threadID: threadID, messageID: messageID };
78
+ }
79
+
80
+ function publishLsRequestWithAck(mqttClient, content, requestId, timeout) {
81
+ timeout = timeout || 15000;
82
+ return new Promise(function (resolve, reject) {
83
+ var timer = setTimeout(function () {
84
+ mqttClient.removeListener('message', onMessage);
85
+ reject(new Error('MQTT sendMessage timed out'));
86
+ }, timeout);
87
+
88
+ function onMessage(topic, message) {
89
+ if (topic !== '/ls_resp') return;
90
+ try {
91
+ var data = JSON.parse(message.toString());
92
+ if (String(data.request_id) === String(requestId)) {
93
+ clearTimeout(timer);
94
+ mqttClient.removeListener('message', onMessage);
95
+ var extracted = extractIdsFromPayload(
96
+ data.payload ? JSON.parse(data.payload) : {}
97
+ );
98
+ resolve({
99
+ threadID: extracted.threadID,
100
+ messageID: extracted.messageID
101
+ });
102
+ }
103
+ } catch (_) { }
104
+ }
105
+
106
+ mqttClient.on('message', onMessage);
107
+ mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1 }, function (err) {
108
+ if (err) {
109
+ clearTimeout(timer);
110
+ mqttClient.removeListener('message', onMessage);
111
+ reject(err);
112
+ }
113
+ });
114
+ });
115
+ }
116
+
117
+
118
+ // Cap the bot-sent message ID set to avoid unbounded growth on long-running bots.
119
+ var _BOT_SENT_CAP = 2000;
120
+ function _cappedSetAdd(s, v) {
121
+ if (s.size >= _BOT_SENT_CAP) s.delete(s.values().next().value);
122
+ s.add(v);
123
+ }
124
+
125
+ module.exports = function (defaultFuncs, api, ctx) {
126
+ var uploadAttachmentFn = require('./uploadAttachment')(defaultFuncs, api, ctx);
127
+
128
+ async function uploadAttachments(attachments) {
129
+ if (!Array.isArray(attachments)) attachments = [attachments];
130
+ return await uploadAttachmentFn(attachments);
131
+ }
132
+
133
+ async function sendViaMqtt(msg, threadID, replyToMessage) {
134
+ var mqttClient = ctx.mqttClient || global.mqttClient;
135
+ if (!mqttClient) throw new Error('MQTT client not available');
136
+
137
+ var baseBody = msg.body != null ? String(msg.body) : "";
138
+ var requestId = Math.floor(100 + Math.random() * 900);
139
+ var epoch = (BigInt(Date.now()) << 22n).toString();
140
+
141
+ var payload0 = {
142
+ thread_id: String(threadID),
143
+ otid: utils.generateOfflineThreadingID(),
144
+ source: 2097153,
145
+ send_type: 1,
146
+ sync_group: 1,
147
+ mark_thread_read: 1,
148
+ text: baseBody === "" ? null : baseBody,
149
+ initiating_source: 0,
150
+ skip_url_preview_gen: 0,
151
+ text_has_links: hasLinks(baseBody) ? 1 : 0,
152
+ multitab_env: 0,
153
+ metadata_dataclass: JSON.stringify({ media_accessibility_metadata: { alt_text: null } })
154
+ };
155
+
156
+ var mentionData = buildMentionData(msg, baseBody);
157
+ if (mentionData) payload0.mention_data = mentionData;
158
+
159
+ if (msg.sticker) {
160
+ payload0.send_type = 2;
161
+ payload0.sticker_id = msg.sticker;
162
+ }
163
+
164
+ if (msg.emoji) {
165
+ payload0.send_type = 1;
166
+ payload0.text = msg.emoji;
167
+ payload0.hot_emoji_size = toEmojiSize(msg.emojiSize);
168
+ }
169
+
170
+ if (msg.location && msg.location.latitude != null && msg.location.longitude != null) {
171
+ payload0.send_type = 1;
172
+ payload0.location_data = {
173
+ coordinates: { latitude: msg.location.latitude, longitude: msg.location.longitude },
174
+ is_current_location: Boolean(msg.location.current),
175
+ is_live_location: Boolean(msg.location.live)
176
+ };
177
+ }
178
+
179
+ var effectiveReplyTo = replyToMessage || msg.replyToMessage;
180
+ if (effectiveReplyTo) {
181
+ payload0.reply_metadata = {
182
+ reply_source_id: effectiveReplyTo,
183
+ reply_source_type: 1,
184
+ reply_type: 0
185
+ };
186
+ }
187
+
188
+ if (msg.attachment) {
189
+ payload0.send_type = 3;
190
+ if (payload0.text === "") payload0.text = null;
191
+ payload0.attachment_fbids = [];
192
+
193
+ var list = Array.isArray(msg.attachment) ? msg.attachment : [msg.attachment];
194
+ var preuploaded = [];
195
+ var toUpload = [];
196
+
197
+ for (var i = 0; i < list.length; i++) {
198
+ var item = list[i];
199
+ if (Array.isArray(item) && item.length >= 2 && typeof item[0] === "string") {
200
+ preuploaded.push(String(item[1]));
201
+ } else if (utils.isReadableStream(item)) {
202
+ toUpload.push(item);
203
+ }
204
+ }
205
+
206
+ if (preuploaded.length) {
207
+ payload0.attachment_fbids = payload0.attachment_fbids.concat(preuploaded);
208
+ }
209
+
210
+ if (Array.isArray(msg.forwardAttachmentIds) && msg.forwardAttachmentIds.length) {
211
+ payload0.attachment_fbids = payload0.attachment_fbids.concat(msg.forwardAttachmentIds.map(String));
212
+ }
213
+
214
+ if (toUpload.length) {
215
+ var uploaded = await uploadAttachments(toUpload);
216
+ for (var f = 0; f < uploaded.length; f++) {
217
+ var key = Object.keys(uploaded[f])[0];
218
+ payload0.attachment_fbids.push(String(uploaded[f][key]));
219
+ }
220
+ }
221
+ }
222
+
223
+ var content = {
224
+ app_id: "2220391788200892",
225
+ payload: {
226
+ tasks: [
227
+ {
228
+ label: "46",
229
+ payload: payload0,
230
+ queue_name: String(threadID),
231
+ task_id: 400,
232
+ failure_count: null
233
+ },
234
+ {
235
+ label: "21",
236
+ payload: {
237
+ thread_id: String(threadID),
238
+ last_read_watermark_ts: Date.now(),
239
+ sync_group: 1
240
+ },
241
+ queue_name: String(threadID),
242
+ task_id: 401,
243
+ failure_count: null
244
+ }
245
+ ],
246
+ epoch_id: epoch,
247
+ version_id: "24804310205905615",
248
+ data_trace_id: "#" + Buffer.from(String(Math.random())).toString("base64").replace(/=+$/, "")
249
+ },
250
+ request_id: requestId,
251
+ type: 3
252
+ };
253
+
254
+ content.payload.tasks = content.payload.tasks.map(function (task) {
255
+ return Object.assign({}, task, { payload: JSON.stringify(task.payload) });
256
+ });
257
+ content.payload = JSON.stringify(content.payload);
258
+
259
+ return await publishLsRequestWithAck(mqttClient, content, requestId);
260
+ }
261
+
262
+ return async function sendMessage(msg, threadID, callback, replyToMessage, isSingleUser) {
263
+ if (typeof callback === "string") {
264
+ isSingleUser = replyToMessage;
265
+ replyToMessage = callback;
266
+ callback = function () { };
267
+ } else if (typeof callback !== "function") {
268
+ callback = function () { };
269
+ }
270
+
271
+ // ── E2EE routing – sendMessageE2EE + sendMediaE2EE + downloadE2EEMedia ──
272
+ // When the destination thread is an E2EE JID (contains "@"), route through
273
+ // the Labyrinth native bridge instead of the MQTT/HTTP path.
274
+ // (Also exposes api.downloadE2EEMedia for decrypting received attachments.)
275
+ var _e2eeMod = require('../e2ee');
276
+ // Route to E2EE bridge whenever threadID is a JID — the "@" is definitive.
277
+ // Also respect explicit enableE2EE flag as a fallback check.
278
+ if (_e2eeMod.isE2EEChatJid(String(threadID))) {
279
+ var _bridge = _e2eeMod.createBridge(ctx);
280
+ var _form = typeof msg === "string" ? { body: msg } : (msg || {});
281
+ var _text = String(_form.body || _form.text || "");
282
+ var _atts = !_form.attachment ? []
283
+ : (Array.isArray(_form.attachment) ? _form.attachment : [_form.attachment]);
284
+ var _sendOpts = {};
285
+ if (replyToMessage) {
286
+ _sendOpts.replyToId = String(replyToMessage);
287
+ var _rjid = global._e2eeSenderJidMap && global._e2eeSenderJidMap.get(String(replyToMessage));
288
+ if (_rjid) _sendOpts.replyToSenderJid = _rjid;
289
+ }
290
+
291
+ var _e2eePromise = (async function () {
292
+ var _last;
293
+ // sendMediaE2EE: send each attachment
294
+ for (var _i = 0; _i < _atts.length; _i++) {
295
+ var _att = _atts[_i]; if (!_att) continue;
296
+ try {
297
+ var _buf;
298
+ if (Buffer.isBuffer(_att)) {
299
+ _buf = _att;
300
+ } else if (_att && typeof _att.read === "function") {
301
+ _buf = await new Promise(function (res, rej) {
302
+ var chunks = [];
303
+ _att.on("data", function (c) { chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c)); });
304
+ _att.on("end", function () { res(Buffer.concat(chunks)); });
305
+ _att.on("error", rej);
306
+ });
307
+ } else if (_att && _att.type === "Buffer" && Array.isArray(_att.data)) {
308
+ _buf = Buffer.from(_att.data);
309
+ } else { continue; }
310
+
311
+ var _mt = (_att.mediaType ? String(_att.mediaType).toLowerCase() : null)
312
+ || (function () {
313
+ var p = String(_att.path || _att.filename || "").split(".").pop().toLowerCase();
314
+ if (["jpg","jpeg","png","gif","webp","bmp"].includes(p)) return "image";
315
+ if (["mp4","mov","avi","mkv","webm"].includes(p)) return "video";
316
+ if (["mp3","ogg","oga","opus","wav","m4a","aac","flac"].includes(p)) return "audio";
317
+ return "document";
318
+ })();
319
+ var _mOpts = Object.assign({}, _sendOpts);
320
+ if (_i === 0 && _text) _mOpts.caption = _text;
321
+ if (!_mOpts.mimeType && _att.mimeType) _mOpts.mimeType = _att.mimeType;
322
+ if ((_mt === "file" || _mt === "document") && !_mOpts.filename)
323
+ _mOpts.filename = (_att.filename || _att.path || "file.bin").split(/[\\/]/).pop();
324
+ if (_att.duration != null) _mOpts.duration = Number(_att.duration);
325
+ if (_att.width != null) _mOpts.width = Number(_att.width);
326
+ if (_att.height != null) _mOpts.height = Number(_att.height);
327
+ if (_att.ptt || _att.voice) _mOpts.ptt = true;
328
+
329
+ var _mRes = await _bridge.sendMedia(threadID, _mt, _buf, _mOpts);
330
+ _last = { threadID: threadID, messageID: _mRes && _mRes.messageId ? String(_mRes.messageId) : undefined, isE2EE: true };
331
+ if (_last.messageID) {
332
+ var _e2eeM = require('../e2ee');
333
+ global._e2eeMessageMap = global._e2eeMessageMap || new Map();
334
+ _e2eeM._cappedMapSet
335
+ ? _e2eeM._cappedMapSet(global._e2eeMessageMap, _last.messageID, String(threadID))
336
+ : global._e2eeMessageMap.set(_last.messageID, String(threadID));
337
+ global._e2eeBotSentMsgIds = global._e2eeBotSentMsgIds || new Set();
338
+ _cappedSetAdd(global._e2eeBotSentMsgIds, _last.messageID);
339
+ }
340
+ } catch (_me) { log.error("E2EE", "sendMedia att#" + _i + " failed:", _me && _me.message ? _me.message : _me); }
341
+ }
342
+ // sendMessageE2EE: send text (if no attachments, or text wasn't used as caption)
343
+ if (!_last || _atts.length === 0) {
344
+ var _tRes = await _bridge.sendMessage(threadID, _text || "\u200b", _sendOpts);
345
+ _last = { threadID: threadID, messageID: _tRes && _tRes.messageId ? String(_tRes.messageId) : undefined, isE2EE: true };
346
+ if (_last.messageID) {
347
+ var _e2eeM2 = require('../e2ee');
348
+ global._e2eeMessageMap = global._e2eeMessageMap || new Map();
349
+ _e2eeM2._cappedMapSet
350
+ ? _e2eeM2._cappedMapSet(global._e2eeMessageMap, _last.messageID, String(threadID))
351
+ : global._e2eeMessageMap.set(_last.messageID, String(threadID));
352
+ global._e2eeBotSentMsgIds = global._e2eeBotSentMsgIds || new Set();
353
+ _cappedSetAdd(global._e2eeBotSentMsgIds, _last.messageID);
354
+ }
355
+ }
356
+ return _last;
357
+ })();
358
+
359
+ // downloadE2EEMedia: exposed on api for received attachment decryption
360
+ if (typeof api.downloadE2EEMedia !== "function") {
361
+ api.downloadE2EEMedia = function (options) { return _bridge.downloadMedia(options); };
362
+ }
363
+
364
+ _e2eePromise.then(function (r) { callback(null, r); }).catch(function (e) { callback(e); });
365
+ return _e2eePromise;
366
+ }
367
+ // ── end E2EE routing ───────────────────────────────────────────────────────
368
+
369
+ var msgType = utils.getType(msg);
370
+ var threadIDType = utils.getType(threadID);
371
+
372
+ if (msgType !== "String" && msgType !== "Object") throw new Error("Message should be of type string or object and not " + msgType + ".");
373
+ if (threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") throw new Error("ThreadID should be of type number, string, or array and not " + threadIDType + ".");
374
+ if (replyToMessage && utils.getType(replyToMessage) !== "String") throw new Error("replyToMessage should be of type string.");
375
+
376
+ if (msgType === "String") msg = { body: msg };
377
+
378
+ var disallowedProperties = Object.keys(msg).filter(function (prop) { return !allowedProperties[prop]; });
379
+ if (disallowedProperties.length > 0) throw new Error("Disallowed props: `" + disallowedProperties.join(", ") + "`");
380
+
381
+ var configSource = (global.GoatBot && global.GoatBot.config) ? global.GoatBot.config : (ctx.config || {});
382
+ var enableTypingIndicator = typeof configSource.enableTypingIndicator !== 'undefined' ? configSource.enableTypingIndicator : (ctx.config && ctx.config.enableTypingIndicator);
383
+ var typingDuration = Number(configSource.typingDuration || (ctx.config && ctx.config.typingDuration) || 4000);
384
+ // E2EE threads need a longer visible duration — 1 second disappears before
385
+ // Messenger renders it. Use e2eeTypingDuration from config (default 3000 ms).
386
+ var _isE2EEThread = require('../e2ee').isE2EEChatJid(String(threadID));
387
+ if (_isE2EEThread) {
388
+ typingDuration = Number(configSource.e2eeTypingDuration || (ctx.config && ctx.config.e2eeTypingDuration) || 3000);
389
+ }
390
+
391
+ if (enableTypingIndicator) {
392
+ await api.sendTypingIndicator(true, threadID, function () { }).catch(function () { });
393
+ await utils.delay(typingDuration);
394
+ }
395
+
396
+ // Normal inbox/page conversations need a reliable HTTP fallback. In page mode
397
+ // OldMessage also supplies the page-specific creator fields that the MQTT
398
+ // payload does not carry. For ordinary personal inbox chats, MQTT remains
399
+ // the fast path and HTTP is used only when the transport actually fails.
400
+ var normalizedThreadID = typeof threadID === "string" ? threadID.trim() : threadID;
401
+ if (global._alphaFcaConfig && global._alphaFcaConfig.inbox && global._alphaFcaConfig.inbox.enabled === false) {
402
+ throw new Error("Inbox messaging is disabled by configuration");
403
+ }
404
+
405
+ // Shared typing-off helper — always call this before returning, success or error.
406
+ function stopTyping() {
407
+ if (enableTypingIndicator) {
408
+ api.sendTypingIndicator(false, normalizedThreadID, function () {}).catch(function () {});
409
+ }
410
+ }
411
+
412
+ try {
413
+ var result;
414
+ if (ctx.globalOptions.pageID) {
415
+ // Page inbox: use the legacy HTTP sender first because it correctly
416
+ // sets creator_info/pageID fields required by Facebook page threads.
417
+ result = await new Promise(function (resolve, reject) {
418
+ api.OldMessage(msg, normalizedThreadID, function (err, data) {
419
+ if (err) return reject(err);
420
+ resolve(data);
421
+ }, replyToMessage, isSingleUser);
422
+ });
423
+ } else {
424
+ result = await sendViaMqtt(msg, normalizedThreadID, replyToMessage);
425
+ }
426
+ stopTyping();
427
+ // MQTT acknowledgements can omit the message/thread ID on some responses.
428
+ // Guarantee the caller always gets a usable threadID back.
429
+ if (!result || typeof result !== 'object') result = {};
430
+ if (!result.threadID) result.threadID = String(normalizedThreadID);
431
+ if (typeof callback === "function") callback(null, result);
432
+ return result;
433
+ } catch (sendErr) {
434
+ stopTyping();
435
+ log.warn("sendMessage", "MQTT send failed, falling back to HTTP: " +
436
+ (sendErr && sendErr.message ? sendErr.message : sendErr));
437
+ return api.OldMessage(msg, normalizedThreadID, callback, replyToMessage, isSingleUser);
438
+ }
439
+ };
440
+ };