nayan-remake-api 0.0.1-security → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nayan-remake-api might be problematic. Click here for more details.

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