delta-chat-bot/php-proxy/media.php
Алексей Будаев 8f47610133 Initial commit: delta-chat-bot
2026-06-13 15:53:05 +08:00

120 lines
3.2 KiB
PHP

<?php
/**
* Telegram Media Proxy with cache
*
* GET /media.php?url=https://tg.i-c-a.su/media/CHANNEL/POSTID/HASH.ext
*
* Returns cached image or proxies from tg.i-c-a.su.
* Old cache files are cleaned up probabilistically (~2% of requests).
*/
define('IMG_DIR', __DIR__ . '/cache/img');
define('TG_BASE', 'https://tg.i-c-a.su');
define('UA', 'Mozilla/5.0 (compatible; ProxyBot/1.0)');
define('MEDIA_TTL', 86400);
$url = $_GET['url'] ?? '';
if (!$url || strpos($url, TG_BASE) !== 0) {
header('HTTP/1.1 400 Bad Request');
echo 'Invalid URL';
exit;
}
if (!is_dir(IMG_DIR)) mkdir(IMG_DIR, 0755, true);
// Determine cache path from URL
$path = parse_url($url, PHP_URL_PATH);
$basename = basename($path);
if (!preg_match('/\.\w+$/', $basename)) {
$basename .= '.jpg';
}
$cacheFile = IMG_DIR . '/' . $basename;
// Content type map
$extToType = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'webp' => 'image/webp',
];
$ext = strtolower(pathinfo($basename, PATHINFO_EXTENSION));
$contentType = $extToType[$ext] ?? 'image/jpeg';
// Periodic cleanup (~2% of requests)
maybeCleanOldCache();
// Serve from cache if fresh
if (file_exists($cacheFile) && filemtime($cacheFile) > time() - MEDIA_TTL) {
header('Content-Type: ' . $contentType);
header('Content-Length: ' . filesize($cacheFile));
header('Cache-Control: public, max-age=' . MEDIA_TTL);
header('X-Cache: HIT');
readfile($cacheFile);
exit;
}
// Fetch from tg.i-c-a.su
$ctx = stream_context_create(['http' => ['timeout' => 60, 'user_agent' => UA, 'follow_location' => true]]);
$data = @file_get_contents($url, false, $ctx);
if (!$data) {
header('HTTP/1.1 502 Bad Gateway');
echo 'Failed to fetch media';
exit;
}
// Verify it's actually an image before caching
$imageSignatures = [
"\xff\xd8\xff", // JPEG
"\x89\x50\x4e\x47", // PNG
"GIF87a", // GIF
"GIF89a", // GIF
"RIFF", // WebP
];
$isImage = false;
foreach ($imageSignatures as $sig) {
if (strncmp($data, $sig, strlen($sig)) === 0) {
$isImage = true;
break;
}
}
if (!$isImage) {
header('HTTP/1.1 415 Unsupported Media Type');
header('Content-Type: text/plain');
echo 'Not an image';
exit;
}
// Save to cache
file_put_contents($cacheFile, $data);
// Return
header('Content-Type: ' . $contentType);
header('Content-Length: ' . strlen($data));
header('Cache-Control: public, max-age=' . MEDIA_TTL);
header('X-Cache: MISS');
echo $data;
// Clean files older than TTL, runs ~2% of requests
function maybeCleanOldCache(): void {
if (rand(1, 50) !== 1) return;
$dir = IMG_DIR;
if (!is_dir($dir)) return;
$cutoff = time() - MEDIA_TTL;
$imgExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$dh = opendir($dir);
if (!$dh) return;
while (($f = readdir($dh)) !== false) {
if ($f === '.' || $f === '..') continue;
$fp = $dir . '/' . $f;
if (!is_file($fp)) continue;
$ext = strtolower(pathinfo($fp, PATHINFO_EXTENSION));
// Remove non-image files and files older than TTL
if (!in_array($ext, $imgExts) || filemtime($fp) < $cutoff) {
unlink($fp);
}
}
closedir($dh);
}