fca-pretest 2.0.0 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/sendMessage.js +471 -328
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fca-pretest",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Facebook-chat-api made by Priyansh rajput",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -49,7 +49,7 @@
49
49
  "websocket-stream": "latest"
50
50
  },
51
51
  "engines": {
52
- "node": ">=14.x <20.x"
52
+ "node": ">=14.x <21.x"
53
53
  },
54
54
  "devDependencies": {
55
55
  "eslint": "latest",
@@ -1,334 +1,477 @@
1
1
  "use strict";
2
2
 
3
- /**
4
- * Được Fix Hay Làm Màu Bởi: @HarryWakazaki
5
- * 21/4/2022
6
- */
7
-
8
- var utils = require("../utils");
9
- var log = require("npmlog");
10
- var bluebird = require("bluebird");
11
-
12
- var allowedProperties = {
13
- attachment: true,
14
- url: true,
15
- sticker: true,
16
- emoji: true,
17
- emojiSize: true,
18
- body: true,
19
- mentions: true,
20
- location: true,
3
+ const utils = require("../utils");
4
+ const log = require("npmlog");
5
+
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
21
15
  };
22
16
 
17
+ function removeSpecialChar(inputString) { // remove char banned by facebook
18
+ if (typeof inputString !== "string")
19
+ return inputString;
20
+ // Convert string to Buffer
21
+ const buffer = Buffer.from(inputString, 'utf8');
22
+
23
+ // Filter buffer start with ef b8 8f
24
+ let filteredBuffer = Buffer.alloc(0);
25
+ for (let i = 0; i < buffer.length; i++) {
26
+ if (buffer[i] === 0xEF && buffer[i + 1] === 0xB8 && buffer[i + 2] === 0x8F) {
27
+ i += 2; // Skip 3 bytes of buffer starting with ef b8 8f
28
+ } else {
29
+ filteredBuffer = Buffer.concat([filteredBuffer, buffer.slice(i, i + 1)]);
30
+ }
31
+ }
32
+
33
+ // Convert filtered buffer to string
34
+ const convertedString = filteredBuffer.toString('utf8');
35
+
36
+ return convertedString;
37
+ }
38
+
23
39
  module.exports = function (defaultFuncs, api, ctx) {
24
- function uploadAttachment(attachments, callback) {
25
- var uploads = [];
26
-
27
- // create an array of promises
28
- for (var i = 0; i < attachments.length; i++) {
29
- if (!utils.isReadableStream(attachments[i])) throw { error: "Attachment should be a readable stream and not " + utils.getType(attachments[i]) + "." };
30
- var form = {
31
- upload_1024: attachments[i],
32
- voice_clip: "true"
33
- };
34
-
35
- uploads.push(
36
- defaultFuncs
37
- .postFormData("https://upload.facebook.com/ajax/mercury/upload.php", ctx.jar, form, {})
38
- .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
39
- .then(function (resData) {
40
- if (resData.error) throw resData;
41
- // We have to return the data unformatted unless we want to change it
42
- // back in sendMessage.
43
- return resData.payload.metadata[0];
44
- })
45
- );
46
- }
47
-
48
- // resolve all promises
49
- bluebird
50
- .all(uploads)
51
- .then(resData => callback(null, resData)
52
- )
53
- .catch(function (err) {
54
- log.error("uploadAttachment", err);
55
- return callback(err);
56
- });
57
- }
58
-
59
- function getUrl(url, callback) {
60
- var form = {
61
- image_height: 960,
62
- image_width: 960,
63
- uri: url
64
- };
65
-
66
- defaultFuncs
67
- .post("https://www.facebook.com/message_share_attachment/fromURI/", ctx.jar, form)
68
- .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
69
- .then(function (resData) {
70
- if (resData.error) return callback(resData);
71
- if (!resData.payload) return callback({ error: "Invalid url" });
72
- callback(null, resData.payload.share_data.share_params);
73
- })
74
- .catch(function (err) {
75
- log.error("getUrl", err);
76
- return callback(err);
77
- });
78
- }
79
-
80
- function sendContent(form, threadID, isSingleUser, messageAndOTID, callback) {
81
- // There are three cases here:
82
- // 1. threadID is of type array, where we're starting a new group chat with users
83
- // specified in the array.
84
- // 2. User is sending a message to a specific user.
85
- // 3. No additional form params and the message goes to an existing group chat.
86
- if (utils.getType(threadID) === "Array") {
87
- for (var i = 0; i < threadID.length; i++) form["specific_to_list[" + i + "]"] = "fbid:" + threadID[i];
88
- form["specific_to_list[" + threadID.length + "]"] = "fbid:" + ctx.userID;
89
- form["client_thread_id"] = "root:" + messageAndOTID;
90
- log.info("sendMessage", "Sending message to multiple users: " + threadID);
91
- }
92
- else {
93
- // This means that threadID is the id of a user, and the chat
94
- // is a single person chat
95
- if (isSingleUser) {
96
- form["specific_to_list[0]"] = "fbid:" + threadID;
97
- form["specific_to_list[1]"] = "fbid:" + ctx.userID;
98
- form["other_user_fbid"] = threadID;
99
- }
100
- else form["thread_fbid"] = threadID;
101
- }
102
-
103
- if (ctx.globalOptions.pageID) {
104
- form["author"] = "fbid:" + ctx.globalOptions.pageID;
105
- form["specific_to_list[1]"] = "fbid:" + ctx.globalOptions.pageID;
106
- form["creator_info[creatorID]"] = ctx.userID;
107
- form["creator_info[creatorType]"] = "direct_admin";
108
- form["creator_info[labelType]"] = "sent_message";
109
- form["creator_info[pageID]"] = ctx.globalOptions.pageID;
110
- form["request_user_id"] = ctx.globalOptions.pageID;
111
- form["creator_info[profileURI]"] = "https://www.facebook.com/profile.php?id=" + ctx.userID;
112
- }
113
-
114
- defaultFuncs
115
- .post("https://www.facebook.com/messaging/send/", ctx.jar, form)
116
- .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
117
- .then(function (resData) {
118
- if (!resData) return callback({ error: "Send message failed." });
119
- if (resData.error) {
120
- if (resData.error === 1545012) log.warn("sendMessage", "Got error 1545012. This might mean that you're not part of the conversation " + threadID);
121
- return callback(resData);
122
- }
123
-
124
- var messageInfo = resData.payload.actions.reduce(function (p, v) {
125
- return (
126
- {
127
- threadID: v.thread_fbid,
128
- messageID: v.message_id,
129
- timestamp: v.timestamp
130
- } || p
131
- );
132
- }, null);
133
- return callback(null, messageInfo);
134
- })
135
- .catch(function (err) {
136
- log.error("sendMessage", err);
137
- if (utils.getType(err) == "Object" && err.error === "Not logged in.") ctx.loggedIn = false;
138
- return callback(err,null);
139
- });
140
- }
141
-
142
- function send(form, threadID, messageAndOTID, callback, isGroup) {
143
- //Full Fix sendMessage
144
- if (utils.getType(threadID) === "Array") sendContent(form, threadID, false, messageAndOTID, callback);
145
- else {
146
- var THREADFIX = "ThreadID".replace("ThreadID",threadID); // i cũng đôn nâu
147
- if (THREADFIX.length <= 15 || global.Fca.isUser.includes(threadID)) sendContent(form, threadID, !isGroup, messageAndOTID, callback);
148
- else if (THREADFIX.length >= 15 && THREADFIX.indexOf(1) != 0 || global.Fca.isThread.includes(threadID)) sendContent(form, threadID, threadID.length === 15, messageAndOTID, callback);
149
- else {
150
- if (global.Fca.Data.event.isGroup) {
151
- sendContent(form, threadID, threadID.length === 15, messageAndOTID, callback);
152
- global.Fca.isThread.push(threadID);
153
- }
154
- else {
155
- sendContent(form, threadID, !isGroup, messageAndOTID, callback);
156
- global.Fca.isUser.push(threadID)
157
- }
158
- }
159
- }
160
- }
161
-
162
- function handleUrl(msg, form, callback, cb) {
163
- if (msg.url) {
164
- form["shareable_attachment[share_type]"] = "100";
165
- getUrl(msg.url, function (err, params) {
166
- if (err) return callback(err);
167
- form["shareable_attachment[share_params]"] = params;
168
- cb();
169
- });
170
- }
171
- else cb();
172
- }
173
-
174
- function handleLocation(msg, form, callback, cb) {
175
- if (msg.location) {
176
- if (msg.location.latitude == null || msg.location.longitude == null) return callback({ error: "location property needs both latitude and longitude" });
177
- form["location_attachment[coordinates][latitude]"] = msg.location.latitude;
178
- form["location_attachment[coordinates][longitude]"] = msg.location.longitude;
179
- form["location_attachment[is_current_location]"] = !!msg.location.current;
180
- }
181
- cb();
182
- }
183
-
184
- function handleSticker(msg, form, callback, cb) {
185
- if (msg.sticker) form["sticker_id"] = msg.sticker;
186
- cb();
187
- }
188
-
189
- function handleEmoji(msg, form, callback, cb) {
190
- if (msg.emojiSize != null && msg.emoji == null) return callback({ error: "emoji property is empty" });
191
- if (msg.emoji) {
192
- if (msg.emojiSize == null) msg.emojiSize = "medium";
193
- if (msg.emojiSize != "small" && msg.emojiSize != "medium" && msg.emojiSize != "large") return callback({ error: "emojiSize property is invalid" });
194
- if (form["body"] != null && form["body"] != "") return callback({ error: "body is not empty" });
195
- form["body"] = msg.emoji;
196
- form["tags[0]"] = "hot_emoji_size:" + msg.emojiSize;
197
- }
198
- cb();
199
- }
200
-
201
- function handleAttachment(msg, form, callback, cb) {
202
- if (msg.attachment) {
203
- form["image_ids"] = [];
204
- form["gif_ids"] = [];
205
- form["file_ids"] = [];
206
- form["video_ids"] = [];
207
- form["audio_ids"] = [];
208
-
209
- if (utils.getType(msg.attachment) !== "Array") msg.attachment = [msg.attachment];
210
-
211
- uploadAttachment(msg.attachment, function (err, files) {
212
- if (err) return callback(err);
213
- files.forEach(function (file) {
214
- var key = Object.keys(file);
215
- var type = key[0]; // image_id, file_id, etc
216
- form["" + type + "s"].push(file[type]); // push the id
217
- });
218
- cb();
219
- });
220
- }
221
- else cb();
222
- }
223
-
224
- function handleMention(msg, form, callback, cb) {
225
- if (msg.mentions) {
226
- for (let i = 0; i < msg.mentions.length; i++) {
227
- const mention = msg.mentions[i];
228
- const tag = mention.tag;
229
- if (typeof tag !== "string") return callback({ error: "Mention tags must be strings." });
230
- const offset = msg.body.indexOf(tag, mention.fromIndex || 0);
231
- if (offset < 0) log.warn("handleMention", 'Mention for "' + tag + '" not found in message string.');
232
- if (mention.id == null) log.warn("handleMention", "Mention id should be non-null.");
233
-
234
- const id = mention.id || 0;
235
- const emptyChar = '\u200E';
236
- form["body"] = emptyChar + msg.body;
237
- form["profile_xmd[" + i + "][offset]"] = offset + 1;
238
- form["profile_xmd[" + i + "][length]"] = tag.length;
239
- form["profile_xmd[" + i + "][id]"] = id;
240
- form["profile_xmd[" + i + "][type]"] = "p";
241
- }
242
- }
243
- cb();
244
- }
245
-
246
- return function sendMessage(msg, threadID, callback, replyToMessage, isGroup) {
247
- typeof isGroup == "undefined" ? isGroup = null : "";
248
- if (!callback && (utils.getType(threadID) === "Function" || utils.getType(threadID) === "AsyncFunction")) return threadID({ error: "Pass a threadID as a second argument." });
249
- if (!replyToMessage && utils.getType(callback) === "String") {
250
- replyToMessage = callback;
251
- callback = function () { };
252
- }
253
-
254
- var resolveFunc = function () { };
255
- var rejectFunc = function () { };
256
- var returnPromise = new Promise(function (resolve, reject) {
257
- resolveFunc = resolve;
258
- rejectFunc = reject;
259
- });
260
-
261
- if (!callback) {
262
- callback = function (err, data) {
263
- if (err) return rejectFunc(err);
264
- resolveFunc(data);
265
- };
266
- }
267
-
268
- var msgType = utils.getType(msg);
269
- var threadIDType = utils.getType(threadID);
270
- var messageIDType = utils.getType(replyToMessage);
271
-
272
- if (msgType !== "String" && msgType !== "Object") return callback({ error: "Message should be of type string or object and not " + msgType + "." });
273
-
274
- // Changing this to accomodate an array of users
275
- if (threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") return callback({ error: "ThreadID should be of type number, string, or array and not " + threadIDType + "." });
276
-
277
- if (replyToMessage && messageIDType !== 'String') return callback({ error: "MessageID should be of type string and not " + threadIDType + "." });
278
-
279
- if (msgType === "String") msg = { body: msg };
280
- var disallowedProperties = Object.keys(msg).filter(prop => !allowedProperties[prop]);
281
- if (disallowedProperties.length > 0) return callback({ error: "Dissallowed props: `" + disallowedProperties.join(", ") + "`" });
282
-
283
- var messageAndOTID = utils.generateOfflineThreadingID();
284
-
285
- var form = {
286
- client: "mercury",
287
- action_type: "ma-type:user-generated-message",
288
- author: "fbid:" + ctx.userID,
289
- timestamp: Date.now(),
290
- timestamp_absolute: "Today",
291
- timestamp_relative: utils.generateTimestampRelative(),
292
- timestamp_time_passed: "0",
293
- is_unread: false,
294
- is_cleared: false,
295
- is_forward: false,
296
- is_filtered_content: false,
297
- is_filtered_content_bh: false,
298
- is_filtered_content_account: false,
299
- is_filtered_content_quasar: false,
300
- is_filtered_content_invalid_app: false,
301
- is_spoof_warning: false,
302
- source: "source:chat:web",
303
- "source_tags[0]": "source:chat",
304
- body: msg.body ? msg.body.toString().replace("\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f\ufe0f",' ') : "",
305
- html_body: false,
306
- ui_push_phase: "V3",
307
- status: "0",
308
- offline_threading_id: messageAndOTID,
309
- message_id: messageAndOTID,
310
- threading_id: utils.generateThreadingID(ctx.clientID),
311
- "ephemeral_ttl_mode:": "0",
312
- manual_retry_cnt: "0",
313
- has_attachment: !!(msg.attachment || msg.url || msg.sticker),
314
- signatureID: utils.getSignatureID(),
315
- replied_to_message_id: replyToMessage
316
- };
317
-
318
- handleLocation(msg, form, callback, () =>
319
- handleSticker(msg, form, callback, () =>
320
- handleAttachment(msg, form, callback, () =>
321
- handleUrl(msg, form, callback, () =>
322
- handleEmoji(msg, form, callback, () =>
323
- handleMention(msg, form, callback, () =>
324
- send(form, threadID, messageAndOTID, callback, isGroup)
325
- )
326
- )
327
- )
328
- )
329
- )
330
- );
331
-
332
- return returnPromise;
333
- };
40
+ function uploadAttachment(attachments, callback) {
41
+ const uploads = [];
42
+
43
+ // create an array of promises
44
+ for (let i = 0; i < attachments.length; i++) {
45
+ if (!utils.isReadableStream(attachments[i])) {
46
+ throw {
47
+ error:
48
+ "Attachment should be a readable stream and not " +
49
+ utils.getType(attachments[i]) +
50
+ "."
51
+ };
52
+ }
53
+
54
+ const form = {
55
+ upload_1024: attachments[i],
56
+ voice_clip: "true"
57
+ };
58
+
59
+ uploads.push(
60
+ defaultFuncs
61
+ .postFormData(
62
+ "https://upload.facebook.com/ajax/mercury/upload.php",
63
+ ctx.jar,
64
+ form,
65
+ {}
66
+ )
67
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
68
+ .then(function (resData) {
69
+ if (resData.error) {
70
+ throw resData;
71
+ }
72
+
73
+ // We have to return the data unformatted unless we want to change it
74
+ // back in sendMessage.
75
+ return resData.payload.metadata[0];
76
+ })
77
+ );
78
+ }
79
+
80
+ // resolve all promises
81
+ Promise
82
+ .all(uploads)
83
+ .then(function (resData) {
84
+ callback(null, resData);
85
+ })
86
+ .catch(function (err) {
87
+ log.error("uploadAttachment", err);
88
+ return callback(err);
89
+ });
90
+ }
91
+
92
+ function getUrl(url, callback) {
93
+ const form = {
94
+ image_height: 960,
95
+ image_width: 960,
96
+ uri: url
97
+ };
98
+
99
+ defaultFuncs
100
+ .post(
101
+ "https://www.facebook.com/message_share_attachment/fromURI/",
102
+ ctx.jar,
103
+ form
104
+ )
105
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
106
+ .then(function (resData) {
107
+ if (resData.error) {
108
+ return callback(resData);
109
+ }
110
+
111
+ if (!resData.payload) {
112
+ return callback({ error: "Invalid url" });
113
+ }
114
+
115
+ callback(null, resData.payload.share_data.share_params);
116
+ })
117
+ .catch(function (err) {
118
+ log.error("getUrl", err);
119
+ return callback(err);
120
+ });
121
+ }
122
+
123
+ function sendContent(form, threadID, isSingleUser, messageAndOTID, callback) {
124
+ // There are three cases here:
125
+ // 1. threadID is of type array, where we're starting a new group chat with users
126
+ // specified in the array.
127
+ // 2. User is sending a message to a specific user.
128
+ // 3. No additional form params and the message goes to an existing group chat.
129
+ if (utils.getType(threadID) === "Array") {
130
+ for (let i = 0; i < threadID.length; i++) {
131
+ form["specific_to_list[" + i + "]"] = "fbid:" + threadID[i];
132
+ }
133
+ form["specific_to_list[" + threadID.length + "]"] = "fbid:" + (ctx.i_userID || ctx.userID);
134
+ form["client_thread_id"] = "root:" + messageAndOTID;
135
+ log.info("sendMessage", "Sending message to multiple users: " + threadID);
136
+ } else {
137
+ // This means that threadID is the id of a user, and the chat
138
+ // is a single person chat
139
+ if (isSingleUser) {
140
+ form["specific_to_list[0]"] = "fbid:" + threadID;
141
+ form["specific_to_list[1]"] = "fbid:" + (ctx.i_userID || ctx.userID);
142
+ form["other_user_fbid"] = threadID;
143
+ } else {
144
+ form["thread_fbid"] = threadID;
145
+ }
146
+ }
147
+
148
+ if (ctx.globalOptions.pageID) {
149
+ form["author"] = "fbid:" + ctx.globalOptions.pageID;
150
+ form["specific_to_list[1]"] = "fbid:" + ctx.globalOptions.pageID;
151
+ form["creator_info[creatorID]"] = ctx.i_userID || ctx.userID;
152
+ form["creator_info[creatorType]"] = "direct_admin";
153
+ form["creator_info[labelType]"] = "sent_message";
154
+ form["creator_info[pageID]"] = ctx.globalOptions.pageID;
155
+ form["request_user_id"] = ctx.globalOptions.pageID;
156
+ form["creator_info[profileURI]"] =
157
+ "https://www.facebook.com/profile.php?id=" + (ctx.i_userID || ctx.userID);
158
+ }
159
+
160
+ defaultFuncs
161
+ .post("https://www.facebook.com/messaging/send/", ctx.jar, form)
162
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
163
+ .then(function (resData) {
164
+ if (!resData) {
165
+ return callback({ error: "Send message failed." });
166
+ }
167
+
168
+ if (resData.error) {
169
+ if (resData.error === 1545012) {
170
+ log.warn(
171
+ "sendMessage",
172
+ "Got error 1545012. This might mean that you're not part of the conversation " +
173
+ threadID
174
+ );
175
+ }
176
+ else {
177
+ log.error("sendMessage", resData);
178
+ }
179
+ return callback(resData);
180
+ }
181
+
182
+ const messageInfo = resData.payload.actions.reduce(function (p, v) {
183
+ return (
184
+ {
185
+ threadID: v.thread_fbid,
186
+ messageID: v.message_id,
187
+ timestamp: v.timestamp
188
+ } || p
189
+ );
190
+ }, null);
191
+
192
+ return callback(null, messageInfo);
193
+ })
194
+ .catch(function (err) {
195
+ log.error("sendMessage", err);
196
+ if (utils.getType(err) == "Object" && err.error === "Not logged in.") {
197
+ ctx.loggedIn = false;
198
+ }
199
+ return callback(err);
200
+ });
201
+ }
202
+
203
+ function send(form, threadID, messageAndOTID, callback, isGroup) {
204
+ // We're doing a query to this to check if the given id is the id of
205
+ // a user or of a group chat. The form will be different depending
206
+ // on that.
207
+ if (utils.getType(threadID) === "Array") {
208
+ sendContent(form, threadID, false, messageAndOTID, callback);
209
+ } else {
210
+ if (utils.getType(isGroup) != "Boolean") {
211
+ // Removed the use of api.getUserInfo() in the old version to reduce account lockout
212
+ sendContent(form, threadID, threadID.toString().length < 16, messageAndOTID, callback);
213
+ } else {
214
+ sendContent(form, threadID, !isGroup, messageAndOTID, callback);
215
+ }
216
+ }
217
+ }
218
+
219
+ function handleUrl(msg, form, callback, cb) {
220
+ if (msg.url) {
221
+ form["shareable_attachment[share_type]"] = "100";
222
+ getUrl(msg.url, function (err, params) {
223
+ if (err) {
224
+ return callback(err);
225
+ }
226
+
227
+ form["shareable_attachment[share_params]"] = params;
228
+ cb();
229
+ });
230
+ } else {
231
+ cb();
232
+ }
233
+ }
234
+
235
+ function handleLocation(msg, form, callback, cb) {
236
+ if (msg.location) {
237
+ if (msg.location.latitude == null || msg.location.longitude == null) {
238
+ return callback({ error: "location property needs both latitude and longitude" });
239
+ }
240
+
241
+ form["location_attachment[coordinates][latitude]"] = msg.location.latitude;
242
+ form["location_attachment[coordinates][longitude]"] = msg.location.longitude;
243
+ form["location_attachment[is_current_location]"] = !!msg.location.current;
244
+ }
245
+
246
+ cb();
247
+ }
248
+
249
+ function handleSticker(msg, form, callback, cb) {
250
+ if (msg.sticker) {
251
+ form["sticker_id"] = msg.sticker;
252
+ }
253
+ cb();
254
+ }
255
+
256
+ function handleEmoji(msg, form, callback, cb) {
257
+ if (msg.emojiSize != null && msg.emoji == null) {
258
+ return callback({ error: "emoji property is empty" });
259
+ }
260
+ if (msg.emoji) {
261
+ if (msg.emojiSize == null) {
262
+ msg.emojiSize = "medium";
263
+ }
264
+ if (
265
+ msg.emojiSize != "small" &&
266
+ msg.emojiSize != "medium" &&
267
+ msg.emojiSize != "large"
268
+ ) {
269
+ return callback({ error: "emojiSize property is invalid" });
270
+ }
271
+ if (form["body"] != null && form["body"] != "") {
272
+ return callback({ error: "body is not empty" });
273
+ }
274
+ form["body"] = msg.emoji;
275
+ form["tags[0]"] = "hot_emoji_size:" + msg.emojiSize;
276
+ }
277
+ cb();
278
+ }
279
+
280
+ function handleAttachment(msg, form, callback, cb) {
281
+ if (msg.attachment) {
282
+ form["image_ids"] = [];
283
+ form["gif_ids"] = [];
284
+ form["file_ids"] = [];
285
+ form["video_ids"] = [];
286
+ form["audio_ids"] = [];
287
+
288
+ if (utils.getType(msg.attachment) !== "Array") {
289
+ msg.attachment = [msg.attachment];
290
+ }
291
+
292
+ uploadAttachment(msg.attachment, function (err, files) {
293
+ if (err) {
294
+ return callback(err);
295
+ }
296
+
297
+ files.forEach(function (file) {
298
+ const key = Object.keys(file);
299
+ const type = key[0]; // image_id, file_id, etc
300
+ form["" + type + "s"].push(file[type]); // push the id
301
+ });
302
+ cb();
303
+ });
304
+ } else {
305
+ cb();
306
+ }
307
+ }
308
+
309
+ function handleMention(msg, form, callback, cb) {
310
+ if (msg.mentions) {
311
+ for (let i = 0; i < msg.mentions.length; i++) {
312
+ const mention = msg.mentions[i];
313
+
314
+ const tag = mention.tag;
315
+ if (typeof tag !== "string") {
316
+ return callback({ error: "Mention tags must be strings." });
317
+ }
318
+
319
+ const offset = msg.body.indexOf(tag, mention.fromIndex || 0);
320
+
321
+ if (offset < 0) {
322
+ log.warn(
323
+ "handleMention",
324
+ 'Mention for "' + tag + '" not found in message string.'
325
+ );
326
+ }
327
+
328
+ if (mention.id == null) {
329
+ log.warn("handleMention", "Mention id should be non-null.");
330
+ }
331
+
332
+ const id = mention.id || 0;
333
+ form["profile_xmd[" + i + "][offset]"] = offset;
334
+ form["profile_xmd[" + i + "][length]"] = tag.length;
335
+ form["profile_xmd[" + i + "][id]"] = id;
336
+ form["profile_xmd[" + i + "][type]"] = "p";
337
+ }
338
+ }
339
+ cb();
340
+ }
341
+
342
+ return function sendMessage(msg, threadID, callback, replyToMessage, isGroup) {
343
+ typeof isGroup == "undefined" ? isGroup = null : "";
344
+ if (
345
+ !callback &&
346
+ (utils.getType(threadID) === "Function" ||
347
+ utils.getType(threadID) === "AsyncFunction")
348
+ ) {
349
+ return threadID({ error: "Pass a threadID as a second argument." });
350
+ }
351
+ if (
352
+ !replyToMessage &&
353
+ utils.getType(callback) === "String"
354
+ ) {
355
+ replyToMessage = callback;
356
+ callback = function () { };
357
+ }
358
+
359
+ let resolveFunc = function () { };
360
+ let rejectFunc = function () { };
361
+ const returnPromise = new Promise(function (resolve, reject) {
362
+ resolveFunc = resolve;
363
+ rejectFunc = reject;
364
+ });
365
+
366
+ if (!callback) {
367
+ callback = function (err, friendList) {
368
+ if (err) {
369
+ return rejectFunc(err);
370
+ }
371
+ resolveFunc(friendList);
372
+ };
373
+ }
374
+
375
+ const msgType = utils.getType(msg);
376
+ const threadIDType = utils.getType(threadID);
377
+ const messageIDType = utils.getType(replyToMessage);
378
+
379
+ if (msgType !== "String" && msgType !== "Object") {
380
+ return callback({
381
+ error:
382
+ "Message should be of type string or object and not " + msgType + "."
383
+ });
384
+ }
385
+
386
+ // Changing this to accomodate an array of users
387
+ if (
388
+ threadIDType !== "Array" &&
389
+ threadIDType !== "Number" &&
390
+ threadIDType !== "String"
391
+ ) {
392
+ return callback({
393
+ error:
394
+ "ThreadID should be of type number, string, or array and not " +
395
+ threadIDType +
396
+ "."
397
+ });
398
+ }
399
+
400
+ if (replyToMessage && messageIDType !== 'String') {
401
+ return callback({
402
+ error:
403
+ "MessageID should be of type string and not " +
404
+ threadIDType +
405
+ "."
406
+ });
407
+ }
408
+
409
+ if (msgType === "String") {
410
+ msg = { body: msg };
411
+ }
412
+
413
+ if (utils.getType(msg.body) === "String") {
414
+ msg.body = removeSpecialChar(msg.body);
415
+ }
416
+
417
+ const disallowedProperties = Object.keys(msg).filter(
418
+ prop => !allowedProperties[prop]
419
+ );
420
+ if (disallowedProperties.length > 0) {
421
+ return callback({
422
+ error: "Dissallowed props: `" + disallowedProperties.join(", ") + "`"
423
+ });
424
+ }
425
+
426
+ const messageAndOTID = utils.generateOfflineThreadingID();
427
+
428
+ const form = {
429
+ client: "mercury",
430
+ action_type: "ma-type:user-generated-message",
431
+ author: "fbid:" + (ctx.i_userID || ctx.userID),
432
+ timestamp: Date.now(),
433
+ timestamp_absolute: "Today",
434
+ timestamp_relative: utils.generateTimestampRelative(),
435
+ timestamp_time_passed: "0",
436
+ is_unread: false,
437
+ is_cleared: false,
438
+ is_forward: false,
439
+ is_filtered_content: false,
440
+ is_filtered_content_bh: false,
441
+ is_filtered_content_account: false,
442
+ is_filtered_content_quasar: false,
443
+ is_filtered_content_invalid_app: false,
444
+ is_spoof_warning: false,
445
+ source: "source:chat:web",
446
+ "source_tags[0]": "source:chat",
447
+ body: msg.body ? msg.body.toString() : "",
448
+ html_body: false,
449
+ ui_push_phase: "V3",
450
+ status: "0",
451
+ offline_threading_id: messageAndOTID,
452
+ message_id: messageAndOTID,
453
+ threading_id: utils.generateThreadingID(ctx.clientID),
454
+ "ephemeral_ttl_mode:": "0",
455
+ manual_retry_cnt: "0",
456
+ has_attachment: !!(msg.attachment || msg.url || msg.sticker),
457
+ signatureID: utils.getSignatureID(),
458
+ replied_to_message_id: replyToMessage
459
+ };
460
+
461
+ handleLocation(msg, form, callback, () =>
462
+ handleSticker(msg, form, callback, () =>
463
+ handleAttachment(msg, form, callback, () =>
464
+ handleUrl(msg, form, callback, () =>
465
+ handleEmoji(msg, form, callback, () =>
466
+ handleMention(msg, form, callback, () =>
467
+ send(form, threadID, messageAndOTID, callback, isGroup)
468
+ )
469
+ )
470
+ )
471
+ )
472
+ )
473
+ );
474
+
475
+ return returnPromise;
476
+ };
334
477
  };