@dongdev/fca-unofficial 0.0.5 → 0.0.7

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 CHANGED
@@ -2,267 +2,357 @@
2
2
 
3
3
  const utils = require("./utils");
4
4
  const log = require("npmlog");
5
- const fs = require('node:fs');
6
5
  let checkVerified = null;
7
6
  const defaultLogRecordSize = 100;
8
7
  log.maxRecordSize = defaultLogRecordSize;
9
- global.fca = {};
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'];
8
+ const Boolean_Option = [
9
+ "online",
10
+ "selfListen",
11
+ "listenEvents",
12
+ "updatePresence",
13
+ "forceLogin",
14
+ "autoMarkDelivery",
15
+ "autoMarkRead",
16
+ "listenTyping",
17
+ "autoReconnect",
18
+ "emitReady",
19
+ ];
27
20
  function setOptions(globalOptions, options) {
28
- Object.keys(options).map(function (key) {
29
- switch (Boolean_Option.includes(key)) {
30
- case true: {
31
- globalOptions[key] = Boolean(options[key]);
32
- break;
33
- }
34
- case false: {
35
- switch (key) {
36
- case 'pauseLog': {
37
- if (options.pauseLog) log.pause();
38
- else log.resume();
39
- break;
40
- }
41
- case 'logLevel': {
42
- log.level = options.logLevel;
43
- globalOptions.logLevel = options.logLevel;
44
- break;
45
- }
46
- case 'logRecordSize': {
47
- log.maxRecordSize = options.logRecordSize;
48
- globalOptions.logRecordSize = options.logRecordSize;
49
- break;
50
- }
51
- case 'pageID': {
52
- globalOptions.pageID = options.pageID.toString();
53
- break;
54
- }
55
- case 'userAgent': {
56
- globalOptions.userAgent = (options.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36');
57
- break;
58
- }
59
- case 'proxy': {
60
- if (typeof options.proxy != "string") {
61
- delete globalOptions.proxy;
62
- utils.setProxy();
63
- } else {
64
- globalOptions.proxy = options.proxy;
65
- utils.setProxy(globalOptions.proxy);
66
- }
67
- break;
68
- }
69
- default: {
70
- log.warn("setOptions", "Unrecognized option given to setOptions: " + key);
71
- break;
72
- }
73
- }
74
- break;
75
- }
76
- }
77
- });
21
+ Object.keys(options).map(function(key) {
22
+ switch (Boolean_Option.includes(key)) {
23
+ case true: {
24
+ globalOptions[key] = Boolean(options[key]);
25
+ break;
26
+ }
27
+ case false: {
28
+ switch (key) {
29
+ case "pauseLog": {
30
+ if (options.pauseLog) log.pause();
31
+ else log.resume();
32
+ break;
33
+ }
34
+ case "logLevel": {
35
+ log.level = options.logLevel;
36
+ globalOptions.logLevel = options.logLevel;
37
+ break;
38
+ }
39
+ case "logRecordSize": {
40
+ log.maxRecordSize = options.logRecordSize;
41
+ globalOptions.logRecordSize = options.logRecordSize;
42
+ break;
43
+ }
44
+ case "pageID": {
45
+ globalOptions.pageID = options.pageID.toString();
46
+ break;
47
+ }
48
+ case "userAgent": {
49
+ globalOptions.userAgent =
50
+ options.userAgent ||
51
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
52
+ break;
53
+ }
54
+ case "proxy": {
55
+ if (typeof options.proxy != "string") {
56
+ delete globalOptions.proxy;
57
+ utils.setProxy();
58
+ } else {
59
+ globalOptions.proxy = options.proxy;
60
+ utils.setProxy(globalOptions.proxy);
61
+ }
62
+ break;
63
+ }
64
+ default: {
65
+ log.warn(
66
+ "setOptions",
67
+ "Unrecognized option given to setOptions: " + key
68
+ );
69
+ break;
70
+ }
71
+ }
72
+ break;
73
+ }
74
+ }
75
+ });
78
76
  }
79
-
80
77
  function buildAPI(globalOptions, html, jar) {
81
- const cookies = jar.getCookies("https://www.facebook.com");
82
- const objCookie = cookies.reduce((obj, val) => {
83
- const [key, value] = val.cookieString().split("=");
84
- obj[key] = value;
85
- return obj;
86
- }, {});
87
- const maybeCookie = cookies.find(val => val.cookieString().startsWith("c_user="));
88
- if (!maybeCookie) {
89
- 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
- }
91
- if (html.includes("/checkpoint/block/?next")) {
92
- log.warn("login", "Checkpoint detected. Please log in with a browser to verify.");
93
- }
94
- const userID = maybeCookie.cookieString().split("=")[1];
95
- const i_userID = objCookie.i_user || null;
96
- log.info("login", `Logged in as ${userID}`);
97
- try { clearInterval(checkVerified) } catch (_) { }
98
- const clientID = (Math.random() * 2147483648 | 0).toString(16);
99
- let mqttEndpoint, region, fb_dtsg;
100
- try {
101
- const endpointMatch = html.match(/"endpoint":"([^"]+)"/);
102
- if (endpointMatch) {
103
- mqttEndpoint = endpointMatch[1].replace(/\\\//g, '/');
104
- const url = new URL(mqttEndpoint);
105
- region = url.searchParams.get('region')?.toUpperCase() || "PRN";
106
- }
107
- log.info('login', `MQTT region ${region}`);
108
- } catch (e) {
109
- log.warning('login', 'Not MQTT endpoint');
110
- }
111
- const tokenMatch = html.match(/DTSGInitialData.*?token":"(.*?)"/);
112
- if (tokenMatch) {
113
- fb_dtsg = tokenMatch[1];
114
- if (fb_dtsg) {
115
- log.info(`login`, `Đã tìm thấy fb_dtsg`);
116
- }
117
- }
118
- const ctx = {
119
- userID: userID,
120
- i_userID: i_userID,
121
- jar: jar,
122
- clientID: clientID,
123
- globalOptions: globalOptions,
124
- loggedIn: true,
125
- access_token: 'NONE',
126
- clientMutationId: 0,
127
- mqttClient: undefined,
128
- lastSeqId: undefined,
129
- syncToken: undefined,
130
- mqttEndpoint,
131
- region,
132
- firstListen: true,
133
- fb_dtsg
134
- };
135
- const api = {
136
- setOptions: setOptions.bind(null, globalOptions),
137
- getAppState: function getAppState() {
138
- const appState = utils.getAppState(jar);
139
- return appState.filter((item, index, self) => self.findIndex((t) => { return t.key === item.key }) === index);
140
- }
141
- };
142
- const defaultFuncs = utils.makeDefaults(html, i_userID || userID, ctx);
143
- fs.readdirSync(__dirname + '/src/').filter((v) => v.endsWith('.js')).map(function (v) {
144
- api[v.replace('.js', '')] = require('./src/' + v)(defaultFuncs, api, ctx);
145
- });
146
- api.listen = api.listenMqtt;
147
- return [ctx, defaultFuncs, api];
78
+ const maybeCookie = jar
79
+ .getCookies("https://www.facebook.com")
80
+ .filter(function(val) {
81
+ return val.cookieString().split("=")[0] === "c_user";
82
+ });
83
+ const objCookie = jar
84
+ .getCookies("https://www.facebook.com")
85
+ .reduce(function(obj, val) {
86
+ obj[val.cookieString().split("=")[0]] = val.cookieString().split("=")[1];
87
+ return obj;
88
+ }, {});
89
+ if (maybeCookie.length === 0) {
90
+ throw {
91
+ error:
92
+ "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.",
93
+ };
94
+ }
95
+ if (html.indexOf("/checkpoint/block/?next") > -1) {
96
+ log.warn(
97
+ "login",
98
+ "Checkpoint detected. Please log in with a browser to verify."
99
+ );
100
+ }
101
+ const userID = maybeCookie[0]
102
+ .cookieString()
103
+ .split("=")[1]
104
+ .toString();
105
+ const i_userID = objCookie.i_user || null;
106
+ log.info("login", `Logged in as ${userID}`);
107
+ try {
108
+ clearInterval(checkVerified);
109
+ } catch (_) {}
110
+ const clientID = ((Math.random() * 2147483648) | 0).toString(16);
111
+ let mqttEndpoint, region, fb_dtsg, irisSeqID;
112
+ try {
113
+ const endpointMatch = html.match(/"endpoint":"([^"]+)"/);
114
+ if (endpointMatch) {
115
+ mqttEndpoint = endpointMatch[1].replace(/\\\//g, "/");
116
+ const url = new URL(mqttEndpoint);
117
+ region = url.searchParams.get("region")?.toUpperCase() || "PRN";
118
+ }
119
+ log.info("login", `Sever region ${region}`);
120
+ } catch (e) {
121
+ log.warning("login", "Not MQTT endpoint");
122
+ }
123
+ const tokenMatch = html.match(/DTSGInitialData.*?token":"(.*?)"/);
124
+ if (tokenMatch) {
125
+ fb_dtsg = tokenMatch[1];
126
+ }
127
+ const ctx = {
128
+ userID: userID,
129
+ i_userID: i_userID,
130
+ jar: jar,
131
+ clientID: clientID,
132
+ globalOptions: globalOptions,
133
+ loggedIn: true,
134
+ access_token: "NONE",
135
+ clientMutationId: 0,
136
+ mqttClient: undefined,
137
+ lastSeqId: irisSeqID,
138
+ syncToken: undefined,
139
+ mqttEndpoint,
140
+ region,
141
+ firstListen: true,
142
+ fb_dtsg,
143
+ };
144
+ const api = {
145
+ setOptions: setOptions.bind(null, globalOptions),
146
+ getAppState: function getAppState() {
147
+ const appState = utils.getAppState(jar);
148
+ return appState.filter(
149
+ (item, index, self) =>
150
+ self.findIndex((t) => {
151
+ return t.key === item.key;
152
+ }) === index
153
+ );
154
+ },
155
+ };
156
+ const defaultFuncs = utils.makeDefaults(html, i_userID || userID, ctx);
157
+ require("fs")
158
+ .readdirSync(__dirname + "/src/")
159
+ .filter((v) => v.endsWith(".js"))
160
+ .map(function(v) {
161
+ api[v.replace(".js", "")] = require("./src/" + v)(defaultFuncs, api, ctx);
162
+ });
163
+ api.listen = api.listenMqtt;
164
+ setInterval(async () => {
165
+ api
166
+ .refreshFb_dtsg()
167
+ .then(() => {
168
+ console.log("Successfully refreshed fb_dtsg");
169
+ })
170
+ .catch((err) => {
171
+ console.error("An error occurred while refreshing fb_dtsg", err);
172
+ });
173
+ }, 1000 * 60 * 60 * 48);
174
+ return [ctx, defaultFuncs, api];
148
175
  }
149
-
150
- function loginHelper(appState, email, password, globalOptions, callback, prCallback) {
151
- let mainPromise, jar = utils.getJar();
152
- if (appState) {
153
- if (utils.getType(appState) === 'Array' && appState.some(c => c.name)) {
154
- appState = appState.map(c => {
155
- c.key = c.name;
156
- delete c.name;
157
- return c;
158
- })
159
- } else if (utils.getType(appState) === 'String') {
160
- const arrayAppState = [];
161
- appState.split(';').forEach(c => {
162
- const [key, value] = c.split('=');
163
- arrayAppState.push({
164
- key: (key || "").trim(),
165
- value: (value || "").trim(),
166
- domain: "facebook.com",
167
- path: "/",
168
- expires: new Date().getTime() + 1000 * 60 * 60 * 24 * 365
169
- });
170
- });
171
- appState = arrayAppState;
172
- }
173
- appState.map(function (c) {
174
- const str = c.key + "=" + c.value + "; expires=" + c.expires + "; domain=" + c.domain + "; path=" + c.path + ";";
175
- jar.setCookie(str, "http://" + c.domain);
176
- });
177
- mainPromise = utils
178
- .get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true })
179
- .then(utils.saveCookies(jar));
180
- } else {
181
- if (email) {
182
- throw { error: "Currently, the login method by email and password is no longer supported, please use the login method by appState" };
183
- }
184
- else {
185
- throw { error: "No appState given." };
186
- }
187
- }
188
- let ctx, _defaultFuncs, api;
189
- mainPromise = mainPromise
190
- .then(function (res) {
191
- const reg = /<meta http-equiv="refresh" content="0;url=([^"]+)[^>]+>/;
192
- const redirect = reg.exec(res.body);
193
- if (redirect && redirect[1]) {
194
- return utils.get(redirect[1], jar, null, globalOptions).then(utils.saveCookies(jar));
195
- }
196
- return res;
197
- })
198
- .then(function (res) {
199
- const html = res.body;
200
- const stuff = buildAPI(globalOptions, html, jar);
201
- ctx = stuff[0];
202
- _defaultFuncs = stuff[1];
203
- api = stuff[2];
204
- return res;
205
- });
206
- if (globalOptions.pageID) {
207
- mainPromise = mainPromise
208
- .then(function () {
209
- return utils.get('https://www.facebook.com/' + ctx.globalOptions.pageID + '/messages/?section=messages&subsection=inbox', ctx.jar, null, globalOptions);
210
- })
211
- .then(function (resData) {
212
- let url = utils.getFrom(resData.body, 'window.location.replace("https:\\/\\/www.facebook.com\\', '");').split('\\').join('');
213
- url = url.substring(0, url.length - 1);
214
- return utils.get('https://www.facebook.com' + url, ctx.jar, null, globalOptions);
215
- });
216
- }
217
- mainPromise
218
- .then(function () {
219
- log.info("login", 'Done logging in.');
220
- return callback(null, api);
221
- })
222
- .catch(function (e) {
223
- log.error("login", e.error || e);
224
- callback(e);
225
- });
176
+ function loginHelper(
177
+ appState,
178
+ email,
179
+ password,
180
+ globalOptions,
181
+ callback,
182
+ prCallback
183
+ ) {
184
+ let mainPromise = null;
185
+ const jar = utils.getJar();
186
+ if (appState) {
187
+ if (utils.getType(appState) === "Array" && appState.some((c) => c.name)) {
188
+ appState = appState.map((c) => {
189
+ c.key = c.name;
190
+ delete c.name;
191
+ return c;
192
+ });
193
+ } else if (utils.getType(appState) === "String") {
194
+ const arrayAppState = [];
195
+ appState.split(";").forEach((c) => {
196
+ const [key, value] = c.split("=");
197
+ arrayAppState.push({
198
+ key: (key || "").trim(),
199
+ value: (value || "").trim(),
200
+ domain: "facebook.com",
201
+ path: "/",
202
+ expires: new Date().getTime() + 1000 * 60 * 60 * 24 * 365,
203
+ });
204
+ });
205
+ appState = arrayAppState;
206
+ }
207
+ appState.map(function(c) {
208
+ const str =
209
+ c.key +
210
+ "=" +
211
+ c.value +
212
+ "; expires=" +
213
+ c.expires +
214
+ "; domain=" +
215
+ c.domain +
216
+ "; path=" +
217
+ c.path +
218
+ ";";
219
+ jar.setCookie(str, "http://" + c.domain);
220
+ });
221
+ mainPromise = utils
222
+ .get("https://www.facebook.com/", jar, null, globalOptions, {
223
+ noRef: true,
224
+ })
225
+ .then(utils.saveCookies(jar));
226
+ } else {
227
+ if (email) {
228
+ throw {
229
+ error:
230
+ "Currently, the login method by email and password is no longer supported, please use the login method by appState",
231
+ };
232
+ } else {
233
+ throw { error: "No appState given." };
234
+ }
235
+ }
236
+ let ctx = null;
237
+ let _defaultFuncs = null;
238
+ let api = null;
239
+ mainPromise = mainPromise
240
+ .then(function(res) {
241
+ const reg = /<meta http-equiv="refresh" content="0;url=([^"]+)[^>]+>/;
242
+ const redirect = reg.exec(res.body);
243
+ if (redirect && redirect[1]) {
244
+ return utils
245
+ .get(redirect[1], jar, null, globalOptions)
246
+ .then(utils.saveCookies(jar));
247
+ }
248
+ return res;
249
+ })
250
+ .then(function(res) {
251
+ if (res.body.indexOf("checkpoint") > -1) {
252
+ log.warn(
253
+ "login",
254
+ "Checkpoint detected. Please log in with a browser to verify."
255
+ );
256
+ }
257
+ const html = res.body;
258
+ const stuff = buildAPI(globalOptions, html, jar);
259
+ ctx = stuff[0];
260
+ _defaultFuncs = stuff[1];
261
+ api = stuff[2];
262
+ return res;
263
+ });
264
+ if (globalOptions.pageID) {
265
+ mainPromise = mainPromise
266
+ .then(function() {
267
+ return utils.get(
268
+ "https://www.facebook.com/" +
269
+ ctx.globalOptions.pageID +
270
+ "/messages/?section=messages&subsection=inbox",
271
+ ctx.jar,
272
+ null,
273
+ globalOptions
274
+ );
275
+ })
276
+ .then(function(resData) {
277
+ let url = utils
278
+ .getFrom(
279
+ resData.body,
280
+ 'window.location.replace("https:\\/\\/www.facebook.com\\',
281
+ '");'
282
+ )
283
+ .split("\\")
284
+ .join("");
285
+ url = url.substring(0, url.length - 1);
286
+ return utils.get(
287
+ "https://www.facebook.com" + url,
288
+ ctx.jar,
289
+ null,
290
+ globalOptions
291
+ );
292
+ });
293
+ }
294
+ mainPromise
295
+ .then(function() {
296
+ log.info("login", "Done logging in.");
297
+ return callback(null, api);
298
+ })
299
+ .catch(function(e) {
300
+ log.error("login", e.error || e);
301
+ callback(e);
302
+ });
226
303
  }
227
304
  function login(loginData, options, callback) {
228
- if (utils.getType(options) === 'Function' || utils.getType(options) === 'AsyncFunction') {
229
- callback = options;
230
- options = {};
231
- }
232
- const globalOptions = {
233
- selfListen: false,
234
- selfListenEvent: false,
235
- listenEvents: false,
236
- listenTyping: false,
237
- updatePresence: false,
238
- forceLogin: false,
239
- autoMarkDelivery: true,
240
- autoMarkRead: false,
241
- autoReconnect: true,
242
- logRecordSize: defaultLogRecordSize,
243
- online: false,
244
- emitReady: false,
245
- 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
- };
247
- setOptions(globalOptions, options);
248
- let prCallback = null;
249
- if (utils.getType(callback) !== "Function" && utils.getType(callback) !== "AsyncFunction") {
250
- let rejectFunc = null;
251
- let resolveFunc = null;
252
- var returnPromise = new Promise(function (resolve, reject) {
253
- resolveFunc = resolve;
254
- rejectFunc = reject;
255
- });
256
- prCallback = function (error, api) {
257
- if (error) {
258
- return rejectFunc(error);
259
- }
260
- return resolveFunc(api);
261
- };
262
- callback = prCallback;
263
- }
264
- loginHelper(loginData.appState, loginData.email, loginData.password, globalOptions, callback, prCallback);
265
- return returnPromise;
305
+ if (
306
+ utils.getType(options) === "Function" ||
307
+ utils.getType(options) === "AsyncFunction"
308
+ ) {
309
+ callback = options;
310
+ options = {};
311
+ }
312
+ const globalOptions = {
313
+ selfListen: false,
314
+ selfListenEvent: false,
315
+ listenEvents: false,
316
+ listenTyping: false,
317
+ updatePresence: false,
318
+ forceLogin: false,
319
+ autoMarkDelivery: true,
320
+ autoMarkRead: false,
321
+ autoReconnect: true,
322
+ logRecordSize: defaultLogRecordSize,
323
+ online: true,
324
+ emitReady: false,
325
+ userAgent:
326
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
327
+ };
328
+ setOptions(globalOptions, options);
329
+ let prCallback = null;
330
+ if (
331
+ utils.getType(callback) !== "Function" &&
332
+ utils.getType(callback) !== "AsyncFunction"
333
+ ) {
334
+ let rejectFunc = null;
335
+ let resolveFunc = null;
336
+ var returnPromise = new Promise(function(resolve, reject) {
337
+ resolveFunc = resolve;
338
+ rejectFunc = reject;
339
+ });
340
+ prCallback = function(error, api) {
341
+ if (error) {
342
+ return rejectFunc(error);
343
+ }
344
+ return resolveFunc(api);
345
+ };
346
+ callback = prCallback;
347
+ }
348
+ loginHelper(
349
+ loginData.appState,
350
+ loginData.email,
351
+ loginData.password,
352
+ globalOptions,
353
+ callback,
354
+ prCallback
355
+ );
356
+ return returnPromise;
266
357
  }
267
-
268
- module.exports = login;
358
+ module.exports = login;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongdev/fca-unofficial",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
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
- "websocket-stream": "^5.5.0"
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
+ }