@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 CHANGED
@@ -2,119 +2,126 @@
2
2
 
3
3
  const utils = require("./utils");
4
4
  const log = require("npmlog");
5
- const fs = require('node:fs');
5
+
6
6
  let checkVerified = null;
7
+
7
8
  const defaultLogRecordSize = 100;
8
9
  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'];
10
+
27
11
  function setOptions(globalOptions, options) {
28
12
  Object.keys(options).map(function (key) {
29
- switch (Boolean_Option.includes(key)) {
30
- case true: {
31
- globalOptions[key] = Boolean(options[key]);
13
+ switch (key) {
14
+ case 'online':
15
+ globalOptions.online = Boolean(options.online);
32
16
  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
- }
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 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;
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
- const maybeCookie = cookies.find(val => val.cookieString().startsWith("c_user="));
88
- if (!maybeCookie) {
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
- if (html.includes("/checkpoint/block/?next")) {
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
- const userID = maybeCookie.cookieString().split("=")[1];
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
- try { clearInterval(checkVerified) } catch (_) { }
98
- const clientID = (Math.random() * 2147483648 | 0).toString(16);
99
- let mqttEndpoint, region, fb_dtsg;
98
+
100
99
  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
- }
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: undefined,
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
- api[v.replace('.js', '')] = require('./src/' + v)(defaultFuncs, api, ctx);
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, jar = utils.getJar();
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
- } else if (utils.getType(appState) === 'String') {
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
- let ctx, _defaultFuncs, api;
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.get(redirect[1], jar, null, globalOptions).then(utils.saveCookies(jar));
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.get('https://www.facebook.com/' + ctx.globalOptions.pageID + '/messages/?section=messages&subsection=inbox', ctx.jar, null, globalOptions);
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
- return utils.get('https://www.facebook.com' + url, ctx.jar, null, globalOptions);
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: false,
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.5",
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
- "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
+ }