= 1_000_000) return number_format($n / 1_000_000, 1, '.', '') . 'M'; if ($n >= 1_000) return number_format($n / 1_000, 1, '.', '') . 'K'; return (string) $n; } function _statsCurlFetch(string $url, array $headers = []): ?string { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 8, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_FOLLOWLOCATION => false, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_USERAGENT => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/124.0 Safari/537.36', CURLOPT_HTTPHEADER => $headers, ]); $body = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return ($body && $httpCode === 200) ? $body : null; } function getYouTubeSubscriberCount(): ?int { // Priority 1: YouTube Data API v3 (exact count) if (defined('YOUTUBE_API_KEY') && !empty(YOUTUBE_API_KEY)) { $channelId = getYouTubeChannelId(); if ($channelId) { $cacheKey = 'stat_yt_' . $channelId; $cached = $GLOBALS['simple_api_cache']->get($cacheKey, []); if ($cached !== null) return (int) $cached ?: null; $data = callYouTubeApi('channels', ['part' => 'statistics', 'id' => $channelId]); $count = isset($data['items'][0]['statistics']['subscriberCount']) ? (int) $data['items'][0]['statistics']['subscriberCount'] : null; if ($count !== null) { $GLOBALS['simple_api_cache']->set($cacheKey, [], $count, 86400); return $count; } } } // Priority 2: parse ytInitialData from public channel page $handle = ''; if (defined('YOUTUBE_HANDLE') && !empty(YOUTUBE_HANDLE)) { $handle = ltrim(YOUTUBE_HANDLE, '@'); } elseif (defined('YOUTUBE_CHANNEL_HANDLE') && !empty(YOUTUBE_CHANNEL_HANDLE)) { $handle = ltrim(YOUTUBE_CHANNEL_HANDLE, '@'); } $pageUrl = $handle ? 'https://www.youtube.com/@' . urlencode($handle) : (defined('YOUTUBE_CHANNEL_ID') && YOUTUBE_CHANNEL_ID ? 'https://www.youtube.com/channel/' . urlencode(YOUTUBE_CHANNEL_ID) : null); if (!$pageUrl) return null; $cacheKey = 'stat_yt_page_' . preg_replace('/[^a-z0-9]/i', '_', $handle ?: YOUTUBE_CHANNEL_ID); $cached = $GLOBALS['simple_api_cache']->get($cacheKey, []); if ($cached !== null) return (int) $cached ?: null; $body = _statsCurlFetch($pageUrl, ['Accept-Language: fr-FR,fr;q=0.9,en;q=0.8']); if (!$body) return null; // ytInitialData embeds exact subscriberCount as a quoted integer string if (preg_match('/"subscriberCount"\s*:\s*"(\d+)"/', $body, $m)) { $count = (int) $m[1]; $GLOBALS['simple_api_cache']->set($cacheKey, [], $count, 86400); return $count; } return null; } function getInstagramFollowers(): ?int { if (!defined('INSTAGRAM_HANDLE') || empty(INSTAGRAM_HANDLE)) return null; $handle = ltrim(INSTAGRAM_HANDLE, '@'); $cacheKey = 'stat_ig_' . $handle; $cached = $GLOBALS['simple_api_cache']->get($cacheKey, []); if ($cached !== null) return (int) $cached ?: null; $body = _statsCurlFetch( 'https://www.instagram.com/api/v1/users/web_profile_info/?username=' . urlencode($handle), ['X-IG-App-ID: 936619743392459', 'Accept: application/json'] ); if (!$body) return null; $data = json_decode($body, true); $count = $data['data']['user']['edge_followed_by']['count'] ?? null; if ($count !== null) { $count = (int) $count; $GLOBALS['simple_api_cache']->set($cacheKey, [], $count, 86400); } return $count; } function getTikTokFollowers(): ?int { if (!defined('TIKTOK_HANDLE') || empty(TIKTOK_HANDLE)) return null; $handle = ltrim(TIKTOK_HANDLE, '@'); $cacheKey = 'stat_tt_' . $handle; $cached = $GLOBALS['simple_api_cache']->get($cacheKey, []); if ($cached !== null) return (int) $cached ?: null; $body = _statsCurlFetch('https://www.tiktok.com/embed/@' . urlencode($handle)); if (!$body) return null; preg_match('/"followerCount"\s*:\s*(\d+)/', $body, $m); $count = isset($m[1]) ? (int) $m[1] : null; if ($count !== null) { $GLOBALS['simple_api_cache']->set($cacheKey, [], $count, 86400); } return $count; }