@dongdev/fca-unofficial 0.0.9 → 1.0.1

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/README.md CHANGED
@@ -12,15 +12,13 @@ _Disclaimer_: We are not responsible if your account gets banned for spammy acti
12
12
 
13
13
  If you encounter errors on fca, you can contact me [here](https://www.facebook.com/minhdong.dev)
14
14
 
15
- `</div>`
16
-
17
15
  Facebook now has an official API for chat bots [here](https://developers.facebook.com/docs/messenger-platform).
18
16
 
19
17
  This API is the only way to automate chat functionalities on a user account. We do this by emulating the browser. This means doing the exact same GET/POST requests and tricking Facebook into thinking we're accessing the website normally. Because we're doing it this way, this API won't work with an auth token but requires the credentials of a Facebook account.
20
18
 
21
19
  ## Install
22
20
 
23
- If you just want to use ws3-fca, you should use this command:
21
+ If you just want to use @dongdev/fca-unofficial, you should use this command:
24
22
 
25
23
  ```bash
26
24
  npm install @dongdev/fca-unofficial@latest
@@ -31,19 +29,19 @@ It will download @dongdev/fca-unofficial from NPM repositories
31
29
  ## Example Usage
32
30
 
33
31
  ```javascript
32
+
34
33
  const login = require("@dongdev/fca-unofficial");
35
34
 
36
- // Create simple echo bot
37
- login({
38
- appState: []
39
- }, {
40
- //setOptions will be here
41
- } (err, api) => {
42
- if (err) return utils.error(err);
35
+ login({ appState: [] }, (err, api) => {
36
+ if (err) return console.error(err);
37
+
43
38
  api.listenMqtt((err, event) => {
39
+ if (err) return console.error(err);
40
+
44
41
  api.sendMessage(event.body, event.threadID);
45
42
  });
46
43
  });
44
+
47
45
  ```
48
46
 
49
47
  Result:
@@ -73,35 +71,55 @@ __Example (Basic Message)__
73
71
  ```js
74
72
  const login = require("@dongdev/fca-unofficial");
75
73
 
76
- login({
77
- appState: []
78
- }, (err, api) => {
79
- if(err) return console.error(err);
74
+ login({ appState: [] }, (err, api) => {
75
+ if (err) {
76
+ console.error("Login Error:", err);
77
+ return;
78
+ }
80
79
 
81
- var yourID = "000000000000000";
82
- var msg = "Hey!";
83
- api.sendMessage(msg, yourID);
80
+ let yourID = "000000000000000"; // Replace with actual Facebook ID
81
+ let msg = "Hey!";
82
+
83
+ api.sendMessage(msg, yourID, (err) => {
84
+ if (err) console.error("Message Sending Error:", err);
85
+ else console.log("Message sent successfully!");
86
+ });
84
87
  });
88
+
85
89
  ```
86
90
 
87
91
  __Example (File upload)__
88
92
 
89
93
  ```js
90
94
  const login = require("@dongdev/fca-unofficial");
95
+ const fs = require("fs"); // ✅ Required the fs module
96
+
97
+ login({ appState: [] }, (err, api) => {
98
+ if (err) {
99
+ console.error("Login Error:", err);
100
+ return;
101
+ }
91
102
 
92
- login({
93
- appState: []
94
- }, (err, api) => {
95
- if(err) return console.error(err);
103
+ let yourID = "000000000000000"; // Replace with actual Facebook ID
104
+ let imagePath = __dirname + "/image.jpg";
96
105
 
97
- // Note this example uploads an image called image.jpg
98
- var yourID = "000000000000000";
99
- var msg = {
100
- body: "Hey!",
101
- attachment: fs.createReadStream(__dirname + '/image.jpg')
106
+ // Check if the file exists before sending
107
+ if (!fs.existsSync(imagePath)) {
108
+ console.error("Error: Image file not found!");
109
+ return;
102
110
  }
103
- api.sendMessage(msg, yourID);
111
+
112
+ let msg = {
113
+ body: "Hey!",
114
+ attachment: fs.createReadStream(imagePath)
115
+ };
116
+
117
+ api.sendMessage(msg, yourID, (err) => {
118
+ if (err) console.error("Message Sending Error:", err);
119
+ else console.log("Message sent successfully!");
120
+ });
104
121
  });
122
+
105
123
  ```
106
124
 
107
125
  ---
@@ -116,15 +134,23 @@ __Example__
116
134
  const fs = require("fs");
117
135
  const login = require("@dongdev/fca-unofficial");
118
136
 
119
- var credentials = {
120
- appState: []
121
- };
137
+ const credentials = { appState: [] };
122
138
 
123
139
  login(credentials, (err, api) => {
124
- if(err) return console.error(err);
140
+ if (err) {
141
+ console.error("Login Error:", err);
142
+ return;
143
+ }
125
144
 
126
- fs.writeFileSync('appstate.json', JSON.stringify(api.getAppState()));
145
+ try {
146
+ const appState = JSON.stringify(api.getAppState(), null, 2); // Pretty print for readability
147
+ fs.writeFileSync("appstate.json", appState);
148
+ console.log("✅ AppState saved successfully!");
149
+ } catch (error) {
150
+ console.error("Error saving AppState:", error);
151
+ }
127
152
  });
153
+
128
154
  ```
129
155
 
130
156
  Alternative: Use [c3c-fbstate](https://github.com/c3cbot/c3c-fbstate) to get fbstate.json (appstate.json)
@@ -143,34 +169,44 @@ __Example__
143
169
  const fs = require("fs");
144
170
  const login = require("@dongdev/fca-unofficial");
145
171
 
146
- // Simple echo bot. It will repeat everything that you say.
147
- // Will stop when you say '/stop'
148
- login({appState: JSON.parse(fs.readFileSync('appstate.json', 'utf8'))}, (err, api) => {
149
- if(err) return console.error(err);
172
+ // Simple echo bot: Repeats everything you say. Stops when you say "/stop".
173
+ login({ appState: JSON.parse(fs.readFileSync("appstate.json", "utf8")) }, (err, api) => {
174
+ if (err) {
175
+ console.error("Login Error:", err);
176
+ return;
177
+ }
150
178
 
151
- api.setOptions({listenEvents: true});
179
+ api.setOptions({ listenEvents: true });
152
180
 
153
- var stopListening = api.listenMqtt((err, event) => {
154
- if( err) return console.error(err);
181
+ const stopListening = api.listenMqtt((err, event) => {
182
+ if (err) {
183
+ console.error("Listen Error:", err);
184
+ return;
185
+ }
155
186
 
187
+ // Mark message as read
156
188
  api.markAsRead(event.threadID, (err) => {
157
- if(err) console.error(err);
189
+ if (err) console.error("Mark as read error:", err);
158
190
  });
159
191
 
160
- switch(event.type) {
192
+ // Handle different event types
193
+ switch (event.type) {
161
194
  case "message":
162
- if(event.body === '/stop') {
195
+ if (event.body && event.body.trim().toLowerCase() === "/stop") {
163
196
  api.sendMessage("Goodbye…", event.threadID);
164
- return stopListening();
197
+ stopListening();
198
+ return;
165
199
  }
166
- api.sendMessage("TEST BOT: " + event.body, event.threadID);
200
+ api.sendMessage(`TEST BOT: ${event.body}`, event.threadID);
167
201
  break;
202
+
168
203
  case "event":
169
- console.log(event);
204
+ console.log("Event Received:", event);
170
205
  break;
171
206
  }
172
207
  });
173
208
  });
209
+
174
210
  ```
175
211
 
176
212
  `<a name="projects-using-this-api"></a>`
package/index.js CHANGED
@@ -130,33 +130,18 @@ function setOptions(globalOptions, options) {
130
130
  });
131
131
  }
132
132
  function buildAPI(globalOptions, html, jar) {
133
- const maybeCookie = jar
134
- .getCookies("https://www.facebook.com")
135
- .filter(function(val) {
136
- return val.cookieString().split("=")[0] === "c_user";
137
- });
138
- const objCookie = jar
139
- .getCookies("https://www.facebook.com")
140
- .reduce(function(obj, val) {
141
- obj[val.cookieString().split("=")[0]] = val.cookieString().split("=")[1];
142
- return obj;
143
- }, {});
144
- if (maybeCookie.length === 0) {
145
- throw {
146
- error:
147
- "Error retrieving userID. This can be caused by a lot of things, including getting blocked by Facebook for logging in from an unknown location. Try logging in with a browser to verify.",
148
- };
149
- }
150
- if (html.indexOf("/checkpoint/block/?next") > -1 || html.indexOf("/checkpoint/?next") > -1) {
151
- throw {
152
- error: "Checkpoint detected. Please log in with a browser to verify.",
153
- }
133
+ const cookies = jar.getCookies("https://www.facebook.com");
134
+ const userCookie = cookies.find(c => c.cookieString().startsWith("c_user="));
135
+ const tiktikCookie = cookies.find(c => c.cookieString().startsWith("i_user="));
136
+ if (userCookie.length === 0 && tiktikCookie.length === 0) {
137
+ return log.error('login', "Không tìm thấy cookie cho người dùng, vui lòng kiểm tra lại thông tin đăng nhập")
138
+ } else if (!userCookie && !tiktikCookie) {
139
+ return log.error('login', "Không tìm thấy cookie cho người dùng, vui lòng kiểm tra lại thông tin đăng nhập")
140
+ } else if (html.includes("/checkpoint/block/?next")) {
141
+ return log.error('login', "Appstate die, vui lòng thay cái mới!", 'error');
154
142
  }
155
- const userID = maybeCookie[0]
156
- .cookieString()
157
- .split("=")[1]
158
- .toString();
159
- const i_userID = objCookie.i_user || null;
143
+ const userID = (tiktikCookie || userCookie).cookieString().split("=")[1];
144
+ const i_userID = tiktikCookie ? tiktikCookie.cookieString().split("=")[1] : null;
160
145
  logger(`Logged in as ${userID}`, 'info');
161
146
  try {
162
147
  clearInterval(checkVerified);
@@ -204,6 +189,8 @@ function buildAPI(globalOptions, html, jar) {
204
189
  region,
205
190
  firstListen: true,
206
191
  fb_dtsg,
192
+ wsReqNumber: 0,
193
+ wsTaskNumber: 0
207
194
  };
208
195
  const api = {
209
196
  setOptions: setOptions.bind(null, globalOptions),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongdev/fca-unofficial",
3
- "version": "0.0.9",
3
+ "version": "1.0.1",
4
4
  "description": "A Facebook chat API without XMPP, will not be deprecated after April 30th, 2015.",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -22,6 +22,7 @@
22
22
  "request": "^2.53.0",
23
23
  "sequelize": "^6.37.6",
24
24
  "sqlite3": "^5.1.7",
25
+ "totp-generator": "^1.0.0",
25
26
  "ws": "^8.18.1"
26
27
  },
27
28
  "devDependencies": {
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
-
2
+ /*
3
3
  const utils = require("../utils");
4
4
  const log = require("npmlog");
5
5
  const fs = require("fs");
@@ -299,4 +299,237 @@ module.exports = function(defaultFuncs, api, ctx) {
299
299
  }
300
300
  return returnPromise;
301
301
  };
302
+ };
303
+ */
304
+ "use strict";
305
+
306
+ const utils = require("../utils");
307
+ const log = require("npmlog");
308
+
309
+ function formatEventReminders(reminder) {
310
+ return {
311
+ reminderID: reminder.id,
312
+ eventCreatorID: reminder.lightweight_event_creator.id,
313
+ time: reminder.time,
314
+ eventType: reminder.lightweight_event_type.toLowerCase(),
315
+ locationName: reminder.location_name,
316
+ // @TODO verify this
317
+ locationCoordinates: reminder.location_coordinates,
318
+ locationPage: reminder.location_page,
319
+ eventStatus: reminder.lightweight_event_status.toLowerCase(),
320
+ note: reminder.note,
321
+ repeatMode: reminder.repeat_mode.toLowerCase(),
322
+ eventTitle: reminder.event_title,
323
+ triggerMessage: reminder.trigger_message,
324
+ secondsToNotifyBefore: reminder.seconds_to_notify_before,
325
+ allowsRsvp: reminder.allows_rsvp,
326
+ relatedEvent: reminder.related_event,
327
+ members: reminder.event_reminder_members.edges.map(function (member) {
328
+ return {
329
+ memberID: member.node.id,
330
+ state: member.guest_list_state.toLowerCase()
331
+ };
332
+ })
333
+ };
334
+ }
335
+
336
+ function formatThreadGraphQLResponse(data) {
337
+ if (data.errors)
338
+ return data.errors;
339
+ const messageThread = data.message_thread;
340
+ if (!messageThread)
341
+ return null;
342
+ const threadID = messageThread.thread_key.thread_fbid
343
+ ? messageThread.thread_key.thread_fbid
344
+ : messageThread.thread_key.other_user_id;
345
+
346
+ // Remove me
347
+ const lastM = messageThread.last_message;
348
+ const snippetID =
349
+ lastM &&
350
+ lastM.nodes &&
351
+ lastM.nodes[0] &&
352
+ lastM.nodes[0].message_sender &&
353
+ lastM.nodes[0].message_sender.messaging_actor
354
+ ? lastM.nodes[0].message_sender.messaging_actor.id
355
+ : null;
356
+ const snippetText =
357
+ lastM && lastM.nodes && lastM.nodes[0] ? lastM.nodes[0].snippet : null;
358
+ const lastR = messageThread.last_read_receipt;
359
+ const lastReadTimestamp =
360
+ lastR && lastR.nodes && lastR.nodes[0] && lastR.nodes[0].timestamp_precise
361
+ ? lastR.nodes[0].timestamp_precise
362
+ : null;
363
+
364
+ return {
365
+ threadID: threadID,
366
+ threadName: messageThread.name,
367
+ participantIDs: messageThread.all_participants.edges.map(d => d.node.messaging_actor.id),
368
+ userInfo: messageThread.all_participants.edges.map(d => ({
369
+ id: d.node.messaging_actor.id,
370
+ name: d.node.messaging_actor.name,
371
+ firstName: d.node.messaging_actor.short_name,
372
+ vanity: d.node.messaging_actor.username,
373
+ url: d.node.messaging_actor.url,
374
+ thumbSrc: d.node.messaging_actor.big_image_src.uri,
375
+ profileUrl: d.node.messaging_actor.big_image_src.uri,
376
+ gender: d.node.messaging_actor.gender,
377
+ type: d.node.messaging_actor.__typename,
378
+ isFriend: d.node.messaging_actor.is_viewer_friend,
379
+ isBirthday: !!d.node.messaging_actor.is_birthday //not sure?
380
+ })),
381
+ unreadCount: messageThread.unread_count,
382
+ messageCount: messageThread.messages_count,
383
+ timestamp: messageThread.updated_time_precise,
384
+ muteUntil: messageThread.mute_until,
385
+ isGroup: messageThread.thread_type == "GROUP",
386
+ isSubscribed: messageThread.is_viewer_subscribed,
387
+ isArchived: messageThread.has_viewer_archived,
388
+ folder: messageThread.folder,
389
+ cannotReplyReason: messageThread.cannot_reply_reason,
390
+ eventReminders: messageThread.event_reminders
391
+ ? messageThread.event_reminders.nodes.map(formatEventReminders)
392
+ : null,
393
+ emoji: messageThread.customization_info
394
+ ? messageThread.customization_info.emoji
395
+ : null,
396
+ color:
397
+ messageThread.customization_info &&
398
+ messageThread.customization_info.outgoing_bubble_color
399
+ ? messageThread.customization_info.outgoing_bubble_color.slice(2)
400
+ : null,
401
+ threadTheme: messageThread.thread_theme,
402
+ nicknames:
403
+ messageThread.customization_info &&
404
+ messageThread.customization_info.participant_customizations
405
+ ? messageThread.customization_info.participant_customizations.reduce(
406
+ function (res, val) {
407
+ if (val.nickname) res[val.participant_id] = val.nickname;
408
+ return res;
409
+ },
410
+ {}
411
+ )
412
+ : {},
413
+ adminIDs: messageThread.thread_admins,
414
+ approvalMode: Boolean(messageThread.approval_mode),
415
+ approvalQueue: messageThread.group_approval_queue.nodes.map(a => ({
416
+ inviterID: a.inviter.id,
417
+ requesterID: a.requester.id,
418
+ timestamp: a.request_timestamp,
419
+ request_source: a.request_source // @Undocumented
420
+ })),
421
+
422
+ // @Undocumented
423
+ reactionsMuteMode: messageThread.reactions_mute_mode.toLowerCase(),
424
+ mentionsMuteMode: messageThread.mentions_mute_mode.toLowerCase(),
425
+ isPinProtected: messageThread.is_pin_protected,
426
+ relatedPageThread: messageThread.related_page_thread,
427
+
428
+ // @Legacy
429
+ name: messageThread.name,
430
+ snippet: snippetText,
431
+ snippetSender: snippetID,
432
+ snippetAttachments: [],
433
+ serverTimestamp: messageThread.updated_time_precise,
434
+ imageSrc: messageThread.image ? messageThread.image.uri : null,
435
+ isCanonicalUser: messageThread.is_canonical_neo_user,
436
+ isCanonical: messageThread.thread_type != "GROUP",
437
+ recipientsLoadable: true,
438
+ hasEmailParticipant: false,
439
+ readOnly: false,
440
+ canReply: messageThread.cannot_reply_reason == null,
441
+ lastMessageTimestamp: messageThread.last_message
442
+ ? messageThread.last_message.timestamp_precise
443
+ : null,
444
+ lastMessageType: "message",
445
+ lastReadTimestamp: lastReadTimestamp,
446
+ threadType: messageThread.thread_type == "GROUP" ? 2 : 1,
447
+
448
+ // update in Wed, 13 Jul 2022 19:41:12 +0700
449
+ inviteLink: {
450
+ enable: messageThread.joinable_mode ? messageThread.joinable_mode.mode == 1 : false,
451
+ link: messageThread.joinable_mode ? messageThread.joinable_mode.link : null
452
+ }
453
+ };
454
+ }
455
+
456
+ module.exports = function (defaultFuncs, api, ctx) {
457
+ return function getThreadInfoGraphQL(threadID, callback) {
458
+ let resolveFunc = function () { };
459
+ let rejectFunc = function () { };
460
+ const returnPromise = new Promise(function (resolve, reject) {
461
+ resolveFunc = resolve;
462
+ rejectFunc = reject;
463
+ });
464
+
465
+ if (utils.getType(callback) != "Function" && utils.getType(callback) != "AsyncFunction") {
466
+ callback = function (err, data) {
467
+ if (err) {
468
+ return rejectFunc(err);
469
+ }
470
+ resolveFunc(data);
471
+ };
472
+ }
473
+
474
+ if (utils.getType(threadID) !== "Array") {
475
+ threadID = [threadID];
476
+ }
477
+
478
+ let form = {};
479
+ // `queries` has to be a string. I couldn't tell from the dev console. This
480
+ // took me a really long time to figure out. I deserve a cookie for this.
481
+ threadID.map(function (t, i) {
482
+ form["o" + i] = {
483
+ doc_id: "3449967031715030",
484
+ query_params: {
485
+ id: t,
486
+ message_limit: 0,
487
+ load_messages: false,
488
+ load_read_receipts: false,
489
+ before: null
490
+ }
491
+ };
492
+ });
493
+
494
+ form = {
495
+ queries: JSON.stringify(form),
496
+ batch_name: "MessengerGraphQLThreadFetcher"
497
+ };
498
+
499
+ defaultFuncs
500
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
501
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
502
+ .then(function (resData) {
503
+
504
+ if (resData.error) {
505
+ throw resData;
506
+ }
507
+ // This returns us an array of things. The last one is the success /
508
+ // failure one.
509
+ // @TODO What do we do in this case?
510
+ // if (resData[resData.length - 1].error_results !== 0) {
511
+ // throw resData[0].o0.errors[0];
512
+ // }
513
+ // if (!resData[0].o0.data.message_thread) {
514
+ // throw new Error("can't find this thread");
515
+ // }
516
+ const threadInfos = {};
517
+ for (let i = resData.length - 2; i >= 0; i--) {
518
+ const threadInfo = formatThreadGraphQLResponse(resData[i][Object.keys(resData[i])[0]].data);
519
+ threadInfos[threadInfo?.threadID || threadID[threadID.length - 1 - i]] = threadInfo;
520
+ }
521
+ if (Object.values(threadInfos).length == 1) {
522
+ callback(null, Object.values(threadInfos)[0]);
523
+ }
524
+ else {
525
+ callback(null, threadInfos);
526
+ }
527
+ })
528
+ .catch(function (err) {
529
+ log.error("getThreadInfoGraphQL", err);
530
+ return callback(err);
531
+ });
532
+
533
+ return returnPromise;
534
+ };
302
535
  };
@@ -1,117 +1,119 @@
1
1
  "use strict";
2
2
 
3
- const utils = require("../utils");
4
- const log = require("npmlog");
3
+ var utils = require("../utils");
4
+ var log = require("npmlog");
5
+ const { generateOfflineThreadingID } = require("../utils");
5
6
 
6
7
  module.exports = function (defaultFuncs, api, ctx) {
7
- return function setMessageReaction(reaction, messageID, callback, forceCustomReaction) {
8
- let resolveFunc = function () { };
9
- let rejectFunc = function () { };
10
- const returnPromise = new Promise(function (resolve, reject) {
11
- resolveFunc = resolve;
12
- rejectFunc = reject;
13
- });
8
+ function setMessageReactionNoMqtt(reaction, messageID, callback) {
9
+ var resolveFunc = function () {};
10
+ var rejectFunc = function () {};
11
+ var returnPromise = new Promise(function (resolve, reject) {
12
+ resolveFunc = resolve;
13
+ rejectFunc = reject;
14
+ });
15
+ if (!callback) {
16
+ callback = function (err, friendList) {
17
+ if (err) {
18
+ return rejectFunc(err);
19
+ }
20
+ resolveFunc(friendList);
21
+ };
22
+ }
23
+ var variables = {
24
+ data: {
25
+ client_mutation_id: ctx.clientMutationId++,
26
+ actor_id: ctx.userID,
27
+ action: reaction == "" ? "REMOVE_REACTION" : "ADD_REACTION",
28
+ message_id: messageID,
29
+ reaction: reaction,
30
+ },
31
+ };
32
+ var qs = {
33
+ doc_id: "1491398900900362",
34
+ variables: JSON.stringify(variables),
35
+ dpr: 1,
36
+ };
37
+ defaultFuncs
38
+ .postFormData(
39
+ "https://www.facebook.com/webgraphql/mutation/",
40
+ ctx.jar,
41
+ {},
42
+ qs,
43
+ )
44
+ .then(utils.parseAndCheckLogin(ctx.jar, defaultFuncs))
45
+ .then(function (resData) {
46
+ if (!resData) {
47
+ throw { error: "setReaction returned empty object." };
48
+ }
49
+ if (resData.error) {
50
+ throw resData;
51
+ }
52
+ callback(null);
53
+ })
54
+ .catch(function (err) {
55
+ log.error("setReaction", err);
56
+ return callback(err);
57
+ });
14
58
 
15
- if (!callback) {
16
- callback = function (err, friendList) {
17
- if (err) {
18
- return rejectFunc(err);
19
- }
20
- resolveFunc(friendList);
21
- };
22
- }
59
+ return returnPromise;
60
+ }
23
61
 
24
- switch (reaction) {
25
- case "\uD83D\uDE0D": //:heart_eyes:
26
- case "\uD83D\uDE06": //:laughing:
27
- case "\uD83D\uDE2E": //:open_mouth:
28
- case "\uD83D\uDE22": //:cry:
29
- case "\uD83D\uDE20": //:angry:
30
- case "\uD83D\uDC4D": //:thumbsup:
31
- case "\uD83D\uDC4E": //:thumbsdown:
32
- case "\u2764": //:heart:
33
- case "\uD83D\uDC97": //:glowingheart:
34
- case "":
35
- //valid
36
- break;
37
- case ":heart_eyes:":
38
- case ":love:":
39
- reaction = "\uD83D\uDE0D";
40
- break;
41
- case ":laughing:":
42
- case ":haha:":
43
- reaction = "\uD83D\uDE06";
44
- break;
45
- case ":open_mouth:":
46
- case ":wow:":
47
- reaction = "\uD83D\uDE2E";
48
- break;
49
- case ":cry:":
50
- case ":sad:":
51
- reaction = "\uD83D\uDE22";
52
- break;
53
- case ":angry:":
54
- reaction = "\uD83D\uDE20";
55
- break;
56
- case ":thumbsup:":
57
- case ":like:":
58
- reaction = "\uD83D\uDC4D";
59
- break;
60
- case ":thumbsdown:":
61
- case ":dislike:":
62
- reaction = "\uD83D\uDC4E";
63
- break;
64
- case ":heart:":
65
- reaction = "\u2764";
66
- break;
67
- case ":glowingheart:":
68
- reaction = "\uD83D\uDC97";
69
- break;
70
- default:
71
- if (forceCustomReaction) {
72
- break;
73
- }
74
- return callback({ error: "Reaction is not a valid emoji." });
75
- }
76
-
77
- const variables = {
78
- data: {
79
- client_mutation_id: ctx.clientMutationId++,
80
- actor_id: ctx.i_userID || ctx.userID,
81
- action: reaction == "" ? "REMOVE_REACTION" : "ADD_REACTION",
82
- message_id: messageID,
83
- reaction: reaction
84
- }
85
- };
86
-
87
- const qs = {
88
- doc_id: "1491398900900362",
89
- variables: JSON.stringify(variables),
90
- dpr: 1
91
- };
92
-
93
- defaultFuncs
94
- .postFormData(
95
- "https://www.facebook.com/webgraphql/mutation/",
96
- ctx.jar,
97
- {},
98
- qs
99
- )
100
- .then(utils.parseAndCheckLogin(ctx.jar, defaultFuncs))
101
- .then(function (resData) {
102
- if (!resData) {
103
- throw { error: "setReaction returned empty object." };
104
- }
105
- if (resData.error) {
106
- throw resData;
107
- }
108
- callback(null);
109
- })
110
- .catch(function (err) {
111
- log.error("setReaction", err);
112
- return callback(err);
113
- });
114
-
115
- return returnPromise;
116
- };
117
- };
62
+ function setMessageReactionMqtt(reaction, messageID, threadID, callback) {
63
+ if (!ctx.mqttClient) {
64
+ throw new Error("Not connected to MQTT");
65
+ }
66
+ ctx.wsReqNumber += 1;
67
+ let taskNumber = ++ctx.wsTaskNumber;
68
+ const taskPayload = {
69
+ thread_key: threadID,
70
+ timestamp_ms: getCurrentTimestamp(),
71
+ message_id: messageID,
72
+ reaction: reaction,
73
+ actor_id: ctx.userID,
74
+ reaction_style: null,
75
+ sync_group: 1,
76
+ send_attribution: Math.random() < 0.5 ? 65537 : 524289,
77
+ };
78
+ const task = {
79
+ failure_count: null,
80
+ label: "29",
81
+ payload: JSON.stringify(taskPayload),
82
+ queue_name: JSON.stringify(["reaction", messageID]),
83
+ task_id: taskNumber,
84
+ };
85
+ const content = {
86
+ app_id: "2220391788200892",
87
+ payload: JSON.stringify({
88
+ data_trace_id: null,
89
+ epoch_id: parseInt(generateOfflineThreadingID()),
90
+ tasks: [task],
91
+ version_id: "7158486590867448",
92
+ }),
93
+ request_id: ctx.wsReqNumber,
94
+ type: 3,
95
+ };
96
+ if (typeof callback === "function") {
97
+ ctx["tasks"].set(taskNumber, {
98
+ type: "set_message_reaction",
99
+ callback: callback,
100
+ });
101
+ }
102
+ ctx.mqttClient.publish("/ls_req", JSON.stringify(content), {
103
+ qos: 1,
104
+ retain: false,
105
+ });
106
+ }
107
+ return function setMessageReaction(reaction, messageID, threadID, callback) {
108
+ if (ctx.mqttClient) {
109
+ try {
110
+ setMessageReactionMqtt(reaction, messageID, threadID, callback);
111
+ callback();
112
+ } catch (e) {
113
+ setMessageReactionNoMqtt(reaction, messageID, callback);
114
+ }
115
+ } else {
116
+ setMessageReactionNoMqtt(reaction, messageID, callback);
117
+ }
118
+ };
119
+ };