85 lines
2.7 KiB
JavaScript
85 lines
2.7 KiB
JavaScript
import { createHash } from 'crypto';
|
|
import fetch from 'node-fetch';
|
|
|
|
// Try multiple community-maintained rule sources in order
|
|
const RULES_URLS = [
|
|
'https://raw.githubusercontent.com/rafa-9/dynamic-rules/main/rules.json',
|
|
'https://raw.githubusercontent.com/datawhores/onlyfans-dynamic-rules/main/dynamicRules.json',
|
|
'https://raw.githubusercontent.com/DATAHOARDERS/dynamic-rules/main/onlyfans.json',
|
|
];
|
|
const REFRESH_INTERVAL = 60 * 60 * 1000; // 1 hour
|
|
|
|
let rules = null;
|
|
let lastFetch = 0;
|
|
|
|
function normalizeRules(raw) {
|
|
// Different sources use different key names — normalize them
|
|
return {
|
|
static_param: raw.static_param,
|
|
checksum_indexes: raw.checksum_indexes,
|
|
checksum_constant: raw.checksum_constant ?? 0,
|
|
checksum_constants: raw.checksum_constants ?? null, // per-index constants (some sources)
|
|
app_token: raw.app_token || raw['app-token'] || '33d57ade8c02dbc5a333db99ff9ae26a',
|
|
prefix: raw.prefix || raw.format?.split(':')[0],
|
|
suffix: raw.suffix || raw.format?.split(':').pop(),
|
|
remove_headers: raw.remove_headers ?? [],
|
|
};
|
|
}
|
|
|
|
async function fetchRules() {
|
|
for (const url of RULES_URLS) {
|
|
try {
|
|
const res = await fetch(url);
|
|
if (!res.ok) continue;
|
|
const raw = await res.json();
|
|
rules = normalizeRules(raw);
|
|
lastFetch = Date.now();
|
|
console.log(`[signing] Rules loaded from ${url} (prefix: ${rules.prefix})`);
|
|
return rules;
|
|
} catch (err) {
|
|
console.warn(`[signing] Failed to fetch from ${url}: ${err.message}`);
|
|
}
|
|
}
|
|
throw new Error('All dynamic rules sources failed');
|
|
}
|
|
|
|
export async function initRules() {
|
|
await fetchRules();
|
|
}
|
|
|
|
export function getRules() {
|
|
if (Date.now() - lastFetch > REFRESH_INTERVAL) {
|
|
fetchRules().catch((err) => console.error('[signing] Failed to refresh rules:', err.message));
|
|
}
|
|
return rules;
|
|
}
|
|
|
|
export function createSignedHeaders(path, userId) {
|
|
if (!rules) throw new Error('Signing rules not initialized');
|
|
|
|
const timestamp = Date.now().toString();
|
|
|
|
// Use "0" for userId when user-id is in remove_headers
|
|
const signUserId = rules.remove_headers?.includes('user-id') ? '0' : userId;
|
|
const message = [rules.static_param, timestamp, path, signUserId].join('\n');
|
|
|
|
const sha1Hex = createHash('sha1').update(message).digest('hex');
|
|
|
|
const hexBytes = Buffer.from(sha1Hex, 'ascii');
|
|
let checksum = 0;
|
|
for (let i = 0; i < rules.checksum_indexes.length; i++) {
|
|
const byteVal = hexBytes[rules.checksum_indexes[i]];
|
|
const perIndex = rules.checksum_constants?.[i] ?? 0;
|
|
checksum += byteVal + perIndex;
|
|
}
|
|
checksum += rules.checksum_constant;
|
|
|
|
const sign = `${rules.prefix}:${sha1Hex}:${Math.abs(checksum).toString(16)}:${rules.suffix}`;
|
|
|
|
return {
|
|
sign,
|
|
time: timestamp,
|
|
'app-token': rules.app_token,
|
|
};
|
|
}
|