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

99 lines
3.1 KiB
PHP

<?php
/**
* Telegram RSS Proxy with cache
*
* GET /rss.php?channel=CHANNEL&limit=N
* or via .htaccess rewrite: /rss/CHANNEL?limit=N
*
* Returns RSS XML from local cache (if fresh) or proxies from tg.i-c-a.su.
* Enclosure URLs are rewritten to go through media.php for local caching.
* Cache files are pre-rewritten so cache hits avoid DOMDocument entirely.
*/
define('CACHE_DIR', __DIR__ . '/cache');
define('TG_BASE', 'https://tg.i-c-a.su');
define('PROXY_BASE', 'https://proxy.budaev.org');
define('UA', 'Mozilla/5.0 (compatible; ProxyBot/1.0)');
define('CACHE_TTL', 600);
$channel = preg_replace('/[^a-zA-Z0-9_]/', '', $_GET['channel'] ?? '');
if (!$channel) {
header('HTTP/1.1 400 Bad Request');
echo 'Missing channel parameter';
exit;
}
$limit = min(max((int)($_GET['limit'] ?? 10), 1), 100);
// Staggered TTL: vary per channel to avoid mass expiration
$stagger = crc32($channel) % 300;
$ttl = CACHE_TTL + $stagger;
// Non-standard limits: fetch directly, no caching
if ($limit !== 10) {
$xml = fetchFeed($channel, $limit, true);
if ($xml === null) {
header('HTTP/1.1 502 Bad Gateway');
echo 'Failed to fetch feed';
exit;
}
header('Content-Type: application/rss+xml; charset=utf-8');
echo $xml;
exit;
}
$cacheFile = CACHE_DIR . '/' . $channel . '.xml';
// Cache hit — pre-rewritten, no DOMDocument needed
if (file_exists($cacheFile) && filemtime($cacheFile) > time() - $ttl) {
header('Content-Type: application/rss+xml; charset=utf-8');
readfile($cacheFile);
exit;
}
// Cache miss or stale — fetch and rewrite in one DOMDocument pass
$xml = fetchFeed($channel, $limit, false);
if ($xml === null) {
// Serve stale cache if available
if (file_exists($cacheFile)) {
header('Content-Type: application/rss+xml; charset=utf-8');
readfile($cacheFile);
exit;
}
// No cache at all — return empty feed so bot doesn't fall back to tg.i-c-a.su
header('Content-Type: application/rss+xml; charset=utf-8');
echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>' . htmlspecialchars($channel) . '</title></channel></rss>';
exit;
}
// Save pre-rewritten XML to cache
file_put_contents($cacheFile, $xml);
header('Content-Type: application/rss+xml; charset=utf-8');
echo $xml;
function fetchFeed(string $channel, int $limit, bool $skipRewrite): ?string {
$url = TG_BASE . '/rss/' . $channel . '?limit=' . $limit;
$ctx = stream_context_create(['http' => ['timeout' => 30, 'user_agent' => UA, 'follow_location' => true]]);
$raw = @file_get_contents($url, false, $ctx);
if (!$raw) return null;
if ($skipRewrite) {
return $raw;
}
// Parse DOM once, rewrite enclosures, return pre-rewritten XML
$doc = new DOMDocument();
@$doc->loadXML($raw);
if (!$doc->documentElement) return null;
foreach ($doc->getElementsByTagName('enclosure') as $enc) {
$encUrl = $enc->getAttribute('url');
if (strpos($encUrl, TG_BASE) !== false) {
$enc->setAttribute('url', PROXY_BASE . '/media.php?url=' . urlencode($encUrl));
}
}
return $doc->saveXML();
}