forked from ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI
feat: add last podcast from castopod
This commit is contained in:
@@ -134,6 +134,26 @@ if (!defined('PLAYLIST_TITLE')) define('PLAYLIST_TITLE', '');
|
||||
// Description de la playlist (optionnel)
|
||||
if (!defined('PLAYLIST_DESCRIPTION')) define('PLAYLIST_DESCRIPTION', '');
|
||||
|
||||
// =========================================
|
||||
// Configuration Castopod
|
||||
// =========================================
|
||||
|
||||
// Activer l'affichage des podcasts Castopod
|
||||
if (!defined('CASTOPOD_ENABLED')) define('CASTOPOD_ENABLED', true);
|
||||
|
||||
// URL de l'instance Castopod
|
||||
if (!defined('CASTOPOD_URL')) define('CASTOPOD_URL', 'https://kute.o-k-i.net');
|
||||
|
||||
// Liste des slugs de podcasts à afficher (tableau)
|
||||
// Format: ['slug1', 'slug2', 'slug3']
|
||||
// Les épisodes de tous les podcasts seront mélangés et triés par date
|
||||
if (!defined('CASTOPOD_PODCAST_SLUGS')) {
|
||||
define('CASTOPOD_PODCAST_SLUGS', ['joukawouve']);
|
||||
}
|
||||
|
||||
// Nombre d'épisodes à afficher (total, tous podcasts confondus)
|
||||
if (!defined('CASTOPOD_EPISODES_COUNT')) define('CASTOPOD_EPISODES_COUNT', 5);
|
||||
|
||||
// Tags pour filtrer les vidéos selon les catégories
|
||||
if (!defined('TAG_SHORT')) define('TAG_SHORT', 'short');
|
||||
|
||||
|
||||
@@ -306,6 +306,33 @@ define('WORDPRESS_ENABLED', false);
|
||||
// URL du site WordPress pour récupérer les articles (sans trailing slash)
|
||||
// define('WORDPRESS_URL', 'https://votre-site-wordpress.com');
|
||||
|
||||
// =========================================
|
||||
// Intégration Castopod (Podcasts)
|
||||
// =========================================
|
||||
|
||||
// Activer/désactiver l'affichage des podcasts Castopod
|
||||
// define('CASTOPOD_ENABLED', true);
|
||||
|
||||
// URL de l'instance Castopod
|
||||
// define('CASTOPOD_URL', 'https://kute.o-k-i.net');
|
||||
|
||||
// Liste des slugs de podcasts à afficher (tableau)
|
||||
// Les épisodes de tous les podcasts seront mélangés et triés par date
|
||||
// define('CASTOPOD_PODCAST_SLUGS', [
|
||||
// 'joukawouve',
|
||||
// 'cspcc',
|
||||
// 'radyobokaz'
|
||||
// ]);
|
||||
|
||||
// Nombre d'épisodes à afficher (total, tous podcasts confondus)
|
||||
// define('CASTOPOD_EPISODES_COUNT', 5);
|
||||
|
||||
// IMPORTANT : Le cache est fortement recommandé pour éviter le rate limiting de Castopod
|
||||
// Castopod applique un rate limit strict (HTTP 429) sur les requêtes RSS
|
||||
// Avec le cache activé, les données sont mises en cache pendant CACHE_DURATION secondes
|
||||
// define('CACHE_ENABLED', true);
|
||||
// define('CACHE_DURATION', 3600); // 1 heure recommandé
|
||||
|
||||
// =========================================
|
||||
// Système de dons
|
||||
// =========================================
|
||||
|
||||
@@ -649,4 +649,228 @@ function searchVideos($query, $count = COUNT_VIDEO_SEARCH, $start = 0) {
|
||||
]);
|
||||
return formatVideosData($data['data'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les derniers épisodes d'un ou plusieurs podcasts Castopod via leurs feeds RSS
|
||||
*
|
||||
* @param string $castopodUrl URL de l'instance Castopod
|
||||
* @param array|string $podcastSlugs Slug(s) du/des podcast(s) - tableau ou chaîne unique
|
||||
* @param int $count Nombre d'épisodes à récupérer (total)
|
||||
* @return array Liste des épisodes formatés, triés par date
|
||||
*/
|
||||
function getCastopodEpisodes($castopodUrl = null, $podcastSlugs = null, $count = 5) {
|
||||
if (!defined('CASTOPOD_ENABLED') || !CASTOPOD_ENABLED) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$castopodUrl = $castopodUrl ?? CASTOPOD_URL;
|
||||
$podcastSlugs = $podcastSlugs ?? (defined('CASTOPOD_PODCAST_SLUGS') ? CASTOPOD_PODCAST_SLUGS : ['joukawouve']);
|
||||
$count = $count ?? CASTOPOD_EPISODES_COUNT;
|
||||
|
||||
// Convertir en tableau si c'est une chaîne unique (rétrocompatibilité)
|
||||
if (is_string($podcastSlugs)) {
|
||||
$podcastSlugs = [$podcastSlugs];
|
||||
}
|
||||
|
||||
// Clé de cache unique pour la combinaison de podcasts
|
||||
$cacheKey = 'castopod_' . md5($castopodUrl . implode('_', $podcastSlugs) . '_' . $count);
|
||||
|
||||
// Vérifier le cache
|
||||
if (defined('CACHE_ENABLED') && CACHE_ENABLED) {
|
||||
$cachedData = getFromCache($cacheKey);
|
||||
if ($cachedData !== null) {
|
||||
return $cachedData;
|
||||
}
|
||||
}
|
||||
|
||||
$allEpisodes = [];
|
||||
|
||||
// Itérer sur chaque podcast
|
||||
foreach ($podcastSlugs as $index => $podcastSlug) {
|
||||
try {
|
||||
// Ajouter un délai entre les requêtes pour éviter le rate limiting (sauf pour la première)
|
||||
// Note: Castopod a un rate limit strict, le cache est fortement recommandé
|
||||
if ($index > 0) {
|
||||
sleep(5); // 5 secondes de délai minimum pour Castopod
|
||||
}
|
||||
|
||||
// Construire l'URL du feed RSS pour ce podcast
|
||||
$feedUrl = rtrim($castopodUrl, '/') . '/@' . $podcastSlug . '/feed';
|
||||
|
||||
// Récupérer le feed RSS
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $feedUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||
$xmlContent = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200 || !$xmlContent) {
|
||||
continue; // Passer au podcast suivant en cas d'erreur
|
||||
}
|
||||
|
||||
$episodes = [];
|
||||
|
||||
// Vérifier si SimpleXML est disponible
|
||||
if (function_exists('simplexml_load_string')) {
|
||||
// Parser avec SimpleXML (méthode recommandée)
|
||||
$xml = simplexml_load_string($xmlContent);
|
||||
if (!$xml) {
|
||||
continue; // Passer au podcast suivant
|
||||
}
|
||||
|
||||
// Enregistrer les namespaces
|
||||
$namespaces = $xml->getNamespaces(true);
|
||||
|
||||
// Extraire les informations du podcast
|
||||
$podcastTitle = (string) $xml->channel->title;
|
||||
$podcastImage = (string) $xml->channel->image->url;
|
||||
$podcastLink = (string) $xml->channel->link;
|
||||
|
||||
// Parcourir les items (épisodes) - on récupère tous pour trier après
|
||||
foreach ($xml->channel->item as $item) {
|
||||
|
||||
// Extraire les infos de l'épisode
|
||||
$itunesNs = $item->children($namespaces['itunes'] ?? 'http://www.itunes.com/dtds/podcast-1.0.dtd');
|
||||
|
||||
// Récupérer l'image de l'épisode
|
||||
$episodeImage = $podcastImage;
|
||||
if (isset($itunesNs->image)) {
|
||||
$imageAttrs = $itunesNs->image->attributes();
|
||||
if (isset($imageAttrs['href'])) {
|
||||
$episodeImage = (string) $imageAttrs['href'];
|
||||
}
|
||||
}
|
||||
|
||||
$episode = [
|
||||
'title' => (string) $item->title,
|
||||
'link' => (string) $item->link,
|
||||
'pubDate' => (string) $item->pubDate,
|
||||
'description' => strip_tags((string) $item->description),
|
||||
'duration' => (string) $itunesNs->duration ?? '',
|
||||
'image' => $episodeImage,
|
||||
'audioUrl' => (string) $item->enclosure['url'] ?? '',
|
||||
'podcastTitle' => $podcastTitle,
|
||||
'podcastLink' => $podcastLink,
|
||||
'podcastSlug' => $podcastSlug,
|
||||
];
|
||||
|
||||
// Formater la date
|
||||
if ($episode['pubDate']) {
|
||||
try {
|
||||
$date = new DateTime($episode['pubDate']);
|
||||
$episode['formattedDate'] = $date->format('d/m/Y');
|
||||
$episode['timestamp'] = $date->getTimestamp();
|
||||
} catch (Exception $e) {
|
||||
$episode['formattedDate'] = '';
|
||||
$episode['timestamp'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Formater la durée
|
||||
if ($episode['duration']) {
|
||||
$episode['formattedDuration'] = formatDuration($episode['duration']);
|
||||
} else {
|
||||
$episode['formattedDuration'] = '';
|
||||
}
|
||||
|
||||
// Limiter la description
|
||||
if (strlen($episode['description']) > 150) {
|
||||
$episode['description'] = substr($episode['description'], 0, 150) . '...';
|
||||
}
|
||||
|
||||
$episodes[] = $episode;
|
||||
}
|
||||
} else {
|
||||
// Fallback: Parser avec regex (moins fiable mais fonctionne sans SimpleXML)
|
||||
// Extraire les infos du podcast
|
||||
preg_match('/<channel>.*?<title>(.*?)<\/title>/s', $xmlContent, $podcastTitleMatch);
|
||||
preg_match('/<channel>.*?<link>(.*?)<\/link>/s', $xmlContent, $podcastLinkMatch);
|
||||
preg_match('/<image>.*?<url>(.*?)<\/url>/s', $xmlContent, $podcastImageMatch);
|
||||
|
||||
$podcastTitle = $podcastTitleMatch[1] ?? '';
|
||||
$podcastLink = $podcastLinkMatch[1] ?? '';
|
||||
$podcastImage = $podcastImageMatch[1] ?? '';
|
||||
|
||||
// Extraire tous les items
|
||||
preg_match_all('/<item>(.*?)<\/item>/s', $xmlContent, $items);
|
||||
|
||||
foreach ($items[1] as $itemContent) {
|
||||
// Extraire les données de chaque item
|
||||
preg_match('/<title>(.*?)<\/title>/', $itemContent, $titleMatch);
|
||||
preg_match('/<link>(.*?)<\/link>/', $itemContent, $linkMatch);
|
||||
preg_match('/<pubDate>(.*?)<\/pubDate>/', $itemContent, $dateMatch);
|
||||
preg_match('/<description>(.*?)<\/description>/s', $itemContent, $descMatch);
|
||||
preg_match('/<itunes:duration>(.*?)<\/itunes:duration>/', $itemContent, $durationMatch);
|
||||
preg_match('/<itunes:image[^>]*href=["\']([^"\']*)["\']/', $itemContent, $imageMatch);
|
||||
preg_match('/<enclosure[^>]*url=["\']([^"\']*)["\']/', $itemContent, $audioMatch);
|
||||
|
||||
$episode = [
|
||||
'title' => $titleMatch[1] ?? '',
|
||||
'link' => $linkMatch[1] ?? '',
|
||||
'pubDate' => $dateMatch[1] ?? '',
|
||||
'description' => strip_tags($descMatch[1] ?? ''),
|
||||
'duration' => $durationMatch[1] ?? '',
|
||||
'image' => $imageMatch[1] ?? $podcastImage,
|
||||
'audioUrl' => $audioMatch[1] ?? '',
|
||||
'podcastTitle' => $podcastTitle,
|
||||
'podcastLink' => $podcastLink,
|
||||
'podcastSlug' => $podcastSlug,
|
||||
];
|
||||
|
||||
// Formater la date
|
||||
if ($episode['pubDate']) {
|
||||
try {
|
||||
$date = new DateTime($episode['pubDate']);
|
||||
$episode['formattedDate'] = $date->format('d/m/Y');
|
||||
$episode['timestamp'] = $date->getTimestamp();
|
||||
} catch (Exception $e) {
|
||||
$episode['formattedDate'] = '';
|
||||
$episode['timestamp'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Formater la durée
|
||||
if ($episode['duration']) {
|
||||
$episode['formattedDuration'] = formatDuration($episode['duration']);
|
||||
} else {
|
||||
$episode['formattedDuration'] = '';
|
||||
}
|
||||
|
||||
// Limiter la description
|
||||
if (strlen($episode['description']) > 150) {
|
||||
$episode['description'] = substr($episode['description'], 0, 150) . '...';
|
||||
}
|
||||
|
||||
$episodes[] = $episode;
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter les épisodes de ce podcast à la liste globale
|
||||
$allEpisodes = array_merge($allEpisodes, $episodes);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('Erreur lors de la récupération des épisodes Castopod pour ' . $podcastSlug . ': ' . $e->getMessage());
|
||||
// Continuer avec le podcast suivant
|
||||
}
|
||||
}
|
||||
|
||||
// Trier tous les épisodes par date (du plus récent au plus ancien)
|
||||
usort($allEpisodes, function($a, $b) {
|
||||
return ($b['timestamp'] ?? 0) - ($a['timestamp'] ?? 0);
|
||||
});
|
||||
|
||||
// Limiter au nombre demandé
|
||||
$allEpisodes = array_slice($allEpisodes, 0, $count);
|
||||
|
||||
// Mettre en cache
|
||||
if (defined('CACHE_ENABLED') && CACHE_ENABLED) {
|
||||
saveToCache($cacheKey, $allEpisodes);
|
||||
}
|
||||
|
||||
return $allEpisodes;
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -160,6 +160,26 @@ function callApiCached($cacheKey, $callable, $ttl = 900) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helpers simples pour le cache générique
|
||||
*/
|
||||
function getFromCache($key) {
|
||||
if (!defined('CACHE_ENABLED') || !CACHE_ENABLED) {
|
||||
return null;
|
||||
}
|
||||
$cache = $GLOBALS['simple_api_cache'];
|
||||
return $cache->get($key, []);
|
||||
}
|
||||
|
||||
function saveToCache($key, $data, $ttl = null) {
|
||||
if (!defined('CACHE_ENABLED') || !CACHE_ENABLED) {
|
||||
return;
|
||||
}
|
||||
$ttl = $ttl ?? (defined('CACHE_DURATION') ? CACHE_DURATION : 3600);
|
||||
$cache = $GLOBALS['simple_api_cache'];
|
||||
$cache->set($key, [], $data, $ttl);
|
||||
}
|
||||
|
||||
// Nettoyage automatique occasionnel
|
||||
if (rand(1, 100) === 1) {
|
||||
$GLOBALS['simple_api_cache']->cleanup();
|
||||
|
||||
Reference in New Issue
Block a user