shadowx-fca 2.4.0 → 2.5.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.
@@ -1,243 +1,232 @@
1
1
  "use strict";
2
2
 
3
+
3
4
  const utils = require('../utils');
4
- // @NethWs3Dev
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
15
  };
16
16
 
17
+
18
+
17
19
  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
- }
46
-
47
- async function sendContent(form, threadID, isSingleUser, messageAndOTID, callback) {
48
- // There are three cases here:
49
- // 1. threadID is of type array, where we're starting a new group chat with users
50
- // specified in the array.
51
- // 2. User is sending a message to a specific user.
52
- // 3. No additional form params and the message goes to an existing group chat.
53
- if (utils.getType(threadID) === "Array") {
54
- for (var i = 0; i < threadID.length; i++) {
55
- form["specific_to_list[" + i + "]"] = "fbid:" + threadID[i];
56
- }
57
- form["specific_to_list[" + threadID.length + "]"] = "fbid:" + ctx.userID;
58
- form["client_thread_id"] = "root:" + messageAndOTID;
59
- utils.log("sendMessage", "Sending message to multiple users: " + threadID);
60
- } else {
61
- const threadIDStr = threadID.toString();
62
- // Check if it's a DM: doesn't start with numeric group ID pattern or explicitly marked as single user
63
- const isDM = !threadIDStr.match(/^\d{15,}$/) || isSingleUser === true;
64
-
65
- // This means that threadID is the id of a user, and the chat
66
- // is a single person chat
67
- if (isDM) {
68
- form["specific_to_list[0]"] = "fbid:" + threadID;
69
- form["specific_to_list[1]"] = "fbid:" + ctx.userID;
70
- form["other_user_fbid"] = threadID;
71
- } else {
72
- form["thread_fbid"] = threadID;
73
- }
74
- }
75
-
76
- if (ctx.globalOptions.pageID) {
77
- form["author"] = "fbid:" + ctx.globalOptions.pageID;
78
- form["specific_to_list[1]"] = "fbid:" + ctx.globalOptions.pageID;
79
- form["creator_info[creatorID]"] = ctx.userID;
80
- form["creator_info[creatorType]"] = "direct_admin";
81
- form["creator_info[labelType]"] = "sent_message";
82
- form["creator_info[pageID]"] = ctx.globalOptions.pageID;
83
- form["request_user_id"] = ctx.globalOptions.pageID;
84
- form["creator_info[profileURI]"] =
85
- "https://www.facebook.com/profile.php?id=" + ctx.userID;
86
- }
87
-
88
- const resData = await defaultFuncs.post("https://www.facebook.com/messaging/send/", ctx.jar, form).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
89
- if (!resData) {
90
- throw new Error("Send message failed.");
91
- }
92
- if (resData.error) {
93
- if (resData.error === 1545012) {
94
- utils.warn("sendMessage", "Got error 1545012. This might mean that you're not part of the conversation " + threadID);
95
- throw new Error(`Cannot send message to thread ${threadID}: Bot is not part of this conversation (Error 1545012)`);
96
- }
97
- throw new Error(resData);
98
- }
99
- const messageInfo = resData.payload.actions.reduce((p, v) => {
100
- return { threadID: v.thread_fbid, messageID: v.message_id, timestamp: v.timestamp } || p;
101
- }, null);
102
- return messageInfo;
103
- }
104
-
105
- return async (msg, threadID, callback, replyToMessage, isSingleUser = null) => {
106
- // Handle different parameter patterns for backward compatibility
107
- if (typeof callback === "string" || (callback && typeof callback === "object")) {
108
- // callback is actually replyToMessage, shift parameters
109
- isSingleUser = replyToMessage;
110
- replyToMessage = callback;
111
- callback = function() {};
112
- } else if (typeof callback !== "function") {
113
- callback = function() {};
114
- }
115
-
116
- let msgType = utils.getType(msg);
117
- let threadIDType = utils.getType(threadID);
118
- let messageIDType = utils.getType(replyToMessage);
119
- if (msgType !== "String" && msgType !== "Object") throw new Error("Message should be of type string or object and not " + msgType + ".");
120
- if (threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") throw new Error("ThreadID should be of type number, string, or array and not " + threadIDType + ".");
121
- if (replyToMessage && messageIDType !== 'String' && messageIDType !== 'string') throw new Error("MessageID should be of type string and not " + messageIDType + ".");
122
- if (msgType === "String") {
123
- msg = { body: msg };
124
- }
125
-
126
- // Auto-detect if this is a DM if not explicitly specified
127
- if (isSingleUser === null && ctx.threadTypes && ctx.threadTypes[threadID]) {
128
- isSingleUser = ctx.threadTypes[threadID] === 'dm';
129
- } else if (isSingleUser === null) {
130
- // Fallback: check if threadID looks like a user ID (15 digits) vs group ID (longer)
131
- const threadIDStr = threadID.toString();
132
- isSingleUser = threadIDStr.length === 15 || !threadIDStr.match(/^\d{16,}$/);
133
- }
134
- let disallowedProperties = Object.keys(msg).filter(prop => !allowedProperties[prop]);
135
- if (disallowedProperties.length > 0) {
136
- throw new Error("Dissallowed props: `" + disallowedProperties.join(", ") + "`");
137
- }
138
- let messageAndOTID = utils.generateOfflineThreadingID();
139
- let form = {
140
- client: "mercury",
141
- action_type: "ma-type:user-generated-message",
142
- author: "fbid:" + ctx.userID,
143
- timestamp: Date.now(),
144
- timestamp_absolute: "Today",
145
- timestamp_relative: utils.generateTimestampRelative(),
146
- timestamp_time_passed: "0",
147
- is_unread: false,
148
- is_cleared: false,
149
- is_forward: false,
150
- is_filtered_content: false,
151
- is_filtered_content_bh: false,
152
- is_filtered_content_account: false,
153
- is_filtered_content_quasar: false,
154
- is_filtered_content_invalid_app: false,
155
- is_spoof_warning: false,
156
- source: "source:chat:web",
157
- "source_tags[0]": "source:chat",
158
- ...(msg.body && {
159
- body: msg.body
160
- }),
161
- html_body: false,
162
- ui_push_phase: "V3",
163
- status: "0",
164
- offline_threading_id: messageAndOTID,
165
- message_id: messageAndOTID,
166
- threading_id: utils.generateThreadingID(ctx.clientID),
167
- "ephemeral_ttl_mode:": "0",
168
- manual_retry_cnt: "0",
169
- has_attachment: !!(msg.attachment || msg.url || msg.sticker),
170
- signatureID: utils.getSignatureID(),
171
- ...(replyToMessage && {
172
- replied_to_message_id: replyToMessage
173
- })
174
- };
175
-
176
- if (msg.location) {
177
- if (!msg.location.latitude || !msg.location.longitude) throw new Error("location property needs both latitude and longitude");
178
- form["location_attachment[coordinates][latitude]"] = msg.location.latitude;
179
- form["location_attachment[coordinates][longitude]"] = msg.location.longitude;
180
- form["location_attachment[is_current_location]"] = !!msg.location.current;
181
- }
182
- if (msg.sticker) {
183
- form["sticker_id"] = msg.sticker;
184
- }
185
- if (msg.attachment) {
186
- form.image_ids = [];
187
- form.gif_ids = [];
188
- form.file_ids = [];
189
- form.video_ids = [];
190
- form.audio_ids = [];
191
- if (utils.getType(msg.attachment) !== "Array") {
192
- msg.attachment = [msg.attachment];
193
- }
194
- const files = await uploadAttachment(msg.attachment);
195
- files.forEach(file => {
196
- const type = Object.keys(file)[0];
197
- form["" + type + "s"].push(file[type]);
198
- });
199
- }
200
- if (msg.url) {
201
- form["shareable_attachment[share_type]"] = "100";
202
- const params = await getUrl(msg.url);
203
- form["shareable_attachment[share_params]"] = params;
204
- }
205
- if (msg.emoji) {
206
- if (!msg.emojiSize) {
207
- msg.emojiSize = "medium";
208
- }
209
- if (msg.emojiSize !== "small" && msg.emojiSize !== "medium" && msg.emojiSize !== "large") {
210
- throw new Error("emojiSize property is invalid");
211
- }
212
- if (!form.body) {
213
- throw new Error("body is not empty");
214
- }
215
- form.body = msg.emoji;
216
- form["tags[0]"] = "hot_emoji_size:" + msg.emojiSize;
217
- }
218
- if (msg.mentions) {
219
- for (let i = 0; i < msg.mentions.length; i++) {
220
- const mention = msg.mentions[i];
221
- const tag = mention.tag;
222
- if (typeof tag !== "string") {
223
- throw new Error("Mention tags must be strings.");
224
- }
225
- const offset = msg.body.indexOf(tag, mention.fromIndex || 0);
226
- if (offset < 0) utils.warn("handleMention", 'Mention for "' + tag + '" not found in message string.');
227
- if (!mention.id) utils.warn("handleMention", "Mention id should be non-null.");
228
- const id = mention.id || 0;
229
- const emptyChar = '\u200E';
230
- form["body"] = emptyChar + msg.body;
231
- form["profile_xmd[" + i + "][offset]"] = offset + 1;
232
- form["profile_xmd[" + i + "][length]"] = tag.length;
233
- form["profile_xmd[" + i + "][id]"] = id;
234
- form["profile_xmd[" + i + "][type]"] = "p";
235
- }
236
- }
237
- const result = await sendContent(form, threadID, isSingleUser, messageAndOTID);
238
- if (callback && typeof callback === "function") {
239
- callback(null, result);
240
- }
241
- return result;
242
- };
243
- };
20
+
21
+ async function uploadAttachment(attachments) {
22
+ const uploads = [];
23
+ for (const att of attachments) {
24
+ if (!utils.isReadableStream(att)) {
25
+ throw new Error("Attachment must be a readable stream, got: " + utils.getType(att));
26
+ }
27
+ const res = await defaultFuncs.postFormData(
28
+ "https://upload.facebook.com/ajax/mercury/upload.php",
29
+ ctx.jar,
30
+ { upload_1024: att, voice_clip: "true" },
31
+ {}
32
+ ).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
33
+ if (res.error) throw new Error("Upload failed: " + JSON.stringify(res));
34
+ uploads.push(res.payload.metadata[0]);
35
+ }
36
+ return uploads;
37
+ }
38
+
39
+ async function getUrl(url) {
40
+ const res = await defaultFuncs.post(
41
+ "https://www.facebook.com/message_share_attachment/fromURI/",
42
+ ctx.jar,
43
+ { image_height: 960, image_width: 960, uri: url }
44
+ ).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
45
+ if (!res || res.error || !res.payload) throw new Error("URL attachment failed: " + JSON.stringify(res));
46
+ return res.payload;
47
+ }
48
+
49
+ async function sendContent(form, threadID, messageAndOTID) {
50
+ const tid = String(threadID);
51
+
52
+ if (Array.isArray(threadID)) {
53
+ threadID.forEach((id, idx) => { form[`specific_to_list[${idx}]`] = "fbid:" + id; });
54
+ form[`specific_to_list[${threadID.length}]`] = "fbid:" + ctx.userID;
55
+ form["client_thread_id"] = "root:" + messageAndOTID;
56
+ utils.log("sendMessage", "Creating new group with users: " + threadID.join(', '));
57
+ } else {
58
+ // Group thread — works for any digit length (15, 16, 17, 18, 19...)
59
+ form["thread_fbid"] = tid;
60
+ utils.log("sendMessage", "Sending to group thread: " + tid);
61
+ }
62
+
63
+ if (ctx.globalOptions.pageID) {
64
+ form["author"] = "fbid:" + ctx.globalOptions.pageID;
65
+ form["specific_to_list[1]"] = "fbid:" + ctx.globalOptions.pageID;
66
+ form["creator_info[creatorID]"] = ctx.userID;
67
+ form["creator_info[creatorType]"] = "direct_admin";
68
+ form["creator_info[labelType]"] = "sent_message";
69
+ form["creator_info[pageID]"] = ctx.globalOptions.pageID;
70
+ form["request_user_id"] = ctx.globalOptions.pageID;
71
+ form["creator_info[profileURI]"] = "https://www.facebook.com/profile.php?id=" + ctx.userID;
72
+ }
73
+
74
+ const resData = await defaultFuncs
75
+ .post("https://www.facebook.com/messaging/send/", ctx.jar, form)
76
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
77
+
78
+ if (!resData) throw new Error("Send message failed — no response.");
79
+ if (resData.error) {
80
+ if (resData.error === 1545012) {
81
+ utils.warn("sendMessage", "Error 1545012: You may not be a member of thread " + tid);
82
+ }
83
+ throw new Error("Send message error: " + JSON.stringify(resData));
84
+ }
85
+
86
+ return resData.payload.actions.reduce((p, v) => ({
87
+ threadID : v.thread_fbid,
88
+ messageID : v.message_id,
89
+ timestamp : v.timestamp,
90
+ }), null);
91
+ }
92
+
93
+ /**
94
+ * Send a message to a Facebook group thread.
95
+ *
96
+ * Supports both callback and Promise/await style:
97
+ *
98
+ * api.sendMessage("Hello!", threadID, callback) // callback style
99
+ * await api.sendMessage("Hello!", threadID) // promise style
100
+ * api.sendMessage("Reply!", threadID, msgID, callback) // reply + callback
101
+ * await api.sendMessage("Reply!", threadID, msgID) // reply + promise
102
+ *
103
+ * @param {string|object} msg — text string or {body, sticker, attachment, url, emoji, mentions, location}
104
+ * @param {string|Array} threadID — group thread ID (any length) or array of userIDs to create new group
105
+ * @param {string|Function} [replyOrCallback]— optional: message ID to reply to, OR callback function(err, info)
106
+ * @param {Function} [callback] — optional: callback(err, info) when replyToMessage is provided
107
+ */
108
+ return (msg, threadID, replyOrCallback, callbackArg) => {
109
+
110
+ // ── Resolve replyToMessage vs callback ───────────────────────
111
+ let replyToMessage = null;
112
+ let callback = null;
113
+
114
+ if (typeof replyOrCallback === 'function') {
115
+ // sendMessage(msg, threadID, callback)
116
+ callback = replyOrCallback;
117
+ } else if (typeof replyOrCallback === 'string') {
118
+ // sendMessage(msg, threadID, replyMsgID) or sendMessage(msg, threadID, replyMsgID, callback)
119
+ replyToMessage = replyOrCallback;
120
+ if (typeof callbackArg === 'function') callback = callbackArg;
121
+ }
122
+
123
+ // ── Core send logic (async) ──────────────────────────────────
124
+ const doSend = async () => {
125
+ const msgType = utils.getType(msg);
126
+ if (msgType !== "String" && msgType !== "Object") {
127
+ throw new Error("Message must be a string or object, got: " + msgType);
128
+ }
129
+ if (msgType === "String") msg = { body: msg };
130
+
131
+ const disallowed = Object.keys(msg).filter(p => !allowedProperties[p]);
132
+ if (disallowed.length > 0) {
133
+ throw new Error("Disallowed message properties: " + disallowed.join(", "));
134
+ }
135
+
136
+ const messageAndOTID = utils.generateOfflineThreadingID();
137
+ const form = {
138
+ client : "mercury",
139
+ action_type : "ma-type:user-generated-message",
140
+ author : "fbid:" + ctx.userID,
141
+ timestamp : Date.now(),
142
+ timestamp_absolute : "Today",
143
+ timestamp_relative : utils.generateTimestampRelative(),
144
+ timestamp_time_passed : "0",
145
+ is_unread : false,
146
+ is_cleared : false,
147
+ is_forward : false,
148
+ is_filtered_content : false,
149
+ is_filtered_content_bh : false,
150
+ is_filtered_content_account : false,
151
+ is_filtered_content_quasar : false,
152
+ is_filtered_content_invalid_app : false,
153
+ is_spoof_warning : false,
154
+ source : "source:chat:web",
155
+ "source_tags[0]" : "source:chat",
156
+ ...(msg.body && { body: msg.body }),
157
+ html_body : false,
158
+ ui_push_phase : "V3",
159
+ status : "0",
160
+ offline_threading_id : messageAndOTID,
161
+ message_id : messageAndOTID,
162
+ threading_id : utils.generateThreadingID(ctx.clientID),
163
+ "ephemeral_ttl_mode:" : "0",
164
+ manual_retry_cnt : "0",
165
+ has_attachment : !!(msg.attachment || msg.url || msg.sticker),
166
+ signatureID : utils.getSignatureID(),
167
+ ...(replyToMessage && { replied_to_message_id: replyToMessage }),
168
+ };
169
+
170
+ if (msg.location) {
171
+ if (!msg.location.latitude || !msg.location.longitude) {
172
+ throw new Error("location needs both latitude and longitude.");
173
+ }
174
+ form["location_attachment[coordinates][latitude]"] = msg.location.latitude;
175
+ form["location_attachment[coordinates][longitude]"] = msg.location.longitude;
176
+ form["location_attachment[is_current_location]"] = !!msg.location.current;
177
+ }
178
+
179
+ if (msg.sticker) form["sticker_id"] = msg.sticker;
180
+
181
+ if (msg.attachment) {
182
+ form.image_ids = []; form.gif_ids = []; form.file_ids = [];
183
+ form.video_ids = []; form.audio_ids = [];
184
+ if (utils.getType(msg.attachment) !== "Array") msg.attachment = [msg.attachment];
185
+ const files = await uploadAttachment(msg.attachment);
186
+ files.forEach(file => {
187
+ const type = Object.keys(file)[0];
188
+ form[type + "s"].push(file[type]);
189
+ });
190
+ }
191
+
192
+ if (msg.url) {
193
+ form["shareable_attachment[share_type]"] = "100";
194
+ form["shareable_attachment[share_params]"] = await getUrl(msg.url);
195
+ }
196
+
197
+ if (msg.emoji) {
198
+ if (!msg.emojiSize) msg.emojiSize = "medium";
199
+ if (!["small","medium","large"].includes(msg.emojiSize)) {
200
+ throw new Error("emojiSize must be small, medium, or large.");
201
+ }
202
+ if (!form.body) throw new Error("body must not be empty when using emoji.");
203
+ form.body = msg.emoji;
204
+ form["tags[0]"] = "hot_emoji_size:" + msg.emojiSize;
205
+ }
206
+
207
+ if (msg.mentions) {
208
+ for (let i = 0; i < msg.mentions.length; i++) {
209
+ const { tag, id, fromIndex } = msg.mentions[i];
210
+ if (typeof tag !== "string") throw new Error("Mention tags must be strings.");
211
+ const offset = msg.body.indexOf(tag, fromIndex || 0);
212
+ if (offset < 0) utils.warn("sendMessage", `Mention "${tag}" not found in body.`);
213
+ const emptyChar = '\u200E';
214
+ form["body"] = emptyChar + msg.body;
215
+ form[`profile_xmd[${i}][offset]`] = offset + 1;
216
+ form[`profile_xmd[${i}][length]`] = tag.length;
217
+ form[`profile_xmd[${i}][id]`] = id || 0;
218
+ form[`profile_xmd[${i}][type]`] = "p";
219
+ }
220
+ }
221
+
222
+ return sendContent(form, threadID, messageAndOTID);
223
+ };
224
+
225
+ // ── Return Promise OR call callback ──────────────────────────
226
+ if (callback) {
227
+ doSend().then(info => callback(null, info)).catch(err => callback(err));
228
+ } else {
229
+ return doSend();
230
+ }
231
+ };
232
+ };
@@ -1,101 +1,45 @@
1
- "use strict";
2
-
3
- const utils = require("../utils");
4
- // @NethWs3Dev
5
-
6
- module.exports = function (defaultFuncs, api, ctx) {
7
- function makeTypingIndicator(typ, threadID, callback, isGroup) {
8
- const form = {
9
- typ: +typ,
10
- to: "",
11
- source: "mercury-chat",
12
- thread: threadID,
13
- };
14
-
15
- // Check if thread is a single person chat or a group chat
16
- // More info on this is in api.sendMessage
17
- if (utils.getType(isGroup) == "Boolean") {
18
- if (!isGroup) {
19
- form.to = threadID;
20
- }
21
- defaultFuncs
22
- .post("https://www.facebook.com/ajax/messaging/typ.php", ctx.jar, form)
23
- .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
24
- .then(function (resData) {
25
- if (resData.error) {
26
- throw resData;
27
- }
28
-
29
- return callback();
30
- })
31
- .catch(function (err) {
32
- console.error("sendTypingIndicator", err);
33
- return callback(err);
34
- });
35
- } else {
36
- api.getUserInfo(threadID, function (err, res) {
37
- if (err) {
38
- return callback(err);
39
- }
40
-
41
- // If id is single person chat
42
- if (Object.keys(res).length > 0) {
43
- form.to = threadID;
44
- }
45
-
46
- defaultFuncs
47
- .post(
48
- "https://www.facebook.com/ajax/messaging/typ.php",
49
- ctx.jar,
50
- form,
51
- )
52
- .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
53
- .then(function (resData) {
54
- if (resData.error) {
55
- throw resData;
56
- }
57
-
58
- return callback();
59
- })
60
- .catch(function (err) {
61
- console.error("sendTypingIndicator", err);
62
- return callback(err);
63
- });
64
- });
65
- }
66
- }
67
-
68
- return function sendTypingIndicator(threadID, callback, isGroup) {
69
- if (
70
- utils.getType(callback) !== "Function" &&
71
- utils.getType(callback) !== "AsyncFunction"
72
- ) {
73
- if (callback) {
74
- console.warn(
75
- "sendTypingIndicator",
76
- "callback is not a function - ignoring.",
77
- );
78
- }
79
- callback = () => {};
80
- }
81
-
82
- makeTypingIndicator(true, threadID, callback, isGroup);
83
-
84
- return function end(cb) {
85
- if (
86
- utils.getType(cb) !== "Function" &&
87
- utils.getType(cb) !== "AsyncFunction"
88
- ) {
89
- if (cb) {
90
- console.warn(
91
- "sendTypingIndicator",
92
- "callback is not a function - ignoring.",
93
- );
94
- }
95
- cb = () => {};
96
- }
97
-
98
- makeTypingIndicator(false, threadID, cb, isGroup);
99
- };
100
- };
101
- };
1
+ "use strict";
2
+
3
+
4
+
5
+ var utils = require("../utils");
6
+ // @NethWs3Dev
7
+
8
+ module.exports = function (defaultFuncs, api, ctx) {
9
+ return async function sendTypingIndicatorV2(sendTyping, threadID, callback) {
10
+ const mqttClient = ctx.mqttClient || global.mqttClient;
11
+ if (!mqttClient) {
12
+ if (typeof callback === 'function') callback(new Error('No MQTT client available for typing indicator'));
13
+ return;
14
+ }
15
+
16
+ let count_req = 0;
17
+ var wsContent = {
18
+ app_id: 2220391788200892,
19
+ payload: JSON.stringify({
20
+ label: 3,
21
+ payload: JSON.stringify({
22
+ thread_key: threadID.toString(),
23
+ is_group_thread: +(threadID.toString().length >= 16),
24
+ is_typing: +sendTyping,
25
+ attribution: 0
26
+ }),
27
+ version: 5849951561777440
28
+ }),
29
+ request_id: ++count_req,
30
+ type: 4
31
+ };
32
+
33
+ return new Promise((resolve, reject) => {
34
+ mqttClient.publish('/ls_req', JSON.stringify(wsContent), {}, (err, _packet) => {
35
+ if (err) {
36
+ if (typeof callback === 'function') callback(err);
37
+ reject(err);
38
+ } else {
39
+ if (typeof callback === 'function') callback(null, _packet);
40
+ resolve(_packet);
41
+ }
42
+ });
43
+ });
44
+ };
45
+ };