130 lines
4.7 KiB
PHP
130 lines
4.7 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* Récupération des statistiques de followers pour chaque plateforme sociale.
|
||
|
|
* Toutes les fonctions retournent null en cas d'échec — l'affichage reste optionnel.
|
||
|
|
* Cache 24h pour limiter les appels externes.
|
||
|
|
*/
|
||
|
|
|
||
|
|
function formatFollowerCount(int $n): string {
|
||
|
|
if ($n >= 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;
|
||
|
|
}
|