alicezetion 1.0.3 → 1.0.5

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