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.
- package/index.js +197 -347
- package/package.json +2 -2
- package/src/GetBotInfo.js +29 -20
- package/src/OldMessage.js +106 -114
- package/src/comment.js +28 -15
- package/src/getUserInfo.js +43 -222
- package/src/listenMqtt.js +116 -9
- package/src/nickname.js +89 -81
- package/src/sendMessage.js +292 -117
- package/src/sendMessage2.js +243 -0
- package/src/sendTypingIndicator.js +101 -45
- package/src/shareContact.js +78 -30
- package/utils.js +1305 -7
package/src/getUserInfo.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
function
|
|
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,
|
|
40
|
+
callback = function (err, userInfo) {
|
|
143
41
|
if (err) return rejectFunc(err);
|
|
144
|
-
resolveFunc(
|
|
42
|
+
resolveFunc(userInfo);
|
|
145
43
|
};
|
|
146
44
|
}
|
|
147
45
|
|
|
148
|
-
|
|
149
|
-
const ids = originalIdIsArray ? id : [id];
|
|
46
|
+
if (utils.getType(id) !== "Array") id = [id];
|
|
150
47
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
ids
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
'
|
|
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
|
-
|
|
121
|
-
'
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
36
|
-
} else if (
|
|
37
|
-
|
|
38
|
-
participantID =
|
|
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
|
-
|
|
62
|
+
const error = new Error("threadID is required to set a nickname.");
|
|
63
|
+
log.error("setNickname", error);
|
|
64
|
+
return _callback(error);
|
|
74
65
|
}
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
ctx.
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
+
};
|