diff --git a/server/logger.js b/server/logger.js index 8e1cc7a..295268d 100644 --- a/server/logger.js +++ b/server/logger.js @@ -16,51 +16,61 @@ along with this program. If not, see . */ -const { createLogger, format, transports } = require("winston"); -const DailyRotateFile = require("winston-daily-rotate-file"); -const fs = require("fs"); -const path = require("path"); +const { createLogger, format, transports } = require("winston"); +// on importe Winston (logger populaire pour Node.js) +const DailyRotateFile = require("winston-daily-rotate-file"); +// plugin pour Winston qui permet la rotation automatique des fichiers de logs + +const fs = require("fs"); // pour vérifier/créer le dossier de logs +const path = require("path"); // pour gérer proprement les chemins + +// on définit le dossier de logs const logDir = path.join(__dirname, "../logs"); +// on le crée si inexistant if (!fs.existsSync(logDir)) fs.mkdirSync(logDir); +// fonction pour générer une ligne de début de session (date ISO) function getSessionStartLine() { const now = new Date().toISOString(); return `--- Démarrage de la session : ${now} ---`; } +// fonction pour générer une ligne de fin de session (date ISO) function getSessionEndLine() { const now = new Date().toISOString(); return `--- Fin de la session : ${now} ---`; } +// configuration principale de Winston const logger = createLogger({ - level: "info", + level: "info", // niveau de log minimum (info, warn, error, etc.) format: format.combine( - format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), // timestamp lisible format.printf(({ timestamp, level, message }) => - `${timestamp} | ${level.toUpperCase()} | ${message}` + `${timestamp} | ${level.toUpperCase()} | ${message}` // format de ligne de log ) ), transports: [ new DailyRotateFile({ - dirname: logDir, - filename: "combined-%DATE%.log", - datePattern: "YYYY-MM-DD", - zippedArchive: false, // tu peux mettre true si tu veux compresser - maxFiles: "14d", // conserve les logs des 14 derniers jours + dirname: logDir, // dossier de logs + filename: "combined-%DATE%.log", // nom de fichier journalier + datePattern: "YYYY-MM-DD", // pattern de rotation + zippedArchive: false, // true = compression des archives + maxFiles: "14d", // garde 14 jours d’historique format: format.combine( format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), format.printf(({ timestamp, level, message }) => `${timestamp} | ${level.toUpperCase()} | ${message}` ) ), - options: { flags: "a" }, + options: { flags: "a" }, // mode append }), new transports.Console({ + // sortie console colorisée (dev en local) format: format.combine( format.colorize(), - format.timestamp({ format: "HH:mm:ss" }), + format.timestamp({ format: "HH:mm:ss" }), // horodatage plus court format.printf(({ timestamp, level, message }) => `${timestamp} | ${level} | ${message}` ) @@ -69,14 +79,17 @@ const logger = createLogger({ ], }); +// fonction d'utilitaire pour marquer le début de la session function logSessionStart() { logger.info(getSessionStartLine()); } +// idem pour la fin de la session function logSessionEnd() { logger.info(getSessionEndLine()); } +// exporte le logger et les helpers pour les autres modules module.exports = { logger, logSessionStart, diff --git a/server/routes/download.js b/server/routes/download.js index 3bac042..af916b7 100644 --- a/server/routes/download.js +++ b/server/routes/download.js @@ -16,90 +16,110 @@ along with this program. If not, see . */ -const express = require("express"); -const router = express.Router(); -const { execFile } = require("child_process"); -const path = require("path"); -const fs = require("fs"); +const express = require("express"); // importe le framework Express +const router = express.Router(); // crée un routeur Express pour modulariser les routes +const { execFile } = require("child_process"); // permet d’exécuter un binaire externe sans passer par un shell +const path = require("path"); // module Node.js pour gérer les chemins de fichiers +const fs = require("fs"); // module Node.js pour manipuler le système de fichiers -const logger = require("../logger").logger; // importe ton logger Winston +const logger = require("../logger").logger; // récupère l'instance de logger Winston personnalisée +// chemin vers l’exécutable yt-dlp packagé avec l’appli const ytDlpPath = path.join(__dirname, '../../yt-dlp.exe'); +// route POST sur la racine (ex: POST /download) router.post("/", (req, res) => { try { + // récupère les options envoyées depuis le frontend (via formulaire ou requête AJAX) const options = { - url: req.body.url, - audioOnly: req.body.audioOnly === "1", - quality: req.body.quality || "best", - subtitles: req.body.subs === "1", + url: req.body.url, // URL à télécharger + audioOnly: req.body.audioOnly === "1", // conversion audio uniquement (mp3) + quality: req.body.quality || "best", // qualité vidéo à télécharger + subtitles: req.body.subs === "1", // récupérer les sous-titres anglais }; if (!options.url) { + // si l’URL est absente, on log et on renvoie 400 logger.warn("Requête POST /download sans URL"); return res.status(400).send("❌ URL manquante !"); } - // Récupérer le chemin Freedom Loader Output depuis app.locals + // on récupère le dossier de sortie depuis app.locals (défini ailleurs dans le serveur) const outputFolder = req.app.locals.outputFolder; + // s'il n'existe pas, on le crée automatiquement if (!fs.existsSync(outputFolder)) { fs.mkdirSync(outputFolder, { recursive: true }); logger.info("Dossier Freedom Loader Output créé à la volée dans Téléchargements."); } - // Construire le template de sortie + // template du nom de fichier de sortie (ex: MonTitre.mp4 ou MonTitre.mp3) const outputTemplate = path.join(outputFolder, "%(title)s.%(ext)s"); - // Construire les arguments pour execFile + // tableau d'arguments pour yt-dlp const args = []; if (options.audioOnly) { + // si audioOnly, on force extraction audio en mp3 args.push("--extract-audio", "--audio-format", "mp3"); } if (options.subtitles) { + // si l’utilisateur veut des sous-titres args.push("--write-subs", "--sub-lang", "en"); } + // qualité souhaitée args.push("-f", options.quality); + // modèle de sortie args.push("-o", outputTemplate); + // enfin, l'URL à télécharger args.push(options.url); + // log de la commande complète pour debug logger.info(`Commande yt-dlp : ${ytDlpPath} ${args.join(" ")}`); + // exécution du binaire yt-dlp const child = execFile(ytDlpPath, args); + // gestion des logs stdout de yt-dlp child.stdout.on("data", (data) => { data.toString().split("\n").forEach(line => { if(line.trim()) logger.info(`[yt-dlp stdout] ${line.trim()}`); }); }); + // gestion des logs stderr de yt-dlp (les erreurs) child.stderr.on("data", (data) => { data.toString().split("\n").forEach(line => { if(line.trim()) logger.error(`[yt-dlp stderr] ${line.trim()}`); }); }); + // si une erreur système survient (par ex : yt-dlp.exe introuvable) child.on("error", (err) => { logger.error(`Erreur lancement yt-dlp : ${err.message}`); res.status(500).send(`❌ Erreur lors de l'exécution : ${err.message}`); }); + // callback quand le processus se termine child.on("close", (code) => { logger.info(`yt-dlp terminé avec code de sortie : ${code}`); if(code === 0) { + // succès res.send("✅ Téléchargement terminé !"); } else { + // yt-dlp a échoué res.status(500).send(`❌ yt-dlp a échoué avec le code : ${code}`); } }); } catch (err) { + // en cas d'erreur JavaScript logger.error(`Erreur serveur dans /download : ${err.message}`); res.status(500).send(`Erreur serveur : ${err.message}`); } }); -module.exports = router; +// on exporte ce routeur pour qu'il soit utilisé par l'app principale +module.exports = router; \ No newline at end of file diff --git a/server/routes/info.js b/server/routes/info.js index 23eb652..372e823 100644 --- a/server/routes/info.js +++ b/server/routes/info.js @@ -16,32 +16,40 @@ along with this program. If not, see . */ -const express = require("express"); -const router = express.Router(); -const { execFile } = require("child_process"); -const path = require("path"); +const express = require("express"); // importe Express +const router = express.Router(); // crée un routeur Express indépendant +const { execFile } = require("child_process"); // permet d’exécuter un programme externe +const path = require("path"); // module pour gérer les chemins de fichiers +// chemin vers le binaire yt-dlp embarqué const ytDlpPath = path.join(__dirname, "../../yt-dlp.exe"); +// route POST sur la racine de ce routeur router.post("/", (req, res) => { - const url = req.body.url; - if (!url) return res.status(400).send("❌ URL manquante"); + const url = req.body.url; // récupère l'URL depuis la requête POST + if (!url) return res.status(400).send("❌ URL manquante"); // validation simple + // exécute yt-dlp avec --dump-json pour récupérer les métadonnées de la vidéo execFile(ytDlpPath, ["--dump-json", url], (error, stdout, stderr) => { if (error) { + // gestion d’erreur : par exemple yt-dlp plante ou l’URL est invalide console.error("Erreur lors de l'exécution de yt-dlp :", error); console.error("stderr :", stderr); return res.status(500).send("❌ Impossible de récupérer les infos."); } try { + // on parse le JSON envoyé par yt-dlp const info = JSON.parse(stdout); + // on renvoie l’objet JSON directement au frontend res.json(info); } catch (e) { + // le JSON est mal formé console.error("Erreur lors du parsing JSON :", e); res.status(500).send("❌ JSON illisible."); } }); }); +// on exporte le routeur pour être utilisé dans app.js ou ailleurs module.exports = router; diff --git a/server/server.js b/server/server.js index c2e98c3..ba05351 100644 --- a/server/server.js +++ b/server/server.js @@ -16,59 +16,68 @@ along with this program. If not, see . */ -const express = require("express"); -const { exec } = require("child_process"); -const fs = require("fs"); -const path = require("path"); -const debug = require("debug")("freedom-loader:server"); +const express = require("express"); // framework web +const { exec } = require("child_process"); // si besoin d’exécuter des commandes (pas utilisé ici directement) +const fs = require("fs"); // accès au système de fichiers +const path = require("path"); // gestion de chemins +const debug = require("debug")("freedom-loader:server"); +// petit debugger coloré en console + const { logger, logSessionStart, logSessionEnd } = require("./logger"); +// récupère notre logger Winston + helpers de session -const app = express(); +const app = express(); // instancie l'application Express -// // Lancement d'une nouvelle session de log -// logSessionStart(); +// logSessionStart(); // désactivé, mais pourrait logguer le début de session -// dossier de sortie +// on définit le dossier de téléchargement dans le dossier Téléchargements de l’utilisateur const downloadsPath = path.join(process.env.USERPROFILE, "Downloads"); const outputFolder = path.join(downloadsPath, "Freedom Loader Output"); +// création du dossier s’il n’existe pas if (!fs.existsSync(outputFolder)) { try { fs.mkdirSync(outputFolder, { recursive: true }); logger.info("Dossier Freedom Loader Output cree dans Telechargements."); } catch (err) { logger.error("Impossible de creer le dossier :", err); - process.exit(1); + process.exit(1); // arrêt du programme en cas d’échec } } else { logger.info("Dossier Freedom Loader Output deja existant."); } +// on rend ce dossier dispo via app.locals app.locals.outputFolder = outputFolder; + +// configuration pour parser les requêtes POST en urlencoded app.use(express.urlencoded({ extended: true })); +// sert les fichiers statiques (frontend) const staticPath = path.join(__dirname, "../public"); debug("Serveur statique sur", staticPath); app.use(express.static(staticPath)); -// routes +// enregistre les routes API const infoRoute = require("./routes/info"); const downloadRoute = require("./routes/download"); debug("Routes /download et /info installees"); app.use("/download", downloadRoute); app.use("/info", infoRoute); +// route GET / de base app.get("/", (req, res) => { debug("Requete GET / servie"); res.sendFile(path.join(__dirname, "../public/index.html")); }); +// fonction pour démarrer le serveur (Promise pour pouvoir await ailleurs) function startServer() { return new Promise((resolve, reject) => { logger.info("Demarrage du serveur Express..."); const serverInstance = app.listen(8080, () => { logger.info("Serveur Express pret sur http://localhost:8080"); - resolve(serverInstance); + resolve(serverInstance); // succès }); serverInstance.on("error", (err) => { logger.error("Erreur serveur Express :", err); @@ -77,17 +86,18 @@ function startServer() { }); } -// Gestion propre de la fin de session à la fermeture du process +// gestion de la fermeture propre du process // process.on("exit", () => { // logSessionEnd(); // }); -process.on("SIGINT", () => { +process.on("SIGINT", () => { // Ctrl+C logSessionEnd(); process.exit(); }); -process.on("SIGTERM", () => { +process.on("SIGTERM", () => { // kill logSessionEnd(); process.exit(); }); +// on exporte la fonction startServer pour que d’autres modules puissent la lancer module.exports = { startServer };