@cexy/hoonfca 1.0.0 → 1.0.2

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