@dongdev/fca-unofficial 3.0.8 → 3.0.10
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 +6 -0
- package/module/login.js +67 -0
- package/module/loginHelper.js +67 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/module/login.js
CHANGED
|
@@ -3,10 +3,77 @@ const { setOptions } = require("./options");
|
|
|
3
3
|
const { loadConfig } = require("./config");
|
|
4
4
|
const { checkAndUpdateVersion } = require("../func/checkUpdate");
|
|
5
5
|
const loginHelper = require("./loginHelper");
|
|
6
|
+
const logger = require("../func/logger");
|
|
6
7
|
|
|
7
8
|
const { config } = loadConfig();
|
|
8
9
|
global.fca = { config };
|
|
9
10
|
|
|
11
|
+
// Global error handlers to prevent bot crashes
|
|
12
|
+
// Handle unhandled promise rejections (e.g., fetch timeouts, network errors)
|
|
13
|
+
if (!global.fca._errorHandlersInstalled) {
|
|
14
|
+
global.fca._errorHandlersInstalled = true;
|
|
15
|
+
|
|
16
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
17
|
+
try {
|
|
18
|
+
// Check if it's a fetch/network timeout error
|
|
19
|
+
if (reason && typeof reason === "object") {
|
|
20
|
+
const errorCode = reason.code || reason.cause?.code;
|
|
21
|
+
const errorMessage = reason.message || String(reason);
|
|
22
|
+
|
|
23
|
+
// Handle fetch timeout errors gracefully
|
|
24
|
+
if (errorCode === "UND_ERR_CONNECT_TIMEOUT" ||
|
|
25
|
+
errorCode === "ETIMEDOUT" ||
|
|
26
|
+
errorMessage.includes("Connect Timeout") ||
|
|
27
|
+
errorMessage.includes("fetch failed")) {
|
|
28
|
+
logger(`Network timeout error caught (non-fatal): ${errorMessage}`, "warn");
|
|
29
|
+
return; // Don't crash, just log
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Handle other network errors
|
|
33
|
+
if (errorCode === "ECONNREFUSED" ||
|
|
34
|
+
errorCode === "ENOTFOUND" ||
|
|
35
|
+
errorCode === "ECONNRESET" ||
|
|
36
|
+
errorMessage.includes("ECONNREFUSED") ||
|
|
37
|
+
errorMessage.includes("ENOTFOUND")) {
|
|
38
|
+
logger(`Network connection error caught (non-fatal): ${errorMessage}`, "warn");
|
|
39
|
+
return; // Don't crash, just log
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// For other unhandled rejections, log but don't crash
|
|
44
|
+
logger(`Unhandled promise rejection (non-fatal): ${reason && reason.message ? reason.message : String(reason)}`, "error");
|
|
45
|
+
} catch (e) {
|
|
46
|
+
// Fallback if logger fails
|
|
47
|
+
console.error("[FCA-ERROR] Unhandled promise rejection:", reason);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Handle uncaught exceptions (last resort)
|
|
52
|
+
process.on("uncaughtException", (error) => {
|
|
53
|
+
try {
|
|
54
|
+
const errorMessage = error.message || String(error);
|
|
55
|
+
const errorCode = error.code;
|
|
56
|
+
|
|
57
|
+
// Handle fetch/network errors
|
|
58
|
+
if (errorCode === "UND_ERR_CONNECT_TIMEOUT" ||
|
|
59
|
+
errorCode === "ETIMEDOUT" ||
|
|
60
|
+
errorMessage.includes("Connect Timeout") ||
|
|
61
|
+
errorMessage.includes("fetch failed")) {
|
|
62
|
+
logger(`Uncaught network timeout error (non-fatal): ${errorMessage}`, "warn");
|
|
63
|
+
return; // Don't crash
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// For other uncaught exceptions, log but try to continue
|
|
67
|
+
logger(`Uncaught exception (attempting to continue): ${errorMessage}`, "error");
|
|
68
|
+
// Note: We don't exit here to allow bot to continue running
|
|
69
|
+
// In production, you might want to restart the process instead
|
|
70
|
+
} catch (e) {
|
|
71
|
+
// Fallback if logger fails
|
|
72
|
+
console.error("[FCA-ERROR] Uncaught exception:", error);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
10
77
|
function login(loginData, options, callback) {
|
|
11
78
|
if (getType(options) === "Function" || getType(options) === "AsyncFunction") {
|
|
12
79
|
callback = options;
|
package/module/loginHelper.js
CHANGED
|
@@ -465,14 +465,40 @@ async function hydrateJarFromDB(userID) {
|
|
|
465
465
|
}
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
-
async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions, ctxRef) {
|
|
468
|
+
async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions, ctxRef, hadAppStateInput = false) {
|
|
469
469
|
const getUID = cs =>
|
|
470
470
|
cs.find(c => c.key === "i_user")?.value ||
|
|
471
471
|
cs.find(c => c.key === "c_user")?.value ||
|
|
472
472
|
cs.find(c => c.name === "i_user")?.value ||
|
|
473
473
|
cs.find(c => c.name === "c_user")?.value;
|
|
474
|
+
const htmlUID = body => {
|
|
475
|
+
const s = typeof body === "string" ? body : String(body ?? "");
|
|
476
|
+
return s.match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s.match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
|
|
477
|
+
};
|
|
474
478
|
let userID = getUID(currentCookies);
|
|
479
|
+
// Also try to extract userID from HTML if not found in cookies
|
|
480
|
+
if (!userID) {
|
|
481
|
+
userID = htmlUID(currentHtml);
|
|
482
|
+
}
|
|
475
483
|
if (userID) return { html: currentHtml, cookies: currentCookies, userID };
|
|
484
|
+
// If appState/Cookie was provided and is not dead (not checkpointed), skip backup
|
|
485
|
+
if (hadAppStateInput) {
|
|
486
|
+
const isCheckpoint = currentHtml.includes("/checkpoint/block/?next");
|
|
487
|
+
if (!isCheckpoint) {
|
|
488
|
+
// AppState provided and not checkpointed, but userID not found
|
|
489
|
+
// This might be a temporary issue - try to refresh cookies from jar
|
|
490
|
+
try {
|
|
491
|
+
const refreshedCookies = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
|
|
492
|
+
userID = getUID(refreshedCookies);
|
|
493
|
+
if (userID) {
|
|
494
|
+
return { html: currentHtml, cookies: refreshedCookies, userID };
|
|
495
|
+
}
|
|
496
|
+
} catch { }
|
|
497
|
+
// If still no userID, skip backup and throw error
|
|
498
|
+
throw new Error("Missing user cookie from provided appState");
|
|
499
|
+
}
|
|
500
|
+
// AppState is dead (checkpointed), proceed to backup/email login
|
|
501
|
+
}
|
|
476
502
|
const hydrated = await hydrateJarFromDB(null);
|
|
477
503
|
if (hydrated) {
|
|
478
504
|
logger("AppState backup live — proceeding to login", "info");
|
|
@@ -524,6 +550,26 @@ function makeLogin(j, email, password, globalOptions) {
|
|
|
524
550
|
function loginHelper(appState, Cookie, email, password, globalOptions, callback) {
|
|
525
551
|
try {
|
|
526
552
|
const domain = ".facebook.com";
|
|
553
|
+
// Helper to extract userID from appState input
|
|
554
|
+
const extractUIDFromAppState = (appStateInput) => {
|
|
555
|
+
if (!appStateInput) return null;
|
|
556
|
+
let parsed = appStateInput;
|
|
557
|
+
if (typeof appStateInput === "string") {
|
|
558
|
+
try {
|
|
559
|
+
parsed = JSON.parse(appStateInput);
|
|
560
|
+
} catch {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (Array.isArray(parsed)) {
|
|
565
|
+
const cUser = parsed.find(c => (c.key === "c_user" || c.name === "c_user"));
|
|
566
|
+
if (cUser) return cUser.value;
|
|
567
|
+
const iUser = parsed.find(c => (c.key === "i_user" || c.name === "i_user"));
|
|
568
|
+
if (iUser) return iUser.value;
|
|
569
|
+
}
|
|
570
|
+
return null;
|
|
571
|
+
};
|
|
572
|
+
let userIDFromAppState = extractUIDFromAppState(appState);
|
|
527
573
|
try {
|
|
528
574
|
if (appState) {
|
|
529
575
|
if (typeof appState === "string") {
|
|
@@ -673,13 +719,27 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
|
|
|
673
719
|
const processed = (await ctx.bypassAutomation(res, jar)) || res;
|
|
674
720
|
let html = processed && processed.data ? processed.data : "";
|
|
675
721
|
let cookies = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
722
|
+
const getUIDFromCookies = cs =>
|
|
723
|
+
cs.find(c => c.key === "i_user")?.value ||
|
|
724
|
+
cs.find(c => c.key === "c_user")?.value ||
|
|
725
|
+
cs.find(c => c.name === "i_user")?.value ||
|
|
726
|
+
cs.find(c => c.name === "c_user")?.value;
|
|
727
|
+
const getUIDFromHTML = body => {
|
|
728
|
+
const s = typeof body === "string" ? body : String(body ?? "");
|
|
729
|
+
return s.match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s.match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
|
|
730
|
+
};
|
|
731
|
+
let userID = getUIDFromCookies(cookies);
|
|
732
|
+
// Also try to extract userID from HTML if not found in cookies
|
|
733
|
+
if (!userID) {
|
|
734
|
+
userID = getUIDFromHTML(html);
|
|
735
|
+
}
|
|
736
|
+
// If still not found and appState was provided, use userID from appState input as fallback
|
|
737
|
+
if (!userID && userIDFromAppState) {
|
|
738
|
+
userID = userIDFromAppState;
|
|
739
|
+
}
|
|
681
740
|
if (!userID) {
|
|
682
|
-
|
|
741
|
+
// Pass hadAppStateInput=true if appState/Cookie was originally provided
|
|
742
|
+
const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions, ctx, !!(appState || Cookie));
|
|
683
743
|
html = retried.html;
|
|
684
744
|
cookies = retried.cookies;
|
|
685
745
|
userID = retried.userID;
|
package/package.json
CHANGED