Files
john-nuvio/src/flixer/http.js
T
2026-07-19 20:09:25 +03:00

202 lines
8.4 KiB
JavaScript

/** 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();
}