@dongdev/fca-unofficial 3.0.7 → 3.0.9
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 +12 -2
- package/package.json +1 -1
- package/src/api/threads/getThreadList.js +49 -4
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,7 +465,7 @@ 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 ||
|
|
@@ -473,6 +473,15 @@ async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions,
|
|
|
473
473
|
cs.find(c => c.name === "c_user")?.value;
|
|
474
474
|
let userID = getUID(currentCookies);
|
|
475
475
|
if (userID) return { html: currentHtml, cookies: currentCookies, userID };
|
|
476
|
+
// If appState/Cookie was provided and is not dead (not checkpointed), skip backup
|
|
477
|
+
if (hadAppStateInput) {
|
|
478
|
+
const isCheckpoint = currentHtml.includes("/checkpoint/block/?next");
|
|
479
|
+
if (!isCheckpoint) {
|
|
480
|
+
// AppState provided and not checkpointed, skip backup - just throw error
|
|
481
|
+
throw new Error("Missing user cookie from provided appState");
|
|
482
|
+
}
|
|
483
|
+
// AppState is dead (checkpointed), proceed to backup/email login
|
|
484
|
+
}
|
|
476
485
|
const hydrated = await hydrateJarFromDB(null);
|
|
477
486
|
if (hydrated) {
|
|
478
487
|
logger("AppState backup live — proceeding to login", "info");
|
|
@@ -679,7 +688,8 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
|
|
|
679
688
|
cookies.find(c => c.name === "i_user")?.value ||
|
|
680
689
|
cookies.find(c => c.name === "c_user")?.value;
|
|
681
690
|
if (!userID) {
|
|
682
|
-
|
|
691
|
+
// Pass skipBackup=true if appState/Cookie was originally provided
|
|
692
|
+
const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions, ctx, !!(appState || Cookie));
|
|
683
693
|
html = retried.html;
|
|
684
694
|
cookies = retried.cookies;
|
|
685
695
|
userID = retried.userID;
|
package/package.json
CHANGED
|
@@ -226,14 +226,59 @@ module.exports = function(defaultFuncs, api, ctx) {
|
|
|
226
226
|
.post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
|
|
227
227
|
.then(parseAndCheckLogin(ctx, defaultFuncs))
|
|
228
228
|
.then(resData => {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
// Validate resData is an array and has elements
|
|
230
|
+
if (!resData || !Array.isArray(resData) || resData.length === 0) {
|
|
231
|
+
throw {
|
|
232
|
+
error: "getThreadList: Invalid response data - resData is not a valid array",
|
|
233
|
+
res: resData
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Validate last element exists and has required properties
|
|
238
|
+
const lastElement = resData[resData.length - 1];
|
|
239
|
+
if (!lastElement || typeof lastElement !== "object") {
|
|
240
|
+
throw {
|
|
241
|
+
error: "getThreadList: Invalid response data - last element is missing or invalid",
|
|
242
|
+
res: resData
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (lastElement.error_results > 0) {
|
|
247
|
+
// Check if first element and o0 exist before accessing errors
|
|
248
|
+
if (resData[0] && resData[0].o0 && resData[0].o0.errors) {
|
|
249
|
+
throw resData[0].o0.errors;
|
|
250
|
+
} else {
|
|
251
|
+
throw {
|
|
252
|
+
error: "getThreadList: Error results > 0 but error details not available",
|
|
253
|
+
res: resData
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (lastElement.successful_results === 0) {
|
|
232
259
|
throw {
|
|
233
260
|
error: "getThreadList: there was no successful_results",
|
|
234
261
|
res: resData
|
|
235
262
|
};
|
|
236
|
-
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Validate first element and nested data structure
|
|
266
|
+
if (!resData[0] || !resData[0].o0 || !resData[0].o0.data ||
|
|
267
|
+
!resData[0].o0.data.viewer || !resData[0].o0.data.viewer.message_threads ||
|
|
268
|
+
!Array.isArray(resData[0].o0.data.viewer.message_threads.nodes)) {
|
|
269
|
+
throw {
|
|
270
|
+
error: "getThreadList: Invalid response data structure - missing required fields",
|
|
271
|
+
res: resData
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (timestamp) {
|
|
276
|
+
const nodes = resData[0].o0.data.viewer.message_threads.nodes;
|
|
277
|
+
if (Array.isArray(nodes) && nodes.length > 0) {
|
|
278
|
+
nodes.shift();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
237
282
|
callback(
|
|
238
283
|
null,
|
|
239
284
|
formatThreadList(resData[0].o0.data.viewer.message_threads.nodes)
|