diff --git a/OLD CODE/build/apercu1.0.0.png b/OLD CODE/build/apercu1.0.0.png deleted file mode 100644 index 9f0e1ee..0000000 Binary files a/OLD CODE/build/apercu1.0.0.png and /dev/null differ diff --git a/OLD CODE/build/apercu1.0.1.png b/OLD CODE/build/apercu1.0.1.png deleted file mode 100644 index 212f852..0000000 Binary files a/OLD CODE/build/apercu1.0.1.png and /dev/null differ diff --git a/OLD CODE/main.js b/OLD CODE/main.js deleted file mode 100644 index a835d2d..0000000 --- a/OLD CODE/main.js +++ /dev/null @@ -1,165 +0,0 @@ -/* - This file is part of Freedom Loader. - - Copyright (C) 2025 MasterAcnolo - - Freedom Loader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License. - - Freedom Loader 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 GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -const { app, BrowserWindow, ipcMain, dialog, Menu, shell } = require("electron"); -app.disableHardwareAcceleration(); // Désactive l'accélération GPU pour éviter les erreurs GPU sur certains systèmes -const path = require("path"); -const os = require("os"); -const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger"); - -let mainWindow; -// Utilise le vrai dossier de logs défini dans logger.js -const logsFolderPath = logDir; -app.setAppUserModelId("com.masteracnolo.freedomloader"); /* Pour la notif */ -/* - Fonction principale qui crée la fenêtre principale de l'application. - Elle évite la création multiple et configure les dimensions et options de la fenêtre. - Charge l'URL locale du serveur Express et gère les erreurs éventuelles. -*/ -async function createWindow() { - logger.info("Création de la fenêtre..."); - - if (mainWindow) { - logger.warn("La fenêtre existe déjà, pas de nouvelle création"); - return; - } - - mainWindow = new BrowserWindow({ - title: "Freedom Loader 1.2.4", // A Changer à chaque Version - width: 750, - height: 800, - minWidth: 750, - minHeight: 800, - webPreferences: { - nodeIntegration: false, - contextIsolation: true, - preload: path.join(__dirname, "preload.js"), // Chargement du preload script sécurisé - }, - // titleBarStyle: 'hidden', // Option possible pour une barre de titre personnalisée - }); - - try { - await mainWindow.loadURL("http://localhost:8080"); - logger.info("Fenêtre chargée"); - } catch (err) { - logger.error("Erreur chargement fenêtre:", err); - } - - // Événement déclenché à la fermeture de la fenêtre principale - mainWindow.on("closed", () => { - logger.info("Fenêtre principale fermée"); - mainWindow = null; - }); -} - -/* - Définition du chemin par défaut pour les téléchargements. - Ici, on utilise le dossier 'Downloads' de l'utilisateur avec un sous-dossier spécifique à l'application. -*/ -const defaultDownloadPath = path.join(os.homedir(), "Downloads", "Freedom Loader"); - -/* - Gestionnaire IPC qui permet au renderer de demander à l'utilisateur - de sélectionner un dossier via une boîte de dialogue native. - On retourne le chemin sélectionné ou null si annulation. -*/ -ipcMain.handle("select-download-folder", async () => { - logger.info("Demande de sélection d'un dossier reçue depuis le renderer"); - try { - const result = await dialog.showOpenDialog({ - properties: ["openDirectory"] - }); - if (!result.canceled && result.filePaths.length > 0) { - logger.info(`Dossier sélectionné : ${result.filePaths[0]}`); - return result.filePaths[0]; - } - return null; - } catch (err) { - logger.error(`Erreur lors de la sélection de dossier : ${err.message}`); - return null; - } -}); - -/* - Gestionnaire IPC pour exposer au renderer le chemin par défaut de téléchargement - afin qu'il puisse l'afficher ou l'utiliser comme valeur initiale. -*/ -ipcMain.handle("get-default-download-path", () => { - return defaultDownloadPath; -}); - -/* - Événement déclenché quand l'application est prête. - On démarre le serveur Express, puis on crée la fenêtre principale. - En cas d'erreur, on log et on quitte l'application proprement. -*/ -app.whenReady().then(async () => { - logSessionStart(); - logger.info("App prête, démarrage du serveur Express..."); - const expressServer = require("./server/server.js"); - try { - await expressServer.startServer(); - logger.info("Serveur Express démarré"); - await createWindow(); - - // Ajout du menu personnalisé - const menuTemplate = [ - // ... Menu standard Electron ... - { - label: "Logs", - submenu: [ - { - label: "Ouvrir les logs", - click: () => { - shell.openPath(logsFolderPath); - } - } - ] - } - ]; - - // Fusionner avec le menu par défaut - const defaultMenu = Menu.getApplicationMenu(); - let template = menuTemplate; - if (defaultMenu) { - template = [...Menu.getApplicationMenu().items.map(item => item), ...menuTemplate]; - } - const finalMenu = Menu.buildFromTemplate(template); - Menu.setApplicationMenu(finalMenu); - - } catch (error) { - logger.error("Erreur serveur ou fenêtre :", error); - app.quit(); - } -}); - -/* - Quitte l'application lorsque toutes les fenêtres sont fermées, - sauf sous macOS où il est habituel de garder l'application active. -*/ -app.on("window-all-closed", () => { - logger.info("Toutes fenêtres fermées, quitte l'app"); - if (process.platform !== "darwin") app.quit(); -}); - -/* - Avant de quitter l'application, on log la fin de session pour traçabilité. -*/ -app.on("before-quit", () => { - logSessionEnd(); -}); diff --git a/OLD CODE/public/assets/favicon.ico b/OLD CODE/public/assets/favicon.ico deleted file mode 100644 index 679f4f9..0000000 Binary files a/OLD CODE/public/assets/favicon.ico and /dev/null differ diff --git a/OLD CODE/public/assets/icons/GitHub Icon.ico b/OLD CODE/public/assets/icons/GitHub Icon.ico deleted file mode 100644 index 798a024..0000000 Binary files a/OLD CODE/public/assets/icons/GitHub Icon.ico and /dev/null differ diff --git a/OLD CODE/public/assets/icons/GitHub Icon.svg b/OLD CODE/public/assets/icons/GitHub Icon.svg deleted file mode 100644 index a8d1174..0000000 --- a/OLD CODE/public/assets/icons/GitHub Icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/OLD CODE/public/assets/icons/file-audio-solid.svg b/OLD CODE/public/assets/icons/file-audio-solid.svg deleted file mode 100644 index d0582af..0000000 --- a/OLD CODE/public/assets/icons/file-audio-solid.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/OLD CODE/public/script/chirac.js b/OLD CODE/public/script/chirac.js deleted file mode 100644 index fab95b6..0000000 --- a/OLD CODE/public/script/chirac.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - This file is part of Freedom Loader. - - Copyright (C) 2025 MasterAcnolo - - Freedom Loader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License. - - Freedom Loader 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 GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -function Chirac(){ - const title = document.getElementById("title"); - title.innerHTML = "Chirac Loader"; - - const subtitle = document.getElementById("subtitle"); - subtitle.innerHTML = "J'aime les pommes"; - subtitle.style.color= "black"; - - document.body.style.backgroundImage = "url('assets/images/goat2.webp')"; - document.body.style.backgroundSize = "cover"; - document.body.style.backgroundPosition = "center"; - document.body.style.backgroundAttachment = "fixed"; - - console.log("Je serai le président de tous les français"); -} \ No newline at end of file diff --git a/OLD CODE/public/script/customthemes.js b/OLD CODE/public/script/customthemes.js deleted file mode 100644 index 0510f8d..0000000 --- a/OLD CODE/public/script/customthemes.js +++ /dev/null @@ -1,107 +0,0 @@ -/* - This file is part of Freedom Loader. - - Copyright (C) 2025 MasterAcnolo - - Freedom Loader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License. - - Freedom Loader 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 GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -// Liste des thèmes disponibles (clé = nom, valeur = label affiché) -const themes = { - default: "Default", - dark: "Sombre", - light: "Clair", - nf : "NF", - chirac: "Chirac", - fanatic: "Fanatic", - cyberpunk: "Cyberpunk", - spicy: "Spicy", - vilbrequin: "Vilbrequin" -}; - -const themeSubtitles = { - default: "Because why not?", - dark: "Darkness is my ally", - light: "Qui aime ce thème ?", - nf : "You call it music, i call it my Therapist", - chirac: "J'aime les pommes", - fanatic: "Always Fnatic !", - cyberpunk: "Wake up, choom. We’ve got a city to burn.", - spicy: "The Spiciest One", - vilbrequin: "Rend l'argent" -}; - -const themeSelect = document.getElementById("themeSelect"); - -// Remplir le select avec les options -function populateThemeSelect() { - for (const [key, label] of Object.entries(themes)) { - const option = document.createElement("option"); - option.value = key; - option.textContent = label; - themeSelect.appendChild(option); - } -} - -// Appliquer le thème sur le body et changer le subtitle -function applyTheme(themeName) { - // Supprime les classes thème précédentes - document.body.classList.remove(...Object.keys(themes)); - // Ajoute la classe du thème actuel - document.body.classList.add(themeName); - - // Modifier le subtitle - const subtitle = document.getElementById("subtitle"); - if (subtitle && themeSubtitles[themeName]) { - subtitle.textContent = themeSubtitles[themeName]; - } - - // Optionnel : animation spéciale Chirac - if (themeName === "chirac") { - requestAnimationFrame(() => { - document.body.classList.add("theme-active"); - }); - } else { - document.body.classList.remove("theme-active"); - } -} - -// Sauvegarder le thème en localStorage -function saveTheme(themeName) { - localStorage.setItem("selectedTheme", themeName); -} - -// Charger le thème au démarrage -function loadTheme() { - const savedTheme = localStorage.getItem("selectedTheme"); - if (savedTheme && themes[savedTheme]) { - applyTheme(savedTheme); - themeSelect.value = savedTheme; - } else { - applyTheme("dark"); - themeSelect.value = "dark"; - } -} - -// Quand l’utilisateur change le select -themeSelect.addEventListener("change", (e) => { - const selected = e.target.value; - if (themes[selected]) { - applyTheme(selected); - saveTheme(selected); - } -}); - -// Initialisation -populateThemeSelect(); -loadTheme(); diff --git a/OLD CODE/public/script/fetchinfo.js b/OLD CODE/public/script/fetchinfo.js deleted file mode 100644 index 4287f97..0000000 --- a/OLD CODE/public/script/fetchinfo.js +++ /dev/null @@ -1,136 +0,0 @@ -/* - This file is part of Freedom Loader. - - Copyright (C) 2025 MasterAcnolo - - Freedom Loader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License. - - Freedom Loader 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 GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -// On attend que le DOM soit complètement chargé -document.addEventListener("DOMContentLoaded", () => { - // Récupération des éléments du DOM pour interaction - const urlInput = document.getElementById("UrlInput"); - const infoDiv = document.getElementById("videoInfo"); - - // Stocke la dernière URL fetchée pour éviter les requêtes répétées inutiles - let lastFetchedUrl = ""; - - // Active les logs DEBUG pour le développement - const DEBUG = true; - - // Écoute les changements dans l'input URL - urlInput.addEventListener("input", async () => { - const url = urlInput.value.trim(); - - if (DEBUG) console.log("[DEBUG] Input détecté :", url); - - // Si l'URL est vide ou trop courte, on reset l'affichage - if (!url || url.length < 5) { - if (DEBUG) console.log("[DEBUG] URL vide ou trop courte, reset affichage."); - infoDiv.innerHTML = ""; - infoDiv.classList.remove("visible"); - lastFetchedUrl = ""; - return; - } - - // Si l'URL est identique à la dernière requêtée, on ne refait pas la requête - if (url === lastFetchedUrl) { - if (DEBUG) console.log("[DEBUG] Même URL que précédemment, pas de fetch."); - return; - } - - // Mémorise l'URL actuelle comme dernière URL traitée - lastFetchedUrl = url; - - try { - if (DEBUG) console.log("[DEBUG] Envoi requête POST vers /info avec URL:", url); - - // Envoi de la requête POST pour récupérer les infos de la vidéo - const res = await fetch("/info", { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: new URLSearchParams({ url }), - }); - - if (DEBUG) console.log("[DEBUG] Réponse status:", res.status); - - // Si la réponse HTTP est une erreur, on affiche un message - if (!res.ok) { - console.error(`[ERROR] Réponse HTTP ${res.status}: ${res.statusText}`); - infoDiv.innerHTML = "❌ Impossible de récupérer les infos (erreur serveur)."; - infoDiv.classList.remove("visible"); - return; - } - - // Parse la réponse JSON contenant les infos vidéo - const data = await res.json(); - if (DEBUG) console.log("[DEBUG] Données reçues:", data); - - // Vérification simple que les données attendues sont présentes - if (!data || !data.title || !data.thumbnail) { - console.warn("[WARNING] Données incomplètes", data); - infoDiv.innerHTML = "❌ Données incomplètes reçues."; - infoDiv.classList.remove("visible"); - return; - } - - // Formatage de la durée en minutes et secondes - const minutes = Math.floor(data.duration / 60); - const seconds = data.duration % 60; - const durationStr = `${minutes}m ${seconds.toString().padStart(2, "0")}s`; - - // Formatage de la date d'upload au format JJ/MM/AAAA - const date = data.upload_date || ""; - const readableDate = date - ? `${date.slice(6, 8)}/${date.slice(4, 6)}/${date.slice(0, 4)}` - : "Inconnue"; - - // Conversion taille approximative en Mo - const sizeMB = data.filesize_approx - ? (data.filesize_approx / (1024 * 1024)).toFixed(2) + " Mo" - : "Inconnue"; - - // Gestion des catégories, affichage par défaut si absentes - const categories = data.categories - ? data.categories.join(", ") - : "Non spécifiées"; - - // Construction du HTML pour afficher les informations de la vidéo - infoDiv.innerHTML = ` -

${data.title}

- Thumbnail -
    -
  • Durée : ${durationStr}
  • -
  • Uploader : ${data.uploader || "Inconnu"}
  • -
  • Date d’upload : ${readableDate}
  • -
  • Vues : ${data.view_count?.toLocaleString() || "?"}
  • -
  • Likes : ${data.like_count?.toLocaleString() || "?"}
  • -
  • URL : ${data.webpage_url}
  • -
  • Channel : ${data.channel_url}
  • -
  • Taille estimée : ${sizeMB}
  • -
  • Catégories : ${categories}
  • -
- `; - - // Affiche la div contenant les infos - infoDiv.classList.add("visible"); - - if (DEBUG) console.log("[DEBUG] Info affichée avec succès."); - } catch (e) { - // En cas d'erreur réseau ou JSON, on affiche un message d'erreur - console.error("[CRITICAL] Erreur lors de la récupération:", e); - infoDiv.innerHTML = "❌ Erreur réseau ou JSON."; - infoDiv.classList.remove("visible"); - } - }); -}); diff --git a/OLD CODE/public/styles/layout/container.css b/OLD CODE/public/styles/layout/container.css deleted file mode 100644 index bcf2d87..0000000 --- a/OLD CODE/public/styles/layout/container.css +++ /dev/null @@ -1,12 +0,0 @@ -.container, .infos-container { - position: relative; - top: 0; - left: 0; - right: 0; - /* height: 10000px; */ - padding: 1em; - z-index: 1000; - display: flex; - flex-direction: column; - align-items: center; -} diff --git a/OLD CODE/public/styles/layout/ctr-infos.css b/OLD CODE/public/styles/layout/ctr-infos.css deleted file mode 100644 index 1eb8e74..0000000 --- a/OLD CODE/public/styles/layout/ctr-infos.css +++ /dev/null @@ -1,14 +0,0 @@ -#downloadStatus { - - margin-top: 1em; - min-height: 1.5em; - font-weight: bold; - text-align: center; - color: var(--download-status-color); -} - -label { - display: flex; - align-items: center; - font-size: 0.9em; -} \ No newline at end of file diff --git a/OLD CODE/public/styles/theme/cyberpunk-theme.css b/OLD CODE/public/styles/theme/cyberpunk-theme.css deleted file mode 100644 index 0cecee6..0000000 --- a/OLD CODE/public/styles/theme/cyberpunk-theme.css +++ /dev/null @@ -1,45 +0,0 @@ -body.cyberpunk { - /* Couleurs générales */ - --background-color: #0d0d0d; /* noir profond */ - --default-text-color: #e0e0e0; /* texte gris clair */ - --subtitle-color: #ffea00; /* turquoise néon pour les sous-titres */ - --mp3-text-color: #ffea00; /* jaune vif pour la checkbox */ - - /* Formulaire */ - --form-bg-color: #141414; /* fond formulaire sombre */ - --form-input-bg-color: #1a1a1a; /* inputs sombres */ - --form-input-border-color: #333333; /* bordure sobre */ - --form-input-border-focus-color: #ffea00; /* focus jaune néon */ - --form-input-text-color: #e0e0e0; /* texte clair */ - --form-input-placeholder-color: #777777; /* placeholder plus doux */ - --form-button-bg-color: #ffea00; /* bouton jaune */ - --form-button-text-color: #0d0d0d; /* texte bouton noir */ - --form-button-bg-hover-color: #d4c000; /* hover jaune plus foncé */ - - /* Checkbox */ - --checkbox-bg-default: #333333; /* fond checkbox sobre */ - --checkbox-bg-checked: #ffea00; /* coché en jaune vif */ - --checkbox-checkmark-border-color: #0d0d0d; /* checkmark noir */ - --checkbox-pulse-color: rgba(255, 234, 0, 0.5); /* pulse néon jaune */ - - /* Download status */ - --download-status-color: #fffbd1; /* turquoise néon */ - - /* Video Info Box */ - --infos-box-color: #1a1a1a; /* fond infos */ - --video-info-text-color: #e0e0e0; /* texte clair */ - --video-info-heading-color: #ffea00; /* titres turquoise néon */ - --video-info-list-color: #cccccc; /* texte liste gris clair */ - --video-info-list-strong-color: #ffffff; /* texte important blanc */ - --video-info-link-color: #ffea00; /* liens jaune néon */ - --video-info-link-hover-color: #d4c000; /* liens hover plus foncé */ -} - -body.cyberpunk { - background-image: url('../../assets/images/Cyberpunk.webp'); - background-size: cover; - background-position: center; - background-repeat: no-repeat; - background-attachment: fixed; -} - diff --git a/OLD CODE/public/styles/theme/fanatic-theme.css b/OLD CODE/public/styles/theme/fanatic-theme.css deleted file mode 100644 index 8553866..0000000 --- a/OLD CODE/public/styles/theme/fanatic-theme.css +++ /dev/null @@ -1,36 +0,0 @@ -body.fanatic{ - /* Couleurs générales */ - --background-color: #121212; /* fond bleu nuit/noir */ - --default-text-color: #eee; /* texte clair principal */ - --subtitle-color: #FF5900; /* sous-titres, accent orange vif */ - --mp3-text-color: #ffffff; /* texte checkbox inline */ - - /* Formulaire */ - --form-bg-color: #1e1e1e; /* fond formulaire sombre */ - --form-input-bg-color: #2b2b2b; /* fond inputs sombres */ - --form-input-border-color: #333333; /* bordure inputs */ - --form-input-border-focus-color: #FF5900; /* bordure focus orange */ - --form-input-text-color: #eeeeee; /* texte inputs clair */ - --form-input-placeholder-color: #666666; /* placeholder gris foncé */ - --form-button-bg-color: #FF5900; /* bouton orange */ - --form-button-text-color: #121212; /* texte bouton sombre */ - --form-button-bg-hover-color: #e65500; /* bouton hover orange foncé */ - - /* Checkbox */ - --checkbox-bg-default: #555555; /* gris moyen */ - --checkbox-bg-checked: #ff6600; /* orange vif */ - --checkbox-checkmark-border-color: #eee; /* blanc cassé */ - --checkbox-pulse-color: rgba(255, 102, 0, 0.5); /* pulse orange translucide */ - - /* Download status */ - --download-status-color: #FF5900; /* orange */ - - /* Video Info Box */ - --infos-box-color: #222222; /* fond infos sombre */ - --video-info-text-color: #eee; /* texte clair */ - --video-info-heading-color: #FF5900; /* titres orange */ - --video-info-list-color: #ccc; /* texte liste gris clair */ - --video-info-list-strong-color: #fff; /* texte important blanc */ - --video-info-link-color: #FF5900; /* liens orange */ - --video-info-link-hover-color: #e65500; /* liens hover orange foncé */ -} diff --git a/OLD CODE/public/styles/theme/light-theme.css b/OLD CODE/public/styles/theme/light-theme.css deleted file mode 100644 index cd562b7..0000000 --- a/OLD CODE/public/styles/theme/light-theme.css +++ /dev/null @@ -1,36 +0,0 @@ -body.light { - /* Couleurs générales */ - --background-color: #dadada; /* fond clair */ - --default-text-color: #222222; /* texte sombre */ - --subtitle-color: #007bff; /* bleu accent */ - --mp3-text-color: #000000; /* texte checkbox */ - - /* Formulaire */ - --form-bg-color: #ffffff; /* fond formulaire blanc */ - --form-input-bg-color: #fafafa; /* fond inputs */ - --form-input-border-color: #cccccc; /* bordure inputs */ - --form-input-border-focus-color: #007bff; /* focus bleu */ - --form-input-text-color: #333333; /* texte inputs */ - --form-input-placeholder-color: #888888; /* placeholder */ - --form-button-bg-color: #007bff; /* bouton bleu */ - --form-button-text-color: #ffffff; /* texte bouton */ - --form-button-bg-hover-color: #0056b3; /* hover bleu foncé */ - - /* Checkbox */ - --checkbox-bg-default: #cccccc; - --checkbox-bg-checked: #007bff; - --checkbox-checkmark-border-color: #ffffff; - --checkbox-pulse-color: rgba(0, 123, 255, 0.4); - - /* Download status */ - --download-status-color: #007bff; - - /* Video Info Box */ - --infos-box-color: #ffffff; - --video-info-text-color: #222222; - --video-info-heading-color: #007bff; - --video-info-list-color: #444444; - --video-info-list-strong-color: #000000; - --video-info-link-color: #007bff; - --video-info-link-hover-color: #0056b3; -} diff --git a/OLD CODE/public/styles/theme/nf-theme.css b/OLD CODE/public/styles/theme/nf-theme.css deleted file mode 100644 index 198b6a7..0000000 --- a/OLD CODE/public/styles/theme/nf-theme.css +++ /dev/null @@ -1,48 +0,0 @@ -body.nf { - /* Couleurs générales */ - --background-color: #1a1a1a; /* fond sombre neutre */ - --default-text-color: #e6dfd5; /* texte beige clair */ - --subtitle-color: #f5f0e6; /* beige très clair (titres) */ - --mp3-text-color: #d8cfc2; /* texte checkbox */ - - /* Formulaire */ - --form-bg-color: #2a2a2a; /* fond formulaire gris sombre */ - --form-input-bg-color: #222222; /* fond inputs sombre */ - --form-input-border-color: #4a4036; /* bordure beige/brun */ - --form-input-border-focus-color: #cbb89d; /* beige doré en focus */ - --form-input-text-color: #f0e8dd; /* texte clair/beige */ - --form-input-placeholder-color: #9a8f80; /* placeholder beige/gris */ - --form-button-bg-color: #cbb89d; /* bouton beige doré */ - --form-button-text-color: #1a1a1a; /* texte sombre */ - --form-button-bg-hover-color: #b7a588; /* hover beige plus foncé */ - - /* Checkbox */ - --checkbox-bg-default: #4a4036; /* brun/gris */ - --checkbox-bg-checked: #cbb89d; /* beige doré */ - --checkbox-checkmark-border-color: #1a1a1a; /* noir profond */ - --checkbox-pulse-color: rgba(203,184,157,0.3); /* halo beige doux */ - - /* Download status */ - --download-status-color: #cbb89d; /* beige doré */ - - /* Video Info Box */ - --infos-box-color: #2a2a2a; /* fond sombre */ - --video-info-text-color: #e6dfd5; /* texte beige clair */ - --video-info-heading-color: #f5f0e6; /* titres beige clair */ - --video-info-list-color: #d8cfc2; /* texte liste */ - --video-info-list-strong-color: #ffffff; /* texte fort blanc cassé */ - --video-info-link-color: #cbb89d; /* liens beige doré */ - --video-info-link-hover-color: #b7a588; /* hover beige plus foncé */ -} - -/* Fond inspiré de ton image beige */ -body.nf { - background-image: url('../../assets/images/NF.jpg'); /* mets ton image ici */ - background-size: cover; - background-position: 70% center; - background-attachment: fixed; -} - -body.nf .download-path { - color: var(--default-text-color); -} diff --git a/OLD CODE/public/styles/theme/spicy-theme.css b/OLD CODE/public/styles/theme/spicy-theme.css deleted file mode 100644 index 948f133..0000000 --- a/OLD CODE/public/styles/theme/spicy-theme.css +++ /dev/null @@ -1,51 +0,0 @@ -/* - - - -*/ - -body.spicy { - /* Couleurs générales */ - --background-color: #121212; /* fond noir profond */ - --default-text-color: #581010; /* texte clair */ - --subtitle-color: #3d0909; /* accent rouge piment */ - --mp3-text-color: #ffffff; /* texte checkbox inline */ - - /* Formulaire */ - --form-bg-color: #1a1a1a; /* fond formulaire gris très foncé */ - --form-input-bg-color: #2b2b2b; /* fond inputs gris foncé */ - --form-input-border-color: #444444; /* bordure gris */ - --form-input-border-focus-color: #ff1a1a; /* focus rouge piment */ - --form-input-text-color: #eeeeee; /* texte inputs */ - --form-input-placeholder-color: #888888; /* placeholder gris moyen */ - --form-button-bg-color: #ff1a1a; /* bouton rouge vif */ - --form-button-text-color: #ffffff; /* texte bouton blanc */ - --form-button-bg-hover-color: #cc0000; /* hover rouge foncé */ - - /* Checkbox */ - --checkbox-bg-default: #555555; /* gris moyen */ - --checkbox-bg-checked: #ff1a1a; /* rouge piment */ - --checkbox-checkmark-border-color: #f5f5f5; /* blanc cassé */ - --checkbox-pulse-color: rgba(255, 26, 26, 0.5); /* pulse rouge translucide */ - - /* Download status */ - --download-status-color: #ffffff; /* rouge vif */ - - /* Video Info Box */ - --infos-box-color: #1a1a1a; /* fond infos gris foncé */ - --video-info-text-color: #f5f5f5; /* texte clair */ - --video-info-heading-color: #ff1a1a; /* titres rouges */ - --video-info-list-color: #cccccc; /* texte liste gris clair */ - --video-info-list-strong-color: #ffffff; /* texte important blanc */ - --video-info-link-color: #ff1a1a; /* liens rouges */ - --video-info-link-hover-color: #cc0000; /* liens hover rouge foncé */ -} - -/* Optionnel: background piments */ -body.spicy { - background-image: url('../../assets/images/spicy.webp'); - background-size:contain; - background-position: center; - background-attachment: fixed; - background-repeat: no-repeat; -} \ No newline at end of file diff --git a/OLD CODE/public/styles/theme/vilbrequin-theme.css b/OLD CODE/public/styles/theme/vilbrequin-theme.css deleted file mode 100644 index ec9df97..0000000 --- a/OLD CODE/public/styles/theme/vilbrequin-theme.css +++ /dev/null @@ -1,48 +0,0 @@ -body.vilbrequin { - /* Couleurs générales */ - --background-color: #0a1f3d; /* bleu Vilbrequin profond */ - --default-text-color: #535353; /* texte clair */ - --subtitle-color: #001696; /* bleu clair accent (turbo vibes) */ - --mp3-text-color: #ffffff; /* texte checkbox inline */ - - /* Formulaire */ - --form-bg-color: #12284f; /* fond formulaire bleu foncé */ - --form-input-bg-color: #1c3760; /* fond inputs bleu + clair */ - --form-input-border-color: #2a4b80; /* bordure bleue */ - --form-input-border-focus-color: #00bfff; /* focus bleu clair */ - --form-input-text-color: #f0f0f0; /* texte inputs clair */ - --form-input-placeholder-color: #b0c4de; /* placeholder gris bleuté */ - --form-button-bg-color: #ffd700; /* bouton jaune turbo */ - --form-button-text-color: #0a1f3d; /* texte bouton sombre */ - --form-button-bg-hover-color: #e6c200; /* hover jaune foncé */ - - /* Checkbox */ - --checkbox-bg-default: #2a4b80; /* bleu moyen */ - --checkbox-bg-checked: #00bfff; /* bleu clair accent */ - --checkbox-checkmark-border-color: #f0f0f0; /* blanc cassé */ - --checkbox-pulse-color: rgba(0,191,255,0.5); /* pulse bleu clair translucide */ - - /* Download status */ - --download-status-color: #ffd700; /* jaune turbo */ - - /* Video Info Box */ - --infos-box-color: #12284f; /* fond infos bleu foncé */ - --video-info-text-color: #f0f0f0; /* texte clair */ - --video-info-heading-color: #00bfff; /* titres bleu clair */ - --video-info-list-color: #dcdcdc; /* texte liste gris clair */ - --video-info-list-strong-color: #ffffff; /* texte important blanc */ - --video-info-link-color: #00bfff; /* liens bleu clair */ - --video-info-link-hover-color: #009acd; /* liens hover bleu plus foncé */ -} - -/* Optionnel: motif turbo ou jantes */ -body.vilbrequin { - background-image: url('../../assets/images/Vilbrequin.webp'); /* adapte ton image */ - background-size: cover; - background-position: center; - background-attachment: fixed; -} - -body.vilbrequin .download-path{ - color: white; -} \ No newline at end of file diff --git a/OLD CODE/public/styles/variables.css b/OLD CODE/public/styles/variables.css deleted file mode 100644 index 7061e41..0000000 --- a/OLD CODE/public/styles/variables.css +++ /dev/null @@ -1,36 +0,0 @@ -:root { - /* Couleurs générales */ - --background-color: #001224; /* body background bleu nuit */ - --default-text-color: #eee; /* texte clair principal */ - --subtitle-color: #007bff; /* sous-titres, header p */ - --mp3-text-color: #000000; /* couleur texte checkbox inline */ - - /* Formulaire */ - --form-bg-color: #ffffff; /* fond du formulaire */ - --form-input-bg-color: #f9f9f9; /* fond inputs */ - --form-input-border-color: #cccccc; /* bordure inputs */ - --form-input-border-focus-color: #0056b3; /* bordure focus */ - --form-input-text-color: #333333; /* texte inputs */ - --form-input-placeholder-color: #aaaaaa; /* placeholder */ - --form-button-bg-color: #007bff; /* bouton bg */ - --form-button-text-color: #ffffff; /* texte bouton */ - --form-button-bg-hover-color: #0056b3; /* bouton hover */ - - /* Checkbox */ - --checkbox-bg-default: #cccccc; - --checkbox-bg-checked: #3B82F6; - --checkbox-checkmark-border-color: #E0E0E2; - --checkbox-pulse-color: rgba(59, 130, 246, 0.5); - - /* Download status */ - --download-status-color: #007bff; - - /* Video Info Box */ - --infos-box-color: #f0f0f0; - --video-info-text-color: #222222; - --video-info-heading-color: #007bff; - --video-info-list-color: #444444; - --video-info-list-strong-color: #000000; - --video-info-link-color: #007bff; - --video-info-link-hover-color: #0056b3; -} diff --git a/OLD CODE/ressources/ffmpeg.exe b/OLD CODE/ressources/ffmpeg.exe deleted file mode 100644 index 588d161..0000000 Binary files a/OLD CODE/ressources/ffmpeg.exe and /dev/null differ diff --git a/OLD CODE/ressources/ffprobe.exe b/OLD CODE/ressources/ffprobe.exe deleted file mode 100644 index 5d52e65..0000000 Binary files a/OLD CODE/ressources/ffprobe.exe and /dev/null differ diff --git a/OLD CODE/server/logger.js b/OLD CODE/server/logger.js deleted file mode 100644 index c795e39..0000000 --- a/OLD CODE/server/logger.js +++ /dev/null @@ -1,110 +0,0 @@ -/* - This file is part of Freedom Loader. - - Copyright (C) 2025 MasterAcnolo - - Freedom Loader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License. - - Freedom Loader 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 GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -const { createLogger, format, transports } = require("winston"); -const DailyRotateFile = require("winston-daily-rotate-file"); -const fs = require("fs"); // Pour vérifier/créer le dossier de logs -const path = require("path"); // Pour gérer proprement les chemins -const os = require("os"); // Pour gérer les chemins aussi - -// Définition du dossier où seront stockés les logs -// const logDir = path.join(__dirname, "../logs"); Désactivé parce que cela requiert d'exec en admin mode. Donc pas pratique pour les gens - -let logDir; -if (process.platform === "win32") { - logDir = path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs"); -} else { - logDir = path.join(os.homedir(), ".freedomloader", "logs"); -} - -// Création du dossier logs s’il n’existe pas encore -//if (!fs.existsSync(logDir)) fs.mkdirSync(logDir); OBSOLETE - -fs.mkdirSync(logDir, { recursive: true }); // Evite les erreurs - - -// Fonction utilitaire pour générer une ligne indiquant le début d’une session de logs -function getSessionStartLine() { - const now = new Date().toISOString(); - return `--- Démarrage de la session : ${now} ---`; -} - -// Fonction utilitaire pour générer une ligne indiquant la fin d’une session de logs -function getSessionEndLine() { - const now = new Date().toISOString(); - return `--- Fin de la session : ${now} ---`; -} - -// Configuration principale de Winston -// - Niveau minimum d’écriture : info (enregistre info, warn, error, etc.) -// - Formatage des logs avec timestamp lisible et format personnalisé -// - Transports : écriture dans fichier journalier + console avec couleurs -const logger = createLogger({ - level: "info", - format: format.combine( - format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), - format.printf(({ timestamp, level, message }) => - `${timestamp} | ${level.toUpperCase()} | ${message}` - ) - ), - transports: [ - // Rotation quotidienne des fichiers de logs - new DailyRotateFile({ - dirname: logDir, // dossier cible pour les logs - filename: "LOGS-%DATE%.log", // nom du fichier par date - datePattern: "YYYY-MM-DD", // pattern de date dans le nom - zippedArchive: false, // ne pas compresser les archives - maxFiles: "7d", // conserve 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" }, // mode ajout à la fin (append) - }), - // Affichage des logs en console avec coloration et format simple - new transports.Console({ - format: format.combine( - format.colorize(), - format.timestamp({ format: "HH:mm:ss" }), - format.printf(({ timestamp, level, message }) => - `${timestamp} | ${level} | ${message}` - ) - ), - }), - ], -}); - -// Helper pour marquer clairement le début de session dans les logs -function logSessionStart() { - logger.info(getSessionStartLine()); -} - -// Helper pour marquer la fin de session dans les logs -function logSessionEnd() { - logger.info(getSessionEndLine()); -} - -// Export du logger principal et des helpers pour usage dans d’autres modules -module.exports = { - logger, - logSessionStart, - logSessionEnd, - logDir, -}; diff --git a/OLD CODE/server/routes/download.js b/OLD CODE/server/routes/download.js deleted file mode 100644 index 04f93d9..0000000 --- a/OLD CODE/server/routes/download.js +++ /dev/null @@ -1,205 +0,0 @@ -/* - This file is part of Freedom Loader. - - Copyright (C) 2025 MasterAcnolo - - Freedom Loader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License. - - Freedom Loader 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 GNU General Public License for more details. -*/ - -const express = require("express"); -const router = express.Router(); -const { execFile } = require("child_process"); -const path = require("path"); -const fs = require("fs"); -const { Notification } = require("electron"); -const logger = require("../logger").logger; - -// Path vers le fichier exécutable yt-dlp (outil tiers pour le téléchargement) -// const ytDlpPath = path.join(process.resourcesPath, '../../yt-dlp.exe'); -const ytDlpPath = path.join(process.resourcesPath, 'yt-dlp.exe'); - -execFile(ytDlpPath, ["-U"], (err, stdout, stderr) => { - if (err) { - logger.error("Erreur update yt-dlp:", err); - return; - } - logger.info(`Update yt-dlp : ${stdout}`); -}); - -router.post("/", (req, res) => { - try { - // Récupération des options envoyées par le frontend via le formulaire - // On vérifie la présence de l'URL, le choix audioOnly, et la qualité demandée - const options = { - url: req.body.url, - audioOnly: req.body.audioOnly === "1", - quality: req.body.quality || "best", - }; - - // Validation simple : si l'URL est manquante, on rejette la requête - if (!options.url) { - logger.warn("Requête POST /download sans URL"); - return res.status(400).send("❌ URL manquante !"); - } - - // Récupération du chemin de sauvegarde personnalisé envoyé depuis Electron, - // ou on utilise un chemin par défaut stocké dans app.locals - let requestedOutputFolder = req.body.savePath || req.app.locals.outputFolder; - - // On normalise le chemin pour éviter des erreurs liées aux séparateurs ou chemins relatifs - requestedOutputFolder = path.normalize(requestedOutputFolder); - - /* - Sécurité basique : On refuse certains chemins sensibles ou suspects - (ex: System32, /etc, Windows) pour éviter que l'application n'écrive dans des dossiers système. - On vérifie aussi la longueur minimale du chemin pour éviter des valeurs vides ou invalides. - */ - if ( - !requestedOutputFolder || - requestedOutputFolder.length < 3 || - requestedOutputFolder.includes("System32") || - requestedOutputFolder.includes("/etc") || - requestedOutputFolder.includes("\\Windows") - ) { - logger.warn(`Chemin potentiellement dangereux refusé : ${requestedOutputFolder}`); - return res.status(400).send("❌ Chemin de sauvegarde non autorisé."); - } - - // Vérifie que le dossier de sortie existe, sinon le crée récursivement - if (!fs.existsSync(requestedOutputFolder)) { - fs.mkdirSync(requestedOutputFolder, { recursive: true }); - logger.info(`Dossier de sortie créé : ${requestedOutputFolder}`); - } - - // Construction du template de sortie pour yt-dlp (nom de fichier basé sur le titre) - const outputTemplate = path.join(requestedOutputFolder, "%(title)s.%(ext)s"); - - // Construction du tableau d'arguments à passer à yt-dlp - // --no-continue pour forcer un téléchargement propre - // --restrict-filenames pour éviter les caractères problématiques dans les noms - const args = [ - "--no-continue", // pas de reprise, c'est un choix que j'ai fait voila - //"--restrict-filenames", // noms de fichiers sans caractères spéciaux (Cyrilique, accents, etc.) - "--no-overwrites", // évite d'écraser un fichier existant - "--embed-thumbnail", // ajoute la pochette - "--add-metadata", // ajoute les tags (titre, artiste, etc.) - "--concurrent-fragments", "8",// accélère le téléchargement - "--retries", "10", // réessaie jusqu'à 10 fois en cas d'erreur - "--fragment-retries", "10" , // réessaie aussi 10 fois chaque fragment - "--ffmpeg-location", path.join(process.resourcesPath, "ffmpeg.exe") - ]; - - // Si l'option audioOnly est activée, on ajoute les flags pour extraction audio en mp3 - if (options.audioOnly) { - args.push( - "-f", - "bestaudio", - "--extract-audio", - "--audio-format", - "mp3", - ); - } - - - // Correspondance entre la qualité choisie et le format à demander à yt-dlp - const qualityMap = { - best: "bestvideo+bestaudio/best", - medium: "bestvideo[height<=720]+bestaudio/best[height<=720]", - worst: "worstvideo+worstaudio/worst", - 1080: "bestvideo[height<=1080]+bestaudio/best[height<=1080]", - 720: "bestvideo[height<=720]+bestaudio/best[height<=720]", - 480: "bestvideo[height<=480]+bestaudio/best[height<=480]", - }; - - // Sélection du format basé sur la qualité, par défaut 'best' si option invalide - const format = qualityMap[options.quality] || "best"; - args.push("-f", format); - - // Ajout du template de sortie et de l'URL à télécharger - args.push("-o", outputTemplate); - args.push(options.url); - - // Log de la commande complète pour traçabilité et débogage - logger.info(`Téléchargement demandé : url=${options.url}, audioOnly=${options.audioOnly}, quality=${options.quality}`); - logger.info(`Commande yt-dlp : ${ytDlpPath} ${args.join(" ")}`); - - // Lancement du processus yt-dlp avec les arguments définis - const child = execFile(ytDlpPath, args); - - /* - Gestion des sorties standards (stdout) du processus, - on log chaque ligne pour suivre la progression ou informations diverses - */ - child.stdout.on("data", (data) => { - data.toString().split("\n").forEach(line => { - if (line.trim()) logger.info(`[yt-dlp stdout] ${line.trim()}`); - }); - }); - - /* - Gestion des erreurs (stderr) du processus, - on log chaque ligne d'erreur pour diagnostic - */ - child.stderr.on("data", (data) => { - data.toString().split("\n").forEach(line => { - if (line.trim()) logger.error(`[yt-dlp stderr] ${line.trim()}`); - }); - }); - - /* - En cas d'erreur lors du lancement du processus yt-dlp, - on log l'erreur et on répond au client avec un statut 500 - */ - child.on("error", (err) => { - logger.error(`Erreur lancement yt-dlp : ${err.message}`); - res.status(500).send(`❌ Erreur lors de l'exécution : ${err.message}`); - }); - - /* - Quand le processus se termine, on vérifie le code de sortie. - Code 0 = succès, on informe le client que le téléchargement est terminé. - Sinon, on envoie une erreur au client avec le code d'échec. - */ - child.on("close", (code) => { - logger.info(`yt-dlp terminé avec code de sortie : ${code}`); - if (code === 0) { - - const iconPath = path.join(process.resourcesPath, "confirm-icon.png"); // la sécurité pour toutes les machines - - console.log("Icon path pour la notif :", iconPath); - - const notif = new Notification({ - title: "Freedom Loader", - body: "Ton téléchargement est terminé, clique ici pour l'ouvrir.", - icon: iconPath, - }); - - notif.on("click", () => { - console.log("Notification cliquée !"); - //Pour pouvoir ouvrir le dossier de la vidéo - const { shell } = require("electron"); - shell.openPath(requestedOutputFolder); - }); - - notif.show(); - res.send("✅ Téléchargement terminé !"); - } else { - res.status(500).send(`❌ yt-dlp a échoué avec le code : ${code}`); - } - }); - - } catch (err) { - // Capture toute autre erreur serveur non prévue, log et réponse 500 - logger.error(`Erreur serveur dans /download : ${err.message}`); - res.status(500).send(`Erreur serveur : ${err.message}`); - } -}); - -module.exports = router; diff --git a/OLD CODE/server/routes/info.js b/OLD CODE/server/routes/info.js deleted file mode 100644 index a14fffb..0000000 --- a/OLD CODE/server/routes/info.js +++ /dev/null @@ -1,81 +0,0 @@ -/* - This file is part of Freedom Loader. - - Copyright (C) 2025 MasterAcnolo - - Freedom Loader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License. - - Freedom Loader 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 GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - 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 { logger } = require("../logger"); // On récupère ton logger Winston - -// Path absolu vers l'exécutable yt-dlp -const ytDlpPath = path.join(__dirname, '../../ressources/yt-dlp.exe'); - -// Vérification que yt-dlp.exe existe bien au lancement du module -if (!fs.existsSync(ytDlpPath)) { - logger.error(`❌ yt-dlp.exe introuvable à ${ytDlpPath}`); - throw new Error(`yt-dlp.exe introuvable à ${ytDlpPath}`); -} - -// Route POST pour récupérer les métadonnées d'une vidéo via yt-dlp -router.post("/", (req, res) => { - const url = req.body.url; - - // Validation basique : on refuse la requête sans URL - if (!url) { - logger.warn("Requête metadata sans URL"); - return res.status(400).send("❌ URL manquante"); - } - - logger.info(`Requête metadata reçue pour ${url}`); - - // Exécution de yt-dlp avec l'option --dump-json pour récupérer les infos vidéo - execFile( - ytDlpPath, - ["--dump-json", url], - { timeout: 10_000 }, // Timeout fixé à 10 secondes pour éviter blocage - (error, stdout, stderr) => { - if (error) { - // En cas d'erreur, on log l'erreur et le stderr, puis on renvoie un code 500 - logger.error(`Erreur exécution yt-dlp: ${error.message}`); - logger.debug(`stderr: ${stderr}`); - return res.status(500).send("❌ Impossible de récupérer les infos."); - } - - try { - // yt-dlp peut retourner plusieurs JSON séparés par des sauts de ligne - // On split et parse chacun proprement - const infos = stdout - .trim() - .split("\n") - .map(line => JSON.parse(line)); - - logger.info(`Infos récupérées pour ${url} (${infos.length} élément(s))`); - - // Si on a un seul élément, on renvoie directement l'objet JSON, sinon un tableau - res.json(infos.length === 1 ? infos[0] : infos); - } catch (e) { - // Si parsing JSON échoue, on log et renvoie une erreur 500 - logger.error(`Erreur parsing JSON: ${e.message}`); - return res.status(500).send("❌ JSON illisible."); - } - } - ); -}); - -module.exports = router; diff --git a/OLD CODE/server/server.js b/OLD CODE/server/server.js deleted file mode 100644 index daa9cce..0000000 --- a/OLD CODE/server/server.js +++ /dev/null @@ -1,106 +0,0 @@ -/* - This file is part of Freedom Loader. - - Copyright (C) 2025 MasterAcnolo - - Freedom Loader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License. - - Freedom Loader 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 GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -const express = require("express"); // Framework web pour créer l’API et servir les fichiers -const { exec } = require("child_process"); // Permet d’exécuter des commandes systèmes (pas utilisé directement ici) -const fs = require("fs"); // Module système pour gérer fichiers et dossiers -const path = require("path"); // Gestion propre des chemins de fichiers et dossiers -const debug = require("debug")("freedom-loader:server"); -// Module de debug coloré en console, pratique pour dev - -const { logger, logSessionStart, logSessionEnd } = require("./logger"); -// Import du logger Winston et helpers pour marquer début/fin de session - -const app = express(); // Création de l’instance Express, notre serveur web - -// Définition du dossier par défaut où enregistrer les téléchargements -// On prend le dossier Téléchargements de l’utilisateur Windows (USERPROFILE) -const downloadsPath = path.join(process.env.USERPROFILE, "Downloads"); -const outputFolder = path.join(downloadsPath, "Freedom Loader"); - -// Création du dossier de sortie s’il n’existe pas déjà -if (!fs.existsSync(outputFolder)) { - try { - fs.mkdirSync(outputFolder, { recursive: true }); // création récursive au cas où - logger.info("Dossier Freedom Loader cree dans Telechargements."); - } catch (err) { - logger.error("Impossible de creer le dossier :", err); - process.exit(1); // Arrêt du programme si dossier non créé (critique) - } -} else { - logger.info("Dossier Freedom Loader deja existant."); -} - -// On rend ce dossier accessible globalement via app.locals pour l’utiliser dans les routes -app.locals.outputFolder = outputFolder; - -// Middleware pour parser le corps des requêtes POST en application/x-www-form-urlencoded -app.use(express.urlencoded({ extended: true })); - -// Définition du dossier contenant les fichiers statiques (frontend) -// Permet d’accéder au HTML, CSS, JS côté client -const staticPath = path.join(__dirname, "../public"); -debug("Serveur statique sur", staticPath); -app.use(express.static(staticPath)); - -// Import et enregistrement des routes API -// Ces routes gèrent les requêtes pour les infos vidéo et téléchargement -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 / qui sert la page principale index.html -app.get("/", (req, res) => { - debug("Requete GET / servie"); - res.sendFile(path.join(__dirname, "../public/index.html")); -}); - -// Fonction pour démarrer le serveur Express -// Retourne une Promise pour pouvoir await le démarrage dans d’autres modules -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); // Serveur prêt, on résout la promesse - }); - // Gestion des erreurs serveur lors du démarrage - serverInstance.on("error", (err) => { - logger.error("Erreur serveur Express :", err); - reject(err); // Rejet de la promesse en cas d’erreur critique - }); - }); -} - -// Gestion propre de la fermeture du process pour logger la fin de session -process.on("SIGINT", () => { // Capture Ctrl+C (interruption) - logSessionEnd(); - process.exit(); -}); -process.on("SIGTERM", () => { // Capture kill ou arrêt du process - logSessionEnd(); - process.exit(); -}); - -// Export de la fonction startServer pour permettre son appel depuis d’autres fichiers -module.exports = { startServer }; - -const { startRPC } = require("./discordRPC"); -startRPC(); diff --git a/OLD CODE/build/apercu1.2.4.png b/build/apercu1.2.4.png similarity index 100% rename from OLD CODE/build/apercu1.2.4.png rename to build/apercu1.2.4.png diff --git a/OLD CODE/build/confirm-icon.png b/build/confirm-icon.png similarity index 100% rename from OLD CODE/build/confirm-icon.png rename to build/confirm-icon.png diff --git a/OLD CODE/build/example-developertools.png b/build/example-developertools.png similarity index 100% rename from OLD CODE/build/example-developertools.png rename to build/example-developertools.png diff --git a/OLD CODE/build/installer-icon.ico b/build/installer-icon.ico similarity index 100% rename from OLD CODE/build/installer-icon.ico rename to build/installer-icon.ico diff --git a/OLD CODE/build/uninstaller-icon.ico b/build/uninstaller-icon.ico similarity index 100% rename from OLD CODE/build/uninstaller-icon.ico rename to build/uninstaller-icon.ico diff --git a/OLD CODE/forge.config.js b/forge.config.js similarity index 58% rename from OLD CODE/forge.config.js rename to forge.config.js index fa4a113..dcbb74d 100644 --- a/OLD CODE/forge.config.js +++ b/forge.config.js @@ -2,35 +2,16 @@ const { FusesPlugin } = require('@electron-forge/plugin-fuses'); const { FuseV1Options, FuseVersion } = require('@electron/fuses'); module.exports = { - packagerConfig: { - asar: true, - }, + packagerConfig: { asar: true }, rebuildConfig: {}, makers: [ { name: '@electron-forge/maker-squirrel', config: {}, }, - { - name: '@electron-forge/maker-zip', - platforms: ['darwin'], - }, - { - name: '@electron-forge/maker-deb', - config: {}, - }, - { - name: '@electron-forge/maker-rpm', - config: {}, - }, ], plugins: [ - { - name: '@electron-forge/plugin-auto-unpack-natives', - config: {}, - }, - // Fuses are used to enable/disable various Electron functionality - // at package time, before code signing the application + { name: '@electron-forge/plugin-auto-unpack-natives', config: {} }, new FusesPlugin({ version: FuseVersion.V1, [FuseV1Options.RunAsNode]: false, diff --git a/main.js b/main.js new file mode 100644 index 0000000..fa1b9e9 --- /dev/null +++ b/main.js @@ -0,0 +1,124 @@ +/* + This file is part of Freedom Loader. + + Copyright (C) 2025 MasterAcnolo + + Freedom Loader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License. + + Freedom Loader 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 GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +const { app, BrowserWindow, ipcMain, dialog, Menu, shell } = require("electron"); +const path = require("path"); +const os = require("os"); +const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger"); + +app.disableHardwareAcceleration(); // safe sur GPU old / soucis Electron + +let mainWindow; +const logsFolderPath = logDir; +const defaultDownloadPath = path.join(os.homedir(), "Downloads", "Freedom Loader"); + +app.setAppUserModelId("com.masteracnolo.freedomloader"); // pour notifications Windows + +// ---------- FENÊTRE ---------- // +async function createWindow() { + if (mainWindow) { + logger.warn("La fenêtre existe déjà, pas de nouvelle création"); + return; + } + + mainWindow = new BrowserWindow({ + title: "Freedom Loader 1.3.0", + width: 750, + height: 800, + minWidth: 750, + minHeight: 800, + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + preload: path.join(__dirname, "preload.js"), + }, + }); + + try { + await mainWindow.loadURL(`http://localhost:8787`); + logger.info("Fenêtre chargée"); + } catch (err) { + logger.error("Erreur chargement fenêtre:", err); + } + + mainWindow.on("closed", () => { + logger.info("Fenêtre principale fermée"); + mainWindow = null; + }); +} + +// IPC +ipcMain.handle("select-download-folder", async () => { + try { + const result = await dialog.showOpenDialog({ properties: ["openDirectory"] }); + if (!result.canceled && result.filePaths.length > 0) { + logger.info(`Dossier sélectionné : ${result.filePaths[0]}`); + return result.filePaths[0]; + } + return null; + } catch (err) { + logger.error(`Erreur lors de la sélection de dossier : ${err.message}`); + return null; + } +}); + +ipcMain.handle("get-default-download-path", () => defaultDownloadPath); + +function setupMenu() { + const menuTemplate = [ + { + label: "Logs", + submenu: [ + { + label: "Ouvrir les logs", + click: () => shell.openPath(logsFolderPath), + }, + ], + }, + ]; + + const defaultMenu = Menu.getApplicationMenu(); + const mergedTemplate = defaultMenu + ? [...defaultMenu.items.map(item => item), ...menuTemplate] + : menuTemplate; + + Menu.setApplicationMenu(Menu.buildFromTemplate(mergedTemplate)); +} + +app.whenReady().then(async () => { + logSessionStart(); + logger.info("App prête, démarrage du serveur Express..."); + + const expressServer = require("./server/server.js"); + try { + await expressServer.startServer(); + logger.info("Serveur Express démarré"); + await createWindow(); + setupMenu(); + } catch (err) { + logger.error("Erreur serveur ou fenêtre :", err); + app.quit(); + } +}); + +app.on("window-all-closed", () => { + logger.info("Toutes fenêtres fermées, quitte l'app"); + if (process.platform !== "darwin") app.quit(); +}); + +app.on("before-quit", () => logSessionEnd()); diff --git a/OLD CODE/preload.js b/preload.js similarity index 100% rename from OLD CODE/preload.js rename to preload.js diff --git a/OLD CODE/public/assets/images/Chirac.png b/public/assets/images/Chirac.png similarity index 100% rename from OLD CODE/public/assets/images/Chirac.png rename to public/assets/images/Chirac.png diff --git a/OLD CODE/public/assets/images/Cyberpunk.webp b/public/assets/images/Cyberpunk.webp similarity index 100% rename from OLD CODE/public/assets/images/Cyberpunk.webp rename to public/assets/images/Cyberpunk.webp diff --git a/public/assets/images/Drift.jpg b/public/assets/images/Drift.jpg new file mode 100644 index 0000000..9fe9e29 Binary files /dev/null and b/public/assets/images/Drift.jpg differ diff --git a/OLD CODE/public/assets/images/NF.jpg b/public/assets/images/NF.jpg similarity index 100% rename from OLD CODE/public/assets/images/NF.jpg rename to public/assets/images/NF.jpg diff --git a/OLD CODE/public/assets/images/Vilbrequin.webp b/public/assets/images/Vilbrequin.webp similarity index 100% rename from OLD CODE/public/assets/images/Vilbrequin.webp rename to public/assets/images/Vilbrequin.webp diff --git a/OLD CODE/public/assets/images/spicy.webp b/public/assets/images/spicy.webp similarity index 100% rename from OLD CODE/public/assets/images/spicy.webp rename to public/assets/images/spicy.webp diff --git a/OLD CODE/public/index.html b/public/index.html similarity index 96% rename from OLD CODE/public/index.html rename to public/index.html index ab90727..248ba02 100644 --- a/OLD CODE/public/index.html +++ b/public/index.html @@ -14,8 +14,6 @@ - - diff --git a/OLD CODE/public/script/custompath.js b/public/script/custompath.js similarity index 100% rename from OLD CODE/public/script/custompath.js rename to public/script/custompath.js diff --git a/public/script/customthemes.js b/public/script/customthemes.js new file mode 100644 index 0000000..e2228a9 --- /dev/null +++ b/public/script/customthemes.js @@ -0,0 +1,92 @@ +/* + This file is part of Freedom Loader. + + Copyright (C) 2025 MasterAcnolo + + Freedom Loader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License. + + Freedom Loader 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 GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Définition des thèmes disponibles +// Chaque thème a un label (affiché dans le select) et un subtitle (texte sous le titre) +const themes = { + default: { label: "Default", subtitle: "Because why not?" }, + dark: { label: "Sombre", subtitle: "Darkness is my ally" }, + light: { label: "Clair", subtitle: "Qui aime ce thème ?" }, + neon: { label: "Néon", subtitle: "How was your day ?"}, + nf: { label: "NF", subtitle: "You call it music, i call it my Therapist" }, + drift: { label: "Drift", subtitle: "Si la route t'appelle, contre appel" }, + fanatic: { label: "Fanatic", subtitle: "Always Fnatic !" }, + cyberpunk: { label: "Cyberpunk", subtitle: "Wake up, choom. We’ve got a city to burn." }, + chirac: { label: "Chirac", subtitle: "J'aime les pommes" }, + spicy: { label: "Spicy", subtitle: "The Spiciest One" }, + vilbrequin: { label: "Vilbrequin", subtitle: "Rend l'argent" } +}; + + +const themeSelect = document.getElementById("themeSelect"); + +// Remplir le select avec les options à partir du dictionnaire +function populateThemeSelect() { + for (const [themeKey, themeInfo] of Object.entries(themes)) { + const option = document.createElement("option"); + option.value = themeKey; + option.textContent = themeInfo.label; + themeSelect.appendChild(option); + } +} + +// Appliquer un thème sur le body et mettre à jour le subtitle +function applyTheme(themeKey) { + // Supprimer les classes de tous les thèmes + document.body.classList.remove(...Object.keys(themes)); + // Ajouter la classe correspondant au thème sélectionné + document.body.classList.add(themeKey); + + // Mettre à jour le subtitle + const subtitleElement = document.getElementById("subtitle"); + if (subtitleElement && themes[themeKey]) { + subtitleElement.textContent = themes[themeKey].subtitle; + } + +} + +// Sauvegarder le thème choisi dans le navigateur +function saveTheme(themeKey) { + localStorage.setItem("selectedTheme", themeKey); +} + +// Charger le thème sauvegardé au démarrage +function loadTheme() { + const savedTheme = localStorage.getItem("selectedTheme"); + + if (savedTheme && themes[savedTheme]) { + applyTheme(savedTheme); + themeSelect.value = savedTheme; + } else { + applyTheme("dark"); // thème par défaut + themeSelect.value = "dark"; + } +} + +// Quand l'utilisateur change le thème depuis le select +themeSelect.addEventListener("change", (event) => { + const selectedTheme = event.target.value; + if (themes[selectedTheme]) { + applyTheme(selectedTheme); + saveTheme(selectedTheme); + } +}); + +// Initialisation +populateThemeSelect(); +loadTheme(); \ No newline at end of file diff --git a/OLD CODE/public/script/downloadstatus.js b/public/script/downloadstatus.js similarity index 59% rename from OLD CODE/public/script/downloadstatus.js rename to public/script/downloadstatus.js index 9f30b03..315cbc2 100644 --- a/OLD CODE/public/script/downloadstatus.js +++ b/public/script/downloadstatus.js @@ -16,56 +16,38 @@ along with this program. If not, see . */ -// Récupération du formulaire de téléchargement const form = document.getElementById("downloadForm"); - -// Élément où on affichera l’état du téléchargement (en cours, erreur, succès) const statusDiv = document.getElementById("downloadStatus"); - -// Récupération du bouton de soumission pour pouvoir le désactiver pendant la requête const button = form.querySelector("button"); -// Écouteur d’événement pour intercepter la soumission du formulaire form.addEventListener("submit", async (e) => { - e.preventDefault(); // Empêche le rechargement automatique de la page - - // Désactive le bouton pour éviter les clics multiples pendant la requête - button.disabled = true; - - // Affiche un message d’attente pour informer l’utilisateur + e.preventDefault(); + button.disabled = true; // Empêche les clics multiples statusDiv.textContent = "Téléchargement en cours..."; - // Prépare les données du formulaire au format URL-encoded const formData = new FormData(form); const params = new URLSearchParams(formData); try { - // Envoie la requête POST vers /download avec les données du formulaire const res = await fetch("/download", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params, }); - // Si la réponse n’est pas un succès (code HTTP 4xx ou 5xx) if (!res.ok) { statusDiv.textContent = "❌ Erreur pendant le téléchargement."; return; } - // Récupère le texte envoyé par le serveur (message de succès ou erreur) const text = await res.text(); - - // Affiche le message retourné par le serveur statusDiv.textContent = text; + } catch { - // Gestion des erreurs réseau ou autres exceptions statusDiv.textContent = "❌ Une erreur s’est produite."; } finally { - // Réactive le bouton pour permettre d’autres soumissions button.disabled = false; - // Efface le message d’état après 5 secondes pour garder l’interface propre setTimeout(() => { statusDiv.textContent = ""; }, 5000); diff --git a/public/script/fetchinfo.js b/public/script/fetchinfo.js new file mode 100644 index 0000000..16d768e --- /dev/null +++ b/public/script/fetchinfo.js @@ -0,0 +1,122 @@ +/* + This file is part of Freedom Loader. + + Copyright (C) 2025 MasterAcnolo + + Freedom Loader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License. + + Freedom Loader 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 GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +function formatDate(dateStr) { + if (!dateStr || dateStr.length !== 8) return "Inconnue"; + return `${dateStr.slice(6,8)}/${dateStr.slice(4,6)}/${dateStr.slice(0,4)}`; +} + +function formatSize(bytes) { + return bytes ? (bytes / (1024*1024)).toFixed(2) + " Mo" : "Inconnue"; +} + +async function fetchVideoInfo(url) { + try { + const res = await fetch("/info", { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ url }), + }); + + if (!res.ok) return { error: `Erreur serveur: ${res.status}` }; + + const data = await res.json(); + if (!data) return { error: "Données manquantes" }; + + return data; + } catch (e) { + return { error: "Erreur réseau ou JSON" }; + } +} + +document.addEventListener("DOMContentLoaded", () => { + const urlInput = document.getElementById("UrlInput"); + const infoDiv = document.getElementById("videoInfo"); + let lastFetchedUrl = ""; + + urlInput.addEventListener("input", async () => { + const url = urlInput.value.trim(); + + if (!url || url.length < 5) { + infoDiv.innerHTML = ""; + infoDiv.classList.remove("visible"); + lastFetchedUrl = ""; + return; + } + + if (url === lastFetchedUrl) return; + lastFetchedUrl = url; + + const data = await fetchVideoInfo(url); + if (data.error) { + infoDiv.innerHTML = `❌ ${data.error}`; + infoDiv.classList.remove("visible"); + return; + } + + // Playlist + if (data.type === "playlist") { + infoDiv.innerHTML = ` +

Playlist détectée – ${data.count} vidéos

+

${data.title}

+

Channel : ${data.channel || "Inconnu"}

+
+ `; + const listDiv = document.getElementById("playlistVideos"); + data.videos.forEach(v => { + const durationStr = `${Math.floor(v.duration/60)}m ${(v.duration%60).toString().padStart(2,"0")}s`; + listDiv.innerHTML += ` +
+ Thumbnail +

${v.title}

+

Durée : ${durationStr}

+

URL : ${v.webpage_url}

+
+ `; + }); + infoDiv.classList.add("visible"); + return; + } + + + // Vidéo normale + const durationStr = `${Math.floor(data.duration/60)}m ${(data.duration%60) + .toString() + .padStart(2,"0")}s`; + const sizeStr = formatSize(data.filesize_approx); + const readableDate = formatDate(data.upload_date); + const categories = data.categories?.join(", ") || "Non spécifiées"; + + infoDiv.innerHTML = ` +

${data.title}

+ Thumbnail +
    +
  • Durée : ${durationStr}
  • +
  • Uploader : ${data.uploader || "Inconnu"}
  • +
  • Date d’upload : ${readableDate}
  • +
  • Vues : ${data.view_count?.toLocaleString() || "?"}
  • +
  • Likes : ${data.like_count?.toLocaleString() || "?"}
  • +
  • URL : ${data.webpage_url}
  • +
  • Channel : ${data.channel_url}
  • +
  • Taille estimée : ${sizeStr}
  • +
  • Catégories : ${categories}
  • +
+ `; + infoDiv.classList.add("visible"); + }); +}) \ No newline at end of file diff --git a/OLD CODE/public/styles/components/checkbox.css b/public/styles/components/checkbox.css similarity index 100% rename from OLD CODE/public/styles/components/checkbox.css rename to public/styles/components/checkbox.css diff --git a/OLD CODE/public/styles/components/editpathbutton.css b/public/styles/components/editpathbutton.css similarity index 100% rename from OLD CODE/public/styles/components/editpathbutton.css rename to public/styles/components/editpathbutton.css diff --git a/OLD CODE/public/styles/components/themebutton.css b/public/styles/components/themebutton.css similarity index 100% rename from OLD CODE/public/styles/components/themebutton.css rename to public/styles/components/themebutton.css diff --git a/public/styles/layout/container.css b/public/styles/layout/container.css new file mode 100644 index 0000000..14e1ad4 --- /dev/null +++ b/public/styles/layout/container.css @@ -0,0 +1,25 @@ +.container, .infos-container { + position: relative; + top: 0; + left: 0; + right: 0; + padding: 1em; + z-index: 1000; + display: flex; + flex-direction: column; + align-items: center; +} + +#downloadStatus { + margin-top: 1em; + min-height: 1.5em; + font-weight: bold; + text-align: center; + color: var(--download-status-color); +} + +label { + display: flex; + align-items: center; + font-size: 0.9em; +} \ No newline at end of file diff --git a/OLD CODE/public/styles/layout/form.css b/public/styles/layout/form.css similarity index 100% rename from OLD CODE/public/styles/layout/form.css rename to public/styles/layout/form.css diff --git a/OLD CODE/public/styles/layout/header.css b/public/styles/layout/header.css similarity index 100% rename from OLD CODE/public/styles/layout/header.css rename to public/styles/layout/header.css diff --git a/OLD CODE/public/styles/layout/videoinfo.css b/public/styles/layout/videoinfo.css similarity index 100% rename from OLD CODE/public/styles/layout/videoinfo.css rename to public/styles/layout/videoinfo.css diff --git a/OLD CODE/public/styles/styles.css b/public/styles/styles.css similarity index 79% rename from OLD CODE/public/styles/styles.css rename to public/styles/styles.css index 16c0fa0..d385576 100644 --- a/OLD CODE/public/styles/styles.css +++ b/public/styles/styles.css @@ -1,11 +1,13 @@ +/* Layout */ @import url("layout/form.css"); @import url("layout/container.css"); -@import url("layout/ctr-infos.css"); @import url("layout/header.css"); @import url("layout/videoinfo.css"); + +/* Components */ @import url("components/checkbox.css"); @import url("components/themebutton.css"); -/* @import url("variables.css"); */ + @import url("theme/themeimport.css"); @import url("components/editpathbutton.css"); @@ -24,7 +26,7 @@ html,body{ margin: 0; padding: 0; scroll-behavior: smooth; - /* outline: 2px solid red; */ /*<-- Utile pour le début ça */ + /*outline: 2px solid red; /*<-- Utile pour le debug ça */ font-family: "Poppins", sans-serif; font-weight: 500; font-style: normal; @@ -33,10 +35,6 @@ html,body{ body { margin: 0; background-color: var(--background-color); - /* background-image: linear-gradient(to left top, #838383, #a1a1a1, #bfbfbf, #dfdfdf, #ffffff); - background-size: cover; - background-position: center; - background-attachment: fixed; */ width: 100%; height: 100%; color: var(--default-text-color); @@ -46,7 +44,6 @@ body { display: flex; flex-direction: column; align-items: center; - } diff --git a/OLD CODE/public/styles/theme/chirac-theme.css b/public/styles/theme/chirac-theme.css similarity index 79% rename from OLD CODE/public/styles/theme/chirac-theme.css rename to public/styles/theme/chirac-theme.css index 8efbaac..6818b5c 100644 --- a/OLD CODE/public/styles/theme/chirac-theme.css +++ b/public/styles/theme/chirac-theme.css @@ -1,16 +1,15 @@ body.chirac { - /* Background image Chirac */ + background-image: url('../../assets/images/Chirac.png'); - /* background-position: right; */ background-repeat: no-repeat; background-size: cover; background-attachment: fixed; - /* Couleurs drapeau français */ - --background-color: rgba(255, 255, 255, 0.85); /* blanc légèrement transparent pour lisibilité */ - --default-text-color: #002395; /* bleu foncé */ - --subtitle-color: #EF4135; /* rouge vif */ - --mp3-text-color: #002395; /* bleu */ + + --background-color: rgba(255, 255, 255, 0.85); + --default-text-color: #002395; + --subtitle-color: #EF4135; + --mp3-text-color: #002395; /* Formulaire */ --form-bg-color: rgba(255, 255, 255, 0.9); @@ -19,9 +18,9 @@ body.chirac { --form-input-border-focus-color: #EF4135; --form-input-text-color: #002395; --form-input-placeholder-color: #999999; - --form-button-bg-color: #002395; /* bleu */ + --form-button-bg-color: #002395; --form-button-text-color: #ffffff; - --form-button-bg-hover-color: #EF4135; /* rouge */ + --form-button-bg-hover-color: #EF4135; /* Checkbox */ --checkbox-bg-default: #cccccc; @@ -40,15 +39,8 @@ body.chirac { --video-info-list-strong-color: #000000; --video-info-link-color: #002395; --video-info-link-hover-color: #EF4135; - - /* animation d'apparition */ - opacity: 0; - transition: opacity 0.8s ease, background-color 0.8s ease; } -body.chirac.theme-active { - opacity: 1; -} /* diff --git a/public/styles/theme/cyberpunk-theme.css b/public/styles/theme/cyberpunk-theme.css new file mode 100644 index 0000000..8c0ac6d --- /dev/null +++ b/public/styles/theme/cyberpunk-theme.css @@ -0,0 +1,43 @@ +body.cyberpunk { + + background-image: url('../../assets/images/Cyberpunk.webp'); + background-size: cover; + background-position: center; + background-repeat: no-repeat; + background-attachment: fixed; + + /* Couleurs générales */ + --background-color: #0d0d0d; + --default-text-color: #e0e0e0; + --subtitle-color: #ffea00; + --mp3-text-color: #ffea00; + + /* Formulaire */ + --form-bg-color: #141414; + --form-input-bg-color: #1a1a1a; + --form-input-border-color: #333333; + --form-input-border-focus-color: #ffea00; + --form-input-text-color: #e0e0e0; + --form-input-placeholder-color: #777777; + --form-button-bg-color: #ffea00; + --form-button-text-color: #0d0d0d; + --form-button-bg-hover-color: #d4c000; + + /* Checkbox */ + --checkbox-bg-default: #333333; + --checkbox-bg-checked: #ffea00; + --checkbox-checkmark-border-color: #0d0d0d; + --checkbox-pulse-color: rgba(255, 234, 0, 0.5); + + /* Download status */ + --download-status-color: #fffbd1; + + /* Video Info Box */ + --infos-box-color: #1a1a1a; + --video-info-text-color: #e0e0e0; + --video-info-heading-color: #ffea00; + --video-info-list-color: #cccccc; + --video-info-list-strong-color: #ffffff; + --video-info-link-color: #ffea00; + --video-info-link-hover-color: #d4c000; +} \ No newline at end of file diff --git a/OLD CODE/public/styles/theme/dark-theme.css b/public/styles/theme/dark-theme.css similarity index 100% rename from OLD CODE/public/styles/theme/dark-theme.css rename to public/styles/theme/dark-theme.css diff --git a/OLD CODE/public/styles/theme/default-theme.css b/public/styles/theme/default-theme.css similarity index 100% rename from OLD CODE/public/styles/theme/default-theme.css rename to public/styles/theme/default-theme.css diff --git a/public/styles/theme/drift-theme.css b/public/styles/theme/drift-theme.css new file mode 100644 index 0000000..c88693d --- /dev/null +++ b/public/styles/theme/drift-theme.css @@ -0,0 +1,47 @@ +body.drift { + + background-image: url('../../assets/images/Drift.jpg'); + background-size: cover; + background-position: center; + background-attachment: fixed; + + /* Palette principale */ + --background-color: #0f1218; + --default-text-color: #3d4b61; + --subtitle-color: #1a1a1a; + --mp3-text-color: #c0d4ff; + + /* Formulaire */ + --form-bg-color: #1e222a; + --form-input-bg-color: #13171d; + --form-input-border-color: #3b4b6d; + --form-input-border-focus-color: #6ea8ff; + --form-input-text-color: #f0f4ff; + --form-input-placeholder-color: #7a8fae; + --form-button-bg-color: #2a2979; + --form-button-text-color: #ffffff; + --form-button-bg-hover-color: #353393; + + /* Checkbox */ + --checkbox-bg-default: #2a2e38; + --checkbox-bg-checked: #2a2979; + --checkbox-checkmark-border-color: #ffffff; + --checkbox-pulse-color: rgba(76,108,255,0.3); + + /* Download status */ + --download-status-color: #ff8e8e; + + /* Video Info Box */ + --infos-box-color: #1e222a; + --video-info-text-color: #e8e8f0; + --video-info-heading-color: #9ab6ff; + --video-info-list-color: #c0d4ff; + --video-info-list-strong-color: #ffffff; + --video-info-link-color: #4c6cff; + --video-info-link-hover-color: #6c8dff; + } + + +body.drift .download-path{ + color: #ededed; +} diff --git a/public/styles/theme/fanatic-theme.css b/public/styles/theme/fanatic-theme.css new file mode 100644 index 0000000..3601445 --- /dev/null +++ b/public/styles/theme/fanatic-theme.css @@ -0,0 +1,36 @@ +body.fanatic{ + /* Couleurs générales */ + --background-color: #121212; + --default-text-color: #eee; + --subtitle-color: #FF5900; + --mp3-text-color: #ffffff; + + /* Formulaire */ + --form-bg-color: #1e1e1e; + --form-input-bg-color: #2b2b2b; + --form-input-border-color: #333333; + --form-input-border-focus-color: #FF5900; + --form-input-text-color: #eeeeee; + --form-input-placeholder-color: #666666; + --form-button-bg-color: #FF5900; + --form-button-text-color: #121212; + --form-button-bg-hover-color: #e65500; + + /* Checkbox */ + --checkbox-bg-default: #555555; + --checkbox-bg-checked: #ff6600; + --checkbox-checkmark-border-color: #eee; + --checkbox-pulse-color: rgba(255, 102, 0, 0.5); + + /* Download status */ + --download-status-color: #FF5900; + + /* Video Info Box */ + --infos-box-color: #222222; + --video-info-text-color: #eee; + --video-info-heading-color: #FF5900; + --video-info-list-color: #ccc; + --video-info-list-strong-color: #fff; + --video-info-link-color: #FF5900; + --video-info-link-hover-color: #e65500; +} diff --git a/public/styles/theme/light-theme.css b/public/styles/theme/light-theme.css new file mode 100644 index 0000000..12d7724 --- /dev/null +++ b/public/styles/theme/light-theme.css @@ -0,0 +1,36 @@ +body.light { + /* Couleurs générales */ + --background-color: #dadada; + --default-text-color: #222222; + --subtitle-color: #007bff; + --mp3-text-color: #000000; + + /* Formulaire */ + --form-bg-color: #ffffff; + --form-input-bg-color: #fafafa; + --form-input-border-color: #cccccc; + --form-input-border-focus-color: #007bff; + --form-input-text-color: #333333; + --form-input-placeholder-color: #888888; + --form-button-bg-color: #007bff; + --form-button-text-color: #ffffff; + --form-button-bg-hover-color: #0056b3; + + /* Checkbox */ + --checkbox-bg-default: #cccccc; + --checkbox-bg-checked: #007bff; + --checkbox-checkmark-border-color: #ffffff; + --checkbox-pulse-color: rgba(0, 123, 255, 0.4); + + /* Download status */ + --download-status-color: #007bff; + + /* Video Info Box */ + --infos-box-color: #ffffff; + --video-info-text-color: #222222; + --video-info-heading-color: #007bff; + --video-info-list-color: #444444; + --video-info-list-strong-color: #000000; + --video-info-link-color: #007bff; + --video-info-link-hover-color: #0056b3; +} diff --git a/public/styles/theme/neon-theme.css b/public/styles/theme/neon-theme.css new file mode 100644 index 0000000..544a23a --- /dev/null +++ b/public/styles/theme/neon-theme.css @@ -0,0 +1,47 @@ +body.neon { + + background: (url("../../assets/images/drift.jpg")); + background-image: linear-gradient(135deg, #0f0c29, #302b63, #24243e); /* dégradé sombre violet/bleu */ + background-size: cover; + background-position: center; + background-attachment: fixed; + + /* Couleurs générales */ + --background-color: #1b1b2f; + --default-text-color: #e0e0ff; + --subtitle-color: #a3a3ff; + --mp3-text-color: #c0c0ff; + + /* Formulaire */ + --form-bg-color: #2c2c4a; + --form-input-bg-color: #1f1f3a; + --form-input-border-color: #5858f0; + --form-input-border-focus-color: #a3a3ff; + --form-input-text-color: #ffffff; + --form-input-placeholder-color: #8888ff; + --form-button-bg-color: #5858f0; + --form-button-text-color: #ffffff; + --form-button-bg-hover-color: #7a7aff; + + /* Checkbox */ + --checkbox-bg-default: #302b63; + --checkbox-bg-checked: #5858f0; + --checkbox-checkmark-border-color: #ffffff; + --checkbox-pulse-color: rgba(88,88,240,0.3); + + /* Download status */ + --download-status-color: #a3a3ff; + + /* Video Info Box */ + --infos-box-color: #2c2c4a; + --video-info-text-color: #e0e0ff; + --video-info-heading-color: #a3a3ff; + --video-info-list-color: #c0c0ff; + --video-info-list-strong-color: #ffffff; + --video-info-link-color: #5858f0; + --video-info-link-hover-color: #7a7aff; +} + +body.neon .download-path { + color: var(--default-text-color); +} diff --git a/public/styles/theme/nf-theme.css b/public/styles/theme/nf-theme.css new file mode 100644 index 0000000..5bef409 --- /dev/null +++ b/public/styles/theme/nf-theme.css @@ -0,0 +1,46 @@ +body.nf { + + background-image: url('../../assets/images/NF.jpg'); + background-size: cover; + background-position: 70% center; + background-attachment: fixed; + + /* Couleurs générales */ + --background-color: #1a1a1a; + --default-text-color: #e6dfd5; + --subtitle-color: #f5f0e6; + --mp3-text-color: #d8cfc2; + + /* Formulaire */ + --form-bg-color: #2a2a2a; + --form-input-bg-color: #222222; + --form-input-border-color: #4a4036; + --form-input-border-focus-color: #cbb89d; + --form-input-text-color: #f0e8dd; + --form-input-placeholder-color: #9a8f80; + --form-button-bg-color: #cbb89d; + --form-button-text-color: #1a1a1a; + --form-button-bg-hover-color: #b7a588; + + /* Checkbox */ + --checkbox-bg-default: #4a4036; + --checkbox-bg-checked: #cbb89d; + --checkbox-checkmark-border-color: #1a1a1a; + --checkbox-pulse-color: rgba(203,184,157,0.3); + + /* Download status */ + --download-status-color: #cbb89d; + + /* Video Info Box */ + --infos-box-color: #2a2a2a; + --video-info-text-color: #e6dfd5; + --video-info-heading-color: #f5f0e6; + --video-info-list-color: #d8cfc2; + --video-info-list-strong-color: #ffffff; + --video-info-link-color: #cbb89d; + --video-info-link-hover-color: #b7a588; +} + +body.nf .download-path { + color: var(--default-text-color); +} diff --git a/public/styles/theme/spicy-theme.css b/public/styles/theme/spicy-theme.css new file mode 100644 index 0000000..3ee396d --- /dev/null +++ b/public/styles/theme/spicy-theme.css @@ -0,0 +1,43 @@ +body.spicy { + + background-image: url('../../assets/images/spicy.webp'); + background-size:contain; + background-position: center; + background-attachment: fixed; + background-repeat: no-repeat; + + /* Couleurs générales */ + --background-color: #121212; + --default-text-color: #581010; + --subtitle-color: #3d0909; + --mp3-text-color: #ffffff; + + /* Formulaire */ + --form-bg-color: #1a1a1a; + --form-input-bg-color: #2b2b2b; + --form-input-border-color: #444444; + --form-input-border-focus-color: #ff1a1a; + --form-input-text-color: #eeeeee; + --form-input-placeholder-color: #888888; + --form-button-bg-color: #ff1a1a; + --form-button-text-color: #ffffff; + --form-button-bg-hover-color: #cc0000; + + /* Checkbox */ + --checkbox-bg-default: #555555; + --checkbox-bg-checked: #ff1a1a; + --checkbox-checkmark-border-color: #f5f5f5; + --checkbox-pulse-color: rgba(255, 26, 26, 0.5); + + /* Download status */ + --download-status-color: #ffffff; + + /* Video Info Box */ + --infos-box-color: #1a1a1a; + --video-info-text-color: #f5f5f5; + --video-info-heading-color: #ff1a1a; + --video-info-list-color: #cccccc; + --video-info-list-strong-color: #ffffff; + --video-info-link-color: #ff1a1a; + --video-info-link-hover-color: #cc0000; +} \ No newline at end of file diff --git a/OLD CODE/public/styles/theme/themeimport.css b/public/styles/theme/themeimport.css similarity index 74% rename from OLD CODE/public/styles/theme/themeimport.css rename to public/styles/theme/themeimport.css index 3748f0c..887fb90 100644 --- a/OLD CODE/public/styles/theme/themeimport.css +++ b/public/styles/theme/themeimport.css @@ -6,4 +6,6 @@ @import url("cyberpunk-theme.css"); @import url("spicy-theme.css"); @import url("vilbrequin-theme.css"); -@import url("nf-theme.css"); \ No newline at end of file +@import url("nf-theme.css"); +@import url("drift-theme.css"); +@import url("neon-theme.css"); \ No newline at end of file diff --git a/public/styles/theme/vilbrequin-theme.css b/public/styles/theme/vilbrequin-theme.css new file mode 100644 index 0000000..d42799f --- /dev/null +++ b/public/styles/theme/vilbrequin-theme.css @@ -0,0 +1,46 @@ +body.vilbrequin { + + background-image: url('../../assets/images/Vilbrequin.webp'); + background-size: cover; + background-position: center; + background-attachment: fixed; + + /* Couleurs générales */ + --background-color: #0a1f3d; + --default-text-color: #535353; + --subtitle-color: #001696; + --mp3-text-color: #ffffff; + + /* Formulaire */ + --form-bg-color: #12284f; + --form-input-bg-color: #1c3760; + --form-input-border-color: #2a4b80; + --form-input-border-focus-color: #00bfff; + --form-input-text-color: #f0f0f0; + --form-input-placeholder-color: #b0c4de; + --form-button-bg-color: #ffd700; + --form-button-text-color: #0a1f3d; + --form-button-bg-hover-color: #e6c200; + + /* Checkbox */ + --checkbox-bg-default: #2a4b80; + --checkbox-bg-checked: #00bfff; + --checkbox-checkmark-border-color: #f0f0f0; + --checkbox-pulse-color: rgba(0,191,255,0.5); + + /* Download status */ + --download-status-color: #ffd700; + + /* Video Info Box */ + --infos-box-color: #12284f; + --video-info-text-color: #f0f0f0; + --video-info-heading-color: #00bfff; + --video-info-list-color: #dcdcdc; + --video-info-list-strong-color: #ffffff; + --video-info-link-color: #00bfff; + --video-info-link-hover-color: #009acd; +} + +body.vilbrequin .download-path{ + color: white; +} \ No newline at end of file diff --git a/public/styles/variables.css b/public/styles/variables.css new file mode 100644 index 0000000..cfbb85d --- /dev/null +++ b/public/styles/variables.css @@ -0,0 +1,36 @@ +:root { + /* Couleurs générales */ + --background-color: #001224; + --default-text-color: #eee; + --subtitle-color: #007bff; + --mp3-text-color: #000000; + + /* Formulaire */ + --form-bg-color: #ffffff; + --form-input-bg-color: #f9f9f9; + --form-input-border-color: #cccccc; + --form-input-border-focus-color: #0056b3; + --form-input-text-color: #333333; + --form-input-placeholder-color: #aaaaaa; + --form-button-bg-color: #007bff; + --form-button-text-color: #ffffff; + --form-button-bg-hover-color: #0056b3; + + /* Checkbox */ + --checkbox-bg-default: #cccccc; + --checkbox-bg-checked: #3B82F6; + --checkbox-checkmark-border-color: #E0E0E2; + --checkbox-pulse-color: rgba(59, 130, 246, 0.5); + + /* Download status */ + --download-status-color: #007bff; + + /* Video Info Box */ + --infos-box-color: #f0f0f0; + --video-info-text-color: #222222; + --video-info-heading-color: #007bff; + --video-info-list-color: #444444; + --video-info-list-strong-color: #000000; + --video-info-link-color: #007bff; + --video-info-link-hover-color: #0056b3; +} diff --git a/OLD CODE/ressources/yt-dlp.exe b/ressources/yt-dlp.exe similarity index 100% rename from OLD CODE/ressources/yt-dlp.exe rename to ressources/yt-dlp.exe diff --git a/OLD CODE/server/discordRPC.js b/server/discordRPC.js similarity index 59% rename from OLD CODE/server/discordRPC.js rename to server/discordRPC.js index d48a77a..47abb5d 100644 --- a/OLD CODE/server/discordRPC.js +++ b/server/discordRPC.js @@ -20,22 +20,24 @@ const RPC = require("discord-rpc"); const clientId = "1410934537051181146"; const rpc = new RPC.Client({ transport: "ipc" }); +let intervalId; + function startRPC() { rpc.on("ready", () => { - console.log("✅ Connecté à Discord !"); + console.log("Connecté à Discord !"); const presence = { - largeImageKey: "icon", - smallImageKey: "acnolo_pfp", - smallImageText: "By MasterAcnolo", - startTimestamp: new Date(), // compteur de temps - details: "github.com/MasterAcnolo/Freedom-Loader", + largeImageKey: "icon", + smallImageKey: "acnolo_pfp", + smallImageText: "By MasterAcnolo", + startTimestamp: new Date(), + details: "github.com/MasterAcnolo/Freedom-Loader", }; rpc.setActivity(presence); - // Mise à jour toutes les 15 secondes si tu veux garder le compteur à jour - setInterval(() => { + // Met à jour la présence toutes les 15s + intervalId = setInterval(() => { rpc.setActivity(presence); }, 15000); }); @@ -43,6 +45,23 @@ function startRPC() { rpc.login({ clientId }).catch(err => { console.error("Erreur Discord RPC :", err); }); + + // Gestion propre de la fermeture + const cleanExit = () => { + if (intervalId) clearInterval(intervalId); // stop interval + rpc.destroy(); // déconnecte proprement + console.log("Discord RPC arrêté proprement."); + }; + + process.on("exit", cleanExit); + process.on("SIGINT", () => { + cleanExit(); + process.exit(); + }); + process.on("SIGTERM", () => { + cleanExit(); + process.exit(); + }); } module.exports = { startRPC }; diff --git a/server/logger.js b/server/logger.js new file mode 100644 index 0000000..9597671 --- /dev/null +++ b/server/logger.js @@ -0,0 +1,86 @@ +/* + This file is part of Freedom Loader. + + Copyright (C) 2025 MasterAcnolo + + Freedom Loader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License. + + Freedom Loader 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 GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + 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 os = require("os"); + +// Dossier de logs Windows +const logDir = path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs"); + +// Création du dossier si nécessaire +fs.mkdirSync(logDir, { recursive: true }); + +// Format commun pour tous les logs +const logFormat = format.combine( + format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + format.printf(({ timestamp, level, message }) => `${timestamp} | ${level.toUpperCase()} | ${message}`) +); + +const consoleFormat = format.combine( + format.colorize(), + format.timestamp({ format: "HH:mm:ss" }), + format.printf(({ timestamp, level, message }) => `${timestamp} | ${level} | ${message}`) +); + +// Configuration du logger +const logger = createLogger({ + level: "info", + format: logFormat, + transports: [ + new DailyRotateFile({ + dirname: logDir, + filename: "LOGS-%DATE%.log", + datePattern: "YYYY-MM-DD", + zippedArchive: false, + maxFiles: "7d", + format: logFormat, + options: { flags: "a" }, + }), + new transports.Console({ + format: consoleFormat, + }), + ], +}); + +// Helpers pour sessions +function getSessionStartLine() { + return `--- Démarrage de la session : ${new Date().toISOString()} ---`; +} + +function getSessionEndLine() { + return `--- Fin de la session : ${new Date().toISOString()} ---`; +} + +function logSessionStart() { + logger.info(getSessionStartLine()); +} + +function logSessionEnd() { + logger.info(getSessionEndLine()); +} + +// Export +module.exports = { + logger, + logSessionStart, + logSessionEnd, + logDir, +}; diff --git a/server/routes/download.js b/server/routes/download.js new file mode 100644 index 0000000..0db6fe2 --- /dev/null +++ b/server/routes/download.js @@ -0,0 +1,172 @@ +/* + This file is part of Freedom Loader. + + Copyright (C) 2025 MasterAcnolo + + Freedom Loader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License. + + Freedom Loader 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 GNU General Public License for more details. +*/ +const { app } = require("electron"); +const express = require("express"); +const router = express.Router(); +const { execFile } = require("child_process"); +const path = require("path"); +const fs = require("fs"); +const { Notification } = require("electron"); +const logger = require("../logger").logger; +const userYtDlp = path.join(app.getPath("userData"), "yt-dlp.exe"); + +// Path vers yt-dlp +const ytDlpPath = path.join(process.resourcesPath, "yt-dlp.exe"); +const sourceYtDlp = path.join(process.resourcesPath, "yt-dlp.exe"); + + +if (!fs.existsSync(userYtDlp)) { + fs.copyFileSync(sourceYtDlp, userYtDlp); + logger.info(`yt-dlp copié dans le dossier utilisateur : ${userYtDlp}`); +} + +// Lancement de la mise à jour une seule fois au démarrage +execFile(userYtDlp, ["-U"], (err, stdout, stderr) => { + if (err) { + logger.warn("Erreur update yt-dlp:", err); + logger.debug(err); + return; + } + logger.info(`Update yt-dlp : ${stdout}`); +}); + +// Validation simple d'une URL +function isValidUrl(url) { + return /^https?:\/\/.+/.test(url); // Petit Regex qui renvoie true si l'URL est bonne et False si elle est pas bonne +} + +router.post("/", (req, res) => { + try { + const options = { + url: req.body.url, + audioOnly: req.body.audioOnly === "1", + quality: req.body.quality || "best", + }; + + if (!options.url) { + logger.warn("Requête POST /download sans URL"); + return res.status(400).send("❌ URL manquante !"); + } + + if (!isValidUrl(options.url)) { + logger.warn(`URL invalide : ${options.url}`); + return res.status(400).send("❌ URL invalide !"); + } + + let requestedOutputFolder = req.body.savePath || req.app.locals.outputFolder; + requestedOutputFolder = path.normalize(requestedOutputFolder); + + if ( + !requestedOutputFolder || + requestedOutputFolder.length < 3 || + requestedOutputFolder.includes("System32") || + requestedOutputFolder.includes("/etc") || + requestedOutputFolder.includes("\\Windows") + ) { + logger.warn(`Chemin potentiellement dangereux refusé : ${requestedOutputFolder}`); + return res.status(400).send("❌ Chemin de sauvegarde non autorisé."); + } + + if (!fs.existsSync(requestedOutputFolder)) { + fs.mkdirSync(requestedOutputFolder, { recursive: true }); + logger.info(`Dossier de sortie créé : ${requestedOutputFolder}`); + } + + const outputTemplate = path.join(requestedOutputFolder, "%(title)s.%(ext)s"); + + const args = [ + "--no-continue", + // "--restrict-filenames", + "--no-overwrites", + + "--embed-thumbnail", + "--add-metadata", + "--concurrent-fragments", "8", + "--retries", "10", + "--fragment-retries", "10", + "--ffmpeg-location", path.join(process.resourcesPath, "ffmpeg.exe") + ]; + + if (options.audioOnly) { + args.push("-f", "bestaudio", "--extract-audio", "--audio-format", "mp3"); + } else + args.push("--merge-output","mp4") + + const qualityMap = { + best: "bestvideo+bestaudio/best/mp4", + medium: "bestvideo[height<=720]+bestaudio/best[height<=720]/mp4", + worst: "worstvideo+worstaudio/worst/mp4", + 1080: "bestvideo[height<=1080]+bestaudio/best[height<=1080]/mp4", + 720: "bestvideo[height<=720]+bestaudio/best[height<=720]/mp4", + 480: "bestvideo[height<=480]+bestaudio/best[height<=480]/mp4", + }; + + const format = qualityMap[options.quality] || "best"; + args.push("-f", format); + + args.push("-o", outputTemplate); + args.push(options.url); + + logger.info(`Téléchargement demandé : url=${options.url}, audioOnly=${options.audioOnly}, quality=${options.quality}`); + logger.info(`Commande yt-dlp : ${ytDlpPath} ${args.join(" ")}`); + + const child = execFile(userYtDlp, args); + + child.stdout.on("data", (data) => { + data.toString().split("\n").forEach(line => { + if (line.trim()) logger.info(`[yt-dlp stdout] ${line.trim()}`); + }); + }); + + child.stderr.on("data", (data) => { + data.toString().split("\n").forEach(line => { + if (line.trim()) logger.error(`[yt-dlp stderr] ${line.trim()}`); + }); + }); + + child.on("error", (err) => { + logger.error(`Erreur lancement yt-dlp : ${err.message}`); + res.status(500).send(`❌ Erreur lors de l'exécution : ${err.message}`); + }); + + child.on("close", (code) => { + logger.info(`yt-dlp terminé avec code de sortie : ${code}`); + if (code === 0) { + const iconPath = path.join(process.resourcesPath, "confirm-icon.png"); + const notif = new Notification({ + title: "Freedom Loader", + body: "Ton téléchargement est terminé, clique ici pour l'ouvrir.", + icon: iconPath, + }); + + notif.on("click", () => { + const { shell } = require("electron"); + shell.openPath(requestedOutputFolder); + }); + + notif.show(); + res.send("✅ Téléchargement terminé !"); + } else { + res.status(500).send(`❌ yt-dlp a échoué avec le code : ${code}`); + } + }); + + } catch (err) { + logger.error(`Erreur serveur dans /download : ${err.message}`); + res.status(500).send(`Erreur serveur : ${err.message}`); + } +}); + +module.exports = router; diff --git a/server/routes/info.js b/server/routes/info.js new file mode 100644 index 0000000..4c43d46 --- /dev/null +++ b/server/routes/info.js @@ -0,0 +1,101 @@ +/* + This file is part of Freedom Loader. + + Copyright (C) 2025 MasterAcnolo + + Freedom Loader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License. + + Freedom Loader 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 GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + 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 { logger } = require("../logger"); +const { app } = require("electron"); +const userYtDlp = path.join(app.getPath("userData"), "yt-dlp.exe"); + +const ytDlpPath = path.join(__dirname, '../../ressources/yt-dlp.exe'); + +// Copie yt-dlp si besoin +if (!fs.existsSync(userYtDlp)) { + fs.copyFileSync(ytDlpPath, userYtDlp); + logger.info(`yt-dlp copié dans le dossier utilisateur : ${userYtDlp}`); +} + +if (!fs.existsSync(ytDlpPath)) { + logger.error(`❌ yt-dlp introuvable à ${ytDlpPath}`); + throw new Error(`yt-dlp introuvable à ${ytDlpPath}`); +} + +function isValidUrl(url) { + return typeof url === "string" && /^https?:\/\//.test(url); +} + +router.post("/", (req, res) => { + const url = req.body.url; + if (!url || !isValidUrl(url)) { + logger.warn("Requête /info invalide ou URL manquante"); + return res.status(400).send("❌ URL invalide ou manquante"); + } + + logger.info(`Requête /info reçue pour ${url}`); + + const args = ["--dump-single-json", "--ignore-errors", "--yes-playlist", url]; + + execFile(userYtDlp, args, { timeout: 30_000 }, (error, stdout, stderr) => { + if (error) { + logger.error(`Erreur exécution yt-dlp: ${error.message}`); + if (stderr) logger.debug(`stderr: ${stderr}`); + return res.status(500).send("❌ Impossible de récupérer les infos."); + } + + try { + const data = JSON.parse(stdout); + + if (data._type === "playlist") { + logger.info(`Playlist détectée : ${data.title} (${data.entries.length} vidéos)`); + + const playlistPayload = { + type: "playlist", + title: data.title || data.id, + channel: data.uploader, + count: data.entries.length, + videos: (data.entries || []).map(v => ({ + id: v.id, + title: v.title, + webpage_url: v.webpage_url, + duration: v.duration, + thumbnail: v.thumbnail, + uploader: v.uploader + })) + }; + + return res.json(playlistPayload); + } + + // Vidéo unique + logger.info(`Vidéo unique récupérée : ${data.title}`); + res.json({ + type: "video", + ...data + }); + + } catch (e) { + logger.error(`Erreur parsing JSON: ${e.message}`); + return res.status(500).send("❌ JSON illisible."); + } + }); +}); + +module.exports = router; diff --git a/server/server.js b/server/server.js new file mode 100644 index 0000000..a739d8b --- /dev/null +++ b/server/server.js @@ -0,0 +1,106 @@ +/* + This file is part of Freedom Loader. + + Copyright (C) 2025 MasterAcnolo + + Freedom Loader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License. + + Freedom Loader 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 GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +const express = require("express"); +const fs = require("fs"); +const path = require("path"); +const { logger, logSessionStart, logSessionEnd } = require("./logger"); +const debug = require("debug")("freedom-loader:server"); + +const app = express(); + +const PORT = 8787; // Port de l'app + +// Dossier de téléchargement Windows +const downloadsPath = path.join(process.env.USERPROFILE, "Downloads"); +const outputFolder = path.join(downloadsPath, "Freedom Loader"); + +// Création du dossier si inexistant +if (!fs.existsSync(outputFolder)) { + try { + fs.mkdirSync(outputFolder, { recursive: true }); + logger.info("Dossier Freedom Loader créé dans Téléchargements."); + } catch (err) { + logger.error("Impossible de créer le dossier :", err); + process.exit(1); + } +} else { + logger.info("Dossier Freedom Loader déjà existant."); +} + +app.locals.outputFolder = outputFolder; + +app.use(express.urlencoded({ extended: true })); // Middleware pour parser les POST en x-www-form-urlencoded + +// Fichiers statiques (frontend) +const staticPath = path.join(__dirname, "../public"); +debug("Serveur statique sur", staticPath); +app.use(express.static(staticPath)); + +// Routes API +const infoRoute = require("./routes/info"); +const downloadRoute = require("./routes/download"); +debug("Routes /download et /info installées"); +app.use("/download", downloadRoute); +app.use("/info", infoRoute); + +// Route principale +app.get("/", (req, res) => { + debug("Requête GET / servie"); + res.sendFile(path.join(__dirname, "../public/index.html")); +}); + +// Fonction pour démarrer le serveur Express +async function startServer() { + return new Promise((resolve, reject) => { + logSessionStart(); // Début de session + + const serverInstance = app.listen(PORT, () => { + logger.info(`Serveur Express prêt sur http://localhost:${PORT}`); + resolve(serverInstance); + }); + + // Gestion des erreurs serveur + serverInstance.on("error", (err) => { + logger.error("Erreur serveur Express :", err); + reject(err); + }); + + // Gestion propre de la fermeture du serveur + const gracefulExit = () => { + logSessionEnd(); + serverInstance.close(() => { + logger.info("Serveur Express fermé proprement."); + process.exit(); + }); + }; + + process.on("SIGINT", gracefulExit); + process.on("SIGTERM", gracefulExit); + }); +} + +// Export de startServer +module.exports = { startServer }; + +// Lancement du Discord RPC après que le serveur soit prêt +(async () => { + const { startRPC } = require("./discordRPC"); + await startServer(); // on attend que le serveur démarre + startRPC(); +})();