Je suis un grand adepte de l’auto-hébergement, et j’utilise Scenari pour mes cours et la documentation de mon homelab. Pour me simplifier la vie, je suis en train d’écrire un script Zsh (tournant sous macOS et Linux) afin d’automatiser la vérification et le téléchargement des dernières versions des modèles documentaires (Canoprof, Opale, Dokiel, Optim, etc.) au format .wsppack.
L’objectif : Interroger le site de téléchargement, comparer la version disponible avec mes archives locales, et télécharger le .wsppack francophone si une mise à jour est disponible.
Ce que j’ai tenté (avec l’aide de Gemini) :
Scraping HTML direct : J’ai utilisé curl pour récupérer le code source de la page (ex: https://download.scenari.software/Opale@26/?os=all) en espérant y parser les liens avec des expressions régulières (grep/sed).
Variations des requêtes : J’ai simulé différents User-Agent, forcé le paramètre ?os=all pour contourner le masquage des binaires selon l’OS, et assoupli au maximum les Regex pour accepter les nouvelles nomenclatures de version (comme sc26.0).
API Elasticsearch : J’ai également tenté d’interroger l’endpoint interne /u/essearch, mais je me heurte à des erreurs HTTP 400/403.
Le problème actuel : Quoi que je fasse, le code HTML brut retourné par curl ne contient pas les noms des fichiers .wsppack. J’en déduis que la liste des téléchargements est générée dynamiquement via JavaScript côté client, ce qui rend mon approche via curl inopérante.
Ma question : Existe-t-il une API publique, un endpoint JSON, ou un flux RSS officiel qui permettrait de récupérer programmatiquement le lien ou le nom exact du dernier .wsppack d’une version majeure donnée pour les différents modèles documentaires que j’utilise ?
Et même question pour les habillages des modèles documentaires qui en ont.
Merci d’avance pour vos lumières et pour le travail formidable sur ces outils !
Je cite quelques personnes qui seraient susceptbiles de pouvoir m’aider : @lalejand@sam@xah
On va prochainement intégrer la possibilité d’installer des packs (issus de scenari.software) ou de déclencher une mise à jour mineur à partir de l’exécutable d’administration d’un serveur Scenari en ligne de commande.
Il est probable que cette nouvelle fonctionnalité arrive sur une prochaine version mineure des serveurs en 6.5. Sinon, ce sera en 6.6.
On n’a pas d’API de description des téléchargements (au sens de description normalisée sur laquelle on s’engage dans le temps sur sa maintenance). Cependant, on utilise bien une description JSON qui change peu dans le temps. Elle est accessible avec ce genre d’URL : https://download.scenari.software/Opale@26/?productInfos
Si vous l’utilisez, attention donc, on s’autorise à changer le retour de cette requête n’importe quand sans préavis.
Bonsoir @tha
Merci pour l’information.
J’attends avec impatience l’implémentation de cette fonctionnalité d’installation des packs issus de scenari.software directement depuis scenari même
Pour les téléchargement, Gemini a fini par me dire la même chose : ajouter ?productInfos à la fin de l’url pour avoir toutes les infos.
J’ai même poussé le vice à ne pas mettre le numéro de version majeure, car sinon je n’avais pas la version 26 de Parcours.
Voici le script actuel, si ça peut aider quelqu’un
Il peut être lancé depuis macOS ou linux, sans problèmes de compatibilité des binaires sous macOS moyennant l’installation de gsed et gawk avec homebrew.
#!/usr/bin/env zsh
#
# ==============================================================================
# 🛠️ SCRIPT DE MISE À JOUR DES MODÈLES DOCUMENTAIRES SCENARI & HABILLAGES
# ==============================================================================
# 📝 Description : Récupère la version exacte et extrait les packages .wsppack
# et .skinpack en interrogeant directement l'endpoint JSON.
# Détecte automatiquement la "Latest Release" sans version fixe.
# 👨💻 OS Support : macOS (M1/M2/Intel) & Linux (Debian/Ubuntu)
# ==============================================================================
# 📜 CHANGELOG (Antéchronologique)
# ------------------------------------------------------------------------------
# • v2.8.0 (2026-07-20) - Passage en mode "Latest Release" dynamique.
# - Suppression des versions majeures codées en dur dans la configuration.
# - Interrogation de la racine des modèles/addons via l'API ?productInfos.
# - Extraction dynamique de la version majeure depuis le nom de fichier (.wsppack).
# - Conversion syntaxique des versions d'habillages (ex: "26_1" -> "@26.1").
# • v2.7.0 (2026-07-16) - Séparation des répertoires de destination.
# - Isolation des .wsppack et .skinpack dans deux dossiers distincts.
# - Gestion de l'archivage séparé pour chaque type d'extension.
# • v2.6.0 (2026-07-16) - Intégration des habillages (Skinpacks).
# - Ajout d'une 4ème colonne dans la configuration pour déclarer les addons.
# - Boucle secondaire pour interroger l'API sur le répertoire /addons/.
# - Gestion des déclinaisons multiples (ex: Blueberry et Mint pour Daylight).
# - Algorithme de nettoyage ciblé par déclinaison d'habillage.
# • v2.5.0 (2026-07-16) - Bascule sur l'API JSON native (?productInfos).
# • v2.4.2 (2026-07-16) - Regex unifiée ultra-robuste & Diagnostic automatique.
# • v2.4.1 (2026-07-16) - Correction du bypass de debug & Forçage de l'OS global.
# • v2.4.0 (2026-07-16) - Scraping HTML direct & Restauration des étapes de log.
# • v2.3.2-ciblé (2026-07-16) - Correction de la liaison logique de probing.
# • v2.3.0 (2026-07-15) - Intégration expérimentale de l'API Elasticsearch.
# • v2.2.0 (2026-06-12) - Amélioration de l'archivage local et de la détection.
# • v2.1.0 (2026-05-08) - Prise en charge multi-modèles.
# • v2.0.0 (2026-03-10) - Migration complète et optimisation pour Zsh sous macOS.
# • v1.0.0 (2026-02-15) - Version initiale du script de récupération.
# ==============================================================================
# ------------------------------------------------------------------------------
# ⚙️ CONFIGURATION & CONSTANTES
# ------------------------------------------------------------------------------
VERSION="2.8.0"
BASE_URL="https://download.scenari.software"
# 📂 Dossiers de destination séparés
DOSSIER_CIBLE_WSPPACK="${HOME}/Downloads/SCENARI - Fichiers pour le serveur/wsppack"
DOSSIER_CIBLE_SKINPACK="${HOME}/Downloads/SCENARI - Fichiers pour le serveur/skinpack"
NOM_DOSSIER_ARCHIVE="archives"
# 🛠️ MODE DEBUG : Mettre à "true" pour ne tester QUE le premier modèle (Opale).
DEBUG_SINGLE_MODE=false
USER_AGENT="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15"
# 📦 Liste des modèles à suivre (Dynamique)
# Format : "Nom_Scenari:Pattern_Local:Liste_Skins_Separees_Par_Virgule"
TYPES_MODELES=(
"Opale:opale:OpaleSkinDaylight,OpaleSkinSunrise"
"Dokiel:dokiel:DokielSkinTitania"
"Optim:optimoffice:OptimSkinFool,OptimSkinKarmaIsAPeach,OptimSkinLeaf"
"Parcours:parcours:"
"Canoprof:canoprof:"
)
# ------------------------------------------------------------------------------
# 🔍 DÉTECTION DE L'ENVIRONNEMENT
# ------------------------------------------------------------------------------
OS_TYPE=$(uname -s)
if [[ "$OS_TYPE" == "Darwin" ]]; then
CMD_SED="gsed"
CMD_AWK="gawk"
elif [[ "$OS_TYPE" == "Linux" ]]; then
CMD_SED="sed"
CMD_AWK="awk"
else
echo "❌ [ERREUR] OS non supporté : $OS_TYPE"
exit 1
fi
for cmd in $CMD_SED $CMD_AWK curl wget grep; do
if ! command -v $cmd &> /dev/null; then
echo "❌ [ERREUR] Commande requise introuvable : $cmd"
exit 1
fi
done
if [[ "$DEBUG_SINGLE_MODE" == "true" ]]; then
TYPES_MODELES=("${TYPES_MODELES[1]}")
fi
# Création des arborescences
mkdir -p "$DOSSIER_CIBLE_WSPPACK/$NOM_DOSSIER_ARCHIVE"
mkdir -p "$DOSSIER_CIBLE_SKINPACK/$NOM_DOSSIER_ARCHIVE"
echo "=========================================================================="
echo "🚀 Démarrage du script Scenari v$VERSION (Mode Automatique & Dynamique)"
echo "💻 Environnement détecté : $OS_TYPE"
echo "📂 Cible WSPPACK : $DOSSIER_CIBLE_WSPPACK"
echo "📂 Cible SKINPACK : $DOSSIER_CIBLE_SKINPACK"
[[ "$DEBUG_SINGLE_MODE" == "true" ]] && echo "⚠️ [DEBUG] Mode cible unique activé (Opale uniquement)"
echo "=========================================================================="
# ------------------------------------------------------------------------------
# 🔄 BOUCLE PRINCIPALE
# ------------------------------------------------------------------------------
for msg_info in "${TYPES_MODELES[@]}"; do
MODELE_NOM=$(echo "$msg_info" | cut -d: -f1)
MODELE_PREFIXE=$(echo "$msg_info" | cut -d: -f2)
LISTE_SKINS=$(echo "$msg_info" | cut -d: -f3)
# ==========================================
# 🗂️ PARTIE 1 : LE MODÈLE DOCUMENTAIRE (.wsppack)
# ==========================================
# Interrogation de la racine du produit sans tag @version pour chasser la Latest Release
URL_API="${BASE_URL}/${MODELE_NOM}/?productInfos"
echo -e "\n📦 \e[1;34m$MODELE_NOM\e[0m (Recherche de la dernière version stable...)"
echo "📝 [STEP 1] Modèle - Interrogation de l'API : $URL_API"
JSON_PAYLOAD=$(curl -sL -A "$USER_AGENT" "$URL_API")
if [[ -z "$JSON_PAYLOAD" ]]; then
echo "❌ [ERREUR] Impossible de joindre l'API pour $MODELE_NOM."
continue
fi
FICHIER_DISTANT=$(echo "$JSON_PAYLOAD" | grep -oE '[a-zA-Z0-9_.-]+\.wsppack' | grep -i "_fr" | head -n 1)
if [[ -n "$FICHIER_DISTANT" ]]; then
# Extraction à la volée de la version majeure depuis le nom de fichier distant
VERSION_EXACTE=$(echo "$FICHIER_DISTANT" | $CMD_AWK -F'_' '{print $2}')
VERSION_MAJEURE=$(echo "$VERSION_EXACTE" | cut -d'.' -f1)
echo "🎯 Fichier distant identifié : $FICHIER_DISTANT (Majeure détectée : $VERSION_MAJEURE)"
if [[ "$MODELE_NOM" == "Canoprof" ]]; then
FICHIER_LOCAL_EXISTANT=$(find "$DOSSIER_CIBLE_WSPPACK" -maxdepth 1 -name "canoprofExpert_*.wsppack" -o -name "canoprof_*.wsppack" | head -n 1)
elif [[ "$MODELE_NOM" == "Optim" ]]; then
FICHIER_LOCAL_EXISTANT=$(find "$DOSSIER_CIBLE_WSPPACK" -maxdepth 1 -iname "optimoffice_*.wsppack" | head -n 1)
else
FICHIER_LOCAL_EXISTANT=$(find "$DOSSIER_CIBLE_WSPPACK" -maxdepth 1 -iname "${MODELE_PREFIXE}_*.wsppack" | head -n 1)
fi
if [[ -n "$FICHIER_LOCAL_EXISTANT" ]]; then
NOM_FICHIER_LOCAL=$(basename "$FICHIER_LOCAL_EXISTANT")
if [[ "$NOM_FICHIER_LOCAL" == "$FICHIER_DISTANT" ]]; then
echo "✅ [OK] Le modèle $MODELE_NOM est à jour."
else
echo "🔄 Archivage de l'ancien modèle : $NOM_FICHIER_LOCAL"
mv "$FICHIER_LOCAL_EXISTANT" "$DOSSIER_CIBLE_WSPPACK/$NOM_DOSSIER_ARCHIVE/"
URL_TELECHARGEMENT="${BASE_URL}/${MODELE_NOM}@${VERSION_MAJEURE}/${FICHIER_DISTANT}"
echo "📥 Téléchargement de $FICHIER_DISTANT..."
wget -q --show-progress --user-agent="$USER_AGENT" -O "$DOSSIER_CIBLE_WSPPACK/$FICHIER_DISTANT" "$URL_TELECHARGEMENT"
fi
else
URL_TELECHARGEMENT="${BASE_URL}/${MODELE_NOM}@${VERSION_MAJEURE}/${FICHIER_DISTANT}"
echo "📥 Premier téléchargement de $FICHIER_DISTANT..."
wget -q --show-progress --user-agent="$USER_AGENT" -O "$DOSSIER_CIBLE_WSPPACK/$FICHIER_DISTANT" "$URL_TELECHARGEMENT"
fi
else
echo "❌ [ERREUR] Aucun fichier .wsppack trouvé pour $MODELE_NOM."
fi
# ==========================================
# 🎨 PARTIE 2 : LES HABILLAGES (.skinpack)
# ==========================================
if [[ -n "$LISTE_SKINS" ]]; then
echo -e "\n🎨 [STEP 2] Vérification des habillages associés..."
IFS=',' read -r -A SKINS_ARRAY <<< "$LISTE_SKINS"
for NOM_ADDON in "${SKINS_ARRAY[@]}"; do
# Interrogation de la racine de l'addon pour s'affranchir de la version forcée
URL_SKIN_API="${BASE_URL}/addons/${MODELE_NOM}/${NOM_ADDON}/?productInfos"
echo " 🔍 Recherche pour l'addon : $NOM_ADDON via $URL_SKIN_API"
SKIN_JSON=$(curl -sL -A "$USER_AGENT" "$URL_SKIN_API")
if [[ -z "$SKIN_JSON" ]]; then
echo " ❌ Impossible de joindre l'API pour l'addon $NOM_ADDON."
continue
fi
SKINPACKS=$(echo "$SKIN_JSON" | grep -oE '[a-zA-Z0-9_.-]+\.skinpack' | sort -u)
if [[ -z "$SKINPACKS" ]]; then
echo " ⚠️ Aucun fichier .skinpack trouvé dans l'addon $NOM_ADDON."
continue
fi
echo "$SKINPACKS" | while read -r FICHIER_SKIN; do
echo " 🎯 Skin identifiée : $FICHIER_SKIN"
BASE_SKIN_NAME=$(echo "$FICHIER_SKIN" | $CMD_SED -E 's/[0-9_]+\.skinpack$//')
# Extraction et conversion de la version du skin (ex: "26_1" -> "@26.1") pour l'URL de DL
SKIN_VER_RAW=$(echo "$FICHIER_SKIN" | grep -oE '[0-9]+_[0-9]+' | head -n 1)
SKIN_VER_URL=""
if [[ -n "$SKIN_VER_RAW" ]]; then
SKIN_VER_DOT=$(echo "$SKIN_VER_RAW" | $CMD_SED 's/_/./')
SKIN_VER_URL="@${SKIN_VER_DOT}"
fi
# Vérification de l'existence dans le dossier skinpack
if [[ -f "$DOSSIER_CIBLE_SKINPACK/$FICHIER_SKIN" ]]; then
echo " ✅ [OK] Déjà à jour."
continue
fi
# Archivage des anciennes versions dans le dossier skinpack/archives
ANCIENS_SKINS=$(find "$DOSSIER_CIBLE_SKINPACK" -maxdepth 1 -name "${BASE_SKIN_NAME}*.skinpack")
if [[ -n "$ANCIENS_SKINS" ]]; then
echo "$ANCIENS_SKINS" | while read -r ANCIEN; do
NOM_ANCIEN=$(basename "$ANCIEN")
echo " 🔄 Archivage de l'ancienne skin : $NOM_ANCIEN"
mv "$ANCIEN" "$DOSSIER_CIBLE_SKINPACK/$NOM_DOSSIER_ARCHIVE/"
done
fi
URL_TELECHARGEMENT_SKIN="${BASE_URL}/addons/${MODELE_NOM}/${NOM_ADDON}${SKIN_VER_URL}/${FICHIER_SKIN}"
echo " 📥 Téléchargement de $FICHIER_SKIN..."
wget -q --show-progress --user-agent="$USER_AGENT" -O "$DOSSIER_CIBLE_SKINPACK/$FICHIER_SKIN" "$URL_TELECHARGEMENT_SKIN"
done
done
fi
done
echo -e "\n=========================================================================="
echo "🏁 Fin du traitement des modèles et habillages."
echo "=========================================================================="