Initial commit — OFApp client + server
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
84
server/signing.js
Normal file
84
server/signing.js
Normal file
@@ -0,0 +1,84 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user