naughty-fb-chatify 1.0.0

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