/** HTTP and request-signing helpers for Flixer's source API. */ export const SITE_URL = 'https://flixer.gd'; export const API_URL = 'https://plsdontscrapemelove.flixer.gd'; export const HEADERS = { 'Accept': 'text/plain', 'Origin': SITE_URL, 'Referer': `${SITE_URL}/`, 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36' }; const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; const SHA256_CONSTANTS = [ 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2 ]; export function encodeUtf8(value) { const bytes = []; for (let index = 0; index < value.length; index++) { let codePoint = value.charCodeAt(index); if (codePoint >= 0xD800 && codePoint <= 0xDBFF && index + 1 < value.length) { const trailing = value.charCodeAt(index + 1); if (trailing >= 0xDC00 && trailing <= 0xDFFF) { codePoint = 0x10000 + ((codePoint - 0xD800) << 10) + trailing - 0xDC00; index++; } } if (codePoint < 0x80) bytes.push(codePoint); else if (codePoint < 0x800) bytes.push(0xC0 | (codePoint >> 6), 0x80 | (codePoint & 63)); else if (codePoint < 0x10000) bytes.push(0xE0 | (codePoint >> 12), 0x80 | ((codePoint >> 6) & 63), 0x80 | (codePoint & 63)); else bytes.push(0xF0 | (codePoint >> 18), 0x80 | ((codePoint >> 12) & 63), 0x80 | ((codePoint >> 6) & 63), 0x80 | (codePoint & 63)); } return new Uint8Array(bytes); } function rotateRight(value, amount) { return (value >>> amount) | (value << (32 - amount)); } export function sha256(input) { const bitLength = input.length * 8; const paddedLength = Math.ceil((input.length + 9) / 64) * 64; const bytes = new Uint8Array(paddedLength); bytes.set(input); bytes[input.length] = 0x80; const high = Math.floor(bitLength / 0x100000000); const low = bitLength >>> 0; const end = paddedLength - 8; bytes[end] = (high >>> 24) & 255; bytes[end + 1] = (high >>> 16) & 255; bytes[end + 2] = (high >>> 8) & 255; bytes[end + 3] = high & 255; bytes[end + 4] = (low >>> 24) & 255; bytes[end + 5] = (low >>> 16) & 255; bytes[end + 6] = (low >>> 8) & 255; bytes[end + 7] = low & 255; const hash = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19]; const words = new Uint32Array(64); for (let offset = 0; offset < bytes.length; offset += 64) { for (let index = 0; index < 16; index++) { const position = offset + index * 4; words[index] = ((bytes[position] << 24) | (bytes[position + 1] << 16) | (bytes[position + 2] << 8) | bytes[position + 3]) >>> 0; } for (let index = 16; index < 64; index++) { const first = rotateRight(words[index - 15], 7) ^ rotateRight(words[index - 15], 18) ^ (words[index - 15] >>> 3); const second = rotateRight(words[index - 2], 17) ^ rotateRight(words[index - 2], 19) ^ (words[index - 2] >>> 10); words[index] = (words[index - 16] + first + words[index - 7] + second) >>> 0; } let [a, b, c, d, e, f, g, h] = hash; for (let index = 0; index < 64; index++) { const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25); const choice = (e & f) ^ (~e & g); const temporary1 = (h + sum1 + choice + SHA256_CONSTANTS[index] + words[index]) >>> 0; const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22); const majority = (a & b) ^ (a & c) ^ (b & c); const temporary2 = (sum0 + majority) >>> 0; h = g; g = f; f = e; e = (d + temporary1) >>> 0; d = c; c = b; b = a; a = (temporary1 + temporary2) >>> 0; } hash[0] = (hash[0] + a) >>> 0; hash[1] = (hash[1] + b) >>> 0; hash[2] = (hash[2] + c) >>> 0; hash[3] = (hash[3] + d) >>> 0; hash[4] = (hash[4] + e) >>> 0; hash[5] = (hash[5] + f) >>> 0; hash[6] = (hash[6] + g) >>> 0; hash[7] = (hash[7] + h) >>> 0; } const output = new Uint8Array(32); for (let index = 0; index < hash.length; index++) { output[index * 4] = (hash[index] >>> 24) & 255; output[index * 4 + 1] = (hash[index] >>> 16) & 255; output[index * 4 + 2] = (hash[index] >>> 8) & 255; output[index * 4 + 3] = hash[index] & 255; } return output; } export function hmacSha256(key, message) { let keyBytes = key instanceof Uint8Array ? key : encodeUtf8(String(key)); if (keyBytes.length > 64) keyBytes = sha256(keyBytes); const messageBytes = message instanceof Uint8Array ? message : encodeUtf8(String(message)); const inner = new Uint8Array(64 + messageBytes.length); const outer = new Uint8Array(96); for (let index = 0; index < 64; index++) { const value = index < keyBytes.length ? keyBytes[index] : 0; inner[index] = value ^ 0x36; outer[index] = value ^ 0x5C; } inner.set(messageBytes, 64); outer.set(sha256(inner), 64); return sha256(outer); } function encodeBase64(bytes) { let output = ''; for (let index = 0; index < bytes.length; index += 3) { const secondExists = index + 1 < bytes.length; const thirdExists = index + 2 < bytes.length; const value = (bytes[index] << 16) | ((secondExists ? bytes[index + 1] : 0) << 8) | (thirdExists ? bytes[index + 2] : 0); output += BASE64_ALPHABET[(value >>> 18) & 63]; output += BASE64_ALPHABET[(value >>> 12) & 63]; output += secondExists ? BASE64_ALPHABET[(value >>> 6) & 63] : '='; output += thirdExists ? BASE64_ALPHABET[value & 63] : '='; } return output; } function createNonce() { const bytes = new Uint8Array(16); for (let index = 0; index < bytes.length; index++) bytes[index] = Math.floor(Math.random() * 256); return encodeBase64(bytes).replace(/[/+=]/g, '').slice(0, 22); } let serverTimeOffset = 0; let serverTimeFetchedAt = 0; async function getServerTimestamp() { const now = Date.now(); if (serverTimeFetchedAt && now - serverTimeFetchedAt < 300000) { return Math.floor((now + serverTimeOffset) / 1000); } try { const startedAt = Date.now(); const response = await fetch(`${API_URL}/api/time?t=${startedAt}`, { headers: { ...HEADERS, 'Cache-Control': 'no-cache' } }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const payload = await response.json(); if (!payload || typeof payload.timestamp !== 'number') throw new Error('invalid response'); const finishedAt = Date.now(); serverTimeOffset = payload.timestamp * 1000 + (finishedAt - startedAt) / 2 - finishedAt; serverTimeFetchedAt = finishedAt; return Math.floor((finishedAt + serverTimeOffset) / 1000); } catch (_) { return Math.floor(Date.now() / 1000); } } export async function createSecureHeaders(apiKey, path) { const timestamp = await getServerTimestamp(); const nonce = createNonce(); const message = `${apiKey}:${timestamp}:${nonce}:${path}`; return { 'X-Api-Key': apiKey, 'X-Request-Timestamp': String(timestamp), 'X-Request-Nonce': nonce, 'X-Request-Signature': encodeBase64(hmacSha256(apiKey, message)), 'X-Client-Fingerprint': 'zerlgt', 'X-Fingerprint-Lite': 'e9136c41504646444' }; } export async function fetchText(url, options = {}) { const response = await fetch(url, { ...options, headers: { ...HEADERS, ...(options.headers || {}) } }); if (!response.ok) throw new Error(`HTTP ${response.status} for ${url}`); return await response.text(); }