shadowx-fca 2.5.0 → 2.7.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,245 +1,66 @@
1
1
  "use strict";
2
- // sahilchat-fca — Author: S4hiilAns4ri (github.com/S4hiilAns4ri)
3
- const utils = require('../utils');
4
- const _ = require('lodash');
5
- const deepdash = require('deepdash');
6
- deepdash(_);
7
2
 
8
- /**
9
- * @param {object} data
10
- * @param {string} userID
11
- * @returns {object|null}
12
- */
13
- function findMainUserObject(data, userID) {
14
- let mainUserObject = null;
15
- if (!Array.isArray(data)) return null;
16
- function deepFind(obj) {
17
- if (mainUserObject || typeof obj !== 'object' || obj === null) return;
18
- if (obj.id === userID && obj.__typename === 'User' && obj.profile_tabs) {
19
- mainUserObject = obj;
20
- return;
21
- }
22
- for (const k in obj) {
23
- if (obj.hasOwnProperty(k)) {
24
- deepFind(obj[k]);
25
- }
26
- }
27
- }
28
- deepFind({ all: data });
29
- return mainUserObject;
30
- }
3
+ var utils = require("../utils");
4
+ var log = require("npmlog");
31
5
 
32
- /**
33
- * @param {object} socialContext
34
- * @param {string} keyword
35
- * @returns {string|null}
36
- */
37
- function findSocialContextText(socialContext, keyword) {
38
- if (socialContext && Array.isArray(socialContext.content)) {
39
- for (const item of socialContext.content) {
40
- const text = item?.text?.text;
41
- if (text && text.toLowerCase().includes(keyword.toLowerCase())) {
42
- return text;
43
- }
44
- }
45
- }
46
- return null;
47
- }
6
+ function formatData(data) {
7
+ var retObj = {};
48
8
 
49
- /**
50
- * @param {Array<Object>} dataArray
51
- * @param {string} key
52
- * @returns {any}
53
- */
54
- function findFirstValueByKey(dataArray, key) {
55
- if (!Array.isArray(dataArray)) return null;
56
- let found = null;
57
- function deepSearch(obj) {
58
- if (found !== null || typeof obj !== 'object' || obj === null) return;
59
- if (obj.hasOwnProperty(key)) {
60
- found = obj[key];
61
- return;
62
- }
63
- for (const k in obj) {
64
- if (obj.hasOwnProperty(k)) {
65
- deepSearch(obj[k]);
66
- }
9
+ for (var prop in data) {
10
+ // eslint-disable-next-line no-prototype-builtins
11
+ if (data.hasOwnProperty(prop)) {
12
+ var innerObj = data[prop];
13
+ retObj[prop] = {
14
+ name: innerObj.name,
15
+ firstName: innerObj.firstName,
16
+ vanity: innerObj.vanity,
17
+ thumbSrc: innerObj.thumbSrc,
18
+ profileUrl: innerObj.uri,
19
+ gender: innerObj.gender,
20
+ type: innerObj.type,
21
+ isFriend: innerObj.is_friend,
22
+ isBirthday: !!innerObj.is_birthday
23
+ };
67
24
  }
68
25
  }
69
- for (const obj of dataArray) {
70
- deepSearch(obj);
71
- }
72
- return found;
73
- }
74
26
 
75
- /**
76
- * @param {Array<Object>} allJsonData
77
- * @returns {string|null}
78
- */
79
- function findBioFromProfileTiles(allJsonData) {
80
- try {
81
- const bio = findFirstValueByKey(allJsonData, 'profile_status_text');
82
- return bio?.text || null;
83
- } catch {
84
- return null;
85
- }
27
+ return retObj;
86
28
  }
87
29
 
88
- /**
89
- * @param {Array<Object>} allJsonData
90
- * @returns {string|null}
91
- */
92
- function findLiveCityFromProfileTiles(allJsonData) {
93
- try {
94
- const result = _.findDeep(allJsonData, (value, key, parent) => {
95
- return key === 'text' &&
96
- typeof value === 'string' &&
97
- value.includes('Lives in') &&
98
- parent?.ranges?.[0]?.entity?.category_type === "CITY_WITH_ID";
99
- });
100
-
101
- if (result) {
102
- return result.value;
103
- }
104
-
105
- return null;
106
- } catch (err) {
107
- return null;
108
- }
109
- }
110
-
111
- module.exports = (defaultFuncs, api, ctx) => {
112
- function createDefaultUser(id) {
113
- return {
114
- id,
115
- name: "Facebook User",
116
- firstName: "Facebook",
117
- lastName: null,
118
- vanity: id,
119
- profilePicUrl: `https://graph.facebook.com/${id}/picture?width=720&height=720&access_token=6628568379%7Cc1e620fa708a1d5696fb991c1bde5662`,
120
- profileUrl: `https://www.facebook.com/profile.php?id=${id}`,
121
- gender: "no specific gender",
122
- type: "user",
123
- isFriend: false,
124
- isBirthday: false
125
- };
126
- }
127
-
128
- return function getUserInfo(id, usePayload, callback, groupFields = []) {
129
- let resolveFunc = () => {};
130
- let rejectFunc = () => {};
131
- const returnPromise = new Promise((resolve, reject) => {
30
+ module.exports = function (defaultFuncs, api, ctx) {
31
+ return function getUserInfo(id, callback) {
32
+ var resolveFunc = function () { };
33
+ var rejectFunc = function () { };
34
+ var returnPromise = new Promise(function (resolve, reject) {
132
35
  resolveFunc = resolve;
133
36
  rejectFunc = reject;
134
37
  });
135
38
 
136
- if (typeof usePayload === 'function') {
137
- callback = usePayload;
138
- usePayload = true;
139
- }
140
- if (usePayload === undefined) usePayload = true;
141
39
  if (!callback) {
142
- callback = (err, data) => {
40
+ callback = function (err, userInfo) {
143
41
  if (err) return rejectFunc(err);
144
- resolveFunc(data);
42
+ resolveFunc(userInfo);
145
43
  };
146
44
  }
147
45
 
148
- const originalIdIsArray = Array.isArray(id);
149
- const ids = originalIdIsArray ? id : [id];
46
+ if (utils.getType(id) !== "Array") id = [id];
150
47
 
151
- if (usePayload) {
152
- const form = {};
153
- ids.forEach((v, i) => { form[`ids[${i}]`] = v; });
154
- const getGenderString = (code) => code === 1 ? "male" : code === 2 ? "female" : "no specific gender";
155
- defaultFuncs.post("https://www.facebook.com/chat/user_info/", ctx.jar, form)
156
- .then(resData => utils.parseAndCheckLogin(ctx, defaultFuncs)(resData))
157
- .then(resData => {
158
- if (resData?.error && resData?.error !== 3252001) throw resData;
159
- const retObj = {};
160
- const profiles = resData?.payload?.profiles;
161
- if (profiles) {
162
- for (const prop in profiles) {
163
- if (profiles.hasOwnProperty(prop)) {
164
- const inner = profiles[prop];
165
- const nameParts = inner.name ? inner.name.split(' ') : [];
166
- retObj[prop] = {
167
- id: prop,
168
- name: inner.name,
169
- firstName: inner.firstName,
170
- lastName: nameParts.length > 1 ? nameParts[nameParts.length - 1] : null,
171
- vanity: inner.vanity,
172
- profilePicUrl: `https://graph.facebook.com/${prop}/picture?width=720&height=720&access_token=6628568379%7Cc1e620fa708a1d5696fb991c1bde5662`,
173
- profileUrl: inner.uri,
174
- gender: getGenderString(inner.gender),
175
- type: inner.type,
176
- isFriend: inner.is_friend,
177
- isBirthday: !!inner.is_birthday,
178
- searchTokens: inner.searchTokens,
179
- alternateName: inner.alternateName
180
- };
181
- }
182
- }
183
- } else {
184
- for (const prop of ids) {
185
- retObj[prop] = createDefaultUser(prop);
186
- }
187
- }
188
- return originalIdIsArray ? callback(null, Object.values(retObj)) : callback(null, retObj[ids[0]]);
189
- }).catch(err => {
190
- utils.error("getUserInfo (payload)", err);
191
- return callback(err);
192
- });
193
- } else {
194
- const fetchProfile = async (userID) => {
195
- try {
196
- const url = `https://www.facebook.com/${userID}`;
197
- const allJsonData = await utils.json(url, ctx.jar, null, ctx.globalOptions, ctx);
198
- if (!allJsonData || allJsonData.length === 0) throw new Error(`Could not find JSON data for ID: ${userID}`);
199
- const mainUserObject = findMainUserObject(allJsonData, userID);
200
- if (!mainUserObject) throw new Error(`Could not isolate main user object for ID: ${userID}`);
201
- const get = (obj, path) => {
202
- if (!obj || !path) return null;
203
- return path.split('.').reduce((prev, curr) => (prev ? prev[curr] : undefined), obj);
204
- };
205
- const name = mainUserObject.name;
206
- const nameParts = name ? name.split(' ') : [];
207
- const result = {
208
- id: mainUserObject.id,
209
- name: name,
210
- firstName: nameParts[0] || get(mainUserObject, 'short_name') || get(findFirstValueByKey(allJsonData, 'profile_owner'), 'short_name'),
211
- lastName: nameParts.length > 1 ? nameParts[nameParts.length - 1] : null,
212
- vanity: get(mainUserObject, 'vanity') || get(findFirstValueByKey(allJsonData, 'props'), 'userVanity') || null,
213
- profileUrl: mainUserObject.url,
214
- profilePicUrl: `https://graph.facebook.com/${userID}/picture?width=720&height=720&access_token=6628568379%7Cc1e620fa708a1d5696fb991c1bde5662`,
215
- gender: mainUserObject.gender,
216
- type: mainUserObject.__typename,
217
- isFriend: mainUserObject.is_viewer_friend,
218
- isBirthday: !!mainUserObject.is_birthday,
219
- isVerified: !!mainUserObject.show_verified_badge_on_profile,
220
- bio: findBioFromProfileTiles(allJsonData) || get(findFirstValueByKey(allJsonData, 'delegate_page'), 'best_description.text'),
221
- live_city: findLiveCityFromProfileTiles(allJsonData),
222
- headline: get(mainUserObject, 'contextual_headline.text') || get(findFirstValueByKey(allJsonData, 'meta_verified_section'), 'headline'),
223
- followers: findSocialContextText(mainUserObject.profile_social_context, "followers"),
224
- following: findSocialContextText(mainUserObject.profile_social_context, "following"),
225
- coverPhoto: get(mainUserObject, 'cover_photo.photo.image.uri')
226
- };
227
- return result;
228
- } catch (err) {
229
- utils.error(`Failed to fetch profile for ${userID}: ${err.message}`, err);
230
- return createDefaultUser(userID);
231
- }
232
- };
48
+ var form = {};
49
+ id.map(function (v, i) {
50
+ form["ids[" + i + "]"] = v;
51
+ });
52
+ defaultFuncs
53
+ .post("https://www.facebook.com/chat/user_info/", ctx.jar, form)
54
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
55
+ .then(function (resData) {
56
+ if (resData.error) throw resData;
57
+ return callback(null, formatData(resData.payload.profiles));
58
+ })
59
+ .catch(function (err) {
60
+ log.error("getUserInfo", err);
61
+ return callback(err);
62
+ });
233
63
 
234
- Promise.all(ids.map(fetchProfile))
235
- .then(results => {
236
- return originalIdIsArray ? callback(null, results) : callback(null, results[0] || null);
237
- })
238
- .catch(err => {
239
- utils.error("getUserInfo (fetch)", err);
240
- callback(err);
241
- });
242
- }
243
64
  return returnPromise;
244
65
  };
245
66
  };
package/src/listenMqtt.js CHANGED
@@ -114,24 +114,27 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
114
114
 
115
115
  // Display connection success message with branding and loading animation
116
116
  const messages = [
117
- '\n🖤 SHADOWX FCA MQTT Connected',
117
+ '🖤 SHADOWX-FCA MQTT Connected',
118
118
  `🔰 Region: ${ctx.region || 'PNB'}`,
119
119
  `🔄 Auto-reconnect: ${ctx.globalOptions.autoReconnect ? 'Enabled' : 'Disabled'}${ctx.globalOptions.autoReconnect ? ' (reconnects every 3s on disconnect)' : ''}`,
120
- `⏱️ MQTT Restart Interval: ${ctx.globalOptions.restartListenMqtt?.enable ? `${ctx.globalOptions.restartListenMqtt.timeRestart / 1000}s` : 'Disabled'}`,
121
- ' ShadowX FCA STARTED\n'
120
+ `🧬 MQTT Restart Interval: ${(ctx.globalOptions.restartListenMqtt && ctx.globalOptions.restartListenMqtt.enable) ? `${ctx.globalOptions.restartListenMqtt.timeRestart / 1000}s` : 'Disabled'}`,
121
+ '🔖Author: Mueid Mursalin Rifat'
122
122
  ];
123
123
 
124
124
  let index = 0;
125
125
  const displayMessages = () => {
126
126
  if (index < messages.length) {
127
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
128
+ let frameIndex = 0;
127
129
 
128
- // loading condition - Using the extra safe method to prevent reconnect spam
129
- if (index === 0 && !ctx._loadedOnce) {
130
- process.stdout.write("⏳ Loading...\n");
131
- ctx._loadedOnce = true;
132
- }
130
+ const loadingInterval = setInterval(() => {
131
+ process.stdout.write(`\r${frames[frameIndex]} Loading...`);
132
+ frameIndex = (frameIndex + 1) % frames.length;
133
+ }, 80);
133
134
 
134
135
  setTimeout(() => {
136
+ clearInterval(loadingInterval);
137
+ process.stdout.write('\r' + ' '.repeat(20) + '\r');
135
138
  console.log(messages[index]);
136
139
  index++;
137
140
  displayMessages();
@@ -674,6 +677,110 @@ function markDelivery(ctx, api, threadID, messageID) {
674
677
 
675
678
  module.exports = function (defaultFuncs, api, ctx) {
676
679
  let globalCallback = identity;
680
+ // function getSeqID() {
681
+ // ctx.t_mqttCalled = false;
682
+ // async function attemptRequest(retries = 3) {
683
+ // try {
684
+ // if (!ctx.fb_dtsg) {
685
+ // const dtsg = await api.getFreshDtsg();
686
+ // if (!dtsg) {
687
+ // if (retries > 0) {
688
+ // logger.Warning("Failed to get fb_dtsg, retrying...");
689
+ // await utils.sleep(2000); // Longer delay for token retry
690
+ // return attemptRequest(retries - 1);
691
+ // }
692
+ // throw { error: "Could not obtain fb_dtsg after multiple attempts" };
693
+ // }
694
+ // ctx.fb_dtsg = dtsg;
695
+ // }
696
+
697
+ // const form = {
698
+ // av: ctx.userID,
699
+ // fb_dtsg: ctx.fb_dtsg,
700
+ // queries: JSON.stringify({
701
+ // o0: {
702
+ // doc_id: '3336396659757871',
703
+ // query_params: {
704
+ // limit: 1,
705
+ // before: null,
706
+ // tags: ['INBOX'],
707
+ // includeDeliveryReceipts: false,
708
+ // includeSeqID: true
709
+ // }
710
+ // }
711
+ // }),
712
+ // __user: ctx.userID,
713
+ // __a: '1',
714
+ // __req: '8',
715
+ // __hs: '19577.HYP:comet_pkg.2.1..2.1',
716
+ // dpr: '1',
717
+ // fb_api_caller_class: 'RelayModern',
718
+ // fb_api_req_friendly_name: 'MessengerGraphQLThreadlistFetcher'
719
+ // };
720
+
721
+ // const headers = {
722
+ // 'Content-Type': 'application/x-www-form-urlencoded',
723
+ // 'Referer': 'https://www.facebook.com/',
724
+ // 'Origin': 'https://www.facebook.com',
725
+ // 'sec-fetch-site': 'same-origin',
726
+ // 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
727
+ // 'Cookie': ctx.jar.getCookieString('https://www.facebook.com'),
728
+ // 'accept': '*/*',
729
+ // 'accept-encoding': 'gzip, deflate, br'
730
+ // };
731
+
732
+ // const resData = await defaultFuncs
733
+ // .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form, { headers })
734
+ // .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
735
+
736
+ // if (debugSeq) {
737
+ // console.log('GraphQL SeqID Response:', JSON.stringify(resData, null, 2));
738
+ // }
739
+
740
+ // if (resData.error === 1357004 || resData.error === 1357001) {
741
+ // if (retries > 0) {
742
+ // logger.Warning("Session error, refreshing token and retrying...");
743
+ // ctx.fb_dtsg = null; // Force new token
744
+ // await utils.sleep(2000);
745
+ // return attemptRequest(retries - 1);
746
+ // }
747
+ // throw { error: "Session refresh failed after retries" };
748
+ // }
749
+
750
+ // if (!Array.isArray(resData)) {
751
+ // throw { error: "Invalid response format", res: resData };
752
+ // }
753
+
754
+ // const seqID = resData[0]?.o0?.data?.viewer?.message_threads?.sync_sequence_id;
755
+ // if (!seqID) {
756
+ // throw { error: "Missing sync_sequence_id", res: resData };
757
+ // }
758
+
759
+ // ctx.lastSeqId = seqID;
760
+ // if (debugSeq) {
761
+ // console.log('Got SeqID:', ctx.lastSeqId);
762
+ // }
763
+
764
+ // return listenMqtt(defaultFuncs, api, ctx, globalCallback);
765
+
766
+ // } catch (err) {
767
+ // if (retries > 0) {
768
+ // console.log("Request failed, retrying...");
769
+
770
+ // return attemptRequest(retries - 1);
771
+ // }
772
+ // throw err;
773
+ // }
774
+ // }
775
+
776
+ // return attemptRequest()
777
+ // .catch((err) => {
778
+ // log.error("getSeqId", err);
779
+ // if (utils.getType(err) == "Object" && err.error === "Not logged in") ctx.loggedIn = false;
780
+ // return globalCallback(err);
781
+ // });
782
+ // }
783
+
677
784
  getSeqID = function getSeqID() {
678
785
  ctx.t_mqttCalled = false;
679
786
  defaultFuncs
@@ -760,4 +867,4 @@ module.exports = function (defaultFuncs, api, ctx) {
760
867
  api.stopListeningAsync = msgEmitter.stopListeningAsync;
761
868
  return msgEmitter;
762
869
  };
763
- };
870
+ };
package/src/nickname.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  const utils = require('../utils');
4
+ const log = require('npmlog');
4
5
 
5
6
  module.exports = function (defaultFuncs, api, ctx) {
6
7
  /**
@@ -10,123 +11,130 @@ module.exports = function (defaultFuncs, api, ctx) {
10
11
  *
11
12
  * @param {string} nickname The new nickname to set.
12
13
  * @param {string} threadID The ID of the thread.
13
- * @param {string} participantID The ID of the participant whose nickname will be changed. Defaults to the current user's ID if not provided or a function.
14
+ * @param {string} participantID The ID of the participant whose nickname will be changed. Defaults to the current user's ID if not provided.
14
15
  * @param {Function} [callback] Optional callback function to be invoked upon completion.
15
- * @param {string} [initiatorID] The ID of the user who initiated the nickname change (e.g., from event.senderID).
16
16
  * @returns {Promise<object>} A promise that resolves with a structured event object on success or rejects on error.
17
17
  */
18
- return function setNickname(nickname, threadID, participantID, callback, initiatorID) {
18
+ return function setNickname(nickname, threadID, participantID, callback) {
19
19
  let _callback;
20
- let _initiatorID;
21
-
22
20
  let _resolvePromise;
23
21
  let _rejectPromise;
22
+
24
23
  const returnPromise = new Promise((resolve, reject) => {
25
24
  _resolvePromise = resolve;
26
25
  _rejectPromise = reject;
27
26
  });
28
27
 
29
- if (utils.getType(callback) === "Function" || utils.getType(callback) === "AsyncFunction") {
30
- _callback = callback;
31
- _initiatorID = initiatorID;
32
- } else if (utils.getType(threadID) === "Function" || utils.getType(threadID) === "AsyncFunction") {
33
- _callback = threadID;
28
+ // Handle parameter shifting
29
+ if (typeof threadID === 'function') {
30
+ callback = threadID;
34
31
  threadID = null;
35
- _initiatorID = callback;
36
- } else if (utils.getType(participantID) === "Function" || utils.getType(participantID) === "AsyncFunction") {
37
- _callback = participantID;
38
- participantID = ctx.userID;
39
- _initiatorID = callback;
32
+ participantID = null;
33
+ } else if (typeof participantID === 'function') {
34
+ callback = participantID;
35
+ participantID = null;
40
36
  }
41
- else if (utils.getType(callback) === "string") {
42
- _initiatorID = callback;
43
- _callback = undefined;
44
- } else {
45
- _callback = undefined;
46
- _initiatorID = undefined;
47
- }
48
-
49
- if (!_callback) {
50
- _callback = function (__err, __data) {
51
- if (__err) _rejectPromise(__err);
52
- else _resolvePromise(__data);
53
- };
54
- } else {
55
- const originalCallback = _callback;
56
- _callback = function(__err, __data) {
57
- if (__err) {
58
- originalCallback(__err);
59
- _rejectPromise(__err);
60
- } else {
61
- originalCallback(null, __data);
62
- _resolvePromise(__data);
63
- }
64
- };
65
- }
66
-
67
- _initiatorID = _initiatorID || ctx.userID;
68
37
 
38
+ // Set defaults
69
39
  threadID = threadID || ctx.threadID;
70
40
  participantID = participantID || ctx.userID;
71
41
 
42
+ // Setup callback
43
+ if (typeof callback === 'function') {
44
+ _callback = (err, data) => {
45
+ if (err) {
46
+ callback(err);
47
+ _rejectPromise(err);
48
+ } else {
49
+ callback(null, data);
50
+ _resolvePromise(data);
51
+ }
52
+ };
53
+ } else {
54
+ _callback = (err, data) => {
55
+ if (err) _rejectPromise(err);
56
+ else _resolvePromise(data);
57
+ };
58
+ }
59
+
60
+ // Validation
72
61
  if (!threadID) {
73
- return _callback(new Error("threadID is required to set a nickname."));
62
+ const error = new Error("threadID is required to set a nickname.");
63
+ log.error("setNickname", error);
64
+ return _callback(error);
74
65
  }
75
- if (typeof nickname !== 'string') {
76
- return _callback(new Error("nickname must be a string."));
66
+
67
+ if (typeof nickname !== 'string' || !nickname.trim()) {
68
+ const error = new Error("nickname must be a non-empty string.");
69
+ log.error("setNickname", error);
70
+ return _callback(error);
77
71
  }
78
72
 
79
73
  if (!ctx.mqttClient) {
80
- return _callback(new Error("Not connected to MQTT"));
74
+ const error = new Error("Not connected to MQTT. Make sure listenMqtt is called first.");
75
+ log.error("setNickname", error);
76
+ return _callback(error);
81
77
  }
82
78
 
83
- ctx.wsReqNumber += 1;
84
- ctx.wsTaskNumber += 1;
79
+ // Check if appID exists
80
+ if (!ctx.appID) {
81
+ log.warn("setNickname", "ctx.appID not found, using default");
82
+ ctx.appID = "2220391788200892";
83
+ }
84
+
85
+ ctx.wsReqNumber = (ctx.wsReqNumber || 0) + 1;
86
+ ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1;
85
87
 
86
88
  const queryPayload = {
87
- thread_key: threadID.toString(),
88
- contact_id: participantID.toString(),
89
- nickname: nickname,
90
- sync_group: 1,
89
+ thread_key: String(threadID),
90
+ contact_id: String(participantID),
91
+ nickname: nickname,
92
+ sync_group: 1
91
93
  };
92
94
 
93
95
  const query = {
94
- failure_count: null,
95
- label: '44',
96
- payload: JSON.stringify(queryPayload),
97
- queue_name: 'thread_participant_nickname',
98
- task_id: ctx.wsTaskNumber,
96
+ failure_count: null,
97
+ label: '44',
98
+ payload: JSON.stringify(queryPayload),
99
+ queue_name: String(threadID),
100
+ task_id: ctx.wsTaskNumber
99
101
  };
100
102
 
101
103
  const context = {
102
- app_id: ctx.appID,
103
- payload: {
104
- epoch_id: parseInt(utils.generateOfflineThreadingID()),
105
- tasks: [query],
106
- version_id: '24631415369801570',
107
- },
108
- request_id: ctx.wsReqNumber,
109
- type: 3,
104
+ app_id: ctx.appID,
105
+ payload: {
106
+ epoch_id: parseInt(utils.generateOfflineThreadingID()) || Date.now(),
107
+ tasks: [query],
108
+ version_id: '24631415369801570'
109
+ },
110
+ request_id: ctx.wsReqNumber,
111
+ type: 3
110
112
  };
111
- context.payload = JSON.stringify(context.payload);
113
+
114
+ const payloadString = JSON.stringify(context.payload);
115
+ context.payload = payloadString;
116
+
117
+ log.info("setNickname", `Setting nickname "${nickname}" for ${participantID} in thread ${threadID}`);
112
118
 
113
119
  ctx.mqttClient.publish('/ls_req', JSON.stringify(context), { qos: 1, retain: false }, (err) => {
114
- if (err) {
115
- return _callback(new Error(`MQTT publish failed for setNickname: ${err.message || err}`));
116
- }
117
-
118
- const nicknameChangeEvent = {
119
- type: "thread_nickname_update",
120
- threadID: threadID,
121
- participantID: participantID,
122
- newNickname: nickname,
123
- senderID: _initiatorID,
124
- BotID: ctx.userID,
125
- timestamp: Date.now(),
126
- };
127
- _callback(null, nicknameChangeEvent);
120
+ if (err) {
121
+ log.error("setNickname", `MQTT publish failed: ${err.message || err}`);
122
+ return _callback(new Error(`MQTT publish failed: ${err.message || err}`));
123
+ }
124
+
125
+ const nicknameChangeEvent = {
126
+ type: "thread_nickname_update",
127
+ threadID: String(threadID),
128
+ participantID: String(participantID),
129
+ newNickname: nickname,
130
+ author: ctx.userID,
131
+ timestamp: Date.now()
132
+ };
133
+
134
+ log.info("setNickname", `Nickname set successfully`);
135
+ _callback(null, nicknameChangeEvent);
128
136
  });
129
137
 
130
138
  return returnPromise;
131
139
  };
132
- };
140
+ };