Files
2026-07-13 13:39:12 +08:00

127 lines
4.3 KiB
TypeScript

/**
* FingerprintRotator — Browser fingerprint profiles for session diversity
*
* Each profile mimics a real browser user-agent + Sec-CH-UA headers.
* Rotating through these prevents providers from associating requests
* to a single browser identity for rate-limiting purposes.
*/
import { type Fingerprint } from "./types.ts";
// ─── Profiles ──────────────────────────────────────────────────────────────
const PROFILES: Fingerprint[] = [
{
id: "chrome-mac",
userAgent:
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36",
acceptLanguage: "en-US,en;q=0.9",
secChUa: '"Not-A.Brand";v="99", "Chromium";v="149", "Google Chrome";v="149"',
secChUaPlatform: '"macOS"',
secChUaMobile: "?0",
},
{
id: "chrome-linux",
userAgent:
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36",
acceptLanguage: "en-US,en;q=0.9",
secChUa: '"Not-A.Brand";v="99", "Chromium";v="149", "Google Chrome";v="149"',
secChUaPlatform: '"Linux"',
secChUaMobile: "?0",
},
{
id: "chrome-win",
userAgent:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36",
acceptLanguage: "en-US,en;q=0.9",
secChUa: '"Not-A.Brand";v="99", "Chromium";v="149", "Google Chrome";v="149"',
secChUaPlatform: '"Windows"',
secChUaMobile: "?0",
},
{
id: "firefox-mac",
userAgent:
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0",
acceptLanguage: "en-US,en;q=0.9",
},
{
id: "firefox-linux",
userAgent: "Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0",
acceptLanguage: "en-US,en;q=0.9",
},
{
id: "safari-mac",
userAgent:
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15",
acceptLanguage: "en-US,en;q=0.9",
},
{
id: "edge-mac",
userAgent:
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36 Edg/149.0.0.0",
acceptLanguage: "en-US,en;q=0.9",
secChUa: '"Not-A.Brand";v="99", "Chromium";v="149", "Microsoft Edge";v="149"',
secChUaPlatform: '"macOS"',
secChUaMobile: "?0",
},
{
id: "edge-win",
userAgent:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36 Edg/149.0.0.0",
acceptLanguage: "en-US,en;q=0.9",
secChUa: '"Not-A.Brand";v="99", "Chromium";v="149", "Microsoft Edge";v="149"',
secChUaPlatform: '"Windows"',
secChUaMobile: "?0",
},
];
// ─── FingerprintRotator ────────────────────────────────────────────────────
export class FingerprintRotator {
private index = 0;
/** Get the next profile in round-robin order */
next(): Fingerprint {
const profile = PROFILES[this.index % PROFILES.length];
this.index++;
return profile;
}
/** Get a random profile */
random(): Fingerprint {
return PROFILES[Math.floor(Math.random() * PROFILES.length)];
}
/** Reset the round-robin counter */
reset(): void {
this.index = 0;
}
/** Number of available profiles */
get count(): number {
return PROFILES.length;
}
/** Build a Headers object from a fingerprint (with optional extras) */
buildHeaders(fingerprint: Fingerprint, extra?: Record<string, string>): Record<string, string> {
const headers: Record<string, string> = {
"Content-Type": "application/json",
Accept: "application/json, text/plain, */*",
"Accept-Language": fingerprint.acceptLanguage ?? "en-US,en;q=0.9",
"User-Agent": fingerprint.userAgent,
...extra,
};
if (fingerprint.secChUa) {
headers["Sec-CH-UA"] = fingerprint.secChUa;
headers["Sec-CH-UA-Mobile"] = fingerprint.secChUaMobile ?? "?0";
headers["Sec-CH-UA-Platform"] = fingerprint.secChUaPlatform ?? '"Windows"';
}
return headers;
}
/** List all profiles */
listAll(): Fingerprint[] {
return [...PROFILES];
}
}