alicezetion 1.8.8 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
+