fca-luxury 1.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fca-luxury might be problematic. Click here for more details.

Files changed (66) hide show
  1. package/DOCS.md +1686 -0
  2. package/README.md +220 -0
  3. package/index.js +544 -0
  4. package/package.json +73 -0
  5. package/src/Screenshot.js +83 -0
  6. package/src/addExternalModule.js +15 -0
  7. package/src/addUserToGroup.js +77 -0
  8. package/src/changeAdminStatus.js +47 -0
  9. package/src/changeArchivedStatus.js +41 -0
  10. package/src/changeAvt.js +85 -0
  11. package/src/changeBio.js +65 -0
  12. package/src/changeBlockedStatus.js +36 -0
  13. package/src/changeGroupImage.js +106 -0
  14. package/src/changeNickname.js +45 -0
  15. package/src/changeThreadColor.js +61 -0
  16. package/src/changeThreadEmoji.js +41 -0
  17. package/src/createNewGroup.js +70 -0
  18. package/src/createPoll.js +59 -0
  19. package/src/deleteMessage.js +44 -0
  20. package/src/deleteThread.js +42 -0
  21. package/src/editMessage.js +55 -0
  22. package/src/forwardAttachment.js +47 -0
  23. package/src/getCurrentUserID.js +7 -0
  24. package/src/getEmojiUrl.js +27 -0
  25. package/src/getFriendsList.js +73 -0
  26. package/src/getThreadHistory.js +537 -0
  27. package/src/getThreadHistoryDeprecated.js +71 -0
  28. package/src/getThreadInfo.js +171 -0
  29. package/src/getThreadInfoDeprecated.js +56 -0
  30. package/src/getThreadList.js +213 -0
  31. package/src/getThreadListDeprecated.js +46 -0
  32. package/src/getThreadPictures.js +59 -0
  33. package/src/getUserID.js +61 -0
  34. package/src/getUserInfo.js +66 -0
  35. package/src/handleFriendRequest.js +46 -0
  36. package/src/handleMessageRequest.js +47 -0
  37. package/src/httpGet.js +49 -0
  38. package/src/httpPost.js +48 -0
  39. package/src/listenMqtt.js +701 -0
  40. package/src/logout.js +68 -0
  41. package/src/markAsDelivered.js +47 -0
  42. package/src/markAsRead.js +70 -0
  43. package/src/markAsReadAll.js +40 -0
  44. package/src/markAsSeen.js +48 -0
  45. package/src/muteThread.js +45 -0
  46. package/src/removeUserFromGroup.js +45 -0
  47. package/src/resolvePhotoUrl.js +36 -0
  48. package/src/searchForThread.js +42 -0
  49. package/src/sendMessage.js +328 -0
  50. package/src/sendTypingIndicator.js +70 -0
  51. package/src/setMessageReaction.js +109 -0
  52. package/src/setPostReaction.js +102 -0
  53. package/src/setTitle.js +70 -0
  54. package/src/shareContact.js +46 -0
  55. package/src/shareLink.js +55 -0
  56. package/src/threadColors.js +41 -0
  57. package/src/unfriend.js +42 -0
  58. package/src/unsendMessage.js +39 -0
  59. package/test/data/shareAttach.js +146 -0
  60. package/test/data/something.mov +0 -0
  61. package/test/data/test.png +0 -0
  62. package/test/data/test.txt +7 -0
  63. package/test/example-config.json +18 -0
  64. package/test/test-page.js +140 -0
  65. package/test/test.js +385 -0
  66. package/utils.js +1196 -0
package/utils.js ADDED
@@ -0,0 +1,1196 @@
1
+ /* eslint-disable no-prototype-builtins */
2
+ "use strict";
3
+
4
+ var bluebird = require("bluebird");
5
+ var request = bluebird.promisify(require("request").defaults({ jar: true }));
6
+ var stream = require("stream");
7
+ var log = require("npmlog");
8
+ var querystring = require("querystring");
9
+ var url = require("url");
10
+
11
+ function setProxy(url) {
12
+ if (typeof url == undefined) return request = bluebird.promisify(require("request").defaults({ jar: true }));
13
+ return request = bluebird.promisify(require("request").defaults({ jar: true, proxy: url }));
14
+ }
15
+
16
+ function getHeaders(url, options, ctx, customHeader) {
17
+ var headers = {
18
+ "Content-Type": "application/x-www-form-urlencoded",
19
+ Referer: "https://www.facebook.com/",
20
+ Host: url.replace("https://", "").split("/")[0],
21
+ Origin: "https://www.facebook.com",
22
+ "User-Agent": options.userAgent,
23
+ Connection: "keep-alive"
24
+ };
25
+ if (customHeader) Object.assign(headers, customHeader);
26
+
27
+ if (ctx && ctx.region) headers["X-MSGR-Region"] = ctx.region;
28
+
29
+ return headers;
30
+ }
31
+
32
+ function isReadableStream(obj) {
33
+ return (
34
+ obj instanceof stream.Stream &&
35
+ (getType(obj._read) === "Function" ||
36
+ getType(obj._read) === "AsyncFunction") &&
37
+ getType(obj._readableState) === "Object"
38
+ );
39
+ }
40
+
41
+ function get(url, jar, qs, options, ctx) {
42
+ // I'm still confused about this
43
+ if (getType(qs) === "Object")
44
+ for (var prop in qs)
45
+ if (qs.hasOwnProperty(prop) && getType(qs[prop]) === "Object") qs[prop] = JSON.stringify(qs[prop]);
46
+ var op = {
47
+ headers: getHeaders(url, options, ctx),
48
+ timeout: 60000,
49
+ qs: qs,
50
+ url: url,
51
+ method: "GET",
52
+ jar: jar,
53
+ gzip: true
54
+ };
55
+
56
+ return request(op).then(function (res) {
57
+ return res[0];
58
+ });
59
+ }
60
+
61
+ function post(url, jar, form, options, ctx, customHeader) {
62
+ var op = {
63
+ headers: getHeaders(url, options, ctx, customHeader),
64
+ timeout: 60000,
65
+ url: url,
66
+ method: "POST",
67
+ form: form,
68
+ jar: jar,
69
+ gzip: true
70
+ };
71
+
72
+ return request(op).then(function (res) {
73
+ return res[0];
74
+ });
75
+ }
76
+
77
+ function postFormData(url, jar, form, qs, options, ctx) {
78
+ var headers = getHeaders(url, options, ctx);
79
+ headers["Content-Type"] = "multipart/form-data";
80
+ var op = {
81
+ headers: headers,
82
+ timeout: 60000,
83
+ url: url,
84
+ method: "POST",
85
+ formData: form,
86
+ qs: qs,
87
+ jar: jar,
88
+ gzip: true
89
+ };
90
+
91
+ return request(op).then(function (res) {
92
+ return res[0];
93
+ });
94
+ }
95
+
96
+ function padZeros(val, len) {
97
+ val = String(val);
98
+ len = len || 2;
99
+ while (val.length < len) val = "0" + val;
100
+ return val;
101
+ }
102
+
103
+ function generateThreadingID(clientID) {
104
+ var k = Date.now();
105
+ var l = Math.floor(Math.random() * 4294967295);
106
+ var m = clientID;
107
+ return "<" + k + ":" + l + "-" + m + "@mail.projektitan.com>";
108
+ }
109
+
110
+ function binaryToDecimal(data) {
111
+ var ret = "";
112
+ while (data !== "0") {
113
+ var end = 0;
114
+ var fullName = "";
115
+ var i = 0;
116
+ for (; i < data.length; i++) {
117
+ end = 2 * end + parseInt(data[i], 10);
118
+ if (end >= 10) {
119
+ fullName += "1";
120
+ end -= 10;
121
+ }
122
+ else fullName += "0";
123
+ }
124
+ ret = end.toString() + ret;
125
+ data = fullName.slice(fullName.indexOf("1"));
126
+ }
127
+ return ret;
128
+ }
129
+
130
+ function generateOfflineThreadingID() {
131
+ var ret = Date.now();
132
+ var value = Math.floor(Math.random() * 4294967295);
133
+ var str = ("0000000000000000000000" + value.toString(2)).slice(-22);
134
+ var msgs = ret.toString(2) + str;
135
+ return binaryToDecimal(msgs);
136
+ }
137
+
138
+ var h;
139
+ var i = {};
140
+ var j = {
141
+ _: "%",
142
+ A: "%2",
143
+ B: "000",
144
+ C: "%7d",
145
+ D: "%7b%22",
146
+ E: "%2c%22",
147
+ F: "%22%3a",
148
+ G: "%2c%22ut%22%3a1",
149
+ H: "%2c%22bls%22%3a",
150
+ I: "%2c%22n%22%3a%22%",
151
+ J: "%22%3a%7b%22i%22%3a0%7d",
152
+ K: "%2c%22pt%22%3a0%2c%22vis%22%3a",
153
+ L: "%2c%22ch%22%3a%7b%22h%22%3a%22",
154
+ M: "%7b%22v%22%3a2%2c%22time%22%3a1",
155
+ N: ".channel%22%2c%22sub%22%3a%5b",
156
+ O: "%2c%22sb%22%3a1%2c%22t%22%3a%5b",
157
+ P: "%2c%22ud%22%3a100%2c%22lc%22%3a0",
158
+ Q: "%5d%2c%22f%22%3anull%2c%22uct%22%3a",
159
+ R: ".channel%22%2c%22sub%22%3a%5b1%5d",
160
+ S: "%22%2c%22m%22%3a0%7d%2c%7b%22i%22%3a",
161
+ T: "%2c%22blc%22%3a1%2c%22snd%22%3a1%2c%22ct%22%3a",
162
+ U: "%2c%22blc%22%3a0%2c%22snd%22%3a1%2c%22ct%22%3a",
163
+ V: "%2c%22blc%22%3a0%2c%22snd%22%3a0%2c%22ct%22%3a",
164
+ W: "%2c%22s%22%3a0%2c%22blo%22%3a0%7d%2c%22bl%22%3a%7b%22ac%22%3a",
165
+ X: "%2c%22ri%22%3a0%7d%2c%22state%22%3a%7b%22p%22%3a0%2c%22ut%22%3a1",
166
+ Y: "%2c%22pt%22%3a0%2c%22vis%22%3a1%2c%22bls%22%3a0%2c%22blc%22%3a0%2c%22snd%22%3a1%2c%22ct%22%3a",
167
+ Z: "%2c%22sb%22%3a1%2c%22t%22%3a%5b%5d%2c%22f%22%3anull%2c%22uct%22%3a0%2c%22s%22%3a0%2c%22blo%22%3a0%7d%2c%22bl%22%3a%7b%22ac%22%3a"
168
+ };
169
+ (function () {
170
+ var l = [];
171
+ for (var m in j) {
172
+ i[j[m]] = m;
173
+ l.push(j[m]);
174
+ }
175
+ l.reverse();
176
+ h = new RegExp(l.join("|"), "g");
177
+ })();
178
+
179
+ function presenceEncode(str) {
180
+ return encodeURIComponent(str)
181
+ .replace(/([_A-Z])|%../g, function (m, n) {
182
+ return n ? "%" + n.charCodeAt(0).toString(16) : m;
183
+ })
184
+ .toLowerCase()
185
+ .replace(h, function (m) {
186
+ return i[m];
187
+ });
188
+ }
189
+
190
+ // eslint-disable-next-line no-unused-vars
191
+ function presenceDecode(str) {
192
+ return decodeURIComponent(
193
+ str.replace(/[_A-Z]/g, function (m) {
194
+ return j[m];
195
+ })
196
+ );
197
+ }
198
+
199
+ function generatePresence(userID) {
200
+ var time = Date.now();
201
+ return (
202
+ "E" +
203
+ presenceEncode(
204
+ JSON.stringify({
205
+ v: 3,
206
+ time: parseInt(time / 1000, 10),
207
+ user: userID,
208
+ state: {
209
+ ut: 0,
210
+ t2: [],
211
+ lm2: null,
212
+ uct2: time,
213
+ tr: null,
214
+ tw: Math.floor(Math.random() * 4294967295) + 1,
215
+ at: time
216
+ },
217
+ ch: { ["p_" + userID]: 0 }
218
+ })
219
+ )
220
+ );
221
+ }
222
+
223
+ function generateAccessiblityCookie() {
224
+ var time = Date.now();
225
+ return encodeURIComponent(
226
+ JSON.stringify({
227
+ sr: 0,
228
+ "sr-ts": time,
229
+ jk: 0,
230
+ "jk-ts": time,
231
+ kb: 0,
232
+ "kb-ts": time,
233
+ hcm: 0,
234
+ "hcm-ts": time
235
+ })
236
+ );
237
+ }
238
+
239
+ function getGUID() {
240
+ /** @type {number} */
241
+ var sectionLength = Date.now();
242
+ /** @type {string} */
243
+ var id = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
244
+ /** @type {number} */
245
+ var r = Math.floor((sectionLength + Math.random() * 16) % 16);
246
+ /** @type {number} */
247
+ sectionLength = Math.floor(sectionLength / 16);
248
+ /** @type {string} */
249
+ var _guid = (c == "x" ? r : (r & 7) | 8).toString(16);
250
+ return _guid;
251
+ });
252
+ return id;
253
+ }
254
+
255
+ function _formatAttachment(attachment1, attachment2) {
256
+ // TODO: THIS IS REALLY BAD
257
+ // This is an attempt at fixing Facebook's inconsistencies. Sometimes they give us
258
+ // two attachment objects, but sometimes only one. They each contain part of the
259
+ // data that you'd want so we merge them for convenience.
260
+ // Instead of having a bunch of if statements guarding every access to image_data,
261
+ // we set it to empty object and use the fact that it'll return undefined.
262
+ attachment2 = attachment2 || { id: "", image_data: {} };
263
+ attachment1 = attachment1.mercury ? attachment1.mercury : attachment1;
264
+ var blob = attachment1.blob_attachment;
265
+ var type =
266
+ blob && blob.__typename ? blob.__typename : attachment1.attach_type;
267
+ if (!type && attachment1.sticker_attachment) {
268
+ type = "StickerAttachment";
269
+ blob = attachment1.sticker_attachment;
270
+ }
271
+ else if (!type && attachment1.extensible_attachment) {
272
+ if (attachment1.extensible_attachment.story_attachment && attachment1.extensible_attachment.story_attachment.target && attachment1.extensible_attachment.story_attachment.target.__typename && attachment1.extensible_attachment.story_attachment.target.__typename === "MessageLocation") type = "MessageLocation";
273
+ else type = "ExtensibleAttachment";
274
+
275
+ blob = attachment1.extensible_attachment;
276
+ }
277
+ // TODO: Determine whether "sticker", "photo", "file" etc are still used
278
+ // KEEP IN SYNC WITH getThreadHistory
279
+ switch (type) {
280
+ case "sticker":
281
+ return {
282
+ type: "sticker",
283
+ ID: attachment1.metadata.stickerID.toString(),
284
+ url: attachment1.url,
285
+
286
+ packID: attachment1.metadata.packID.toString(),
287
+ spriteUrl: attachment1.metadata.spriteURI,
288
+ spriteUrl2x: attachment1.metadata.spriteURI2x,
289
+ width: attachment1.metadata.width,
290
+ height: attachment1.metadata.height,
291
+
292
+ caption: attachment2.caption,
293
+ description: attachment2.description,
294
+
295
+ frameCount: attachment1.metadata.frameCount,
296
+ frameRate: attachment1.metadata.frameRate,
297
+ framesPerRow: attachment1.metadata.framesPerRow,
298
+ framesPerCol: attachment1.metadata.framesPerCol,
299
+
300
+ stickerID: attachment1.metadata.stickerID.toString(), // @Legacy
301
+ spriteURI: attachment1.metadata.spriteURI, // @Legacy
302
+ spriteURI2x: attachment1.metadata.spriteURI2x // @Legacy
303
+ };
304
+ case "file":
305
+ return {
306
+ type: "file",
307
+ filename: attachment1.name,
308
+ ID: attachment2.id.toString(),
309
+ url: attachment1.url,
310
+
311
+ isMalicious: attachment2.is_malicious,
312
+ contentType: attachment2.mime_type,
313
+
314
+ name: attachment1.name, // @Legacy
315
+ mimeType: attachment2.mime_type, // @Legacy
316
+ fileSize: attachment2.file_size // @Legacy
317
+ };
318
+ case "photo":
319
+ return {
320
+ type: "photo",
321
+ ID: attachment1.metadata.fbid.toString(),
322
+ filename: attachment1.fileName,
323
+ thumbnailUrl: attachment1.thumbnail_url,
324
+
325
+ previewUrl: attachment1.preview_url,
326
+ previewWidth: attachment1.preview_width,
327
+ previewHeight: attachment1.preview_height,
328
+
329
+ largePreviewUrl: attachment1.large_preview_url,
330
+ largePreviewWidth: attachment1.large_preview_width,
331
+ largePreviewHeight: attachment1.large_preview_height,
332
+
333
+ url: attachment1.metadata.url, // @Legacy
334
+ width: attachment1.metadata.dimensions.split(",")[0], // @Legacy
335
+ height: attachment1.metadata.dimensions.split(",")[1], // @Legacy
336
+ name: attachment1.fileName // @Legacy
337
+ };
338
+ case "animated_image":
339
+ return {
340
+ type: "animated_image",
341
+ ID: attachment2.id.toString(),
342
+ filename: attachment2.filename,
343
+
344
+ previewUrl: attachment1.preview_url,
345
+ previewWidth: attachment1.preview_width,
346
+ previewHeight: attachment1.preview_height,
347
+
348
+ url: attachment2.image_data.url,
349
+ width: attachment2.image_data.width,
350
+ height: attachment2.image_data.height,
351
+
352
+ name: attachment1.name, // @Legacy
353
+ facebookUrl: attachment1.url, // @Legacy
354
+ thumbnailUrl: attachment1.thumbnail_url, // @Legacy
355
+ mimeType: attachment2.mime_type, // @Legacy
356
+ rawGifImage: attachment2.image_data.raw_gif_image, // @Legacy
357
+ rawWebpImage: attachment2.image_data.raw_webp_image, // @Legacy
358
+ animatedGifUrl: attachment2.image_data.animated_gif_url, // @Legacy
359
+ animatedGifPreviewUrl: attachment2.image_data.animated_gif_preview_url, // @Legacy
360
+ animatedWebpUrl: attachment2.image_data.animated_webp_url, // @Legacy
361
+ animatedWebpPreviewUrl: attachment2.image_data.animated_webp_preview_url // @Legacy
362
+ };
363
+ case "share":
364
+ return {
365
+ type: "share",
366
+ ID: attachment1.share.share_id.toString(),
367
+ url: attachment2.href,
368
+
369
+ title: attachment1.share.title,
370
+ description: attachment1.share.description,
371
+ source: attachment1.share.source,
372
+
373
+ image: attachment1.share.media.image,
374
+ width: attachment1.share.media.image_size.width,
375
+ height: attachment1.share.media.image_size.height,
376
+ playable: attachment1.share.media.playable,
377
+ duration: attachment1.share.media.duration,
378
+
379
+ subattachments: attachment1.share.subattachments,
380
+ properties: {},
381
+
382
+ animatedImageSize: attachment1.share.media.animated_image_size, // @Legacy
383
+ facebookUrl: attachment1.share.uri, // @Legacy
384
+ target: attachment1.share.target, // @Legacy
385
+ styleList: attachment1.share.style_list // @Legacy
386
+ };
387
+ case "video":
388
+ return {
389
+ type: "video",
390
+ ID: attachment1.metadata.fbid.toString(),
391
+ filename: attachment1.name,
392
+
393
+ previewUrl: attachment1.preview_url,
394
+ previewWidth: attachment1.preview_width,
395
+ previewHeight: attachment1.preview_height,
396
+
397
+ url: attachment1.url,
398
+ width: attachment1.metadata.dimensions.width,
399
+ height: attachment1.metadata.dimensions.height,
400
+
401
+ duration: attachment1.metadata.duration,
402
+ videoType: "unknown",
403
+
404
+ thumbnailUrl: attachment1.thumbnail_url // @Legacy
405
+ };
406
+ case "error":
407
+ return {
408
+ type: "error",
409
+
410
+ // Save error attachments because we're unsure of their format,
411
+ // and whether there are cases they contain something useful for debugging.
412
+ attachment1: attachment1,
413
+ attachment2: attachment2
414
+ };
415
+ case "MessageImage":
416
+ return {
417
+ type: "photo",
418
+ ID: blob.legacy_attachment_id,
419
+ filename: blob.filename,
420
+ thumbnailUrl: blob.thumbnail.uri,
421
+
422
+ previewUrl: blob.preview.uri,
423
+ previewWidth: blob.preview.width,
424
+ previewHeight: blob.preview.height,
425
+
426
+ largePreviewUrl: blob.large_preview.uri,
427
+ largePreviewWidth: blob.large_preview.width,
428
+ largePreviewHeight: blob.large_preview.height,
429
+
430
+ url: blob.large_preview.uri, // @Legacy
431
+ width: blob.original_dimensions.x, // @Legacy
432
+ height: blob.original_dimensions.y, // @Legacy
433
+ name: blob.filename // @Legacy
434
+ };
435
+ case "MessageAnimatedImage":
436
+ return {
437
+ type: "animated_image",
438
+ ID: blob.legacy_attachment_id,
439
+ filename: blob.filename,
440
+
441
+ previewUrl: blob.preview_image.uri,
442
+ previewWidth: blob.preview_image.width,
443
+ previewHeight: blob.preview_image.height,
444
+
445
+ url: blob.animated_image.uri,
446
+ width: blob.animated_image.width,
447
+ height: blob.animated_image.height,
448
+
449
+ thumbnailUrl: blob.preview_image.uri, // @Legacy
450
+ name: blob.filename, // @Legacy
451
+ facebookUrl: blob.animated_image.uri, // @Legacy
452
+ rawGifImage: blob.animated_image.uri, // @Legacy
453
+ animatedGifUrl: blob.animated_image.uri, // @Legacy
454
+ animatedGifPreviewUrl: blob.preview_image.uri, // @Legacy
455
+ animatedWebpUrl: blob.animated_image.uri, // @Legacy
456
+ animatedWebpPreviewUrl: blob.preview_image.uri // @Legacy
457
+ };
458
+ case "MessageVideo":
459
+ return {
460
+ type: "video",
461
+ filename: blob.filename,
462
+ ID: blob.legacy_attachment_id,
463
+
464
+ previewUrl: blob.large_image.uri,
465
+ previewWidth: blob.large_image.width,
466
+ previewHeight: blob.large_image.height,
467
+
468
+ url: blob.playable_url,
469
+ width: blob.original_dimensions.x,
470
+ height: blob.original_dimensions.y,
471
+
472
+ duration: blob.playable_duration_in_ms,
473
+ videoType: blob.video_type.toLowerCase(),
474
+
475
+ thumbnailUrl: blob.large_image.uri // @Legacy
476
+ };
477
+ case "MessageAudio":
478
+ return {
479
+ type: "audio",
480
+ filename: blob.filename,
481
+ ID: blob.url_shimhash,
482
+
483
+ audioType: blob.audio_type,
484
+ duration: blob.playable_duration_in_ms,
485
+ url: blob.playable_url,
486
+
487
+ isVoiceMail: blob.is_voicemail
488
+ };
489
+ case "StickerAttachment":
490
+ return {
491
+ type: "sticker",
492
+ ID: blob.id,
493
+ url: blob.url,
494
+
495
+ packID: blob.pack ? blob.pack.id : null,
496
+ spriteUrl: blob.sprite_image,
497
+ spriteUrl2x: blob.sprite_image_2x,
498
+ width: blob.width,
499
+ height: blob.height,
500
+
501
+ caption: blob.label,
502
+ description: blob.label,
503
+
504
+ frameCount: blob.frame_count,
505
+ frameRate: blob.frame_rate,
506
+ framesPerRow: blob.frames_per_row,
507
+ framesPerCol: blob.frames_per_column,
508
+
509
+ stickerID: blob.id, // @Legacy
510
+ spriteURI: blob.sprite_image, // @Legacy
511
+ spriteURI2x: blob.sprite_image_2x // @Legacy
512
+ };
513
+ case "MessageLocation":
514
+ var urlAttach = blob.story_attachment.url;
515
+ var mediaAttach = blob.story_attachment.media;
516
+
517
+ var u = querystring.parse(url.parse(urlAttach).query).u;
518
+ var where1 = querystring.parse(url.parse(u).query).where1;
519
+ var address = where1.split(", ");
520
+
521
+ var latitude;
522
+ var longitude;
523
+
524
+ try {
525
+ latitude = Number.parseFloat(address[0]);
526
+ longitude = Number.parseFloat(address[1]);
527
+ }
528
+ catch (err) {
529
+ /* empty */
530
+ }
531
+
532
+ var imageUrl;
533
+ var width;
534
+ var height;
535
+
536
+ if (mediaAttach && mediaAttach.image) {
537
+ imageUrl = mediaAttach.image.uri;
538
+ width = mediaAttach.image.width;
539
+ height = mediaAttach.image.height;
540
+ }
541
+
542
+ return {
543
+ type: "location",
544
+ ID: blob.legacy_attachment_id,
545
+ latitude: latitude,
546
+ longitude: longitude,
547
+ image: imageUrl,
548
+ width: width,
549
+ height: height,
550
+ url: u || urlAttach,
551
+ address: where1,
552
+
553
+ facebookUrl: blob.story_attachment.url, // @Legacy
554
+ target: blob.story_attachment.target, // @Legacy
555
+ styleList: blob.story_attachment.style_list // @Legacy
556
+ };
557
+ case "ExtensibleAttachment":
558
+ return {
559
+ type: "share",
560
+ ID: blob.legacy_attachment_id,
561
+ url: blob.story_attachment.url,
562
+
563
+ title: blob.story_attachment.title_with_entities.text,
564
+ description: blob.story_attachment.description && blob.story_attachment.description.text,
565
+ source: blob.story_attachment.source ? blob.story_attachment.source.text : null,
566
+
567
+ image: blob.story_attachment.media && blob.story_attachment.media.image && blob.story_attachment.media.image.uri,
568
+ width: blob.story_attachment.media && blob.story_attachment.media.image && blob.story_attachment.media.image.width,
569
+ height: blob.story_attachment.media && blob.story_attachment.media.image && blob.story_attachment.media.image.height,
570
+ playable: blob.story_attachment.media && blob.story_attachment.media.is_playable,
571
+ duration: blob.story_attachment.media && blob.story_attachment.media.playable_duration_in_ms,
572
+ playableUrl: blob.story_attachment.media == null ? null : blob.story_attachment.media.playable_url,
573
+
574
+ subattachments: blob.story_attachment.subattachments,
575
+ properties: blob.story_attachment.properties.reduce(function (obj, cur) {
576
+ obj[cur.key] = cur.value.text;
577
+ return obj;
578
+ }, {}),
579
+
580
+ facebookUrl: blob.story_attachment.url, // @Legacy
581
+ target: blob.story_attachment.target, // @Legacy
582
+ styleList: blob.story_attachment.style_list // @Legacy
583
+ };
584
+ case "MessageFile":
585
+ return {
586
+ type: "file",
587
+ filename: blob.filename,
588
+ ID: blob.message_file_fbid,
589
+
590
+ url: blob.url,
591
+ isMalicious: blob.is_malicious,
592
+ contentType: blob.content_type,
593
+
594
+ name: blob.filename,
595
+ mimeType: "",
596
+ fileSize: -1
597
+ };
598
+ default:
599
+ throw new Error(`Unrecognized attach_file of type type\`JSON.stringify(attachment1, null, 4) attachment2: JSON.stringify(attachment2, null, 4)\``);
600
+ }
601
+ }
602
+
603
+ function formatAttachment(attachments, attachmentIds, attachmentMap, shareMap) {
604
+ attachmentMap = shareMap || attachmentMap;
605
+ return attachments ?
606
+ attachments.map(function (val, i) {
607
+ if (!attachmentMap || !attachmentIds || !attachmentMap[attachmentIds[i]]) return _formatAttachment(val);
608
+ return _formatAttachment(val, attachmentMap[attachmentIds[i]]);
609
+ }) : [];
610
+ }
611
+
612
+ function formatDeltaMessage(m) {
613
+ var md = m.delta.messageMetadata;
614
+ var mdata = m.delta.data === undefined ? [] : m.delta.data.prng === undefined ? [] : JSON.parse(m.delta.data.prng);
615
+ var m_id = mdata.map(u => u.i);
616
+ var m_offset = mdata.map(u => u.o);
617
+ var m_length = mdata.map(u => u.l);
618
+ var mentions = {};
619
+ var body = m.delta.body || "";
620
+ var args = body == "" ? [] : body.trim().split(/\s+/);
621
+ for (var i = 0; i < m_id.length; i++) mentions[m_id[i]] = m.delta.body.substring(m_offset[i], m_offset[i] + m_length[i]);
622
+
623
+ return {
624
+ type: "message",
625
+ senderID: formatID(md.actorFbId.toString()),
626
+ threadID: formatID((md.threadKey.threadFbId || md.threadKey.otherUserFbId).toString()),
627
+ messageID: md.messageId,
628
+ args: args,
629
+ body: body,
630
+ attachments: (m.delta.attachments || []).map(v => _formatAttachment(v)),
631
+ mentions: mentions,
632
+ timestamp: md.timestamp,
633
+ isGroup: !!md.threadKey.threadFbId,
634
+ participantIDs: m.delta.participants || (md.cid ? md.cid.canonicalParticipantFbids : []) || []
635
+ };
636
+ }
637
+
638
+ function formatID(id) {
639
+ if (id != undefined && id != null) return id.replace(/(fb)?id[:.]/, "");
640
+ else return id;
641
+ }
642
+
643
+ function formatMessage(m) {
644
+ var originalMessage = m.message ? m.message : m;
645
+ var obj = {
646
+ type: "message",
647
+ senderName: originalMessage.sender_name,
648
+ senderID: formatID(originalMessage.sender_fbid.toString()),
649
+ participantNames: originalMessage.group_thread_info ? originalMessage.group_thread_info.participant_names : [originalMessage.sender_name.split(" ")[0]],
650
+ participantIDs: originalMessage.group_thread_info
651
+ ? originalMessage.group_thread_info.participant_ids.map(function (v) {
652
+ return formatID(v.toString());
653
+ })
654
+ : [formatID(originalMessage.sender_fbid)],
655
+ body: originalMessage.body || "",
656
+ threadID: formatID((originalMessage.thread_fbid || originalMessage.other_user_fbid).toString()),
657
+ threadName: originalMessage.group_thread_info ? originalMessage.group_thread_info.name : originalMessage.sender_name,
658
+ location: originalMessage.coordinates ? originalMessage.coordinates : null,
659
+ messageID: originalMessage.mid ? originalMessage.mid.toString() : originalMessage.message_id,
660
+ attachments: formatAttachment(originalMessage.attachments, originalMessage.attachmentIds, originalMessage.attachment_map, originalMessage.share_map),
661
+ timestamp: originalMessage.timestamp,
662
+ timestampAbsolute: originalMessage.timestamp_absolute,
663
+ timestampRelative: originalMessage.timestamp_relative,
664
+ timestampDatetime: originalMessage.timestamp_datetime,
665
+ tags: originalMessage.tags,
666
+ reactions: originalMessage.reactions ? originalMessage.reactions : [],
667
+ isUnread: originalMessage.is_unread
668
+ };
669
+
670
+ if (m.type === "pages_messaging") obj.pageID = m.realtime_viewer_fbid.toString();
671
+ obj.isGroup = obj.participantIDs.length > 2;
672
+
673
+ return obj;
674
+ }
675
+
676
+ function formatEvent(m) {
677
+ var originalMessage = m.message ? m.message : m;
678
+ var logMessageType = originalMessage.log_message_type;
679
+ var logMessageData;
680
+ if (logMessageType === "log:generic-admin-text") {
681
+ logMessageData = originalMessage.log_message_data.untypedData;
682
+ logMessageType = getAdminTextMessageType(originalMessage.log_message_data.message_type);
683
+ }
684
+ else logMessageData = originalMessage.log_message_data;
685
+
686
+ return Object.assign(formatMessage(originalMessage), {
687
+ type: "event",
688
+ logMessageType: logMessageType,
689
+ logMessageData: logMessageData,
690
+ logMessageBody: originalMessage.log_message_body
691
+ });
692
+ }
693
+
694
+ function formatHistoryMessage(m) {
695
+ switch (m.action_type) {
696
+ case "ma-type:log-message":
697
+ return formatEvent(m);
698
+ default:
699
+ return formatMessage(m);
700
+ }
701
+ }
702
+
703
+ // Get a more readable message type for AdminTextMessages
704
+ function getAdminTextMessageType(m) {
705
+ switch (m.type) {
706
+ case "change_thread_theme":
707
+ return "log:thread-color";
708
+ case "change_thread_icon":
709
+ case "change_thread_quick_reaction":
710
+ return "log:thread-icon";
711
+ case "change_thread_nickname":
712
+ return "log:user-nickname";
713
+ case "change_thread_admins":
714
+ return "log:thread-admins";
715
+ case "group_poll":
716
+ return "log:thread-poll";
717
+ case "change_thread_approval_mode":
718
+ return "log:thread-approval-mode";
719
+ case "messenger_call_log":
720
+ case "participant_joined_group_call":
721
+ return "log:thread-call";
722
+ }
723
+ }
724
+
725
+ function formatDeltaEvent(m) {
726
+ var logMessageType;
727
+ var logMessageData;
728
+
729
+ // log:thread-color => {theme_color}
730
+ // log:user-nickname => {participant_id, nickname}
731
+ // log:thread-icon => {thread_icon}
732
+ // log:thread-name => {name}
733
+ // log:subscribe => {addedParticipants - [Array]}
734
+ // log:unsubscribe => {leftParticipantFbId}
735
+
736
+ switch (m.class) {
737
+ case "AdminTextMessage":
738
+ logMessageType = getAdminTextMessageType(m);
739
+ logMessageData = m.untypedData;
740
+ break;
741
+ case "ThreadName":
742
+ logMessageType = "log:thread-name";
743
+ logMessageData = { name: m.name };
744
+ break;
745
+ case "ParticipantsAddedToGroupThread":
746
+ logMessageType = "log:subscribe";
747
+ logMessageData = { addedParticipants: m.addedParticipants };
748
+ break;
749
+ case "ParticipantLeftGroupThread":
750
+ logMessageType = "log:unsubscribe";
751
+ logMessageData = { leftParticipantFbId: m.leftParticipantFbId };
752
+ break;
753
+ }
754
+
755
+ return {
756
+ type: "event",
757
+ threadID: formatID((m.messageMetadata.threadKey.threadFbId || m.messageMetadata.threadKey.otherUserFbId).toString()),
758
+ logMessageType: logMessageType,
759
+ logMessageData: logMessageData,
760
+ logMessageBody: m.messageMetadata.adminText,
761
+ author: m.messageMetadata.actorFbId,
762
+ participantIDs: (m.participants || []).map(p => p.toString())
763
+ };
764
+ }
765
+
766
+ function formatTyp(event) {
767
+ return {
768
+ isTyping: !!event.st,
769
+ from: event.from.toString(),
770
+ threadID: formatID((event.to || event.thread_fbid || event.from).toString()),
771
+ // When receiving typ indication from mobile, `from_mobile` isn't set.
772
+ // If it is, we just use that value.
773
+ fromMobile: event.hasOwnProperty("from_mobile") ? event.from_mobile : true,
774
+ userID: (event.realtime_viewer_fbid || event.from).toString(),
775
+ type: "typ"
776
+ };
777
+ }
778
+
779
+ function formatDeltaReadReceipt(delta) {
780
+ // otherUserFbId seems to be used as both the readerID and the threadID in a 1-1 chat.
781
+ // In a group chat actorFbId is used for the reader and threadFbId for the thread.
782
+ return {
783
+ reader: (delta.threadKey.otherUserFbId || delta.actorFbId).toString(),
784
+ time: delta.actionTimestampMs,
785
+ threadID: formatID((delta.threadKey.otherUserFbId || delta.threadKey.threadFbId).toString()),
786
+ type: "read_receipt"
787
+ };
788
+ }
789
+
790
+ function formatReadReceipt(event) {
791
+ return {
792
+ reader: event.reader.toString(),
793
+ time: event.time,
794
+ threadID: formatID((event.thread_fbid || event.reader).toString()),
795
+ type: "read_receipt"
796
+ };
797
+ }
798
+
799
+ function formatRead(event) {
800
+ return {
801
+ threadID: formatID(((event.chat_ids && event.chat_ids[0]) || (event.thread_fbids && event.thread_fbids[0])).toString()),
802
+ time: event.timestamp,
803
+ type: "read"
804
+ };
805
+ }
806
+
807
+ function getFrom(str, startToken, endToken) {
808
+ var start = str.indexOf(startToken) + startToken.length;
809
+ if (start < startToken.length) return "";
810
+
811
+ var lastHalf = str.substring(start);
812
+ var end = lastHalf.indexOf(endToken);
813
+ if (end === -1) throw Error("Could not find endTime `" + endToken + "` in the given string.");
814
+ return lastHalf.substring(0, end);
815
+ }
816
+
817
+ function makeParsable(html) {
818
+ let withoutForLoop = html.replace(/for\s*\(\s*;\s*;\s*\)\s*;\s*/, "");
819
+
820
+ // (What the fuck FB, why windows style newlines?)
821
+ // So sometimes FB will send us base multiple objects in the same response.
822
+ // They're all valid JSON, one after the other, at the top level. We detect
823
+ // that and make it parse-able by JSON.parse.
824
+ // Ben - July 15th 2017
825
+ //
826
+ // It turns out that Facebook may insert random number of spaces before
827
+ // next object begins (issue #616)
828
+ // rav_kr - 2018-03-19
829
+ let maybeMultipleObjects = withoutForLoop.split(/\}\r\n *\{/);
830
+ if (maybeMultipleObjects.length === 1) return maybeMultipleObjects;
831
+
832
+ return "[" + maybeMultipleObjects.join("},{") + "]";
833
+ }
834
+
835
+ function arrToForm(form) {
836
+ return arrayToObject(form,
837
+ function (v) {
838
+ return v.name;
839
+ },
840
+ function (v) {
841
+ return v.val;
842
+ }
843
+ );
844
+ }
845
+
846
+ function arrayToObject(arr, getKey, getValue) {
847
+ return arr.reduce(function (acc, val) {
848
+ acc[getKey(val)] = getValue(val);
849
+ return acc;
850
+ }, {});
851
+ }
852
+
853
+ function getSignatureID() {
854
+ return Math.floor(Math.random() * 2147483648).toString(16);
855
+ }
856
+
857
+ function generateTimestampRelative() {
858
+ var d = new Date();
859
+ return d.getHours() + ":" + padZeros(d.getMinutes());
860
+ }
861
+
862
+ function makeDefaults(html, userID, ctx) {
863
+ var reqCounter = 1;
864
+ var fb_dtsg = getFrom(html, 'name="fb_dtsg" value="', '"');
865
+
866
+ // @Hack Ok we've done hacky things, this is definitely on top 5.
867
+ // We totally assume the object is flat and try parsing until a }.
868
+ // If it works though it's cool because we get a bunch of extra data things.
869
+ //
870
+ // Update: we don't need this. Leaving it in in case we ever do.
871
+ // Ben - July 15th 2017
872
+
873
+ // var siteData = getFrom(html, "[\"SiteData\",[],", "},");
874
+ // try {
875
+ // siteData = JSON.parse(siteData + "}");
876
+ // } catch(e) {
877
+ // log.warn("makeDefaults", "Couldn't parse SiteData. Won't have access to some variables.");
878
+ // siteData = {};
879
+ // }
880
+
881
+ var ttstamp = "2";
882
+ for (var i = 0; i < fb_dtsg.length; i++) ttstamp += fb_dtsg.charCodeAt(i);
883
+ var revision = getFrom(html, 'revision":', ",");
884
+
885
+ function mergeWithDefaults(obj) {
886
+ // @TODO This is missing a key called __dyn.
887
+ // After some investigation it seems like __dyn is some sort of set that FB
888
+ // calls BitMap. It seems like certain responses have a "define" key in the
889
+ // res.jsmods arrays. I think the code iterates over those and calls `set`
890
+ // on the bitmap for each of those keys. Then it calls
891
+ // bitmap.toCompressedString() which returns what __dyn is.
892
+ //
893
+ // So far the API has been working without this.
894
+ //
895
+ // Ben - July 15th 2017
896
+ var newObj = {
897
+ __user: userID,
898
+ __req: (reqCounter++).toString(36),
899
+ __rev: revision,
900
+ __a: 1,
901
+ // __af: siteData.features,
902
+ fb_dtsg: ctx.fb_dtsg ? ctx.fb_dtsg : fb_dtsg,
903
+ jazoest: ctx.ttstamp ? ctx.ttstamp : ttstamp
904
+ // __spin_r: siteData.__spin_r,
905
+ // __spin_b: siteData.__spin_b,
906
+ // __spin_t: siteData.__spin_t,
907
+ };
908
+
909
+ // @TODO this is probably not needed.
910
+ // Ben - July 15th 2017
911
+ // if (siteData.be_key) {
912
+ // newObj[siteData.be_key] = siteData.be_mode;
913
+ // }
914
+ // if (siteData.pkg_cohort_key) {
915
+ // newObj[siteData.pkg_cohort_key] = siteData.pkg_cohort;
916
+ // }
917
+
918
+ if (!obj) return newObj;
919
+ for (var prop in obj)
920
+ if (obj.hasOwnProperty(prop))
921
+ if (!newObj[prop]) newObj[prop] = obj[prop];
922
+ return newObj;
923
+ }
924
+
925
+ function postWithDefaults(url, jar, form, ctxx) {
926
+ return post(url, jar, mergeWithDefaults(form), ctx.globalOptions, ctxx || ctx);
927
+ }
928
+
929
+ function getWithDefaults(url, jar, qs, ctxx) {
930
+ return get(url, jar, mergeWithDefaults(qs), ctx.globalOptions, ctxx || ctx);
931
+ }
932
+
933
+ function postFormDataWithDefault(url, jar, form, qs, ctxx) {
934
+ return postFormData(url, jar, mergeWithDefaults(form), mergeWithDefaults(qs), ctx.globalOptions, ctxx || ctx);
935
+ }
936
+
937
+ return {
938
+ get: getWithDefaults,
939
+ post: postWithDefaults,
940
+ postFormData: postFormDataWithDefault
941
+ };
942
+ }
943
+
944
+ function parseAndCheckLogin(ctx, defaultFuncs, retryCount) {
945
+ if (retryCount == undefined) retryCount = 0;
946
+ return function (data) {
947
+ return bluebird.try(function () {
948
+ log.verbose("parseAndCheckLogin", data.body);
949
+ if (data.statusCode >= 500 && data.statusCode < 600) {
950
+ if (retryCount >= 5) {
951
+ throw {
952
+ error: "Request retry failed. Check the `res` and `statusCode` property on this error.",
953
+ statusCode: data.statusCode,
954
+ res: data.body
955
+ };
956
+ }
957
+ retryCount++;
958
+ var retryTime = Math.floor(Math.random() * 5000);
959
+ log.warn("parseAndCheckLogin", "Got status code " + data.statusCode + " - " + retryCount + ". attempt to retry in " + retryTime + " milliseconds...");
960
+ var url = data.request.uri.protocol + "//" + data.request.uri.hostname + data.request.uri.pathname;
961
+ if (data.request.headers["Content-Type"].split(";")[0] === "multipart/form-data") return bluebird.delay(retryTime).then(() => defaultFuncs.postFormData(url, ctx.jar, data.request.formData, {})).then(parseAndCheckLogin(ctx, defaultFuncs, retryCount));
962
+ else return bluebird.delay(retryTime).then(() => defaultFuncs.post(url, ctx.jar, data.request.formData)).then(parseAndCheckLogin(ctx, defaultFuncs, retryCount));
963
+ }
964
+ if (data.statusCode !== 200) throw new Error("parseAndCheckLogin got status code: " + data.statusCode + ". Bailing out of trying to parse response.");
965
+
966
+ var res = null;
967
+ try {
968
+ res = JSON.parse(makeParsable(data.body));
969
+ }
970
+ catch (e) {
971
+ throw {
972
+ error: "JSON.parse error. Check the `detail` property on this error.",
973
+ detail: e,
974
+ res: data.body
975
+ };
976
+ }
977
+
978
+ // In some cases the response contains only a redirect URL which should be followed
979
+ if (res.redirect && data.request.method === "GET") return defaultFuncs.get(res.redirect, ctx.jar).then(parseAndCheckLogin(ctx, defaultFuncs));
980
+
981
+ // TODO: handle multiple cookies?
982
+ if (res.jsmods && res.jsmods.require && Array.isArray(res.jsmods.require[0]) && res.jsmods.require[0][0] === "Cookie") {
983
+ res.jsmods.require[0][3][0] = res.jsmods.require[0][3][0].replace("_js_", "");
984
+ var cookie = formatCookie(res.jsmods.require[0][3], "facebook");
985
+ var cookie2 = formatCookie(res.jsmods.require[0][3], "messenger");
986
+ ctx.jar.setCookie(cookie, "https://www.facebook.com");
987
+ ctx.jar.setCookie(cookie2, "https://www.messenger.com");
988
+ }
989
+
990
+ // On every request we check if we got a DTSG and we mutate the context so that we use the latest
991
+ // one for the next requests.
992
+ if (res.jsmods && Array.isArray(res.jsmods.require)) {
993
+ var arr = res.jsmods.require;
994
+ for (var i in arr) {
995
+ if (arr[i][0] === "DTSG" && arr[i][1] === "setToken") {
996
+ ctx.fb_dtsg = arr[i][3][0];
997
+
998
+ // Update ttstamp since that depends on fb_dtsg
999
+ ctx.ttstamp = "2";
1000
+ for (var j = 0; j < ctx.fb_dtsg.length; j++) ctx.ttstamp += ctx.fb_dtsg.charCodeAt(j);
1001
+ }
1002
+ }
1003
+ }
1004
+
1005
+ if (res.error === 1357001) throw { error: "Not logged in." };
1006
+ return res;
1007
+ });
1008
+ };
1009
+ }
1010
+
1011
+ function saveCookies(jar) {
1012
+ return function (res) {
1013
+ var cookies = res.headers["set-cookie"] || [];
1014
+ cookies.forEach(function (c) {
1015
+ if (c.indexOf(".facebook.com") > -1) jar.setCookie(c, "https://www.facebook.com");
1016
+ var c2 = c.replace(/domain=\.facebook\.com/, "domain=.messenger.com");
1017
+ jar.setCookie(c2, "https://www.messenger.com");
1018
+ });
1019
+ return res;
1020
+ };
1021
+ }
1022
+
1023
+ var NUM_TO_MONTH = [
1024
+ "Jan",
1025
+ "Feb",
1026
+ "Mar",
1027
+ "Apr",
1028
+ "May",
1029
+ "Jun",
1030
+ "Jul",
1031
+ "Aug",
1032
+ "Sep",
1033
+ "Oct",
1034
+ "Nov",
1035
+ "Dec"
1036
+ ];
1037
+ var NUM_TO_DAY = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1038
+
1039
+ function formatDate(date) {
1040
+ var d = date.getUTCDate();
1041
+ d = d >= 10 ? d : `0${d}`;
1042
+ var h = date.getUTCHours();
1043
+ h = h >= 10 ? h : `0${h}`;
1044
+ var m = date.getUTCMinutes();
1045
+ m = m >= 10 ? m : `0${m}`;
1046
+ var s = date.getUTCSeconds();
1047
+ s = s >= 10 ? s : `0${s}`;
1048
+ return `${NUM_TO_DAY[date.getUTCDay()]}, ${d} ${NUM_TO_MONTH[date.getUTCMonth()]} ${date.getUTCFullYear()} ${h}:${m}:${s} GMT`;
1049
+ }
1050
+
1051
+ function formatCookie(arr, url) {
1052
+ return arr[0] + "=" + arr[1] + "; Path=" + arr[3] + "; Domain=" + url + ".com";
1053
+ }
1054
+
1055
+ function formatThread(data) {
1056
+ return {
1057
+ threadID: formatID(data.thread_fbid.toString()),
1058
+ participants: data.participants.map(formatID),
1059
+ participantIDs: data.participants.map(formatID),
1060
+ name: data.name,
1061
+ nicknames: data.custom_nickname,
1062
+ snippet: data.snippet,
1063
+ snippetAttachments: data.snippet_attachments,
1064
+ snippetSender: formatID((data.snippet_sender || "").toString()),
1065
+ unreadCount: data.unread_count,
1066
+ messageCount: data.message_count,
1067
+ imageSrc: data.image_src,
1068
+ timestamp: data.timestamp,
1069
+ serverTimestamp: data.server_timestamp, // what is this?
1070
+ muteUntil: data.mute_until,
1071
+ isCanonicalUser: data.is_canonical_user,
1072
+ isCanonical: data.is_canonical,
1073
+ isSubscribed: data.is_subscribed,
1074
+ folder: data.folder,
1075
+ isArchived: data.is_archived,
1076
+ recipientsLoadable: data.recipients_loadable,
1077
+ hasEmailParticipant: data.has_email_participant,
1078
+ readOnly: data.read_only,
1079
+ canReply: data.can_reply,
1080
+ cannotReplyReason: data.cannot_reply_reason,
1081
+ lastMessageTimestamp: data.last_message_timestamp,
1082
+ lastReadTimestamp: data.last_read_timestamp,
1083
+ lastMessageType: data.last_message_type,
1084
+ emoji: data.custom_like_icon,
1085
+ color: data.custom_color,
1086
+ adminIDs: data.admin_ids,
1087
+ threadType: data.thread_type
1088
+ };
1089
+ }
1090
+
1091
+ function getType(obj) {
1092
+ return Object.prototype.toString.call(obj).slice(8, -1);
1093
+ }
1094
+
1095
+ function formatProxyPresence(presence, userID) {
1096
+ if (presence.lat === undefined || presence.p === undefined) return null;
1097
+ return {
1098
+ type: "presence",
1099
+ timestamp: presence.lat * 1000,
1100
+ userID: userID || '',
1101
+ statuses: presence.p
1102
+ };
1103
+ }
1104
+
1105
+ function formatPresence(presence, userID) {
1106
+ return {
1107
+ type: "presence",
1108
+ timestamp: presence.la * 1000,
1109
+ userID: userID || '',
1110
+ statuses: presence.a
1111
+ };
1112
+ }
1113
+
1114
+ function decodeClientPayload(payload) {
1115
+ /*
1116
+ Special function which Client using to "encode" clients JSON payload
1117
+ */
1118
+ function Utf8ArrayToStr(array) {
1119
+ var out, i, len, c;
1120
+ var char2, char3;
1121
+ out = "";
1122
+ len = array.length;
1123
+ i = 0;
1124
+ while (i < len) {
1125
+ c = array[i++];
1126
+ switch (c >> 4) {
1127
+ case 0:
1128
+ case 1:
1129
+ case 2:
1130
+ case 3:
1131
+ case 4:
1132
+ case 5:
1133
+ case 6:
1134
+ case 7:
1135
+ out += String.fromCharCode(c);
1136
+ break;
1137
+ case 12:
1138
+ case 13:
1139
+ char2 = array[i++];
1140
+ out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
1141
+ break;
1142
+ case 14:
1143
+ char2 = array[i++];
1144
+ char3 = array[i++];
1145
+ out += String.fromCharCode(((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0));
1146
+ break;
1147
+ }
1148
+ }
1149
+ return out;
1150
+ }
1151
+ return JSON.parse(Utf8ArrayToStr(payload));
1152
+ }
1153
+
1154
+ function getAppState(jar) {
1155
+ return jar.getCookies("https://www.facebook.com").concat(jar.getCookies("https://facebook.com")).concat(jar.getCookies("https://www.messenger.com"));
1156
+ }
1157
+ module.exports = {
1158
+ isReadableStream,
1159
+ get,
1160
+ post,
1161
+ postFormData,
1162
+ generateThreadingID,
1163
+ generateOfflineThreadingID,
1164
+ getGUID,
1165
+ getFrom,
1166
+ makeParsable,
1167
+ arrToForm,
1168
+ getSignatureID,
1169
+ getJar: request.jar,
1170
+ generateTimestampRelative,
1171
+ makeDefaults,
1172
+ parseAndCheckLogin,
1173
+ saveCookies,
1174
+ getType,
1175
+ _formatAttachment,
1176
+ formatHistoryMessage,
1177
+ formatID,
1178
+ formatMessage,
1179
+ formatDeltaEvent,
1180
+ formatDeltaMessage,
1181
+ formatProxyPresence,
1182
+ formatPresence,
1183
+ formatTyp,
1184
+ formatDeltaReadReceipt,
1185
+ formatCookie,
1186
+ formatThread,
1187
+ formatReadReceipt,
1188
+ formatRead,
1189
+ generatePresence,
1190
+ generateAccessiblityCookie,
1191
+ formatDate,
1192
+ decodeClientPayload,
1193
+ getAppState,
1194
+ getAdminTextMessageType,
1195
+ setProxy
1196
+ };