Files

1797 lines
64 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
= 🎙️ ANNU KUTE CED — Hub multimédia du podcast
:toc: left
:toc-title: Sommaire / Contents
:toclevels: 3
[[top]]
🌍 *Hub multimédia du podcast ANNU KUTE CED* +
🌍 *Multimedia hub for the ANNU KUTE CED podcast*
[.text-center]
*Choisissez votre langue / Choose your language :* +
<<fr,🇫🇷 Version française>> · <<en,🇬🇧 English version>>
'''
[[fr]]
== 🇫🇷 Version française
=== 📖 Description
*ANNU KUTE CED* est une interface web responsive qui fait office de *hub multimédia* pour le podcast du même nom. Elle regroupe en un seul lieu :
- 🎥 la chaîne *PeerTube* du podcast (vidéos, shorts, directs) ;
- 🎙️ son compte *Castopod* (derniers épisodes, lecture audio intégrée) ;
- 📡 sa timeline *Mastodon* (actualités et annonces).
La plateforme est légère, déployable sur un serveur mutualisé (PHP + serveur web, sans base de données) et optimisée pour mobile et desktop. C'est aussi une *Progressive Web App* installable, avec mode hors ligne.
🎯 *Mission* : offrir un point d'entrée unique, libre et décentralisé, pour découvrir, écouter et suivre le podcast ANNU KUTE CED sans dépendre des grandes plateformes propriétaires.
=== 🌳 Origine du projet
Cette application est un *fork* de https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI[*FEDIVERSE OKI*^], développée par l'https://o-k-i.net[ORGANISATION KA INTERNATIONALE (OKI)^], elle-même issue du projet https://codeberg.org/Ka-Ubuntu/kaubuntu.re[kaubuntu.re^] du mouvement https://kaubuntu.com[Ka-Ubuntu^]. La licence d'origine (GNU AGPL v3) est conservée et respectée.
[cols="1,3",options="header"]
|===
| Élément | Détail
| Dépôt upstream
| https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI
| Dépôt de ce fork
| `git@labola.o-k-i.net:cedric/annu-kute-ced.git`
| Domaine de production
| https://annukuteced.buzz
| Gouvernance
| Application maintenue par OKI ; fork porté par le propriétaire du dépôt *cedric* (Cédric Famibelle-Pronzola)
| Licence
| https://www.gnu.org/licenses/agpl-3.0.html[GNU Affero General Public License v3^] (AGPL-V3) ou ultérieure
|===
=== 🔗 Sources et instances utilisées
Le hub est configuré par défaut pour agréger les sources du podcast ANNU KUTE CED :
[cols="1,2,3",options="header"]
|===
| Service | Instance | Compte / chaîne
| 🎥 PeerTube (vidéos, lives)
| GADE — `https://gade.o-k-i.net`
| https://gade.o-k-i.net/c/annu_kute_ced/videos[`annu_kute_ced`^]
| 🎙️ Castopod (épisodes audio)
| KUTE — `https://kute.o-k-i.net`
| https://kute.o-k-i.net/@annu_kute_cedric[`@annu_kute_cedric`^]
| 📡 Mastodon (timeline)
| BOKANTE — `https://bokante.o-k-i.net`
| https://bokante.o-k-i.net/@cedric[`@cedric`^]
| 🎵 Funkwhale (musique, optionnel, désactivé par défaut)
| MIZIK — `https://mizik.o-k-i.net`
| morceaux aléatoires de l'instance
|===
Toutes ces sources sont modifiables dans `includes/config.local.php` (voir <<fr-configuration,Configuration>>).
=== ✨ Fonctionnalités
==== 🎥 Vidéos PeerTube
- Vidéos récentes, tendances et par catégories (configurables)
- *Shorts* : carrousel dédié aux vidéos portrait de moins de 3 minutes (défilement tactile)
- Page vidéo complète : lecteur PeerTube embarqué, description Markdown, badge de licence Creative Commons, commentaires (lecture seule), vidéos suggérées
- *Téléchargement* : fichiers directs et playlists HLS, avec résolution et taille
- *Partage* : copie de lien, code d'intégration, e-mail, Facebook, X, WhatsApp, LinkedIn, Telegram
- Recherche plein texte et recherche par hashtag (préfixe `#`)
- Pagination AJAX « Voir plus » (protégée par jeton CSRF et vérification d'origine)
==== 📺 Directs et annonces
- Page *Direct* (`/direct`) : embed du live PeerTube s'il est en cours, avec autoplay
- *Annonce du prochain live* : date et heure converties automatiquement pour 5 territoires (Ma'ohi Nui, Martinique/Guadeloupe, Guyane, France, Kanaky), image personnalisable (4:5, 1:1, 16:9)
- Section *hero* d'accueil configurable : live, vidéo unique, playlist (PeerTube / Funkwhale / Castopod) ou masquée
==== 🎙️ Podcast et audio
- *Castopod* : derniers épisodes via flux RSS, avec *lecteur audio intégré* (lecture/pause, avance automatique à l'épisode suivant, un seul flux à la fois)
- *Funkwhale* (optionnel) : sélection aléatoire de morceaux de l'instance, avec le même lecteur intégré
==== 📡 Réseaux sociaux et contenus externes
- *Timeline Mastodon* intégrée (bibliothèque vendored https://gitlab.com/idotj/mastodon-embed-timeline[mastodon-embed-timeline^] v4.7.0, AGPLv3)
- *Compatibilité Pleroma* : adaptateur qui convertit les réponses de l'API Pleroma au format Mastodon attendu par la timeline
- *WordPress* (optionnel) : derniers articles via l'API REST, avec image à la une et auteur
==== 📱 PWA et confort d'utilisation
- *Progressive Web App* installable (bouton d'installation automatique)
- Mode hors ligne : pages et ressources statiques en cache via Service Worker
- Indicateur visuel de perte de connexion
- *Mode sombre* (basé sur `prefers-color-scheme`, mémorisé en `localStorage`)
- Interface entièrement responsive (mobile, tablette, desktop)
==== 💝 Dons, compte à rebours et divers
- *Page de dons* : LiberaPay, Ko-fi et Stripe (dons ponctuels et mensuels, montants suggérés)
- *Compte à rebours* : page de lancement/maintenance multi-fuseaux qui verrouille le site jusqu'à la date cible
- Bloc « À propos » configurable (titre, deux paragraphes, image légendée)
==== 🔍 SEO et données structurées
- *JSON-LD* généré automatiquement : `WebSite` (avec `SearchAction`), `VideoObject`, `CollectionPage`, `BreadcrumbList`
- Balises *Open Graph* et *Twitter Cards*
- `sitemap.xml` et `robots.txt` fournis en exemples
- Prêt pour *Plausible Analytics* (voir <<fr-analytics,Analytics>>) : désactivé par défaut, sans cookies et conforme RGPD
==== 🛡️ Sécurité
- En-têtes de sécurité complets : CSP dynamique avec *nonce* par requête, HSTS, `X-Frame-Options`, `nosniff`, `Referrer-Policy`, `Permissions-Policy`
- Protection *CSRF* sur les requêtes AJAX, vérification de l'en-tête `Origin`
- Validation anti-*SSRF* des URLs d'instances et liste blanche des endpoints de l'API PeerTube
- Échappement systématique des sorties (anti-XSS)
- Exemples de configuration sécurisée pour Apache et Nginx dans `conf/`
=== 🛠️ Technologies utilisées
- 📄 HTML5, 🎨 CSS3 (Media Queries), ⚡ JavaScript vanilla (sans framework, sans jQuery)
- 🐘 PHP 7.4+ (aucune base de données requise)
- 🔧 Service Worker (cache offline) et 📋 Web App Manifest (installation)
- 📦 Bibliothèques :
* 🎯 Font Awesome 7 (icônes, via CDN cdnjs)
* 📡 mastodon-embed-timeline v4.7.0 (vendored dans `js/`, AGPLv3)
* 📊 Plausible Analytics (pré-configuré, à activer manuellement)
=== 📁 Structure du projet
[source]
----
├── ajax/
│ └── load-more-videos.php # Endpoint AJAX « Voir plus » (CSRF + Origin)
├── conf/
│ ├── .htaccess.sample # Configuration Apache sécurisée
│ └── nginx.conf.sample # Configuration Nginx sécurisée
├── css/ # Feuilles de style (styles, vidéo, catégories,
│ │ # recherche, dons, countdown, mastodon,
│ ├── castopod-podcasts.css # castopod, funkwhale, wordpress, grille…)
│ └── …
├── docs/
│ ├── wireframes/ # Maquettes d'origine (PNG desktop + mobile)
│ ├── docinfo.html # Styles d'impression (fallback emoji) pour l'export
│ └── generate-readme-pdf.sh # Export de la doc : README.html / README.pdf
├── img/ # Logo, favicons, icône de lecture
├── includes/
│ ├── config.php # Bootstrap + fonctions API (PeerTube, Castopod,
│ │ # Funkwhale) et formatage
│ ├── config.default.php # Valeurs par défaut de toutes les constantes
│ ├── config.local.php.sample # Modèle de configuration locale (à copier)
│ ├── security.php # CSRF, CSP + nonce, en-têtes, validateurs
│ ├── simple-cache.php # Cache fichier de l'API (cache/api/)
│ ├── wordpress.php # Intégration API REST WordPress
│ ├── structured-data.php # Générateurs JSON-LD
│ ├── header.php # Barre supérieure (recherche, réseaux, thème…)
│ ├── footer.php # Pied de page
│ ├── sidebar.php # Navigation latérale
│ ├── mobile-menu.php # Menu mobile coulissant
│ ├── hero-section.php # Bannière d'accueil (live/vidéo/playlist)
│ ├── featured-videos.php # ⚠️ legacy, non utilisé par les pages actuelles
│ ├── recent-videos.php # ⚠️ legacy, non utilisé par les pages actuelles
│ ├── pwa-init.php # ⚠️ legacy, PWA désormais inline dans index.php
│ └── lib/
│ └── markdown.php # Rendu Markdown (sous-ensemble PeerTube)
├── js/
│ ├── main.js # UI globale (thème, carrousel, « Voir plus »…)
│ ├── audio-player.js # Lecteur audio Castopod / Funkwhale
│ ├── categories.js # « Voir plus » de la page catégorie
│ ├── countdown.js # Compte à rebours
│ ├── search.js # Cartes cliquables de la recherche
│ ├── mastodon-config.php # Configuration JS de la timeline (servie en JS)
│ ├── mastodon-timeline.umd.js # mastodon-embed-timeline v4.7.0 (vendored)
│ └── pleroma-adapter.js # Adaptateur Pleroma → API Mastodon
├── uploads/ # Images d'annonces (non versionné, hors .gitkeep)
├── index.php # Page d'accueil (agrégateur)
├── video.php # Page de lecture d'une vidéo
├── categories.php # Page d'une catégorie
├── recherche.php # Page de recherche
├── direct.php # Page du direct / annonce du prochain live
├── countdown.php # Page de compte à rebours (maintenance)
├── sw.js # Service Worker (PWA)
├── site.webmanifest.sample # Exemple de manifest PWA
├── browserconfig.xml # Tuiles Windows
├── sitemap.xml.sample # Exemple de sitemap
├── robots.txt.sample # Exemple de robots.txt
├── mentions-legales.php.sample # Exemple de mentions légales
├── dons.php.sample # Exemple de page de dons
├── LICENSE # GNU AGPL v3
└── README.adoc
----
NOTE: Les fichiers marqués « legacy » sont conservés pour référence mais ne sont inclus par aucune page ; la page d'accueil rend ces sections en interne.
=== 📋 Prérequis
- 🐘 *PHP 7.4+* (recommandé : PHP 8.0+)
- 📦 *Extensions PHP requises :*
* `curl` — appels API (PeerTube, Castopod, Funkwhale, WordPress)
* `json` — traitement des réponses
* `intl` — dates internationales et fuseaux horaires
* `mbstring` — chaînes multi-octets
* `xml` (SimpleXML) — recommandée pour Castopod (un fallback regex existe)
- 🌐 *Serveur web* : Apache ou Nginx
- 🔒 *HTTPS* : requis pour les fonctionnalités PWA
.Installation de l'extension `intl`
[source,bash]
----
# Ubuntu / Debian
sudo apt-get install php-intl
# Fedora / RHEL / CentOS
sudo dnf install php-intl
# Redémarrer le serveur web
sudo systemctl restart apache2 # Apache
sudo systemctl restart php8.3-fpm # Nginx + php-fpm
# Vérifier
php -m | grep intl
----
=== 🚀 Installation
. 📥 Clonez le dépôt :
+
[source,bash]
----
git clone git@labola.o-k-i.net:cedric/annu-kute-ced.git
cd annu-kute-ced
----
. 📦 Vérifiez les prérequis PHP (voir ci-dessus).
. 🔧 Créez votre configuration locale et les fichiers d'instance :
+
[source,bash]
----
cp includes/config.local.php.sample includes/config.local.php
cp site.webmanifest.sample site.webmanifest
cp robots.txt.sample robots.txt
cp sitemap.xml.sample sitemap.xml
cp mentions-legales.php.sample mentions-legales.php
cp dons.php.sample dons.php
----
. 🛡️ Installez la configuration serveur sécurisée depuis `conf/` (voir <<fr-securite,Sécurité>>).
. ✍️ Rendez le dossier `cache/` accessible en écriture par le serveur web (créé automatiquement au premier appel API).
. 🔒 Assurez-vous que HTTPS est actif (obligatoire pour la PWA).
. 🌐 Faites pointer votre domaine (`annukuteced.buzz`) vers la racine du projet.
[[fr-configuration]]
=== ⚙️ Configuration
==== Le système en trois fichiers
[cols="1,2,2",options="header"]
|===
| Fichier | Rôle | Versionné ?
| `includes/config.php`
| Bootstrap : charge les autres fichiers et contient toutes les fonctions métier
| ✅ Oui
| `includes/config.default.php`
| Valeurs par défaut de chaque constante (définies seulement si absentes)
| ✅ Oui
| `includes/config.local.php`
| Vos surcharges locales — *prioritaires* car chargées en premier
| ❌ Non (`.gitignore`)
|===
Pour personnaliser l'instance, ne modifiez jamais `config.default.php` : définissez la constante dans `config.local.php`. Comme ce dernier est chargé avant, vos valeurs ont toujours la priorité.
[source,bash]
----
cp includes/config.local.php.sample includes/config.local.php
----
==== Référence des constantes
Valeurs par défaut issues de `includes/config.default.php`.
.Application et organisation
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `APP_HOST_NAME`
| `'annukuteced.buzz'`
| Nom de domaine affiché (mentions légales…)
| `ORGANIZATION_SHORT_NAME`
| `'ANNU KUTE CED'`
| Nom court de l'organisation
| `ORGANIZATION_NAME`
| `'ANNU KUTE CED'`
| Nom complet de l'organisation
|===
.PeerTube
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `PEERTUBE_URL`
| `'https://gade.o-k-i.net'`
| URL de l'instance PeerTube (obligatoire)
| `PEERTUBE_DISPLAY_NAME`
| `'gade.o-k-i.net'`
| Nom affiché de l'instance
| `API_KEY`
| `''`
| Clé API optionnelle (`Authorization: ApiKey …`)
| `SHOW_VIDEO_VIEWS`
| `false`
| Afficher le nombre de vues sur les cartes vidéo
| `PRIORITY_CATEGORIES`
| `[11, 15, 4, 9, 10]`
| Catégories mises en avant : `[ID => 'Nom']`, ordre = ordre d'affichage (accueil, sidebar, footer, menu mobile)
| `DEFAULT_TIMEZONE`
| `'America/Guadeloupe'`
| Fuseau horaire d'affichage (https://www.php.net/manual/fr/timezones.php[liste PHP^])
|===
.Section hero (bannière d'accueil)
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `HERO_TYPE`
| `'live'`
| `'live'` · `'video'` · `'playlist'` · `'none'`
| `LIVE_ACCOUNT_NAME`
| `'annu_kute_ced'`
| Compte PeerTube surveillé pour les directs (hero + page `/direct`)
| `HERO_VIDEO_ID`
| `'fDE6tvQMXRuBpE4eDDS7kj'`
| UUID de la vidéo (mode `video`), extrait de l'URL `/w/<uuid>`
| `HERO_VIDEO_TITLE`
| `'Vidéo de présentation'`
| Titre (accessibilité)
| `PLAYLIST_PLATFORM`
| `'peertube'`
| `'peertube'` · `'funkwhale'` · `'castopod'`
| `PLAYLIST_INSTANCE_URL`
| `''`
| URL de base de la plateforme de playlist
| `PLAYLIST_ID`
| `''`
| ID de la playlist ; pour Castopod : `'@slug'`
| `PLAYLIST_TITLE` / `PLAYLIST_DESCRIPTION`
| `''` / `''`
| Textes optionnels (sinon récupérés via API)
|===
.Castopod (podcast)
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `CASTOPOD_ENABLED`
| `true`
| Activer la section podcast de l'accueil
| `CASTOPOD_URL`
| `'https://kute.o-k-i.net'`
| URL de l'instance Castopod
| `CASTOPOD_PODCAST_SLUGS`
| `['annu_kute_cedric']`
| Slug(s) de podcast ; flux RSS = `{URL}/@{slug}/feed`. Plusieurs podcasts : épisodes fusionnés et triés par date
| `CASTOPOD_EPISODES_COUNT`
| `5`
| Nombre total d'épisodes affichés
|===
WARNING: Castopod applique un *rate limit* strict (HTTP 429) sur les flux RSS. Le code patiente 5 s entre chaque podcast. *Activez le cache* (`CACHE_ENABLED`) pour éviter ces délais à chaque page.
.Funkwhale (musique, optionnel)
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `FUNKWHALE_ENABLED`
| `false`
| Activer la section musique
| `FUNKWHALE_URL`
| `'https://mizik.o-k-i.net'`
| URL de l'instance Funkwhale
| `FUNKWHALE_TRACKS_COUNT`
| `10`
| Morceaux aléatoires locaux affichés (50 mis en cache)
|===
.Mastodon (timeline)
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `MASTODON_INSTANCE_URL`
| `'https://bokante.o-k-i.net'`
| Instance Mastodon (ou Pleroma, via l'adaptateur)
| `MASTODON_URL`
| `'https://bokante.o-k-i.net/@cedric'`
| URL publique du compte (liens « Voir plus », footer)
| `MASTODON_DATE_FORMAT`
| `'fr-FR'`
| Locale de date des posts
| `MASTODON_BTN_SEE_MORE` / `MASTODON_BTN_RELOAD`
| `'Voir plus de post'` / `'Rafraichir'`
| Libellés des boutons
| `MASTODON_MAX_POST_FETCH` / `MASTODON_MAX_POST_SHOW`
| `'10'` / `'10'`
| Posts récupérés / affichés
| `MASTODON_S3_MEDIA_URL`
| _(non défini)_
| Origine S3 des médias (ajoutée à la CSP `media-src`)
|===
NOTE: La timeline n'a pas de flag d'activation : elle est toujours affichée sur l'accueil.
.WordPress (optionnel)
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `WORDPRESS_ENABLED`
| `false`
| Activer la section articles
| `WORDPRESS_URL`
| `''`
| URL du site WordPress (sans `/` final)
| `WORDPRESS_POSTS_COUNT`
| `6`
| Nombre d'articles (API REST `/wp-json/wp/v2/`)
|===
.Dons
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `DONATIONS_ENABLED`
| `false`
| Activer le système (icône cœur, lien « Soutenir », page `/dons.php`)
| `LIBERAPAY_URL` / `KOFI_URL`
| `''` / `''`
| URLs des plateformes externes
| `STRIPE_ENABLED`
| `false`
| Activer l'interface Stripe (onglets ponctuel / mensuel)
| `STRIPE_ONE_TIME_LINKS`
| `[1, 5, 10, 20, 50, 'custom'] => ''`
| Liens de paiement Stripe par montant
| `STRIPE_MONTHLY_LINKS`
| `[1, 5, 10, 20, 50] => ''`
| Liens d'abonnement mensuel Stripe
| `DONATION_AMOUNTS`
| `[1, 5, 10, 20, 50]`
| Montants suggérés
| `DONATION_CURRENCY`
| `'EUR'`
| Devise
|===
NOTE: Au moins une plateforme (LiberaPay, Ko-fi ou Stripe) doit être configurée pour que `dons.php` s'affiche, sinon la page renvoie une erreur 500.
.Compte à rebours (maintenance / lancement)
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `COUNTDOWN_ENABLED`
| `false`
| ⚠️ Si `true`, *toutes* les pages redirigent vers `countdown.php`
| `COUNTDOWN_TARGET_DATE`
| `'2025-10-11 00:00:00'`
| Date cible (`Y-m-d H:i:s`) ; à la fin, redirection vers `/`
| `COUNTDOWN_TIMEZONES`
| 5 territoires
| `[Libellé => fuseau PHP]` affichés sur la page
|===
.Annonce du prochain live
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `NEXT_LIVE_ENABLED`
| `false`
| Afficher l'annonce quand aucun direct n'est en cours
| `NEXT_LIVE_TITLE` / `NEXT_LIVE_DESCRIPTION`
| `'Prochain live'` / `'Rejoignez-nous…'`
| Textes (date et heure ajoutées automatiquement)
| `NEXT_LIVE_DATE`
| `''`
| Date du live (`Y-m-d H:i:s`)
| `NEXT_LIVE_IMAGE`
| `'uploads/next-live.jpg'`
| Image d'annonce (utilisée seulement si le fichier existe)
|===
.Site, réseaux sociaux et contact
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `SITE_NAME`
| `'ANNU KUTE CED'`
| Nom du site (titres, meta)
| `SITE_DESCRIPTION`
| `'Hub multimédia du podcast ANNU KUTE CED'`
| Description SEO
| `SITE_LOGO` / `SITE_FAVICON`
| `'img/logo.png'` / `'img/favicon.png'`
| Chemins des images
| `FACEBOOK_URL`, `X_URL`, `INSTAGRAM_URL`, `YOUTUBE_URL`, `TIKTOK_URL`
| `'#'`
| `'#'` ou vide = icône masquée
| `CONTACT_EMAIL`
| `'kontak@o-k-i.net'`
| E-mail de contact (footer)
|===
.Mentions légales
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `LEGAL_COPYRIGHT`
| `'ANNU KUTE CED'`
| Titulaire du copyright
| `LEGAL_WEBMASTER_NAME` / `LEGAL_WEBMASTER_EMAIL`
| `'Cédric Famibelle-Pronzola'` / `'contact@cedric-pronzola.dev'`
| Webmaster
| `LEGAL_HOST_NAME`, `LEGAL_HOST_COMPANY`, `LEGAL_HOST_RCS`, `LEGAL_HOST_ADDRESS`
| valeurs o2Switch
| Coordonnées de l'hébergeur
| `LEGAL_CONTACT_EMAIL`
| `'kontak@o-k-i.net'`
| Contact juridique
| `LEGAL_LICENSE` / `LEGAL_LICENSE_URL`
| AGPL-V3 + URL GNU
| Licence du code
| `LEGAL_SOURCE_CODE_URL`
| `'https://labola.o-k-i.net/cedric/annu-kute-ced'`
| Dépôt du code source (exigé par l'AGPL pour un service réseau)
| `LEGAL_SERVICE_DESCRIPTION`
| `'est le hub multimédia du podcast ANNU KUTE CED.'`
| Phrase de description du service
|===
.Tags et filtres
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `TAG_INDEPENDENCE`
| `'indépendance'`
| Tag de la fonction `getIndependenceVideos()` (pas de section d'accueil associée actuellement)
| `SHORTS_MAX_DURATION`
| `180`
| Durée max d'un short (s) ; ratio portrait (`aspectRatio ≤ 1`) aussi requis
| `IMPORTANT_TAGS`
| `['ANNUKUTECED', 'podcast', …]`
| Hashtags de la sidebar, du footer et du menu mobile
| `POPULAR_TAGS`
| `['podcast', 'annukuteced', …]`
| Hashtags affichés sur l'accueil
|===
.Pagination et volumes d'affichage
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `VIDEOS_PER_PAGE`
| `12`
| Vidéos par page (recherche)
| `COUNT_VIDEO_SEARCH`
| `20`
| Résultats récupérés par recherche
| `RECENT_VIDEOS_COUNT` / `TRENDING_VIDEOS_COUNT` / `CATEGORY_VIDEOS_COUNT`
| `6`
| Taille des sections d'accueil
| `SHORTS_COUNT` / `SHORTS_COUNT_SEARCH`
| `6` / `100`
| Shorts affichés / pool de recherche
| `LOAD_MORE_COUNT`
| `6`
| Vidéos chargées par clic sur « Voir plus »
| `FEATURED_VIDEOS_COUNT` / `INDEPENDENCE_VIDEOS_COUNT`
| `6`
| Réservés (sections non affichées actuellement)
|===
.Cache
[cols="2,2,3",options="header"]
|===
| Constante | Défaut | Description
| `CACHE_ENABLED`
| `false`
| Active le cache pour *Castopod et Funkwhale* (fortement recommandé)
| `CACHE_DURATION`
| `3600`
| Durée de vie du cache (s)
|===
NOTE: Le cache de l'API PeerTube et de WordPress (`includes/simple-cache.php`) est *toujours actif*, indépendamment de `CACHE_ENABLED`, avec des TTL par endpoint (catégories 1 h, vidéos/recherche 10 min, articles WP 15 min, comptes 5 min). Les fichiers sont stockés dans `cache/api/`.
.Bloc « À propos » (pas de valeur par défaut — bloc masqué tant que non défini)
[cols="2,3",options="header"]
|===
| Constante | Description
| `MOVEMENT_TITLE`
| Titre du bloc (défaut template : « À propos »)
| `MOVEMENT_DESCRIPTION`
| Premier paragraphe ; *commenter cette ligne masque tout le bloc*
| `MOVEMENT_DESCRIPTION_2`
| Second paragraphe (optionnel)
| `MOVEMENT_IMAGE` / `MOVEMENT_IMAGE_ALT` / `MOVEMENT_CAPTION`
| Image (`img/movement_presentation.png`, non versionnée), texte alternatif et légende (HTML simple accepté)
|===
==== Exemple complet de `config.local.php`
[source,php]
----
<?php
define('APP_HOST_NAME', 'annukuteced.buzz');
// PeerTube (chaîne GADE du podcast)
define('PEERTUBE_URL', 'https://gade.o-k-i.net');
define('PEERTUBE_DISPLAY_NAME', 'gade.o-k-i.net');
// Hero : direct de la chaîne, sinon annonce du prochain live
define('HERO_TYPE', 'live');
define('LIVE_ACCOUNT_NAME', 'annu_kute_ced');
define('NEXT_LIVE_ENABLED', true);
define('NEXT_LIVE_TITLE', 'Prochain live');
define('NEXT_LIVE_DESCRIPTION', 'Enregistrement du prochain épisode !');
define('NEXT_LIVE_DATE', '2025-10-11 10:00:00');
// Castopod (compte KUTE du podcast)
define('CASTOPOD_ENABLED', true);
define('CASTOPOD_URL', 'https://kute.o-k-i.net');
define('CASTOPOD_PODCAST_SLUGS', ['annu_kute_cedric']);
define('CASTOPOD_EPISODES_COUNT', 5);
// Mastodon (compte BOKANTE)
define('MASTODON_INSTANCE_URL', 'https://bokante.o-k-i.net');
define('MASTODON_URL', 'https://bokante.o-k-i.net/@cedric');
// Cache : indispensable pour Castopod (rate limit RSS)
define('CACHE_ENABLED', true);
define('CACHE_DURATION', 3600);
----
=== 🎨 Personnalisation de l'apparence
. 🖼️ Remplacez `img/logo.png` par votre logo (et les favicons du dossier `img/`)
. 🎨 Modifiez les couleurs dans `css/styles.css`
. 📝 Textes : la plupart passent par les constantes de `config.local.php` (bloc « À propos », libellés Mastodon, etc.)
==== Fichiers d'instance à personnaliser
Les fichiers `sitemap.xml`, `robots.txt`, `site.webmanifest`, `mentions-legales.php` et `dons.php` sont ignorés par Git : copiez les `.sample` puis adaptez-les. Les exemples fournis sont *pré-remplis pour `annukuteced.buzz`* ; pour un autre domaine, remplacez simplement les occurrences du domaine. Dans `mentions-legales.php`, remplacez aussi le placeholder `VOTRE-DATE-MAJ`.
=== 🧭 Ordre des sections sur la page d'accueil
La page d'accueil assemble les sections dans cet ordre, chacune conditionnée par sa configuration :
. *Hero* — si `HERO_TYPE` ≠ `'none'` (live, vidéo ou playlist)
. *Podcasts Castopod* — si `CASTOPOD_ENABLED` et `CASTOPOD_URL` non vide
. *Morceaux Funkwhale* — si `FUNKWHALE_ENABLED` et `FUNKWHALE_URL` non vide
. *Timeline Mastodon* — toujours affichée
. *Articles WordPress* — si `WORDPRESS_ENABLED` et `WORDPRESS_URL` non vide
. *Hashtags populaires* (`POPULAR_TAGS`)
. *Shorts* — carrousel
. *Dernières vidéos* + bouton « Voir plus »
. *Tendances* + « Voir plus »
. *Une section par catégorie prioritaire* (`PRIORITY_CATEGORIES`, seulement si elle contient des vidéos) + « Voir plus »
. *Bloc « À propos »* (si `MOVEMENT_DESCRIPTION` défini) + aside hashtags
Mise en page desktop : hero/Castopod/Funkwhale + Mastodon + WordPress s'organisent en colonnes (1fr 2fr, ou 1fr 2fr 1fr avec WordPress) ; empilés verticalement sur mobile.
[[fr-securite]]
=== 🛡️ Sécurité
==== Ce que l'application applique déjà
- *CSP dynamique* : `default-src 'self'` avec *nonce* unique par requête pour les scripts/styles inline ; origines PeerTube/Mastodon/CDN ajoutées automatiquement ; assouplissements `localhost` pour le développement
- *En-têtes* : `X-Frame-Options: SAMEORIGIN`, `X-Content-Type-Options: nosniff`, `X-XSS-Protection`, `Referrer-Policy: strict-origin-when-cross-origin`, `Permissions-Policy` restrictive, HSTS (HTTPS uniquement), CORP/COOP `same-origin`
+
NOTE: L'en-tête COEP a été *volontairement retiré* (commit `62d4d99`) : `require-corp` bloquait les embeds cross-origin de PeerTube, Mastodon, Castopod et Funkwhale.
- *CSRF* : jeton de session (`random_bytes(32)`) injecté en `<meta>`, vérifié par `hash_equals` sur l'endpoint AJAX ; vérification de l'en-tête `Origin`
- *Anti-SSRF* : validation des URLs d'instances (schéma http/https, blocage IP privées et `localhost`), liste blanche des endpoints de l'API PeerTube, cURL sans redirections
- *Anti-XSS* : échappement systématique des sorties ; validation des entrées (UUID vidéo, requête de recherche ≤ 200 car., numéros de page, ID de catégorie 120)
==== Configuration serveur (Apache)
[source,bash]
----
cp conf/.htaccess.sample .htaccess
----
Protections incluses : blocage des fichiers de configuration et `.sample`, des dossiers `/includes/`, `/cache/`, `/docs/`, `/conf/` ; pas de listing de répertoires ; fichiers cachés bloqués ; HTTPS forcé ; masquage de l'extension `.php` (URLs propres `/video` au lieu de `/video.php`).
WARNING: Cette configuration est *essentielle* : sans elle, vos fichiers de configuration sont exposés aux visiteurs.
==== Configuration serveur (Nginx)
[source,bash]
----
# Adaptez les chemins dans conf/nginx.conf.sample puis :
sudo cp conf/nginx.conf.sample /etc/nginx/sites-available/annukuteced.buzz
sudo ln -s /etc/nginx/sites-available/annukuteced.buzz /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
----
WARNING: Le bloc `location ~* \.(php|inc|conf|config|local)$ { deny all; }` de l'exemple correspond tel quel à *tous* les fichiers `.php`. Ajustez-le pour ne cibler que les chemins sensibles (par exemple `^/includes/`), sous peine de rendre l'application inaccessible.
=== 📱 Progressive Web App (PWA)
==== Fonctionnalités
- 📲 *Installation native* : bouton « Installer » dans le header à la première visite
- 🌐 *Mode hors ligne* : cache des pages visitées et des ressources statiques
- 📡 *Détection de connexion* : indicateur visuel en cas de perte réseau
- 🔄 *Mise à jour* : proposition de rechargement quand une nouvelle version est détectée
==== Stratégies de cache du Service Worker (`sw.js`)
[cols="1,2,3",options="header"]
|===
| Type de requête | Stratégie | Détail
| Ressources statiques (`/css/`, `/js/`, `/img/`, CDN cdnjs)
| *Cache First*
| Mise en cache à l'installation et au premier accès ; repli sur `img/logo.png` si une image échoue
| Pages (`/`, `*.php`…)
| *Network First*
| Réseau d'abord, cache en secours hors ligne
| API et AJAX (`/ajax/`, `mastodon-config.php`…)
| *Network Only*
| Jamais de cache ; réponse JSON 503 synthétique hors ligne
|===
TIP: Pour déployer une nouvelle version des assets, changez le suffixe de version des noms de cache dans `sw.js` (format date, ex. `08072026-0720`) : les anciens caches sont supprimés à l'activation.
==== Installation par les visiteurs
- 🌐 *Chrome/Edge* : menu → « Installer annukuteced.buzz »
- 🍎 *Safari iOS* : Partager → « Ajouter à l'écran d'accueil »
- 🦊 *Firefox Android* : menu → « Installer »
[[fr-analytics]]
=== 📊 Analytics (Plausible)
Le site est *pré-configuré* pour https://plausible.io[Plausible Analytics^] (sans cookies, conforme RGPD, données anonymes), mais le script est *désactivé par défaut*.
Pour l'activer :
. Ouvrez `index.php` et *décommentez* le snippet Plausible (balise `<script defer data-domain=… src="https://plausible.io/js/script…js">` et son shim `window.plausible`).
. C'est tout : la CSP autorise déjà `https://plausible.io` en `script-src` et `connect-src`, et le `data-domain` utilise automatiquement le domaine courant.
Données alors collectées (anonymes) : pages visitées, pays d'origine (IP non stockée), type d'appareil, navigateur, temps passé.
=== 🚀 Déploiement sur serveur mutualisé
. 🐘 Vérifiez que l'hébergeur fournit PHP 7.4+ et les extensions requises
. 🔒 Activez HTTPS (obligatoire pour la PWA)
. 📤 Transférez les fichiers via FTP à la racine du site
. 🔧 Permissions : `644` pour les fichiers, `755` pour les dossiers ; `cache/` accessible en écriture
. 📄 Créez les fichiers d'instance depuis les `.sample` (config, manifest, robots, sitemap, mentions légales, dons)
. 🛡️ Copiez `conf/.htaccess.sample` en `.htaccess` (Apache)
. 🌐 Faites pointer le domaine `annukuteced.buzz` vers le dossier d'installation
. 🧪 Testez la PWA via les outils de développement du navigateur (onglet Application)
=== 👨‍💻 Développement et contribution
. 🌿 Créez une branche : `git checkout -b ma-fonctionnalite`
. 💾 Committez avec des *messages atomiques en anglais*, au format conventionnel (< 70 caractères) :
* `feat: add …`, `fix: prevent …`, `docs: update …`, `refactor: …`, `typo: …`
. 📤 Poussez : `git push origin ma-fonctionnalite`
. 🔀 Ouvrez une pull request
Bonnes pratiques du dépôt :
- Les remontées de bugs et propositions sont les bienvenues sur le dépôt du fork ; les améliorations génériques peuvent être proposées en upstream (FEDIVERSE OKI)
- Ne versionnez jamais `config.local.php` ni les fichiers dérivés des `.sample`
- Quelques constantes sont définies mais non utilisées par le code actuel (`ENABLE_SEARCH`, `ENABLE_COMMENTS`, `ENABLE_USER_ACCOUNTS`, `TAG_SHORT`) : ne vous en servez pas comme points d'extension
==== 📄 Exporter la documentation (HTML / PDF)
Ce README peut être exporté en HTML et en PDF en une seule commande :
[source,bash]
----
docs/generate-readme-pdf.sh # README.html + README.pdf
docs/generate-readme-pdf.sh --html # HTML seulement
docs/generate-readme-pdf.sh --pdf # PDF seulement
----
Les fichiers sont produits à la racine du projet et ignorés par Git (`.gitignore`).
Prérequis : `asciidoctor`, `chromium` (rendu headless) et le paquet `fonts-noto-color-emoji` (émojis couleur dans le PDF). La feuille de style `docs/docinfo.html` (fallback emoji + règles d'impression) est injectée automatiquement via le mécanisme `docinfo` d'Asciidoctor.
NOTE: `asciidoctor-pdf` (utilisé par l'extension AsciiDoc de VSCodium) n'est pas utilisable ici : son moteur Prawn ne sait pas embarquer les polices emoji couleur (CBDT/COLR), les émojis disparaissent du PDF. Chromium les gère nativement.
=== 📜 Licence
Copyright (C) 2025 Cédric Famibelle-Pronzola & *ORGANISATION KA INTERNATIONALE*
Ce programme est un logiciel libre : vous pouvez le redistribuer et/ou le modifier selon les termes de la licence publique générale GNU Affero publiée par la Free Software Foundation, soit la version 3 de la licence, soit (à votre choix) toute version ultérieure.
Ce programme est distribué dans l'espoir qu'il sera utile, mais SANS AUCUNE GARANTIE ; sans même la garantie implicite de COMMERCIALISATION ou d'ADAPTATION À UN USAGE PARTICULIER. Voir la https://www.gnu.org/licenses/agpl-3.0.html[licence publique générale GNU Affero^] pour plus de détails (texte intégral dans `LICENSE`).
=== 📞 Contact
Pour toute question ou suggestion concernant cette plateforme : mailto:kontak@o-k-i.net[kontak@o-k-i.net]
<<top,⬆ Retour au sommaire>>
'''
[[en]]
== 🇬🇧 English version
=== 📖 Description
*ANNU KUTE CED* is a responsive web interface acting as a *multimedia hub* for the podcast of the same name. It brings together in a single place:
- 🎥 the podcast's *PeerTube* channel (videos, shorts, live streams);
- 🎙️ its *Castopod* account (latest episodes, built-in audio playback);
- 📡 its *Mastodon* timeline (news and announcements).
The platform is lightweight, deployable on shared hosting (PHP + web server, no database), and optimized for both mobile and desktop. It is also an installable *Progressive Web App* with offline support.
🎯 *Mission*: provide a single, free and decentralized entry point to discover, listen to and follow the ANNU KUTE CED podcast without relying on Big Tech platforms.
=== 🌳 Project origin
This application is a *fork* of https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI[*FEDIVERSE OKI*^], developed by the https://o-k-i.net[ORGANISATION KA INTERNATIONALE (OKI)^], itself derived from the https://codeberg.org/Ka-Ubuntu/kaubuntu.re[kaubuntu.re^] project by the https://kaubuntu.com[Ka-Ubuntu^] movement. The original license (GNU AGPL v3) is preserved and respected.
[cols="1,3",options="header"]
|===
| Item | Detail
| Upstream repository
| https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI
| This fork's repository
| `git@labola.o-k-i.net:cedric/annu-kute-ced.git`
| Production domain
| https://annukuteced.buzz
| Governance
| Application maintained by OKI; fork carried by the repository owner *cedric* (Cédric Famibelle-Pronzola)
| License
| https://www.gnu.org/licenses/agpl-3.0.html[GNU Affero General Public License v3^] (AGPL-V3) or later
|===
=== 🔗 Aggregated sources and instances
The hub is configured by default to aggregate the ANNU KUTE CED podcast's sources:
[cols="1,2,3",options="header"]
|===
| Service | Instance | Account / channel
| 🎥 PeerTube (videos, lives)
| GADE — `https://gade.o-k-i.net`
| https://gade.o-k-i.net/c/annu_kute_ced/videos[`annu_kute_ced`^]
| 🎙️ Castopod (audio episodes)
| KUTE — `https://kute.o-k-i.net`
| https://kute.o-k-i.net/@annu_kute_cedric[`@annu_kute_cedric`^]
| 📡 Mastodon (timeline)
| BOKANTE — `https://bokante.o-k-i.net`
| https://bokante.o-k-i.net/@cedric[`@cedric`^]
| 🎵 Funkwhale (music, optional, disabled by default)
| MIZIK — `https://mizik.o-k-i.net`
| random tracks from the instance
|===
All of these sources can be changed in `includes/config.local.php` (see <<en-configuration,Configuration>>).
=== ✨ Features
==== 🎥 PeerTube videos
- Recent, trending and per-category videos (configurable)
- *Shorts*: dedicated carousel for portrait videos under 3 minutes (touch scrolling)
- Full video page: embedded PeerTube player, Markdown description, Creative Commons licence badge, comments (read-only), suggested videos
- *Downloads*: direct files and HLS playlists, with resolution and size
- *Sharing*: copy link, embed code, e-mail, Facebook, X, WhatsApp, LinkedIn, Telegram
- Full-text search and hashtag search (`#` prefix)
- AJAX "Load more" pagination (protected by CSRF token and Origin check)
==== 📺 Live streams and announcements
- *Live page* (`/direct`): embeds the PeerTube live stream when one is running, with autoplay
- *Next live announcement*: date and time automatically converted for 5 territories (Ma'ohi Nui, Martinique/Guadeloupe, French Guiana, France, Kanaky), custom image (4:5, 1:1, 16:9)
- Configurable homepage *hero section*: live, single video, playlist (PeerTube / Funkwhale / Castopod) or hidden
==== 🎙️ Podcast and audio
- *Castopod*: latest episodes via RSS feed, with a *built-in audio player* (play/pause, auto-advance to the next episode, one stream at a time)
- *Funkwhale* (optional): random track selection from the instance, using the same built-in player
==== 📡 Social networks and external content
- Embedded *Mastodon timeline* (vendored https://gitlab.com/idotj/mastodon-embed-timeline[mastodon-embed-timeline^] v4.7.0 library, AGPLv3)
- *Pleroma compatibility*: adapter converting Pleroma API responses to the Mastodon format expected by the timeline
- *WordPress* (optional): latest posts via the REST API, with featured image and author
==== 📱 PWA and user experience
- Installable *Progressive Web App* (automatic install button)
- Offline mode: visited pages and static assets cached via Service Worker
- Visual indicator when the connection is lost
- *Dark mode* (based on `prefers-color-scheme`, persisted in `localStorage`)
- Fully responsive interface (mobile, tablet, desktop)
==== 💝 Donations, countdown and more
- *Donation page*: LiberaPay, Ko-fi and Stripe (one-time and monthly donations, suggested amounts)
- *Countdown*: multi-timezone launch/maintenance page that locks the whole site until the target date
- Configurable "About" block (title, two paragraphs, captioned image)
==== 🔍 SEO and structured data
- Automatically generated *JSON-LD*: `WebSite` (with `SearchAction`), `VideoObject`, `CollectionPage`, `BreadcrumbList`
- *Open Graph* and *Twitter Cards* meta tags
- `sitemap.xml` and `robots.txt` provided as samples
- Ready for *Plausible Analytics* (see <<en-analytics,Analytics>>): disabled by default, cookie-free and GDPR-compliant
==== 🛡️ Security
- Full security headers: dynamic CSP with a per-request *nonce*, HSTS, `X-Frame-Options`, `nosniff`, `Referrer-Policy`, `Permissions-Policy`
- *CSRF* protection on AJAX requests, `Origin` header verification
- Anti-*SSRF* validation of instance URLs and an allowlist of PeerTube API endpoints
- Systematic output escaping (anti-XSS)
- Hardened Apache and Nginx configuration samples in `conf/`
=== 🛠️ Tech stack
- 📄 HTML5, 🎨 CSS3 (Media Queries), ⚡ vanilla JavaScript (no framework, no jQuery)
- 🐘 PHP 7.4+ (no database required)
- 🔧 Service Worker (offline cache) and 📋 Web App Manifest (installation)
- 📦 Libraries:
* 🎯 Font Awesome 7 (icons, via cdnjs CDN)
* 📡 mastodon-embed-timeline v4.7.0 (vendored in `js/`, AGPLv3)
* 📊 Plausible Analytics (pre-configured, to be enabled manually)
=== 📁 Project structure
[source]
----
├── ajax/
│ └── load-more-videos.php # "Load more" AJAX endpoint (CSRF + Origin)
├── conf/
│ ├── .htaccess.sample # Hardened Apache configuration
│ └── nginx.conf.sample # Hardened Nginx configuration
├── css/ # Stylesheets (styles, video, categories,
│ │ # search, donations, countdown, mastodon,
│ ├── castopod-podcasts.css # castopod, funkwhale, wordpress, grid…)
│ └── …
├── docs/
│ ├── wireframes/ # Original mockups (desktop + mobile PNGs)
│ ├── docinfo.html # Print styles (emoji fallback) for the export
│ └── generate-readme-pdf.sh # Docs export: README.html / README.pdf
├── img/ # Logo, favicons, play icon
├── includes/
│ ├── config.php # Bootstrap + API functions (PeerTube, Castopod,
│ │ # Funkwhale) and formatting helpers
│ ├── config.default.php # Default values for every constant
│ ├── config.local.php.sample # Local configuration template (to copy)
│ ├── security.php # CSRF, CSP + nonce, headers, validators
│ ├── simple-cache.php # File-based API cache (cache/api/)
│ ├── wordpress.php # WordPress REST API integration
│ ├── structured-data.php # JSON-LD generators
│ ├── header.php # Top bar (search, socials, theme…)
│ ├── footer.php # Footer
│ ├── sidebar.php # Side navigation
│ ├── mobile-menu.php # Slide-in mobile menu
│ ├── hero-section.php # Homepage banner (live/video/playlist)
│ ├── featured-videos.php # ⚠️ legacy, unused by current pages
│ ├── recent-videos.php # ⚠️ legacy, unused by current pages
│ ├── pwa-init.php # ⚠️ legacy, PWA now inlined in index.php
│ └── lib/
│ └── markdown.php # Markdown renderer (PeerTube-flavored subset)
├── js/
│ ├── main.js # Global UI (theme, carousel, "Load more"…)
│ ├── audio-player.js # Castopod / Funkwhale audio player
│ ├── categories.js # Category page "Load more"
│ ├── countdown.js # Countdown timer
│ ├── search.js # Clickable search result cards
│ ├── mastodon-config.php # Timeline JS config (served as JavaScript)
│ ├── mastodon-timeline.umd.js # mastodon-embed-timeline v4.7.0 (vendored)
│ └── pleroma-adapter.js # Pleroma → Mastodon API adapter
├── uploads/ # Announcement images (not versioned, except .gitkeep)
├── index.php # Homepage (aggregator)
├── video.php # Video playback page
├── categories.php # Category page
├── recherche.php # Search page
├── direct.php # Live stream / next-live announcement page
├── countdown.php # Countdown page (maintenance)
├── sw.js # Service Worker (PWA)
├── site.webmanifest.sample # PWA manifest sample
├── browserconfig.xml # Windows tiles
├── sitemap.xml.sample # Sitemap sample
├── robots.txt.sample # robots.txt sample
├── mentions-legales.php.sample # Legal notice sample
├── dons.php.sample # Donation page sample
├── LICENSE # GNU AGPL v3
└── README.adoc
----
NOTE: Files marked "legacy" are kept for reference but are not included by any page; the homepage renders those sections internally.
=== 📋 Requirements
- 🐘 *PHP 7.4+* (recommended: PHP 8.0+)
- 📦 *Required PHP extensions:*
* `curl` — API calls (PeerTube, Castopod, Funkwhale, WordPress)
* `json` — response handling
* `intl` — international dates and timezones
* `mbstring` — multi-byte strings
* `xml` (SimpleXML) — recommended for Castopod (a regex fallback exists)
- 🌐 *Web server*: Apache or Nginx
- 🔒 *HTTPS*: required for PWA features
.Installing the `intl` extension
[source,bash]
----
# Ubuntu / Debian
sudo apt-get install php-intl
# Fedora / RHEL / CentOS
sudo dnf install php-intl
# Restart the web server
sudo systemctl restart apache2 # Apache
sudo systemctl restart php8.3-fpm # Nginx + php-fpm
# Check
php -m | grep intl
----
=== 🚀 Installation
. 📥 Clone the repository:
+
[source,bash]
----
git clone git@labola.o-k-i.net:cedric/annu-kute-ced.git
cd annu-kute-ced
----
. 📦 Check the PHP requirements (see above).
. 🔧 Create your local configuration and instance files:
+
[source,bash]
----
cp includes/config.local.php.sample includes/config.local.php
cp site.webmanifest.sample site.webmanifest
cp robots.txt.sample robots.txt
cp sitemap.xml.sample sitemap.xml
cp mentions-legales.php.sample mentions-legales.php
cp dons.php.sample dons.php
----
. 🛡️ Install the hardened server configuration from `conf/` (see <<en-securite,Security>>).
. ✍️ Make the `cache/` directory writable by the web server (created automatically on the first API call).
. 🔒 Make sure HTTPS is enabled (mandatory for the PWA).
. 🌐 Point your domain (`annukuteced.buzz`) to the project root.
[[en-configuration]]
=== ⚙️ Configuration
==== The three-file system
[cols="1,2,2",options="header"]
|===
| File | Role | Versioned?
| `includes/config.php`
| Bootstrap: loads the other files and holds all business functions
| ✅ Yes
| `includes/config.default.php`
| Default values for every constant (defined only if missing)
| ✅ Yes
| `includes/config.local.php`
| Your local overrides — *take precedence* as they load first
| ❌ No (`.gitignore`)
|===
To customize the instance, never edit `config.default.php`: define the constant in `config.local.php`. Since it loads first, your values always win.
[source,bash]
----
cp includes/config.local.php.sample includes/config.local.php
----
==== Constants reference
Default values from `includes/config.default.php`.
.Application and organization
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `APP_HOST_NAME`
| `'annukuteced.buzz'`
| Displayed domain name (legal notice…)
| `ORGANIZATION_SHORT_NAME`
| `'ANNU KUTE CED'`
| Short organization name
| `ORGANIZATION_NAME`
| `'ANNU KUTE CED'`
| Full organization name
|===
.PeerTube
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `PEERTUBE_URL`
| `'https://gade.o-k-i.net'`
| PeerTube instance URL (required)
| `PEERTUBE_DISPLAY_NAME`
| `'gade.o-k-i.net'`
| Displayed instance name
| `API_KEY`
| `''`
| Optional API key (`Authorization: ApiKey …`)
| `SHOW_VIDEO_VIEWS`
| `false`
| Show view counts on video cards
| `PRIORITY_CATEGORIES`
| `[11, 15, 4, 9, 10]`
| Featured categories: `[ID => 'Name']`, order = display order (homepage, sidebar, footer, mobile menu)
| `DEFAULT_TIMEZONE`
| `'America/Guadeloupe'`
| Display timezone (https://www.php.net/manual/en/timezones.php[PHP list^])
|===
.Hero section (homepage banner)
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `HERO_TYPE`
| `'live'`
| `'live'` · `'video'` · `'playlist'` · `'none'`
| `LIVE_ACCOUNT_NAME`
| `'annu_kute_ced'`
| PeerTube account watched for live streams (hero + `/direct` page)
| `HERO_VIDEO_ID`
| `'fDE6tvQMXRuBpE4eDDS7kj'`
| Video UUID (`video` mode), extracted from the `/w/<uuid>` URL
| `HERO_VIDEO_TITLE`
| `'Vidéo de présentation'`
| Title (accessibility)
| `PLAYLIST_PLATFORM`
| `'peertube'`
| `'peertube'` · `'funkwhale'` · `'castopod'`
| `PLAYLIST_INSTANCE_URL`
| `''`
| Base URL of the playlist platform
| `PLAYLIST_ID`
| `''`
| Playlist ID; for Castopod: `'@slug'`
| `PLAYLIST_TITLE` / `PLAYLIST_DESCRIPTION`
| `''` / `''`
| Optional texts (otherwise fetched via API)
|===
.Castopod (podcast)
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `CASTOPOD_ENABLED`
| `true`
| Enable the homepage podcast section
| `CASTOPOD_URL`
| `'https://kute.o-k-i.net'`
| Castopod instance URL
| `CASTOPOD_PODCAST_SLUGS`
| `['annu_kute_cedric']`
| Podcast slug(s); RSS feed = `{URL}/@{slug}/feed`. Multiple podcasts: episodes merged and date-sorted
| `CASTOPOD_EPISODES_COUNT`
| `5`
| Total number of episodes displayed
|===
WARNING: Castopod enforces a strict *rate limit* (HTTP 429) on RSS feeds. The code waits 5 s between podcasts. *Enable the cache* (`CACHE_ENABLED`) to avoid these delays on every page.
.Funkwhale (music, optional)
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `FUNKWHALE_ENABLED`
| `false`
| Enable the music section
| `FUNKWHALE_URL`
| `'https://mizik.o-k-i.net'`
| Funkwhale instance URL
| `FUNKWHALE_TRACKS_COUNT`
| `10`
| Random local tracks displayed (50 cached)
|===
.Mastodon (timeline)
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `MASTODON_INSTANCE_URL`
| `'https://bokante.o-k-i.net'`
| Mastodon instance (or Pleroma, via the adapter)
| `MASTODON_URL`
| `'https://bokante.o-k-i.net/@cedric'`
| Public account URL ("See more" links, footer)
| `MASTODON_DATE_FORMAT`
| `'fr-FR'`
| Post date locale
| `MASTODON_BTN_SEE_MORE` / `MASTODON_BTN_RELOAD`
| `'Voir plus de post'` / `'Rafraichir'`
| Button labels
| `MASTODON_MAX_POST_FETCH` / `MASTODON_MAX_POST_SHOW`
| `'10'` / `'10'`
| Posts fetched / displayed
| `MASTODON_S3_MEDIA_URL`
| _(undefined)_
| S3 media origin (added to the CSP `media-src`)
|===
NOTE: The timeline has no enable flag: it is always displayed on the homepage.
.WordPress (optional)
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `WORDPRESS_ENABLED`
| `false`
| Enable the posts section
| `WORDPRESS_URL`
| `''`
| WordPress site URL (no trailing `/`)
| `WORDPRESS_POSTS_COUNT`
| `6`
| Number of posts (REST API `/wp-json/wp/v2/`)
|===
.Donations
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `DONATIONS_ENABLED`
| `false`
| Enable the system (heart icon, "Support" link, `/dons.php` page)
| `LIBERAPAY_URL` / `KOFI_URL`
| `''` / `''`
| External platform URLs
| `STRIPE_ENABLED`
| `false`
| Enable the Stripe interface (one-time / monthly tabs)
| `STRIPE_ONE_TIME_LINKS`
| `[1, 5, 10, 20, 50, 'custom'] => ''`
| Stripe payment links per amount
| `STRIPE_MONTHLY_LINKS`
| `[1, 5, 10, 20, 50] => ''`
| Stripe monthly subscription links
| `DONATION_AMOUNTS`
| `[1, 5, 10, 20, 50]`
| Suggested amounts
| `DONATION_CURRENCY`
| `'EUR'`
| Currency
|===
NOTE: At least one platform (LiberaPay, Ko-fi or Stripe) must be configured for `dons.php` to render, otherwise the page returns a 500 error.
.Countdown (maintenance / launch)
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `COUNTDOWN_ENABLED`
| `false`
| ⚠️ When `true`, *every* page redirects to `countdown.php`
| `COUNTDOWN_TARGET_DATE`
| `'2025-10-11 00:00:00'`
| Target date (`Y-m-d H:i:s`); on completion, redirects to `/`
| `COUNTDOWN_TIMEZONES`
| 5 territories
| `[Label => PHP timezone]` shown on the page
|===
.Next-live announcement
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `NEXT_LIVE_ENABLED`
| `false`
| Show the announcement when no live stream is running
| `NEXT_LIVE_TITLE` / `NEXT_LIVE_DESCRIPTION`
| `'Prochain live'` / `'Rejoignez-nous…'`
| Texts (date and time appended automatically)
| `NEXT_LIVE_DATE`
| `''`
| Live date (`Y-m-d H:i:s`)
| `NEXT_LIVE_IMAGE`
| `'uploads/next-live.jpg'`
| Announcement image (used only if the file exists)
|===
.Site, social networks and contact
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `SITE_NAME`
| `'ANNU KUTE CED'`
| Site name (titles, meta)
| `SITE_DESCRIPTION`
| `'Hub multimédia du podcast ANNU KUTE CED'`
| SEO description
| `SITE_LOGO` / `SITE_FAVICON`
| `'img/logo.png'` / `'img/favicon.png'`
| Image paths
| `FACEBOOK_URL`, `X_URL`, `INSTAGRAM_URL`, `YOUTUBE_URL`, `TIKTOK_URL`
| `'#'`
| `'#'` or empty = icon hidden
| `CONTACT_EMAIL`
| `'kontak@o-k-i.net'`
| Contact e-mail (footer)
|===
.Legal notice
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `LEGAL_COPYRIGHT`
| `'ANNU KUTE CED'`
| Copyright holder
| `LEGAL_WEBMASTER_NAME` / `LEGAL_WEBMASTER_EMAIL`
| `'Cédric Famibelle-Pronzola'` / `'contact@cedric-pronzola.dev'`
| Webmaster
| `LEGAL_HOST_NAME`, `LEGAL_HOST_COMPANY`, `LEGAL_HOST_RCS`, `LEGAL_HOST_ADDRESS`
| o2Switch values
| Hosting provider details
| `LEGAL_CONTACT_EMAIL`
| `'kontak@o-k-i.net'`
| Legal contact
| `LEGAL_LICENSE` / `LEGAL_LICENSE_URL`
| AGPL-V3 + GNU URL
| Code license
| `LEGAL_SOURCE_CODE_URL`
| `'https://labola.o-k-i.net/cedric/annu-kute-ced'`
| Source code repository (required by the AGPL for a network service)
| `LEGAL_SERVICE_DESCRIPTION`
| `'est le hub multimédia du podcast ANNU KUTE CED.'`
| Service description sentence
|===
.Tags and filters
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `TAG_INDEPENDENCE`
| `'indépendance'`
| Tag used by `getIndependenceVideos()` (no associated homepage section currently)
| `SHORTS_MAX_DURATION`
| `180`
| Max short duration (s); portrait ratio (`aspectRatio ≤ 1`) also required
| `IMPORTANT_TAGS`
| `['ANNUKUTECED', 'podcast', …]`
| Hashtags in the sidebar, footer and mobile menu
| `POPULAR_TAGS`
| `['podcast', 'annukuteced', …]`
| Hashtags displayed on the homepage
|===
.Pagination and display volumes
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `VIDEOS_PER_PAGE`
| `12`
| Videos per page (search)
| `COUNT_VIDEO_SEARCH`
| `20`
| Results fetched per search
| `RECENT_VIDEOS_COUNT` / `TRENDING_VIDEOS_COUNT` / `CATEGORY_VIDEOS_COUNT`
| `6`
| Homepage section sizes
| `SHORTS_COUNT` / `SHORTS_COUNT_SEARCH`
| `6` / `100`
| Shorts displayed / search pool
| `LOAD_MORE_COUNT`
| `6`
| Videos loaded per "Load more" click
| `FEATURED_VIDEOS_COUNT` / `INDEPENDENCE_VIDEOS_COUNT`
| `6`
| Reserved (sections not currently displayed)
|===
.Cache
[cols="2,2,3",options="header"]
|===
| Constant | Default | Description
| `CACHE_ENABLED`
| `false`
| Enable caching for *Castopod and Funkwhale* (strongly recommended)
| `CACHE_DURATION`
| `3600`
| Cache lifetime (s)
|===
NOTE: The PeerTube API and WordPress cache (`includes/simple-cache.php`) is *always on*, regardless of `CACHE_ENABLED`, with per-endpoint TTLs (categories 1 h, videos/search 10 min, WP posts 15 min, accounts 5 min). Files are stored in `cache/api/`.
."About" block (no default values — block hidden until defined)
[cols="2,3",options="header"]
|===
| Constant | Description
| `MOVEMENT_TITLE`
| Block title (template fallback: "À propos")
| `MOVEMENT_DESCRIPTION`
| First paragraph; *commenting this line hides the whole block*
| `MOVEMENT_DESCRIPTION_2`
| Second paragraph (optional)
| `MOVEMENT_IMAGE` / `MOVEMENT_IMAGE_ALT` / `MOVEMENT_CAPTION`
| Image (`img/movement_presentation.png`, not versioned), alt text and caption (simple HTML allowed)
|===
==== Complete `config.local.php` example
[source,php]
----
<?php
define('APP_HOST_NAME', 'annukuteced.buzz');
// PeerTube (the podcast's GADE channel)
define('PEERTUBE_URL', 'https://gade.o-k-i.net');
define('PEERTUBE_DISPLAY_NAME', 'gade.o-k-i.net');
// Hero: channel live stream, otherwise next-live announcement
define('HERO_TYPE', 'live');
define('LIVE_ACCOUNT_NAME', 'annu_kute_ced');
define('NEXT_LIVE_ENABLED', true);
define('NEXT_LIVE_TITLE', 'Prochain live');
define('NEXT_LIVE_DESCRIPTION', 'Recording the next episode!');
define('NEXT_LIVE_DATE', '2025-10-11 10:00:00');
// Castopod (the podcast's KUTE account)
define('CASTOPOD_ENABLED', true);
define('CASTOPOD_URL', 'https://kute.o-k-i.net');
define('CASTOPOD_PODCAST_SLUGS', ['annu_kute_cedric']);
define('CASTOPOD_EPISODES_COUNT', 5);
// Mastodon (BOKANTE account)
define('MASTODON_INSTANCE_URL', 'https://bokante.o-k-i.net');
define('MASTODON_URL', 'https://bokante.o-k-i.net/@cedric');
// Cache: essential for Castopod (RSS rate limit)
define('CACHE_ENABLED', true);
define('CACHE_DURATION', 3600);
----
=== 🎨 Appearance customization
. 🖼️ Replace `img/logo.png` with your logo (and the favicons in `img/`)
. 🎨 Edit the colors in `css/styles.css`
. 📝 Most texts are set through `config.local.php` constants ("About" block, Mastodon labels, etc.)
==== Instance files to customize
The `sitemap.xml`, `robots.txt`, `site.webmanifest`, `mentions-legales.php` and `dons.php` files are Git-ignored: copy the `.sample` files then adapt them. The provided samples are *pre-filled for `annukuteced.buzz`*; for another domain, simply replace the domain occurrences. In `mentions-legales.php`, also replace the `VOTRE-DATE-MAJ` placeholder.
=== 🧭 Homepage section order
The homepage assembles sections in this order, each gated by its configuration:
. *Hero* — if `HERO_TYPE` ≠ `'none'` (live, video or playlist)
. *Castopod podcasts* — if `CASTOPOD_ENABLED` and `CASTOPOD_URL` is not empty
. *Funkwhale tracks* — if `FUNKWHALE_ENABLED` and `FUNKWHALE_URL` is not empty
. *Mastodon timeline* — always displayed
. *WordPress posts* — if `WORDPRESS_ENABLED` and `WORDPRESS_URL` is not empty
. *Popular hashtags* (`POPULAR_TAGS`)
. *Shorts* — carousel
. *Latest videos* + "Load more" button
. *Trending* + "Load more"
. *One section per priority category* (`PRIORITY_CATEGORIES`, only when it has videos) + "Load more"
. *"About" block* (if `MOVEMENT_DESCRIPTION` is defined) + hashtags aside
Desktop layout: hero/Castopod/Funkwhale + Mastodon + WordPress are arranged in columns (1fr 2fr, or 1fr 2fr 1fr with WordPress); stacked vertically on mobile.
[[en-securite]]
=== 🛡️ Security
==== What the application already enforces
- *Dynamic CSP*: `default-src 'self'` with a unique per-request *nonce* for inline scripts/styles; PeerTube/Mastodon/CDN origins added automatically; `localhost` relaxations for development
- *Headers*: `X-Frame-Options: SAMEORIGIN`, `X-Content-Type-Options: nosniff`, `X-XSS-Protection`, `Referrer-Policy: strict-origin-when-cross-origin`, restrictive `Permissions-Policy`, HSTS (HTTPS only), CORP/COOP `same-origin`
+
NOTE: The COEP header was *deliberately removed* (commit `62d4d99`): `require-corp` broke cross-origin embeds from PeerTube, Mastodon, Castopod and Funkwhale.
- *CSRF*: session token (`random_bytes(32)`) injected as a `<meta>` tag, verified with `hash_equals` on the AJAX endpoint; `Origin` header verification
- *Anti-SSRF*: instance URL validation (http/https scheme, blocking private IPs and `localhost`), allowlist of PeerTube API endpoints, cURL without redirects
- *Anti-XSS*: systematic output escaping; input validation (video UUID, search query ≤ 200 chars, page numbers, category ID 120)
==== Server configuration (Apache)
[source,bash]
----
cp conf/.htaccess.sample .htaccess
----
Included protections: blocking of configuration and `.sample` files, of the `/includes/`, `/cache/`, `/docs/`, `/conf/` directories; no directory listing; hidden files blocked; HTTPS enforced; `.php` extension masking (clean URLs `/video` instead of `/video.php`).
WARNING: This configuration is *essential*: without it, your configuration files are exposed to visitors.
==== Server configuration (Nginx)
[source,bash]
----
# Adjust the paths in conf/nginx.conf.sample, then:
sudo cp conf/nginx.conf.sample /etc/nginx/sites-available/annukuteced.buzz
sudo ln -s /etc/nginx/sites-available/annukuteced.buzz /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
----
WARNING: The sample's `location ~* \.(php|inc|conf|config|local)$ { deny all; }` block as written matches *all* `.php` files. Adjust it to target only sensitive paths (e.g. `^/includes/`), or the whole application will become unreachable.
=== 📱 Progressive Web App (PWA)
==== Features
- 📲 *Native installation*: an "Install" button in the header on first visit
- 🌐 *Offline mode*: visited pages and static assets cached
- 📡 *Connection detection*: visual indicator when the network is lost
- 🔄 *Updates*: reload prompt when a new version is detected
==== Service Worker caching strategies (`sw.js`)
[cols="1,2,3",options="header"]
|===
| Request type | Strategy | Detail
| Static assets (`/css/`, `/js/`, `/img/`, cdnjs CDN)
| *Cache First*
| Cached at install time and on first access; falls back to `img/logo.png` if an image fails
| Pages (`/`, `*.php`…)
| *Network First*
| Network first, cache fallback when offline
| API and AJAX (`/ajax/`, `mastodon-config.php`…)
| *Network Only*
| Never cached; synthetic 503 JSON response when offline
|===
TIP: To deploy a new version of the assets, change the version suffix of the cache names in `sw.js` (date format, e.g. `08072026-0720`): old caches are deleted on activation.
==== Installation by visitors
- 🌐 *Chrome/Edge*: menu → "Install annukuteced.buzz"
- 🍎 *Safari iOS*: Share → "Add to Home Screen"
- 🦊 *Firefox Android*: menu → "Install"
[[en-analytics]]
=== 📊 Analytics (Plausible)
The site is *pre-configured* for https://plausible.io[Plausible Analytics^] (cookie-free, GDPR-compliant, anonymous data), but the script is *disabled by default*.
To enable it:
. Open `index.php` and *uncomment* the Plausible snippet (the `<script defer data-domain=… src="https://plausible.io/js/script…js">` tag and its `window.plausible` shim).
. That's it: the CSP already allows `https://plausible.io` in `script-src` and `connect-src`, and the `data-domain` automatically uses the current domain.
Data then collected (anonymous): visited pages, country of origin (IP not stored), device type, browser, time spent.
=== 🚀 Deploying to shared hosting
. 🐘 Check that the host provides PHP 7.4+ and the required extensions
. 🔒 Enable HTTPS (mandatory for the PWA)
. 📤 Upload the files via FTP to the site root
. 🔧 Permissions: `644` for files, `755` for directories; `cache/` must be writable
. 📄 Create the instance files from the `.sample` files (config, manifest, robots, sitemap, legal notice, donations)
. 🛡️ Copy `conf/.htaccess.sample` to `.htaccess` (Apache)
. 🌐 Point the `annukuteced.buzz` domain to the installation directory
. 🧪 Test the PWA via the browser developer tools (Application tab)
=== 👨‍💻 Development and contributing
. 🌿 Create a branch: `git checkout -b my-feature`
. 💾 Commit with *atomic, English messages*, in conventional format (< 70 characters):
* `feat: add …`, `fix: prevent …`, `docs: update …`, `refactor: …`, `typo: …`
. 📤 Push: `git push origin my-feature`
. 🔀 Open a pull request
Repository best practices:
- Bug reports and suggestions are welcome on the fork's repository; generic improvements can be proposed upstream (FEDIVERSE OKI)
- Never version `config.local.php` or files derived from the `.sample` files
- A few constants are defined but unused by the current code (`ENABLE_SEARCH`, `ENABLE_COMMENTS`, `ENABLE_USER_ACCOUNTS`, `TAG_SHORT`): do not rely on them as extension points
==== 📄 Exporting the documentation (HTML / PDF)
This README can be exported to HTML and PDF with a single command:
[source,bash]
----
docs/generate-readme-pdf.sh # README.html + README.pdf
docs/generate-readme-pdf.sh --html # HTML only
docs/generate-readme-pdf.sh --pdf # PDF only
----
Files are produced in the project root and are Git-ignored (`.gitignore`).
Requirements: `asciidoctor`, `chromium` (headless rendering) and the `fonts-noto-color-emoji` package (color emoji in the PDF). The `docs/docinfo.html` stylesheet (emoji fallback + print rules) is automatically injected through Asciidoctor's `docinfo` mechanism.
NOTE: `asciidoctor-pdf` (used by VSCodium's AsciiDoc extension) cannot be used here: its Prawn engine cannot embed color-emoji fonts (CBDT/COLR), so emoji disappear from the PDF. Chromium handles them natively.
=== 📜 License
Copyright (C) 2025 Cédric Famibelle-Pronzola & *ORGANISATION KA INTERNATIONALE*
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the https://www.gnu.org/licenses/agpl-3.0.html[GNU Affero General Public License^] for more details (full text in `LICENSE`).
=== 📞 Contact
For any question or suggestion about this platform: mailto:kontak@o-k-i.net[kontak@o-k-i.net]
<<top,⬆ Back to top>>