/** * geminigen.ai x-guard-id header generator * * Reverse-engineered from geminigen.ai/_nuxt/nBh81x6X.js * Constants extracted from: window.__NUXT__.config.public.antibot * * Algorithm: * stableId = 22-char alphanumeric string (persistent per server instance) * timeBucket = Math.floor(Date.now() / 60000) — changes every 60 seconds * domFp = "0".repeat(32) — server has no DOM * * hmacKey = SHA256(`${SECRET_KEY}:${stableId}`).slice(0, 32) * signHash = SHA256(`${path}:${METHOD}:${hmacKey}:${timeBucket}:${SECRET_KEY}`) * * payload = [0x01, ...hexBytes(hmacKey), // 16 bytes * ...uint32BE(timeBucket), // 4 bytes * ...hexBytes(signHash), // 32 bytes * ...hexBytes(domFp)] // 16 bytes → 69 bytes total * * x-guard-id = base64url(payload) (no padding) */ import { createHash, randomBytes } from "crypto"; const SECRET_KEY = "45NPBH$&"; const TIME_BUCKET_MS = 60_000; const STABLE_ID_LEN = 22; const DOM_FP = "0".repeat(32); // 32 hex zeros → 16 zero bytes // ── One stable-id per server instance ──────────────────────────────────────── function makeStableId(): string { const rand = randomBytes(16).toString("hex"); const hash = createHash("sha256").update(`server:${rand}`).digest("hex"); // Take alphanumeric chars from the hex (all are [0-9a-f] so fine for [A-Za-z0-9]) return hash.slice(0, STABLE_ID_LEN); } let _stableId: string = makeStableId(); /** Replace the stable-id (e.g., if you want to rotate it). */ export function rotateStableId(): void { _stableId = makeStableId(); } // ── Helpers ─────────────────────────────────────────────────────────────────── /** SHA-256 of a UTF-8 string → lowercase hex (64 chars). */ function sha256(str: string): string { return createHash("sha256").update(str, "utf8").digest("hex"); } /** Convert a hex string to a byte array (every 2 hex chars → 1 byte). */ function hexToBytes(hex: string): number[] { const bytes: number[] = []; for (let i = 0; i < hex.length; i += 2) { bytes.push(parseInt(hex.substring(i, 2 + i), 16)); } return bytes; } /** Encode uint32 as 4 big-endian bytes. */ function uint32BE(n: number): number[] { return [(n >>> 24) & 255, (n >>> 16) & 255, (n >>> 8) & 255, n & 255]; } // ── Public API ───────────────────────────────────────────────────────────────── /** * Generate the `x-guard-id` header value for a given API path + HTTP method. * * @param path e.g. "/api/video-gen/grok-stream" * @param method e.g. "post" */ export function generateGuardId(path: string, method: string): string { const stableId = _stableId; const timeBucket = Math.floor(Date.now() / TIME_BUCKET_MS); // hmacKey: first 32 hex chars of SHA256("${SECRET_KEY}:${stableId}") → 16 bytes const hmacKey = sha256(`${SECRET_KEY}:${stableId}`).slice(0, 32); // signHash: full 64-hex SHA256 → 32 bytes const signHash = sha256(`${path}:${method.toUpperCase()}:${hmacKey}:${timeBucket}:${SECRET_KEY}`); const payload: number[] = [ 1, // version byte (LK = 1) ...hexToBytes(hmacKey), // 16 bytes ...uint32BE(timeBucket), // 4 bytes ...hexToBytes(signHash), // 32 bytes ...hexToBytes(DOM_FP), // 16 bytes ]; return Buffer.from(payload).toString("base64url"); // Node ≥ 16, no trailing = }