120 lines
3.2 KiB
PHP
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);
|
|
}
|