stfca 1.0.26 → 1.1.27

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.
@@ -1,285 +1,299 @@
1
1
  "use strict";
2
2
 
3
3
  const utils = require('../utils');
4
- // @NethWs3Dev
4
+ const log = require('npmlog');
5
5
 
6
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,
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,
15
17
  };
16
18
 
17
- module.exports = (defaultFuncs, api, ctx) => {
18
- async function uploadAttachment(attachments) {
19
- var uploads = [];
20
- for (var i = 0; i < attachments.length; i++) {
21
- if (!utils.isReadableStream(attachments[i])) {
22
- throw new Error("Attachment should be a readable stream and not " + utils.getType(attachments[i]) + ".");
23
- }
24
- const oksir = await defaultFuncs.postFormData("https://upload.facebook.com/ajax/mercury/upload.php", ctx.jar,{
25
- upload_1024: attachments[i],
26
- voice_clip: "true"
27
- }, {}).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
28
- if (oksir.error) {
29
- throw new Error(resData);
30
- }
31
- uploads.push(oksir.payload.metadata[0]);
32
- }
33
- return uploads;
34
- }
35
-
36
- async function getUrl(url) {
37
- const resData = await defaultFuncs.post("https://www.facebook.com/message_share_attachment/fromURI/", ctx.jar, {
38
- image_height: 960,
39
- image_width: 960,
40
- uri: url
41
- }).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
42
- if (!resData || resData.error || !resData.payload){
43
- throw new Error(resData);
44
- }
45
- return resData.payload;
46
- }
47
-
48
- async function ensureImgbbUrl(msg) {
49
- if (!api.uploadImageToImgbb) return;
50
- try {
51
- if (msg.attachment && typeof msg.attachment === 'string') {
52
- const uploaded = await api.uploadImageToImgbb(msg.attachment).catch(() => null);
53
- if (uploaded && uploaded.data) {
54
- msg.url = uploaded.data.url || uploaded.data.display_url || (uploaded.data.image && uploaded.data.image.url);
55
- delete msg.attachment;
56
- }
57
- }
58
- if (msg.url && typeof msg.url === 'string') {
59
- const dataUri = /^data:image\/[a-zA-Z]+;base64,/.test(msg.url);
60
- const base64String = /^[A-Za-z0-9+/=]+$/.test(msg.url.replace(/\s+/g, '')) && msg.url.length > 100;
61
- if (dataUri || base64String) {
62
- const uploaded = await api.uploadImageToImgbb(msg.url).catch(() => null);
63
- if (uploaded && uploaded.data) {
64
- msg.url = uploaded.data.url || uploaded.data.display_url || (uploaded.data.image && uploaded.data.image.url);
65
- }
66
- }
67
- }
68
- } catch (err) {
69
- return;
70
- }
71
- }
72
-
73
- async function sendContent(form, threadID, isSingleUser, messageAndOTID, callback) {
74
- // There are three cases here:
75
- // 1. threadID is of type array, where we're starting a new group chat with users
76
- // specified in the array.
77
- // 2. User is sending a message to a specific user.
78
- // 3. No additional form params and the message goes to an existing group chat.
79
- if (utils.getType(threadID) === "Array") {
80
- for (var i = 0; i < threadID.length; i++) {
81
- form["specific_to_list[" + i + "]"] = "fbid:" + threadID[i];
82
- }
83
- form["specific_to_list[" + threadID.length + "]"] = "fbid:" + ctx.userID;
84
- form["client_thread_id"] = "root:" + messageAndOTID;
85
- utils.log("sendMessage", "Sending message to multiple users: " + threadID);
86
- } else {
87
- const threadIDStr = threadID.toString();
88
- // Check if it's a DM: doesn't start with numeric group ID pattern or explicitly marked as single user
89
- const isDM = !threadIDStr.match(/^\d{15,}$/) || isSingleUser === true;
90
-
91
- // This means that threadID is the id of a user, and the chat
92
- // is a single person chat
93
- if (isDM) {
94
- form["specific_to_list[0]"] = "fbid:" + threadID;
95
- form["specific_to_list[1]"] = "fbid:" + ctx.userID;
96
- form["other_user_fbid"] = threadID;
97
- } else {
98
- form["thread_fbid"] = threadID;
99
- }
100
- }
101
-
102
- if (ctx.globalOptions.pageID) {
103
- form["author"] = "fbid:" + ctx.globalOptions.pageID;
104
- form["specific_to_list[1]"] = "fbid:" + ctx.globalOptions.pageID;
105
- form["creator_info[creatorID]"] = ctx.userID;
106
- form["creator_info[creatorType]"] = "direct_admin";
107
- form["creator_info[labelType]"] = "sent_message";
108
- form["creator_info[pageID]"] = ctx.globalOptions.pageID;
109
- form["request_user_id"] = ctx.globalOptions.pageID;
110
- form["creator_info[profileURI]"] =
111
- "https://www.facebook.com/profile.php?id=" + ctx.userID;
112
- }
113
-
114
- const resData = await defaultFuncs.post("https://www.facebook.com/messaging/send/", ctx.jar, form).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
115
- if (!resData) {
116
- throw new Error("Send message failed.");
117
- }
118
- if (resData.error) {
119
- if (resData.error === 1545012) {
120
- utils.warn("sendMessage", "Got error 1545012. This might mean that you're not part of the conversation " + threadID);
121
- throw new Error(`Cannot send message to thread ${threadID}: Bot is not part of this conversation (Error 1545012)`);
122
- }
123
- throw new Error(resData);
124
- }
125
- const messageInfo = resData.payload.actions.reduce((p, v) => {
126
- return { threadID: v.thread_fbid, messageID: v.message_id, timestamp: v.timestamp } || p;
127
- }, null);
128
- return messageInfo;
129
- }
130
-
131
- return async (msg, threadID, callback, replyToMessage, isSingleUser = null) => {
132
- // Handle different parameter patterns for backward compatibility
133
- if (typeof callback === "string" || (callback && typeof callback === "object")) {
134
- // callback is actually replyToMessage, shift parameters
135
- isSingleUser = replyToMessage;
136
- replyToMessage = callback;
137
- callback = function() {};
138
- } else if (typeof callback !== "function") {
139
- callback = function() {};
140
- }
141
-
142
- let msgType = utils.getType(msg);
143
- let threadIDType = utils.getType(threadID);
144
- let messageIDType = utils.getType(replyToMessage);
145
- if (msgType !== "String" && msgType !== "Object") throw new Error("Message should be of type string or object and not " + msgType + ".");
146
- if (threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") throw new Error("ThreadID should be of type number, string, or array and not " + threadIDType + ".");
147
- if (replyToMessage && messageIDType !== 'String' && messageIDType !== 'string') throw new Error("MessageID should be of type string and not " + messageIDType + ".");
148
- if (msgType === "String") {
149
- msg = { body: msg };
150
- }
151
- await ensureImgbbUrl(msg);
152
-
153
- // Auto-detect if this is a DM if not explicitly specified
154
- if (isSingleUser === null && ctx.threadTypes && ctx.threadTypes[threadID]) {
155
- isSingleUser = ctx.threadTypes[threadID] === 'dm';
156
- } else if (isSingleUser === null) {
157
- // Fallback: check if threadID looks like a user ID (15 digits) vs group ID (longer)
158
- const threadIDStr = threadID.toString();
159
- isSingleUser = threadIDStr.length === 15 || !threadIDStr.match(/^\d{16,}$/);
160
- }
161
- let disallowedProperties = Object.keys(msg).filter(prop => !allowedProperties[prop]);
162
- if (disallowedProperties.length > 0) {
163
- throw new Error("Dissallowed props: `" + disallowedProperties.join(", ") + "`");
164
- }
165
- let messageAndOTID = utils.generateOfflineThreadingID();
166
- let form = {
167
- client: "mercury",
168
- action_type: "ma-type:user-generated-message",
169
- author: "fbid:" + ctx.userID,
170
- timestamp: Date.now(),
171
- timestamp_absolute: "Today",
172
- timestamp_relative: utils.generateTimestampRelative(),
173
- timestamp_time_passed: "0",
174
- is_unread: false,
175
- is_cleared: false,
176
- is_forward: false,
177
- is_filtered_content: false,
178
- is_filtered_content_bh: false,
179
- is_filtered_content_account: false,
180
- is_filtered_content_quasar: false,
181
- is_filtered_content_invalid_app: false,
182
- is_spoof_warning: false,
183
- source: "source:chat:web",
184
- "source_tags[0]": "source:chat",
185
- ...(msg.body && {
186
- body: msg.body
187
- }),
188
- html_body: false,
189
- ui_push_phase: "V3",
190
- status: "0",
191
- offline_threading_id: messageAndOTID,
192
- message_id: messageAndOTID,
193
- threading_id: utils.generateThreadingID(ctx.clientID),
194
- "ephemeral_ttl_mode:": "0",
195
- manual_retry_cnt: "0",
196
- has_attachment: !!(msg.attachment || msg.url || msg.sticker),
197
- signatureID: utils.getSignatureID(),
198
- ...(replyToMessage && {
199
- replied_to_message_id: replyToMessage
200
- })
201
- };
202
-
203
- if (msg.location) {
204
- if (!msg.location.latitude || !msg.location.longitude) throw new Error("location property needs both latitude and longitude");
205
- form["location_attachment[coordinates][latitude]"] = msg.location.latitude;
206
- form["location_attachment[coordinates][longitude]"] = msg.location.longitude;
207
- form["location_attachment[is_current_location]"] = !!msg.location.current;
208
- }
209
- if (msg.sticker) {
210
- form["sticker_id"] = msg.sticker;
211
- }
212
- if (msg.attachment) {
213
- form.image_ids = [];
214
- form.gif_ids = [];
215
- form.file_ids = [];
216
- form.video_ids = [];
217
- form.audio_ids = [];
218
- if (utils.getType(msg.attachment) !== "Array") {
219
- msg.attachment = [msg.attachment];
220
- }
221
- const files = await uploadAttachment(msg.attachment);
222
- files.forEach(file => {
223
- const type = Object.keys(file)[0];
224
- form["" + type + "s"].push(file[type]);
225
- });
226
- }
227
- if (msg.url) {
228
- form["shareable_attachment[share_type]"] = "100";
229
- const params = await getUrl(msg.url);
230
- form["shareable_attachment[share_params]"] = params;
231
- }
232
- if (msg.emoji) {
233
- if (!msg.emojiSize) {
234
- msg.emojiSize = "medium";
235
- }
236
- if (msg.emojiSize !== "small" && msg.emojiSize !== "medium" && msg.emojiSize !== "large") {
237
- throw new Error("emojiSize property is invalid");
238
- }
239
- if (!form.body) {
240
- throw new Error("body is not empty");
241
- }
242
- form.body = msg.emoji;
243
- form["tags[0]"] = "hot_emoji_size:" + msg.emojiSize;
244
- }
245
- if (msg.mentions) {
246
- for (let i = 0; i < msg.mentions.length; i++) {
247
- const mention = msg.mentions[i];
248
- const tag = mention.tag;
249
- if (typeof tag !== "string") {
250
- throw new Error("Mention tags must be strings.");
251
- }
252
- const offset = msg.body.indexOf(tag, mention.fromIndex || 0);
253
- if (offset < 0) utils.warn("handleMention", 'Mention for "' + tag + '" not found in message string.');
254
- if (!mention.id) utils.warn("handleMention", "Mention id should be non-null.");
255
- const id = mention.id || 0;
256
- const emptyChar = '\u200E';
257
- form["body"] = emptyChar + msg.body;
258
- form["profile_xmd[" + i + "][offset]"] = offset + 1;
259
- form["profile_xmd[" + i + "][length]"] = tag.length;
260
- form["profile_xmd[" + i + "][id]"] = id;
261
- form["profile_xmd[" + i + "][type]"] = "p";
262
- }
263
- }
264
- const configSource = (global.GoatBot && global.GoatBot.config) ? global.GoatBot.config : ctx.config || {};
265
- const enableTypingIndicator = typeof configSource.enableTypingIndicator !== 'undefined' ? configSource.enableTypingIndicator : ctx.config?.enableTypingIndicator;
266
- const typingDuration = Number(configSource.typingDuration || ctx.config?.typingDuration || 4000);
267
-
268
- if (enableTypingIndicator) {
269
- await api.sendTypingIndicator(true, threadID, () => {});
270
- await utils.delay(typingDuration);
271
- const result = await sendContent(form, threadID, isSingleUser, messageAndOTID);
272
- await api.sendTypingIndicator(false, threadID, () => {});
273
- if (callback && typeof callback === "function") {
274
- callback(null, result);
275
- }
276
- return result;
277
- } else {
278
- const result = await sendContent(form, threadID, isSingleUser, messageAndOTID);
279
- if (callback && typeof callback === "function") {
280
- callback(null, result);
281
- }
282
- return result;
283
- }
284
- };
285
- };
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
+ module.exports = function (defaultFuncs, api, ctx) {
118
+ var uploadAttachmentFn = require('./uploadAttachment')(defaultFuncs, api, ctx);
119
+
120
+ async function uploadAttachments(attachments) {
121
+ if (!Array.isArray(attachments)) attachments = [attachments];
122
+ return await uploadAttachmentFn(attachments);
123
+ }
124
+
125
+ async function sendViaMqtt(msg, threadID, replyToMessage) {
126
+ var mqttClient = ctx.mqttClient || global.mqttClient;
127
+ if (!mqttClient) throw new Error('MQTT client not available');
128
+
129
+ var baseBody = msg.body != null ? String(msg.body) : "";
130
+ var requestId = Math.floor(100 + Math.random() * 900);
131
+ var epoch = (BigInt(Date.now()) << 22n).toString();
132
+
133
+ var payload0 = {
134
+ thread_id: String(threadID),
135
+ otid: utils.generateOfflineThreadingID(),
136
+ source: 2097153,
137
+ send_type: 1,
138
+ sync_group: 1,
139
+ mark_thread_read: 1,
140
+ text: baseBody === "" ? null : baseBody,
141
+ initiating_source: 0,
142
+ skip_url_preview_gen: 0,
143
+ text_has_links: hasLinks(baseBody) ? 1 : 0,
144
+ multitab_env: 0,
145
+ metadata_dataclass: JSON.stringify({ media_accessibility_metadata: { alt_text: null } })
146
+ };
147
+
148
+ var mentionData = buildMentionData(msg, baseBody);
149
+ if (mentionData) payload0.mention_data = mentionData;
150
+
151
+ if (msg.sticker) {
152
+ payload0.send_type = 2;
153
+ payload0.sticker_id = msg.sticker;
154
+ }
155
+
156
+ if (msg.emoji) {
157
+ payload0.send_type = 1;
158
+ payload0.text = msg.emoji;
159
+ payload0.hot_emoji_size = toEmojiSize(msg.emojiSize);
160
+ }
161
+
162
+ if (msg.location && msg.location.latitude != null && msg.location.longitude != null) {
163
+ payload0.send_type = 1;
164
+ payload0.location_data = {
165
+ coordinates: { latitude: msg.location.latitude, longitude: msg.location.longitude },
166
+ is_current_location: Boolean(msg.location.current),
167
+ is_live_location: Boolean(msg.location.live)
168
+ };
169
+ }
170
+
171
+ var effectiveReplyTo = replyToMessage || msg.replyToMessage;
172
+ if (effectiveReplyTo) {
173
+ payload0.reply_metadata = {
174
+ reply_source_id: effectiveReplyTo,
175
+ reply_source_type: 1,
176
+ reply_type: 0
177
+ };
178
+ }
179
+
180
+ if (msg.attachment) {
181
+ payload0.send_type = 3;
182
+ if (payload0.text === "") payload0.text = null;
183
+ payload0.attachment_fbids = [];
184
+
185
+ var list = Array.isArray(msg.attachment) ? msg.attachment : [msg.attachment];
186
+ var preuploaded = [];
187
+ var toUpload = [];
188
+
189
+ for (var i = 0; i < list.length; i++) {
190
+ var item = list[i];
191
+ if (Array.isArray(item) && item.length >= 2 && typeof item[0] === "string") {
192
+ preuploaded.push(String(item[1]));
193
+ } else if (utils.isReadableStream(item)) {
194
+ toUpload.push(item);
195
+ }
196
+ }
197
+
198
+ if (preuploaded.length) {
199
+ payload0.attachment_fbids = payload0.attachment_fbids.concat(preuploaded);
200
+ }
201
+
202
+ if (Array.isArray(msg.forwardAttachmentIds) && msg.forwardAttachmentIds.length) {
203
+ payload0.attachment_fbids = payload0.attachment_fbids.concat(msg.forwardAttachmentIds.map(String));
204
+ }
205
+
206
+ if (toUpload.length) {
207
+ var uploaded = await uploadAttachments(toUpload);
208
+ for (var f = 0; f < uploaded.length; f++) {
209
+ var key = Object.keys(uploaded[f])[0];
210
+ payload0.attachment_fbids.push(String(uploaded[f][key]));
211
+ }
212
+ }
213
+ }
214
+
215
+ var content = {
216
+ app_id: "2220391788200892",
217
+ payload: {
218
+ tasks: [
219
+ {
220
+ label: "46",
221
+ payload: payload0,
222
+ queue_name: String(threadID),
223
+ task_id: 400,
224
+ failure_count: null
225
+ },
226
+ {
227
+ label: "21",
228
+ payload: {
229
+ thread_id: String(threadID),
230
+ last_read_watermark_ts: Date.now(),
231
+ sync_group: 1
232
+ },
233
+ queue_name: String(threadID),
234
+ task_id: 401,
235
+ failure_count: null
236
+ }
237
+ ],
238
+ epoch_id: epoch,
239
+ version_id: "24804310205905615",
240
+ data_trace_id: "#" + Buffer.from(String(Math.random())).toString("base64").replace(/=+$/, "")
241
+ },
242
+ request_id: requestId,
243
+ type: 3
244
+ };
245
+
246
+ content.payload.tasks = content.payload.tasks.map(function (task) {
247
+ return Object.assign({}, task, { payload: JSON.stringify(task.payload) });
248
+ });
249
+ content.payload = JSON.stringify(content.payload);
250
+
251
+ return await publishLsRequestWithAck(mqttClient, content, requestId);
252
+ }
253
+
254
+ return async function sendMessage(msg, threadID, callback, replyToMessage, isSingleUser) {
255
+ if (typeof callback === "string") {
256
+ isSingleUser = replyToMessage;
257
+ replyToMessage = callback;
258
+ callback = function () { };
259
+ } else if (typeof callback !== "function") {
260
+ callback = function () { };
261
+ }
262
+
263
+ var msgType = utils.getType(msg);
264
+ var threadIDType = utils.getType(threadID);
265
+
266
+ if (msgType !== "String" && msgType !== "Object") throw new Error("Message should be of type string or object and not " + msgType + ".");
267
+ if (threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") throw new Error("ThreadID should be of type number, string, or array and not " + threadIDType + ".");
268
+ if (replyToMessage && utils.getType(replyToMessage) !== "String") throw new Error("replyToMessage should be of type string.");
269
+
270
+ if (msgType === "String") msg = { body: msg };
271
+
272
+ var disallowedProperties = Object.keys(msg).filter(function (prop) { return !allowedProperties[prop]; });
273
+ if (disallowedProperties.length > 0) throw new Error("Disallowed props: `" + disallowedProperties.join(", ") + "`");
274
+
275
+ var configSource = (global.GoatBot && global.GoatBot.config) ? global.GoatBot.config : (ctx.config || {});
276
+ var enableTypingIndicator = typeof configSource.enableTypingIndicator !== 'undefined' ? configSource.enableTypingIndicator : (ctx.config && ctx.config.enableTypingIndicator);
277
+ var typingDuration = Number(configSource.typingDuration || (ctx.config && ctx.config.typingDuration) || 4000);
278
+
279
+ if (enableTypingIndicator) {
280
+ await api.sendTypingIndicator(true, threadID, function () { });
281
+ await utils.delay(typingDuration);
282
+ }
283
+
284
+ try {
285
+ var result = await sendViaMqtt(msg, threadID, replyToMessage);
286
+ if (enableTypingIndicator) {
287
+ api.sendTypingIndicator(false, threadID, function () { }).catch(function () { });
288
+ }
289
+ if (typeof callback === "function") callback(null, result);
290
+ return result;
291
+ } catch (mqttErr) {
292
+ log.warn("sendMessage", "MQTT send failed, falling back to HTTP: " + (mqttErr && mqttErr.message));
293
+ if (enableTypingIndicator) {
294
+ api.sendTypingIndicator(false, threadID, function () { }).catch(function () { });
295
+ }
296
+ return api.OldMessage(msg, threadID, callback, replyToMessage, isSingleUser);
297
+ }
298
+ };
299
+ };
@@ -209,7 +209,6 @@ module.exports = function (defaultFuncs, api, ctx) {
209
209
  task.payload = JSON.stringify(task.payload);
210
210
  });
211
211
  form.payload = JSON.stringify(form.payload);
212
- console.log(global.jsonStringifyColor(form, null, 2));
213
212
 
214
213
  return mqttClient.publish(
215
214
  "/ls_req",