@dongdev/fca-unofficial 0.0.5 → 0.0.6
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/index.js +142 -102
- package/package.json +4 -3
- package/src/getThreadList.js +175 -224
- package/src/listenMqtt.js +230 -138
- package/utils.js +71 -40
package/index.js
CHANGED
@@ -2,119 +2,126 @@
|
|
2
2
|
|
3
3
|
const utils = require("./utils");
|
4
4
|
const log = require("npmlog");
|
5
|
-
|
5
|
+
|
6
6
|
let checkVerified = null;
|
7
|
+
|
7
8
|
const defaultLogRecordSize = 100;
|
8
9
|
log.maxRecordSize = defaultLogRecordSize;
|
9
|
-
|
10
|
-
function ensureConfigFile() {
|
11
|
-
if (!fs.existsSync('./fca_config.json')) {
|
12
|
-
const defaultConfig = {
|
13
|
-
autologin: {
|
14
|
-
enable: false,
|
15
|
-
email: "",
|
16
|
-
password: "",
|
17
|
-
key2fa: ""
|
18
|
-
},
|
19
|
-
autoRestartMQTT: 0
|
20
|
-
};
|
21
|
-
fs.writeFileSync('./fca_config.json', JSON.stringify(defaultConfig, null, 2));
|
22
|
-
}
|
23
|
-
}
|
24
|
-
ensureConfigFile();
|
25
|
-
global.fca.config = require(process.cwd()+'/fca_config.json');
|
26
|
-
const Boolean_Option = ['online', 'selfListen', 'listenEvents', 'updatePresence', 'forceLogin', 'autoMarkDelivery', 'autoMarkRead', 'listenTyping', 'autoReconnect', 'emitReady'];
|
10
|
+
|
27
11
|
function setOptions(globalOptions, options) {
|
28
12
|
Object.keys(options).map(function (key) {
|
29
|
-
switch (
|
30
|
-
case
|
31
|
-
globalOptions
|
13
|
+
switch (key) {
|
14
|
+
case 'online':
|
15
|
+
globalOptions.online = Boolean(options.online);
|
32
16
|
break;
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
17
|
+
case 'logLevel':
|
18
|
+
log.level = options.logLevel;
|
19
|
+
globalOptions.logLevel = options.logLevel;
|
20
|
+
break;
|
21
|
+
case 'logRecordSize':
|
22
|
+
log.maxRecordSize = options.logRecordSize;
|
23
|
+
globalOptions.logRecordSize = options.logRecordSize;
|
24
|
+
break;
|
25
|
+
case 'selfListen':
|
26
|
+
globalOptions.selfListen = Boolean(options.selfListen);
|
27
|
+
break;
|
28
|
+
case 'selfListenEvent':
|
29
|
+
globalOptions.selfListenEvent = options.selfListenEvent;
|
30
|
+
break;
|
31
|
+
case 'listenEvents':
|
32
|
+
globalOptions.listenEvents = Boolean(options.listenEvents);
|
33
|
+
break;
|
34
|
+
case 'pageID':
|
35
|
+
globalOptions.pageID = options.pageID.toString();
|
36
|
+
break;
|
37
|
+
case 'updatePresence':
|
38
|
+
globalOptions.updatePresence = Boolean(options.updatePresence);
|
39
|
+
break;
|
40
|
+
case 'forceLogin':
|
41
|
+
globalOptions.forceLogin = Boolean(options.forceLogin);
|
42
|
+
break;
|
43
|
+
case 'userAgent':
|
44
|
+
globalOptions.userAgent = options.userAgent;
|
45
|
+
break;
|
46
|
+
case 'autoMarkDelivery':
|
47
|
+
globalOptions.autoMarkDelivery = Boolean(options.autoMarkDelivery);
|
48
|
+
break;
|
49
|
+
case 'autoMarkRead':
|
50
|
+
globalOptions.autoMarkRead = Boolean(options.autoMarkRead);
|
51
|
+
break;
|
52
|
+
case 'listenTyping':
|
53
|
+
globalOptions.listenTyping = Boolean(options.listenTyping);
|
54
|
+
break;
|
55
|
+
case 'proxy':
|
56
|
+
if (typeof options.proxy != "string") {
|
57
|
+
delete globalOptions.proxy;
|
58
|
+
utils.setProxy();
|
59
|
+
} else {
|
60
|
+
globalOptions.proxy = options.proxy;
|
61
|
+
utils.setProxy(globalOptions.proxy);
|
73
62
|
}
|
74
63
|
break;
|
75
|
-
|
64
|
+
case 'autoReconnect':
|
65
|
+
globalOptions.autoReconnect = Boolean(options.autoReconnect);
|
66
|
+
break;
|
67
|
+
case 'emitReady':
|
68
|
+
globalOptions.emitReady = Boolean(options.emitReady);
|
69
|
+
break;
|
70
|
+
default:
|
71
|
+
log.warn("setOptions", "Unrecognized option given to setOptions: " + key);
|
72
|
+
break;
|
76
73
|
}
|
77
74
|
});
|
78
75
|
}
|
79
76
|
|
80
77
|
function buildAPI(globalOptions, html, jar) {
|
81
|
-
const
|
82
|
-
|
83
|
-
|
84
|
-
|
78
|
+
const maybeCookie = jar.getCookies("https://www.facebook.com").filter(function (val) {
|
79
|
+
return val.cookieString().split("=")[0] === "c_user";
|
80
|
+
});
|
81
|
+
|
82
|
+
const objCookie = jar.getCookies("https://www.facebook.com").reduce(function (obj, val) {
|
83
|
+
obj[val.cookieString().split("=")[0]] = val.cookieString().split("=")[1];
|
85
84
|
return obj;
|
86
85
|
}, {});
|
87
|
-
|
88
|
-
if (
|
86
|
+
|
87
|
+
if (maybeCookie.length === 0) {
|
89
88
|
throw { error: "Error retrieving userID. This can be caused by a lot of things, including getting blocked by Facebook for logging in from an unknown location. Try logging in with a browser to verify." };
|
90
89
|
}
|
91
|
-
|
90
|
+
|
91
|
+
if (html.indexOf("/checkpoint/block/?next") > -1) {
|
92
92
|
log.warn("login", "Checkpoint detected. Please log in with a browser to verify.");
|
93
93
|
}
|
94
|
-
|
94
|
+
|
95
|
+
const userID = maybeCookie[0].cookieString().split("=")[1].toString();
|
95
96
|
const i_userID = objCookie.i_user || null;
|
96
97
|
log.info("login", `Logged in as ${userID}`);
|
97
|
-
|
98
|
-
const clientID = (Math.random() * 2147483648 | 0).toString(16);
|
99
|
-
let mqttEndpoint, region, fb_dtsg;
|
98
|
+
|
100
99
|
try {
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
100
|
+
clearInterval(checkVerified);
|
101
|
+
} catch (_) { }
|
102
|
+
|
103
|
+
const clientID = (Math.random() * 2147483648 | 0).toString(16);
|
104
|
+
|
105
|
+
|
106
|
+
const oldFBMQTTMatch = html.match(/irisSeqID:"(.+?)",appID:219994525426954,endpoint:"(.+?)"/);
|
107
|
+
let mqttEndpoint, region, fb_dtsg, irisSeqID;
|
108
|
+
try {
|
109
|
+
const endpointMatch = html.match(/"endpoint":"([^"]+)"/);
|
110
|
+
if (endpointMatch) {
|
111
|
+
mqttEndpoint = endpointMatch[1].replace(/\\\//g, '/');
|
112
|
+
const url = new URL(mqttEndpoint);
|
113
|
+
region = url.searchParams.get('region')?.toUpperCase() || "PRN";
|
114
|
+
}
|
115
|
+
log.info('login', `Sever region ${region}`);
|
116
|
+
} catch (e) {
|
117
|
+
log.warning('login', 'Not MQTT endpoint');
|
118
|
+
}
|
119
|
+
const tokenMatch = html.match(/DTSGInitialData.*?token":"(.*?)"/);
|
120
|
+
if (tokenMatch) {
|
121
|
+
fb_dtsg = tokenMatch[1];
|
122
|
+
}
|
123
|
+
|
124
|
+
// All data available to api functions
|
118
125
|
const ctx = {
|
119
126
|
userID: userID,
|
120
127
|
i_userID: i_userID,
|
@@ -125,41 +132,53 @@ function buildAPI(globalOptions, html, jar) {
|
|
125
132
|
access_token: 'NONE',
|
126
133
|
clientMutationId: 0,
|
127
134
|
mqttClient: undefined,
|
128
|
-
lastSeqId:
|
135
|
+
lastSeqId: irisSeqID,
|
129
136
|
syncToken: undefined,
|
130
137
|
mqttEndpoint,
|
131
138
|
region,
|
132
139
|
firstListen: true,
|
133
140
|
fb_dtsg
|
134
141
|
};
|
142
|
+
|
135
143
|
const api = {
|
136
144
|
setOptions: setOptions.bind(null, globalOptions),
|
137
145
|
getAppState: function getAppState() {
|
138
146
|
const appState = utils.getAppState(jar);
|
147
|
+
// filter duplicate
|
139
148
|
return appState.filter((item, index, self) => self.findIndex((t) => { return t.key === item.key }) === index);
|
140
149
|
}
|
141
150
|
};
|
151
|
+
|
142
152
|
const defaultFuncs = utils.makeDefaults(html, i_userID || userID, ctx);
|
143
|
-
fs.readdirSync(__dirname + '/src/').filter((v) => v.endsWith('.js')).map(function (v) {
|
144
|
-
|
145
|
-
|
153
|
+
require('fs').readdirSync(__dirname + '/src/').filter((v) => v.endsWith('.js')).map(function (v) {
|
154
|
+
api[v.replace('.js', '')] = require('./src/' + v)(defaultFuncs, api, ctx);
|
155
|
+
});
|
146
156
|
api.listen = api.listenMqtt;
|
157
|
+
|
147
158
|
return [ctx, defaultFuncs, api];
|
148
159
|
}
|
149
160
|
|
161
|
+
// Helps the login
|
150
162
|
function loginHelper(appState, email, password, globalOptions, callback, prCallback) {
|
151
|
-
let mainPromise
|
163
|
+
let mainPromise = null;
|
164
|
+
const jar = utils.getJar();
|
165
|
+
|
166
|
+
// If we're given an appState we loop through it and save each cookie
|
167
|
+
// back into the jar.
|
152
168
|
if (appState) {
|
169
|
+
// check and convert cookie to appState
|
153
170
|
if (utils.getType(appState) === 'Array' && appState.some(c => c.name)) {
|
154
171
|
appState = appState.map(c => {
|
155
172
|
c.key = c.name;
|
156
173
|
delete c.name;
|
157
174
|
return c;
|
158
175
|
})
|
159
|
-
}
|
176
|
+
}
|
177
|
+
else if (utils.getType(appState) === 'String') {
|
160
178
|
const arrayAppState = [];
|
161
179
|
appState.split(';').forEach(c => {
|
162
180
|
const [key, value] = c.split('=');
|
181
|
+
|
163
182
|
arrayAppState.push({
|
164
183
|
key: (key || "").trim(),
|
165
184
|
value: (value || "").trim(),
|
@@ -170,10 +189,13 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
|
|
170
189
|
});
|
171
190
|
appState = arrayAppState;
|
172
191
|
}
|
192
|
+
|
173
193
|
appState.map(function (c) {
|
174
194
|
const str = c.key + "=" + c.value + "; expires=" + c.expires + "; domain=" + c.domain + "; path=" + c.path + ";";
|
175
195
|
jar.setCookie(str, "http://" + c.domain);
|
176
196
|
});
|
197
|
+
|
198
|
+
// Load the main page.
|
177
199
|
mainPromise = utils
|
178
200
|
.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true })
|
179
201
|
.then(utils.saveCookies(jar));
|
@@ -185,13 +207,20 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
|
|
185
207
|
throw { error: "No appState given." };
|
186
208
|
}
|
187
209
|
}
|
188
|
-
|
210
|
+
|
211
|
+
let ctx = null;
|
212
|
+
let _defaultFuncs = null;
|
213
|
+
let api = null;
|
214
|
+
|
189
215
|
mainPromise = mainPromise
|
190
216
|
.then(function (res) {
|
217
|
+
// Hacky check for the redirection that happens on some ISPs, which doesn't return statusCode 3xx
|
191
218
|
const reg = /<meta http-equiv="refresh" content="0;url=([^"]+)[^>]+>/;
|
192
219
|
const redirect = reg.exec(res.body);
|
193
220
|
if (redirect && redirect[1]) {
|
194
|
-
return utils
|
221
|
+
return utils
|
222
|
+
.get(redirect[1], jar, null, globalOptions)
|
223
|
+
.then(utils.saveCookies(jar));
|
195
224
|
}
|
196
225
|
return res;
|
197
226
|
})
|
@@ -203,17 +232,24 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
|
|
203
232
|
api = stuff[2];
|
204
233
|
return res;
|
205
234
|
});
|
235
|
+
|
236
|
+
// given a pageID we log in as a page
|
206
237
|
if (globalOptions.pageID) {
|
207
238
|
mainPromise = mainPromise
|
208
239
|
.then(function () {
|
209
|
-
return utils
|
240
|
+
return utils
|
241
|
+
.get('https://www.facebook.com/' + ctx.globalOptions.pageID + '/messages/?section=messages&subsection=inbox', ctx.jar, null, globalOptions);
|
210
242
|
})
|
211
243
|
.then(function (resData) {
|
212
244
|
let url = utils.getFrom(resData.body, 'window.location.replace("https:\\/\\/www.facebook.com\\', '");').split('\\').join('');
|
213
245
|
url = url.substring(0, url.length - 1);
|
214
|
-
|
246
|
+
|
247
|
+
return utils
|
248
|
+
.get('https://www.facebook.com' + url, ctx.jar, null, globalOptions);
|
215
249
|
});
|
216
250
|
}
|
251
|
+
|
252
|
+
// At the end we call the callback or catch an exception
|
217
253
|
mainPromise
|
218
254
|
.then(function () {
|
219
255
|
log.info("login", 'Done logging in.');
|
@@ -224,11 +260,13 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
|
|
224
260
|
callback(e);
|
225
261
|
});
|
226
262
|
}
|
263
|
+
|
227
264
|
function login(loginData, options, callback) {
|
228
265
|
if (utils.getType(options) === 'Function' || utils.getType(options) === 'AsyncFunction') {
|
229
266
|
callback = options;
|
230
267
|
options = {};
|
231
268
|
}
|
269
|
+
|
232
270
|
const globalOptions = {
|
233
271
|
selfListen: false,
|
234
272
|
selfListenEvent: false,
|
@@ -240,11 +278,13 @@ function login(loginData, options, callback) {
|
|
240
278
|
autoMarkRead: false,
|
241
279
|
autoReconnect: true,
|
242
280
|
logRecordSize: defaultLogRecordSize,
|
243
|
-
online:
|
281
|
+
online: true,
|
244
282
|
emitReady: false,
|
245
283
|
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
|
246
284
|
};
|
285
|
+
|
247
286
|
setOptions(globalOptions, options);
|
287
|
+
|
248
288
|
let prCallback = null;
|
249
289
|
if (utils.getType(callback) !== "Function" && utils.getType(callback) !== "AsyncFunction") {
|
250
290
|
let rejectFunc = null;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@dongdev/fca-unofficial",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.6",
|
4
4
|
"description": "A Facebook chat API without XMPP, will not be deprecated after April 30th, 2015.",
|
5
5
|
"main": "index.js",
|
6
6
|
"repository": {
|
@@ -12,11 +12,12 @@
|
|
12
12
|
"dependencies": {
|
13
13
|
"bluebird": "^2.11.0",
|
14
14
|
"cheerio": "^1.0.0-rc.10",
|
15
|
+
"duplexify": "^4.1.3",
|
15
16
|
"https-proxy-agent": "^4.0.0",
|
16
17
|
"mqtt": "^4.3.7",
|
17
18
|
"npmlog": "^1.2.0",
|
18
19
|
"request": "^2.53.0",
|
19
|
-
"
|
20
|
+
"ws": "^8.18.1"
|
20
21
|
},
|
21
22
|
"devDependencies": {
|
22
23
|
"eslint": "^7.5.0",
|
@@ -45,4 +46,4 @@
|
|
45
46
|
"directories": {
|
46
47
|
"test": "test"
|
47
48
|
}
|
48
|
-
}
|
49
|
+
}
|