@dongdev/fca-unofficial 3.0.23 → 3.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 +6 -0
- package/DOCS.md +3 -3
- package/Fca_Database/database.sqlite +0 -0
- package/LICENSE-MIT +1 -1
- package/README.md +1 -1
- package/index.d.ts +1 -1
- package/module/config.js +3 -7
- package/module/login.js +2 -4
- package/module/loginHelper.js +479 -287
- package/package.json +6 -7
- package/src/api/messaging/changeAdminStatus.js +56 -52
- package/src/api/messaging/changeGroupImage.js +2 -1
- package/src/api/messaging/changeThreadEmoji.js +47 -42
- package/src/api/messaging/createPoll.js +46 -43
- package/src/api/messaging/deleteMessage.js +110 -30
- package/src/api/messaging/forwardAttachment.js +28 -22
- package/src/api/messaging/removeUserFromGroup.js +11 -55
- package/src/api/messaging/sendMessage.js +15 -17
- package/src/api/messaging/sendTypingIndicator.js +23 -16
- package/src/api/messaging/setMessageReaction.js +91 -76
- package/src/api/messaging/setTitle.js +47 -42
- package/src/api/messaging/shareContact.js +1 -1
- package/src/api/messaging/uploadAttachment.js +471 -73
- package/src/api/socket/core/connectMqtt.js +0 -5
- package/src/api/socket/core/emitAuth.js +1 -8
- package/src/api/socket/core/getSeqID.js +292 -10
- package/src/api/socket/listenMqtt.js +15 -7
- package/src/utils/client.js +2 -312
- package/src/utils/cookies.js +68 -0
- package/src/utils/format.js +117 -90
- package/src/utils/loginParser.js +347 -0
- package/src/utils/messageFormat.js +1173 -0
package/module/loginHelper.js
CHANGED
|
@@ -8,13 +8,8 @@ const { saveCookies, getAppState } = require("../src/utils/client");
|
|
|
8
8
|
const { getFrom } = require("../src/utils/constants");
|
|
9
9
|
const { loadConfig } = require("./config");
|
|
10
10
|
const { config } = loadConfig();
|
|
11
|
-
const { v4: uuidv4 } = require("uuid");
|
|
12
|
-
const { CookieJar } = require("tough-cookie");
|
|
13
|
-
const { wrapper } = require("axios-cookiejar-support");
|
|
14
11
|
const axiosBase = require("axios");
|
|
15
|
-
const
|
|
16
|
-
const crypto = require("crypto");
|
|
17
|
-
const { TOTP } = require("totp-generator");
|
|
12
|
+
const parseUserHtml = require("./parseUseerHtml");
|
|
18
13
|
|
|
19
14
|
const regions = [
|
|
20
15
|
{ code: "PRN", name: "Pacific Northwest Region", location: "Khu vực Tây Bắc Thái Bình Dương" },
|
|
@@ -53,81 +48,167 @@ function mask(s, keep = 3) {
|
|
|
53
48
|
return n <= keep ? "*".repeat(n) : s.slice(0, keep) + "*".repeat(Math.max(0, n - keep));
|
|
54
49
|
}
|
|
55
50
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
51
|
+
/**
|
|
52
|
+
* Login via external API endpoint (iOS method)
|
|
53
|
+
* @param {string} email - Email hoặc số điện thoại
|
|
54
|
+
* @param {string} password - Mật khẩu
|
|
55
|
+
* @param {string|null} twoFactor - Secret Base32 cho 2FA (không phải mã 6 số)
|
|
56
|
+
* @param {string|null} apiBaseUrl - Base URL của API server (mặc định: https://minhdong.site)
|
|
57
|
+
* @param {string|null} apiKey - API key để xác thực (x-api-key header)
|
|
58
|
+
* @returns {Promise<{ok: boolean, uid?: string, access_token?: string, cookies?: Array, cookie?: string, message?: string}>}
|
|
59
|
+
*/
|
|
60
|
+
async function loginViaAPI(email, password, twoFactor = null, apiBaseUrl = null, apiKey = null) {
|
|
61
|
+
try {
|
|
62
|
+
const baseUrl = apiBaseUrl || config.apiServer || "https://minhdong.site";
|
|
63
|
+
const endpoint = `${baseUrl}/api/v1/facebook/login_ios`;
|
|
64
|
+
const xApiKey = apiKey || config.apiKey || null;
|
|
59
65
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
+
// Build request body
|
|
67
|
+
const body = {
|
|
68
|
+
email,
|
|
69
|
+
password
|
|
70
|
+
};
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
72
|
+
// Only include twoFactor if provided (must be Base32 secret, not 6-digit code)
|
|
73
|
+
if (twoFactor && typeof twoFactor === "string" && twoFactor.trim()) {
|
|
74
|
+
// Clean up the secret - remove spaces and convert to uppercase
|
|
75
|
+
body.twoFactor = twoFactor.replace(/\s+/g, "").toUpperCase();
|
|
76
|
+
}
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
78
|
+
// Build headers
|
|
79
|
+
const headers = {
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
"Accept": "application/json"
|
|
82
|
+
};
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
// Add x-api-key header if provided
|
|
85
|
+
if (xApiKey) {
|
|
86
|
+
headers["x-api-key"] = xApiKey;
|
|
87
|
+
}
|
|
83
88
|
|
|
84
|
-
|
|
85
|
-
return arr[Math.floor(Math.random() * arr.length)];
|
|
86
|
-
}
|
|
89
|
+
logger(`API-LOGIN: Attempting login for ${mask(email, 2)} via iOS API`, "info");
|
|
87
90
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
const response = await axiosBase({
|
|
92
|
+
method: "POST",
|
|
93
|
+
url: endpoint,
|
|
94
|
+
headers,
|
|
95
|
+
data: body,
|
|
96
|
+
timeout: 60000,
|
|
97
|
+
validateStatus: () => true
|
|
98
|
+
});
|
|
99
|
+
if (response.status === 200 && response.data) {
|
|
100
|
+
const data = response.data;
|
|
92
101
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
102
|
+
// Check if login was successful
|
|
103
|
+
if (data.error) {
|
|
104
|
+
logger(`API-LOGIN: Login failed - ${data.error}`, "error");
|
|
105
|
+
return { ok: false, message: data.error };
|
|
106
|
+
}
|
|
97
107
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
// Extract response data
|
|
109
|
+
const uid = data.uid || data.user_id || data.userId || null;
|
|
110
|
+
const accessToken = data.access_token || data.accessToken || null;
|
|
111
|
+
const cookie = data.cookie || data.cookies || null;
|
|
101
112
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const a = choice(androidVersions);
|
|
109
|
-
const d = choice(devices);
|
|
110
|
-
const b = randomBuildId();
|
|
111
|
-
const r = randomResolution();
|
|
112
|
-
const fbav = randomFbav();
|
|
113
|
-
const fbbv = rand(320000000, 520000000);
|
|
114
|
-
const arch = `${choice(archs)}:${choice(archs)}`;
|
|
115
|
-
const ua = `Dalvik/2.1.0 (Linux; U; Android ${a}; ${d.model} Build/${b}) [FBAN/Orca-Android;FBAV/${fbav};FBPN/com.facebook.orca;FBLC/${choice(locales)};FBBV/${fbbv};FBCR/${choice(carriers)};FBMF/${d.brand};FBBD/${d.brand};FBDV/${d.model};FBSV/${a};FBCA/${arch};FBDM/{density=${r.d.toFixed(1)},width=${r.w},height=${r.h}};FB_FW/1;]`;
|
|
116
|
-
return ua;
|
|
117
|
-
}
|
|
113
|
+
if (!uid && !accessToken && !cookie) {
|
|
114
|
+
logger("API-LOGIN: Response missing required fields (uid, access_token, cookie)", "warn");
|
|
115
|
+
return { ok: false, message: "Invalid response from API" };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
logger(`API-LOGIN: Login successful for UID: ${uid || "unknown"}`, "info");
|
|
118
119
|
|
|
119
|
-
|
|
120
|
+
// Parse cookies if provided as string
|
|
121
|
+
let cookies = [];
|
|
122
|
+
if (typeof cookie === "string") {
|
|
123
|
+
// Parse cookie string format: "key1=value1; key2=value2"
|
|
124
|
+
const pairs = cookie.split(";").map(p => p.trim()).filter(Boolean);
|
|
125
|
+
for (const pair of pairs) {
|
|
126
|
+
const eq = pair.indexOf("=");
|
|
127
|
+
if (eq > 0) {
|
|
128
|
+
const key = pair.slice(0, eq).trim();
|
|
129
|
+
const value = pair.slice(eq + 1).trim();
|
|
130
|
+
cookies.push({
|
|
131
|
+
key,
|
|
132
|
+
value,
|
|
133
|
+
domain: ".facebook.com",
|
|
134
|
+
path: "/"
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} else if (Array.isArray(cookie)) {
|
|
139
|
+
// Already in array format
|
|
140
|
+
cookies = cookie.map(c => ({
|
|
141
|
+
key: c.key || c.name,
|
|
142
|
+
value: c.value,
|
|
143
|
+
domain: c.domain || ".facebook.com",
|
|
144
|
+
path: c.path || "/"
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
ok: true,
|
|
150
|
+
uid,
|
|
151
|
+
access_token: accessToken,
|
|
152
|
+
cookies,
|
|
153
|
+
cookie: typeof cookie === "string" ? cookie : null
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Handle error responses
|
|
158
|
+
const errorMsg = response.data && response.data.error
|
|
159
|
+
? response.data.error
|
|
160
|
+
: response.data && response.data.message
|
|
161
|
+
? response.data.message
|
|
162
|
+
: `HTTP ${response.status}`;
|
|
120
163
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
164
|
+
logger(`API-LOGIN: Login failed - ${errorMsg}`, "error");
|
|
165
|
+
return { ok: false, message: errorMsg };
|
|
166
|
+
|
|
167
|
+
} catch (error) {
|
|
168
|
+
const errMsg = error && error.message ? error.message : String(error);
|
|
169
|
+
logger(`API-LOGIN: Request failed - ${errMsg}`, "error");
|
|
170
|
+
return { ok: false, message: errMsg };
|
|
171
|
+
}
|
|
124
172
|
}
|
|
125
173
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
174
|
+
/**
|
|
175
|
+
* High-level login function that uses the API endpoint
|
|
176
|
+
* @param {string} email - Email hoặc số điện thoại
|
|
177
|
+
* @param {string} password - Mật khẩu
|
|
178
|
+
* @param {string|null} twoFactor - Secret Base32 cho 2FA (không phải mã 6 số)
|
|
179
|
+
* @param {string|null} apiBaseUrl - Base URL của API server
|
|
180
|
+
* @returns {Promise<{status: boolean, cookies?: Array, uid?: string, access_token?: string, message?: string}>}
|
|
181
|
+
*/
|
|
182
|
+
async function tokensViaAPI(email, password, twoFactor = null, apiBaseUrl = null) {
|
|
183
|
+
const t0 = process.hrtime.bigint();
|
|
184
|
+
|
|
185
|
+
if (!email || !password) {
|
|
186
|
+
return { status: false, message: "Please provide email and password" };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
logger(`API-LOGIN: Initialize login ${mask(email, 2)}`, "info");
|
|
190
|
+
|
|
191
|
+
const res = await loginViaAPI(email, password, twoFactor, apiBaseUrl);
|
|
192
|
+
|
|
193
|
+
if (res && res.ok) {
|
|
194
|
+
logger(`API-LOGIN: Login success - UID: ${res.uid}`, "info");
|
|
195
|
+
const t1 = Number(process.hrtime.bigint() - t0) / 1e6;
|
|
196
|
+
logger(`Done API login ${Math.round(t1)}ms`, "info");
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
status: true,
|
|
200
|
+
cookies: res.cookies,
|
|
201
|
+
uid: res.uid,
|
|
202
|
+
access_token: res.access_token,
|
|
203
|
+
cookie: res.cookie
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
status: false,
|
|
209
|
+
message: res && res.message ? res.message : "Login failed"
|
|
210
|
+
};
|
|
211
|
+
}
|
|
131
212
|
|
|
132
213
|
function normalizeCookieHeaderString(s) {
|
|
133
214
|
let str = String(s || "").trim();
|
|
@@ -149,17 +230,36 @@ function normalizeCookieHeaderString(s) {
|
|
|
149
230
|
|
|
150
231
|
function setJarFromPairs(j, pairs, domain) {
|
|
151
232
|
const expires = new Date(Date.now() + 31536e6).toUTCString();
|
|
233
|
+
// URLs to set cookies for - include both desktop and mobile versions
|
|
234
|
+
const urls = [
|
|
235
|
+
"https://www.facebook.com",
|
|
236
|
+
"https://facebook.com",
|
|
237
|
+
"https://m.facebook.com",
|
|
238
|
+
"http://www.facebook.com",
|
|
239
|
+
"http://facebook.com",
|
|
240
|
+
"http://m.facebook.com"
|
|
241
|
+
];
|
|
242
|
+
|
|
152
243
|
for (const kv of pairs) {
|
|
153
244
|
const cookieStr = `${kv}; expires=${expires}; domain=${domain}; path=/;`;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
245
|
+
// Set cookie for all URLs to ensure it works on both desktop and mobile
|
|
246
|
+
for (const url of urls) {
|
|
247
|
+
try {
|
|
248
|
+
if (typeof j.setCookieSync === "function") {
|
|
249
|
+
j.setCookieSync(cookieStr, url);
|
|
250
|
+
} else if (typeof j.setCookie === "function") {
|
|
251
|
+
j.setCookie(cookieStr, url);
|
|
252
|
+
}
|
|
253
|
+
} catch (err) {
|
|
254
|
+
// Silently ignore domain mismatch errors
|
|
255
|
+
// These can happen when setting cookies across different subdomains
|
|
256
|
+
}
|
|
257
|
+
}
|
|
158
258
|
}
|
|
159
259
|
}
|
|
160
260
|
|
|
161
261
|
function cookieHeaderFromJar(j) {
|
|
162
|
-
const urls = ["https://www.facebook.com"
|
|
262
|
+
const urls = ["https://www.facebook.com"];
|
|
163
263
|
const seen = new Set();
|
|
164
264
|
const parts = [];
|
|
165
265
|
for (const u of urls) {
|
|
@@ -249,10 +349,6 @@ async function backupAppStateSQL(j, userID) {
|
|
|
249
349
|
const ck = cookieHeaderFromJar(j);
|
|
250
350
|
await upsertBackup(Model, userID, "appstate", JSON.stringify(appJson));
|
|
251
351
|
await upsertBackup(Model, userID, "cookie", ck);
|
|
252
|
-
try {
|
|
253
|
-
const out = path.join(process.cwd(), "appstate.json");
|
|
254
|
-
fs.writeFileSync(out, JSON.stringify(appJson, null, 2));
|
|
255
|
-
} catch { }
|
|
256
352
|
logger("Backup stored (overwrite mode)", "info");
|
|
257
353
|
} catch (e) {
|
|
258
354
|
logger(`Failed to save appstate backup ${e && e.message ? e.message : String(e)}`, "warn");
|
|
@@ -281,20 +377,7 @@ async function getLatestBackupAny(type) {
|
|
|
281
377
|
}
|
|
282
378
|
}
|
|
283
379
|
|
|
284
|
-
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;]";
|
|
285
380
|
|
|
286
|
-
function encodesig(obj) {
|
|
287
|
-
let data = "";
|
|
288
|
-
Object.keys(obj).forEach(k => { data += `${k}=${obj[k]}`; });
|
|
289
|
-
return md5(data + "62f8ce9f74b12f84c123cc23437a4a32");
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function sort(obj) {
|
|
293
|
-
const keys = Object.keys(obj).sort();
|
|
294
|
-
const out = {};
|
|
295
|
-
for (const k of keys) out[k] = obj[k];
|
|
296
|
-
return out;
|
|
297
|
-
}
|
|
298
381
|
|
|
299
382
|
async function setJarCookies(j, appstate) {
|
|
300
383
|
const tasks = [];
|
|
@@ -357,48 +440,24 @@ async function setJarCookies(j, appstate) {
|
|
|
357
440
|
|
|
358
441
|
return cookieParts.join("; ");
|
|
359
442
|
};
|
|
360
|
-
|
|
361
|
-
// Determine target URLs and cookie strings based on domain
|
|
362
443
|
const cookieConfigs = [];
|
|
363
|
-
|
|
364
|
-
// For .facebook.com domain, set for both facebook.com and messenger.com
|
|
365
444
|
if (cookieDomain === ".facebook.com" || cookieDomain === "facebook.com") {
|
|
366
|
-
// Set for facebook.com with .facebook.com domain
|
|
367
445
|
cookieConfigs.push({ url: `http://${dom}${cookiePath}`, cookieStr: buildCookieString() });
|
|
368
446
|
cookieConfigs.push({ url: `https://${dom}${cookiePath}`, cookieStr: buildCookieString() });
|
|
369
447
|
cookieConfigs.push({ url: `http://www.${dom}${cookiePath}`, cookieStr: buildCookieString() });
|
|
370
448
|
cookieConfigs.push({ url: `https://www.${dom}${cookiePath}`, cookieStr: buildCookieString() });
|
|
371
|
-
|
|
372
|
-
// Set for messenger.com with .messenger.com domain (or without domain for host-only)
|
|
373
|
-
// Use .messenger.com domain to allow cross-subdomain sharing
|
|
374
|
-
cookieConfigs.push({ url: `http://messenger.com${cookiePath}`, cookieStr: buildCookieString(".messenger.com") });
|
|
375
|
-
cookieConfigs.push({ url: `https://messenger.com${cookiePath}`, cookieStr: buildCookieString(".messenger.com") });
|
|
376
|
-
cookieConfigs.push({ url: `http://www.messenger.com${cookiePath}`, cookieStr: buildCookieString(".messenger.com") });
|
|
377
|
-
cookieConfigs.push({ url: `https://www.messenger.com${cookiePath}`, cookieStr: buildCookieString(".messenger.com") });
|
|
378
|
-
} else if (cookieDomain === ".messenger.com" || cookieDomain === "messenger.com") {
|
|
379
|
-
// Set for messenger.com only
|
|
380
|
-
const messengerDom = cookieDomain.replace(/^\./, "");
|
|
381
|
-
cookieConfigs.push({ url: `http://${messengerDom}${cookiePath}`, cookieStr: buildCookieString() });
|
|
382
|
-
cookieConfigs.push({ url: `https://${messengerDom}${cookiePath}`, cookieStr: buildCookieString() });
|
|
383
|
-
cookieConfigs.push({ url: `http://www.${messengerDom}${cookiePath}`, cookieStr: buildCookieString() });
|
|
384
|
-
cookieConfigs.push({ url: `https://www.${messengerDom}${cookiePath}`, cookieStr: buildCookieString() });
|
|
385
449
|
} else {
|
|
386
|
-
// For other domains, set normally
|
|
387
450
|
cookieConfigs.push({ url: `http://${dom}${cookiePath}`, cookieStr: buildCookieString() });
|
|
388
451
|
cookieConfigs.push({ url: `https://${dom}${cookiePath}`, cookieStr: buildCookieString() });
|
|
389
452
|
cookieConfigs.push({ url: `http://www.${dom}${cookiePath}`, cookieStr: buildCookieString() });
|
|
390
453
|
cookieConfigs.push({ url: `https://www.${dom}${cookiePath}`, cookieStr: buildCookieString() });
|
|
391
454
|
}
|
|
392
455
|
|
|
393
|
-
// Set cookie for all target URLs, silently catch domain errors
|
|
394
456
|
for (const config of cookieConfigs) {
|
|
395
457
|
tasks.push(j.setCookie(config.cookieStr, config.url).catch((err) => {
|
|
396
|
-
// Silently ignore domain mismatch errors for cross-domain cookies
|
|
397
|
-
// These are expected when setting cookies across domains
|
|
398
458
|
if (err && err.message && err.message.includes("Cookie not in this host's domain")) {
|
|
399
|
-
return;
|
|
459
|
+
return;
|
|
400
460
|
}
|
|
401
|
-
// Log other errors but don't throw
|
|
402
461
|
return;
|
|
403
462
|
}));
|
|
404
463
|
}
|
|
@@ -406,142 +465,9 @@ async function setJarCookies(j, appstate) {
|
|
|
406
465
|
await Promise.all(tasks);
|
|
407
466
|
}
|
|
408
467
|
|
|
409
|
-
|
|
410
|
-
const cookieJar = externalJar instanceof CookieJar ? externalJar : new CookieJar();
|
|
411
|
-
const client = wrapper(axiosBase.create({ jar: cookieJar, withCredentials: true, timeout: 30000, validateStatus: () => true }));
|
|
412
|
-
const device_id = uuidv4();
|
|
413
|
-
const family_device_id = device_id;
|
|
414
|
-
const machine_id = randomString(24);
|
|
415
|
-
const base = {
|
|
416
|
-
adid: "00000000-0000-0000-0000-000000000000",
|
|
417
|
-
format: "json",
|
|
418
|
-
device_id,
|
|
419
|
-
email: username,
|
|
420
|
-
password,
|
|
421
|
-
generate_analytics_claim: "1",
|
|
422
|
-
community_id: "",
|
|
423
|
-
cpl: "true",
|
|
424
|
-
try_num: "1",
|
|
425
|
-
family_device_id,
|
|
426
|
-
secure_family_device_id: "",
|
|
427
|
-
credentials_type: "password",
|
|
428
|
-
enroll_misauth: "false",
|
|
429
|
-
generate_session_cookies: "1",
|
|
430
|
-
source: "login",
|
|
431
|
-
generate_machine_id: "1",
|
|
432
|
-
jazoest: "22297",
|
|
433
|
-
meta_inf_fbmeta: "NO_FILE",
|
|
434
|
-
advertiser_id: "00000000-0000-0000-0000-000000000000",
|
|
435
|
-
currently_logged_in_userid: "0",
|
|
436
|
-
locale: "vi_VN",
|
|
437
|
-
client_country_code: "VN",
|
|
438
|
-
fb_api_req_friendly_name: "authenticate",
|
|
439
|
-
fb_api_caller_class: "AuthOperations$PasswordAuthOperation",
|
|
440
|
-
api_key: "256002347743983",
|
|
441
|
-
access_token: "256002347743983|374e60f8b9bb6b8cbb30f78030438895"
|
|
442
|
-
};
|
|
443
|
-
const headers = {
|
|
444
|
-
"User-Agent": MESSENGER_USER_AGENT,
|
|
445
|
-
"Accept-Encoding": "gzip, deflate",
|
|
446
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
447
|
-
"x-fb-connection-quality": "EXCELLENT",
|
|
448
|
-
"x-fb-sim-hni": "45204",
|
|
449
|
-
"x-fb-net-hni": "45204",
|
|
450
|
-
"x-fb-connection-type": "WIFI",
|
|
451
|
-
"x-tigon-is-retry": "False",
|
|
452
|
-
"x-fb-friendly-name": "authenticate",
|
|
453
|
-
"x-fb-request-analytics-tags": '{"network_tags":{"retry_attempt":"0"},"application_tags":"unknown"}',
|
|
454
|
-
"x-fb-http-engine": "Liger",
|
|
455
|
-
"x-fb-client-ip": "True",
|
|
456
|
-
"x-fb-server-cluster": "True",
|
|
457
|
-
authorization: `OAuth ${base.access_token}`
|
|
458
|
-
};
|
|
459
|
-
const form1 = { ...base };
|
|
460
|
-
form1.sig = encodesig(sort(form1));
|
|
461
|
-
const res1 = await client.request({ url: "https://b-graph.facebook.com/auth/login", method: "post", data: qs.stringify(form1), headers });
|
|
462
|
-
if (res1.status === 200 && res1.data && res1.data.session_cookies) {
|
|
463
|
-
const appstate = res1.data.session_cookies.map(c => ({ key: c.name, value: c.value, domain: c.domain, path: c.path }));
|
|
464
|
-
const cUserCookie = appstate.find(c => c.key === "c_user");
|
|
465
|
-
if (i_user) appstate.push({ key: "i_user", value: i_user, domain: ".facebook.com", path: "/" });
|
|
466
|
-
else if (cUserCookie) appstate.push({ key: "i_user", value: cUserCookie.value, domain: ".facebook.com", path: "/" });
|
|
467
|
-
await setJarCookies(cookieJar, appstate);
|
|
468
|
-
let eaau = null;
|
|
469
|
-
let eaad6v7 = null;
|
|
470
|
-
try {
|
|
471
|
-
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}` } });
|
|
472
|
-
eaau = r1.data && r1.data.access_token ? r1.data.access_token : null;
|
|
473
|
-
} catch { }
|
|
474
|
-
try {
|
|
475
|
-
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}` } });
|
|
476
|
-
eaad6v7 = r2.data && r2.data.access_token ? r2.data.access_token : null;
|
|
477
|
-
} catch { }
|
|
478
|
-
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 };
|
|
479
|
-
}
|
|
480
|
-
const err = res1 && res1.data && res1.data.error ? res1.data.error : {};
|
|
481
|
-
if (err && err.code === 406) {
|
|
482
|
-
const data = err.error_data || {};
|
|
483
|
-
let code = null;
|
|
484
|
-
if (twofactorSecretOrCode && /^\d{6}$/.test(String(twofactorSecretOrCode))) code = String(twofactorSecretOrCode);
|
|
485
|
-
else if (twofactorSecretOrCode) {
|
|
486
|
-
try {
|
|
487
|
-
const clean = decodeURI(twofactorSecretOrCode).replace(/\s+/g, "").toUpperCase();
|
|
488
|
-
const { otp } = await TOTP.generate(clean);
|
|
489
|
-
code = otp;
|
|
490
|
-
} catch { }
|
|
491
|
-
} else if (config.credentials?.twofactor) {
|
|
492
|
-
const { otp } = await TOTP.generate(String(config.credentials.twofactor).replace(/\s+/g, "").toUpperCase());
|
|
493
|
-
code = otp;
|
|
494
|
-
}
|
|
495
|
-
if (!code) return { ok: false, message: "2FA required" };
|
|
496
|
-
const form2 = {
|
|
497
|
-
...base,
|
|
498
|
-
credentials_type: "two_factor",
|
|
499
|
-
twofactor_code: code,
|
|
500
|
-
userid: data.uid || username,
|
|
501
|
-
first_factor: data.login_first_factor || "",
|
|
502
|
-
machine_id: data.machine_id || machine_id
|
|
503
|
-
};
|
|
504
|
-
form2.sig = encodesig(sort(form2));
|
|
505
|
-
const res2 = await client.request({ url: "https://b-graph.facebook.com/auth/login", method: "post", data: qs.stringify(form2), headers });
|
|
506
|
-
if (res2.status === 200 && res2.data && res2.data.session_cookies) {
|
|
507
|
-
const appstate = res2.data.session_cookies.map(c => ({ key: c.name, value: c.value, domain: c.domain, path: c.path }));
|
|
508
|
-
const cUserCookie = appstate.find(c => c.key === "c_user");
|
|
509
|
-
if (i_user) appstate.push({ key: "i_user", value: i_user, domain: ".facebook.com", path: "/" });
|
|
510
|
-
else if (cUserCookie) appstate.push({ key: "i_user", value: cUserCookie.value, domain: ".facebook.com", path: "/" });
|
|
511
|
-
await setJarCookies(cookieJar, appstate);
|
|
512
|
-
let eaau = null;
|
|
513
|
-
let eaad6v7 = null;
|
|
514
|
-
try {
|
|
515
|
-
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}` } });
|
|
516
|
-
eaau = r1.data && r1.data.access_token ? r1.data.access_token : null;
|
|
517
|
-
} catch { }
|
|
518
|
-
try {
|
|
519
|
-
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}` } });
|
|
520
|
-
eaad6v7 = r2.data && r2.data.access_token ? r2.data.access_token : null;
|
|
521
|
-
} catch { }
|
|
522
|
-
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 };
|
|
523
|
-
}
|
|
524
|
-
return { ok: false, message: "2FA failed" };
|
|
525
|
-
}
|
|
526
|
-
return { ok: false, message: "Login failed" };
|
|
527
|
-
}
|
|
528
|
-
|
|
468
|
+
// tokens function - alias to tokensViaAPI for backward compatibility
|
|
529
469
|
async function tokens(username, password, twofactor = null) {
|
|
530
|
-
|
|
531
|
-
if (!username || !password) return { status: false, message: "Please provide email and password" };
|
|
532
|
-
logger(`AUTO-LOGIN: Initialize login ${mask(username, 2)}`, "info");
|
|
533
|
-
const res = await loginViaGraph(username, password, twofactor, null, jar);
|
|
534
|
-
if (res && res.ok && Array.isArray(res.cookies)) {
|
|
535
|
-
logger(`AUTO-LOGIN: Login success ${res.cookies.length} cookies`, "info");
|
|
536
|
-
const t1 = Number(process.hrtime.bigint() - t0) / 1e6;
|
|
537
|
-
logger(`Done success login ${Math.round(t1)}ms`, "info");
|
|
538
|
-
return { status: true, cookies: res.cookies };
|
|
539
|
-
}
|
|
540
|
-
if (res && res.message === "2FA required") {
|
|
541
|
-
logger("AUTO-LOGIN: 2FA required but secret missing", "warn");
|
|
542
|
-
return { status: false, message: "Please provide the 2FA secret!" };
|
|
543
|
-
}
|
|
544
|
-
return { status: false, message: res && res.message ? res.message : "Login failed" };
|
|
470
|
+
return tokensViaAPI(username, password, twofactor);
|
|
545
471
|
}
|
|
546
472
|
|
|
547
473
|
async function hydrateJarFromDB(userID) {
|
|
@@ -580,6 +506,9 @@ async function hydrateJarFromDB(userID) {
|
|
|
580
506
|
}
|
|
581
507
|
|
|
582
508
|
async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions, ctxRef, hadAppStateInput = false) {
|
|
509
|
+
// Helper to validate UID - must be a non-zero positive number string
|
|
510
|
+
const isValidUID = uid => uid && uid !== "0" && /^\d+$/.test(uid) && parseInt(uid, 10) > 0;
|
|
511
|
+
|
|
583
512
|
const getUID = cs =>
|
|
584
513
|
cs.find(c => c.key === "i_user")?.value ||
|
|
585
514
|
cs.find(c => c.key === "c_user")?.value ||
|
|
@@ -589,59 +518,206 @@ async function tryAutoLoginIfNeeded(currentHtml, currentCookies, globalOptions,
|
|
|
589
518
|
const s = typeof body === "string" ? body : String(body ?? "");
|
|
590
519
|
return s.match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s.match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
|
|
591
520
|
};
|
|
521
|
+
|
|
592
522
|
let userID = getUID(currentCookies);
|
|
593
|
-
// Also try to extract userID from HTML if
|
|
594
|
-
if (!userID) {
|
|
523
|
+
// Also try to extract userID from HTML if cookie userID is invalid
|
|
524
|
+
if (!isValidUID(userID)) {
|
|
595
525
|
userID = htmlUID(currentHtml);
|
|
596
526
|
}
|
|
597
|
-
|
|
598
|
-
|
|
527
|
+
// If we have a valid userID, return success
|
|
528
|
+
if (isValidUID(userID)) {
|
|
529
|
+
return { html: currentHtml, cookies: currentCookies, userID };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// No valid userID found - need to try auto-login
|
|
533
|
+
logger("tryAutoLoginIfNeeded: No valid userID found, attempting recovery...", "warn");
|
|
534
|
+
|
|
535
|
+
// If appState/Cookie was provided and is not checkpointed, try refresh
|
|
599
536
|
if (hadAppStateInput) {
|
|
600
537
|
const isCheckpoint = currentHtml.includes("/checkpoint/block/?next");
|
|
601
538
|
if (!isCheckpoint) {
|
|
602
|
-
// AppState provided and not checkpointed, but userID not found
|
|
603
|
-
// This might be a temporary issue - try to refresh cookies from jar
|
|
604
539
|
try {
|
|
605
540
|
const refreshedCookies = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
|
|
606
541
|
userID = getUID(refreshedCookies);
|
|
607
|
-
if (userID) {
|
|
542
|
+
if (isValidUID(userID)) {
|
|
608
543
|
return { html: currentHtml, cookies: refreshedCookies, userID };
|
|
609
544
|
}
|
|
610
545
|
} catch { }
|
|
611
|
-
// If still no userID, skip backup and throw error
|
|
612
|
-
throw new Error("Missing user cookie from provided appState");
|
|
613
546
|
}
|
|
614
|
-
// AppState is dead (checkpointed), proceed to backup/email login
|
|
615
547
|
}
|
|
548
|
+
|
|
549
|
+
// Try to hydrate from DB backup
|
|
616
550
|
const hydrated = await hydrateJarFromDB(null);
|
|
617
551
|
if (hydrated) {
|
|
618
|
-
logger("
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
552
|
+
logger("tryAutoLoginIfNeeded: Trying backup from DB...", "info");
|
|
553
|
+
try {
|
|
554
|
+
const initial = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
|
|
555
|
+
const resB = (await ctxRef.bypassAutomation(initial, jar)) || initial;
|
|
556
|
+
const htmlB = resB && resB.data ? resB.data : "";
|
|
557
|
+
if (!htmlB.includes("/checkpoint/block/?next")) {
|
|
558
|
+
const htmlUserID = htmlUID(htmlB);
|
|
559
|
+
if (isValidUID(htmlUserID)) {
|
|
560
|
+
const cookiesB = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
|
|
561
|
+
logger(`tryAutoLoginIfNeeded: DB backup session valid, USER_ID=${htmlUserID}`, "info");
|
|
562
|
+
return { html: htmlB, cookies: cookiesB, userID: htmlUserID };
|
|
563
|
+
} else {
|
|
564
|
+
logger(`tryAutoLoginIfNeeded: DB backup session dead (HTML USER_ID=${htmlUserID || "empty"}), will try API login...`, "warn");
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
} catch (dbErr) {
|
|
568
|
+
logger(`tryAutoLoginIfNeeded: DB backup failed - ${dbErr && dbErr.message ? dbErr.message : String(dbErr)}`, "warn");
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Check if auto-login is enabled (support both true and "true")
|
|
573
|
+
if (config.autoLogin === false || config.autoLogin === "false") {
|
|
574
|
+
throw new Error("AppState backup die — Auto-login is disabled");
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Try API login
|
|
578
|
+
const u = config.credentials?.email || config.email;
|
|
579
|
+
const p = config.credentials?.password || config.password;
|
|
580
|
+
const tf = config.credentials?.twofactor || config.twofactor || null;
|
|
581
|
+
|
|
582
|
+
if (!u || !p) {
|
|
583
|
+
logger("tryAutoLoginIfNeeded: No credentials configured for auto-login!", "error");
|
|
584
|
+
throw new Error("Missing credentials for auto-login (email/password not configured in fca-config.json)");
|
|
626
585
|
}
|
|
627
|
-
|
|
628
|
-
logger(
|
|
629
|
-
|
|
630
|
-
const p = config.credentials?.password;
|
|
631
|
-
const tf = config.credentials?.twofactor || null;
|
|
632
|
-
if (!u || !p) throw new Error("Missing user cookie");
|
|
586
|
+
|
|
587
|
+
logger(`tryAutoLoginIfNeeded: Attempting API login for ${u.slice(0, 3)}***...`, "info");
|
|
588
|
+
|
|
633
589
|
const r = await tokens(u, p, tf);
|
|
634
|
-
if (!
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
590
|
+
if (!r || !r.status) {
|
|
591
|
+
throw new Error(r && r.message ? r.message : "API Login failed");
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
logger(`tryAutoLoginIfNeeded: API login successful! UID: ${r.uid}`, "info");
|
|
595
|
+
|
|
596
|
+
// Handle cookies - can be array, cookie string header, or both
|
|
597
|
+
let cookiePairs = [];
|
|
598
|
+
|
|
599
|
+
// If cookies is a string (cookie header format), parse it
|
|
600
|
+
if (typeof r.cookies === "string") {
|
|
601
|
+
cookiePairs = normalizeCookieHeaderString(r.cookies);
|
|
602
|
+
}
|
|
603
|
+
// If cookies is an array, convert to pairs
|
|
604
|
+
else if (Array.isArray(r.cookies)) {
|
|
605
|
+
cookiePairs = r.cookies.map(c => {
|
|
606
|
+
if (typeof c === "string") {
|
|
607
|
+
// Already in "key=value" format
|
|
608
|
+
return c;
|
|
609
|
+
} else if (c && typeof c === "object") {
|
|
610
|
+
// Object format {key, value} or {name, value}
|
|
611
|
+
return `${c.key || c.name}=${c.value}`;
|
|
612
|
+
}
|
|
613
|
+
return null;
|
|
614
|
+
}).filter(Boolean);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Also check for cookie field (alternative field name)
|
|
618
|
+
if (cookiePairs.length === 0 && r.cookie) {
|
|
619
|
+
if (typeof r.cookie === "string") {
|
|
620
|
+
cookiePairs = normalizeCookieHeaderString(r.cookie);
|
|
621
|
+
} else if (Array.isArray(r.cookie)) {
|
|
622
|
+
cookiePairs = r.cookie.map(c => {
|
|
623
|
+
if (typeof c === "string") return c;
|
|
624
|
+
if (c && typeof c === "object") return `${c.key || c.name}=${c.value}`;
|
|
625
|
+
return null;
|
|
626
|
+
}).filter(Boolean);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (cookiePairs.length === 0) {
|
|
631
|
+
logger("tryAutoLoginIfNeeded: No cookies found in API response", "warn");
|
|
632
|
+
throw new Error("API login returned no cookies");
|
|
633
|
+
} else {
|
|
634
|
+
logger(`tryAutoLoginIfNeeded: Parsed ${cookiePairs.length} cookies from API response`, "info");
|
|
635
|
+
setJarFromPairs(jar, cookiePairs, ".facebook.com");
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Wait a bit for cookies to be set
|
|
639
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
640
|
+
|
|
641
|
+
// Refresh Facebook page with new cookies - try multiple times if needed
|
|
642
|
+
// Try both www.facebook.com and m.facebook.com to ensure session is established
|
|
643
|
+
let html2 = "";
|
|
644
|
+
let res2 = null;
|
|
645
|
+
let retryCount = 0;
|
|
646
|
+
const maxRetries = 3;
|
|
647
|
+
const urlsToTry = ["https://m.facebook.com/", "https://www.facebook.com/"];
|
|
648
|
+
|
|
649
|
+
while (retryCount < maxRetries) {
|
|
650
|
+
try {
|
|
651
|
+
// Try m.facebook.com first (mobile version often works better for API login)
|
|
652
|
+
const urlToUse = retryCount === 0 ? urlsToTry[0] : urlsToTry[retryCount % urlsToTry.length];
|
|
653
|
+
logger(`tryAutoLoginIfNeeded: Refreshing ${urlToUse} (attempt ${retryCount + 1}/${maxRetries})...`, "info");
|
|
654
|
+
|
|
655
|
+
const initial2 = await get(urlToUse, jar, null, globalOptions).then(saveCookies(jar));
|
|
656
|
+
res2 = (await ctxRef.bypassAutomation(initial2, jar)) || initial2;
|
|
657
|
+
html2 = res2 && res2.data ? res2.data : "";
|
|
658
|
+
|
|
659
|
+
if (html2.includes("/checkpoint/block/?next")) {
|
|
660
|
+
throw new Error("Checkpoint after API login");
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Check if HTML contains valid USER_ID
|
|
664
|
+
const htmlUserID = htmlUID(html2);
|
|
665
|
+
if (isValidUID(htmlUserID)) {
|
|
666
|
+
logger(`tryAutoLoginIfNeeded: Found valid USER_ID in HTML from ${urlToUse}: ${htmlUserID}`, "info");
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// If no valid USER_ID found, wait and retry with different URL
|
|
671
|
+
if (retryCount < maxRetries - 1) {
|
|
672
|
+
logger(`tryAutoLoginIfNeeded: No valid USER_ID in HTML from ${urlToUse} (attempt ${retryCount + 1}/${maxRetries}), retrying...`, "warn");
|
|
673
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * (retryCount + 1)));
|
|
674
|
+
retryCount++;
|
|
675
|
+
} else {
|
|
676
|
+
logger("tryAutoLoginIfNeeded: No valid USER_ID found in HTML after retries", "warn");
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
} catch (err) {
|
|
680
|
+
if (err.message && err.message.includes("Checkpoint")) {
|
|
681
|
+
throw err;
|
|
682
|
+
}
|
|
683
|
+
if (retryCount < maxRetries - 1) {
|
|
684
|
+
logger(`tryAutoLoginIfNeeded: Error refreshing page (attempt ${retryCount + 1}/${maxRetries}): ${err && err.message ? err.message : String(err)}`, "warn");
|
|
685
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * (retryCount + 1)));
|
|
686
|
+
retryCount++;
|
|
687
|
+
} else {
|
|
688
|
+
throw err;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
641
693
|
const cookies2 = await Promise.resolve(jar.getCookies("https://www.facebook.com"));
|
|
642
694
|
const uid2 = getUID(cookies2);
|
|
643
|
-
|
|
644
|
-
|
|
695
|
+
const htmlUserID2 = htmlUID(html2);
|
|
696
|
+
|
|
697
|
+
// Prioritize USER_ID from HTML over cookies (more reliable)
|
|
698
|
+
let finalUID = null;
|
|
699
|
+
if (isValidUID(htmlUserID2)) {
|
|
700
|
+
finalUID = htmlUserID2;
|
|
701
|
+
logger(`tryAutoLoginIfNeeded: Using USER_ID from HTML: ${finalUID}`, "info");
|
|
702
|
+
} else if (isValidUID(uid2)) {
|
|
703
|
+
finalUID = uid2;
|
|
704
|
+
logger(`tryAutoLoginIfNeeded: Using USER_ID from cookies: ${finalUID}`, "info");
|
|
705
|
+
} else if (isValidUID(r.uid)) {
|
|
706
|
+
finalUID = r.uid;
|
|
707
|
+
logger(`tryAutoLoginIfNeeded: Using USER_ID from API response: ${finalUID}`, "info");
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
if (!isValidUID(finalUID)) {
|
|
711
|
+
logger(`tryAutoLoginIfNeeded: HTML check - USER_ID from HTML: ${htmlUserID2 || "none"}, from cookies: ${uid2 || "none"}, from API: ${r.uid || "none"}`, "error");
|
|
712
|
+
throw new Error("Login failed - could not get valid userID after API login. HTML may indicate session is not established.");
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Final validation: ensure HTML shows we're logged in
|
|
716
|
+
if (!isValidUID(htmlUserID2)) {
|
|
717
|
+
logger("tryAutoLoginIfNeeded: WARNING - HTML does not show valid USER_ID, but proceeding with cookie-based UID", "warn");
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return { html: html2, cookies: cookies2, userID: finalUID };
|
|
645
721
|
}
|
|
646
722
|
|
|
647
723
|
function makeLogin(j, email, password, globalOptions) {
|
|
@@ -866,31 +942,121 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
|
|
|
866
942
|
const s = typeof body === "string" ? body : String(body ?? "");
|
|
867
943
|
return s.match(/"USER_ID"\s*:\s*"(\d+)"/)?.[1] || s.match(/\["CurrentUserInitialData",\[\],\{.*?"USER_ID":"(\d+)".*?\},\d+\]/)?.[1];
|
|
868
944
|
};
|
|
945
|
+
// Helper to validate UID - must be a non-zero positive number string
|
|
946
|
+
const isValidUID = uid => uid && uid !== "0" && /^\d+$/.test(uid) && parseInt(uid, 10) > 0;
|
|
947
|
+
|
|
869
948
|
let userID = getUIDFromCookies(cookies);
|
|
870
949
|
// Also try to extract userID from HTML if not found in cookies
|
|
871
|
-
if (!userID) {
|
|
950
|
+
if (!isValidUID(userID)) {
|
|
872
951
|
userID = getUIDFromHTML(html);
|
|
873
952
|
}
|
|
874
953
|
// If still not found and appState was provided, use userID from appState input as fallback
|
|
875
|
-
if (!userID && userIDFromAppState) {
|
|
954
|
+
if (!isValidUID(userID) && userIDFromAppState && isValidUID(userIDFromAppState)) {
|
|
876
955
|
userID = userIDFromAppState;
|
|
877
956
|
}
|
|
878
|
-
if (
|
|
957
|
+
// Trigger auto-login if userID is invalid (missing or "0")
|
|
958
|
+
if (!isValidUID(userID)) {
|
|
959
|
+
logger("Invalid userID detected (missing or 0), attempting auto-login...", "warn");
|
|
879
960
|
// Pass hadAppStateInput=true if appState/Cookie was originally provided
|
|
880
961
|
const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions, ctx, !!(appState || Cookie));
|
|
881
962
|
html = retried.html;
|
|
882
963
|
cookies = retried.cookies;
|
|
883
964
|
userID = retried.userID;
|
|
965
|
+
|
|
966
|
+
// Validate HTML after auto-login - ensure it contains valid USER_ID
|
|
967
|
+
const htmlUserIDAfterLogin = getUIDFromHTML(html);
|
|
968
|
+
if (!isValidUID(htmlUserIDAfterLogin)) {
|
|
969
|
+
logger("After auto-login, HTML still does not contain valid USER_ID. Session may not be established.", "error");
|
|
970
|
+
// Try one more refresh
|
|
971
|
+
try {
|
|
972
|
+
const refreshRes = await get("https://www.facebook.com/", jar, null, globalOptions).then(saveCookies(jar));
|
|
973
|
+
const refreshedHtml = refreshRes && refreshRes.data ? refreshRes.data : "";
|
|
974
|
+
const refreshedHtmlUID = getUIDFromHTML(refreshedHtml);
|
|
975
|
+
if (isValidUID(refreshedHtmlUID)) {
|
|
976
|
+
html = refreshedHtml;
|
|
977
|
+
userID = refreshedHtmlUID;
|
|
978
|
+
logger(`After refresh, found valid USER_ID in HTML: ${userID}`, "info");
|
|
979
|
+
} else {
|
|
980
|
+
throw new Error("Login failed - HTML does not show valid USER_ID after auto-login and refresh");
|
|
981
|
+
}
|
|
982
|
+
} catch (refreshErr) {
|
|
983
|
+
throw new Error(`Login failed - Could not establish valid session. HTML USER_ID check failed: ${refreshErr && refreshErr.message ? refreshErr.message : String(refreshErr)}`);
|
|
984
|
+
}
|
|
985
|
+
} else {
|
|
986
|
+
// Use USER_ID from HTML as it's more reliable
|
|
987
|
+
userID = htmlUserIDAfterLogin;
|
|
988
|
+
logger(`After auto-login, using USER_ID from HTML: ${userID}`, "info");
|
|
989
|
+
}
|
|
884
990
|
}
|
|
885
991
|
if (html.includes("/checkpoint/block/?next")) {
|
|
886
992
|
logger("Appstate die, vui lòng thay cái mới!", "error");
|
|
887
993
|
throw new Error("Checkpoint");
|
|
888
994
|
}
|
|
995
|
+
|
|
996
|
+
// Final validation: ensure HTML shows we're logged in before proceeding
|
|
997
|
+
let finalHtmlUID = getUIDFromHTML(html);
|
|
998
|
+
if (!isValidUID(finalHtmlUID)) {
|
|
999
|
+
// If cookies have valid UID but HTML doesn't, try to "activate" session
|
|
1000
|
+
if (isValidUID(userID)) {
|
|
1001
|
+
logger(`HTML shows USER_ID=${finalHtmlUID || "none"} but cookies have valid UID=${userID}. Attempting to activate session...`, "warn");
|
|
1002
|
+
|
|
1003
|
+
// Try making requests to activate the session
|
|
1004
|
+
try {
|
|
1005
|
+
// Wait a bit first for cookies to propagate
|
|
1006
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1007
|
+
|
|
1008
|
+
// Try refreshing with m.facebook.com/home.php (mobile home page)
|
|
1009
|
+
logger("Trying to activate session via m.facebook.com/home.php...", "info");
|
|
1010
|
+
const activateRes = await get("https://m.facebook.com/home.php", jar, null, globalOptions).then(saveCookies(jar));
|
|
1011
|
+
const activateHtml = activateRes && activateRes.data ? activateRes.data : "";
|
|
1012
|
+
const activateUID = getUIDFromHTML(activateHtml);
|
|
1013
|
+
|
|
1014
|
+
if (isValidUID(activateUID)) {
|
|
1015
|
+
html = activateHtml;
|
|
1016
|
+
finalHtmlUID = activateUID;
|
|
1017
|
+
userID = activateUID;
|
|
1018
|
+
logger(`Session activated! Found valid USER_ID in HTML: ${userID}`, "info");
|
|
1019
|
+
} else {
|
|
1020
|
+
// Try one more time with www.facebook.com/home.php after delay
|
|
1021
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
1022
|
+
logger("Trying to activate session via www.facebook.com/home.php...", "info");
|
|
1023
|
+
const activateRes2 = await get("https://www.facebook.com/home.php", jar, null, globalOptions).then(saveCookies(jar));
|
|
1024
|
+
const activateHtml2 = activateRes2 && activateRes2.data ? activateRes2.data : "";
|
|
1025
|
+
const activateUID2 = getUIDFromHTML(activateHtml2);
|
|
1026
|
+
|
|
1027
|
+
if (isValidUID(activateUID2)) {
|
|
1028
|
+
html = activateHtml2;
|
|
1029
|
+
finalHtmlUID = activateUID2;
|
|
1030
|
+
userID = activateUID2;
|
|
1031
|
+
logger(`Session activated on second try! Found valid USER_ID in HTML: ${userID}`, "info");
|
|
1032
|
+
} else {
|
|
1033
|
+
// If cookies have valid UID, we can proceed with cookie-based UID but warn
|
|
1034
|
+
logger(`WARNING: HTML still shows USER_ID=${finalHtmlUID || "none"} but cookies have valid UID=${userID}. Proceeding with cookie-based UID.`, "warn");
|
|
1035
|
+
// Don't throw error, proceed with cookie-based UID
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
} catch (activateErr) {
|
|
1039
|
+
logger(`Failed to activate session: ${activateErr && activateErr.message ? activateErr.message : String(activateErr)}. Proceeding with cookie-based UID.`, "warn");
|
|
1040
|
+
// Don't throw error, proceed with cookie-based UID
|
|
1041
|
+
}
|
|
1042
|
+
} else {
|
|
1043
|
+
// No valid UID in either cookies or HTML
|
|
1044
|
+
logger(`Final HTML validation failed - USER_ID from HTML: ${finalHtmlUID || "none"}, from cookies: ${userID || "none"}`, "error");
|
|
1045
|
+
throw new Error("Login validation failed - HTML does not contain valid USER_ID. Session may not be properly established.");
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// Final check: ensure we have a valid userID (either from HTML or cookies)
|
|
1050
|
+
if (!isValidUID(userID)) {
|
|
1051
|
+
logger(`No valid USER_ID found - HTML: ${finalHtmlUID || "none"}, Cookies: ${userID || "none"}`, "error");
|
|
1052
|
+
throw new Error("Login validation failed - No valid USER_ID found in HTML or cookies.");
|
|
1053
|
+
}
|
|
889
1054
|
let mqttEndpoint;
|
|
890
1055
|
let region = "PRN";
|
|
891
1056
|
let fb_dtsg;
|
|
892
1057
|
let irisSeqID;
|
|
893
1058
|
try {
|
|
1059
|
+
parseUserHtml(html);
|
|
894
1060
|
const m1 = html.match(/"endpoint":"([^"]+)"/);
|
|
895
1061
|
const m2 = m1 ? null : html.match(/endpoint\\":\\"([^\\"]+)\\"/);
|
|
896
1062
|
const raw = (m1 && m1[1]) || (m2 && m2[1]);
|
|
@@ -907,10 +1073,30 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
|
|
|
907
1073
|
if (userDataMatch) {
|
|
908
1074
|
const info = JSON.parse(userDataMatch[1]);
|
|
909
1075
|
logger(`Đăng nhập tài khoản: ${info.NAME} (${info.USER_ID})`, "info");
|
|
1076
|
+
|
|
1077
|
+
// Check if Facebook response shows USER_ID = 0 (session dead)
|
|
1078
|
+
if (!isValidUID(info.USER_ID)) {
|
|
1079
|
+
logger("Facebook response shows invalid USER_ID (0 or empty), session is dead!", "warn");
|
|
1080
|
+
// Force trigger auto-login
|
|
1081
|
+
const retried = await tryAutoLoginIfNeeded(html, cookies, globalOptions, ctx, !!(appState || Cookie));
|
|
1082
|
+
html = retried.html;
|
|
1083
|
+
cookies = retried.cookies;
|
|
1084
|
+
userID = retried.userID;
|
|
1085
|
+
// Re-check after auto-login
|
|
1086
|
+
if (!isValidUID(userID)) {
|
|
1087
|
+
throw new Error("Auto-login failed - could not get valid userID");
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
910
1090
|
} else if (userID) {
|
|
911
1091
|
logger(`ID người dùng: ${userID}`, "info");
|
|
912
1092
|
}
|
|
913
|
-
} catch {
|
|
1093
|
+
} catch (userDataErr) {
|
|
1094
|
+
// If error is from our validation, rethrow it
|
|
1095
|
+
if (userDataErr && userDataErr.message && userDataErr.message.includes("Auto-login failed")) {
|
|
1096
|
+
throw userDataErr;
|
|
1097
|
+
}
|
|
1098
|
+
// Otherwise ignore parsing errors
|
|
1099
|
+
}
|
|
914
1100
|
const tokenMatch = html.match(/DTSGInitialData.*?token":"(.*?)"/);
|
|
915
1101
|
if (tokenMatch) fb_dtsg = tokenMatch[1];
|
|
916
1102
|
try {
|
|
@@ -1046,3 +1232,9 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
|
|
|
1046
1232
|
}
|
|
1047
1233
|
|
|
1048
1234
|
module.exports = loginHelper;
|
|
1235
|
+
module.exports.loginHelper = loginHelper;
|
|
1236
|
+
module.exports.tokensViaAPI = tokensViaAPI;
|
|
1237
|
+
module.exports.loginViaAPI = loginViaAPI;
|
|
1238
|
+
module.exports.tokens = tokens;
|
|
1239
|
+
module.exports.normalizeCookieHeaderString = normalizeCookieHeaderString;
|
|
1240
|
+
module.exports.setJarFromPairs = setJarFromPairs;
|