alicezetion 1.5.7 → 1.5.8

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 +489 -526
  4. package/leiamnash/addExternalModule.js +15 -19
  5. package/leiamnash/addUserToGroup.js +77 -113
  6. package/leiamnash/changeAdminStatus.js +47 -79
  7. package/leiamnash/changeArchivedStatus.js +41 -55
  8. package/leiamnash/changeBio.js +64 -77
  9. package/leiamnash/changeBlockedStatus.js +36 -47
  10. package/leiamnash/changeGroupImage.js +105 -129
  11. package/leiamnash/changeNickname.js +43 -59
  12. package/leiamnash/changeThreadColor.js +61 -71
  13. package/leiamnash/changeThreadEmoji.js +41 -55
  14. package/leiamnash/chat.js +324 -459
  15. package/leiamnash/createNewGroup.js +70 -86
  16. package/leiamnash/createPoll.js +59 -71
  17. package/leiamnash/deleteMessage.js +44 -56
  18. package/leiamnash/deleteThread.js +42 -56
  19. package/leiamnash/forwardAttachment.js +47 -60
  20. package/leiamnash/forwardMessage.js +0 -0
  21. package/leiamnash/getCurrentUserID.js +7 -7
  22. package/leiamnash/getEmojiUrl.js +27 -29
  23. package/leiamnash/getFriendsList.js +73 -84
  24. package/leiamnash/getThreadHistory.js +537 -645
  25. package/leiamnash/getThreadHistoryDeprecated.js +71 -93
  26. package/leiamnash/getThreadInfo.js +171 -206
  27. package/leiamnash/getThreadInfoDeprecated.js +56 -80
  28. package/leiamnash/getThreadList.js +213 -238
  29. package/leiamnash/getThreadListDeprecated.js +46 -75
  30. package/leiamnash/getThreadPictures.js +59 -79
  31. package/leiamnash/getUserID.js +61 -66
  32. package/leiamnash/getUserInfo.js +66 -72
  33. package/leiamnash/handleFriendRequest.js +46 -61
  34. package/leiamnash/handleMessageRequest.js +47 -65
  35. package/leiamnash/httpGet.js +47 -52
  36. package/leiamnash/httpPost.js +47 -52
  37. package/leiamnash/listen.js +553 -0
  38. package/leiamnash/listenMqtt-Test.js +687 -0
  39. package/leiamnash/listenMqtt.js +685 -789
  40. package/leiamnash/logout.js +68 -75
  41. package/leiamnash/markAsDelivered.js +47 -58
  42. package/leiamnash/markAsRead.js +70 -80
  43. package/leiamnash/markAsReadAll.js +39 -49
  44. package/leiamnash/markAsSeen.js +48 -59
  45. package/leiamnash/muteThread.js +45 -52
  46. package/leiamnash/removeUserFromGroup.js +45 -79
  47. package/leiamnash/resolvePhotoUrl.js +36 -45
  48. package/leiamnash/searchForThread.js +42 -53
  49. package/leiamnash/sendTypingIndicator.js +70 -103
  50. package/leiamnash/setMessageReaction.js +103 -117
  51. package/leiamnash/setPostReaction.js +63 -76
  52. package/leiamnash/setTitle.js +70 -86
  53. package/leiamnash/threadColors.js +41 -57
  54. package/leiamnash/unfriend.js +42 -52
  55. package/leiamnash/unsendMessage.js +39 -49
  56. package/package.json +73 -71
  57. package/utils.js +1198 -1356
package/utils.js CHANGED
@@ -1,1356 +1,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
-
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
+ };