feat: social media aggregator (YouTube, Instagram, TikTok, WordPress)
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* Intégration YouTube Data API v3
|
||||
*/
|
||||
|
||||
function callYouTubeApi(string $endpoint, array $params = []): array {
|
||||
if (!defined('YOUTUBE_API_KEY') || empty(YOUTUBE_API_KEY)) return [];
|
||||
|
||||
$params['key'] = YOUTUBE_API_KEY;
|
||||
$url = 'https://www.googleapis.com/youtube/v3/' . $endpoint . '?' . http_build_query($params);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_CONNECTTIMEOUT => 8,
|
||||
CURLOPT_FOLLOWLOCATION => false,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_USERAGENT => 'KaubuntuRe/2.0',
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response === false || !empty($error) || $httpCode < 200 || $httpCode >= 300) {
|
||||
error_log('YouTube API error: ' . $error . ' (HTTP ' . $httpCode . ')');
|
||||
return [];
|
||||
}
|
||||
|
||||
return json_decode($response, true) ?: [];
|
||||
}
|
||||
|
||||
function getYouTubeChannelId(): string {
|
||||
if (defined('YOUTUBE_CHANNEL_ID') && !empty(YOUTUBE_CHANNEL_ID)) {
|
||||
return YOUTUBE_CHANNEL_ID;
|
||||
}
|
||||
|
||||
if (!defined('YOUTUBE_API_KEY') || empty(YOUTUBE_API_KEY)) return '';
|
||||
if (!defined('YOUTUBE_CHANNEL_HANDLE') || empty(YOUTUBE_CHANNEL_HANDLE)) return '';
|
||||
|
||||
$handle = ltrim(YOUTUBE_CHANNEL_HANDLE, '@');
|
||||
$cached = $GLOBALS['simple_api_cache']->get('yt_channel_id_' . $handle, []);
|
||||
if ($cached !== null) return (string) $cached;
|
||||
|
||||
$data = callYouTubeApi('channels', ['part' => 'snippet', 'forHandle' => $handle]);
|
||||
$channelId = $data['items'][0]['id'] ?? '';
|
||||
|
||||
if ($channelId) {
|
||||
$GLOBALS['simple_api_cache']->set('yt_channel_id_' . $handle, [], $channelId, 86400);
|
||||
}
|
||||
|
||||
return $channelId;
|
||||
}
|
||||
|
||||
function isYouTubeShort(string $isoDuration): bool {
|
||||
preg_match('/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/', $isoDuration, $m);
|
||||
$seconds = (int)($m[1] ?? 0) * 3600 + (int)($m[2] ?? 0) * 60 + (int)($m[3] ?? 0);
|
||||
return $seconds > 0 && $seconds <= 60;
|
||||
}
|
||||
|
||||
function getYouTubeLatestVideos(int $count = 6): array {
|
||||
// Priorité 1 : YouTube Data API v3
|
||||
if (defined('YOUTUBE_API_KEY') && !empty(YOUTUBE_API_KEY)) {
|
||||
$channelId = getYouTubeChannelId();
|
||||
if ($channelId) {
|
||||
$shortsTarget = min(3, $count - 1);
|
||||
$regularTarget = $count - $shortsTarget;
|
||||
|
||||
$cacheKey = 'yt_videos2_' . $channelId . '_' . $count;
|
||||
$cached = $GLOBALS['simple_api_cache']->get($cacheKey, []);
|
||||
if ($cached !== null) return $cached;
|
||||
|
||||
// On récupère 50 vidéos pour avoir suffisamment de chaque type
|
||||
$searchData = callYouTubeApi('search', [
|
||||
'part' => 'snippet',
|
||||
'channelId' => $channelId,
|
||||
'order' => 'date',
|
||||
'type' => 'video',
|
||||
'maxResults' => 50,
|
||||
]);
|
||||
|
||||
if (!empty($searchData['items'])) {
|
||||
$videoIds = array_map(fn($i) => $i['id']['videoId'], $searchData['items']);
|
||||
|
||||
// videos.list coûte 1 unité quota (vs 100 pour search)
|
||||
$detailData = callYouTubeApi('videos', [
|
||||
'part' => 'contentDetails',
|
||||
'id' => implode(',', $videoIds),
|
||||
]);
|
||||
|
||||
$durations = [];
|
||||
foreach ($detailData['items'] ?? [] as $item) {
|
||||
$durations[$item['id']] = $item['contentDetails']['duration'] ?? '';
|
||||
}
|
||||
|
||||
$shorts = [];
|
||||
$regular = [];
|
||||
|
||||
foreach ($searchData['items'] as $item) {
|
||||
$videoId = $item['id']['videoId'];
|
||||
$isShort = isset($durations[$videoId])
|
||||
? isYouTubeShort($durations[$videoId])
|
||||
: false;
|
||||
|
||||
$video = [
|
||||
'id' => $videoId,
|
||||
'title' => html_entity_decode($item['snippet']['title'], ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'thumbnail' => $item['snippet']['thumbnails']['high']['url']
|
||||
?? $item['snippet']['thumbnails']['default']['url'],
|
||||
'publishedAt' => $item['snippet']['publishedAt'],
|
||||
'url' => $isShort
|
||||
? 'https://www.youtube.com/shorts/' . $videoId
|
||||
: 'https://www.youtube.com/watch?v=' . $videoId,
|
||||
'isShort' => $isShort,
|
||||
];
|
||||
|
||||
if ($isShort && count($shorts) < $shortsTarget) {
|
||||
$shorts[] = $video;
|
||||
} elseif (!$isShort && count($regular) < $regularTarget) {
|
||||
$regular[] = $video;
|
||||
}
|
||||
|
||||
if (count($shorts) >= $shortsTarget && count($regular) >= $regularTarget) break;
|
||||
}
|
||||
|
||||
// Shorts en premier, puis vidéos normales
|
||||
$videos = array_merge($shorts, $regular);
|
||||
|
||||
if (!empty($videos)) {
|
||||
$ttl = defined('CACHE_DURATION') ? CACHE_DURATION : 900;
|
||||
$GLOBALS['simple_api_cache']->set($cacheKey, [], $videos, $ttl);
|
||||
return $videos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Priorité 2 : flux RSS public YouTube (sans clé API, nécessite YOUTUBE_CHANNEL_ID)
|
||||
return getYouTubeVideosFromRSS($count);
|
||||
}
|
||||
|
||||
function getYouTubeVideosFromRSS(int $count = 6): array {
|
||||
if (!defined('YOUTUBE_CHANNEL_ID') || empty(YOUTUBE_CHANNEL_ID)) return [];
|
||||
|
||||
$cacheKey = 'yt_rss_' . YOUTUBE_CHANNEL_ID . '_' . $count;
|
||||
$cached = $GLOBALS['simple_api_cache']->get($cacheKey, []);
|
||||
if ($cached !== null) return $cached;
|
||||
|
||||
$feedUrl = 'https://www.youtube.com/feeds/videos.xml?channel_id=' . urlencode(YOUTUBE_CHANNEL_ID);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $feedUrl,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_FOLLOWLOCATION => false,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_USERAGENT => 'KaubuntuRe/2.0',
|
||||
]);
|
||||
$xmlString = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if (!$xmlString || $httpCode !== 200) {
|
||||
error_log('YouTube RSS error: HTTP ' . $httpCode);
|
||||
return [];
|
||||
}
|
||||
|
||||
$xml = @simplexml_load_string($xmlString);
|
||||
if (!$xml) return [];
|
||||
|
||||
$videos = [];
|
||||
$ytNs = 'http://www.youtube.com/xml/schemas/2015';
|
||||
|
||||
foreach ($xml->entry as $entry) {
|
||||
if (count($videos) >= $count) break;
|
||||
$yt = $entry->children($ytNs);
|
||||
$videoId = (string) $yt->videoId;
|
||||
if (!$videoId) continue;
|
||||
|
||||
$videos[] = [
|
||||
'id' => $videoId,
|
||||
'title' => html_entity_decode((string) $entry->title, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'thumbnail' => "https://i.ytimg.com/vi/{$videoId}/hqdefault.jpg",
|
||||
'publishedAt' => (string) $entry->published,
|
||||
'url' => "https://www.youtube.com/watch?v={$videoId}",
|
||||
'isShort' => false,
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($videos)) {
|
||||
$ttl = defined('CACHE_DURATION') ? CACHE_DURATION : 3600;
|
||||
$GLOBALS['simple_api_cache']->set($cacheKey, [], $videos, $ttl);
|
||||
}
|
||||
|
||||
return $videos;
|
||||
}
|
||||
Reference in New Issue
Block a user