'https://...', 'src' => 'https://.../banner.png', 'alt' => 'name']. * The image is fetched + cached server-side, so nothing hotlinks the remote host on every visit. */ const BUTTONS_88x31 = [ ['href' => 'https://patate.dev', 'src' => 'https://patate.dev/images/patate_dev_banner.jpeg', 'alt' => 'patate.dev'], ['href' => 'https://www.counter-strike.net/news/updates', 'src' => 'https://matdoes.dev/buttons/i/8678a5dfa58ce412f7d8f978b14e2f64.gif', 'alt' => 'https://blog.counter-strike.net/'], ['href' => 'caddyserver.com', 'src' => 'https://caddyserver.com/resources/images/nostalgia/caddy.png', 'alt' => 'caddyserver.com'], ['href' => '', 'src' => 'https://matdoes.dev/buttons/i/9025eae1851e8f32ff70f43f2c3393f0.png', 'alt' => 'ownpaws'], ['href' => 'https://ssi.fyi/', 'src'=>'https://ssi.fyi/88x31.gif', 'alt'=>'https://ssi.fyi'], ['href' => 'https://vmfunc.re/', 'src' => 'https://vmfunc.re/button.png', 'alt' => 'vmfunc.re'], ['href' => 'https://m6.wtf', 'src' => 'https://m6.wtf/88x31.gif', 'alt' => 'm6.wtf'], ['href' => 'https://0xa0.dev/', 'src' => '/img/0xa0.png', 'alt' => '0xA0.dev'] ]; const BUTTON_CACHE_TTL = 7 * 86400; // re-fetch each button at most once a week /** * Fetch a remote image into the local cache (at most once a week) and return * the cache file's path, or null if it couldn't be fetched. */ function button_cache(string $url): ?string { $dir = DATA_DIR . '/buttons'; @mkdir($dir, 0775, true); $file = $dir . '/' . md5($url); // stores "content-type\n" if (!is_file($file) || (time() - filemtime($file)) > BUTTON_CACHE_TTL) { $ctx = stream_context_create(['http' => [ 'timeout' => 5, 'user_agent' => 'Mozilla/5.0 (0xa0.dev button fetcher)', ]]); $bytes = @file_get_contents($url, false, $ctx); if ($bytes !== false && $bytes !== '') { $type = 'image/png'; foreach ($http_response_header ?? [] as $h) { if (stripos($h, 'content-type:') === 0) { $type = trim(substr($h, strlen('content-type:'))); break; } } file_put_contents($file, $type . "\n" . $bytes); } } return is_file($file) ? $file : null; } /** * Where the page should load a button from: our own copy (button.php serves * it, versioned by the cache file's mtime so browsers can keep it), or the * original if fetching failed. Was inlined as base64 before, which made every * page ~260 KB heavier. */ function button_src(string $url): string { $file = button_cache($url); return $file ? '/button.php?h=' . basename($file) . '&v=' . filemtime($file) : $url; } /** Render the whole 88x31 button row as HTML. */ function render_buttons_88x31(): string { $html = ''; foreach (BUTTONS_88x31 as $b) { $uri = button_src($b['src']); $img = '' . htmlspecialchars($b['alt']) . ''; $href = $b['href'] ?? ''; if ($href !== '') { $html .= '' . $img . '' . "\n"; } else { $html .= $img . "\n"; // no href → just the image, not clickable } } return $html; }