alicezetion 1.0.3 → 1.0.4

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