| /** | |
| * 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 = | |
| } | |