diff --git a/css/funkwhale-tracks.css b/css/funkwhale-tracks.css new file mode 100644 index 0000000..6b3e230 --- /dev/null +++ b/css/funkwhale-tracks.css @@ -0,0 +1,216 @@ +/* Styles pour la liste compacte de morceaux Funkwhale */ + +.funkwhale-container { + background: var(--card-bg); + border-radius: 12px; + border: 1px solid var(--border-color); + box-shadow: var(--card-shadow); + overflow: hidden; + height: 400px; + display: flex; + flex-direction: column; +} + +.funkwhale-section { + width: 100%; + min-width: 0; +} + +.funkwhale-header { + background: var(--primary-red); + color: white; + padding: 12px 16px; + font-size: 1rem; + font-weight: bold; + display: flex; + align-items: center; + gap: 8px; +} + +.funkwhale-header a { + color: white; + text-decoration: none; + transition: opacity 0.2s ease; +} + +.funkwhale-header a:hover { + opacity: 0.8; + text-decoration: underline; +} + +.funkwhale-tracks-list { + flex: 1; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: var(--primary-red) transparent; +} + +.funkwhale-tracks-list::-webkit-scrollbar { + width: 6px; +} + +.funkwhale-tracks-list::-webkit-scrollbar-track { + background: transparent; +} + +.funkwhale-tracks-list::-webkit-scrollbar-thumb { + background-color: var(--primary-red); + border-radius: 3px; +} + +.funkwhale-track-item { + display: flex; + padding: 8px 12px; + border-bottom: 1px solid var(--border-color); + text-decoration: none; + color: inherit; + transition: background-color 0.2s ease; + gap: 8px; +} + +.funkwhale-track-item:hover { + background: var(--hover-bg); + text-decoration: none; + color: inherit; +} + +.funkwhale-track-item:last-child { + border-bottom: none; +} + +.funkwhale-track-thumb { + width: 60px; + height: 60px; + border-radius: 8px; + overflow: hidden; + flex-shrink: 0; + background: var(--tag-bg); + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + position: relative; +} + +.funkwhale-track-thumb img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.funkwhale-play-icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 1.5rem; + color: rgba(255, 255, 255, 0.9); + text-shadow: 0 0 10px rgba(0, 0, 0, 0.8); + opacity: 0; + transition: opacity 0.2s ease; + pointer-events: none; +} + +.funkwhale-track-item:hover .funkwhale-play-icon { + opacity: 1; +} + +.funkwhale-track-info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + justify-content: center; +} + +.funkwhale-track-title { + font-size: 0.8rem; + font-weight: 600; + color: var(--text-color); + margin: 0 0 4px 0; + line-height: 1.2; + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.funkwhale-track-artist { + font-size: 0.7rem; + color: var(--primary-red); + font-weight: 600; + margin: 0 0 4px 0; + display: flex; + align-items: center; + gap: 4px; +} + +.funkwhale-track-artist i { + font-size: 0.65rem; +} + +.funkwhale-track-meta { + font-size: 0.65rem; + color: var(--text-secondary); + display: flex; + gap: 6px; + align-items: center; + flex-wrap: wrap; +} + +.funkwhale-track-meta i { + font-size: 0.6rem; +} + +.funkwhale-no-tracks { + text-align: center; + color: var(--text-secondary); + padding: 20px; + font-style: italic; + font-size: 0.9rem; +} + +/* Responsive Design */ +@media (max-width: 1024px) { + .funkwhale-section { + width: 100%; + } + + .funkwhale-container { + height: auto; + max-height: 400px; + } +} + +@media (max-width: 768px) { + .funkwhale-track-thumb { + width: 65px; + height: 65px; + } + + .funkwhale-track-item { + padding: 10px 12px; + gap: 10px; + } + + .funkwhale-track-title { + font-size: 0.85rem; + } + + .funkwhale-track-artist { + font-size: 0.7rem; + } + + .funkwhale-track-meta { + font-size: 0.65rem; + } + + .funkwhale-header { + padding: 10px 12px; + font-size: 0.9rem; + } + + .funkwhale-play-icon { + font-size: 1.3rem; + } +} diff --git a/css/grid-layout.css b/css/grid-layout.css new file mode 100644 index 0000000..732bcfc --- /dev/null +++ b/css/grid-layout.css @@ -0,0 +1,117 @@ +/* Layout grid pour Hero, Castopod, Funkwhale, Mastodon et WordPress */ +/* Ce fichier surcharge les anciens styles pour le nouveau layout responsive */ + +/* Force le wrapper parent à être un simple bloc, pas une grille */ +.hero-mastodon-wrapper { + display: block !important; + grid-template-columns: none !important; + width: 100%; + padding: 0 20px; + margin-bottom: 30px; +} + +/* Le vrai container grid qui contient tous les éléments */ +.timeline-wordpress-container { + display: grid !important; + gap: 20px; + margin: 0; + width: 100%; +} + +/* Supprime toutes les limitations de largeur sur les anciens containers */ +.timeline-container { + max-width: none !important; + width: 100% !important; + justify-self: auto !important; + grid-template-columns: none !important; + display: block !important; +} + +/* S'assurer que les sections prennent toute la largeur disponible */ +.castopod-section, +.funkwhale-section, +.mastodon-section, +.wordpress-section { + width: 100% !important; + max-width: none !important; + min-width: 0 !important; +} + +/* Desktop : 2 éléments par ligne */ +@media (min-width: 1200px) { + .timeline-wordpress-container { + grid-template-columns: repeat(2, 1fr) !important; + } + + /* Hero prend toute la largeur si présent */ + .hero-video-container, + .hero-live-container, + .hero-playlist-container { + grid-column: 1 / -1 !important; + } + + /* Centrer Mastodon quand il est seul (HERO_TYPE='none', pas de Castopod ni Funkwhale) */ + .timeline-wordpress-container:not(:has(.castopod-section)):not(:has(.funkwhale-section)) { + grid-template-columns: 1fr !important; + justify-items: center !important; + } + + .timeline-wordpress-container:not(:has(.castopod-section)):not(:has(.funkwhale-section)) .mastodon-section { + max-width: 800px !important; + width: 100% !important; + } + + /* Centrer le dernier élément quand il est seul sur sa ligne (nombre impair d'éléments) */ + /* Cas : Castopod + Funkwhale + Mastodon (3 éléments) */ + /* Mastodon sera le 3e élément (impair) et doit être centré */ + .timeline-wordpress-container:has(.castopod-section):has(.funkwhale-section) .mastodon-section:nth-child(3) { + grid-column: 1 / -1 !important; + max-width: 800px !important; + width: 800px !important; + margin: 0 auto !important; + justify-self: center !important; + } +} + +/* Tablette et Mobile : 1 élément par ligne */ +@media (max-width: 1199px) { + .timeline-wordpress-container { + grid-template-columns: 1fr !important; + } + + .hero-mastodon-wrapper { + padding: 0 15px; + } + + /* En mobile, tous les éléments prennent toute la largeur */ + .castopod-section, + .funkwhale-section, + .mastodon-section, + .wordpress-section { + width: 100% !important; + max-width: none !important; + } +} + +@media (max-width: 768px) { + .hero-mastodon-wrapper { + padding: 0 10px; + } +} + +/* Hauteurs fixes pour les containers scrollables */ +.castopod-container, +.funkwhale-container, +.mt-container, +.wordpress-container { + height: 400px; +} + +@media (max-width: 768px) { + .castopod-container, + .funkwhale-container, + .mt-container, + .wordpress-container { + height: 350px; + } +} diff --git a/includes/config.default.php b/includes/config.default.php index 07e9609..9636ad1 100644 --- a/includes/config.default.php +++ b/includes/config.default.php @@ -154,6 +154,19 @@ if (!defined('CASTOPOD_PODCAST_SLUGS')) { // Nombre d'épisodes à afficher (total, tous podcasts confondus) if (!defined('CASTOPOD_EPISODES_COUNT')) define('CASTOPOD_EPISODES_COUNT', 5); +// ========================================= +// Configuration Funkwhale +// ========================================= + +// Activer l'affichage des morceaux Funkwhale +if (!defined('FUNKWHALE_ENABLED')) define('FUNKWHALE_ENABLED', false); + +// URL de l'instance Funkwhale +if (!defined('FUNKWHALE_URL')) define('FUNKWHALE_URL', 'https://mizik.o-k-i.net'); + +// Nombre de morceaux à afficher +if (!defined('FUNKWHALE_TRACKS_COUNT')) define('FUNKWHALE_TRACKS_COUNT', 10); + // Tags pour filtrer les vidéos selon les catégories if (!defined('TAG_SHORT')) define('TAG_SHORT', 'short'); diff --git a/includes/config.local.php.sample b/includes/config.local.php.sample index 93016ca..94f1090 100644 --- a/includes/config.local.php.sample +++ b/includes/config.local.php.sample @@ -333,6 +333,19 @@ define('WORDPRESS_ENABLED', false); // define('CACHE_ENABLED', true); // define('CACHE_DURATION', 3600); // 1 heure recommandé +// ========================================= +// Intégration Funkwhale (Musique) +// ========================================= + +// Activer/désactiver l'affichage des morceaux Funkwhale +// define('FUNKWHALE_ENABLED', true); + +// URL de l'instance Funkwhale +// define('FUNKWHALE_URL', 'https://mizik.o-k-i.net'); + +// Nombre de morceaux aléatoires à afficher +// define('FUNKWHALE_TRACKS_COUNT', 10); + // ========================================= // Système de dons // ========================================= diff --git a/includes/config.php b/includes/config.php index ce2894b..f04249d 100644 --- a/includes/config.php +++ b/includes/config.php @@ -873,4 +873,131 @@ function getCastopodEpisodes($castopodUrl = null, $podcastSlugs = null, $count = return $allEpisodes; } + +/** + * Récupère des morceaux aléatoires depuis Funkwhale via l'API + * + * @param string $funkwhaleUrl URL de l'instance Funkwhale + * @param int $count Nombre de morceaux à récupérer + * @return array Liste des morceaux formatés + */ +function getFunkwhaleTracks($funkwhaleUrl = null, $count = null) { + if (!defined('FUNKWHALE_ENABLED') || !FUNKWHALE_ENABLED) { + return []; + } + + $funkwhaleUrl = $funkwhaleUrl ?? FUNKWHALE_URL; + $count = $count ?? FUNKWHALE_TRACKS_COUNT; + + // Clé de cache - on récupère un grand nombre de morceaux pour le cache + $cacheKey = 'funkwhale_' . md5($funkwhaleUrl); + $cacheFetchSize = 50; // Nombre de morceaux à mettre en cache + + // Vérifier le cache + $allTracks = null; + if (defined('CACHE_ENABLED') && CACHE_ENABLED) { + $allTracks = getFromCache($cacheKey); + } + + // Si pas de cache, récupérer depuis l'API + if ($allTracks === null) { + try { + // Construire l'URL de l'API avec filtre pour morceaux locaux uniquement + // Le paramètre scope=me récupère uniquement les morceaux de l'instance locale + $apiUrl = rtrim($funkwhaleUrl, '/') . '/api/v1/tracks?ordering=random&page_size=' . $cacheFetchSize . '&scope=all'; + + // Récupérer les morceaux via cURL + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $apiUrl); + 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); + $jsonContent = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpCode !== 200 || !$jsonContent) { + return []; + } + + // Parser le JSON + $data = json_decode($jsonContent, true); + if (!$data || !isset($data['results'])) { + return []; + } + + $allTracks = []; + + // Extraire le domaine de l'instance locale pour filtrer + $localDomain = parse_url($funkwhaleUrl, PHP_URL_HOST); + + // Parcourir les morceaux et filtrer pour garder uniquement les morceaux locaux + foreach ($data['results'] as $item) { + // Vérifier si le morceau est local en comparant le domaine + // Les morceaux fédérés ont généralement un domaine différent dans leur URL + $isLocal = true; + + // Vérifier via l'URL de l'artiste ou de l'album + if (isset($item['artist']['channel'])) { + $artistDomain = parse_url($item['artist']['channel'], PHP_URL_HOST); + if ($artistDomain && $artistDomain !== $localDomain) { + $isLocal = false; + } + } + + // Ou vérifier via le champ is_local si disponible + if (isset($item['is_local']) && !$item['is_local']) { + $isLocal = false; + } + + // Ne garder que les morceaux locaux + if ($isLocal) { + $track = [ + 'title' => $item['title'] ?? '', + 'artist' => $item['artist']['name'] ?? 'Artiste inconnu', + 'album' => $item['album']['title'] ?? '', + 'cover' => $item['album']['cover']['urls']['medium_square_crop'] ?? '', + 'duration' => $item['uploads'][0]['duration'] ?? 0, + 'link' => rtrim($funkwhaleUrl, '/') . '/library/tracks/' . ($item['id'] ?? ''), + 'trackId' => $item['id'] ?? 0, + ]; + + // Formater la durée + if ($track['duration']) { + $track['formattedDuration'] = formatDuration($track['duration']); + } else { + $track['formattedDuration'] = ''; + } + + $allTracks[] = $track; + } + } + + // Mettre en cache tous les morceaux locaux + if (defined('CACHE_ENABLED') && CACHE_ENABLED) { + saveToCache($cacheKey, $allTracks); + } + + } catch (Exception $e) { + error_log('Erreur lors de la récupération des morceaux Funkwhale: ' . $e->getMessage()); + return []; + } + } + + // Si pas assez de morceaux, retourner ce qu'on a + if (empty($allTracks)) { + return []; + } + + // Sélectionner aléatoirement $count morceaux parmi tous les morceaux en cache + $trackCount = count($allTracks); + if ($trackCount <= $count) { + return $allTracks; + } + + // Mélanger et prendre les N premiers + shuffle($allTracks); + return array_slice($allTracks, 0, $count); +} ?> diff --git a/index.php b/index.php index 24b715a..c0d8565 100644 --- a/index.php +++ b/index.php @@ -30,6 +30,10 @@ setSecurityHeaders(); + + + + @@ -104,10 +108,11 @@ setSecurityHeaders();