49 Commits

Author SHA1 Message Date
MasterAcnolo
251049c7fe Undo last Commit 2025-07-01 15:37:13 +02:00
MasterAcnolo
5d347967b1 ReadMe Fix 2025-07-01 15:36:33 +02:00
MasterAcnolo
d5c506c4e0 ReadME Fix %20 2025-07-01 15:35:47 +02:00
MasterAcnolo
6718f4133e ReadME Fix %20 2025-07-01 15:35:24 +02:00
MasterAcnolo
9ff6678730 ReadME Fix 2025-07-01 15:34:36 +02:00
MasterAcnolo
3957450fdb ReadME Fix 2025-07-01 15:34:06 +02:00
MasterAcnolo
25e50a6e7a ReadME Fix 2025-07-01 15:33:31 +02:00
MasterAcnolo
22f935f678 ReadME Fix 2025-07-01 15:32:04 +02:00
MasterAcnolo
58b843630b ReadME Fix 2025-07-01 15:30:54 +02:00
MasterAcnolo
dec3f3272a ReadME Fix 2025-07-01 15:30:23 +02:00
MasterAcnolo
f3252c2548 ReadME Fix 2025-07-01 15:29:18 +02:00
MasterAcnolo
ac28afb17a ReadME Fix 2025-07-01 15:27:44 +02:00
MasterAcnolo
b1a42f44fa ReadME Fix 2025-07-01 15:26:21 +02:00
MasterAcnolo
78eea2fa0f ReadME Fix 2025-07-01 15:25:21 +02:00
MasterAcnolo
4290e6db52 Merge pull request #1 from MasterAcnolo/Développement-Branch
[PR] 1.0.1
2025-07-01 15:21:11 +02:00
MasterAcnolo
0ffc1af218 1.0.1 Commit 2025-07-01 15:16:17 +02:00
MasterAcnolo
10cefdba19 CSS Variable Begin 2025-07-01 13:51:42 +02:00
MasterAcnolo
c888946588 Logs Name Ajust + 1.0.1 Prepa 2025-06-30 13:49:02 +02:00
MasterAcnolo
9026c63979 Clarification 2025-06-29 15:48:06 +02:00
MasterAcnolo
cfc2c5bbaa Update README-EN.md 2025-06-29 14:13:24 +02:00
MasterAcnolo
a41da5c4c2 Update README.md 2025-06-29 14:12:40 +02:00
MasterAcnolo
b4242e4295 Correction Git Ignore 2025-06-29 14:10:58 +02:00
MasterAcnolo
1f0650a71c MD En + FR 2025-06-29 13:59:33 +02:00
MasterAcnolo
87fe8af6f5 Push 2025-06-29 13:57:00 +02:00
MasterAcnolo
592d3a5ae4 Push 2025-06-29 13:56:10 +02:00
MasterAcnolo
dc847a5a8c Correction 2025-06-29 13:55:40 +02:00
MasterAcnolo
250e6c3f23 Correction Path 2025-06-29 13:54:46 +02:00
MasterAcnolo
1ffe9dcea0 Push ReadME + License 2025-06-29 13:52:46 +02:00
MasterAcnolo
b40a4fc033 MD Push 2025-06-29 13:00:15 +02:00
MasterAcnolo
0900e1b485 Correction Path YT DLP 2025-06-29 12:09:37 +02:00
MasterAcnolo
432633af52 Fix NPM RUN DIST 2025-06-29 11:33:14 +02:00
MasterAcnolo
152ee39535 Working on my machine 2025-06-29 11:27:52 +02:00
MasterAcnolo
9d824f5c33 It Works on my machine 2025-06-28 22:36:03 +02:00
MasterAcnolo
accb55e538 Push 2025-06-28 21:25:57 +02:00
MasterAcnolo
bfb3e0cbdf Push avant l'enfer (PRIDEFUl - NF) 2025-06-28 21:19:48 +02:00
MasterAcnolo
b9460dbceb Aled 2025-06-28 20:54:37 +02:00
MasterAcnolo
1216e5f10d Push Version 1 2025-06-28 18:33:16 +02:00
MasterAcnolo
8b4b3c84e0 Push avant modif DIV INFO 2025-06-28 17:56:36 +02:00
MasterAcnolo
f7abca90cb LE POULET 2025-06-28 09:49:36 +02:00
MasterAcnolo
83b31cb645 CSS Beginning 2025-06-27 23:21:50 +02:00
MasterAcnolo
bb967834f0 Loading Screen 2025-06-27 23:11:42 +02:00
MasterAcnolo
e9f4b80b7b Fix Bug Download 2025-06-27 22:57:43 +02:00
MasterAcnolo
abd5295395 Diviser pour mieux régner 2025-06-27 22:48:24 +02:00
MasterAcnolo
d4d36c39ca Informations Vidéo 2025-06-27 15:42:46 +02:00
MasterAcnolo
95701bf56e Working V1 2025-06-27 15:02:35 +02:00
MasterAcnolo
7b93413050 Working V1 2025-06-27 09:47:11 +02:00
MasterAcnolo
c2c69935b1 Push 2025-06-26 20:40:55 +02:00
MasterAcnolo
07485f39ba OUII 2025-06-26 16:19:26 +02:00
MasterAcnolo
0c99e48950 CA MARCHE 2025-06-26 14:27:54 +02:00
31 changed files with 9469 additions and 1 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
/node_modules
/downloads
/dist
/logs
/out
yt-dlp.exe

20
LICENSE Normal file
View File

@@ -0,0 +1,20 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2025 MasterAcnolo
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This program 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, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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/>.

103
README-EN.md Normal file
View File

@@ -0,0 +1,103 @@
# 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)
> This project aims to enable free, open-source, and unrestricted downloading of Video/Audio content. Eventually, it will allow users to choose
## 🚀 Features
✅ Video Download (MP4)
✅ Audio Download (MP3)
✅ Detailed Logs (Console + Logs)
✅ Automatic Fetch of Information
✅ Playlist Download
---
## 🔧 Installation
To install the application, simply download the installer from the releases section. Take the latest version and run it.
---
## ❓ Frequently Asked Questions
### _Where are my downloaded files located?_
In your computer's Downloads folder (**C:\Users\YOUR NAME\Downloads**), inside a folder named **Freedom Loader Output**
<br>
### _After installing and running the program, I get an error_
Try restarting the application, checking the logs, or opening the browser's developer console by pressing `CTRL + SHIFT + I` or by going to `View > Toggle Developer Tools`
<!-- ![Example Path](build/example-developertools.png) -->
<p align="center">
<img src="build/example-developertools.png" alt="Example Developer Tools" />
</p>
## 🏗️ Upcoming Features (Roadmap)
- [ ] More format options
- [ ] Subtitle support
- [ ] Improved UI / UX
- [ ] Language selection
- [ ] Download a specific part of the video
- [ ] Rename output file
- [ ] Extract / Split video using native chapters
- [ ] Parallel downloads
- [ ] Skip sponsored parts of videos
- [ ] Custom output path selection (default **C:\Users\Username\Downloads** with a **Freedom Loader Output** folder created)
---
## 🖼️ Preview
<p align="center">
<img src="build/exemple.png" alt="Example Image" />
</p>
> Here is an example of the app after launch and entering a URL
---
## ℹ️ About
This project was built using [![yt-dlp](https://img.shields.io/badge/yt--dlp-Visit%20Repo-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/yt-dlp/yt-dlp)
Thanks to them for providing a free and open-source API.
This project is open-source and welcomes your contributions.
Here is how to get involved:
- **🪲 Bug Reporting**
Please use the [GitHub issues system](https://github.com/MasterAcnolo/Freedom-Loader/issues) to report any bugs you encounter.
Try to provide clear reproduction steps and logs if possible. Logs are located in the installation folder of the program. Example path:
`C:\Users\[YOUR NAME]\Desktop\Freedom Loader\resources\app\logs`
The log file will be named something like `combined-20xx-xx-xx.log`
<br>
- **🛠️ Contribution**
Pull Requests are welcome! Please fork the repo and submit your PR after ensuring you meet the contribution guidelines (tests, style, etc.).
<br>
- **💡 Feature Suggestions**
You can propose new ideas via a [feature request issue](https://github.com/MasterAcnolo/Freedom-Loader/issues).
Any clear and detailed proposal will be reviewed.
<br>
- **💰 Support the Project**
<p>
If you'd like to support me, you can donate via
<a href="https://paypal.me/axelnicolas25">
<img src="https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white" alt="PayPal">
</a>
</p>
---
## 📄 License
This project is licensed under the **MIT License** — see the [LICENSE](./LICENSE) file for details.
---
## ⚠️ Disclaimer
I have obviously used AI assistance for some parts of this project. Use AI sparingly and always be aware of what you are doing.

103
README.md
View File

@@ -1,2 +1,103 @@
# MP3 Downloader
# 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)
> 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
✅ Téléchargement Vidéo (MP4)
✅ Téléchargement Audio (MP3)
✅ Logs détaillés (console + fichiers logs)
✅ Récupération automatique des informations
✅ Téléchargement des playlists
---
## 🔧 Installation
Pour installer l'application, il vous suffit de télécharger l'installateur dans la section releases, de prendre la dernière version et de l'exécuter.
---
## ❓ Questions fréquentes
### _Où se situent mes fichiers une fois téléchargés ?_
Dans le dossier Téléchargements (**C:\Users\VOTRE NOM\Downloads**) de votre ordinateur, dans un dossier nommé **Freedom Loader Output**
<br>
### _Une fois le programme installé et lancé, j'ai une erreur_
Essayez de relancer l'application, de consulter les logs ou d'ouvrir la console de développement du navigateur en faisant le raccourci `CTRL + SHIFT + I` ou via `View > Toggle Developer Tools`.
<!-- ![Exemple Chemin](build/example-developertools.png) -->
<p align="center">
<img src="build/example-developertools.png" alt="Exemple Developer Tools" />
</p>
## 🏗️ Fonctionnalités à venir (Roadmap)
- [ ] Plus de choix de formats
- [ ] Support des sous-titres
- [ ] Meilleure UI / UX
- [ ] Choix des langues
- [ ] Téléchargement d’une partie de la vidéo
- [ ] Renommage du fichier de sortie
- [ ] Extraction / découpage de la vidéo via chapitrage natif
- [ ] Téléchargements parallèles
- [ ] 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éé)
---
## 🖼️ Aperçu
<p align="center">
<img src="build/exemple.png" alt="Exemple d'application" />
</p>
> Voici un exemple de l’application lancée après avoir saisi une URL.
---
## ℹ️ À propos
Ce projet a été réalisé en utilisant [![yt-dlp](https://img.shields.io/badge/yt--dlp-Visit%20Repo-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/yt-dlp/yt-dlp)
Merci à eux de nous permettre d’utiliser leur API gratuitement et en open source.
Ce projet est open source et accueille volontiers vos contributions.
Voici comment participer :
- **🪲 Signalement de bugs**
Merci d’utiliser le [système d’issues GitHub](https://github.com/MasterAcnolo/Freedom-Loader/issues) pour remonter les bugs rencontrés.
Essayez de fournir une reproduction claire et les logs si possible. Les logs se trouvent dans le dossier d’installation du programme. Exemple de chemin :
`C:\Users\[VOTRE NOM]\Desktop\Freedom Loader\resources\app\logs`
Le fichier aura un nom du type `combined-20xx-xx-xx.log`.
<br>
- **🛠️ Contribution**
Les Pull Requests sont les bienvenues ! Merci de forker le repo et de soumettre votre PR après avoir vérifié le respect des guidelines (tests, style, etc.).
<br>
- **💡 Suggestions de fonctionnalités**
Vous pouvez proposer de nouvelles idées via une [issue de type feature request](https://github.com/MasterAcnolo/Freedom-Loader/issues).
Toute proposition claire et détaillée sera étudiée.
<br>
- **💰 Soutenir le projet**
<p>
Si vous souhaitez m’encourager, vous pouvez faire un don sur
<a href="https://paypal.me/axelnicolas25">
<img src="https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white" alt="PayPal">
</a>
</p>
---
## 📄 Licence
Ce projet est sous licence **MIT** — voir le fichier [LICENSE](./LICENSE) pour plus d’informations.
---
## ⚠️ Disclaimer
J’ai bien évidemment eu recours à l’IA pour m’aider sur certaines parties du projet. Utilisez l’IA avec parcimonie et soyez toujours conscient de ce que vous faites.

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
build/exemple.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

BIN
build/installer-icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

BIN
build/uninstaller-icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

44
forge.config.js Normal file
View File

@@ -0,0 +1,44 @@
const { FusesPlugin } = require('@electron-forge/plugin-fuses');
const { FuseV1Options, FuseVersion } = require('@electron/fuses');
module.exports = {
packagerConfig: {
asar: true,
},
rebuildConfig: {},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: {},
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin'],
},
{
name: '@electron-forge/maker-deb',
config: {},
},
{
name: '@electron-forge/maker-rpm',
config: {},
},
],
plugins: [
{
name: '@electron-forge/plugin-auto-unpack-natives',
config: {},
},
// Fuses are used to enable/disable various Electron functionality
// at package time, before code signing the application
new FusesPlugin({
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false,
[FuseV1Options.EnableCookieEncryption]: true,
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
[FuseV1Options.EnableNodeCliInspectArguments]: false,
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}),
],
};

79
main.js Normal file
View File

@@ -0,0 +1,79 @@
/*
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 { app, BrowserWindow } = require("electron");
const path = require("path");
const { logger, logSessionStart, logSessionEnd } = require("./server/logger");
let mainWindow;
async function createWindow() {
logger.info("Creation de la fenetre...");
if (mainWindow) {
logger.warn("La fenetre existe deja, pas de nouvelle creation");
return;
}
mainWindow = new BrowserWindow({
title: "Freedom Loader",
width: 750,
height: 800,
minWidth: 750,
minHeight: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
},
// titleBarStyle: 'hidden',
});
try {
await mainWindow.loadURL("http://localhost:8080");
logger.info("Fenetre chargee");
} catch (err) {
logger.error("Erreur chargement fenetre:", err);
}
mainWindow.on("closed", () => {
logger.info("Fenetre principale fermee");
mainWindow = null;
});
}
app.whenReady().then(async () => {
logSessionStart();
logger.info("App prete, demarrage du serveur Express...");
const expressServer = require("./server/server.js");
try {
await expressServer.startServer();
logger.info("Serveur Express demarre");
await createWindow();
} catch (error) {
logger.error("Erreur serveur ou fenetre :", error);
app.quit();
}
});
app.on("window-all-closed", () => {
logger.info("Toutes fenetres fermees, quitte l'app");
if (process.platform !== "darwin") app.quit();
});
app.on("before-quit", () => {
logSessionEnd();
});

8031
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

60
package.json Normal file
View File

@@ -0,0 +1,60 @@
{
"name": "freedom-loader",
"productName": "Freedom Loader",
"version": "1.0.1",
"author": "MasterAcnolo",
"description": "Freedom Loader, parce que nous le valons bien",
"main": "main.js",
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make",
"dist": "electron-builder"
},
"dependencies": {
"chalk": "^4.1.2",
"debug": "^4.4.1",
"electron-squirrel-startup": "^1.0.1",
"express": "^5.1.0",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0"
},
"devDependencies": {
"@electron-forge/cli": "^7.8.1",
"@electron-forge/maker-deb": "^7.8.1",
"@electron-forge/maker-rpm": "^7.8.1",
"@electron-forge/maker-squirrel": "^7.8.1",
"@electron-forge/maker-zip": "^7.8.1",
"@electron-forge/plugin-auto-unpack-natives": "^7.8.1",
"@electron-forge/plugin-fuses": "^7.8.1",
"@electron/fuses": "^1.8.0",
"electron": "^37.1.0",
"electron-builder": "^26.0.12"
},
"build": {
"appId": "com.masteracnolo.freedomloader",
"productName": "Freedom Loader",
"files": [
"**/*",
"node_modules/**/*"
],
"directories": {
"buildResources": "build"
},
"asar": false,
"win": {
"target": "nsis",
"icon": "build/installer-icon.ico"
},
"nsis": {
"oneClick": false,
"perMachine": true,
"allowToChangeInstallationDirectory": true,
"installerIcon": "build/installer-icon.ico",
"uninstallerIcon": "build/uninstaller-icon.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "Freedom Loader"
}
}
}

BIN
public/assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -0,0 +1,3 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z" transform="scale(64)" fill="#1B1F23"/>
</svg>

After

Width:  |  Height:  |  Size: 968 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M64 0C28.7 0 0 28.7 0 64L0 448c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-288-128 0c-17.7 0-32-14.3-32-32L224 0 64 0zM256 0l0 128 128 0L256 0zm2 226.3c37.1 22.4 62 63.1 62 109.7s-24.9 87.3-62 109.7c-7.6 4.6-17.4 2.1-22-5.4s-2.1-17.4 5.4-22C269.4 401.5 288 370.9 288 336s-18.6-65.5-46.5-82.3c-7.6-4.6-10-14.4-5.4-22s14.4-10 22-5.4zm-91.9 30.9c6 2.5 9.9 8.3 9.9 14.8l0 128c0 6.5-3.9 12.3-9.9 14.8s-12.9 1.1-17.4-3.5L113.4 376 80 376c-8.8 0-16-7.2-16-16l0-48c0-8.8 7.2-16 16-16l33.4 0 35.3-35.3c4.6-4.6 11.5-5.9 17.4-3.5zm51 34.9c6.6-5.9 16.7-5.3 22.6 1.3C249.8 304.6 256 319.6 256 336s-6.2 31.4-16.3 42.7c-5.9 6.6-16 7.1-22.6 1.3s-7.1-16-1.3-22.6c5.1-5.7 8.1-13.1 8.1-21.3s-3.1-15.7-8.1-21.3c-5.9-6.6-5.3-16.7 1.3-22.6z"/></svg>

After

Width:  |  Height:  |  Size: 955 B

74
public/index.html Normal file
View File

@@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="
default-src 'self' 'unsafe-inline' data:;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src https://fonts.gstatic.com;
img-src 'self' data: https://i.ytimg.com;
">
<title>Freedom Loader</title>
<!--CSS-->
<link rel="stylesheet" href="styles/styles.css">
<!--Icons-->
<link rel="icon" href="assets/icons/file-audio-solid.svg" type="image/svg+xml">
</head>
<body>
<div class="container">
<header>
<h1> Freedom Loader </h1>
<p> Because why not? </p>
</header>
<form action="/download" method="POST" id="downloadForm">
<input name="url" placeholder="URL" id="UrlInput">
<label class="checkbox-inline">
<label class="checkbox-mp3">
<input type="checkbox" name="audioOnly" value="1">
<div class="checkmark"></div>
</label>
<!-- <input type="checkbox" name="audioOnly" value="1"> -->
Audio seulement (MP3)
</label>
<!-- <input type="checkbox" name="audioOnly" value="1"> Audio seulement (MP3) -->
<select name="quality">
<option value="best">Meilleure qualité</option>
<option value="medium">Qualité moyenne</option>
<option value="worst">Qualité minimale</option>
<option value="1080">1080p</option>
<option value="720">720p</option>
<option value="480">480p</option>
</select>
<button type="submit">Télécharger</button>
</form>
<div id="downloadStatus"></div>
<!--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
ê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
que l'on envoie des données.-->
</div>
<div class="infos-container">
<div id="videoInfo" style="margin-top: 20px;"></div>
</div>
<script src="script/downloadstatus.js"></script>
<script src="script/fetchinfo.js"></script>
</body>
</html>

View File

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

114
public/script/fetchinfo.js Normal file
View File

@@ -0,0 +1,114 @@
/*
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/>.
*/
document.addEventListener("DOMContentLoaded", () => {
const urlInput = document.getElementById("UrlInput");
const infoDiv = document.getElementById("videoInfo");
let lastFetchedUrl = "";
const DEBUG = true;
urlInput.addEventListener("input", async () => {
const url = urlInput.value.trim();
if (DEBUG) console.log("[DEBUG] Input détecté :", url);
if (!url || url.length < 5) {
if (DEBUG) console.log("[DEBUG] URL vide ou trop courte, reset affichage.");
infoDiv.innerHTML = "";
infoDiv.classList.remove("visible");
lastFetchedUrl = "";
return;
}
if (url === lastFetchedUrl) {
if (DEBUG) console.log("[DEBUG] Même URL que précédemment, pas de fetch.");
return;
}
lastFetchedUrl = url;
try {
if (DEBUG) console.log("[DEBUG] Envoi requête POST vers /info avec URL:", url);
const res = await fetch("/info", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ url }),
});
if (DEBUG) console.log("[DEBUG] Réponse status:", res.status);
if (!res.ok) {
console.error(`[ERROR] Réponse HTTP ${res.status}: ${res.statusText}`);
infoDiv.innerHTML = "❌ Impossible de récupérer les infos (erreur serveur).";
infoDiv.classList.remove("visible");
return;
}
const data = await res.json();
if (DEBUG) console.log("[DEBUG] Données reçues:", data);
// Vérification minimaliste
if (!data || !data.title || !data.thumbnail) {
console.warn("[WARNING] Données incomplètes", data);
infoDiv.innerHTML = "❌ Données incomplètes reçues.";
infoDiv.classList.remove("visible");
return;
}
const minutes = Math.floor(data.duration / 60);
const seconds = data.duration % 60;
const durationStr = `${minutes}m ${seconds.toString().padStart(2, "0")}s`;
const date = data.upload_date || "";
const readableDate = date
? `${date.slice(6, 8)}/${date.slice(4, 6)}/${date.slice(0, 4)}`
: "Inconnue";
const sizeMB = data.filesize_approx
? (data.filesize_approx / (1024 * 1024)).toFixed(2) + " Mo"
: "Inconnue";
const categories = data.categories
? data.categories.join(", ")
: "Non spécifiées";
infoDiv.innerHTML = `
<h3>${data.title}</h3>
<img src="${data.thumbnail}" width="320" alt="Thumbnail">
<ul>
<li><strong>Durée :</strong> ${durationStr}</li>
<li><strong>Uploader :</strong> ${data.uploader || "Inconnu"}</li>
<li><strong>Date d’upload :</strong> ${readableDate}</li>
<li><strong>Vues :</strong> ${data.view_count?.toLocaleString() || "?"}</li>
<li><strong>Likes :</strong> ${data.like_count?.toLocaleString() || "?"}</li>
<li><strong>URL :</strong> <a href="${data.webpage_url}" target="_blank" rel="noopener noreferrer">${data.webpage_url}</a></li>
<li><strong>Channel :</strong> <a href="${data.channel_url}" target="_blank" rel="noopener noreferrer">${data.channel_url}</a></li>
<li><strong>Taille estimée :</strong> ${sizeMB}</li>
<li><strong>Catégories :</strong> ${categories}</li>
</ul>
`;
infoDiv.classList.add("visible");
if (DEBUG) console.log("[DEBUG] Info affichée avec succès.");
} catch (e) {
console.error("[CRITICAL] Erreur lors de la récupération:", e);
infoDiv.innerHTML = "❌ Erreur réseau ou JSON.";
infoDiv.classList.remove("visible");
}
});
});

View File

@@ -0,0 +1,73 @@
.checkbox-mp3 input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkbox-mp3 {
display: block;
position: relative;
cursor: pointer;
font-size: 1.5rem;
user-select: none;
}
.checkmark {
--clr: #3B82F6 ;
position: relative;
top: 0;
left: 0;
height: 1.3em;
width: 1.3em;
background-color: #ccc;
border-radius: 50%;
transition: 300ms;
}
.checkbox-mp3 input:checked ~ .checkmark {
background-color: var(--clr);
border-radius: .5rem;
animation: pulse 500ms ease-in-out;
}
.checkmark:after {
content: "";
position: absolute;
display: none;
}
.checkbox-mp3 input:checked ~ .checkmark:after {
display: block;
}
.container .checkmark:after {
left: 0.45em;
top: 0.25em;
width: 0.25em;
height: 0.5em;
border: solid #E0E0E2;
border-width: 0 0.15em 0.15em 0;
transform: rotate(45deg);
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 var(--checkbox-pulse-color);
rotate: 20deg;
}
50% {
rotate: -20deg;
}
75% {
box-shadow: 0 0 0 10px var(--checkbox-pulse-color);
}
100% {
box-shadow: 0 0 0 13px var(--checkbox-pulse-color);
rotate: 0;
}
}

View File

@@ -0,0 +1,12 @@
.container, .infos-container {
position: relative;
top: 0;
left: 0;
right: 0;
/* height: 10000px; */
padding: 1em;
z-index: 1000;
display: flex;
flex-direction: column;
align-items: center;
}

View File

@@ -0,0 +1,14 @@
#downloadStatus {
margin-top: 1em;
min-height: 1.5em;
font-weight: bold;
text-align: center;
color: #007bff;
}
label {
display: flex;
align-items: center;
font-size: 0.9em;
}

View File

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

View File

@@ -0,0 +1,11 @@
header {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 2vw;
}
header h1{
margin-bottom: 0.2vw;
}

View File

@@ -0,0 +1,81 @@
#videoInfo {
margin-top: 20px;
max-width: 600px;
margin-left: auto;
margin-right: auto;
padding: 1.5em 2em;
background-color: var(--infos-box-color);
border-radius: 12px;
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
color: #222;
font-size: 1rem;
line-height: 1.5;
opacity: 0;
max-height: 0;
overflow: hidden;
transition: opacity 0.5s ease, max-height 0.5s ease;
user-select: text;
word-wrap: break-word;
}
#videoInfo.visible {
opacity: 1;
max-height: 1500px;
overflow: visible;
}
#videoInfo h3 {
font-size: 1.8rem;
margin-bottom: 0.6em;
color: #007bff;
font-weight: 700;
word-break: break-word;
}
#videoInfo img {
max-width: 100%;
height: auto;
border-radius: 10px;
margin-bottom: 1em;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
display: block;
margin-left: auto;
margin-right: auto;
}
#videoInfo ul {
list-style: none;
padding-left: 0;
}
#videoInfo ul li {
margin-bottom: 0.5em;
font-weight: 500;
color: #444;
}
#videoInfo ul li strong {
color: #000;
}
#videoInfo ul li a {
color: #007bff;
text-decoration: none;
}
#videoInfo ul li a:hover,
#videoInfo ul li a:focus {
text-decoration: underline;
}
/* Responsive */
@media (max-width: 480px) {
#videoInfo {
padding: 1em;
font-size: 0.9rem;
}
#videoInfo h3 {
font-size: 1.4rem;
}
}

66
public/styles/styles.css Normal file
View File

@@ -0,0 +1,66 @@
@import url("layout/form.css");
@import url("layout/container.css");
@import url("layout/ctr-infos.css");
@import url("layout/header.css");
@import url("layout/videoinfo.css");
@import url("components/checkbox.css");
@import url("variables.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');
html,body{
margin: 0;
padding: 0;
scroll-behavior: smooth;
/* outline: 2px solid red; */ /*<-- Utile pour le début ça */
font-family: "Poppins", sans-serif;
font-weight: 500;
font-style: normal;
}
body {
margin: 0;
background-color: var(--background-color);
/* background-image: linear-gradient(to left top, #838383, #a1a1a1, #bfbfbf, #dfdfdf, #ffffff);
background-size: cover;
background-position: center;
background-attachment: fixed; */
width: 100%;
height: 100%;
color: var(--default-text-color);
display: flex;
justify-content: center;
align-items: center;
display: flex;
flex-direction: column;
align-items: center;
}
header p{
color: var(--subtitle-color);
}
.checkbox-inline{
color: var(--mp3-text-color);
}
/* #videoInfo {
margin-top: 100px;
padding: 1em;
max-width: 600px;
margin-left: auto;
margin-right: auto;
border-radius: 8px;
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,11 @@
:root{
--background-color : #0F172A;
--infos-box-color: #f1f1f1 ;
--title-color: ;
--subtitle-color : #a3aab4;
--mp3-text-color: #0F172A;
--default-text-color: #F8FAFC;
--checkbox-pulse-color:#9bbcf1;
}

93
server/logger.js Normal file
View File

@@ -0,0 +1,93 @@
/*
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 { createLogger, format, transports } = require("winston");
const DailyRotateFile = require("winston-daily-rotate-file");
const fs = require("fs"); // pour vérifier/créer le dossier de logs
const path = require("path"); // pour gérer proprement les chemins
// on définit le dossier de logs
const logDir = path.join(__dirname, "../logs");
// on le crée si inexistant
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir);
// fonction pour générer une ligne de début de session
function getSessionStartLine() {
const now = new Date().toISOString();
return `--- Démarrage de la session : ${now} ---`;
}
// fonction pour générer une ligne de fin de session
function getSessionEndLine() {
const now = new Date().toISOString();
return `--- Fin de la session : ${now} ---`;
}
// configuration principale de Winston
const logger = createLogger({
level: "info", // niveau de log minimum (info, warn, error, etc.)
format: format.combine(
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), // timestamp lisible
format.printf(({ timestamp, level, message }) =>
`${timestamp} | ${level.toUpperCase()} | ${message}` // format de ligne de log
)
),
transports: [
new DailyRotateFile({
dirname: logDir, // dossier de logs
filename: "LOGS-%DATE%.log", // nom de fichier journalier
datePattern: "YYYY-MM-DD", // pattern de rotation
zippedArchive: false, // true = compression des archives
maxFiles: "14d", // garde 14 jours d’historique
format: format.combine(
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.printf(({ timestamp, level, message }) =>
`${timestamp} | ${level.toUpperCase()} | ${message}`
)
),
options: { flags: "a" }, // mode append
}),
new transports.Console({
format: format.combine(
format.colorize(),
format.timestamp({ format: "HH:mm:ss" }),
format.printf(({ timestamp, level, message }) =>
`${timestamp} | ${level} | ${message}`
)
),
}),
],
});
// fonction d'utilitaire pour marquer le début de la session
function logSessionStart() {
logger.info(getSessionStartLine());
}
// pour la fin de la session
function logSessionEnd() {
logger.info(getSessionEndLine());
}
// exporte le logger et les helpers pour les autres modules
module.exports = {
logger,
logSessionStart,
logSessionEnd,
};

165
server/routes/download.js Normal file
View File

@@ -0,0 +1,165 @@
/*
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 express = require("express"); // importe le framework Express
const router = express.Router(); // crée un routeur Express pour modulariser les routes
const { execFile } = require("child_process"); // permet d’exécuter un binaire externe sans passer par un shell
const path = require("path"); // module Node.js pour gérer les chemins de fichiers
const fs = require("fs"); // module Node.js pour manipuler le système de fichiers
const logger = require("../logger").logger; // récupère l'instance de logger Winston personnalisée
// chemin vers l’exécutable yt-dlp packagé avec l’appli
const ytDlpPath = path.join(__dirname, '../../yt-dlp.exe');
// route POST sur la racine (ex: POST /download)
router.post("/", (req, res) => {
try {
// récupère les options envoyées depuis le frontend (via formulaire ou requête AJAX)
const options = {
url: req.body.url, // URL à télécharger
audioOnly: req.body.audioOnly === "1", // conversion audio uniquement (mp3)
quality: req.body.quality || "best", // qualité vidéo à télécharger
subtitles: req.body.subs === "1", // récupérer les sous-titres anglais
};
if (!options.url) {
// si l’URL est absente, on log et on renvoie 400
logger.warn("Requête POST /download sans URL");
return res.status(400).send("❌ URL manquante !");
}
// on récupère le dossier de sortie depuis app.locals (défini ailleurs dans le serveur)
const outputFolder = req.app.locals.outputFolder;
// s'il n'existe pas, on le crée automatiquement
if (!fs.existsSync(outputFolder)) {
fs.mkdirSync(outputFolder, { recursive: true });
logger.info("Dossier Freedom Loader Output créé à la volée dans Téléchargements.");
}
// template du nom de fichier de sortie (ex: MonTitre.mp4 ou MonTitre.mp3)
const outputTemplate = path.join(outputFolder, "%(title)s.%(ext)s");
// tableau d'arguments pour yt-dlp
const args = [];
if (options.audioOnly) {
// si audioOnly, on force extraction audio en mp3
args.push("--extract-audio", "--audio-format", "mp3");
}
if (options.subtitles) {
// si l’utilisateur veut des sous-titres
args.push("--write-subs", "--sub-lang", "en");
}
// // qualité souhaitée
// args.push("-f", options.quality);
// let format;
// if (options.quality === "best") {
// format = "bestvideo+bestaudio/best";
// } else if (options.quality === "worst") {
// format = "worstvideo+worstaudio/worst";
// } else {
// format = "best"; // fallback
// }
// args.push("-f", format);
args.push("--no-continue");
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);
// enfin, l'URL à télécharger
args.push(options.url);
// log de la commande complète pour debug
logger.info(`Commande yt-dlp : ${ytDlpPath} ${args.join(" ")}`);
// exécution du binaire yt-dlp
const child = execFile(ytDlpPath, args);
// gestion des logs stdout de yt-dlp
child.stdout.on("data", (data) => {
data.toString().split("\n").forEach(line => {
if(line.trim()) logger.info(`[yt-dlp stdout] ${line.trim()}`);
});
});
// gestion des logs stderr de yt-dlp (les erreurs)
child.stderr.on("data", (data) => {
data.toString().split("\n").forEach(line => {
if(line.trim()) logger.error(`[yt-dlp stderr] ${line.trim()}`);
});
});
// si une erreur système survient (par ex : yt-dlp.exe introuvable)
child.on("error", (err) => {
logger.error(`Erreur lancement yt-dlp : ${err.message}`);
res.status(500).send(`❌ Erreur lors de l'exécution : ${err.message}`);
});
// callback quand le processus se termine
child.on("close", (code) => {
logger.info(`yt-dlp terminé avec code de sortie : ${code}`);
if(code === 0) {
// succès
res.send("✅ Téléchargement terminé !");
} else {
// yt-dlp a échoué
res.status(500).send(`❌ yt-dlp a échoué avec le code : ${code}`);
}
});
} catch (err) {
// en cas d'erreur JavaScript
logger.error(`Erreur serveur dans /download : ${err.message}`);
res.status(500).send(`Erreur serveur : ${err.message}`);
}
});
// on exporte ce routeur pour qu'il soit utilisé par l'app principale
module.exports = router;

55
server/routes/info.js Normal file
View File

@@ -0,0 +1,55 @@
/*
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 express = require("express"); // importe Express
const router = express.Router(); // crée un routeur Express indépendant
const { execFile } = require("child_process"); // permet d’exécuter un programme externe
const path = require("path"); // module pour gérer les chemins de fichiers
// chemin vers le binaire yt-dlp embarqué
const ytDlpPath = path.join(__dirname, "../../yt-dlp.exe");
// route POST sur la racine de ce routeur
router.post("/", (req, res) => {
const url = req.body.url; // récupère l'URL depuis la requête POST
if (!url) return res.status(400).send("❌ URL manquante"); // validation simple
// exécute yt-dlp avec --dump-json pour récupérer les métadonnées de la vidéo
execFile(ytDlpPath, ["--dump-json", url], (error, stdout, stderr) => {
if (error) {
// gestion d’erreur : par exemple yt-dlp plante ou l’URL est invalide
console.error("Erreur lors de l'exécution de yt-dlp :", error);
console.error("stderr :", stderr);
return res.status(500).send("❌ Impossible de récupérer les infos.");
}
try {
// on parse le JSON envoyé par yt-dlp
const info = JSON.parse(stdout);
// on renvoie l’objet JSON directement au frontend
res.json(info);
} catch (e) {
// le JSON est mal formé
console.error("Erreur lors du parsing JSON :", e);
res.status(500).send("❌ JSON illisible.");
}
});
});
// on exporte le routeur pour être utilisé dans app.js ou ailleurs
module.exports = router;

103
server/server.js Normal file
View File

@@ -0,0 +1,103 @@
/*
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 express = require("express"); // framework web
const { exec } = require("child_process"); // si besoin d’exécuter des commandes (pas utilisé ici directement)
const fs = require("fs"); // accès au système de fichiers
const path = require("path"); // gestion de chemins
const debug = require("debug")("freedom-loader:server");
// petit debugger coloré en console
const { logger, logSessionStart, logSessionEnd } = require("./logger");
// récupère notre logger Winston + helpers de session
const app = express(); // instancie l'application Express
// logSessionStart(); // désactivé, mais pourrait logguer le début de session
// on définit le dossier de téléchargement dans le dossier Téléchargements de l’utilisateur
const downloadsPath = path.join(process.env.USERPROFILE, "Downloads");
const outputFolder = path.join(downloadsPath, "Freedom Loader Output");
// création du dossier s’il n’existe pas
if (!fs.existsSync(outputFolder)) {
try {
fs.mkdirSync(outputFolder, { recursive: true });
logger.info("Dossier Freedom Loader Output cree dans Telechargements.");
} catch (err) {
logger.error("Impossible de creer le dossier :", err);
process.exit(1); // arrêt du programme en cas d’échec
}
} else {
logger.info("Dossier Freedom Loader Output deja existant.");
}
// on rend ce dossier dispo via app.locals
app.locals.outputFolder = outputFolder;
// configuration pour parser les requêtes POST en urlencoded
app.use(express.urlencoded({ extended: true }));
// sert les fichiers statiques (frontend)
const staticPath = path.join(__dirname, "../public");
debug("Serveur statique sur", staticPath);
app.use(express.static(staticPath));
// enregistre les routes API
const infoRoute = require("./routes/info");
const downloadRoute = require("./routes/download");
debug("Routes /download et /info installees");
app.use("/download", downloadRoute);
app.use("/info", infoRoute);
// route GET / de base
app.get("/", (req, res) => {
debug("Requete GET / servie");
res.sendFile(path.join(__dirname, "../public/index.html"));
});
// fonction pour démarrer le serveur (Promise pour pouvoir await ailleurs)
function startServer() {
return new Promise((resolve, reject) => {
logger.info("Demarrage du serveur Express...");
const serverInstance = app.listen(8080, () => {
logger.info("Serveur Express pret sur http://localhost:8080");
resolve(serverInstance); // succès
});
serverInstance.on("error", (err) => {
logger.error("Erreur serveur Express :", err);
reject(err);
});
});
}
// gestion de la fermeture propre du process
// process.on("exit", () => {
// logSessionEnd();
// });
process.on("SIGINT", () => { // Ctrl+C
logSessionEnd();
process.exit();
});
process.on("SIGTERM", () => { // kill
logSessionEnd();
process.exit();
});
// on exporte la fonction startServer pour que d’autres modules puissent la lancer
module.exports = { startServer };