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.
- package/checkUpdate.js +1 -1
- package/index.js +664 -418
- package/package.json +1 -1
- package/src/GetBotInfo.js +57 -0
- package/src/OldMessage.js +38 -10
- package/src/comment.js +213 -0
- package/src/emoji.js +124 -0
- package/src/friend.js +243 -0
- package/src/gcmember.js +122 -0
- package/src/getUserInfo.js +222 -43
- package/src/listenMqtt.js +9 -116
- package/src/nickname.js +132 -0
- package/src/sendMessage.js +224 -235
- package/src/sendTypingIndicator.js +45 -101
- package/src/share.js +62 -0
- package/src/shareContact.js +17 -61
- package/src/stickers.js +117 -0
- package/src/story.js +181 -0
- package/src/theme.js +233 -0
- package/utils.js +24 -1311
package/package.json
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// sahilchat-fca — GetBotInfo
|
|
3
|
+
// Author: S4hiilAns4ri (github.com/S4hiilAns4ri)
|
|
4
|
+
|
|
5
|
+
const utils = require('../utils');
|
|
6
|
+
|
|
7
|
+
module.exports = (defaultFuncs, api, ctx) => {
|
|
8
|
+
/**
|
|
9
|
+
* Returns bot's current login info, tokens, and context accessor functions.
|
|
10
|
+
* @param {Array<object>} netData - JSON blobs extracted from Facebook HTML
|
|
11
|
+
* @returns {object|null}
|
|
12
|
+
*/
|
|
13
|
+
return function GetBotInfo(netData) {
|
|
14
|
+
if (!netData || !Array.isArray(netData)) {
|
|
15
|
+
utils.error("GetBotInfo", "netData is not a valid array.");
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const findConfig = (key) => {
|
|
20
|
+
for (const scriptData of netData) {
|
|
21
|
+
if (!scriptData.require) continue;
|
|
22
|
+
for (const req of scriptData.require) {
|
|
23
|
+
if (Array.isArray(req) && req[0] === key && req[2]) return req[2];
|
|
24
|
+
if (Array.isArray(req) && req[3]?.[0]?.__bbox?.define) {
|
|
25
|
+
for (const def of req[3][0].__bbox.define) {
|
|
26
|
+
if (Array.isArray(def) && def[0].endsWith(key) && def[2]) return def[2];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const currentUserData = findConfig("CurrentUserInitialData");
|
|
35
|
+
const dtsgInitialData = findConfig("DTSGInitialData");
|
|
36
|
+
const dtsgInitData = findConfig("DTSGInitData");
|
|
37
|
+
const lsdData = findConfig("LSD");
|
|
38
|
+
|
|
39
|
+
if (!currentUserData || !dtsgInitialData) {
|
|
40
|
+
utils.error("GetBotInfo", "Could not find required data (CurrentUserInitialData or DTSGInitialData).");
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
name : currentUserData.NAME,
|
|
46
|
+
firstName : currentUserData.SHORT_NAME,
|
|
47
|
+
uid : currentUserData.USER_ID,
|
|
48
|
+
appID : currentUserData.APP_ID,
|
|
49
|
+
dtsgToken : dtsgInitialData.token,
|
|
50
|
+
lsdToken : lsdData?.token,
|
|
51
|
+
dtsgInit : dtsgInitData ? { token: dtsgInitData.token, async_get_token: dtsgInitData.async_get_token } : undefined,
|
|
52
|
+
getCtx : (key) => ctx[key],
|
|
53
|
+
getOptions : (key) => ctx.globalOptions[key],
|
|
54
|
+
getRegion : () => ctx?.region,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
};
|
package/src/OldMessage.js
CHANGED
|
@@ -311,19 +311,47 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
311
311
|
};
|
|
312
312
|
// console.log(form)
|
|
313
313
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
314
|
+
const configSource = (global.GoatBot && global.GoatBot.config) ? global.GoatBot.config : ctx.config || {};
|
|
315
|
+
const enableTypingIndicator = typeof configSource.enableTypingIndicator !== 'undefined' ? configSource.enableTypingIndicator : ctx.config?.enableTypingIndicator;
|
|
316
|
+
const typingDuration = Number(configSource.typingDuration || ctx.config?.typingDuration || 4000);
|
|
317
|
+
|
|
318
|
+
if (enableTypingIndicator) {
|
|
319
|
+
api.sendTypingIndicator(true, threadID, () => {});
|
|
320
|
+
const originalCallback = callback;
|
|
321
|
+
callback = (err, data) => {
|
|
322
|
+
api.sendTypingIndicator(false, threadID, () => {});
|
|
323
|
+
originalCallback(err, data);
|
|
324
|
+
};
|
|
325
|
+
setTimeout(() => {
|
|
326
|
+
handleLocation(msg, form, callback, () =>
|
|
327
|
+
handleSticker(msg, form, callback, () =>
|
|
328
|
+
handleAttachment(msg, form, callback, () =>
|
|
329
|
+
handleUrl(msg, form, callback, () =>
|
|
330
|
+
handleEmoji(msg, form, callback, () =>
|
|
331
|
+
handleMention(msg, form, callback, () =>
|
|
332
|
+
sendContent(form, threadID, isSingleUser, messageAndOTID, callback)
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
)
|
|
338
|
+
);
|
|
339
|
+
}, typingDuration);
|
|
340
|
+
} else {
|
|
341
|
+
handleLocation(msg, form, callback, () =>
|
|
342
|
+
handleSticker(msg, form, callback, () =>
|
|
343
|
+
handleAttachment(msg, form, callback, () =>
|
|
344
|
+
handleUrl(msg, form, callback, () =>
|
|
345
|
+
handleEmoji(msg, form, callback, () =>
|
|
346
|
+
handleMention(msg, form, callback, () =>
|
|
347
|
+
sendContent(form, threadID, isSingleUser, messageAndOTID, callback)
|
|
348
|
+
)
|
|
349
|
+
)
|
|
321
350
|
)
|
|
322
351
|
)
|
|
323
352
|
)
|
|
324
|
-
)
|
|
325
|
-
|
|
326
|
-
);
|
|
353
|
+
);
|
|
354
|
+
}
|
|
327
355
|
return returnPromise;
|
|
328
356
|
};
|
|
329
357
|
};
|
package/src/comment.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const utils = require('../utils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Handles the upload of attachments (images/videos) for a comment.
|
|
7
|
+
* @param {object} defaultFuncs - The default functions for making API requests.
|
|
8
|
+
* @param {object} ctx - The context object.
|
|
9
|
+
* @param {object} msg - The message object, containing attachments.
|
|
10
|
+
* @param {object} form - The main form object to be populated.
|
|
11
|
+
* @returns {Promise<void>}
|
|
12
|
+
*/
|
|
13
|
+
async function handleUpload(defaultFuncs, ctx, msg, form) {
|
|
14
|
+
if (!msg.attachments || msg.attachments.length === 0) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const uploads = msg.attachments.map(item => {
|
|
19
|
+
if (!utils.isReadableStream(item)) {
|
|
20
|
+
throw new Error('Attachments must be a readable stream.');
|
|
21
|
+
}
|
|
22
|
+
return defaultFuncs
|
|
23
|
+
.postFormData('https://www.facebook.com/ajax/ufi/upload/', ctx.jar, {
|
|
24
|
+
profile_id: ctx.userID,
|
|
25
|
+
source: 19,
|
|
26
|
+
target_id: ctx.userID,
|
|
27
|
+
file: item
|
|
28
|
+
})
|
|
29
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
|
|
30
|
+
.then(res => {
|
|
31
|
+
if (res.error || !res.payload?.fbid) {
|
|
32
|
+
throw res;
|
|
33
|
+
}
|
|
34
|
+
return { media: { id: res.payload.fbid } };
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const results = await Promise.all(uploads);
|
|
39
|
+
form.input.attachments.push(...results);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Handles a URL attachment for a comment.
|
|
44
|
+
* @param {object} msg - The message object.
|
|
45
|
+
* @param {object} form - The main form object.
|
|
46
|
+
*/
|
|
47
|
+
function handleURL(msg, form) {
|
|
48
|
+
if (typeof msg.url === 'string') {
|
|
49
|
+
form.input.attachments.push({
|
|
50
|
+
link: {
|
|
51
|
+
external: {
|
|
52
|
+
url: msg.url
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Handles mentions in the comment body.
|
|
61
|
+
* @param {object} msg - The message object.
|
|
62
|
+
* @param {object} form - The main form object.
|
|
63
|
+
*/
|
|
64
|
+
function handleMentions(msg, form) {
|
|
65
|
+
if (!msg.mentions) return;
|
|
66
|
+
|
|
67
|
+
for (const item of msg.mentions) {
|
|
68
|
+
const { tag, id, fromIndex } = item;
|
|
69
|
+
if (typeof tag !== 'string' || !id) {
|
|
70
|
+
utils.warn('createCommentPost', 'Mentions must have a string "tag" and an "id".');
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const offset = msg.body.indexOf(tag, fromIndex || 0);
|
|
74
|
+
if (offset < 0) {
|
|
75
|
+
utils.warn('createCommentPost', `Mention for "${tag}" not found in message string.`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
form.input.message.ranges.push({
|
|
79
|
+
entity: { id },
|
|
80
|
+
length: tag.length,
|
|
81
|
+
offset
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Handles a sticker attachment for a comment.
|
|
88
|
+
* @param {object} msg - The message object.
|
|
89
|
+
* @param {object} form - The main form object.
|
|
90
|
+
*/
|
|
91
|
+
function handleSticker(msg, form) {
|
|
92
|
+
if (msg.sticker) {
|
|
93
|
+
form.input.attachments.push({
|
|
94
|
+
media: {
|
|
95
|
+
id: String(msg.sticker)
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Submits the final comment form to the GraphQL endpoint.
|
|
103
|
+
* @param {object} defaultFuncs - The default functions.
|
|
104
|
+
* @param {object} ctx - The context object.
|
|
105
|
+
* @param {object} form - The fully constructed form object.
|
|
106
|
+
* @returns {Promise<object>} A promise that resolves with the comment info.
|
|
107
|
+
*/
|
|
108
|
+
async function createContent(defaultFuncs, ctx, form) {
|
|
109
|
+
const res = await defaultFuncs
|
|
110
|
+
.post('https://www.facebook.com/api/graphql/', ctx.jar, {
|
|
111
|
+
fb_api_caller_class: 'RelayModern',
|
|
112
|
+
fb_api_req_friendly_name: 'useCometUFICreateCommentMutation',
|
|
113
|
+
variables: JSON.stringify(form),
|
|
114
|
+
server_timestamps: true,
|
|
115
|
+
doc_id: 6993516810709754
|
|
116
|
+
})
|
|
117
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
118
|
+
|
|
119
|
+
// Hard error from Facebook — comment was NOT sent
|
|
120
|
+
if (res.errors && res.errors.length > 0) {
|
|
121
|
+
throw new Error(res.errors[0].message || JSON.stringify(res.errors[0]));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Try to extract comment info from response
|
|
125
|
+
try {
|
|
126
|
+
const commentEdge = res?.data?.comment_create?.feedback_comment_edge;
|
|
127
|
+
const id = commentEdge?.node?.id || null;
|
|
128
|
+
const url = commentEdge?.node?.feedback?.url || null;
|
|
129
|
+
const count = res?.data?.comment_create?.feedback?.total_comment_count || 0;
|
|
130
|
+
return { id, url, count };
|
|
131
|
+
} catch (_) {
|
|
132
|
+
// Comment went through but response structure was unexpected — still a success
|
|
133
|
+
return { id: null, url: null, count: 0 };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Creates a comment on a Facebook post. Can also reply to an existing comment.
|
|
139
|
+
* @param {string|object} msg - The message to post. Can be a string or an object with `body`, `attachments`, `mentions`, etc.
|
|
140
|
+
* @param {string} postID - The ID of the post to comment on.
|
|
141
|
+
* @param {string} [replyCommentID] - (Optional) The ID of the comment to reply to.
|
|
142
|
+
* @param {function} [callback] - (Optional) A callback function.
|
|
143
|
+
* @returns {Promise<object>} A promise that resolves with the new comment's information.
|
|
144
|
+
*/
|
|
145
|
+
module.exports = function(defaultFuncs, api, ctx) {
|
|
146
|
+
return async function createCommentPost(msg, postID, replyCommentID, callback) {
|
|
147
|
+
let cb;
|
|
148
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
149
|
+
cb = (error, info) => info ? resolve(info) : reject(error);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (typeof replyCommentID === 'function') {
|
|
153
|
+
callback = replyCommentID;
|
|
154
|
+
replyCommentID = null;
|
|
155
|
+
}
|
|
156
|
+
if (typeof callback === 'function') {
|
|
157
|
+
cb = callback;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (typeof msg !== 'string' && typeof msg !== 'object') {
|
|
161
|
+
const error = 'Message must be a string or an object.';
|
|
162
|
+
utils.error('createCommentPost', error);
|
|
163
|
+
return cb(error);
|
|
164
|
+
}
|
|
165
|
+
if (typeof postID !== 'string') {
|
|
166
|
+
const error = 'postID must be a string.';
|
|
167
|
+
utils.error('createCommentPost', error);
|
|
168
|
+
return cb(error);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let messageObject = typeof msg === 'string' ? { body: msg } : { ...msg };
|
|
172
|
+
messageObject.mentions = messageObject.mentions || [];
|
|
173
|
+
messageObject.attachments = messageObject.attachments || [];
|
|
174
|
+
|
|
175
|
+
const form = {
|
|
176
|
+
feedLocation: 'NEWSFEED',
|
|
177
|
+
feedbackSource: 1,
|
|
178
|
+
groupID: null,
|
|
179
|
+
input: {
|
|
180
|
+
client_mutation_id: Math.round(Math.random() * 19).toString(),
|
|
181
|
+
actor_id: ctx.userID,
|
|
182
|
+
attachments: [],
|
|
183
|
+
feedback_id: Buffer.from('feedback:' + postID).toString('base64'),
|
|
184
|
+
message: {
|
|
185
|
+
ranges: [],
|
|
186
|
+
text: messageObject.body || ''
|
|
187
|
+
},
|
|
188
|
+
reply_comment_parent_fbid: replyCommentID || null,
|
|
189
|
+
is_tracking_encrypted: true,
|
|
190
|
+
tracking: [],
|
|
191
|
+
feedback_source: 'NEWS_FEED',
|
|
192
|
+
idempotence_token: 'client:' + utils.getGUID(),
|
|
193
|
+
session_id: utils.getGUID()
|
|
194
|
+
},
|
|
195
|
+
scale: 1,
|
|
196
|
+
useDefaultActor: false
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
await handleUpload(defaultFuncs, ctx, messageObject, form);
|
|
201
|
+
handleURL(messageObject, form);
|
|
202
|
+
handleMentions(messageObject, form);
|
|
203
|
+
handleSticker(messageObject, form);
|
|
204
|
+
const info = await createContent(defaultFuncs, ctx, form);
|
|
205
|
+
cb(null, info);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
utils.error('createCommentPost', err);
|
|
208
|
+
cb(err);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return returnPromise;
|
|
212
|
+
};
|
|
213
|
+
};
|
package/src/emoji.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../utils');
|
|
4
|
+
|
|
5
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
6
|
+
/**
|
|
7
|
+
* Author: S4hiilAns4ri (github.com/S4hiilAns4ri)
|
|
8
|
+
* Mqtt
|
|
9
|
+
* Sets the custom emoji for a specific Facebook thread via MQTT.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} emoji The emoji character to set as the custom emoji (e.g., "👍", "❤️").
|
|
12
|
+
* @param {string} threadID The ID of the thread where the emoji will be set.
|
|
13
|
+
* @param {Function} [callback] Optional callback function to be invoked upon completion.
|
|
14
|
+
* @param {string} [initiatorID] The ID of the user who initiated the emoji change (e.g., from event.senderID).
|
|
15
|
+
* @returns {Promise<object>} A promise that resolves with a structured event object on success or rejects on error.
|
|
16
|
+
*/
|
|
17
|
+
return function emoji(emoji, threadID, callback, initiatorID) {
|
|
18
|
+
let _callback;
|
|
19
|
+
let _initiatorID;
|
|
20
|
+
|
|
21
|
+
let _resolvePromise;
|
|
22
|
+
let _rejectPromise;
|
|
23
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
24
|
+
_resolvePromise = resolve;
|
|
25
|
+
_rejectPromise = reject;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (utils.getType(callback) === "Function" || utils.getType(callback) === "AsyncFunction") {
|
|
29
|
+
_callback = callback;
|
|
30
|
+
_initiatorID = initiatorID;
|
|
31
|
+
} else if (utils.getType(threadID) === "Function" || utils.getType(threadID) === "AsyncFunction") {
|
|
32
|
+
_callback = threadID;
|
|
33
|
+
threadID = null;
|
|
34
|
+
_initiatorID = callback;
|
|
35
|
+
} else if (utils.getType(callback) === "string") {
|
|
36
|
+
_initiatorID = callback;
|
|
37
|
+
_callback = undefined;
|
|
38
|
+
} else {
|
|
39
|
+
_callback = undefined;
|
|
40
|
+
_initiatorID = undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!_callback) {
|
|
44
|
+
_callback = function (__err, __data) {
|
|
45
|
+
if (__err) _rejectPromise(__err);
|
|
46
|
+
else _resolvePromise(__data);
|
|
47
|
+
};
|
|
48
|
+
} else {
|
|
49
|
+
const originalCallback = _callback;
|
|
50
|
+
_callback = function(__err, __data) {
|
|
51
|
+
if (__err) {
|
|
52
|
+
originalCallback(__err);
|
|
53
|
+
_rejectPromise(__err);
|
|
54
|
+
} else {
|
|
55
|
+
originalCallback(null, __data);
|
|
56
|
+
_resolvePromise(__data);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_initiatorID = _initiatorID || ctx.userID;
|
|
62
|
+
|
|
63
|
+
threadID = threadID || ctx.threadID;
|
|
64
|
+
|
|
65
|
+
if (!threadID) {
|
|
66
|
+
return _callback(new Error("threadID is required to set an emoji."));
|
|
67
|
+
}
|
|
68
|
+
if (!emoji) {
|
|
69
|
+
return _callback(new Error("An emoji character is required."));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!ctx.mqttClient) {
|
|
73
|
+
return _callback(new Error("Not connected to MQTT"));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
ctx.wsReqNumber += 1;
|
|
77
|
+
ctx.wsTaskNumber += 1;
|
|
78
|
+
|
|
79
|
+
const queryPayload = {
|
|
80
|
+
thread_key: threadID.toString(),
|
|
81
|
+
custom_emoji: emoji,
|
|
82
|
+
avatar_sticker_instruction_key_id: null,
|
|
83
|
+
sync_group: 1,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const query = {
|
|
87
|
+
failure_count: null,
|
|
88
|
+
label: '100003',
|
|
89
|
+
payload: JSON.stringify(queryPayload),
|
|
90
|
+
queue_name: 'thread_quick_reaction',
|
|
91
|
+
task_id: ctx.wsTaskNumber,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const context = {
|
|
95
|
+
app_id: ctx.appID,
|
|
96
|
+
payload: {
|
|
97
|
+
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
98
|
+
tasks: [query],
|
|
99
|
+
version_id: '24631415369801570',
|
|
100
|
+
},
|
|
101
|
+
request_id: ctx.wsReqNumber,
|
|
102
|
+
type: 3,
|
|
103
|
+
};
|
|
104
|
+
context.payload = JSON.stringify(context.payload);
|
|
105
|
+
|
|
106
|
+
ctx.mqttClient.publish('/ls_req', JSON.stringify(context), { qos: 1, retain: false }, (err) => {
|
|
107
|
+
if (err) {
|
|
108
|
+
return _callback(new Error(`MQTT publish failed for emoji: ${err.message || err}`));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const emojiChangeEvent = {
|
|
112
|
+
type: "thread_emoji_update",
|
|
113
|
+
threadID: threadID,
|
|
114
|
+
newEmoji: emoji,
|
|
115
|
+
senderID: _initiatorID,
|
|
116
|
+
BotID: ctx.userID,
|
|
117
|
+
timestamp: Date.now(),
|
|
118
|
+
};
|
|
119
|
+
_callback(null, emojiChangeEvent);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return returnPromise;
|
|
123
|
+
};
|
|
124
|
+
};
|