Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c415c9106 | ||
|
|
b0495d033d | ||
|
|
d72cc6e0ca | ||
|
|
b59cb9f57f | ||
|
|
af743be5b0 | ||
|
|
024d039701 | ||
|
|
e9a91656bc | ||
|
|
3505606b44 | ||
|
|
815abae0a0 | ||
|
|
004271241e | ||
|
|
8295967ff2 | ||
|
|
053dca365c | ||
|
|
62110b1a5b | ||
|
|
aae9b2866c | ||
|
|
0d8bddbc20 | ||
|
|
2a7d61bab7 | ||
|
|
de71035fa0 | ||
|
|
5368fb9311 | ||
|
|
3d583a9b6f | ||
|
|
94de0a42f1 | ||
|
|
5ba2901273 | ||
|
|
f5888b8382 | ||
|
|
6ece182629 | ||
|
|
777257b71d | ||
|
|
df0156d603 | ||
|
|
68cdad06ab | ||
|
|
ee7c6520b9 | ||
|
|
4ed1609e41 | ||
|
|
b64e61d45d | ||
|
|
b4c2c865d8 | ||
|
|
e32ffd8fa0 | ||
|
|
8be6a932e5 | ||
|
|
6ed59fd272 | ||
|
|
4df1cb207f | ||
|
|
d03e3bc7e5 | ||
|
|
df02acb97f | ||
|
|
6ef04700ab | ||
|
|
766694f330 | ||
|
|
93380b2f36 | ||
|
|
8c969d694f | ||
|
|
f2f3d42e8c | ||
|
|
8d8973acfc | ||
|
|
281bff2379 | ||
|
|
e30f54050f | ||
|
|
0ae808c1a0 | ||
|
|
2598573844 | ||
|
|
27782376a3 | ||
|
|
876f7e1db0 | ||
|
|
db654c5f82 | ||
|
|
751f3d5c3e | ||
|
|
2a8d307bc8 | ||
|
|
ec6963cff3 | ||
|
|
196a07977a | ||
|
|
f20366f0c7 | ||
|
|
f54dc18e85 | ||
|
|
251049c7fe | ||
|
|
5d347967b1 | ||
|
|
d5c506c4e0 | ||
|
|
6718f4133e | ||
|
|
9ff6678730 | ||
|
|
3957450fdb | ||
|
|
25e50a6e7a | ||
|
|
22f935f678 | ||
|
|
58b843630b | ||
|
|
dec3f3272a | ||
|
|
f3252c2548 | ||
|
|
ac28afb17a | ||
|
|
b1a42f44fa | ||
|
|
78eea2fa0f | ||
|
|
4290e6db52 | ||
|
|
0ffc1af218 | ||
|
|
10cefdba19 | ||
|
|
c888946588 | ||
|
|
9026c63979 | ||
|
|
cfc2c5bbaa | ||
|
|
a41da5c4c2 | ||
|
|
b4242e4295 |
7
.gitignore
vendored
@@ -1,9 +1,10 @@
|
|||||||
/node_modules
|
/node_modules
|
||||||
/downloads
|
/downloads
|
||||||
/dist
|
/dist
|
||||||
/logs
|
|
||||||
/out
|
/out
|
||||||
|
|
||||||
|
/ressources/ffmpeg.exe
|
||||||
|
/ressources/ffprobe.exe
|
||||||
|
|
||||||
yt-dlp.exe
|
logs/*.json
|
||||||
forge.config.js
|
logs/*.log
|
||||||
103
README-EN.md
@@ -1,103 +0,0 @@
|
|||||||
# Freedom Loader
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
> 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`
|
|
||||||
|
|
||||||
<!--  -->
|
|
||||||
<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 [](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/votre-repo/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/votre-utilisateur/votre-repo/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.
|
|
||||||
116
README-FR.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# Freedom Loader
|
||||||
|
|
||||||
|
> A besoin de FFMPEG pour fonctionner, si vous avez besoin d'aide pour l'installer. Suivez [Ce lien](https://lecrabeinfo.net/tutoriels/installer-ffmpeg-sur-windows/).
|
||||||
|
|
||||||
|
<!-- Release -->
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<!-- Build (Next Release) -->
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 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 de vidéos avec les métadonnées (MP4)
|
||||||
|
- ✅ Téléchargement audio uniquement, avec la couverture ainsi que les métadonnées (MP3)
|
||||||
|
- ✅ Support des playlists complètes
|
||||||
|
- ✅ Récupération automatique des métadonnées (titre, durée, etc.)
|
||||||
|
- ✅ 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 l’interface
|
||||||
|
- ✅ Interface simple, intuitive
|
||||||
|
- ✅ Notification quand le téléchargement est fini (Click = Ouverture Dossier Output)
|
||||||
|
- ✅ Discord Rich Presence
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 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`.
|
||||||
|
|
||||||
|
<!--  -->
|
||||||
|
<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
|
||||||
|
- [x] ~~Extraction / découpage de la vidéo via chapitrage natif~~
|
||||||
|
- [ ] Téléchargements parallèles
|
||||||
|
- [ ] Suppression automatique des parties sponsorisées (skip sponsor)
|
||||||
|
- [x] ~~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/apercu1.0.1.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 [](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.
|
||||||
117
README.md
@@ -1,90 +1,103 @@
|
|||||||
# Freedom Loader
|
# Freedom Loader
|
||||||
|
|
||||||

|
<!-- Release -->
|
||||||
|
|
||||||
> 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
|
> Require FFMPEG, Follow the tutorial [here](https://lecrabeinfo.net/tutoriels/installer-ffmpeg-sur-windows/) if you need some help.
|
||||||
|
|
||||||
## 🚀 Fonctionnalités
|

|
||||||
|
|
||||||
✅ Téléchargement Vidéo (MP4)
|
<!-- Build (Next Release) -->
|
||||||
✅ Téléchargement Audio (MP3)
|
|
||||||
✅ Logs détaillés (console + fichiers logs)
|

|
||||||
✅ Récupération automatique des informations
|
|
||||||
✅ Téléchargement des playlists
|
> 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 with metadata(MP4)
|
||||||
|
- ✅ Audio-only download with thumbail and metadata(MP3)
|
||||||
|
- ✅ Full playlist support
|
||||||
|
- ✅ Automatic metadata retrieval (title, duration, etc.)
|
||||||
|
- ✅ 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)
|
||||||
|
- ✅ Discord Rich Presence
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔧 Installation
|
## 🔧 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.
|
To install the application, simply download the installer from the releases section. Take the latest version and run it.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ❓ Questions fréquentes
|
## ❓ Frequently Asked Questions
|
||||||
|
|
||||||
### _Où se situent mes fichiers une fois téléchargés ?_
|
### _Where are my downloaded files located?_
|
||||||
|
|
||||||
Dans le dossier Téléchargements (**C:\Users\VOTRE NOM\Downloads**) de votre ordinateur, dans un dossier nommé **Freedom Loader Output**
|
In your computer's Downloads folder (**C:\Users\YOUR NAME\Downloads**), inside a folder named **Freedom Loader Output**
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
### _Une fois le programme installé et lancé, j'ai une erreur_
|
### _After installing and running the program, I get an error_
|
||||||
|
|
||||||
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`.
|
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`
|
||||||
|
|
||||||
<!--  -->
|
<!--  -->
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="build/example-developertools.png" alt="Exemple Developer Tools" />
|
<img src="build/example-developertools.png" alt="Example Developer Tools" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## 🏗️ Fonctionnalités à venir (Roadmap)
|
## 🏗️ Upcoming Features (Roadmap)
|
||||||
|
|
||||||
- [ ] Plus de choix de formats
|
- [ ] More format options
|
||||||
- [ ] Support des sous-titres
|
- [ ] Subtitle support
|
||||||
- [ ] Meilleure UI / UX
|
- [ ] Improved UI / UX
|
||||||
- [ ] Choix des langues
|
- [ ] Language selection
|
||||||
- [ ] Téléchargement d’une partie de la vidéo
|
- [ ] Download a specific part of the video
|
||||||
- [ ] Renommage du fichier de sortie
|
- [ ] Rename output file
|
||||||
- [ ] Extraction / découpage de la vidéo via chapitrage natif
|
- [x] ~~Extract / Split video using native chapters~~
|
||||||
- [ ] Téléchargements parallèles
|
- [ ] Parallel downloads
|
||||||
- [ ] Suppression automatique des parties sponsorisées (skip sponsor)
|
- [ ] Skip sponsored parts of videos
|
||||||
- [ ] Choix personnalisé du chemin de sortie (par défaut **C:\Users\Utilisateur\Downloads**, où un dossier **Freedom Loader Output** sera créé)
|
- [x] ~~Custom output path selection (default **C:\Users\Username\Downloads** with a **Freedom Loader Output** folder created)~~
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🖼️ Aperçu
|
## 🖼️ Preview
|
||||||
|
|
||||||
<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 l’application lancée après avoir saisi une URL.
|
> Here is an example of the app after launch and entering a URL
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ℹ️ À propos
|
## ℹ️ About
|
||||||
|
|
||||||
Ce projet a été réalisé en utilisant [](https://github.com/yt-dlp/yt-dlp)
|
This project was built using [](https://github.com/yt-dlp/yt-dlp)
|
||||||
Merci à eux de nous permettre d’utiliser leur API gratuitement et en open source.
|
Thanks to them for providing a free and open-source API.
|
||||||
|
|
||||||
Ce projet est open source et accueille volontiers vos contributions.
|
This project is open-source and welcomes your contributions.
|
||||||
Voici comment participer :
|
Here is how to get involved:
|
||||||
|
|
||||||
- **🪲 Signalement de bugs**
|
- **🪲 Bug Reporting**
|
||||||
Merci d’utiliser le [système d’issues GitHub](https://github.com/MasterAcnolo/votre-repo/issues) pour remonter les bugs rencontrés.
|
Please use the [GitHub issues system](https://github.com/MasterAcnolo/Freedom-Loader/issues) to report any bugs you encounter.
|
||||||
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 :
|
Try to provide clear reproduction steps and logs if possible. Logs are located in the installation folder of the program. Example path:
|
||||||
`C:\Users\[VOTRE NOM]\Desktop\Freedom Loader\resources\app\logs`
|
`C:\Users\[YOUR NAME]\Desktop\Freedom Loader\resources\app\logs`
|
||||||
Le fichier aura un nom du type `combined-20xx-xx-xx.log`.
|
The log file will be named something like `combined-20xx-xx-xx.log`
|
||||||
<br>
|
<br>
|
||||||
- **🛠️ Contribution**
|
- **🛠️ 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.).
|
Pull Requests are welcome! Please fork the repo and submit your PR after ensuring you meet the contribution guidelines (tests, style, etc.).
|
||||||
<br>
|
<br>
|
||||||
- **💡 Suggestions de fonctionnalités**
|
- **💡 Feature Suggestions**
|
||||||
Vous pouvez proposer de nouvelles idées via une [issue de type feature request](https://github.com/votre-utilisateur/votre-repo/issues).
|
You can propose new ideas via a [feature request issue](https://github.com/MasterAcnolo/Freedom-Loader/issues).
|
||||||
Toute proposition claire et détaillée sera étudiée.
|
Any clear and detailed proposal will be reviewed.
|
||||||
<br>
|
<br>
|
||||||
- **💰 Soutenir le projet**
|
- **💰 Support the Project**
|
||||||
<p>
|
<p>
|
||||||
Si vous souhaitez m’encourager, vous pouvez faire un don sur
|
If you'd like to support me, you can donate via
|
||||||
<a href="https://paypal.me/axelnicolas25">
|
<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">
|
<img src="https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white" alt="PayPal">
|
||||||
</a>
|
</a>
|
||||||
@@ -92,12 +105,12 @@ Voici comment participer :
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📄 Licence
|
## 📄 License
|
||||||
|
|
||||||
Ce projet est sous licence **MIT** — voir le fichier [LICENSE](./LICENSE) pour plus d’informations.
|
This project is licensed under the **MIT License** — see the [LICENSE](./LICENSE) file for details.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚠️ Disclaimer
|
## ⚠️ 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.
|
I have obviously used AI assistance for some parts of this project. Use AI sparingly and always be aware of what you are doing.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
BIN
build/apercu1.0.1.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
build/confirm-icon.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 264 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 264 KiB After Width: | Height: | Size: 146 KiB |
44
forge.config.js
Normal 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,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
117
main.js
@@ -16,59 +16,150 @@
|
|||||||
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({
|
||||||
width: 1000,
|
title: "Freedom Loader",
|
||||||
height: 700,
|
width: 750,
|
||||||
|
height: 800,
|
||||||
|
minWidth: 750,
|
||||||
|
minHeight: 800,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
|
preload: path.join(__dirname, "preload.js"), // Chargement du preload script sécurisé
|
||||||
},
|
},
|
||||||
|
// titleBarStyle: 'hidden', // Option possible pour une barre de titre personnalisée
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
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();
|
||||||
});
|
});
|
||||||
3566
package-lock.json
generated
14
package.json
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "freedom-loader",
|
"name": "freedom-loader",
|
||||||
"productName": "Freedom Loader",
|
"productName": "Freedom Loader",
|
||||||
"version": "1.0.0",
|
"version": "1.2.1",
|
||||||
"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",
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
"debug": "^4.4.1",
|
"debug": "^4.4.1",
|
||||||
|
"discord-rpc": "^4.0.1",
|
||||||
"electron-squirrel-startup": "^1.0.1",
|
"electron-squirrel-startup": "^1.0.1",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
@@ -36,12 +37,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
@@ -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"),
|
||||||
|
});
|
||||||
BIN
public/assets/icons/GitHub Icon.ico
Normal file
|
After Width: | Height: | Size: 97 KiB |
3
public/assets/icons/GitHub Icon.svg
Normal 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 |
BIN
public/assets/images/Chirac.png
Normal file
|
After Width: | Height: | Size: 3.2 MiB |
BIN
public/assets/images/Cyberpunk.webp
Normal file
|
After Width: | Height: | Size: 805 KiB |
BIN
public/assets/images/NF.jpg
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
public/assets/images/Vilbrequin.webp
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/assets/images/spicy.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
@@ -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">
|
||||||
@@ -41,29 +41,50 @@
|
|||||||
<!-- <input type="checkbox" name="audioOnly" value="1"> Audio seulement (MP3) -->
|
<!-- <input type="checkbox" name="audioOnly" value="1"> Audio seulement (MP3) -->
|
||||||
<select name="quality">
|
<select name="quality">
|
||||||
<option value="best">Meilleure qualité</option>
|
<option value="best">Meilleure qualité</option>
|
||||||
|
<option value="medium">Qualité moyenne</option>
|
||||||
<option value="worst">Qualité minimale</option>
|
<option value="worst">Qualité minimale</option>
|
||||||
|
<option value="1080">1080p</option>
|
||||||
|
<option value="720">720p</option>
|
||||||
|
<option value="480">480p</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<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
@@ -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");
|
||||||
|
}
|
||||||
71
public/script/custompath.js
Normal 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");
|
||||||
|
|
||||||
|
// 1️Essayer de charger depuis le localStorage
|
||||||
|
let savedPath = localStorage.getItem("customDownloadPath");
|
||||||
|
|
||||||
|
// 2️Sinon 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
107
public/script/customthemes.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
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",
|
||||||
|
nf : "NF",
|
||||||
|
chirac: "Chirac",
|
||||||
|
fanatic: "Fanatic",
|
||||||
|
cyberpunk: "Cyberpunk",
|
||||||
|
spicy: "Spicy",
|
||||||
|
vilbrequin: "Vilbrequin"
|
||||||
|
};
|
||||||
|
|
||||||
|
const themeSubtitles = {
|
||||||
|
default: "Because why not?",
|
||||||
|
dark: "Darkness is my ally",
|
||||||
|
light: "Qui aime ce thème ?",
|
||||||
|
nf : "You call it music, i call it my Therapist",
|
||||||
|
chirac: "J'aime les pommes",
|
||||||
|
fanatic: "Always Fnatic !",
|
||||||
|
cyberpunk: "Wake up, choom. We’ve got a city to burn.",
|
||||||
|
spicy: "The Spiciest One",
|
||||||
|
vilbrequin: "Rend l'argent"
|
||||||
|
};
|
||||||
|
|
||||||
|
const themeSelect = document.getElementById("themeSelect");
|
||||||
|
|
||||||
|
// Remplir le select avec les options
|
||||||
|
function populateThemeSelect() {
|
||||||
|
for (const [key, label] of Object.entries(themes)) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = key;
|
||||||
|
option.textContent = label;
|
||||||
|
themeSelect.appendChild(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appliquer le thème sur le body et changer le subtitle
|
||||||
|
function applyTheme(themeName) {
|
||||||
|
// Supprime les classes thème précédentes
|
||||||
|
document.body.classList.remove(...Object.keys(themes));
|
||||||
|
// Ajoute la classe du thème actuel
|
||||||
|
document.body.classList.add(themeName);
|
||||||
|
|
||||||
|
// Modifier le subtitle
|
||||||
|
const subtitle = document.getElementById("subtitle");
|
||||||
|
if (subtitle && themeSubtitles[themeName]) {
|
||||||
|
subtitle.textContent = themeSubtitles[themeName];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionnel : animation spéciale Chirac
|
||||||
|
if (themeName === "chirac") {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
document.body.classList.add("theme-active");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove("theme-active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sauvegarder le thème en localStorage
|
||||||
|
function saveTheme(themeName) {
|
||||||
|
localStorage.setItem("selectedTheme", themeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Charger le thème au démarrage
|
||||||
|
function loadTheme() {
|
||||||
|
const savedTheme = localStorage.getItem("selectedTheme");
|
||||||
|
if (savedTheme && themes[savedTheme]) {
|
||||||
|
applyTheme(savedTheme);
|
||||||
|
themeSelect.value = savedTheme;
|
||||||
|
} else {
|
||||||
|
applyTheme("dark");
|
||||||
|
themeSelect.value = "dark";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quand l’utilisateur change le select
|
||||||
|
themeSelect.addEventListener("change", (e) => {
|
||||||
|
const selected = e.target.value;
|
||||||
|
if (themes[selected]) {
|
||||||
|
applyTheme(selected);
|
||||||
|
saveTheme(selected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialisation
|
||||||
|
populateThemeSelect();
|
||||||
|
loadTheme();
|
||||||
@@ -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 d’attente pour informer l’utilisateur
|
||||||
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 n’est 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 s’est produite.";
|
statusDiv.textContent = "❌ Une erreur s’est produite.";
|
||||||
} finally {
|
} finally {
|
||||||
// Réactive le bouton pour autoriser d'autres soumissions
|
// Réactive le bouton pour permettre d’autres 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 l’interface propre
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
statusDiv.textContent = "";
|
statusDiv.textContent = "";
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
/* From Uiverse.io by bociKond */
|
|
||||||
/* Hide the default checkbox */
|
|
||||||
.checkbox-mp3 input {
|
.checkbox-mp3 input {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -16,52 +14,47 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create a custom checkbox */
|
|
||||||
.checkmark {
|
.checkmark {
|
||||||
--clr: #0B6E4F;
|
--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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* When the checkbox is checked, add a blue background */
|
|
||||||
.checkbox-mp3 input:checked ~ .checkmark {
|
.checkbox-mp3 input:checked ~ .checkmark {
|
||||||
background-color: var(--clr);
|
background-color: var(--clr);
|
||||||
border-radius: .5rem;
|
border-radius: .5rem;
|
||||||
animation: pulse 500ms ease-in-out;
|
animation: pulse 500ms ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create the checkmark/indicator (hidden when not checked) */
|
|
||||||
.checkmark:after {
|
.checkmark:after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show the checkmark when checked */
|
|
||||||
.checkbox-mp3 input:checked ~ .checkmark:after {
|
.checkbox-mp3 input:checked ~ .checkmark:after {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style the checkmark/indicator */
|
|
||||||
.container .checkmark:after {
|
.container .checkmark:after {
|
||||||
left: 0.45em;
|
left: 0.45em;
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0% {
|
0% {
|
||||||
box-shadow: 0 0 0 #0B6E4F90;
|
box-shadow: 0 0 0 var(--checkbox-pulse-color);
|
||||||
rotate: 20deg;
|
rotate: 20deg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,11 +63,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
75% {
|
75% {
|
||||||
box-shadow: 0 0 0 10px #0B6E4F60;
|
box-shadow: 0 0 0 10px var(--checkbox-pulse-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
box-shadow: 0 0 0 13px #0B6E4F30;
|
box-shadow: 0 0 0 13px var(--checkbox-pulse-color);
|
||||||
rotate: 0;
|
rotate: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
42
public/styles/components/editpathbutton.css
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
.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: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 550;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-path button#changePath:hover {
|
||||||
|
background-color: var(--form-button-bg-hover-color);
|
||||||
|
}
|
||||||
38
public/styles/components/themebutton.css
Normal 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;
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
padding: 1.5em 2em;
|
padding: 1.5em 2em;
|
||||||
background-color: #fff;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,30 +3,43 @@
|
|||||||
@import url("layout/ctr-infos.css");
|
@import url("layout/ctr-infos.css");
|
||||||
@import url("layout/header.css");
|
@import url("layout/header.css");
|
||||||
@import url("layout/videoinfo.css");
|
@import url("layout/videoinfo.css");
|
||||||
@import url("button/checkbox.css");
|
@import url("components/checkbox.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;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
/* outline: 2px solid red; */
|
/* outline: 2px solid red; */ /*<-- Utile pour le début ça */
|
||||||
font-family: "Poppins", sans-serif;
|
font-family: "Poppins", sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
/* background-color: #f5f5f5; */
|
background-color: var(--background-color);
|
||||||
background-image: linear-gradient(to left top, #838383, #a1a1a1, #bfbfbf, #dfdfdf, #ffffff);
|
/* background-image: linear-gradient(to left top, #838383, #a1a1a1, #bfbfbf, #dfdfdf, #ffffff);
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed; */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
color: #333;
|
color: var(--default-text-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -37,23 +50,19 @@ body {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header p{
|
||||||
/* #videoInfo {
|
color: var(--subtitle-color);
|
||||||
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 {
|
.checkbox-inline{
|
||||||
opacity: 1;
|
color: var(--mp3-text-color);
|
||||||
max-height: 1000px;
|
}
|
||||||
} */
|
|
||||||
|
body,
|
||||||
|
.container,
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
label {
|
||||||
|
transition: background-color 0.2s ease, color 0.5s ease, border-color 0.2s ease;
|
||||||
|
}
|
||||||
98
public/styles/theme/chirac-theme.css
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
body.chirac {
|
||||||
|
/* Background image Chirac */
|
||||||
|
background-image: url('../../assets/images/Chirac.png');
|
||||||
|
/* background-position: right; */
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-attachment: fixed;
|
||||||
|
|
||||||
|
/* Couleurs drapeau français */
|
||||||
|
--background-color: rgba(255, 255, 255, 0.85); /* blanc légèrement transparent pour lisibilité */
|
||||||
|
--default-text-color: #002395; /* bleu foncé */
|
||||||
|
--subtitle-color: #EF4135; /* rouge vif */
|
||||||
|
--mp3-text-color: #002395; /* bleu */
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
|
||||||
|
*/
|
||||||
45
public/styles/theme/cyberpunk-theme.css
Normal 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/Cyberpunk.webp');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
36
public/styles/theme/dark-theme.css
Normal 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;
|
||||||
|
}
|
||||||
40
public/styles/theme/default-theme.css
Normal 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;
|
||||||
|
}
|
||||||
36
public/styles/theme/fanatic-theme.css
Normal 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é */
|
||||||
|
}
|
||||||
36
public/styles/theme/light-theme.css
Normal 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;
|
||||||
|
}
|
||||||
48
public/styles/theme/nf-theme.css
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
body.nf {
|
||||||
|
/* Couleurs générales */
|
||||||
|
--background-color: #1a1a1a; /* fond sombre neutre */
|
||||||
|
--default-text-color: #e6dfd5; /* texte beige clair */
|
||||||
|
--subtitle-color: #f5f0e6; /* beige très clair (titres) */
|
||||||
|
--mp3-text-color: #d8cfc2; /* texte checkbox */
|
||||||
|
|
||||||
|
/* Formulaire */
|
||||||
|
--form-bg-color: #2a2a2a; /* fond formulaire gris sombre */
|
||||||
|
--form-input-bg-color: #222222; /* fond inputs sombre */
|
||||||
|
--form-input-border-color: #4a4036; /* bordure beige/brun */
|
||||||
|
--form-input-border-focus-color: #cbb89d; /* beige doré en focus */
|
||||||
|
--form-input-text-color: #f0e8dd; /* texte clair/beige */
|
||||||
|
--form-input-placeholder-color: #9a8f80; /* placeholder beige/gris */
|
||||||
|
--form-button-bg-color: #cbb89d; /* bouton beige doré */
|
||||||
|
--form-button-text-color: #1a1a1a; /* texte sombre */
|
||||||
|
--form-button-bg-hover-color: #b7a588; /* hover beige plus foncé */
|
||||||
|
|
||||||
|
/* Checkbox */
|
||||||
|
--checkbox-bg-default: #4a4036; /* brun/gris */
|
||||||
|
--checkbox-bg-checked: #cbb89d; /* beige doré */
|
||||||
|
--checkbox-checkmark-border-color: #1a1a1a; /* noir profond */
|
||||||
|
--checkbox-pulse-color: rgba(203,184,157,0.3); /* halo beige doux */
|
||||||
|
|
||||||
|
/* Download status */
|
||||||
|
--download-status-color: #cbb89d; /* beige doré */
|
||||||
|
|
||||||
|
/* Video Info Box */
|
||||||
|
--infos-box-color: #2a2a2a; /* fond sombre */
|
||||||
|
--video-info-text-color: #e6dfd5; /* texte beige clair */
|
||||||
|
--video-info-heading-color: #f5f0e6; /* titres beige clair */
|
||||||
|
--video-info-list-color: #d8cfc2; /* texte liste */
|
||||||
|
--video-info-list-strong-color: #ffffff; /* texte fort blanc cassé */
|
||||||
|
--video-info-link-color: #cbb89d; /* liens beige doré */
|
||||||
|
--video-info-link-hover-color: #b7a588; /* hover beige plus foncé */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fond inspiré de ton image beige */
|
||||||
|
body.nf {
|
||||||
|
background-image: url('../../assets/images/NF.jpg'); /* mets ton image ici */
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 70% center;
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.nf .download-path {
|
||||||
|
color: var(--default-text-color);
|
||||||
|
}
|
||||||
50
public/styles/theme/spicy-theme.css
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
body.spicy {
|
||||||
|
/* Couleurs générales */
|
||||||
|
--background-color: #121212; /* fond noir profond */
|
||||||
|
--default-text-color: #581010; /* texte clair */
|
||||||
|
--subtitle-color: #3d0909; /* accent rouge piment */
|
||||||
|
--mp3-text-color: #ffffff; /* texte checkbox inline */
|
||||||
|
|
||||||
|
/* Formulaire */
|
||||||
|
--form-bg-color: #1a1a1a; /* fond formulaire gris très foncé */
|
||||||
|
--form-input-bg-color: #2b2b2b; /* fond inputs gris foncé */
|
||||||
|
--form-input-border-color: #444444; /* bordure gris */
|
||||||
|
--form-input-border-focus-color: #ff1a1a; /* focus rouge piment */
|
||||||
|
--form-input-text-color: #eeeeee; /* texte inputs */
|
||||||
|
--form-input-placeholder-color: #888888; /* placeholder gris moyen */
|
||||||
|
--form-button-bg-color: #ff1a1a; /* bouton rouge vif */
|
||||||
|
--form-button-text-color: #ffffff; /* texte bouton blanc */
|
||||||
|
--form-button-bg-hover-color: #cc0000; /* hover rouge foncé */
|
||||||
|
|
||||||
|
/* Checkbox */
|
||||||
|
--checkbox-bg-default: #555555; /* gris moyen */
|
||||||
|
--checkbox-bg-checked: #ff1a1a; /* rouge piment */
|
||||||
|
--checkbox-checkmark-border-color: #f5f5f5; /* blanc cassé */
|
||||||
|
--checkbox-pulse-color: rgba(255, 26, 26, 0.5); /* pulse rouge translucide */
|
||||||
|
|
||||||
|
/* Download status */
|
||||||
|
--download-status-color: #ffffff; /* rouge vif */
|
||||||
|
|
||||||
|
/* Video Info Box */
|
||||||
|
--infos-box-color: #1a1a1a; /* fond infos gris foncé */
|
||||||
|
--video-info-text-color: #f5f5f5; /* texte clair */
|
||||||
|
--video-info-heading-color: #ff1a1a; /* titres rouges */
|
||||||
|
--video-info-list-color: #cccccc; /* texte liste gris clair */
|
||||||
|
--video-info-list-strong-color: #ffffff; /* texte important blanc */
|
||||||
|
--video-info-link-color: #ff1a1a; /* liens rouges */
|
||||||
|
--video-info-link-hover-color: #cc0000; /* liens hover rouge foncé */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optionnel: background piments */
|
||||||
|
body.spicy {
|
||||||
|
background-image: url('../../assets/images/Spicy.webp');
|
||||||
|
background-size:contain;
|
||||||
|
background-position: center;
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
9
public/styles/theme/themeimport.css
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
@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");
|
||||||
|
@import url("nf-theme.css");
|
||||||
48
public/styles/theme/vilbrequin-theme.css
Normal 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.webp'); /* adapte ton image */
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.vilbrequin .download-path{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
36
public/styles/variables.css
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
: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;
|
||||||
|
}
|
||||||
BIN
ressources/yt-dlp 2025.08.27.exe
Normal file
48
server/discordRPC.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
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 RPC = require("discord-rpc");
|
||||||
|
const clientId = "1410934537051181146";
|
||||||
|
const rpc = new RPC.Client({ transport: "ipc" });
|
||||||
|
|
||||||
|
function startRPC() {
|
||||||
|
rpc.on("ready", () => {
|
||||||
|
console.log("✅ Connecté à Discord !");
|
||||||
|
|
||||||
|
const presence = {
|
||||||
|
largeImageKey: "icon",
|
||||||
|
smallImageKey: "acnolo_pfp",
|
||||||
|
smallImageText: "By MasterAcnolo",
|
||||||
|
startTimestamp: new Date(), // compteur de temps
|
||||||
|
details: "github.com/MasterAcnolo/Freedom-Loader",
|
||||||
|
};
|
||||||
|
|
||||||
|
rpc.setActivity(presence);
|
||||||
|
|
||||||
|
// Mise à jour toutes les 15 secondes si tu veux garder le compteur à jour
|
||||||
|
setInterval(() => {
|
||||||
|
rpc.setActivity(presence);
|
||||||
|
}, 15000);
|
||||||
|
});
|
||||||
|
|
||||||
|
rpc.login({ clientId }).catch(err => {
|
||||||
|
console.error("Erreur Discord RPC :", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { startRPC };
|
||||||
@@ -18,22 +18,42 @@
|
|||||||
|
|
||||||
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");
|
const fs = require("fs"); // Pour vérifier/créer le dossier de logs
|
||||||
const path = require("path");
|
const path = require("path"); // Pour gérer proprement les chemins
|
||||||
|
const os = require("os"); // Pour gérer les chemins aussi
|
||||||
|
|
||||||
const logDir = path.join(__dirname, "../logs");
|
// Définition du dossier où seront stockés les logs
|
||||||
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir);
|
// const logDir = path.join(__dirname, "../logs"); Désactivé parce que cela requiert d'exec en admin mode. Donc pas pratique pour les gens
|
||||||
|
|
||||||
|
let logDir;
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
logDir = path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs");
|
||||||
|
} else {
|
||||||
|
logDir = path.join(os.homedir(), ".freedomloader", "logs");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Création du dossier logs s’il n’existe pas encore
|
||||||
|
//if (!fs.existsSync(logDir)) fs.mkdirSync(logDir); OBSOLETE
|
||||||
|
|
||||||
|
fs.mkdirSync(logDir, { recursive: true }); // Evite les erreurs
|
||||||
|
|
||||||
|
|
||||||
|
// Fonction utilitaire pour générer une ligne indiquant le début d’une session de logs
|
||||||
function getSessionStartLine() {
|
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 utilitaire pour générer une ligne indiquant la fin d’une 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
|
||||||
|
// - 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",
|
level: "info",
|
||||||
format: format.combine(
|
format: format.combine(
|
||||||
@@ -43,20 +63,22 @@ const logger = createLogger({
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
transports: [
|
transports: [
|
||||||
|
// Rotation quotidienne des fichiers de logs
|
||||||
new DailyRotateFile({
|
new DailyRotateFile({
|
||||||
dirname: logDir,
|
dirname: logDir, // dossier cible pour les logs
|
||||||
filename: "combined-%DATE%.log",
|
filename: "LOGS-%DATE%.log", // nom du fichier par date
|
||||||
datePattern: "YYYY-MM-DD",
|
datePattern: "YYYY-MM-DD", // pattern de date dans le nom
|
||||||
zippedArchive: false, // tu peux mettre true si tu veux compresser
|
zippedArchive: false, // ne pas compresser les archives
|
||||||
maxFiles: "14d", // conserve les logs des 14 derniers jours
|
maxFiles: "7d", // conserve 14 jours d’historique
|
||||||
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" },
|
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(),
|
||||||
@@ -69,16 +91,20 @@ const logger = createLogger({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Helper pour marquer clairement le début de session dans les logs
|
||||||
function logSessionStart() {
|
function logSessionStart() {
|
||||||
logger.info(getSessionStartLine());
|
logger.info(getSessionStartLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper pour marquer la fin de session dans les logs
|
||||||
function logSessionEnd() {
|
function logSessionEnd() {
|
||||||
logger.info(getSessionEndLine());
|
logger.info(getSessionEndLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export du logger principal et des helpers pour usage dans d’autres modules
|
||||||
module.exports = {
|
module.exports = {
|
||||||
logger,
|
logger,
|
||||||
logSessionStart,
|
logSessionStart,
|
||||||
logSessionEnd,
|
logSessionEnd,
|
||||||
|
logDir,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,9 +11,6 @@
|
|||||||
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");
|
const express = require("express");
|
||||||
@@ -21,75 +18,169 @@ const router = express.Router();
|
|||||||
const { execFile } = require("child_process");
|
const { execFile } = require("child_process");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
const { Notification } = require("electron");
|
||||||
|
const logger = require("../logger").logger;
|
||||||
|
|
||||||
const logger = require("../logger").logger; // importe ton logger Winston
|
// 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.exe');
|
const ytDlpPath = path.join(__dirname, '../../ressources/yt-dlp 2025.08.27.exe');
|
||||||
|
|
||||||
router.post("/", (req, res) => {
|
router.post("/", (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
// Récupération des options envoyées par le frontend via le formulaire
|
||||||
|
// On vérifie la présence de l'URL, le choix audioOnly, et la qualité demandée
|
||||||
const options = {
|
const options = {
|
||||||
url: req.body.url,
|
url: req.body.url,
|
||||||
audioOnly: req.body.audioOnly === "1",
|
audioOnly: req.body.audioOnly === "1",
|
||||||
quality: req.body.quality || "best",
|
quality: req.body.quality || "best",
|
||||||
subtitles: req.body.subs === "1",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Validation simple : si l'URL est manquante, on rejette la requête
|
||||||
if (!options.url) {
|
if (!options.url) {
|
||||||
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 !");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer le chemin Freedom Loader Output depuis app.locals
|
// 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;
|
||||||
|
|
||||||
if (!fs.existsSync(outputFolder)) {
|
// On normalise le chemin pour éviter des erreurs liées aux séparateurs ou chemins relatifs
|
||||||
fs.mkdirSync(outputFolder, { recursive: true });
|
requestedOutputFolder = path.normalize(requestedOutputFolder);
|
||||||
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é.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construire le template de sortie
|
// 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}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Construire les arguments pour execFile
|
// 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
|
||||||
|
"--ffmpeg-location", path.join(__dirname, "../../ressources/ffmpeg.exe")
|
||||||
|
];
|
||||||
|
|
||||||
|
// Si l'option audioOnly est activée, on ajoute les flags pour extraction audio en mp3
|
||||||
if (options.audioOnly) {
|
if (options.audioOnly) {
|
||||||
args.push("--extract-audio", "--audio-format", "mp3");
|
args.push(
|
||||||
|
"-f",
|
||||||
|
"bestaudio",
|
||||||
|
"--extract-audio",
|
||||||
|
"--audio-format",
|
||||||
|
"mp3",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.subtitles) {
|
|
||||||
args.push("--write-subs", "--sub-lang", "en");
|
|
||||||
}
|
|
||||||
|
|
||||||
args.push("-f", options.quality);
|
// Correspondance entre la qualité choisie et le format à demander à yt-dlp
|
||||||
|
const qualityMap = {
|
||||||
|
best: "bestvideo+bestaudio/best",
|
||||||
|
medium: "bestvideo[height<=720]+bestaudio/best[height<=720]",
|
||||||
|
worst: "worstvideo+worstaudio/worst",
|
||||||
|
1080: "bestvideo[height<=1080]+bestaudio/best[height<=1080]",
|
||||||
|
720: "bestvideo[height<=720]+bestaudio/best[height<=720]",
|
||||||
|
480: "bestvideo[height<=480]+bestaudio/best[height<=480]",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sélection du format basé sur la qualité, par défaut 'best' si option invalide
|
||||||
|
const format = qualityMap[options.quality] || "best";
|
||||||
|
args.push("-f", format);
|
||||||
|
|
||||||
|
// Ajout du template de sortie et de l'URL à télécharger
|
||||||
args.push("-o", outputTemplate);
|
args.push("-o", outputTemplate);
|
||||||
args.push(options.url);
|
args.push(options.url);
|
||||||
|
|
||||||
|
// Log de la commande complète pour traçabilité et débogage
|
||||||
|
logger.info(`Téléchargement demandé : url=${options.url}, audioOnly=${options.audioOnly}, quality=${options.quality}`);
|
||||||
logger.info(`Commande yt-dlp : ${ytDlpPath} ${args.join(" ")}`);
|
logger.info(`Commande yt-dlp : ${ytDlpPath} ${args.join(" ")}`);
|
||||||
|
|
||||||
|
// Lancement du processus yt-dlp avec les arguments définis
|
||||||
const child = execFile(ytDlpPath, args);
|
const child = execFile(ytDlpPath, args);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gestion des sorties standards (stdout) du processus,
|
||||||
|
on log chaque ligne pour suivre la progression ou informations diverses
|
||||||
|
*/
|
||||||
child.stdout.on("data", (data) => {
|
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 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()}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
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}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
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) {
|
||||||
|
|
||||||
|
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 {
|
||||||
res.status(500).send(`❌ yt-dlp a échoué avec le code : ${code}`);
|
res.status(500).send(`❌ yt-dlp a échoué avec le code : ${code}`);
|
||||||
@@ -97,6 +188,7 @@ router.post("/", (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// 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}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,28 +20,62 @@ const express = require("express");
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { execFile } = require("child_process");
|
const { execFile } = require("child_process");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const fs = require("fs");
|
||||||
|
const { logger } = require("../logger"); // On récupère ton logger Winston
|
||||||
|
|
||||||
const ytDlpPath = path.join(__dirname, "../../yt-dlp.exe");
|
// Path absolu vers l'exécutable yt-dlp
|
||||||
|
const ytDlpPath = path.join(__dirname, '../../ressources/yt-dlp 2025.08.27.exe');
|
||||||
|
|
||||||
|
// Vérification que yt-dlp.exe existe bien au lancement du module
|
||||||
|
if (!fs.existsSync(ytDlpPath)) {
|
||||||
|
logger.error(`❌ yt-dlp.exe introuvable à ${ytDlpPath}`);
|
||||||
|
throw new Error(`yt-dlp.exe introuvable à ${ytDlpPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route POST pour récupérer les métadonnées d'une vidéo via yt-dlp
|
||||||
router.post("/", (req, res) => {
|
router.post("/", (req, res) => {
|
||||||
const url = req.body.url;
|
const url = req.body.url;
|
||||||
if (!url) return res.status(400).send("❌ URL manquante");
|
|
||||||
|
|
||||||
execFile(ytDlpPath, ["--dump-json", url], (error, stdout, stderr) => {
|
// Validation basique : on refuse la requête sans URL
|
||||||
if (error) {
|
if (!url) {
|
||||||
console.error("Erreur lors de l'exécution de yt-dlp :", error);
|
logger.warn("Requête metadata sans URL");
|
||||||
console.error("stderr :", stderr);
|
return res.status(400).send("❌ URL manquante");
|
||||||
return res.status(500).send("❌ Impossible de récupérer les infos.");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
logger.info(`Requête metadata reçue pour ${url}`);
|
||||||
const info = JSON.parse(stdout);
|
|
||||||
res.json(info);
|
// Exécution de yt-dlp avec l'option --dump-json pour récupérer les infos vidéo
|
||||||
} catch (e) {
|
execFile(
|
||||||
console.error("Erreur lors du parsing JSON :", e);
|
ytDlpPath,
|
||||||
res.status(500).send("❌ JSON illisible.");
|
["--dump-json", url],
|
||||||
|
{ timeout: 10_000 }, // Timeout fixé à 10 secondes pour éviter blocage
|
||||||
|
(error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
// En cas d'erreur, on log l'erreur et le stderr, puis on renvoie un code 500
|
||||||
|
logger.error(`Erreur exécution yt-dlp: ${error.message}`);
|
||||||
|
logger.debug(`stderr: ${stderr}`);
|
||||||
|
return res.status(500).send("❌ Impossible de récupérer les infos.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// yt-dlp peut retourner plusieurs JSON séparés par des sauts de ligne
|
||||||
|
// On split et parse chacun proprement
|
||||||
|
const infos = stdout
|
||||||
|
.trim()
|
||||||
|
.split("\n")
|
||||||
|
.map(line => JSON.parse(line));
|
||||||
|
|
||||||
|
logger.info(`Infos récupérées pour ${url} (${infos.length} élément(s))`);
|
||||||
|
|
||||||
|
// Si on a un seul élément, on renvoie directement l'objet JSON, sinon un tableau
|
||||||
|
res.json(infos.length === 1 ? infos[0] : infos);
|
||||||
|
} catch (e) {
|
||||||
|
// Si parsing JSON échoue, on log et renvoie une erreur 500
|
||||||
|
logger.error(`Erreur parsing JSON: ${e.message}`);
|
||||||
|
return res.status(500).send("❌ JSON illisible.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -16,78 +16,91 @@
|
|||||||
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");
|
const express = require("express"); // Framework web pour créer l’API et servir les fichiers
|
||||||
const { exec } = require("child_process");
|
const { exec } = require("child_process"); // Permet d’exécuter des commandes systèmes (pas utilisé directement ici)
|
||||||
const fs = require("fs");
|
const fs = require("fs"); // Module système pour gérer fichiers et dossiers
|
||||||
const path = require("path");
|
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");
|
||||||
|
// Module de debug coloré en console, pratique pour dev
|
||||||
|
|
||||||
const { logger, logSessionStart, logSessionEnd } = require("./logger");
|
const { logger, logSessionStart, logSessionEnd } = require("./logger");
|
||||||
|
// Import du logger Winston et helpers pour marquer début/fin de session
|
||||||
|
|
||||||
const app = express();
|
const app = express(); // Création de l’instance Express, notre serveur web
|
||||||
|
|
||||||
// // Lancement d'une nouvelle session de log
|
// Définition du dossier par défaut où enregistrer les téléchargements
|
||||||
// logSessionStart();
|
// On prend le dossier Téléchargements de l’utilisateur Windows (USERPROFILE)
|
||||||
|
|
||||||
// dossier de sortie
|
|
||||||
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");
|
||||||
|
|
||||||
|
// Création du dossier de sortie s’il n’existe 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 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);
|
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 deja existant.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On rend ce dossier accessible globalement via app.locals pour l’utiliser dans les routes
|
||||||
app.locals.outputFolder = outputFolder;
|
app.locals.outputFolder = outputFolder;
|
||||||
|
|
||||||
|
// Middleware pour parser le corps des requêtes POST en application/x-www-form-urlencoded
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
|
||||||
|
// Définition du dossier contenant les fichiers statiques (frontend)
|
||||||
|
// Permet d’accéder au HTML, CSS, JS côté client
|
||||||
const staticPath = path.join(__dirname, "../public");
|
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));
|
||||||
|
|
||||||
// routes
|
// 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 / 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 Express
|
||||||
|
// Retourne une Promise pour pouvoir await le démarrage dans d’autres 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);
|
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 d’erreur critique
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gestion propre de la fin de session à la fermeture 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", () => {
|
|
||||||
logSessionEnd();
|
logSessionEnd();
|
||||||
process.exit();
|
process.exit();
|
||||||
});
|
});
|
||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => { // Capture kill ou arrêt du process
|
||||||
logSessionEnd();
|
logSessionEnd();
|
||||||
process.exit();
|
process.exit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Export de la fonction startServer pour permettre son appel depuis d’autres fichiers
|
||||||
module.exports = { startServer };
|
module.exports = { startServer };
|
||||||
|
|
||||||
|
const { startRPC } = require("./discordRPC");
|
||||||
|
startRPC();
|
||||||
|
|||||||