alicezetion 1.8.8 → 1.9.0

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