alicezetion 1.6.1 → 1.6.3

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