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