36 Commits
1.0.1 ... 1.1.3

Author SHA1 Message Date
MasterAcnolo
94de0a42f1 Merge pull request #6 from MasterAcnolo/1.2
1.1.3
2025-08-21 16:01:34 +02:00
MasterAcnolo
5ba2901273 ReadME Update 2025-08-21 15:50:51 +02:00
MasterAcnolo
f5888b8382 NPM Update 2025-08-21 15:49:09 +02:00
MasterAcnolo
6ece182629 Update YTDLP + New Icon 2025-08-21 11:27:03 +02:00
MasterAcnolo
777257b71d Clarification 2025-08-21 08:45:50 +02:00
MasterAcnolo
df0156d603 Add Metadata / Thumbail
Optimisation + Retries
2025-08-20 14:27:45 +02:00
MasterAcnolo
68cdad06ab Merge pull request #5 from MasterAcnolo/Notification-System
# Freedom Loader - 1.1.2

## Changelog

- Added a “Logs” menu in the toolbar to open the logs folder directly
- Fixed log path for better Windows/Linux compatibility
- Disabled hardware acceleration to improve stability on some systems
- Fixed Electron menu merging logic
- Various minor improvements and bug fixes

## Found a bug or issue?
- Please report it in the GitHub Issues section

## Next Release (non-exhaustive roadmap)
- More format options  
- Subtitle support  
- Improved UI / UX  
- Language selection  
- Download specific parts of videos  
- File renaming options  
- Video extraction/splitting using native chapters  
- Parallel downloads  
- Skip sponsored parts automatically  

**Full Changelog**: https://github.com/MasterAcnolo/Freedom-Loader/compare/1.1.1...1.1.2
2025-08-13 15:28:57 +02:00
MasterAcnolo
ee7c6520b9 ReadME Fix 2025-08-13 15:22:51 +02:00
MasterAcnolo
4ed1609e41 Logs Fix + Logs Button 2025-08-13 15:20:35 +02:00
MasterAcnolo
b64e61d45d Fix Download 2025-08-13 14:56:14 +02:00
MasterAcnolo
b4c2c865d8 Logger Error Fix 2025-08-13 14:34:00 +02:00
MasterAcnolo
e32ffd8fa0 NPM Update 2025-08-13 14:25:33 +02:00
MasterAcnolo
8be6a932e5 NPM Update 2025-08-13 14:22:51 +02:00
MasterAcnolo
6ed59fd272 Merge pull request #4 from MasterAcnolo/Notification-System
Notification system + YT DLP Update
2025-07-20 18:22:10 +02:00
MasterAcnolo
4df1cb207f ReadME Update 2025-07-20 18:17:19 +02:00
MasterAcnolo
d03e3bc7e5 Notif + Update YT DLP 2025-07-20 15:27:40 +02:00
MasterAcnolo
df02acb97f Notification + Open On CLICK 2025-07-03 18:28:56 +02:00
MasterAcnolo
6ef04700ab NPM Update 2025-07-03 15:39:03 +02:00
MasterAcnolo
766694f330 ReadME Fix 2025-07-02 22:38:38 +02:00
MasterAcnolo
93380b2f36 ReadME Fix 2025-07-02 22:38:15 +02:00
MasterAcnolo
8c969d694f ReadME Fix 2025-07-02 22:37:30 +02:00
MasterAcnolo
f2f3d42e8c ReadME Fix 2025-07-02 22:36:53 +02:00
MasterAcnolo
8d8973acfc ReadME Fix 2025-07-02 22:35:59 +02:00
MasterAcnolo
281bff2379 Merge pull request #3 from MasterAcnolo/1.0.2
1.1.1
2025-07-02 22:33:09 +02:00
MasterAcnolo
e30f54050f ReadME FIX 2025-07-02 22:28:30 +02:00
MasterAcnolo
0ae808c1a0 Merge branch '1.0.2' of https://github.com/MasterAcnolo/MP3-Downloader into 1.0.2 2025-07-02 22:27:46 +02:00
MasterAcnolo
2598573844 Package Fix 2025-07-02 22:27:44 +02:00
MasterAcnolo
27782376a3 Merge pull request #2 from MasterAcnolo/1.0.2
1.1
2025-07-02 22:26:28 +02:00
MasterAcnolo
876f7e1db0 Merge branch 'main' into 1.0.2 2025-07-02 22:26:14 +02:00
MasterAcnolo
db654c5f82 1.1 2025-07-02 22:24:46 +02:00
MasterAcnolo
751f3d5c3e Commentary + Custom PATH 2025-07-02 16:50:52 +02:00
MasterAcnolo
2a8d307bc8 Chirac Easter EGG 2025-07-02 16:17:09 +02:00
MasterAcnolo
ec6963cff3 Logs FIX + Bug FIX 2025-07-02 13:49:55 +02:00
MasterAcnolo
196a07977a Package FIX for 1.0.2 2025-07-02 10:50:10 +02:00
MasterAcnolo
f20366f0c7 Logs Fix 2025-07-01 20:34:22 +02:00
MasterAcnolo
f54dc18e85 ReadME Pictures Fix 2025-07-01 20:13:12 +02:00
44 changed files with 1453 additions and 476 deletions

6
.gitignore vendored
View File

@@ -1,8 +1,6 @@
/node_modules /node_modules
/downloads /downloads
/dist /dist
/logs logs/*.json
logs/*.log
/out /out
yt-dlp.exe

View File

@@ -1,16 +1,26 @@
# Freedom Loader # Freedom Loader
![Static Badge](https://img.shields.io/badge/Latest%20Release_-1.0.1-blue?style=flat) ![Static Badge](https://img.shields.io/badge/Build-1.0.2%20Beta-yellow?style=flat) <!-- Release -->
![Static Badge](https://img.shields.io/badge/Release-1.1.3-blue?style=flat&logo=Test)
<!-- Build (Next Release) -->
![Static Badge](https://img.shields.io/badge/Build-1.2--beta-yellow?style=flat&logo=Test)
> This project aims to enable free, open-source, and unrestricted downloading of Video/Audio content. Eventually, it will allow users to choose > This project aims to enable free, open-source, and unrestricted downloading of Video/Audio content. Eventually, it will allow users to choose
## 🚀 Features ## 🚀 Features
✅ Video Download (MP4) - ✅ Video download with metadata(MP4)
✅ Audio Download (MP3) - ✅ Audio-only download with thumbail and metadata(MP3)
✅ Detailed Logs (Console + Logs) - ✅ Full playlist support
✅ Automatic Fetch of Information - ✅ Automatic metadata retrieval (title, duration, etc.)
✅ Playlist Download - ✅ Detailed logs: console and rotating files (7 days)
- ✅ Custom output path selection with saving
- ✅ Multiple graphical themes available for interface customization
- ✅ Simple, intuitive, and responsive interface
- ✅ Notification system when download finished (Click = Open Output Folder)
--- ---
@@ -47,14 +57,14 @@ Try restarting the application, checking the logs, or opening the browser's deve
- [ ] Extract / Split video using native chapters - [ ] Extract / Split video using native chapters
- [ ] Parallel downloads - [ ] Parallel downloads
- [ ] Skip sponsored parts of videos - [ ] Skip sponsored parts of videos
- [ ] Custom output path selection (default **C:\Users\Username\Downloads** with a **Freedom Loader Output** folder created) - [x] ~~Custom output path selection (default **C:\Users\Username\Downloads** with a **Freedom Loader Output** folder created)~~
--- ---
## 🖼️ Preview ## 🖼️ Preview
<p align="center"> <p align="center">
<img src="build/exemple.png" alt="Example Image" /> <img src="build/apercu1.0.1.png" alt="Exemple d'application" />
</p> </p>
> Here is an example of the app after launch and entering a URL > Here is an example of the app after launch and entering a URL

View File

@@ -1,16 +1,26 @@
# Freedom Loader # Freedom Loader
![Static Badge](https://img.shields.io/badge/Latest%20Release_-1.0.1-blue?style=flat) ![Static Badge](https://img.shields.io/badge/Build-1.0.2%20Beta-yellow?style=flat) <!-- Release -->
![Static Badge](https://img.shields.io/badge/Release-1.1.3-blue?style=flat&logo=Test)
<!-- Build (Next Release) -->
![Static Badge](https://img.shields.io/badge/Build-1.2--beta-yellow?style=flat&logo=Test)
> Ce projet a pour but de permettre le téléchargement de contenu Vidéo/Audio de manière gratuite, open source et libre. Il permettra à terme de choisir > Ce projet a pour but de permettre le téléchargement de contenu Vidéo/Audio de manière gratuite, open source et libre. Il permettra à terme de choisir
## 🚀 Fonctionnalités ## 🚀 Fonctionnalités
✅ Téléchargement Vidéo (MP4) - ✅ Téléchargement de vidéos avec les métadonnées (MP4)
✅ Téléchargement Audio (MP3) - ✅ Téléchargement audio uniquement, avec la couverture ainsi que les métadonnées (MP3)
✅ Logs détaillés (console + fichiers logs) - ✅ Support des playlists complètes
✅ Récupération automatique des informations - ✅ Récupération automatique des métadonnées (titre, durée, etc.)
✅ Téléchargement des playlists - ✅ Logs détaillés : console et fichiers rotatifs (7 jours)
- ✅ Choix du chemin de sortie personnalisé avec sauvegarde
- ✅ Plusieurs thèmes graphiques au choix pour personnaliser linterface
- ✅ Interface simple, intuitive
- ✅ Notification quand le téléchargement est fini (Click = Ouverture Dossier Output)
--- ---
@@ -47,14 +57,14 @@ Essayez de relancer l'application, de consulter les logs ou d'ouvrir la console
- [ ] Extraction / découpage de la vidéo via chapitrage natif - [ ] Extraction / découpage de la vidéo via chapitrage natif
- [ ] Téléchargements parallèles - [ ] Téléchargements parallèles
- [ ] Suppression automatique des parties sponsorisées (skip sponsor) - [ ] Suppression automatique des parties sponsorisées (skip sponsor)
- [ ] Choix personnalisé du chemin de sortie (par défaut **C:\Users\Utilisateur\Downloads**, où un dossier **Freedom Loader Output** sera créé) - [x] ~~Choix personnalisé du chemin de sortie (par défaut **C:\Users\Utilisateur\Downloads**, où un dossier **Freedom Loader Output** sera créé)~~
--- ---
## 🖼️ Aperçu ## 🖼️ Aperçu
<p align="center"> <p align="center">
<img src="build/exemple.png" alt="Exemple d'application" /> <img src="build/apercu1.0.1.png" alt="Exemple d'application" />
</p> </p>
> Voici un exemple de lapplication lancée après avoir saisi une URL. > Voici un exemple de lapplication lancée après avoir saisi une URL.

View File

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

BIN
build/apercu1.0.1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
build/confirm-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 146 KiB

110
main.js
View File

@@ -16,19 +16,29 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
const { app, BrowserWindow } = require("electron"); 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 path = require("path");
const { logger, logSessionStart, logSessionEnd } = require("./server/logger"); const os = require("os");
const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger");
let mainWindow; let mainWindow;
// Utilise le vrai dossier de logs défini dans logger.js
const logsFolderPath = logDir;
/*
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() { async function createWindow() {
logger.info("Creation de la fenetre..."); logger.info("Création de la fenêtre...");
if (mainWindow) { if (mainWindow) {
logger.warn("La fenetre existe deja, pas de nouvelle creation"); logger.warn("La fenêtre existe déjà, pas de nouvelle création");
return; return;
} }
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
title: "Freedom Loader", title: "Freedom Loader",
width: 750, width: 750,
@@ -38,42 +48,118 @@ async function createWindow() {
webPreferences: { webPreferences: {
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
preload: path.join(__dirname, "preload.js"), // Chargement du preload script sécurisé
}, },
// titleBarStyle: 'hidden', // titleBarStyle: 'hidden', // Option possible pour une barre de titre personnalisée
}); });
try { try {
await mainWindow.loadURL("http://localhost:8080"); await mainWindow.loadURL("http://localhost:8080");
logger.info("Fenetre chargee"); logger.info("Fenêtre chargée");
} catch (err) { } catch (err) {
logger.error("Erreur chargement fenetre:", err); logger.error("Erreur chargement fenêtre:", err);
} }
// Événement déclenché à la fermeture de la fenêtre principale
mainWindow.on("closed", () => { mainWindow.on("closed", () => {
logger.info("Fenetre principale fermee"); logger.info("Fenêtre principale fermée");
mainWindow = null; 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 () => { app.whenReady().then(async () => {
logSessionStart(); logSessionStart();
logger.info("App prete, demarrage du serveur Express..."); logger.info("App prête, démarrage du serveur Express...");
const expressServer = require("./server/server.js"); const expressServer = require("./server/server.js");
try { try {
await expressServer.startServer(); await expressServer.startServer();
logger.info("Serveur Express demarre"); logger.info("Serveur Express démarré");
await createWindow(); 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) { } catch (error) {
logger.error("Erreur serveur ou fenetre :", error); logger.error("Erreur serveur ou fenêtre :", error);
app.quit(); 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", () => { app.on("window-all-closed", () => {
logger.info("Toutes fenetres fermees, quitte l'app"); logger.info("Toutes fenêtres fermées, quitte l'app");
if (process.platform !== "darwin") app.quit(); if (process.platform !== "darwin") app.quit();
}); });
/*
Avant de quitter l'application, on log la fin de session pour traçabilité.
*/
app.on("before-quit", () => { app.on("before-quit", () => {
logSessionEnd(); logSessionEnd();
}); });

362
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "freedom-loader", "name": "freedom-loader",
"version": "1.0.1", "version": "1.1.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "freedom-loader", "name": "freedom-loader",
"version": "1.0.1", "version": "1.1.3",
"dependencies": { "dependencies": {
"chalk": "^4.1.2", "chalk": "^4.1.2",
"debug": "^4.4.1", "debug": "^4.4.1",
@@ -67,9 +67,9 @@
} }
}, },
"node_modules/@electron-forge/cli": { "node_modules/@electron-forge/cli": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/cli/-/cli-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/cli/-/cli-7.8.3.tgz",
"integrity": "sha512-QI3EShutfq9Y+2TWWrPjm4JZM3eSAKzoQvRZdVhAfVpUbyJ8K23VqJShg3kGKlPf9BXHAGvE+8LyH5s2yDr1qA==", "integrity": "sha512-BSAjGGfVf0yp3NQhXYmyCw9T//YCQHuktMv4HXfDVfo7AoV6DA1oEhqldI4Q7aHKeRob5+yBkvRRYPiu5ayCPw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -83,9 +83,9 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/core": "7.8.1", "@electron-forge/core": "7.8.3",
"@electron-forge/core-utils": "7.8.1", "@electron-forge/core-utils": "7.8.3",
"@electron-forge/shared-types": "7.8.1", "@electron-forge/shared-types": "7.8.3",
"@electron/get": "^3.0.0", "@electron/get": "^3.0.0",
"chalk": "^4.0.0", "chalk": "^4.0.0",
"commander": "^11.1.0", "commander": "^11.1.0",
@@ -105,9 +105,9 @@
} }
}, },
"node_modules/@electron-forge/core": { "node_modules/@electron-forge/core": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/core/-/core-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/core/-/core-7.8.3.tgz",
"integrity": "sha512-jkh0QPW5p0zmruu1E8+2XNufc4UMxy13WLJcm7hn9jbaXKLkMbKuEvhrN1tH/9uGp1mhr/t8sC4N67gP+gS87w==", "integrity": "sha512-qX2vi/LP3HcSqSfLfzMeH2ll8SFZQnOk8VN/b3bq6XrBCbrfrSsTYYWakN6mmfalLJcQRm4jCEc6gcyuGO4i6Q==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -121,17 +121,17 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/core-utils": "7.8.1", "@electron-forge/core-utils": "7.8.3",
"@electron-forge/maker-base": "7.8.1", "@electron-forge/maker-base": "7.8.3",
"@electron-forge/plugin-base": "7.8.1", "@electron-forge/plugin-base": "7.8.3",
"@electron-forge/publisher-base": "7.8.1", "@electron-forge/publisher-base": "7.8.3",
"@electron-forge/shared-types": "7.8.1", "@electron-forge/shared-types": "7.8.3",
"@electron-forge/template-base": "7.8.1", "@electron-forge/template-base": "7.8.3",
"@electron-forge/template-vite": "7.8.1", "@electron-forge/template-vite": "7.8.3",
"@electron-forge/template-vite-typescript": "7.8.1", "@electron-forge/template-vite-typescript": "7.8.3",
"@electron-forge/template-webpack": "7.8.1", "@electron-forge/template-webpack": "7.8.3",
"@electron-forge/template-webpack-typescript": "7.8.1", "@electron-forge/template-webpack-typescript": "7.8.3",
"@electron-forge/tracer": "7.8.1", "@electron-forge/tracer": "7.8.3",
"@electron/get": "^3.0.0", "@electron/get": "^3.0.0",
"@electron/packager": "^18.3.5", "@electron/packager": "^18.3.5",
"@electron/rebuild": "^3.7.0", "@electron/rebuild": "^3.7.0",
@@ -161,13 +161,13 @@
} }
}, },
"node_modules/@electron-forge/core-utils": { "node_modules/@electron-forge/core-utils": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/core-utils/-/core-utils-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/core-utils/-/core-utils-7.8.3.tgz",
"integrity": "sha512-mRoPLDNZgmjyOURE/K0D3Op53XGFmFRgfIvFC7c9S/BqsRpovVblrqI4XxPRdNmH9dvhd8On9gGz+XIYAKD3aQ==", "integrity": "sha512-8jhK7AvUKEqDyTMMuRhvS1TkE73YEnHYAxVvkJq35e1KdCwGbGnSRhaEs7OOqRfkKxfepdJ7BMKFSoKFeP+z0Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/shared-types": "7.8.1", "@electron-forge/shared-types": "7.8.3",
"@electron/rebuild": "^3.7.0", "@electron/rebuild": "^3.7.0",
"@malept/cross-spawn-promise": "^2.0.0", "@malept/cross-spawn-promise": "^2.0.0",
"chalk": "^4.0.0", "chalk": "^4.0.0",
@@ -182,13 +182,13 @@
} }
}, },
"node_modules/@electron-forge/maker-base": { "node_modules/@electron-forge/maker-base": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/maker-base/-/maker-base-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/maker-base/-/maker-base-7.8.3.tgz",
"integrity": "sha512-GUZqschGuEBzSzE0bMeDip65IDds48DZXzldlRwQ+85SYVA6RMU2AwDDqx3YiYsvP2OuxKruuqIJZtOF5ps4FQ==", "integrity": "sha512-WmF66cHdziaK8Asi7IRTLxZjCZ8IqXXHr6IPl4d5oatN6s5RG+HHzG1hiJ7LzlOEntqdSpE8Wh2nB2TmyR4huQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/shared-types": "7.8.1", "@electron-forge/shared-types": "7.8.3",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"which": "^2.0.2" "which": "^2.0.2"
}, },
@@ -197,14 +197,14 @@
} }
}, },
"node_modules/@electron-forge/maker-deb": { "node_modules/@electron-forge/maker-deb": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/maker-deb/-/maker-deb-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/maker-deb/-/maker-deb-7.8.3.tgz",
"integrity": "sha512-tjjeesQtCP5Xht1X7gl4+K9bwoETPmQfBkOVAY/FZIxPj40uQh/hOUtLX2tYENNGNVZ1ryDYRs8TuPi+I41Vfw==", "integrity": "sha512-GZHJU06LRFJkv1wMc3bjMVzedQhyxnyMZqgDJjE9TIlXXZkN2EqGmKLw/HxwNRA1R8yuQeA4Ih6/dEB4bZTevg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/maker-base": "7.8.1", "@electron-forge/maker-base": "7.8.3",
"@electron-forge/shared-types": "7.8.1" "@electron-forge/shared-types": "7.8.3"
}, },
"engines": { "engines": {
"node": ">= 16.4.0" "node": ">= 16.4.0"
@@ -214,14 +214,14 @@
} }
}, },
"node_modules/@electron-forge/maker-rpm": { "node_modules/@electron-forge/maker-rpm": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/maker-rpm/-/maker-rpm-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/maker-rpm/-/maker-rpm-7.8.3.tgz",
"integrity": "sha512-TF6wylft3BHkw9zdHcxmjEPBZYgTIc0jE31skFnMEQ/aExbNRiNaCZvsXy+7ptTWZxhxUKRc9KHhLFRMCmOK8g==", "integrity": "sha512-TqHHdF5D3Dy4NgG+pi8eRc18P1D0FF1LE+CqQF4MRof+XrpHhRtog5Ac4gWjvg1Wma6WjPyTdryHAkURsoBbuQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/maker-base": "7.8.1", "@electron-forge/maker-base": "7.8.3",
"@electron-forge/shared-types": "7.8.1" "@electron-forge/shared-types": "7.8.3"
}, },
"engines": { "engines": {
"node": ">= 16.4.0" "node": ">= 16.4.0"
@@ -231,14 +231,14 @@
} }
}, },
"node_modules/@electron-forge/maker-squirrel": { "node_modules/@electron-forge/maker-squirrel": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/maker-squirrel/-/maker-squirrel-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/maker-squirrel/-/maker-squirrel-7.8.3.tgz",
"integrity": "sha512-qT1PMvT7ALF0ONOkxlA0oc0PiFuKCAKgoMPoxYo9gGOqFvnAb+TBcnLxflQ4ashE/ZkrHpykr4LcDJxqythQTA==", "integrity": "sha512-6XSEhZMbgfjAaCm8A54pNjn4ghfxJgPu4i7ok3PhP44WOrFPaPivLttpvKRnxRb0PGZstPjPBFcwL1F9S1trjA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/maker-base": "7.8.1", "@electron-forge/maker-base": "7.8.3",
"@electron-forge/shared-types": "7.8.1", "@electron-forge/shared-types": "7.8.3",
"fs-extra": "^10.0.0" "fs-extra": "^10.0.0"
}, },
"engines": { "engines": {
@@ -249,14 +249,14 @@
} }
}, },
"node_modules/@electron-forge/maker-zip": { "node_modules/@electron-forge/maker-zip": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/maker-zip/-/maker-zip-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/maker-zip/-/maker-zip-7.8.3.tgz",
"integrity": "sha512-unIxEoV1lnK4BLVqCy3L2y897fTyg8nKY1WT4rrpv0MUKnQG4qmigDfST5zZNNHHaulEn/ElAic2GEiP7d6bhQ==", "integrity": "sha512-ytao285wKAjKBO6eULzLeqUDP5Zh7beQlGyHjgOMknk7FI0sNy+zGdh3CrCGIhkXSHU/DpukPwRu2SiKvIaIGA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/maker-base": "7.8.1", "@electron-forge/maker-base": "7.8.3",
"@electron-forge/shared-types": "7.8.1", "@electron-forge/shared-types": "7.8.3",
"cross-zip": "^4.0.0", "cross-zip": "^4.0.0",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"got": "^11.8.5" "got": "^11.8.5"
@@ -266,70 +266,70 @@
} }
}, },
"node_modules/@electron-forge/plugin-auto-unpack-natives": { "node_modules/@electron-forge/plugin-auto-unpack-natives": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/plugin-auto-unpack-natives/-/plugin-auto-unpack-natives-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/plugin-auto-unpack-natives/-/plugin-auto-unpack-natives-7.8.3.tgz",
"integrity": "sha512-4URAgWX9qqqKe6Bfad0VmpFRrwINYMODfKGd2nFQrfHxmBtdpXnsWlLwVGE/wGssIQaTMI5bWQ6F2RNeXTgnhA==", "integrity": "sha512-7tRhySvfdTrBC4PEhAzjzjJGT5dBSU6jhy3XlQ8LJH50jHIT9bioQq6dfn/3iw/VaD1ftGWKx+HlntYCmBhViw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/plugin-base": "7.8.1", "@electron-forge/plugin-base": "7.8.3",
"@electron-forge/shared-types": "7.8.1" "@electron-forge/shared-types": "7.8.3"
}, },
"engines": { "engines": {
"node": ">= 16.4.0" "node": ">= 16.4.0"
} }
}, },
"node_modules/@electron-forge/plugin-base": { "node_modules/@electron-forge/plugin-base": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/plugin-base/-/plugin-base-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/plugin-base/-/plugin-base-7.8.3.tgz",
"integrity": "sha512-iCZC2d7CbsZ9l6j5d+KPIiyQx0U1QBfWAbKnnQhWCSizjcrZ7A9V4sMFZeTO6+PVm48b/r9GFPm+slpgZtYQLg==", "integrity": "sha512-0CzPQlO3BGu5bLCrx2Xqo6B1yoHhA9wG9boZE58ANr8Qma1NQfAWZU3LnMmF3EdWNTX76PxpZeeb3QbPNcxSuA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/shared-types": "7.8.1" "@electron-forge/shared-types": "7.8.3"
}, },
"engines": { "engines": {
"node": ">= 16.4.0" "node": ">= 16.4.0"
} }
}, },
"node_modules/@electron-forge/plugin-fuses": { "node_modules/@electron-forge/plugin-fuses": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/plugin-fuses/-/plugin-fuses-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/plugin-fuses/-/plugin-fuses-7.8.3.tgz",
"integrity": "sha512-dYTwvbV1HcDOIQ0wTybpdtPq6YoBYXIWBTb7DJuvFu/c/thj1eoEdnbwr8mT9hEivjlu5p4ls46n16P5EtZ0oA==", "integrity": "sha512-OPWfoVsQ3xzK7GB3C6zoGlUu21RCOEQAm5+zg9VP1HJg5dsrKkqU8A2DsHGxe7PHthUCuQUgcls0ciDOHvckCQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/plugin-base": "7.8.1", "@electron-forge/plugin-base": "7.8.3",
"@electron-forge/shared-types": "7.8.1" "@electron-forge/shared-types": "7.8.3"
}, },
"engines": { "engines": {
"node": ">= 16.4.0" "node": ">= 16.4.0"
}, },
"peerDependencies": { "peerDependencies": {
"@electron/fuses": ">=1.0.0" "@electron/fuses": "^1.0.0"
} }
}, },
"node_modules/@electron-forge/publisher-base": { "node_modules/@electron-forge/publisher-base": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/publisher-base/-/publisher-base-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/publisher-base/-/publisher-base-7.8.3.tgz",
"integrity": "sha512-z2C+C4pcFxyCXIFwXGDcxhU8qtVUPZa3sPL6tH5RuMxJi77768chLw2quDWk2/dfupcSELXcOMYCs7aLysCzeQ==", "integrity": "sha512-kurRKVNyLsK2JgmVl88UHu0+qSH+PysMtk/xP0YX5sYNMVxRay8+S1T10tAh6Qom94KwpF3CLYtkebvxb/fq+A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/shared-types": "7.8.1" "@electron-forge/shared-types": "7.8.3"
}, },
"engines": { "engines": {
"node": ">= 16.4.0" "node": ">= 16.4.0"
} }
}, },
"node_modules/@electron-forge/shared-types": { "node_modules/@electron-forge/shared-types": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/shared-types/-/shared-types-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/shared-types/-/shared-types-7.8.3.tgz",
"integrity": "sha512-guLyGjIISKQQRWHX+ugmcjIOjn2q/BEzCo3ioJXFowxiFwmZw/oCZ2KlPig/t6dMqgUrHTH5W/F0WKu0EY4M+Q==", "integrity": "sha512-gkZtD7ALXHPDOthJo1rQYLDNfG09fdDRMWvjEgaXdF3Z69xXFfnOWPNuOkRUODNalMnuuGs6l7jDl+QFQgHlDg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/tracer": "7.8.1", "@electron-forge/tracer": "7.8.3",
"@electron/packager": "^18.3.5", "@electron/packager": "^18.3.5",
"@electron/rebuild": "^3.7.0", "@electron/rebuild": "^3.7.0",
"listr2": "^7.0.2" "listr2": "^7.0.2"
@@ -339,14 +339,14 @@
} }
}, },
"node_modules/@electron-forge/template-base": { "node_modules/@electron-forge/template-base": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/template-base/-/template-base-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/template-base/-/template-base-7.8.3.tgz",
"integrity": "sha512-k8jEUr0zWFWb16ZGho+Es2OFeKkcbTgbC6mcH4eNyF/sumh/4XZMcwRtX1i7EiZAYiL9sVxyI6KVwGu254g+0g==", "integrity": "sha512-C0tVODDNKoqhCf7T1HRONJs9DKAmjmk8Of0t8rVjT0ERDzMvLGlBByd785v5lFlKbGERyoaXsYltxPAu92G3aA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/core-utils": "7.8.1", "@electron-forge/core-utils": "7.8.3",
"@electron-forge/shared-types": "7.8.1", "@electron-forge/shared-types": "7.8.3",
"@malept/cross-spawn-promise": "^2.0.0", "@malept/cross-spawn-promise": "^2.0.0",
"debug": "^4.3.1", "debug": "^4.3.1",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
@@ -357,14 +357,14 @@
} }
}, },
"node_modules/@electron-forge/template-vite": { "node_modules/@electron-forge/template-vite": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/template-vite/-/template-vite-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/template-vite/-/template-vite-7.8.3.tgz",
"integrity": "sha512-qzSlJaBYYqQAbBdLk4DqAE3HCNz4yXbpkb+VC74ddL4JGwPdPU57DjCthr6YetKJ2FsOVy9ipovA8HX5UbXpAg==", "integrity": "sha512-tDL5h+UO5iOzdEYNVsSu4zkUVN4RTxti5iXzcBquKd9Kgt/A/M7xHeuKj7g1Ds7Ul/n2XcFvcfgLcRmiXQeVDA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/shared-types": "7.8.1", "@electron-forge/shared-types": "7.8.3",
"@electron-forge/template-base": "7.8.1", "@electron-forge/template-base": "7.8.3",
"fs-extra": "^10.0.0" "fs-extra": "^10.0.0"
}, },
"engines": { "engines": {
@@ -372,14 +372,14 @@
} }
}, },
"node_modules/@electron-forge/template-vite-typescript": { "node_modules/@electron-forge/template-vite-typescript": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/template-vite-typescript/-/template-vite-typescript-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/template-vite-typescript/-/template-vite-typescript-7.8.3.tgz",
"integrity": "sha512-CccQhwUjZcc6svzuOi3BtbDal591DzyX2J5GPa6mwVutDP8EMtqJL1VyOHdcWO/7XjI6GNAD0fiXySOJiUAECA==", "integrity": "sha512-HJjjY9xmlpl0vx10mrXdWn2RYHExfazACxmDNNmGO1eq3eqrQk/3R+NDGyqMJ7ajBsRVbkQNt+wayH7HkRJUjA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/shared-types": "7.8.1", "@electron-forge/shared-types": "7.8.3",
"@electron-forge/template-base": "7.8.1", "@electron-forge/template-base": "7.8.3",
"fs-extra": "^10.0.0" "fs-extra": "^10.0.0"
}, },
"engines": { "engines": {
@@ -387,14 +387,14 @@
} }
}, },
"node_modules/@electron-forge/template-webpack": { "node_modules/@electron-forge/template-webpack": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/template-webpack/-/template-webpack-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/template-webpack/-/template-webpack-7.8.3.tgz",
"integrity": "sha512-DA77o9kTCHrq+W211pyNP49DyAt0d1mzMp2gisyNz7a+iKvlv2DsMAeRieLoCQ44akb/z8ZsL0YLteSjKLy4AA==", "integrity": "sha512-1zkji5px1kDXbigFN5959anCf3HAVBvam3bHh2VejnCScegLQP3JBjAn2Nw2ILWq6ej5JNuA/V99b1dr11hPOw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/shared-types": "7.8.1", "@electron-forge/shared-types": "7.8.3",
"@electron-forge/template-base": "7.8.1", "@electron-forge/template-base": "7.8.3",
"fs-extra": "^10.0.0" "fs-extra": "^10.0.0"
}, },
"engines": { "engines": {
@@ -402,14 +402,14 @@
} }
}, },
"node_modules/@electron-forge/template-webpack-typescript": { "node_modules/@electron-forge/template-webpack-typescript": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/template-webpack-typescript/-/template-webpack-typescript-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/template-webpack-typescript/-/template-webpack-typescript-7.8.3.tgz",
"integrity": "sha512-h922E+6zWwym1RT6WKD79BLTc4H8YxEMJ7wPWkBX59kw/exsTB/KFdiJq6r82ON5jSJ+Q8sDGqSmDWdyCfo+Gg==", "integrity": "sha512-Xz3X7YJvot08Xm+0BLIS28GJH+0z9vEN9xYX76SOL4jqDcHH8lRFpNWcE2HsxxxRbCVH+s0etVmACZcWoujUrw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron-forge/shared-types": "7.8.1", "@electron-forge/shared-types": "7.8.3",
"@electron-forge/template-base": "7.8.1", "@electron-forge/template-base": "7.8.3",
"fs-extra": "^10.0.0" "fs-extra": "^10.0.0"
}, },
"engines": { "engines": {
@@ -417,9 +417,9 @@
} }
}, },
"node_modules/@electron-forge/tracer": { "node_modules/@electron-forge/tracer": {
"version": "7.8.1", "version": "7.8.3",
"resolved": "https://registry.npmjs.org/@electron-forge/tracer/-/tracer-7.8.1.tgz", "resolved": "https://registry.npmjs.org/@electron-forge/tracer/-/tracer-7.8.3.tgz",
"integrity": "sha512-r2i7aHVp2fylGQSPDw3aTcdNfVX9cpL1iL2MKHrCRNwgrfR+nryGYg434T745GGm1rNQIv5Egdkh5G9xf00oWA==", "integrity": "sha512-YVVDaPEUOvR1z+DDdj8wR/vO9OSlC91wz4/2Iqe9rqQz8sztnnQBClMAZTqu9bkDTFyHrns8j8v7tPVuVS6ULQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -678,9 +678,9 @@
} }
}, },
"node_modules/@electron/packager": { "node_modules/@electron/packager": {
"version": "18.3.6", "version": "18.4.2",
"resolved": "https://registry.npmjs.org/@electron/packager/-/packager-18.3.6.tgz", "resolved": "https://registry.npmjs.org/@electron/packager/-/packager-18.4.2.tgz",
"integrity": "sha512-1eXHB5t+SQKvUiDpWGpvr90ZSSbXj+isrh3YbjCTjKT4bE4SQrKSBfukEAaBvp67+GXHFtCHjQgN9qSTFIge+Q==", "integrity": "sha512-5wr/6YoOhBlQoAloquKksY21Pa/2Eg4kq4y/+jOX+9smAFS4fcM3kG3KyhU/zEuwitxRjquq9/KxDNNhJbhc+w==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
@@ -690,6 +690,7 @@
"@electron/osx-sign": "^1.0.5", "@electron/osx-sign": "^1.0.5",
"@electron/universal": "^2.0.1", "@electron/universal": "^2.0.1",
"@electron/windows-sign": "^1.0.0", "@electron/windows-sign": "^1.0.0",
"@malept/cross-spawn-promise": "^2.0.0",
"debug": "^4.0.1", "debug": "^4.0.1",
"extract-zip": "^2.0.0", "extract-zip": "^2.0.0",
"filenamify": "^4.1.0", "filenamify": "^4.1.0",
@@ -699,6 +700,7 @@
"junk": "^3.1.0", "junk": "^3.1.0",
"parse-author": "^2.0.0", "parse-author": "^2.0.0",
"plist": "^3.0.0", "plist": "^3.0.0",
"prettier": "^3.4.2",
"resedit": "^2.0.0", "resedit": "^2.0.0",
"resolve": "^1.1.6", "resolve": "^1.1.6",
"semver": "^7.1.3", "semver": "^7.1.3",
@@ -715,9 +717,9 @@
} }
}, },
"node_modules/@electron/packager/node_modules/fs-extra": { "node_modules/@electron/packager/node_modules/fs-extra": {
"version": "11.3.0", "version": "11.3.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz",
"integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -788,9 +790,9 @@
} }
}, },
"node_modules/@electron/universal/node_modules/fs-extra": { "node_modules/@electron/universal/node_modules/fs-extra": {
"version": "11.3.0", "version": "11.3.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz",
"integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -839,9 +841,9 @@
} }
}, },
"node_modules/@electron/windows-sign/node_modules/fs-extra": { "node_modules/@electron/windows-sign/node_modules/fs-extra": {
"version": "11.3.0", "version": "11.3.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz",
"integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1153,9 +1155,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.15.34", "version": "22.17.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.34.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.2.tgz",
"integrity": "sha512-8Y6E5WUupYy1Dd0II32BsWAx5MWdcnRd8L84Oys3veg1YrYtNtzgO4CFhiBg6MDSjk7Ay36HYOnU7/tuOzIzcw==", "integrity": "sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1210,9 +1212,9 @@
} }
}, },
"node_modules/@xmldom/xmldom": { "node_modules/@xmldom/xmldom": {
"version": "0.8.10", "version": "0.8.11",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1247,9 +1249,9 @@
} }
}, },
"node_modules/agent-base": { "node_modules/agent-base": {
"version": "7.1.3", "version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1327,9 +1329,9 @@
} }
}, },
"node_modules/ansi-regex": { "node_modules/ansi-regex": {
"version": "6.1.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1535,9 +1537,9 @@
} }
}, },
"node_modules/app-builder-lib/node_modules/@electron/universal/node_modules/fs-extra": { "node_modules/app-builder-lib/node_modules/@electron/universal/node_modules/fs-extra": {
"version": "11.3.0", "version": "11.3.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz",
"integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2792,9 +2794,9 @@
} }
}, },
"node_modules/electron": { "node_modules/electron": {
"version": "37.1.0", "version": "37.3.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-37.1.0.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-37.3.1.tgz",
"integrity": "sha512-Fcr3yfAw4oU392waVZSlrFUQx4P+h/k31+PRgkBY9tFx9E/zxzdPQQj0achZlG1HRDusw3ooQB+OXb9PvufdzA==", "integrity": "sha512-7DhktRLqhe6OJh/Bo75bTI0puUYEmIwSzMinocgO63mx3MVjtIn2tYMzLmAleNIlud2htkjpsMG2zT4PiTCloA==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
@@ -3976,9 +3978,9 @@
} }
}, },
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.3", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -4622,15 +4624,11 @@
} }
}, },
"node_modules/ip-address": { "node_modules/ip-address": {
"version": "9.0.5", "version": "10.0.1",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": {
"jsbn": "1.1.0",
"sprintf-js": "^1.1.3"
},
"engines": { "engines": {
"node": ">= 12" "node": ">= 12"
} }
@@ -4806,16 +4804,15 @@
} }
}, },
"node_modules/jake": { "node_modules/jake": {
"version": "10.9.2", "version": "10.9.4",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
"integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"async": "^3.2.3", "async": "^3.2.6",
"chalk": "^4.0.2",
"filelist": "^1.0.4", "filelist": "^1.0.4",
"minimatch": "^3.1.2" "picocolors": "^1.1.1"
}, },
"bin": { "bin": {
"jake": "bin/cli.js" "jake": "bin/cli.js"
@@ -4825,9 +4822,9 @@
} }
}, },
"node_modules/jiti": { "node_modules/jiti": {
"version": "2.4.2", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@@ -4847,13 +4844,6 @@
"js-yaml": "bin/js-yaml.js" "js-yaml": "bin/js-yaml.js"
} }
}, },
"node_modules/jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
"dev": true,
"license": "MIT"
},
"node_modules/json-buffer": { "node_modules/json-buffer": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -4890,9 +4880,9 @@
} }
}, },
"node_modules/jsonfile": { "node_modules/jsonfile": {
"version": "6.1.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -6150,6 +6140,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@@ -6214,6 +6211,22 @@
"node": "^12.20.0 || >=14" "node": "^12.20.0 || >=14"
} }
}, },
"node_modules/prettier": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/proc-log": { "node_modules/proc-log": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz",
@@ -6989,13 +7002,13 @@
} }
}, },
"node_modules/socks": { "node_modules/socks": {
"version": "2.8.5", "version": "2.8.7",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
"integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ip-address": "^9.0.5", "ip-address": "^10.0.1",
"smart-buffer": "^4.2.0" "smart-buffer": "^4.2.0"
}, },
"engines": { "engines": {
@@ -7082,9 +7095,9 @@
} }
}, },
"node_modules/spdx-license-ids": { "node_modules/spdx-license-ids": {
"version": "3.0.21", "version": "3.0.22",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz",
"integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==",
"dev": true, "dev": true,
"license": "CC0-1.0" "license": "CC0-1.0"
}, },
@@ -7093,7 +7106,8 @@
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"dev": true, "dev": true,
"license": "BSD-3-Clause" "license": "BSD-3-Clause",
"optional": true
}, },
"node_modules/ssri": { "node_modules/ssri": {
"version": "9.0.1", "version": "9.0.1",
@@ -7466,9 +7480,9 @@
"optional": true "optional": true
}, },
"node_modules/tmp": { "node_modules/tmp": {
"version": "0.2.3", "version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -7584,9 +7598,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.8.3", "version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {

View File

@@ -1,9 +1,9 @@
{ {
"name": "freedom-loader", "name": "freedom-loader",
"productName": "Freedom Loader", "productName": "Freedom Loader",
"version": "1.0.1", "version": "1.1.3",
"author": "MasterAcnolo", "author": "MasterAcnolo",
"description": "Freedom Loader, parce que nous le valons bien", "description": "Freedom Loader",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"start": "electron-forge start", "start": "electron-forge start",
@@ -36,12 +36,19 @@
"productName": "Freedom Loader", "productName": "Freedom Loader",
"files": [ "files": [
"**/*", "**/*",
"node_modules/**/*" "node_modules/**/*",
"!logs/*.json"
], ],
"directories": { "directories": {
"buildResources": "build" "buildResources": "build"
}, },
"asar": false, "asar": false,
"extraResources": [
{
"from": "build/confirm-icon.png",
"to": "confirm-icon.png"
}
],
"win": { "win": {
"target": "nsis", "target": "nsis",
"icon": "build/installer-icon.ico" "icon": "build/installer-icon.ico"

31
preload.js Normal file
View File

@@ -0,0 +1,31 @@
/*
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 <https://www.gnu.org/licenses/>.
*/
const { contextBridge, ipcRenderer } = require("electron");
/*
Expose dans le contexte global (window.electronAPI) deux méthodes sécurisées
pour que le renderer puisse interagir avec le main process via IPC.
- getDefaultDownloadPath : récupère le chemin de téléchargement par défaut.
- selectDownloadFolder : ouvre la boîte de dialogue pour choisir un dossier.
*/
contextBridge.exposeInMainWorld("electronAPI", {
getDefaultDownloadPath: () => ipcRenderer.invoke("get-default-download-path"),
selectDownloadFolder: () => ipcRenderer.invoke("select-download-folder"),
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -19,11 +19,11 @@
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<header> <header>
<h1> Freedom Loader </h1> <h1 id="title"> Freedom Loader </h1>
<p> Because why not? </p> <p id="subtitle"> Because why not? </p>
</header> </header>
<form action="/download" method="POST" id="downloadForm"> <form action="/download" method="POST" id="downloadForm">
@@ -51,24 +51,40 @@
<button type="submit">Télécharger</button> <button type="submit">Télécharger</button>
</form> </form>
<div id="downloadStatus"></div>
<!--Il faut utiliser un formulaire, parce que on doit envoyer a un serveur <!--Il faut utiliser un formulaire, parce que on doit envoyer a un serveur
les données à traiter. Pour cela on définit une route vers /download, ca va les données à traiter. Pour cela on définit une route vers /download, ca va
être l'espèce de "canal" attribué a ça. Ca va faire que si jamais on veut ensuite recup être l'espèce de "canal" attribué a ça. Ca va faire que si jamais on veut ensuite recup
les infos du form, il faut aller sur ce canal précis (/download). La méthode POST dit les infos du form, il faut aller sur ce canal précis (/download). La méthode POST dit
que l'on envoie des données.--> que l'on envoie des données.-->
</div> </div>
<div class="download-path">
Sera enregistré dans <span id="savePath"></span>
<button id="changePath">Modifier</button>
</div>
<div id="downloadStatus"></div>
<div class="infos-container"> <div class="infos-container">
<div id="videoInfo" style="margin-top: 20px;"></div> <div id="videoInfo" style="margin-top: 20px;"></div>
</div> </div>
<footer class="theme-switcher">
<label for="themeSelect">Thème :</label>
<select id="themeSelect" aria-label="Choisir le thème">
<!-- <option value="dark">Sombre</option>
<option value="light">Clair</option>
<option value="Chirac">Chirac</option>
<option value="Fanatic">Fanatic</option> -->
</select>
</footer>
<script src="script/custompath.js"></script>
<script src="script/downloadstatus.js"></script> <script src="script/downloadstatus.js"></script>
<script src="script/fetchinfo.js"></script> <script src="script/fetchinfo.js"></script>
<script src="script/chirac.js"></script>
<script src="script/customthemes.js"></script>
</body> </body>
</html> </html>

33
public/script/chirac.js Normal file
View File

@@ -0,0 +1,33 @@
/*
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 <https://www.gnu.org/licenses/>.
*/
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");
}

View File

@@ -0,0 +1,71 @@
/*
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 <https://www.gnu.org/licenses/>.
*/
/*
Ce script attend que le DOM soit complètement chargé pour initialiser
l'affichage et la gestion du chemin de sauvegarde personnalisé.
1. Récupère le chemin de téléchargement par défaut depuis le main process via l'API exposée.
2. Met à jour le texte affiché dans l'élément avec l'id "savePath".
3. Crée un input caché nommé "savePath" dans le formulaire pour envoyer ce chemin lors du submit.
4. Ajoute un écouteur sur le bouton "changePath" pour permettre à l'utilisateur
de choisir un dossier via une boîte de dialogue native.
5. Met à jour l'affichage et la valeur cachée du chemin sélectionné.
*/
window.addEventListener("DOMContentLoaded", async () => {
const savePathElem = document.getElementById("savePath");
// 1Essayer de charger depuis le localStorage
let savedPath = localStorage.getItem("customDownloadPath");
// 2Sinon demander le chemin par défaut à l'API Electron
if (!savedPath) {
savedPath = await window.electronAPI.getDefaultDownloadPath();
}
// 3 Afficher le chemin
if (savePathElem) {
savePathElem.textContent = savedPath;
}
// Créer l'input caché s'il n'existe pas déjà
let hidden = document.getElementById("savePathInput");
if (!hidden) {
hidden = document.createElement("input");
hidden.type = "hidden";
hidden.name = "savePath";
hidden.id = "savePathInput";
document.getElementById("downloadForm").appendChild(hidden);
}
hidden.value = savedPath;
// Gestion du bouton de modification
document.getElementById("changePath").addEventListener("click", async () => {
const selectedPath = await window.electronAPI.selectDownloadFolder();
if (selectedPath) {
// Met à jour l'affichage
savePathElem.textContent = selectedPath;
hidden.value = selectedPath;
// Et le stocke en localStorage pour la prochaine fois
localStorage.setItem("customDownloadPath", selectedPath);
}
});
});

View File

@@ -0,0 +1,105 @@
/*
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 <https://www.gnu.org/licenses/>.
*/
// Liste des thèmes disponibles (clé = nom, valeur = label affiché)
const themes = {
default: "Default",
dark: "Sombre",
light: "Clair",
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 ?",
chirac: "J'aime les pommes",
fanatic: "Always Fnatic !",
cyberpunk: "Wake up, choom. Weve 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 lutilisateur change le select
themeSelect.addEventListener("change", (e) => {
const selected = e.target.value;
if (themes[selected]) {
applyTheme(selected);
saveTheme(selected);
}
});
// Initialisation
populateThemeSelect();
loadTheme();

View File

@@ -19,53 +19,53 @@
// Récupération du formulaire de téléchargement // Récupération du formulaire de téléchargement
const form = document.getElementById("downloadForm"); const form = document.getElementById("downloadForm");
// Div où on affiche l'état de progression (en cours, erreur, succès) // Élément où on affichera létat du téléchargement (en cours, erreur, succès)
const statusDiv = document.getElementById("downloadStatus"); const statusDiv = document.getElementById("downloadStatus");
// Récupération du bouton de validation pour pouvoir le désactiver pendant la requête // Récupération du bouton de soumission pour pouvoir le désactiver pendant la requête
const button = form.querySelector("button"); const button = form.querySelector("button");
// On intercepte la soumission du formulaire // Écouteur dévénement pour intercepter la soumission du formulaire
form.addEventListener("submit", async (e) => { form.addEventListener("submit", async (e) => {
e.preventDefault(); // empêche le rechargement de la page par le navigateur e.preventDefault(); // Empêche le rechargement automatique de la page
// On désactive le bouton pour éviter les clics multiples // Désactive le bouton pour éviter les clics multiples pendant la requête
button.disabled = true; button.disabled = true;
// Affiche un message d'attente à l'utilisateur // Affiche un message dattente pour informer lutilisateur
statusDiv.textContent = "Téléchargement en cours..."; statusDiv.textContent = "Téléchargement en cours...";
// Prépare les données du formulaire sous forme URL-encoded // Prépare les données du formulaire au format URL-encoded
const formData = new FormData(form); const formData = new FormData(form);
const params = new URLSearchParams(formData); const params = new URLSearchParams(formData);
try { try {
// Envoi de la requête POST vers /download avec les données du formulaire // Envoie la requête POST vers /download avec les données du formulaire
const res = await fetch("/download", { const res = await fetch("/download", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" }, headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: params, body: params,
}); });
// Si la réponse n'est pas un succès (code 4xx ou 5xx) // Si la réponse nest pas un succès (code HTTP 4xx ou 5xx)
if (!res.ok) { if (!res.ok) {
statusDiv.textContent = "❌ Erreur pendant le téléchargement."; statusDiv.textContent = "❌ Erreur pendant le téléchargement.";
return; return;
} }
// On récupère le texte de la réponse du serveur // Récupère le texte envoyé par le serveur (message de succès ou erreur)
const text = await res.text(); const text = await res.text();
// Affiche le message de fin de téléchargement (par exemple « Téléchargement terminé ») // Affiche le message retourné par le serveur
statusDiv.textContent = text; statusDiv.textContent = text;
} catch { } catch {
// Gestion d'erreur en cas de problème réseau ou autre // Gestion des erreurs réseau ou autres exceptions
statusDiv.textContent = "❌ Une erreur sest produite."; statusDiv.textContent = "❌ Une erreur sest produite.";
} finally { } finally {
// Réactive le bouton pour autoriser d'autres soumissions // Réactive le bouton pour permettre dautres soumissions
button.disabled = false; button.disabled = false;
// Efface le message de status après 5 secondes pour garder l'UI propre // Efface le message d’état après 5 secondes pour garder linterface propre
setTimeout(() => { setTimeout(() => {
statusDiv.textContent = ""; statusDiv.textContent = "";
}, 5000); }, 5000);

View File

@@ -16,17 +16,25 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
// On attend que le DOM soit complètement chargé
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
// Récupération des éléments du DOM pour interaction
const urlInput = document.getElementById("UrlInput"); const urlInput = document.getElementById("UrlInput");
const infoDiv = document.getElementById("videoInfo"); const infoDiv = document.getElementById("videoInfo");
// Stocke la dernière URL fetchée pour éviter les requêtes répétées inutiles
let lastFetchedUrl = ""; let lastFetchedUrl = "";
// Active les logs DEBUG pour le développement
const DEBUG = true; const DEBUG = true;
// Écoute les changements dans l'input URL
urlInput.addEventListener("input", async () => { urlInput.addEventListener("input", async () => {
const url = urlInput.value.trim(); const url = urlInput.value.trim();
if (DEBUG) console.log("[DEBUG] Input détecté :", url); 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 (!url || url.length < 5) {
if (DEBUG) console.log("[DEBUG] URL vide ou trop courte, reset affichage."); if (DEBUG) console.log("[DEBUG] URL vide ou trop courte, reset affichage.");
infoDiv.innerHTML = ""; infoDiv.innerHTML = "";
@@ -35,15 +43,19 @@ document.addEventListener("DOMContentLoaded", () => {
return; return;
} }
// Si l'URL est identique à la dernière requêtée, on ne refait pas la requête
if (url === lastFetchedUrl) { if (url === lastFetchedUrl) {
if (DEBUG) console.log("[DEBUG] Même URL que précédemment, pas de fetch."); if (DEBUG) console.log("[DEBUG] Même URL que précédemment, pas de fetch.");
return; return;
} }
// Mémorise l'URL actuelle comme dernière URL traitée
lastFetchedUrl = url; lastFetchedUrl = url;
try { try {
if (DEBUG) console.log("[DEBUG] Envoi requête POST vers /info avec URL:", url); 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", { const res = await fetch("/info", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" }, headers: { "Content-Type": "application/x-www-form-urlencoded" },
@@ -52,6 +64,7 @@ document.addEventListener("DOMContentLoaded", () => {
if (DEBUG) console.log("[DEBUG] Réponse status:", res.status); 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) { if (!res.ok) {
console.error(`[ERROR] Réponse HTTP ${res.status}: ${res.statusText}`); console.error(`[ERROR] Réponse HTTP ${res.status}: ${res.statusText}`);
infoDiv.innerHTML = "❌ Impossible de récupérer les infos (erreur serveur)."; infoDiv.innerHTML = "❌ Impossible de récupérer les infos (erreur serveur).";
@@ -59,10 +72,11 @@ document.addEventListener("DOMContentLoaded", () => {
return; return;
} }
// Parse la réponse JSON contenant les infos vidéo
const data = await res.json(); const data = await res.json();
if (DEBUG) console.log("[DEBUG] Données reçues:", data); if (DEBUG) console.log("[DEBUG] Données reçues:", data);
// Vérification minimaliste // Vérification simple que les données attendues sont présentes
if (!data || !data.title || !data.thumbnail) { if (!data || !data.title || !data.thumbnail) {
console.warn("[WARNING] Données incomplètes", data); console.warn("[WARNING] Données incomplètes", data);
infoDiv.innerHTML = "❌ Données incomplètes reçues."; infoDiv.innerHTML = "❌ Données incomplètes reçues.";
@@ -70,23 +84,28 @@ document.addEventListener("DOMContentLoaded", () => {
return; return;
} }
// Formatage de la durée en minutes et secondes
const minutes = Math.floor(data.duration / 60); const minutes = Math.floor(data.duration / 60);
const seconds = data.duration % 60; const seconds = data.duration % 60;
const durationStr = `${minutes}m ${seconds.toString().padStart(2, "0")}s`; 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 date = data.upload_date || "";
const readableDate = date const readableDate = date
? `${date.slice(6, 8)}/${date.slice(4, 6)}/${date.slice(0, 4)}` ? `${date.slice(6, 8)}/${date.slice(4, 6)}/${date.slice(0, 4)}`
: "Inconnue"; : "Inconnue";
// Conversion taille approximative en Mo
const sizeMB = data.filesize_approx const sizeMB = data.filesize_approx
? (data.filesize_approx / (1024 * 1024)).toFixed(2) + " Mo" ? (data.filesize_approx / (1024 * 1024)).toFixed(2) + " Mo"
: "Inconnue"; : "Inconnue";
// Gestion des catégories, affichage par défaut si absentes
const categories = data.categories const categories = data.categories
? data.categories.join(", ") ? data.categories.join(", ")
: "Non spécifiées"; : "Non spécifiées";
// Construction du HTML pour afficher les informations de la vidéo
infoDiv.innerHTML = ` infoDiv.innerHTML = `
<h3>${data.title}</h3> <h3>${data.title}</h3>
<img src="${data.thumbnail}" width="320" alt="Thumbnail"> <img src="${data.thumbnail}" width="320" alt="Thumbnail">
@@ -102,10 +121,13 @@ document.addEventListener("DOMContentLoaded", () => {
<li><strong>Catégories :</strong> ${categories}</li> <li><strong>Catégories :</strong> ${categories}</li>
</ul> </ul>
`; `;
// Affiche la div contenant les infos
infoDiv.classList.add("visible"); infoDiv.classList.add("visible");
if (DEBUG) console.log("[DEBUG] Info affichée avec succès."); if (DEBUG) console.log("[DEBUG] Info affichée avec succès.");
} catch (e) { } 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); console.error("[CRITICAL] Erreur lors de la récupération:", e);
infoDiv.innerHTML = "❌ Erreur réseau ou JSON."; infoDiv.innerHTML = "❌ Erreur réseau ou JSON.";
infoDiv.classList.remove("visible"); infoDiv.classList.remove("visible");

View File

@@ -15,15 +15,15 @@
} }
.checkmark { .checkmark {
--clr: #3B82F6 ; --clr: var(--checkbox-bg-checked);
position: relative; position: relative;
top: 0; top: 0;
left: 0; left: 0;
height: 1.3em; height: 1.3em;
width: 1.3em; width: 1.3em;
background-color: #ccc; background-color: var(--checkbox-bg-default);
border-radius: 50%; border-radius: .5rem;
transition: 300ms; transition: background-color 300ms, border-radius 300ms;
} }
.checkbox-mp3 input:checked ~ .checkmark { .checkbox-mp3 input:checked ~ .checkmark {
@@ -47,7 +47,7 @@
top: 0.25em; top: 0.25em;
width: 0.25em; width: 0.25em;
height: 0.5em; height: 0.5em;
border: solid #E0E0E2; border: solid var(--checkbox-checkmark-border-color);
border-width: 0 0.15em 0.15em 0; border-width: 0 0.15em 0.15em 0;
transform: rotate(45deg); transform: rotate(45deg);
} }

View File

@@ -0,0 +1,41 @@
.download-path {
margin-top: 1em;
font-size: 1rem;
color: var(--default-text-color);
}
.download-path #savePath:empty {
display: none;
}
.download-path #savePath {
font-size: 1rem;
font-weight: 600;
background-color: rgba(0, 0, 0, 0.1);
color: var(--form-input-text-color);
padding: 0.3em 0.6em;
border-radius: 6px;
font-family: monospace;
border: 1px dashed var(--form-input-border-color);
word-break: break-all;
display: inline-block;
max-width: 80%;
}
.download-path button#changePath {
margin-left: 0.5em;
background-color: var(--form-button-bg-color);
color: var(--form-button-text-color);
border: none;
padding: 0.3em 0.8em;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.2s ease;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.download-path button#changePath:hover {
background-color: var(--form-button-bg-hover-color);
transform: scale(1.05);
}

View File

@@ -0,0 +1,38 @@
.theme-switcher {
position: absolute;
top: 10px;
right: 10px;
background: #242424;
padding: 8px 12px;
border-radius: 8px;
color: #fff;
font-family: sans-serif;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 8px;
z-index: 1000;
}
.theme-switcher label {
cursor: pointer;
font-weight: 600;
}
.theme-switcher select {
background: #fff;
border: 1px solid #242424;
border-radius: 6px;
padding: 4px 8px;
color: #000;
cursor: pointer;
font-weight: 600;
transition: background 0.3s ease, color 0.3s ease;
}
.theme-switcher select:hover,
.theme-switcher select:focus {
background: #474747;
color: #fff;
outline: none;
}

View File

@@ -4,7 +4,7 @@
min-height: 1.5em; min-height: 1.5em;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
color: #007bff; color: var(--download-status-color);
} }
label { label {

View File

@@ -1,5 +1,5 @@
form { form {
background-color: #fff; background-color: var(--form-bg-color);
padding: 2em; padding: 2em;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1); box-shadow: 0 4px 12px rgba(0,0,0,0.1);
@@ -13,8 +13,10 @@ form input[type="text"],
form input[type="url"], form input[type="url"],
form select { form select {
padding: 0.75em; padding: 0.75em;
border: 1px solid #ccc; border: 1px solid var(--form-input-border-color);
border-radius: 8px; border-radius: 8px;
background-color: var(--form-input-bg-color);
color: var(--form-input-text-color);
} }
form input[type="checkbox"] { form input[type="checkbox"] {
@@ -22,8 +24,8 @@ form input[type="checkbox"] {
} }
form button { form button {
background-color: #007bff; background-color: var(--form-button-bg-color);
color: #fff; color: var(--form-button-text-color);
border: none; border: none;
padding: 0.75em; padding: 0.75em;
border-radius: 8px; border-radius: 8px;
@@ -32,42 +34,43 @@ form button {
} }
form button:hover { form button:hover {
background-color: #0056b3; background-color: var(--form-button-bg-hover-color);
} }
form#downloadForm { form#downloadForm {
display: flex; display: flex;
gap: 0.5em; gap: 0.5em;
max-width: 600px; max-width: 600px;
width: 100%; width: 100%;
justify-content: center; justify-content: center;
} }
.checkbox-inline { .checkbox-inline {
display: flex; display: flex;
align-items: center; align-items: center;
align-self: center; align-self: center;
gap: 0.5em; gap: 0.5em;
color: var(--form-input-text-color);
} }
#UrlInput { #UrlInput {
padding: 0.75em 1em; padding: 0.75em 1em;
border-color: #ccc; border-color: var(--form-input-border-color);
box-shadow: none; box-shadow: none;
border-radius: 8px; border-radius: 8px;
font-size: 1rem; font-size: 1rem;
transition: border-color 0.3s ease, box-shadow 0.3s ease; transition: border-color 0.3s ease, box-shadow 0.3s ease;
background-color: #f9f9f9; background-color: var(--form-input-bg-color);
color: #333; color: var(--form-input-text-color);
} }
#UrlInput:focus { #UrlInput:focus {
border-color: #0056b3; border-color: var(--form-input-border-focus-color);
box-shadow: 0 0 5px rgba(0,123,255,0.4); box-shadow: 0 0 5px rgba(0,123,255,0.4);
outline: none; outline: none;
background-color: #fff; background-color: var(--form-input-bg-color);
} }
#UrlInput::placeholder { #UrlInput::placeholder {
color: #aaa; color: var(--form-input-placeholder-color);
} }

View File

@@ -7,7 +7,7 @@
background-color: var(--infos-box-color); background-color: var(--infos-box-color);
border-radius: 12px; border-radius: 12px;
box-shadow: 0 8px 16px rgba(0,0,0,0.1); box-shadow: 0 8px 16px rgba(0,0,0,0.1);
color: #222; color: var(--video-info-text-color);
font-size: 1rem; font-size: 1rem;
line-height: 1.5; line-height: 1.5;
opacity: 0; opacity: 0;
@@ -27,7 +27,7 @@
#videoInfo h3 { #videoInfo h3 {
font-size: 1.8rem; font-size: 1.8rem;
margin-bottom: 0.6em; margin-bottom: 0.6em;
color: #007bff; color: var(--video-info-heading-color);
font-weight: 700; font-weight: 700;
word-break: break-word; word-break: break-word;
} }
@@ -51,15 +51,15 @@
#videoInfo ul li { #videoInfo ul li {
margin-bottom: 0.5em; margin-bottom: 0.5em;
font-weight: 500; font-weight: 500;
color: #444; color: var(--video-info-list-color);
} }
#videoInfo ul li strong { #videoInfo ul li strong {
color: #000; color: var(--video-info-list-strong-color);
} }
#videoInfo ul li a { #videoInfo ul li a {
color: #007bff; color: var(--video-info-link-color);
text-decoration: none; text-decoration: none;
} }

View File

@@ -4,10 +4,22 @@
@import url("layout/header.css"); @import url("layout/header.css");
@import url("layout/videoinfo.css"); @import url("layout/videoinfo.css");
@import url("components/checkbox.css"); @import url("components/checkbox.css");
@import url("variables.css"); @import url("components/themebutton.css");
/* @import url("variables.css"); */
@import url("theme/themeimport.css");
@import url("components/editpathbutton.css");
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
#app {
display: block;
opacity: 0;
transition: opacity 0.6s ease;
}
#app.visible {
opacity: 1;
}
html,body{ html,body{
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -35,6 +47,7 @@ body {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
header p{ header p{
@@ -45,22 +58,11 @@ header p{
color: var(--mp3-text-color); color: var(--mp3-text-color);
} }
/* #videoInfo { body,
margin-top: 100px; .container,
padding: 1em; input,
max-width: 600px; button,
margin-left: auto; select,
margin-right: auto; label {
border-radius: 8px; transition: background-color 0.2s ease, color 0.5s ease, border-color 0.2s ease;
color: rgb(0, 0, 0);
box-shadow: 0 0 10px rgba(0,0,0,0.5);
transition: opacity 0.5s ease, max-height 0.5s ease;
opacity: 0;
max-height: 0;
overflow: hidden;
} }
#videoInfo.visible {
opacity: 1;
max-height: 1000px;
} */

View File

@@ -0,0 +1,98 @@
body.chirac {
/* Background image Chirac */
background-image: url('../../assets/images/goat.webp');
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 */
/* Formulaire */
--form-bg-color: rgba(255, 255, 255, 0.9);
--form-input-bg-color: #f5f5f5;
--form-input-border-color: #002395;
--form-input-border-focus-color: #EF4135;
--form-input-text-color: #002395;
--form-input-placeholder-color: #999999;
--form-button-bg-color: #002395; /* bleu */
--form-button-text-color: #ffffff;
--form-button-bg-hover-color: #EF4135; /* rouge */
/* Checkbox */
--checkbox-bg-default: #cccccc;
--checkbox-bg-checked: #002395;
--checkbox-checkmark-border-color: #ffffff;
--checkbox-pulse-color: rgba(0, 35, 149, 0.5);
/* Download status */
--download-status-color: #002395;
/* Video Info Box */
--infos-box-color: rgba(255, 255, 255, 0.9);
--video-info-text-color: #002395;
--video-info-heading-color: #EF4135;
--video-info-list-color: #444444;
--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;
}
/*
Je serai le président de tous les Français
Tous les Français
Tous les Français
Tous les Français
Tous les Français
Tous les Français
Tous les Français
Tous les Français
Tous les Français
Tous les Français
Mes chers compatriotes
Je serai le président de tous les Français, de tous les Français
Je mesure la difficulté de la tâche qui nous attend
Tous les Français
Tous les Français
Je veux un État, je veux les Français
Je veux un État, je veux
Je veux un État-tat-tat-tat vigoureux
Un État-tat-tat-tat impartial
Un État-tat-tat-tat vigoureux
Un État-tat-tat-tat exigeant
Un État-tat-tat-tat
Je serai le président de tous les Français
Tous les Français
Tous les Français
Tous les Français
Un État qui n'isole pas, tous, pas, pas
Tous, pas, tous, pas, pas
Tous, pas, tous, pas, pas (les Français)
Tous, tous, pas, tous, tous, pas, pas
Tous, tous, pas, tous, tous, pas, pas
Tous, tous, tous, pas, tous, pas, pas (les Français)
Tous, pas, tous, tous, pas, pas
Tous, tous, tous, pas, tous, tous, pas, pas
Tous, tous, pas, tous, tous, pas, pas
Tous, tous, tous-tous, tous-tous, pas, pas
Tous, tous-tous, tous-tous, pas, pas
Un État-tat-tat-tat vigoureux
Un État-tat-tat-tat impartial
Un État-tat-tat-tat exigeant
Un État-tat-tat-tat
Je serai le président de tous les Français
*/

View File

@@ -0,0 +1,45 @@
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/cyberpunk4.webp');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
}

View File

@@ -0,0 +1,36 @@
body.dark {
/* Couleurs générales */
--background-color: #121212; /* noir/gris très foncé */
--default-text-color: #e0e0e0; /* texte clair */
--subtitle-color: #2196f3; /* bleu accent */
--mp3-text-color: #f5f5f5; /* texte checkbox */
/* Formulaire */
--form-bg-color: #1e1e1e; /* fond formulaire */
--form-input-bg-color: #2a2a2a; /* fond inputs */
--form-input-border-color: #444444; /* bordure */
--form-input-border-focus-color: #2196f3; /* focus bleu */
--form-input-text-color: #f5f5f5; /* texte clair */
--form-input-placeholder-color: #888888; /* placeholder gris */
--form-button-bg-color: #2196f3; /* bouton bleu */
--form-button-text-color: #ffffff; /* texte bouton */
--form-button-bg-hover-color: #1976d2; /* hover bleu plus foncé */
/* Checkbox */
--checkbox-bg-default: #555555; /* gris moyen */
--checkbox-bg-checked: #2196f3; /* bleu accent */
--checkbox-checkmark-border-color: #ffffff; /* blanc */
--checkbox-pulse-color: rgba(33, 150, 243, 0.5);
/* Download status */
--download-status-color: #2196f3;
/* Video Info Box */
--infos-box-color: #1a1a1a;
--video-info-text-color: #e0e0e0;
--video-info-heading-color: #2196f3;
--video-info-list-color: #cccccc;
--video-info-list-strong-color: #ffffff;
--video-info-link-color: #2196f3;
--video-info-link-hover-color: #1976d2;
}

View File

@@ -0,0 +1,40 @@
: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;
}
body.default .download-path #savePath {
color: #ffffff;
}

View File

@@ -0,0 +1,36 @@
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é */
}

View File

@@ -0,0 +1,36 @@
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;
}

View File

@@ -0,0 +1,44 @@
body.spicy {
/* Couleurs générales */
--background-color: #121212; /* fond noir profond */
--default-text-color: #f5f5f5; /* texte clair */
--subtitle-color: #e29191; /* 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: cover;
background-position: center;
background-attachment: fixed;
}

8
public/styles/theme/themeimport.css vendored Normal file
View File

@@ -0,0 +1,8 @@
@import url("chirac-theme.css");
@import url("dark-theme.css");
@import url("default-theme.css");
@import url("fanatic-theme.css");
@import url("light-theme.css");
@import url("cyberpunk-theme.css");
@import url("spicy-theme.css");
@import url("vilbrequin-theme.css");

View File

@@ -0,0 +1,48 @@
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.avif'); /* adapte ton image */
background-size: cover;
background-position: center;
background-attachment: fixed;
}
body.vilbrequin .download-path{
color: white;
}

View File

@@ -1,11 +1,36 @@
:root{ :root {
--background-color : #0F172A; /* Couleurs générales */
--infos-box-color: #f1f1f1 ; --background-color: #001224; /* body background bleu nuit */
--title-color: ; --default-text-color: #eee; /* texte clair principal */
--subtitle-color : #a3aab4; --subtitle-color: #007bff; /* sous-titres, header p */
--mp3-text-color: #0F172A; --mp3-text-color: #000000; /* couleur texte checkbox inline */
--default-text-color: #F8FAFC;
--checkbox-pulse-color:#9bbcf1; /* 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;
} }

View File

@@ -18,51 +18,67 @@
const { createLogger, format, transports } = require("winston"); const { createLogger, format, transports } = require("winston");
const DailyRotateFile = require("winston-daily-rotate-file"); 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
const fs = require("fs"); // pour vérifier/créer le dossier de logs // Définition du dossier où seront stockés les logs
const path = require("path"); // pour gérer proprement les chemins // const logDir = path.join(__dirname, "../logs"); Désactivé parce que cela requiert d'exec en admin mode. Donc pas pratique pour les gens
// on définit le dossier de logs let logDir;
const logDir = path.join(__dirname, "../logs"); if (process.platform === "win32") {
// on le crée si inexistant logDir = path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs");
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir); } else {
logDir = path.join(os.homedir(), ".freedomloader", "logs");
}
// fonction pour générer une ligne de début de session // Création du dossier logs sil nexiste 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 dune session de logs
function getSessionStartLine() { function getSessionStartLine() {
const now = new Date().toISOString(); const now = new Date().toISOString();
return `--- Démarrage de la session : ${now} ---`; return `--- Démarrage de la session : ${now} ---`;
} }
// fonction pour générer une ligne de fin de session // Fonction utilitaire pour générer une ligne indiquant la fin dune session de logs
function getSessionEndLine() { function getSessionEndLine() {
const now = new Date().toISOString(); const now = new Date().toISOString();
return `--- Fin de la session : ${now} ---`; return `--- Fin de la session : ${now} ---`;
} }
// configuration principale de Winston // 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({ const logger = createLogger({
level: "info", // niveau de log minimum (info, warn, error, etc.) level: "info",
format: format.combine( format: format.combine(
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), // timestamp lisible format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.printf(({ timestamp, level, message }) => format.printf(({ timestamp, level, message }) =>
`${timestamp} | ${level.toUpperCase()} | ${message}` // format de ligne de log `${timestamp} | ${level.toUpperCase()} | ${message}`
) )
), ),
transports: [ transports: [
// Rotation quotidienne des fichiers de logs
new DailyRotateFile({ new DailyRotateFile({
dirname: logDir, // dossier de logs dirname: logDir, // dossier cible pour les logs
filename: "LOGS-%DATE%.log", // nom de fichier journalier filename: "LOGS-%DATE%.log", // nom du fichier par date
datePattern: "YYYY-MM-DD", // pattern de rotation datePattern: "YYYY-MM-DD", // pattern de date dans le nom
zippedArchive: false, // true = compression des archives zippedArchive: false, // ne pas compresser les archives
maxFiles: "14d", // garde 14 jours dhistorique maxFiles: "7d", // conserve 14 jours dhistorique
format: format.combine( format: format.combine(
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.printf(({ timestamp, level, message }) => format.printf(({ timestamp, level, message }) =>
`${timestamp} | ${level.toUpperCase()} | ${message}` `${timestamp} | ${level.toUpperCase()} | ${message}`
) )
), ),
options: { flags: "a" }, // mode append options: { flags: "a" }, // mode ajout à la fin (append)
}), }),
// Affichage des logs en console avec coloration et format simple
new transports.Console({ new transports.Console({
format: format.combine( format: format.combine(
format.colorize(), format.colorize(),
@@ -75,19 +91,20 @@ const logger = createLogger({
], ],
}); });
// fonction d'utilitaire pour marquer le début de la session // Helper pour marquer clairement le début de session dans les logs
function logSessionStart() { function logSessionStart() {
logger.info(getSessionStartLine()); logger.info(getSessionStartLine());
} }
// pour la fin de la session // Helper pour marquer la fin de session dans les logs
function logSessionEnd() { function logSessionEnd() {
logger.info(getSessionEndLine()); logger.info(getSessionEndLine());
} }
// exporte le logger et les helpers pour les autres modules // Export du logger principal et des helpers pour usage dans dautres modules
module.exports = { module.exports = {
logger, logger,
logSessionStart, logSessionStart,
logSessionEnd, logSessionEnd,
logDir,
}; };

View File

@@ -11,155 +11,186 @@
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details. 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 <https://www.gnu.org/licenses/>.
*/ */
const express = require("express"); // importe le framework Express const express = require("express");
const router = express.Router(); // crée un routeur Express pour modulariser les routes const router = express.Router();
const { execFile } = require("child_process"); // permet dexécuter un binaire externe sans passer par un shell const { execFile } = require("child_process");
const path = require("path"); // module Node.js pour gérer les chemins de fichiers const path = require("path");
const fs = require("fs"); // module Node.js pour manipuler le système de fichiers const fs = require("fs");
const { Notification } = require("electron");
const logger = require("../logger").logger;
const logger = require("../logger").logger; // récupère l'instance de logger Winston personnalisée // Path vers le fichier exécutable yt-dlp (outil tiers pour le téléchargement)
// const ytDlpPath = path.join(__dirname, '../../yt-dlp.exe');
const ytDlpPath = path.join(__dirname, '../../yt-dlp 2025.08.20.exe');
// chemin vers lexécutable yt-dlp packagé avec lappli
const ytDlpPath = path.join(__dirname, '../../yt-dlp.exe');
// route POST sur la racine (ex: POST /download)
router.post("/", (req, res) => { router.post("/", (req, res) => {
try { try {
// récupère les options envoyées depuis le frontend (via formulaire ou requête AJAX) // 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 = { const options = {
url: req.body.url, // URL à télécharger url: req.body.url,
audioOnly: req.body.audioOnly === "1", // conversion audio uniquement (mp3) audioOnly: req.body.audioOnly === "1",
quality: req.body.quality || "best", // qualité vidéo à télécharger quality: req.body.quality || "best",
subtitles: req.body.subs === "1", // récupérer les sous-titres anglais
}; };
// Validation simple : si l'URL est manquante, on rejette la requête
if (!options.url) { if (!options.url) {
// si lURL est absente, on log et on renvoie 400
logger.warn("Requête POST /download sans URL"); logger.warn("Requête POST /download sans URL");
return res.status(400).send("❌ URL manquante !"); return res.status(400).send("❌ URL manquante !");
} }
// on récupère le dossier de sortie depuis app.locals (défini ailleurs dans le serveur) // Récupération du chemin de sauvegarde personnalisé envoyé depuis Electron,
const outputFolder = req.app.locals.outputFolder; // ou on utilise un chemin par défaut stocké dans app.locals
let requestedOutputFolder = req.body.savePath || req.app.locals.outputFolder;
// s'il n'existe pas, on le crée automatiquement // On normalise le chemin pour éviter des erreurs liées aux séparateurs ou chemins relatifs
if (!fs.existsSync(outputFolder)) { requestedOutputFolder = path.normalize(requestedOutputFolder);
fs.mkdirSync(outputFolder, { recursive: true });
logger.info("Dossier Freedom Loader Output créé à la volée dans Téléchargements."); /*
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é.");
} }
// template du nom de fichier de sortie (ex: MonTitre.mp4 ou MonTitre.mp3) // Vérifie que le dossier de sortie existe, sinon le crée récursivement
const outputTemplate = path.join(outputFolder, "%(title)s.%(ext)s"); if (!fs.existsSync(requestedOutputFolder)) {
fs.mkdirSync(requestedOutputFolder, { recursive: true });
logger.info(`Dossier de sortie créé : ${requestedOutputFolder}`);
}
// tableau d'arguments pour yt-dlp // Construction du template de sortie pour yt-dlp (nom de fichier basé sur le titre)
const args = []; 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
];
// Si l'option audioOnly est activée, on ajoute les flags pour extraction audio en mp3
if (options.audioOnly) { if (options.audioOnly) {
// si audioOnly, on force extraction audio en mp3 args.push(
args.push("--extract-audio", "--audio-format", "mp3"); "-f",
"bestaudio",
"--extract-audio",
"--audio-format",
"mp3",
);
} }
if (options.subtitles) {
// si lutilisateur veut des sous-titres
args.push("--write-subs", "--sub-lang", "en");
}
// // qualité souhaitée // Correspondance entre la qualité choisie et le format à demander à yt-dlp
// args.push("-f", options.quality); 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]",
};
// let format; // Sélection du format basé sur la qualité, par défaut 'best' si option invalide
// if (options.quality === "best") { const format = qualityMap[options.quality] || "best";
// format = "bestvideo+bestaudio/best"; args.push("-f", format);
// } else if (options.quality === "worst") {
// format = "worstvideo+worstaudio/worst";
// } else {
// format = "best"; // fallback
// }
// args.push("-f", format);
args.push("--no-continue"); // Ajout du template de sortie et de l'URL à télécharger
let format;
switch (options.quality) {
case "best":
format = "bestvideo+bestaudio/best";
break;
case "medium":
// qualité moyenne, ex: 720p vidéo + meilleur audio
format = "bestvideo[height<=720]+bestaudio/best[height<=720]";
break;
case "worst":
format = "worstvideo+worstaudio/worst";
break;
case "1080":
format = "bestvideo[height<=1080]+bestaudio/best[height<=1080]";
break;
case "720":
format = "bestvideo[height<=720]+bestaudio/best[height<=720]";
break;
case "480":
format = "bestvideo[height<=480]+bestaudio/best[height<=480]";
break;
default:
format = "best";
}
args.push("-f", format);
// modèle de sortie
args.push("-o", outputTemplate); args.push("-o", outputTemplate);
// enfin, l'URL à télécharger
args.push(options.url); args.push(options.url);
// log de la commande complète pour debug // 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(" ")}`); logger.info(`Commande yt-dlp : ${ytDlpPath} ${args.join(" ")}`);
// exécution du binaire yt-dlp // Lancement du processus yt-dlp avec les arguments définis
const child = execFile(ytDlpPath, args); const child = execFile(ytDlpPath, args);
// gestion des logs stdout de yt-dlp /*
Gestion des sorties standards (stdout) du processus,
on log chaque ligne pour suivre la progression ou informations diverses
*/
child.stdout.on("data", (data) => { child.stdout.on("data", (data) => {
data.toString().split("\n").forEach(line => { data.toString().split("\n").forEach(line => {
if(line.trim()) logger.info(`[yt-dlp stdout] ${line.trim()}`); if (line.trim()) logger.info(`[yt-dlp stdout] ${line.trim()}`);
}); });
}); });
// gestion des logs stderr de yt-dlp (les erreurs) /*
Gestion des erreurs (stderr) du processus,
on log chaque ligne d'erreur pour diagnostic
*/
child.stderr.on("data", (data) => { child.stderr.on("data", (data) => {
data.toString().split("\n").forEach(line => { data.toString().split("\n").forEach(line => {
if(line.trim()) logger.error(`[yt-dlp stderr] ${line.trim()}`); if (line.trim()) logger.error(`[yt-dlp stderr] ${line.trim()}`);
}); });
}); });
// si une erreur système survient (par ex : yt-dlp.exe introuvable) /*
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) => { child.on("error", (err) => {
logger.error(`Erreur lancement yt-dlp : ${err.message}`); logger.error(`Erreur lancement yt-dlp : ${err.message}`);
res.status(500).send(`❌ Erreur lors de l'exécution : ${err.message}`); res.status(500).send(`❌ Erreur lors de l'exécution : ${err.message}`);
}); });
// callback quand le processus se termine /*
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) => { child.on("close", (code) => {
logger.info(`yt-dlp terminé avec code de sortie : ${code}`); logger.info(`yt-dlp terminé avec code de sortie : ${code}`);
if(code === 0) { if (code === 0) {
// succès
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é !"); res.send("✅ Téléchargement terminé !");
} else { } else {
// yt-dlp a échoué
res.status(500).send(`❌ yt-dlp a échoué avec le code : ${code}`); res.status(500).send(`❌ yt-dlp a échoué avec le code : ${code}`);
} }
}); });
} catch (err) { } catch (err) {
// en cas d'erreur JavaScript // Capture toute autre erreur serveur non prévue, log et réponse 500
logger.error(`Erreur serveur dans /download : ${err.message}`); logger.error(`Erreur serveur dans /download : ${err.message}`);
res.status(500).send(`Erreur serveur : ${err.message}`); res.status(500).send(`Erreur serveur : ${err.message}`);
} }
}); });
// on exporte ce routeur pour qu'il soit utilisé par l'app principale
module.exports = router; module.exports = router;

View File

@@ -16,40 +16,66 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
const express = require("express"); // importe Express const express = require("express");
const router = express.Router(); // crée un routeur Express indépendant const router = express.Router();
const { execFile } = require("child_process"); // permet dexécuter un programme externe const { execFile } = require("child_process");
const path = require("path"); // module pour gérer les chemins de fichiers const path = require("path");
const fs = require("fs");
const { logger } = require("../logger"); // On récupère ton logger Winston
// chemin vers le binaire yt-dlp embarqué // Path absolu vers l'exécutable yt-dlp
const ytDlpPath = path.join(__dirname, "../../yt-dlp.exe"); const ytDlpPath = path.join(__dirname, '../../yt-dlp 2025.08.20.exe');
// route POST sur la racine de ce routeur // 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) => { router.post("/", (req, res) => {
const url = req.body.url; // récupère l'URL depuis la requête POST const url = req.body.url;
if (!url) return res.status(400).send("❌ URL manquante"); // validation simple
// exécute yt-dlp avec --dump-json pour récupérer les métadonnées de la vidéo // Validation basique : on refuse la requête sans URL
execFile(ytDlpPath, ["--dump-json", url], (error, stdout, stderr) => { if (!url) {
if (error) { logger.warn("Requête metadata sans URL");
// gestion derreur : par exemple yt-dlp plante ou lURL est invalide return res.status(400).send("❌ URL manquante");
console.error("Erreur lors de l'exécution de yt-dlp :", error); }
console.error("stderr :", stderr);
return res.status(500).send("❌ Impossible de récupérer les infos.");
}
try { logger.info(`Requête metadata reçue pour ${url}`);
// on parse le JSON envoyé par yt-dlp
const info = JSON.parse(stdout); // Exécution de yt-dlp avec l'option --dump-json pour récupérer les infos vidéo
// on renvoie lobjet JSON directement au frontend execFile(
res.json(info); ytDlpPath,
} catch (e) { ["--dump-json", url],
// le JSON est mal formé { timeout: 10_000 }, // Timeout fixé à 10 secondes pour éviter blocage
console.error("Erreur lors du parsing JSON :", e); (error, stdout, stderr) => {
res.status(500).send("❌ JSON illisible."); 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.");
}
} }
}); );
}); });
// on exporte le routeur pour être utilisé dans app.js ou ailleurs
module.exports = router; module.exports = router;

View File

@@ -16,88 +16,88 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
const express = require("express"); // framework web const express = require("express"); // Framework web pour créer lAPI et servir les fichiers
const { exec } = require("child_process"); // si besoin dexécuter des commandes (pas utilisé ici directement) const { exec } = require("child_process"); // Permet dexécuter des commandes systèmes (pas utilisé directement ici)
const fs = require("fs"); // accès au système de fichiers const fs = require("fs"); // Module système pour gérer fichiers et dossiers
const path = require("path"); // gestion de chemins const path = require("path"); // Gestion propre des chemins de fichiers et dossiers
const debug = require("debug")("freedom-loader:server"); const debug = require("debug")("freedom-loader:server");
// petit debugger coloré en console // Module de debug coloré en console, pratique pour dev
const { logger, logSessionStart, logSessionEnd } = require("./logger"); const { logger, logSessionStart, logSessionEnd } = require("./logger");
// récupère notre logger Winston + helpers de session // Import du logger Winston et helpers pour marquer début/fin de session
const app = express(); // instancie l'application Express const app = express(); // Création de linstance Express, notre serveur web
// logSessionStart(); // désactivé, mais pourrait logguer le début de session // Définition du dossier par défaut où enregistrer les téléchargements
// On prend le dossier Téléchargements de lutilisateur Windows (USERPROFILE)
// on définit le dossier de téléchargement dans le dossier Téléchargements de lutilisateur
const downloadsPath = path.join(process.env.USERPROFILE, "Downloads"); const downloadsPath = path.join(process.env.USERPROFILE, "Downloads");
const outputFolder = path.join(downloadsPath, "Freedom Loader Output"); const outputFolder = path.join(downloadsPath, "Freedom Loader Output");
// création du dossier sil nexiste pas // Création du dossier de sortie sil nexiste pas déjà
if (!fs.existsSync(outputFolder)) { if (!fs.existsSync(outputFolder)) {
try { try {
fs.mkdirSync(outputFolder, { recursive: true }); fs.mkdirSync(outputFolder, { recursive: true }); // création récursive au cas où
logger.info("Dossier Freedom Loader Output cree dans Telechargements."); logger.info("Dossier Freedom Loader Output cree dans Telechargements.");
} catch (err) { } catch (err) {
logger.error("Impossible de creer le dossier :", err); logger.error("Impossible de creer le dossier :", err);
process.exit(1); // arrêt du programme en cas déchec process.exit(1); // Arrêt du programme si dossier non créé (critique)
} }
} else { } else {
logger.info("Dossier Freedom Loader Output deja existant."); logger.info("Dossier Freedom Loader Output deja existant.");
} }
// on rend ce dossier dispo via app.locals // On rend ce dossier accessible globalement via app.locals pour lutiliser dans les routes
app.locals.outputFolder = outputFolder; app.locals.outputFolder = outputFolder;
// configuration pour parser les requêtes POST en urlencoded // Middleware pour parser le corps des requêtes POST en application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
// sert les fichiers statiques (frontend) // Définition du dossier contenant les fichiers statiques (frontend)
// Permet daccéder au HTML, CSS, JS côté client
const staticPath = path.join(__dirname, "../public"); const staticPath = path.join(__dirname, "../public");
debug("Serveur statique sur", staticPath); debug("Serveur statique sur", staticPath);
app.use(express.static(staticPath)); app.use(express.static(staticPath));
// enregistre les routes API // 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 infoRoute = require("./routes/info");
const downloadRoute = require("./routes/download"); const downloadRoute = require("./routes/download");
debug("Routes /download et /info installees"); debug("Routes /download et /info installees");
app.use("/download", downloadRoute); app.use("/download", downloadRoute);
app.use("/info", infoRoute); app.use("/info", infoRoute);
// route GET / de base // Route GET / qui sert la page principale index.html
app.get("/", (req, res) => { app.get("/", (req, res) => {
debug("Requete GET / servie"); debug("Requete GET / servie");
res.sendFile(path.join(__dirname, "../public/index.html")); res.sendFile(path.join(__dirname, "../public/index.html"));
}); });
// fonction pour démarrer le serveur (Promise pour pouvoir await ailleurs) // Fonction pour démarrer le serveur Express
// Retourne une Promise pour pouvoir await le démarrage dans dautres modules
function startServer() { function startServer() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
logger.info("Demarrage du serveur Express..."); logger.info("Demarrage du serveur Express...");
const serverInstance = app.listen(8080, () => { const serverInstance = app.listen(8080, () => {
logger.info("Serveur Express pret sur http://localhost:8080"); logger.info("Serveur Express pret sur http://localhost:8080");
resolve(serverInstance); // succès resolve(serverInstance); // Serveur prêt, on résout la promesse
}); });
// Gestion des erreurs serveur lors du démarrage
serverInstance.on("error", (err) => { serverInstance.on("error", (err) => {
logger.error("Erreur serveur Express :", err); logger.error("Erreur serveur Express :", err);
reject(err); reject(err); // Rejet de la promesse en cas derreur critique
}); });
}); });
} }
// gestion de la fermeture propre du process // Gestion propre de la fermeture du process pour logger la fin de session
// process.on("exit", () => { process.on("SIGINT", () => { // Capture Ctrl+C (interruption)
// logSessionEnd();
// });
process.on("SIGINT", () => { // Ctrl+C
logSessionEnd(); logSessionEnd();
process.exit(); process.exit();
}); });
process.on("SIGTERM", () => { // kill process.on("SIGTERM", () => { // Capture kill ou arrêt du process
logSessionEnd(); logSessionEnd();
process.exit(); process.exit();
}); });
// on exporte la fonction startServer pour que dautres modules puissent la lancer // Export de la fonction startServer pour permettre son appel depuis dautres fichiers
module.exports = { startServer }; module.exports = { startServer };

BIN
yt-dlp 2025.08.20.exe Normal file

Binary file not shown.