feat(facebook): add challenge detection and session warming utilities
facebook-challenge.ts: session warmup, header construction, and challenge type detection. Spec document for the anti-bot challenge solver design.
This commit is contained in:
128
packages/core/src/utils/facebook-challenge.ts
Normal file
128
packages/core/src/utils/facebook-challenge.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
// Facebook Marketplace session & challenge utilities
|
||||
|
||||
// ------------------ Types ------------------
|
||||
|
||||
export type ChallengeType =
|
||||
| "login_wall"
|
||||
| "checkpoint"
|
||||
| "bad_headers"
|
||||
| "rate_limited"
|
||||
| "none";
|
||||
|
||||
// ------------------ Constants ------------------
|
||||
|
||||
const FACEBOOK_BROWSER_HEADERS: Record<string, string> = {
|
||||
accept:
|
||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
|
||||
"cache-control": "no-cache",
|
||||
"upgrade-insecure-requests": "1",
|
||||
"sec-fetch-dest": "document",
|
||||
"sec-fetch-mode": "navigate",
|
||||
"sec-fetch-site": "none",
|
||||
"sec-fetch-user": "?1",
|
||||
"sec-ch-ua":
|
||||
'"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Linux"',
|
||||
"user-agent":
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
||||
};
|
||||
|
||||
// ------------------ Cookie Management ------------------
|
||||
|
||||
function parseSetCookies(setCookieHeaders: string[]): Record<string, string> {
|
||||
const cookies: Record<string, string> = {};
|
||||
for (const header of setCookieHeaders) {
|
||||
const parts = header.split(";");
|
||||
const firstPart = parts[0]?.trim();
|
||||
if (!firstPart) continue;
|
||||
const eqIdx = firstPart.indexOf("=");
|
||||
if (eqIdx === -1) continue;
|
||||
const name = firstPart.slice(0, eqIdx).trim();
|
||||
const value = firstPart.slice(eqIdx + 1).trim();
|
||||
if (name && value) {
|
||||
cookies[name] = value;
|
||||
}
|
||||
}
|
||||
return cookies;
|
||||
}
|
||||
|
||||
function cookiesToHeader(cookies: Record<string, string>): string {
|
||||
return Object.entries(cookies)
|
||||
.map(([name, value]) => `${name}=${value}`)
|
||||
.join("; ");
|
||||
}
|
||||
|
||||
// ------------------ Session Warmup ------------------
|
||||
|
||||
export async function warmFacebookSession(): Promise<Record<string, string>> {
|
||||
try {
|
||||
const res = await fetch("https://www.facebook.com/", {
|
||||
method: "GET",
|
||||
headers: FACEBOOK_BROWSER_HEADERS,
|
||||
redirect: "manual",
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
const setCookies = res.headers.getSetCookie?.() ?? [];
|
||||
return parseSetCookies(setCookies);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------ Challenge Detection ------------------
|
||||
|
||||
export function detectFacebookChallenge(
|
||||
status: number,
|
||||
html: string,
|
||||
responseUrl: string,
|
||||
): ChallengeType {
|
||||
if (status === 400) {
|
||||
return "bad_headers";
|
||||
}
|
||||
|
||||
if (status === 429) {
|
||||
return "rate_limited";
|
||||
}
|
||||
|
||||
if (responseUrl.includes("/login/")) {
|
||||
return "login_wall";
|
||||
}
|
||||
|
||||
if (html.includes("You must log in") || html.includes("log in to continue")) {
|
||||
return "login_wall";
|
||||
}
|
||||
|
||||
if (
|
||||
responseUrl.includes("/checkpoint/") ||
|
||||
(html.includes("checkpoint") && html.includes("challenge"))
|
||||
) {
|
||||
return "checkpoint";
|
||||
}
|
||||
|
||||
return "none";
|
||||
}
|
||||
|
||||
// ------------------ Header Construction ------------------
|
||||
|
||||
export function buildFacebookHeaders(
|
||||
cookieJar: Record<string, string>,
|
||||
extraHeaders?: Record<string, string>,
|
||||
): Record<string, string> {
|
||||
const headers: Record<string, string> = {
|
||||
...FACEBOOK_BROWSER_HEADERS,
|
||||
};
|
||||
|
||||
const cookieString = cookiesToHeader(cookieJar);
|
||||
if (cookieString) {
|
||||
headers.cookie = cookieString;
|
||||
}
|
||||
|
||||
if (extraHeaders) {
|
||||
Object.assign(headers, extraHeaders);
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
Reference in New Issue
Block a user