alicezetion 1.7.7 → 1.7.9

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