Nom personnalisé] if (!defined('PRIORITY_CATEGORIES')) { define('PRIORITY_CATEGORIES', [ 11 => 'Actualité & Politique', // News & Politique 14 => 'Activisme', // Activism 1 => 'Musique', // Musique ]); } // ========================================= // Configuration Mastodon // ========================================= if (!defined('MASTODON_INSTANCE_URL')) define('MASTODON_INSTANCE_URL', 'https://mamot.fr'); if (!defined('MASTODON_DATE_FORMAT')) define('MASTODON_DATE_FORMAT', 'fr-FR'); if (!defined('MASTODON_BTN_SEE_MORE')) define('MASTODON_BTN_SEE_MORE', 'Voir plus de post'); if (!defined('MASTODON_BTN_RELOAD')) define('MASTODON_BTN_RELOAD', 'Rafraichir'); if (!defined('MASTODON_MAX_POST_FETCH')) define('MASTODON_MAX_POST_FETCH', '10'); if (!defined('MASTODON_MAX_POST_SHOW')) define('MASTODON_MAX_POST_SHOW', '10'); // Informations du site if (!defined('SITE_NAME')) define('SITE_NAME', 'kaubuntu.re'); if (!defined('SITE_DESCRIPTION')) define('SITE_DESCRIPTION', 'Votre plateforme de médias libres'); if (!defined('SITE_LOGO')) define('SITE_LOGO', 'img/logo.png'); if (!defined('SITE_FAVICON')) define('SITE_FAVICON', 'img/favicon.png'); // Réseaux sociaux if (!defined('FACEBOOK_URL')) define('FACEBOOK_URL', '#'); if (!defined('X_URL')) define('X_URL', '#'); if (!defined('INSTAGRAM_URL')) define('INSTAGRAM_URL', '#'); if (!defined('YOUTUBE_URL')) define('YOUTUBE_URL', '#'); if (!defined('TIKTOK_URL')) define('TIKTOK_URL', '#'); // Contacts if (!defined('CONTACT_EMAIL')) define('CONTACT_EMAIL', 'contact@kaubuntu.re'); // Mentions légales if (!defined('LEGAL_COPYRIGHT')) define('LEGAL_COPYRIGHT', 'Ka-Ubuntu'); if (!defined('LEGAL_WEBMASTER_NAME')) define('LEGAL_WEBMASTER_NAME', 'Cédric Famibelle-Pronzola'); if (!defined('LEGAL_WEBMASTER_EMAIL')) define('LEGAL_WEBMASTER_EMAIL', 'contact@cedric-pronzola.re'); if (!defined('LEGAL_HOST_NAME')) define('LEGAL_HOST_NAME', 'o2Switch'); if (!defined('LEGAL_HOST_COMPANY')) define('LEGAL_HOST_COMPANY', 'société au capital de 100 000 €'); if (!defined('LEGAL_HOST_RCS')) define('LEGAL_HOST_RCS', 'immatriculée au RCS de Clermont-Ferrand sous le numéro 510 909 807'); if (!defined('LEGAL_HOST_ADDRESS')) define('LEGAL_HOST_ADDRESS', '222 boulevard Gustave Flaubert, 63000 Clermont-Ferrand, France'); if (!defined('LEGAL_CONTACT_EMAIL')) define('LEGAL_CONTACT_EMAIL', 'zinfos@kaubuntu.com'); if (!defined('LEGAL_LICENSE')) define('LEGAL_LICENSE', 'GNU Affero General Public License version 3 (AGPL-V3)'); if (!defined('LEGAL_LICENSE_URL')) define('LEGAL_LICENSE_URL', 'https://www.gnu.org/licenses/agpl-3.0.html'); if (!defined('LEGAL_SOURCE_CODE_URL')) define('LEGAL_SOURCE_CODE_URL', 'https://codeberg.org/Ka-Ubuntu/kaubuntu.re'); if (!defined('LEGAL_SERVICE_DESCRIPTION')) define('LEGAL_SERVICE_DESCRIPTION', 'est une plateforme multimédia proposant des contenus vidéo, des actualités et des informations liées au mouvement politique indépendantiste et panafricaniste réunionnais Ka-Ubuntu.'); // Fonctionnalités define('ENABLE_SEARCH', true); if (!defined('ENABLE_USER_ACCOUNTS')) define('ENABLE_USER_ACCOUNTS', false); // Cache if (!defined('CACHE_ENABLED')) define('CACHE_ENABLED', false); if (!defined('CACHE_DURATION')) define('CACHE_DURATION', 3600); // En secondes (1 heure) // Compte pour les lives if (!defined('LIVE_ACCOUNT_NAME')) define('LIVE_ACCOUNT_NAME', 'admin'); // Tags pour filtrer les vidéos selon les catégories if (!defined('TAG_SHORT')) define('TAG_SHORT', 'short'); // Hashtags importants à afficher dans la sidebar, footer et menu mobile if (!defined('IMPORTANT_TAGS')) { define('IMPORTANT_TAGS', [ 'Colonialisme', 'La Réunion', 'Panafricanisme', 'Conférence' ]); } // Hashtags populaires à afficher sur la page d'accueil if (!defined('POPULAR_TAGS')) { define('POPULAR_TAGS', [ 'Justice', 'Anticolonial', 'Kanaky', 'Océan Indien' ]); } // Locale et fuseau horaire setlocale(LC_TIME, 'fr_FR.UTF-8'); date_default_timezone_set('Indian/Reunion'); // Initialisation des catégories de vidéo depuis l'API $peertube_categories = initCategories(); define('PEERTUBE_CATEGORIES', $peertube_categories); /** * Initialise et récupère les catégories depuis l'API PeerTube * * @return array Liste des catégories */ function initCategories() { // Récupérer la liste des catégories depuis l'API $categories = callPeerTubeApi('videos/categories'); // Tableau de correspondance pour traduire les catégories en français $translations = [ 'Music' => 'Musique', 'Films' => 'Films', 'Vehicles' => 'Véhicules', 'Art' => 'Art', 'Sports' => 'Sports', 'Travels' => 'Voyages', 'Gaming' => 'Jeux vidéo', 'People' => 'Personnes', 'Comedy' => 'Humour', 'Entertainment' => 'Divertissement', 'News & Politics' => 'Actualités & Politique', 'How To' => 'Tutoriels', 'Education' => 'Éducation', 'Activism' => 'Activisme', 'Science & Technology' => 'Science & Technologie', 'Animals' => 'Animaux', 'Kids' => 'Enfants', 'Food' => 'Cuisine', ]; // Si une constante PRIORITY_CATEGORIES est définie, utiliser ces traductions if (defined('PRIORITY_CATEGORIES')) { $priorityCategories = PRIORITY_CATEGORIES; foreach ($priorityCategories as $id => $name) { // Trouver la clé anglaise correspondant à l'ID $englishName = array_search($id, array_keys($categories)); if ($englishName !== false) { $translations[$englishName] = $name; } } } $result = []; foreach ($categories as $key => $name) { // Utiliser la traduction si disponible, sinon garder le nom original $translatedName = isset($translations[$name]) ? $translations[$name] : $name; $result[$key] = $translatedName; } return $result; } /** * Fonction utilitaire pour appeler l'API PeerTube * * @param string $endpoint Point de terminaison de l'API * @param array $params Paramètres optionnels pour la requête * @return array Données retournées par l'API */ function callPeerTubeApi($endpoint, $params = []) { $url = PEERTUBE_URL . '/api/v1/' . $endpoint; // Ajouter les paramètres à l'URL if (!empty($params)) { $url .= '?' . http_build_query($params); } // Initialiser cURL $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Ajouter la clé API si définie if (defined('API_KEY') && !empty(API_KEY)) { curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: ApiKey ' . API_KEY ]); } // Exécuter la requête $response = curl_exec($ch); curl_close($ch); // Traiter la réponse if ($response === false) { // En cas d'erreur, retourner un tableau vide return []; } // Décoder la réponse JSON $data = json_decode($response, true); return $data ?: []; } /** * Récupère les catégories depuis l'API PeerTube * * @return array Liste des catégories */ function getCategories() { // Utiliser les catégories déjà récupérées $categories = PEERTUBE_CATEGORIES; $result = []; foreach ($categories as $key => $name) { $result[] = [ 'id' => $key, 'name' => $name ]; } return $result; } /** * Récupère les vidéos récentes depuis l'API PeerTube * * @param int $count Nombre de vidéos à récupérer * @return array Liste des vidéos récentes */ function getRecentVideos($count = RECENT_VIDEOS_COUNT) { // Récupérer les vidéos récentes $data = callPeerTubeApi('videos', [ 'sort' => '-publishedAt', 'count' => $count, 'isLocal' => true ]); return formatVideosData($data['data'] ?? []); } /** * Récupère les vidéos tendances depuis l'API PeerTube * * @param int $count Nombre de vidéos à récupérer * @return array Liste des vidéos tendances */ function getTrendingVideos($count = TRENDING_VIDEOS_COUNT) { // Récupérer les vidéos tendances $data = callPeerTubeApi('videos', [ 'sort' => '-trending', 'count' => $count, 'isLocal' => true ]); return formatVideosData($data['data'] ?? []); } /** * Récupère les vidéos avec un tag spécifique depuis l'API PeerTube * * @param string $tag Tag à filtrer * @param int $count Nombre de vidéos à récupérer * @return array Liste des vidéos */ function getVideosByTag($tag, $count) { // Récupérer les vidéos par tag $data = callPeerTubeApi('videos', [ 'tagsOneOf' => $tag, 'count' => $count, 'isLocal' => true ]); return formatVideosData($data['data'] ?? []); } /** * Récupère les shorts (vidéos courtes) depuis l'API PeerTube * Les shorts sont des vidéos locales de moins de 2 minutes * * @param int $count Nombre de shorts à récupérer * @return array Liste des shorts */ function getShorts($count = SHORTS_COUNT) { // Récupérer plus de vidéos que nécessaire pour pouvoir filtrer $data = callPeerTubeApi('videos', [ 'sort' => '-publishedAt', // Les plus récentes d'abord 'count' => SHORTS_COUNT_SEARCH, 'isLocal' => true ]); // Formater les données $allVideos = formatVideosData($data['data'] ?? []); // Filtrer pour ne garder que les vidéos de moins de 2 minutes (120 secondes) et en mode portrait $shortVideos = array_filter($allVideos, function($video) { // Vérifier la durée (moins de 2 minutes) $durationOk = $video['duration'] < SHORTS_MAX_DURATION; // Vérifier le ratio (mode portrait) $ratioOk = isset($video['aspectRatio']) && $video['aspectRatio'] <= 1; return $durationOk && $ratioOk; }); // Limiter au nombre demandé return array_slice($shortVideos, 0, $count); } /** * Récupère les vidéos sur l'indépendance depuis l'API PeerTube * * @param int $count Nombre de vidéos à récupérer * @return array Liste des vidéos sur l'indépendance */ function getIndependenceVideos($count = INDEPENDENCE_VIDEOS_COUNT) { // Récupérer les vidéos sur l'indépendance return getVideosByTag(TAG_INDEPENDENCE, $count); } /** * Vérifie s'il y a un direct en cours du compte LIVE_ACCOUNT_NAME sur l'instance PeerTube * * @return array|null Informations sur le direct en cours ou null si aucun direct */ function getLiveStream() { // Récupérer les lives du compte spécifié $accountName = LIVE_ACCOUNT_NAME; $data = callPeerTubeApi('accounts/' . $accountName . '/videos', [ 'count' => 1, 'isLocal' => true, 'isLive' => true, // Filtrer uniquement les lives 'sort' => '-publishedAt' // Les plus récents en premier ]); // Vérifier si on a des résultats if (empty($data['data']) || count($data['data']) === 0) { return null; } // Formater les données du live $liveData = formatVideosData($data['data']); // Filtrer pour ne garder que les lives en cours $activeLives = array_filter($liveData, function($video) { return isset($video['isLive']) && $video['isLive'] === true; }); // Retourner le premier live trouvé return !empty($activeLives) ? reset($activeLives) : null; } /** * Formate les données brutes des vidéos venant de l'API * * @param array $videosData Données brutes des vidéos * @return array Données formatées */ function formatVideosData($videosData) { $videos = []; foreach ($videosData as $video) { // Récupérer la vignette (thumbnail) $thumbnail = isset($video['previewPath']) ? PEERTUBE_URL . $video['previewPath'] : 'img/default-thumbnail.jpg'; // Récupérer l'avatar de la chaîne $channelAvatar = isset($video['channel']['avatars'][0]['path']) && isset($video['channel']['avatars'][0]['path']) ? PEERTUBE_URL . $video['channel']['avatars'][0]['path'] : 'img/default-avatar.png'; // Formater les données $videos[] = [ 'id' => $video['uuid'], 'title' => $video['name'], 'thumbnail' => $thumbnail, 'duration' => $video['duration'], 'channel' => $video['channel']['displayName'], 'channelAvatar' => $channelAvatar, 'views' => $video['views'], 'date' => $video['publishedAt'], 'aspectRatio' => $video['aspectRatio'], 'description' => $video['description'] ?? '', 'tags' => $video['tags'] ?? [], 'isLive' => isset($video['isLive']) ? $video['isLive'] : false ]; } return $videos; } // Fonctions utilitaires pour formater les données d'affichage function formatDuration($seconds) { $hours = floor($seconds / 3600); $minutes = floor(($seconds % 3600) / 60); $remainingSeconds = $seconds % 60; if ($hours > 0) { return sprintf('%d:%02d:%02d', $hours, $minutes, $remainingSeconds); } else { return sprintf('%d:%02d', $minutes, $remainingSeconds); } } function formatViewCount($views) { if ($views >= 1000000) { return round($views / 1000000, 1) . 'M'; } elseif ($views >= 1000) { return round($views / 1000, 1) . 'K'; } else { return $views; } } function formatDate($dateString) { $date = new DateTime($dateString); $now = new DateTime(); $interval = $now->diff($date); if ($interval->days == 0) { return 'Aujourd\'hui'; } elseif ($interval->days == 1) { return 'Hier'; } elseif ($interval->days < 7) { return 'Il y a ' . $interval->days . ' jours'; } elseif ($interval->days < 30) { $weeks = floor($interval->days / 7); return 'Il y a ' . $weeks . ' semaine' . ($weeks > 1 ? 's' : ''); } elseif ($interval->days < 365) { $months = floor($interval->days / 30); return 'Il y a ' . $months . ' mois'; } else { $years = floor($interval->days / 365); return 'Il y a ' . $years . ' an' . ($years > 1 ? 's' : ''); } } /** * Récupère les vidéos d'une catégorie spécifique depuis l'API PeerTube * * @param int $categoryId Identifiant de la catégorie * @param int $count Nombre de vidéos à récupérer * @return array Liste des vidéos de la catégorie */ function getVideosByCategory($categoryId, $count = CATEGORY_VIDEOS_COUNT) { // Récupérer les vidéos par catégorie $data = callPeerTubeApi('videos', [ 'categoryOneOf' => $categoryId, 'count' => $count, 'sort' => '-publishedAt', // Les plus récentes d'abord 'isLocal' => true ]); return formatVideosData($data['data'] ?? []); } /** * Récupère la liste des catégories à afficher (triées selon les priorités) * * @return array Liste des catégories avec id, name et videos */ function getDisplayCategories() { $categories = []; $priorityCategories = PRIORITY_CATEGORIES; $allCategories = PEERTUBE_CATEGORIES; // Ajouter uniquement les catégories prioritaires dans l'ordre défini foreach ($priorityCategories as $catId => $categoryName) { $videos = getVideosByCategory($catId); // N'ajouter que les catégories qui ont des vidéos if (!empty($videos)) { $categories[] = [ 'id' => $catId, 'name' => $categoryName, 'videos' => $videos ]; } } return $categories; } /** * Récupère les commentaires d'une vidéo * @param string $videoId ID de la vidéo * @return array Tableau des commentaires */ function getVideoComments($videoId) { $endpoint = "videos/{$videoId}/comment-threads"; $response = callPeerTubeApi($endpoint); if (!$response || !isset($response['data'])) { return []; } return $response['data']; } /** * Récupère les options de téléchargement pour une vidéo * @param string $videoId ID de la vidéo * @return array Options de téléchargement */ function getVideoDownloadOptions($videoId) { // Récupérer les informations complètes de la vidéo $videoData = callPeerTubeApi('videos/' . $videoId); $downloadOptions = []; // Ajouter les fichiers directs s'ils existent if (isset($videoData['files']) && !empty($videoData['files'])) { foreach ($videoData['files'] as $file) { if (isset($file['fileDownloadUrl']) && !empty($file['fileDownloadUrl'])) { $downloadOptions[] = [ 'type' => 'direct', 'url' => PEERTUBE_URL . $file['fileDownloadUrl'], 'resolution' => isset($file['resolution']['label']) ? $file['resolution']['label'] : 'Original', 'size' => isset($file['size']) ? formatFileSize($file['size']) : 'Inconnu' ]; } } } // Ajouter les playlists de streaming s'ils existent if (isset($videoData['streamingPlaylists']) && !empty($videoData['streamingPlaylists'])) { foreach ($videoData['streamingPlaylists'] as $playlist) { if (isset($playlist['files']) && !empty($playlist['files'])) { foreach ($playlist['files'] as $file) { if (isset($file['fileDownloadUrl']) && !empty($file['fileDownloadUrl'])) { $downloadOptions[] = [ 'type' => 'hls', 'url' => $file['fileDownloadUrl'], 'resolution' => isset($file['resolution']['label']) ? $file['resolution']['label'] : 'Original', 'size' => isset($file['size']) ? formatFileSize($file['size']) : 'Inconnu' ]; } } } } } return $downloadOptions; } /** * Formate la taille d'un fichier en format lisible * @param int $bytes Taille en octets * @return string Taille formatée */ function formatFileSize($bytes) { $units = ['B', 'KB', 'MB', 'GB', 'TB']; $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); $pow = min($pow, count($units) - 1); $bytes /= (1 << (10 * $pow)); return round($bytes, 2) . ' ' . $units[$pow]; } /** * Recherche des vidéos selon un critère * * @param string $query Terme de recherche * @param int $count Nombre de vidéos à récupérer * @param int $start Index de départ pour la pagination * @return array Liste des vidéos correspondant à la recherche */ function searchVideos($query, $count = COUNT_VIDEO_SEARCH, $start = 0) { if (empty($query)) { return []; } // Vérifier si la recherche concerne un tag (commence par #) $isTagSearch = false; if (substr($query, 0, 1) === '#') { $isTagSearch = true; $tag = substr($query, 1); // Enlever le # du début // Récupérer les vidéos avec ce tag via l'API $data = callPeerTubeApi('videos', [ 'tagsOneOf' => $tag, 'count' => $count, 'start' => $start, 'isLocal' => true, // Uniquement les vidéos locales 'sort' => '-publishedAt' // Les plus récentes d'abord ]); return formatVideosData($data['data'] ?? []); } // Recherche normale (pas un tag) $data = callPeerTubeApi('search/videos', [ 'search' => $query, 'count' => $count, 'start' => $start, 'isLocal' => true, // Uniquement les vidéos locales 'sort' => '-publishedAt' // Les plus récentes d'abord ]); return formatVideosData($data['data'] ?? []); } ?>