@dongdev/fca-unofficial 2.0.25 → 2.0.27

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/CHANGELOG.md CHANGED
@@ -110,3 +110,9 @@ Too lazy to write changelog, sorry! (will write changelog in the next release, t
110
110
 
111
111
  ## v2.0.24 - 2025-10-11
112
112
  - Hotfix / auto bump
113
+
114
+ ## v2.0.25 - 2025-10-12
115
+ - Hotfix / auto bump
116
+
117
+ ## v2.0.26 - 2025-10-16
118
+ - Hotfix / auto bump
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  const fs = require("fs");
2
3
  const path = require("path");
3
4
  const models = require("../src/database/models");
@@ -230,6 +231,10 @@ async function backupAppStateSQL(j, userID) {
230
231
  const ck = cookieHeaderFromJar(j);
231
232
  await upsertBackup(Model, userID, "appstate", JSON.stringify(appJson));
232
233
  await upsertBackup(Model, userID, "cookie", ck);
234
+ try {
235
+ const out = path.join(process.cwd(), "appstate.json");
236
+ fs.writeFileSync(out, JSON.stringify(appJson, null, 2));
237
+ } catch { }
233
238
  logger("Backup stored (overwrite mode)", "info");
234
239
  } catch (e) {
235
240
  logger(`Failed to save appstate backup ${e && e.message ? e.message : String(e)}`, "warn");
@@ -258,66 +263,171 @@ async function getLatestBackupAny(type) {
258
263
  }
259
264
  }
260
265
 
261
- async function tokens(username, password, twofactor = null) {
262
- const t0 = process.hrtime.bigint();
263
- if (!username || !password) {
264
- logger("Missing email or password", "error");
265
- return { status: false, message: "Please provide email and password" };
266
+ const MESSENGER_USER_AGENT = "Dalvik/2.1.0 (Linux; U; Android 9; ASUS_Z01QD Build/PQ3A.190605.003) [FBAN/Orca-Android;FBAV/391.2.0.20.404;FBPN/com.facebook.orca;FBLC/vi_VN;FBBV/437533963;FBCR/Viettel Telecom;FBMF/asus;FBBD/asus;FBDV/ASUS_Z01QD;FBSV/9;FBCA/x86:armeabi-v7a;FBDM/{density=1.5,width=1600,height=900};FB_FW/1;]";
267
+
268
+ function encodesig(obj) {
269
+ let data = "";
270
+ Object.keys(obj).forEach(k => { data += `${k}=${obj[k]}`; });
271
+ return md5(data + "62f8ce9f74b12f84c123cc23437a4a32");
272
+ }
273
+
274
+ function sort(obj) {
275
+ const keys = Object.keys(obj).sort();
276
+ const out = {};
277
+ for (const k of keys) out[k] = obj[k];
278
+ return out;
279
+ }
280
+
281
+ async function setJarCookies(j, appstate) {
282
+ const tasks = [];
283
+ for (const c of appstate) {
284
+ const dom = (c.domain || ".facebook.com").replace(/^\./, "");
285
+ const path = c.path || "/";
286
+ const base1 = `https://${dom}${path}`;
287
+ const base2 = `https://www.${dom}${path}`;
288
+ const str = `${c.key}=${c.value}; Domain=${c.domain || ".facebook.com"}; Path=${path};`;
289
+ tasks.push(j.setCookie(str, base1));
290
+ tasks.push(j.setCookie(str, base2));
266
291
  }
267
- logger(`AUTO-LOGIN: Initialize login ${mask(username, 2)}`, "info");
268
- const cj = new CookieJar();
269
- const axios = wrapper(axiosBase.create({ jar: cj, withCredentials: true, validateStatus: () => true, timeout: 30000 }));
270
- const loginUrl = "https://b-graph.facebook.com/auth/login";
271
- const baseForm = { adid: uuidv4(), email: username, password: password, format: "json", device_id: uuidv4(), cpl: "true", family_device_id: uuidv4(), locale: "en_US", client_country_code: "US", credentials_type: "device_based_login_password", generate_session_cookies: "1", generate_analytics_claim: "1", generate_machine_id: "1", currently_logged_in_userid: "0", irisSeqID: 1, try_num: "1", enroll_misauth: "false", meta_inf_fbmeta: "", source: "login", machine_id: randomString(24), fb_api_req_friendly_name: "authenticate", fb_api_caller_class: "com.facebook.account.login.protocol.Fb4aAuthHandler", api_key: "882a8490361da98702bf97a021ddc14d", access_token: "350685531728%7C62f8ce9f74b12f84c123cc23437a4a32" };
272
- try {
273
- const form1 = { ...baseForm };
274
- form1.sig = encodeSig(sortObject(form1));
275
- logger("AUTO-LOGIN: Send login request", "info");
276
- const r0 = process.hrtime.bigint();
277
- const res1 = await axios.post(loginUrl, qs.stringify(form1), { headers: buildHeaders(loginUrl, { "x-fb-friendly-name": form1.fb_api_req_friendly_name }) });
278
- const dt1 = Number(process.hrtime.bigint() - r0) / 1e6;
279
- logger(`AUTO-LOGIN: Received response ${res1.status} ${Math.round(dt1)}ms`, "info");
280
- if (res1.status >= 400) throw { response: res1 };
281
- if (res1.data && res1.data.session_cookies) {
282
- const cookies = res1.data.session_cookies.map(e => ({ key: e.name, value: e.value, domain: "facebook.com", path: e.path, hostOnly: false }));
283
- logger(`AUTO-LOGIN: Login success (first attempt) ${cookies.length} cookies`, "info");
284
- const t1 = Number(process.hrtime.bigint() - t0) / 1e6;
285
- logger(`Done success login ${Math.round(t1)}ms`, "info");
286
- return { status: true, cookies };
287
- }
288
- throw { response: res1 };
289
- } catch (err) {
290
- const e = err && err.response ? err.response : null;
291
- const code = e && e.data && e.data.error ? e.data.error.code : null;
292
- const message = e && e.data && e.data.error ? e.data.error.message : "";
293
- if (code) logger(`AUTO-LOGIN: Error on request #1 ${code} ${message}`, "warn");
294
- logger("AUTO-LOGIN: Processing twofactor...", "info");
295
- if (code === 401) return { status: false, message: message || "Unauthorized" };
296
- if (!config.credentials?.twofactor) {
297
- logger("AUTO-LOGIN: 2FA required but secret missing", "warn");
298
- return { status: false, message: "Please provide the 2FA secret!" };
299
- }
292
+ await Promise.all(tasks);
293
+ }
294
+
295
+ async function loginViaGraph(username, password, twofactorSecretOrCode, i_user, externalJar) {
296
+ const cookieJar = externalJar instanceof CookieJar ? externalJar : new CookieJar();
297
+ const client = wrapper(axiosBase.create({ jar: cookieJar, withCredentials: true, timeout: 30000, validateStatus: () => true }));
298
+ const device_id = uuidv4();
299
+ const family_device_id = device_id;
300
+ const machine_id = randomString(24);
301
+ const base = {
302
+ adid: "00000000-0000-0000-0000-000000000000",
303
+ format: "json",
304
+ device_id,
305
+ email: username,
306
+ password,
307
+ generate_analytics_claim: "1",
308
+ community_id: "",
309
+ cpl: "true",
310
+ try_num: "1",
311
+ family_device_id,
312
+ secure_family_device_id: "",
313
+ credentials_type: "password",
314
+ enroll_misauth: "false",
315
+ generate_session_cookies: "1",
316
+ source: "login",
317
+ generate_machine_id: "1",
318
+ jazoest: "22297",
319
+ meta_inf_fbmeta: "NO_FILE",
320
+ advertiser_id: "00000000-0000-0000-0000-000000000000",
321
+ currently_logged_in_userid: "0",
322
+ locale: "vi_VN",
323
+ client_country_code: "VN",
324
+ fb_api_req_friendly_name: "authenticate",
325
+ fb_api_caller_class: "AuthOperations$PasswordAuthOperation",
326
+ api_key: "256002347743983",
327
+ access_token: "256002347743983|374e60f8b9bb6b8cbb30f78030438895"
328
+ };
329
+ const headers = {
330
+ "User-Agent": MESSENGER_USER_AGENT,
331
+ "Accept-Encoding": "gzip, deflate",
332
+ "Content-Type": "application/x-www-form-urlencoded",
333
+ "x-fb-connection-quality": "EXCELLENT",
334
+ "x-fb-sim-hni": "45204",
335
+ "x-fb-net-hni": "45204",
336
+ "x-fb-connection-type": "WIFI",
337
+ "x-tigon-is-retry": "False",
338
+ "x-fb-friendly-name": "authenticate",
339
+ "x-fb-request-analytics-tags": '{"network_tags":{"retry_attempt":"0"},"application_tags":"unknown"}',
340
+ "x-fb-http-engine": "Liger",
341
+ "x-fb-client-ip": "True",
342
+ "x-fb-server-cluster": "True",
343
+ authorization: `OAuth ${base.access_token}`
344
+ };
345
+ const form1 = { ...base };
346
+ form1.sig = encodesig(sort(form1));
347
+ const res1 = await client.request({ url: "https://b-graph.facebook.com/auth/login", method: "post", data: qs.stringify(form1), headers });
348
+ if (res1.status === 200 && res1.data && res1.data.session_cookies) {
349
+ const appstate = res1.data.session_cookies.map(c => ({ key: c.name, value: c.value, domain: c.domain, path: c.path }));
350
+ const cUserCookie = appstate.find(c => c.key === "c_user");
351
+ if (i_user) appstate.push({ key: "i_user", value: i_user, domain: ".facebook.com", path: "/" });
352
+ else if (cUserCookie) appstate.push({ key: "i_user", value: cUserCookie.value, domain: ".facebook.com", path: "/" });
353
+ await setJarCookies(cookieJar, appstate);
354
+ let eaau = null;
355
+ let eaad6v7 = null;
356
+ try {
357
+ const r1 = await client.request({ url: `https://api.facebook.com/method/auth.getSessionforApp?format=json&access_token=${res1.data.access_token}&new_app_id=350685531728`, method: "get", headers: { "user-agent": MESSENGER_USER_AGENT, "x-fb-connection-type": "WIFI", authorization: `OAuth ${res1.data.access_token}` } });
358
+ eaau = r1.data && r1.data.access_token ? r1.data.access_token : null;
359
+ } catch { }
300
360
  try {
301
- const dataErr = e && e.data && e.data.error && e.data.error.error_data ? e.data.error_data : {};
302
- const codeTotp = await genTotp(config.credentials.twofactor);
303
- logger(`AUTO-LOGIN: Performing 2FA ${mask(codeTotp, 2)}`, "info");
304
- const form2 = { ...baseForm, twofactor_code: codeTotp, encrypted_msisdn: "", userid: dataErr.uid || "", machine_id: dataErr.machine_id || baseForm.machine_id, first_factor: dataErr.login_first_factor || "", credentials_type: "two_factor" };
305
- form2.sig = encodeSig(sortObject(form2));
306
- const r1 = process.hrtime.bigint();
307
- const res2 = await axios.post(loginUrl, qs.stringify(form2), { headers: buildHeaders(loginUrl, { "x-fb-friendly-name": form2.fb_api_req_friendly_name }) });
308
- const dt2 = Number(process.hrtime.bigint() - r1) / 1e6;
309
- logger(`AUTO-LOGIN: Received 2FA response ${res2.status} ${Math.round(dt2)}ms`, "info");
310
- if (res2.status >= 400 || !(res2.data && res2.data.session_cookies)) throw new Error("2FA failed");
311
- const cookies = res2.data.session_cookies.map(e => ({ key: e.name, value: e.value, domain: "facebook.com", path: e.path, hostOnly: false }));
312
- logger(`AUTO-LOGIN: Login success with 2FA ${cookies.length} cookies`, "info");
313
- const t1 = Number(process.hrtime.bigint() - t0) / 1e6;
314
- logger(`AUTO-LOGIN: Done success login with 2FA ${Math.round(t1)}ms`, "info");
315
- return { status: true, cookies };
316
- } catch {
317
- logger("AUTO-LOGIN: 2FA failed", "error");
318
- return { status: false, message: "Invalid two-factor code!" };
361
+ const r2 = await client.request({ url: `https://api.facebook.com/method/auth.getSessionforApp?format=json&access_token=${res1.data.access_token}&new_app_id=275254692598279`, method: "get", headers: { "user-agent": MESSENGER_USER_AGENT, "x-fb-connection-type": "WIFI", authorization: `OAuth ${res1.data.access_token}` } });
362
+ eaad6v7 = r2.data && r2.data.access_token ? r2.data.access_token : null;
363
+ } catch { }
364
+ return { ok: true, cookies: appstate.map(c => ({ key: c.key, value: c.value })), jar: cookieJar, access_token_mess: res1.data.access_token || null, access_token: eaau, access_token_eaad6v7: eaad6v7, uid: res1.data.uid || cUserCookie?.value || null, session_key: res1.data.session_key || null };
365
+ }
366
+ const err = res1 && res1.data && res1.data.error ? res1.data.error : {};
367
+ if (err && err.code === 406) {
368
+ const data = err.error_data || {};
369
+ let code = null;
370
+ if (twofactorSecretOrCode && /^\d{6}$/.test(String(twofactorSecretOrCode))) code = String(twofactorSecretOrCode);
371
+ else if (twofactorSecretOrCode) {
372
+ try {
373
+ const clean = decodeURI(twofactorSecretOrCode).replace(/\s+/g, "").toUpperCase();
374
+ const { otp } = await TOTP.generate(clean);
375
+ code = otp;
376
+ } catch { }
377
+ } else if (config.credentials?.twofactor) {
378
+ const { otp } = await TOTP.generate(String(config.credentials.twofactor).replace(/\s+/g, "").toUpperCase());
379
+ code = otp;
319
380
  }
381
+ if (!code) return { ok: false, message: "2FA required" };
382
+ const form2 = {
383
+ ...base,
384
+ credentials_type: "two_factor",
385
+ twofactor_code: code,
386
+ userid: data.uid || username,
387
+ first_factor: data.login_first_factor || "",
388
+ machine_id: data.machine_id || machine_id
389
+ };
390
+ form2.sig = encodesig(sort(form2));
391
+ const res2 = await client.request({ url: "https://b-graph.facebook.com/auth/login", method: "post", data: qs.stringify(form2), headers });
392
+ if (res2.status === 200 && res2.data && res2.data.session_cookies) {
393
+ const appstate = res2.data.session_cookies.map(c => ({ key: c.name, value: c.value, domain: c.domain, path: c.path }));
394
+ const cUserCookie = appstate.find(c => c.key === "c_user");
395
+ if (i_user) appstate.push({ key: "i_user", value: i_user, domain: ".facebook.com", path: "/" });
396
+ else if (cUserCookie) appstate.push({ key: "i_user", value: cUserCookie.value, domain: ".facebook.com", path: "/" });
397
+ await setJarCookies(cookieJar, appstate);
398
+ let eaau = null;
399
+ let eaad6v7 = null;
400
+ try {
401
+ const r1 = await client.request({ url: `https://api.facebook.com/method/auth.getSessionforApp?format=json&access_token=${res2.data.access_token}&new_app_id=350685531728`, method: "get", headers: { "user-agent": MESSENGER_USER_AGENT, "x-fb-connection-type": "WIFI", authorization: `OAuth ${res2.data.access_token}` } });
402
+ eaau = r1.data && r1.data.access_token ? r1.data.access_token : null;
403
+ } catch { }
404
+ try {
405
+ const r2 = await client.request({ url: `https://api.facebook.com/method/auth.getSessionforApp?format=json&access_token=${res2.data.access_token}&new_app_id=275254692598279`, method: "get", headers: { "user-agent": MESSENGER_USER_AGENT, "x-fb-connection-type": "WIFI", authorization: `OAuth ${res2.data.access_token}` } });
406
+ eaad6v7 = r2.data && r2.data.access_token ? r2.data.access_token : null;
407
+ } catch { }
408
+ return { ok: true, cookies: appstate.map(c => ({ key: c.key, value: c.value })), jar: cookieJar, access_token_mess: res2.data.access_token || null, access_token: eaau, access_token_eaad6v7: eaad6v7, uid: res2.data.uid || cUserCookie?.value || null, session_key: res2.data.session_key || null };
409
+ }
410
+ return { ok: false, message: "2FA failed" };
411
+ }
412
+ return { ok: false, message: "Login failed" };
413
+ }
414
+
415
+ async function tokens(username, password, twofactor = null) {
416
+ const t0 = process.hrtime.bigint();
417
+ if (!username || !password) return { status: false, message: "Please provide email and password" };
418
+ logger(`AUTO-LOGIN: Initialize login ${mask(username, 2)}`, "info");
419
+ const res = await loginViaGraph(username, password, twofactor, null, jar);
420
+ if (res && res.ok && Array.isArray(res.cookies)) {
421
+ logger(`AUTO-LOGIN: Login success ${res.cookies.length} cookies`, "info");
422
+ const t1 = Number(process.hrtime.bigint() - t0) / 1e6;
423
+ logger(`Done success login ${Math.round(t1)}ms`, "info");
424
+ return { status: true, cookies: res.cookies };
425
+ }
426
+ if (res && res.message === "2FA required") {
427
+ logger("AUTO-LOGIN: 2FA required but secret missing", "warn");
428
+ return { status: false, message: "Please provide the 2FA secret!" };
320
429
  }
430
+ return { status: false, message: res && res.message ? res.message : "Login failed" };
321
431
  }
322
432
 
323
433
  async function hydrateJarFromDB(userID) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongdev/fca-unofficial",
3
- "version": "2.0.25",
3
+ "version": "2.0.27",
4
4
  "description": "Unofficial Facebook Chat API for Node.js - Interact with Facebook Messenger programmatically",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -4,7 +4,7 @@ const uuid = require("uuid");
4
4
  "use strict";
5
5
  module.exports = function createListenMqtt(deps) {
6
6
  const { WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy,
7
- topics, parseDelta, getTaskResponseData, logger
7
+ topics, parseDelta, getTaskResponseData, logger, emitAuth
8
8
  } = deps;
9
9
 
10
10
  return function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
@@ -14,9 +14,12 @@ const markDelivery = require("./core/markDelivery");
14
14
  const getTaskResponseData = require("./core/getTaskResponseData");
15
15
  const createEmitAuth = require("./core/emitAuth");
16
16
  const parseDelta = createParseDelta({ markDelivery, parseAndCheckLogin });
17
- const listenMqtt = createListenMqtt({ WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy, topics, parseDelta, getTaskResponseData, logger });
18
- const getSeqIDFactory = createGetSeqID({ parseAndCheckLogin, listenMqtt, logger });
17
+ // Create emitAuth first so it can be injected into both factories
19
18
  const emitAuth = createEmitAuth({ logger });
19
+ // Pass emitAuth into connectMqtt so errors there can signal auth state
20
+ const listenMqtt = createListenMqtt({ WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy, topics, parseDelta, getTaskResponseData, logger, emitAuth });
21
+ // Inject emitAuth into getSeqID so its catch handler can notify properly
22
+ const getSeqIDFactory = createGetSeqID({ parseAndCheckLogin, listenMqtt, logger, emitAuth });
20
23
 
21
24
  const MQTT_DEFAULTS = { cycleMs: 60 * 60 * 1000, reconnectDelayMs: 2000, autoReconnect: true, reconnectAfterStop: false };
22
25
  function mqttConf(ctx, overrides) {