@dongdev/fca-unofficial 2.0.19 → 2.0.21

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
@@ -92,3 +92,9 @@ Too lazy to write changelog, sorry! (will write changelog in the next release, t
92
92
 
93
93
  ## v2.0.18 - 2025-10-07
94
94
  - Hotfix / auto bump
95
+
96
+ ## v2.0.19 - 2025-10-07
97
+ - Hotfix / auto bump
98
+
99
+ ## v2.0.20 - 2025-10-08
100
+ - Hotfix / auto bump
@@ -298,7 +298,7 @@ async function tokens(username, password, twofactor = null) {
298
298
  return { status: false, message: "Please provide the 2FA secret!" };
299
299
  }
300
300
  try {
301
- const dataErr = e && e.data && e.data.error && e.data.error.error_data ? e.data.error.error_data : {};
301
+ const dataErr = e && e.data && e.data.error && e.data.error.error_data ? e.data.error_data : {};
302
302
  const codeTotp = await genTotp(config.credentials.twofactor);
303
303
  logger(`AUTO-LOGIN: Performing 2FA ${mask(codeTotp, 2)}`, "info");
304
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" };
@@ -355,7 +355,7 @@ async function hydrateJarFromDB(userID) {
355
355
  }
356
356
  }
357
357
 
358
- async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions) {
358
+ async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions, ctxRef) {
359
359
  const getUID = cs =>
360
360
  cs.find(c => c.key === "i_user")?.value ||
361
361
  cs.find(c => c.key === "c_user")?.value ||
@@ -366,7 +366,8 @@ async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions)
366
366
  const hydrated = await hydrateJarFromDB(null);
367
367
  if (hydrated) {
368
368
  logger("AppState backup live — proceeding to login", "info");
369
- const resB = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
369
+ const initial = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
370
+ const resB = (await ctxRef.bypassAutomation(initial, jar)) || initial;
370
371
  const htmlB = resB && resB.data ? resB.data : "";
371
372
  if (htmlB.includes("/checkpoint/block/?next")) throw new Error("Checkpoint");
372
373
  const cookiesB = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
@@ -383,7 +384,8 @@ async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions)
383
384
  if (!(r && r.status && Array.isArray(r.cookies))) throw new Error(r && r.message ? r.message : "Login failed");
384
385
  const pairs = r.cookies.map(c => `${c.key || c.name}=${c.value}`);
385
386
  setJarFromPairs(jar, pairs, ".facebook.com");
386
- const res2 = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
387
+ const initial2 = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
388
+ const res2 = (await ctxRef.bypassAutomation(initial2, jar)) || initial2;
387
389
  const html2 = res2 && res2.data ? res2.data : "";
388
390
  if (html2.includes("/checkpoint/block/?next")) throw new Error("Checkpoint");
389
391
  const cookies2 = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
@@ -447,13 +449,67 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
447
449
  return callback(e);
448
450
  }
449
451
  (async () => {
452
+ const ctx = { globalOptions, options: globalOptions, reconnectAttempts: 0 };
453
+ ctx.bypassAutomation = async function (resp, j) {
454
+ global.fca = global.fca || {};
455
+ global.fca.BypassAutomationNotification = this.bypassAutomation.bind(this);
456
+ const s = x => (typeof x === "string" ? x : String(x ?? ""));
457
+ const u = r => r?.request?.res?.responseUrl || (r?.config?.baseURL ? new URL(r.config.url || "/", r.config.baseURL).toString() : r?.config?.url || "");
458
+ const isCp = r => typeof u(r) === "string" && u(r).includes("checkpoint/601051028565049");
459
+ const cookieUID = async () => {
460
+ try {
461
+ const cookies = typeof j?.getCookies === "function" ? await j.getCookies("https://www.facebook.com") : [];
462
+ return cookies.find(c => c.key === "i_user")?.value || cookies.find(c => c.key === "c_user")?.value;
463
+ } catch { return undefined; }
464
+ };
465
+ const htmlUID = body => s(body).match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s(body).match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
466
+ const getUID = async body => (await cookieUID()) || htmlUID(body);
467
+ const refreshJar = async () => get("https://www.facebook.com/", j, null, this.options).then(saveCookies(j));
468
+ const bypass = async body => {
469
+ const b = s(body);
470
+ const UID = await getUID(b);
471
+ const fb_dtsg = getFrom(b, '"DTSGInitData",[],{"token":"', '",') || b.match(/name="fb_dtsg"\s+value="([^"]+)"/)?.[1];
472
+ const jazoest = getFrom(b, 'name="jazoest" value="', '"') || getFrom(b, "jazoest=", '",') || b.match(/name="jazoest"\s+value="([^"]+)"/)?.[1];
473
+ const lsd = getFrom(b, '["LSD",[],{"token":"', '"}') || b.match(/name="lsd"\s+value="([^"]+)"/)?.[1];
474
+ const form = { av: UID, fb_dtsg, jazoest, lsd, fb_api_caller_class: "RelayModern", fb_api_req_friendly_name: "FBScrapingWarningMutation", variables: "{}", server_timestamps: true, doc_id: 6339492849481770 };
475
+ await post("https://www.facebook.com/api/graphql/", j, form, null, this.options).then(saveCookies(j));
476
+ logger("Facebook automation warning detected, handling...", "warn");
477
+ this.reconnectAttempts = 0;
478
+ };
479
+ try {
480
+ if (resp) {
481
+ if (isCp(resp)) {
482
+ await bypass(s(resp.data));
483
+ const refreshed = await refreshJar();
484
+ if (isCp(refreshed)) logger("Checkpoint still present after refresh", "warn");
485
+ else logger("Bypass complete, cookies refreshed", "info");
486
+ return refreshed;
487
+ }
488
+ return resp;
489
+ }
490
+ const first = await get("https://www.facebook.com/", j, null, this.options).then(saveCookies(j));
491
+ if (isCp(first)) {
492
+ await bypass(s(first.data));
493
+ const refreshed = await refreshJar();
494
+ if (!isCp(refreshed)) logger("Bypass complete, cookies refreshed", "info");
495
+ else logger("Checkpoint still present after refresh", "warn");
496
+ return refreshed;
497
+ }
498
+ return first;
499
+ } catch (e) {
500
+ logger(`Bypass automation error: ${e && e.message ? e.message : String(e)}`, "error");
501
+ return resp;
502
+ }
503
+ };
450
504
  if (appState || Cookie) {
451
- return get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
505
+ const initial = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
506
+ return (await ctx.bypassAutomation(initial, jar)) || initial;
452
507
  }
453
508
  const hydrated = await hydrateJarFromDB(null);
454
509
  if (hydrated) {
455
510
  logger("AppState backup live — proceeding to login", "info");
456
- return get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
511
+ const initial = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
512
+ return (await ctx.bypassAutomation(initial, jar)) || initial;
457
513
  }
458
514
  logger("AppState backup die — proceeding to email/password login", "warn");
459
515
  return get("https://www.facebook.com/", null, null, globalOptions)
@@ -464,7 +520,48 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
464
520
  });
465
521
  })()
466
522
  .then(async function (res) {
467
- let html = res && res.data ? res.data : "";
523
+ const ctx = {};
524
+ ctx.options = globalOptions;
525
+ ctx.bypassAutomation = async function (resp, j) {
526
+ global.fca = global.fca || {};
527
+ global.fca.BypassAutomationNotification = this.bypassAutomation.bind(this);
528
+ const s = x => (typeof x === "string" ? x : String(x ?? ""));
529
+ const u = r => r?.request?.res?.responseUrl || (r?.config?.baseURL ? new URL(r.config.url || "/", r.config.baseURL).toString() : r?.config?.url || "");
530
+ const isCp = r => typeof u(r) === "string" && u(r).includes("checkpoint/601051028565049");
531
+ const cookieUID = async () => {
532
+ try {
533
+ const cookies = typeof j?.getCookies === "function" ? await j.getCookies("https://www.facebook.com") : [];
534
+ return cookies.find(c => c.key === "i_user")?.value || cookies.find(c => c.key === "c_user")?.value;
535
+ } catch { return undefined; }
536
+ };
537
+ const htmlUID = body => s(body).match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s(body).match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
538
+ const getUID = async body => (await cookieUID()) || htmlUID(body);
539
+ const refreshJar = async () => get("https://www.facebook.com/", j, null, this.options).then(saveCookies(j));
540
+ const bypass = async body => {
541
+ const b = s(body);
542
+ const UID = await getUID(b);
543
+ const fb_dtsg = getFrom(b, '"DTSGInitData",[],{"token":"', '",') || b.match(/name="fb_dtsg"\s+value="([^"]+)"/)?.[1];
544
+ const jazoest = getFrom(b, 'name="jazoest" value="', '"') || getFrom(b, "jazoest=", '",') || b.match(/name="jazoest"\s+value="([^"]+)"/)?.[1];
545
+ const lsd = getFrom(b, '["LSD",[],{"token":"', '"}') || b.match(/name="lsd"\s+value="([^"]+)"/)?.[1];
546
+ const form = { av: UID, fb_dtsg, jazoest, lsd, fb_api_caller_class: "RelayModern", fb_api_req_friendly_name: "FBScrapingWarningMutation", variables: "{}", server_timestamps: true, doc_id: 6339492849481770 };
547
+ await post("https://www.facebook.com/api/graphql/", j, form, null, this.options).then(saveCookies(j));
548
+ logger("Facebook automation warning detected, handling...", "warn");
549
+ };
550
+ try {
551
+ if (res && isCp(res)) {
552
+ await bypass(s(res.data));
553
+ const refreshed = await refreshJar();
554
+ if (!isCp(refreshed)) logger("Bypass complete, cookies refreshed", "info");
555
+ return refreshed;
556
+ }
557
+ logger("No checkpoint detected", "info");
558
+ return res;
559
+ } catch {
560
+ return res;
561
+ }
562
+ };
563
+ const processed = (await ctx.bypassAutomation(res, jar)) || res;
564
+ let html = processed && processed.data ? processed.data : "";
468
565
  let cookies = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
469
566
  let userID =
470
567
  cookies.find(c => c.key === "i_user")?.value ||
@@ -472,7 +569,7 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
472
569
  cookies.find(c => c.name === "i_user")?.value ||
473
570
  cookies.find(c => c.name === "c_user")?.value;
474
571
  if (!userID) {
475
- const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions);
572
+ const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions, ctx);
476
573
  html = retried.html;
477
574
  cookies = retried.cookies;
478
575
  userID = retried.userID;
@@ -527,7 +624,7 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
527
624
  console.error("Database connection failed:", error && error.message ? error.message : String(error));
528
625
  });
529
626
  logger("FCA fix/update by DongDev (Donix-VN)", "info");
530
- const ctx = {
627
+ const ctxMain = {
531
628
  userID,
532
629
  jar,
533
630
  globalOptions,
@@ -547,7 +644,9 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
547
644
  wsTaskNumber: 0,
548
645
  tasks: new Map()
549
646
  };
550
- ctx.performAutoLogin = async () => {
647
+ ctxMain.options = globalOptions;
648
+ ctxMain.bypassAutomation = ctx.bypassAutomation.bind(ctxMain);
649
+ ctxMain.performAutoLogin = async () => {
551
650
  try {
552
651
  const u = config.credentials?.email || email;
553
652
  const p = config.credentials?.password || password;
@@ -579,7 +678,7 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
579
678
  return await getLatestBackup(uid, "cookie");
580
679
  }
581
680
  };
582
- const defaultFuncs = makeDefaults(html, userID, ctx);
681
+ const defaultFuncs = makeDefaults(html, userID, ctxMain);
583
682
  const srcRoot = path.join(__dirname, "../src/api");
584
683
  let loaded = 0;
585
684
  let skipped = 0;
@@ -594,7 +693,7 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
594
693
  skipped++;
595
694
  return;
596
695
  }
597
- api[key] = require(p)(defaultFuncs, api, ctx);
696
+ api[key] = require(p)(defaultFuncs, api, ctxMain);
598
697
  loaded++;
599
698
  });
600
699
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongdev/fca-unofficial",
3
- "version": "2.0.19",
3
+ "version": "2.0.21",
4
4
  "description": "Unofficial Facebook Chat API for Node.js - Interact with Facebook Messenger programmatically",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,68 @@
1
+ "use_strict";
2
+
3
+ const { generateOfflineThreadingID } = require("../../utils/format.js");
4
+ module.exports = (defaultFuncs, api, ctx) => {
5
+ return (text, messageID, callback) => {
6
+ let reqID = ctx.wsReqNumber + 1;
7
+ var resolveFunc = () => { };
8
+ var rejectFunc = () => { };
9
+ var returnPromise = new Promise((resolve, reject) => {
10
+ resolveFunc = resolve;
11
+ rejectFunc = reject;
12
+ });
13
+ if (!callback) {
14
+ callback = (err, data) => {
15
+ if (err) {
16
+ return rejectFunc(err);
17
+ }
18
+ resolveFunc(data);
19
+ };
20
+ }
21
+ const content = {
22
+ app_id: '2220391788200892',
23
+ payload: JSON.stringify({
24
+ data_trace_id: null,
25
+ epoch_id: parseInt(generateOfflineThreadingID()),
26
+ tasks: [{
27
+ failure_count: null,
28
+ label: '742',
29
+ payload: JSON.stringify({
30
+ message_id: messageID,
31
+ text: text,
32
+ }),
33
+ queue_name: 'edit_message',
34
+ task_id: ++ctx.wsTaskNumber,
35
+ }],
36
+ version_id: '6903494529735864',
37
+ }),
38
+ request_id: ++ctx.wsReqNumber,
39
+ type: 3
40
+ }
41
+ ctx.mqttClient.publish('/ls_req', JSON.stringify(content), {
42
+ qos: 1,
43
+ retain: false
44
+ });
45
+ const handleRes = (topic, message) => {
46
+ if (topic === "/ls_resp") {
47
+ let jsonMsg = JSON.parse(message.toString());
48
+ jsonMsg.payload = JSON.parse(jsonMsg.payload);
49
+ if (jsonMsg.request_id != reqID) return;
50
+ ctx.mqttClient.removeListener('message', handleRes);
51
+ let msgID = jsonMsg.payload.step[1][2][2][1][2];
52
+ let msgReplace = jsonMsg.payload.step[1][2][2][1][4];
53
+ const bodies = {
54
+ body: msgReplace,
55
+ messageID: msgID
56
+ };
57
+ if (msgReplace != text) {
58
+ return callback({
59
+ error: "The message is too old or not from you!"
60
+ }, bodies);
61
+ }
62
+ return callback(undefined, bodies);
63
+ }
64
+ }
65
+ ctx.mqttClient.on('message', handleRes);
66
+ return returnPromise;
67
+ };
68
+ }
@@ -99,10 +99,10 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
99
99
  throw e;
100
100
  }
101
101
  ctx.auto_login = true;
102
- logL("Phiên đăng nhập hết hạn", "warn");
102
+ logger("Login session expired", "warn");
103
103
  const ok = await ctx.performAutoLogin();
104
104
  if (ok) {
105
- logL("Auto login successful! Restarting...", "AUTO-LOGIN");
105
+ logger("Auto login successful! Restarting...");
106
106
  ctx.auto_login = false;
107
107
  process.exit(1);
108
108
  } else {