alicezetion 1.7.7 → 1.7.9

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