shadowx-fca 2.4.0 → 2.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadowx-fca",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "description": "Unofficial Facebook Chat API for Node.js with Auto-Update System - modify by Mueid Mursalin Rifat",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -50,6 +50,6 @@
50
50
  "ws": "^8.18.1"
51
51
  },
52
52
  "engines": {
53
- "node": ">=14.0.0"
53
+ "node": ">=16.0.0"
54
54
  }
55
55
  }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ // shadowx-fca — GetBotInfo
3
+ // Author: Modified for shadowx-fca
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.log("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]) {
24
+ return req[2];
25
+ }
26
+ if (Array.isArray(req) && req[3] && req[3][0] && req[3][0].__bbox && req[3][0].__bbox.define) {
27
+ for (const def of req[3][0].__bbox.define) {
28
+ if (Array.isArray(def) && def[0] && def[0].endsWith(key) && def[2]) {
29
+ return def[2];
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
35
+ return null;
36
+ };
37
+
38
+ const currentUserData = findConfig("CurrentUserInitialData");
39
+ const dtsgInitialData = findConfig("DTSGInitialData");
40
+ const dtsgInitData = findConfig("DTSGInitData");
41
+ const lsdData = findConfig("LSD");
42
+
43
+ if (!currentUserData || !dtsgInitialData) {
44
+ utils.log("GetBotInfo", "Could not find required data (CurrentUserInitialData or DTSGInitialData).");
45
+ return null;
46
+ }
47
+
48
+ return {
49
+ name: currentUserData.NAME || null,
50
+ firstName: currentUserData.SHORT_NAME || null,
51
+ uid: currentUserData.USER_ID || null,
52
+ appID: currentUserData.APP_ID || null,
53
+ dtsgToken: dtsgInitialData.token || null,
54
+ lsdToken: lsdData?.token || null,
55
+ dtsgInit: dtsgInitData ? {
56
+ token: dtsgInitData.token,
57
+ async_get_token: dtsgInitData.async_get_token
58
+ } : undefined,
59
+ getCtx: (key) => ctx ? ctx[key] : null,
60
+ getOptions: (key) => ctx && ctx.globalOptions ? ctx.globalOptions[key] : null,
61
+ getRegion: () => ctx?.region || null,
62
+ getUserID: () => ctx?.userID || currentUserData?.USER_ID || null,
63
+ getJar: () => ctx?.jar || null
64
+ };
65
+ };
66
+ };
package/src/OldMessage.js CHANGED
@@ -19,9 +19,10 @@ module.exports = function (defaultFuncs, api, ctx) {
19
19
  function uploadAttachment(attachments, callback) {
20
20
  var uploads = [];
21
21
 
22
- // create an array of promises
23
22
  for (var i = 0; i < attachments.length; i++) {
24
- if (!utils.isReadableStream(attachments[i])) throw { error: "Attachment should be a readable stream and not " + utils.getType(attachments[i]) + "." };
23
+ if (!utils.isReadableStream(attachments[i])) {
24
+ throw { error: "Attachment should be a readable stream and not " + utils.getType(attachments[i]) + "." };
25
+ }
25
26
  var form = {
26
27
  upload_1024: attachments[i],
27
28
  voice_clip: "true"
@@ -33,14 +34,11 @@ module.exports = function (defaultFuncs, api, ctx) {
33
34
  .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
34
35
  .then(function (resData) {
35
36
  if (resData.error) throw resData;
36
- // We have to return the data unformatted unless we want to change it
37
- // back in sendMessage.
38
37
  return resData.payload.metadata[0];
39
38
  })
40
39
  );
41
40
  }
42
41
 
43
- // resolve all promises
44
42
  bluebird
45
43
  .all(uploads)
46
44
  .then(resData => callback(null, resData))
@@ -72,26 +70,25 @@ module.exports = function (defaultFuncs, api, ctx) {
72
70
  }
73
71
 
74
72
  function sendContent(form, threadID, isSingleUser, messageAndOTID, callback) {
75
- // There are three cases here:
76
- // 1. threadID is of type array, where we're starting a new group chat with users
77
- // specified in the array.
78
- // 2. User is sending a message to a specific user.
79
- // 3. No additional form params and the message goes to an existing group chat.
80
73
  if (utils.getType(threadID) === "Array") {
81
- for (var i = 0; i < threadID.length; i++) form["specific_to_list[" + i + "]"] = "fbid:" + threadID[i];
74
+ for (var i = 0; i < threadID.length; i++) {
75
+ form["specific_to_list[" + i + "]"] = "fbid:" + threadID[i];
76
+ }
82
77
  form["specific_to_list[" + threadID.length + "]"] = "fbid:" + ctx.userID;
83
78
  form["client_thread_id"] = "root:" + messageAndOTID;
84
79
  log.info("sendMessage", "Sending message to multiple users: " + threadID);
85
- }
86
- else {
87
- // This means that threadID is the id of a user, and the chat
88
- // is a single person chat
89
- if (isSingleUser) {
80
+ } else {
81
+ // Auto-detect if it's a DM
82
+ const threadIDStr = threadID.toString();
83
+ const isDM = isSingleUser === true || threadIDStr.length === 15 || !threadIDStr.match(/^\d{16,}$/);
84
+
85
+ if (isDM) {
90
86
  form["specific_to_list[0]"] = "fbid:" + threadID;
91
87
  form["specific_to_list[1]"] = "fbid:" + ctx.userID;
92
88
  form["other_user_fbid"] = threadID;
89
+ } else {
90
+ form["thread_fbid"] = threadID;
93
91
  }
94
- else form["thread_fbid"] = threadID;
95
92
  }
96
93
 
97
94
  if (ctx.globalOptions.pageID) {
@@ -113,21 +110,18 @@ module.exports = function (defaultFuncs, api, ctx) {
113
110
  if (resData.error) {
114
111
  if (resData.error === 1545012) {
115
112
  log.warn("sendMessage", "Got error 1545012. This might mean that you're not part of the conversation " + threadID);
116
- }
117
- else {
113
+ } else {
118
114
  log.error("sendMessage", resData);
119
115
  }
120
116
  return callback(resData);
121
117
  }
122
118
 
123
119
  var messageInfo = resData.payload.actions.reduce(function (p, v) {
124
- return (
125
- {
126
- threadID: v.thread_fbid,
127
- messageID: v.message_id,
128
- timestamp: v.timestamp
129
- } || p
130
- );
120
+ return {
121
+ threadID: v.thread_fbid,
122
+ messageID: v.message_id,
123
+ timestamp: v.timestamp
124
+ } || p;
131
125
  }, null);
132
126
 
133
127
  return callback(null, messageInfo);
@@ -139,17 +133,6 @@ module.exports = function (defaultFuncs, api, ctx) {
139
133
  });
140
134
  }
141
135
 
142
- function send(form, threadID, messageAndOTID, callback, isGroup) {
143
- // We're doing a query to this to check if the given id is the id of
144
- // a user or of a group chat. The form will be different depending
145
- // on that.
146
- if (utils.getType(threadID) === "Array") sendContent(form, threadID, false, messageAndOTID, callback);
147
- else {
148
- if (utils.getType(isGroup) != "Boolean") sendContent(form, threadID, threadID.length === 15, messageAndOTID, callback);
149
- else sendContent(form, threadID, !isGroup, messageAndOTID, callback);
150
- }
151
- }
152
-
153
136
  function handleUrl(msg, form, callback, cb) {
154
137
  if (msg.url) {
155
138
  form["shareable_attachment[share_type]"] = "100";
@@ -158,13 +141,16 @@ module.exports = function (defaultFuncs, api, ctx) {
158
141
  form["shareable_attachment[share_params]"] = params;
159
142
  cb();
160
143
  });
144
+ } else {
145
+ cb();
161
146
  }
162
- else cb();
163
147
  }
164
148
 
165
149
  function handleLocation(msg, form, callback, cb) {
166
150
  if (msg.location) {
167
- if (msg.location.latitude == null || msg.location.longitude == null) return callback({ error: "location property needs both latitude and longitude" });
151
+ if (msg.location.latitude == null || msg.location.longitude == null) {
152
+ return callback({ error: "location property needs both latitude and longitude" });
153
+ }
168
154
  form["location_attachment[coordinates][latitude]"] = msg.location.latitude;
169
155
  form["location_attachment[coordinates][longitude]"] = msg.location.longitude;
170
156
  form["location_attachment[is_current_location]"] = !!msg.location.current;
@@ -178,11 +164,17 @@ module.exports = function (defaultFuncs, api, ctx) {
178
164
  }
179
165
 
180
166
  function handleEmoji(msg, form, callback, cb) {
181
- if (msg.emojiSize != null && msg.emoji == null) return callback({ error: "emoji property is empty" });
167
+ if (msg.emojiSize != null && msg.emoji == null) {
168
+ return callback({ error: "emoji property is empty" });
169
+ }
182
170
  if (msg.emoji) {
183
171
  if (msg.emojiSize == null) msg.emojiSize = "medium";
184
- if (msg.emojiSize != "small" && msg.emojiSize != "medium" && msg.emojiSize != "large") return callback({ error: "emojiSize property is invalid" });
185
- if (form["body"] != null && form["body"] != "") return callback({ error: "body is not empty" });
172
+ if (msg.emojiSize != "small" && msg.emojiSize != "medium" && msg.emojiSize != "large") {
173
+ return callback({ error: "emojiSize property is invalid" });
174
+ }
175
+ if (form["body"] != null && form["body"] != "") {
176
+ return callback({ error: "body is not empty" });
177
+ }
186
178
  form["body"] = msg.emoji;
187
179
  form["tags[0]"] = "hot_emoji_size:" + msg.emojiSize;
188
180
  }
@@ -197,23 +189,22 @@ module.exports = function (defaultFuncs, api, ctx) {
197
189
  form["video_ids"] = [];
198
190
  form["audio_ids"] = [];
199
191
 
200
- if (utils.getType(msg.attachment) !== "Array") msg.attachment = [msg.attachment];
201
- if (msg.attachment.every(e=>/_id$/.test(e[0]))) {
202
- //console.log(msg.attachment)
203
- msg.attachment.map(e=>form[`${e[0]}s`].push(e[1]));
204
- return cb();
205
- }
192
+ if (utils.getType(msg.attachment) !== "Array") {
193
+ msg.attachment = [msg.attachment];
194
+ }
195
+
206
196
  uploadAttachment(msg.attachment, function (err, files) {
207
197
  if (err) return callback(err);
208
198
  files.forEach(function (file) {
209
199
  var key = Object.keys(file);
210
- var type = key[0]; // image_id, file_id, etc
211
- form["" + type + "s"].push(file[type]); // push the id
200
+ var type = key[0];
201
+ form[type + "s"].push(file[type]);
212
202
  });
213
203
  cb();
214
204
  });
205
+ } else {
206
+ cb();
215
207
  }
216
- else cb();
217
208
  }
218
209
 
219
210
  function handleMention(msg, form, callback, cb) {
@@ -221,10 +212,16 @@ module.exports = function (defaultFuncs, api, ctx) {
221
212
  for (let i = 0; i < msg.mentions.length; i++) {
222
213
  const mention = msg.mentions[i];
223
214
  const tag = mention.tag;
224
- if (typeof tag !== "string") return callback({ error: "Mention tags must be strings." });
215
+ if (typeof tag !== "string") {
216
+ return callback({ error: "Mention tags must be strings." });
217
+ }
225
218
  const offset = msg.body.indexOf(tag, mention.fromIndex || 0);
226
- if (offset < 0) log.warn("handleMention", 'Mention for "' + tag + '" not found in message string.');
227
- if (mention.id == null) log.warn("handleMention", "Mention id should be non-null.");
219
+ if (offset < 0) {
220
+ log.warn("handleMention", 'Mention for "' + tag + '" not found in message string.');
221
+ }
222
+ if (mention.id == null) {
223
+ log.warn("handleMention", "Mention id should be non-null.");
224
+ }
228
225
 
229
226
  const id = mention.id || 0;
230
227
  const emptyChar = '\u200E';
@@ -239,44 +236,67 @@ module.exports = function (defaultFuncs, api, ctx) {
239
236
  }
240
237
 
241
238
  return function sendMessage(msg, threadID, callback, replyToMessage, isGroup) {
242
- typeof isGroup == "undefined" ? isGroup = null : "";
243
- if (!callback && (utils.getType(threadID) === "Function" || utils.getType(threadID) === "AsyncFunction")) return threadID({ error: "Pass a threadID as a second argument." });
244
- if (!replyToMessage && utils.getType(callback) === "String") {
239
+ // Handle parameter shifting
240
+ if (typeof callback === "string") {
241
+ isGroup = replyToMessage;
245
242
  replyToMessage = callback;
246
- callback = function () { };
243
+ callback = function () {};
244
+ } else if (typeof threadID === "function") {
245
+ callback = threadID;
246
+ threadID = null;
247
+ }
248
+
249
+ if (!callback || typeof callback !== "function") {
250
+ callback = function () {};
247
251
  }
248
252
 
249
- var resolveFunc = function () { };
250
- var rejectFunc = function () { };
253
+ var resolveFunc = function () {};
254
+ var rejectFunc = function () {};
251
255
  var returnPromise = new Promise(function (resolve, reject) {
252
256
  resolveFunc = resolve;
253
257
  rejectFunc = reject;
254
258
  });
255
259
 
256
- if (!callback) {
257
- callback = function (err, data) {
258
- if (err) return rejectFunc(err);
260
+ var originalCallback = callback;
261
+ callback = function (err, data) {
262
+ if (err) {
263
+ originalCallback(err);
264
+ rejectFunc(err);
265
+ } else {
266
+ originalCallback(null, data);
259
267
  resolveFunc(data);
260
- };
261
- }
268
+ }
269
+ };
262
270
 
263
271
  var msgType = utils.getType(msg);
264
272
  var threadIDType = utils.getType(threadID);
265
- var messageIDType = utils.getType(replyToMessage);
266
273
 
267
- if (msgType !== "String" && msgType !== "Object") return callback({ error: "Message should be of type string or object and not " + msgType + "." });
274
+ if (msgType !== "String" && msgType !== "Object") {
275
+ return callback({ error: "Message should be of type string or object and not " + msgType + "." });
276
+ }
268
277
 
269
- // Changing this to accomodate an array of users
270
- if (threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") return callback({ error: "ThreadID should be of type number, string, or array and not " + threadIDType + "." });
278
+ if (threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") {
279
+ return callback({ error: "ThreadID should be of type number, string, or array and not " + threadIDType + "." });
280
+ }
271
281
 
272
- if (replyToMessage && messageIDType !== 'String') return callback({ error: "MessageID should be of type string and not " + threadIDType + "." });
282
+ if (msgType === "String") {
283
+ msg = { body: msg };
284
+ }
285
+
286
+ // Determine isSingleUser
287
+ var isSingleUser = !isGroup;
288
+ if (isGroup === null || typeof isGroup === "undefined") {
289
+ const threadIDStr = threadID.toString();
290
+ isSingleUser = threadIDStr.length === 15 || !threadIDStr.match(/^\d{16,}$/);
291
+ }
273
292
 
274
- if (msgType === "String") msg = { body: msg };
275
293
  var disallowedProperties = Object.keys(msg).filter(prop => !allowedProperties[prop]);
276
- if (disallowedProperties.length > 0) return callback({ error: "Dissallowed props: `" + disallowedProperties.join(", ") + "`" });
294
+ if (disallowedProperties.length > 0) {
295
+ return callback({ error: "Dissallowed props: `" + disallowedProperties.join(", ") + "`" });
296
+ }
277
297
 
278
298
  var messageAndOTID = utils.generateOfflineThreadingID();
279
- // console.log(messageAndOTID)
299
+
280
300
  var form = {
281
301
  client: "mercury",
282
302
  action_type: "ma-type:user-generated-message",
@@ -307,9 +327,8 @@ module.exports = function (defaultFuncs, api, ctx) {
307
327
  manual_retry_cnt: "0",
308
328
  has_attachment: !!(msg.attachment || msg.url || msg.sticker),
309
329
  signatureID: utils.getSignatureID(),
310
- replied_to_message_id: replyToMessage
330
+ replied_to_message_id: replyToMessage || undefined
311
331
  };
312
- // console.log(form)
313
332
 
314
333
  handleLocation(msg, form, callback, () =>
315
334
  handleSticker(msg, form, callback, () =>
@@ -317,13 +336,14 @@ module.exports = function (defaultFuncs, api, ctx) {
317
336
  handleUrl(msg, form, callback, () =>
318
337
  handleEmoji(msg, form, callback, () =>
319
338
  handleMention(msg, form, callback, () =>
320
- send(form, threadID, messageAndOTID, callback, isGroup)
339
+ sendContent(form, threadID, isSingleUser, messageAndOTID, callback)
321
340
  )
322
341
  )
323
342
  )
324
343
  )
325
344
  )
326
345
  );
346
+
327
347
  return returnPromise;
328
348
  };
329
349
  };
package/src/comment.js ADDED
@@ -0,0 +1,226 @@
1
+ 'use strict';
2
+
3
+ const utils = require('../utils');
4
+ const log = require('npmlog'); // Add npmlog for proper logging
5
+
6
+ /**
7
+ * Handles the upload of attachments (images/videos) for a comment.
8
+ * @param {object} defaultFuncs - The default functions for making API requests.
9
+ * @param {object} ctx - The context object.
10
+ * @param {object} msg - The message object, containing attachments.
11
+ * @param {object} form - The main form object to be populated.
12
+ * @returns {Promise<void>}
13
+ */
14
+ async function handleUpload(defaultFuncs, ctx, msg, form) {
15
+ if (!msg.attachments || msg.attachments.length === 0) {
16
+ return;
17
+ }
18
+
19
+ const uploads = msg.attachments.map(item => {
20
+ if (!utils.isReadableStream(item)) {
21
+ throw new Error('Attachments must be a readable stream.');
22
+ }
23
+ return defaultFuncs
24
+ .postFormData('https://www.facebook.com/ajax/ufi/upload/', ctx.jar, {
25
+ profile_id: ctx.userID,
26
+ source: 19,
27
+ target_id: ctx.userID,
28
+ file: item
29
+ })
30
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
31
+ .then(res => {
32
+ if (res.error || !res.payload?.fbid) {
33
+ throw res;
34
+ }
35
+ return { media: { id: res.payload.fbid } };
36
+ });
37
+ });
38
+
39
+ const results = await Promise.all(uploads);
40
+ form.input.attachments.push(...results);
41
+ }
42
+
43
+ /**
44
+ * Handles a URL attachment for a comment.
45
+ * @param {object} msg - The message object.
46
+ * @param {object} form - The main form object.
47
+ */
48
+ function handleURL(msg, form) {
49
+ if (typeof msg.url === 'string' && msg.url.trim()) {
50
+ form.input.attachments.push({
51
+ link: {
52
+ external: {
53
+ url: msg.url
54
+ }
55
+ }
56
+ });
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Handles mentions in the comment body.
62
+ * @param {object} msg - The message object.
63
+ * @param {object} form - The main form object.
64
+ */
65
+ function handleMentions(msg, form) {
66
+ if (!msg.mentions || !Array.isArray(msg.mentions)) return;
67
+
68
+ for (const item of msg.mentions) {
69
+ const { tag, id, fromIndex } = item;
70
+ if (typeof tag !== 'string' || !id) {
71
+ log.warn('createCommentPost', 'Mentions must have a string "tag" and an "id".');
72
+ continue;
73
+ }
74
+ const offset = msg.body.indexOf(tag, fromIndex || 0);
75
+ if (offset < 0) {
76
+ log.warn('createCommentPost', `Mention for "${tag}" not found in message string.`);
77
+ continue;
78
+ }
79
+ form.input.message.ranges.push({
80
+ entity: { id: String(id) },
81
+ length: tag.length,
82
+ offset
83
+ });
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Handles a sticker attachment for a comment.
89
+ * @param {object} msg - The message object.
90
+ * @param {object} form - The main form object.
91
+ */
92
+ function handleSticker(msg, form) {
93
+ if (msg.sticker) {
94
+ form.input.attachments.push({
95
+ media: {
96
+ id: String(msg.sticker)
97
+ }
98
+ });
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Submits the final comment form to the GraphQL endpoint.
104
+ * @param {object} defaultFuncs - The default functions.
105
+ * @param {object} ctx - The context object.
106
+ * @param {object} form - The fully constructed form object.
107
+ * @returns {Promise<object>} A promise that resolves with the comment info.
108
+ */
109
+ async function createContent(defaultFuncs, ctx, form) {
110
+ const res = await defaultFuncs
111
+ .post('https://www.facebook.com/api/graphql/', ctx.jar, {
112
+ fb_api_caller_class: 'RelayModern',
113
+ fb_api_req_friendly_name: 'useCometUFICreateCommentMutation',
114
+ variables: JSON.stringify(form),
115
+ server_timestamps: true,
116
+ doc_id: 6993516810709754
117
+ })
118
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
119
+
120
+ // Hard error from Facebook — comment was NOT sent
121
+ if (res.errors && res.errors.length > 0) {
122
+ throw new Error(res.errors[0].message || JSON.stringify(res.errors[0]));
123
+ }
124
+
125
+ // Try to extract comment info from response
126
+ try {
127
+ const commentEdge = res?.data?.comment_create?.feedback_comment_edge;
128
+ const id = commentEdge?.node?.id || null;
129
+ const url = commentEdge?.node?.feedback?.url || null;
130
+ const count = res?.data?.comment_create?.feedback?.total_comment_count || 0;
131
+ return { id, url, count };
132
+ } catch (_) {
133
+ // Comment went through but response structure was unexpected — still a success
134
+ return { id: null, url: null, count: 0 };
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Creates a comment on a Facebook post. Can also reply to an existing comment.
140
+ * @param {string|object} msg - The message to post. Can be a string or an object with `body`, `attachments`, `mentions`, etc.
141
+ * @param {string} postID - The ID of the post to comment on.
142
+ * @param {string} [replyCommentID] - (Optional) The ID of the comment to reply to.
143
+ * @param {function} [callback] - (Optional) A callback function.
144
+ * @returns {Promise<object>} A promise that resolves with the new comment's information.
145
+ */
146
+ module.exports = function(defaultFuncs, api, ctx) {
147
+ return async function createCommentPost(msg, postID, replyCommentID, callback) {
148
+ let cb;
149
+ const returnPromise = new Promise((resolve, reject) => {
150
+ cb = (error, info) => {
151
+ if (error) reject(error);
152
+ else resolve(info);
153
+ };
154
+ });
155
+
156
+ if (typeof replyCommentID === 'function') {
157
+ callback = replyCommentID;
158
+ replyCommentID = null;
159
+ }
160
+ if (typeof callback === 'function') {
161
+ const originalCb = cb;
162
+ cb = (error, info) => {
163
+ if (error) {
164
+ callback(error);
165
+ originalCb(error);
166
+ } else {
167
+ callback(null, info);
168
+ originalCb(null, info);
169
+ }
170
+ };
171
+ }
172
+
173
+ if (typeof msg !== 'string' && typeof msg !== 'object') {
174
+ const error = 'Message must be a string or an object.';
175
+ log.error('createCommentPost', error);
176
+ return cb(error);
177
+ }
178
+ if (typeof postID !== 'string') {
179
+ const error = 'postID must be a string.';
180
+ log.error('createCommentPost', error);
181
+ return cb(error);
182
+ }
183
+
184
+ let messageObject = typeof msg === 'string' ? { body: msg } : { ...msg };
185
+ messageObject.mentions = messageObject.mentions || [];
186
+ messageObject.attachments = messageObject.attachments || [];
187
+
188
+ const form = {
189
+ feedLocation: 'NEWSFEED',
190
+ feedbackSource: 1,
191
+ groupID: null,
192
+ input: {
193
+ client_mutation_id: Math.round(Math.random() * 19).toString(),
194
+ actor_id: ctx.userID,
195
+ attachments: [],
196
+ feedback_id: Buffer.from('feedback:' + postID).toString('base64'),
197
+ message: {
198
+ ranges: [],
199
+ text: messageObject.body || ''
200
+ },
201
+ reply_comment_parent_fbid: replyCommentID || null,
202
+ is_tracking_encrypted: true,
203
+ tracking: [],
204
+ feedback_source: 'NEWS_FEED',
205
+ idempotence_token: 'client:' + utils.getGUID(),
206
+ session_id: utils.getGUID()
207
+ },
208
+ scale: 1,
209
+ useDefaultActor: false
210
+ };
211
+
212
+ try {
213
+ await handleUpload(defaultFuncs, ctx, messageObject, form);
214
+ handleURL(messageObject, form);
215
+ handleMentions(messageObject, form);
216
+ handleSticker(messageObject, form);
217
+ const info = await createContent(defaultFuncs, ctx, form);
218
+ cb(null, info);
219
+ return info;
220
+ } catch (err) {
221
+ log.error('createCommentPost', err);
222
+ cb(err);
223
+ throw err;
224
+ }
225
+ };
226
+ };