169 Commits
1.2.2 ... 1.4.0

Author SHA1 Message Date
MasterAcnolo
ac0b4a0750 1.4.0 - It's time to sleep
1.4.0 - It's time to sleep
2026-01-22 22:09:24 +01:00
MasterAcnolo
29ca2ea496 Fix: Auto Update mispelling (Am a monkey) 2026-01-22 21:39:07 +01:00
MasterAcnolo
2a3905bdfb Add: Video Codec 2026-01-21 20:43:15 +01:00
MasterAcnolo
d3755ab57d Fix: Remove unusued "Settings.svg" icon 2026-01-21 20:25:34 +01:00
MasterAcnolo
9824e107fe Fix: Try to force AAC Audio Codec 2026-01-21 13:29:33 +01:00
MasterAcnolo
e437722937 Merge pull request #27 from MasterAcnolo/sync
Sync
2026-01-21 11:32:19 +01:00
MasterAcnolo
fd8c4ae33c Fix: Check Info config every request 2026-01-21 11:16:53 +01:00
MasterAcnolo
967144bd55 Fix: Config not working propely 2026-01-21 11:08:42 +01:00
MasterAcnolo
cd8eb05020 Fix: Remove Unusued Import 2026-01-21 11:08:20 +01:00
MasterAcnolo
590060e504 Fix: Reference position bottom 2026-01-20 22:34:33 +01:00
MasterAcnolo
26c7d4d2ee Because why not ? 2026-01-20 22:30:41 +01:00
MasterAcnolo
caa987fa42 Fix: Remove unusued files 2026-01-20 22:30:22 +01:00
MasterAcnolo
ccb090e4b2 Add: Settings theme color 2026-01-20 22:29:56 +01:00
MasterAcnolo
8d070ed10d Fix: Neon Theme rename 2026-01-20 22:29:22 +01:00
MasterAcnolo
343dbb26a2 Feat: Settings Panel 2026-01-20 22:28:53 +01:00
MasterAcnolo
fef4f6de36 Sync: Synchro Settings Panel 2026-01-18 14:23:17 +01:00
MasterAcnolo
1a61972750 Add: Settings Icon 2026-01-18 14:11:01 +01:00
MasterAcnolo
45ae5d7f98 Fix: Update Dependencies 2026-01-18 14:09:41 +01:00
MasterAcnolo
fecb123d0e Fix: Indent Index.html 2026-01-18 13:51:17 +01:00
MasterAcnolo
5c62ed081a Fix: Update ReadME Banner Redirection 2026-01-18 11:28:27 +01:00
MasterAcnolo
8f0f31041d Fix: Remove Unusued Dependencies 2026-01-16 09:42:32 +01:00
MasterAcnolo
0e48ab3b4c Fix: Default Theme Info Color 2026-01-16 09:42:09 +01:00
MasterAcnolo
d4c33bcf2d FIx: Updated Appliction Version because i did too much work to be a "minor release" 2026-01-15 22:54:40 +01:00
MasterAcnolo
2f105f5145 Fix: Put config.json file into config folder for better readability + Fix Yt-DLP Path 2026-01-15 22:01:26 +01:00
MasterAcnolo
b7a193b062 Fix: Remove unusued import server.js 2026-01-15 21:23:26 +01:00
MasterAcnolo
65364d3095 Fix: Update Dependencies 2026-01-15 21:22:57 +01:00
MasterAcnolo
c356e2e30d Fix: Using ASAR for every parts of the projects (the server is now in it) and i put the binaries in a /binaries folder. It's better for readability of the resources folder 2026-01-15 21:22:40 +01:00
MasterAcnolo
e45810b54c Add: Better logs. Write logs when a download is init 2026-01-15 16:22:29 +01:00
MasterAcnolo
55db30febc Fix: Remove Useless Console.log 2026-01-15 16:21:43 +01:00
MasterAcnolo
163faec221 Feat: Add Configuration Button. user can edit config.json as wanted. Linked and IPC 2026-01-15 16:01:24 +01:00
MasterAcnolo
5398fed928 Fix: Remove unusued Variable 2026-01-15 10:59:45 +01:00
MasterAcnolo
f959171db5 Feat: Add CustomVideo Codec Choice with verification in config.json 2026-01-15 10:57:37 +01:00
MasterAcnolo
01b6bcd02c Feat: Add Newline YT-DLP Args. Add Toggle Switches for verbose, autodownloadplaylist + Fix Thumbail misspell 2026-01-15 10:00:17 +01:00
MasterAcnolo
0ff4715b35 Fix: Correct Config.json file for packaged version 2026-01-15 09:28:46 +01:00
MasterAcnolo
745a73a5b0 Feat: Add version badge controlled. Before we needed to write with the hand for the front. But now there is an IPC event that provide App Version from package.json 2026-01-15 09:03:50 +01:00
MasterAcnolo
a69114edce Feat: Package Update + Add NSIS Installer Banner 2026-01-14 22:49:34 +01:00
MasterAcnolo
489a4766fa Feat: Path Verification (Security #4) 2026-01-14 21:47:38 +01:00
MasterAcnolo
adcee62386 Fix: ReadME Badge Version 2026-01-14 20:41:55 +01:00
MasterAcnolo
b867d819b7 Fix: Increment App Version for 1.3.2 2026-01-14 14:04:06 +01:00
MasterAcnolo
31d8b20a76 Feat: Implement Config File. Read by Front and Back. It aims to give possibility to disable some features quickly. Currenty: DiscordRPC, AutoCheckInfo, AutoUpdate, Custom TopBar, AddMetadata, Add Thumbail were put and implement. Other need to be implement. That's the begin of Settings Panel 2026-01-14 14:03:32 +01:00
MasterAcnolo
42ae0e7204 Feat: Add Codec H264 to vidéo output 2026-01-14 10:06:17 +01:00
MasterAcnolo
43869420c0 Update: Update Script + Dependencies 2026-01-14 10:04:38 +01:00
MasterAcnolo
6c9d296fbd Add: Some Variables in config.js. For Modules 2025-12-30 11:38:18 +01:00
MasterAcnolo
008428f10a Fix: Remove Unsued Import in Controller 2025-12-28 12:47:00 +01:00
MasterAcnolo
ae7d338474 Add: Rate Limit to front rendering. Using Components for reusability 2025-12-28 12:40:55 +01:00
MasterAcnolo
12ff25cc4b Fix: Error Handling Download Controller 2025-12-27 12:15:28 +01:00
MasterAcnolo
f31767e7e7 Fix: Error Handling Info Controller 2025-12-27 12:14:21 +01:00
MasterAcnolo
7c4f2841e1 Fix: Security #5 2025-12-27 11:41:52 +01:00
MasterAcnolo
c451ddd042 Add pull request template for consistent feature submissions 2025-12-20 13:51:55 +01:00
MasterAcnolo
da6311055f Replace feature and version templates in PULL_REQUEST_TEMPLATE directory 2025-12-20 13:46:47 +01:00
MasterAcnolo
945efb2059 Add feature issue template for improved feature requests 2025-12-20 13:46:00 +01:00
MasterAcnolo
ab2881f8e9 Add version template for changelog and roadmap 2025-12-20 13:45:39 +01:00
MasterAcnolo
f23811fff4 Create SECURITY.md 2025-12-20 13:39:50 +01:00
MasterAcnolo
19ab77501b Create CONTRIBUTING.md for project guidelines
Added a comprehensive contributing guide for Freedom Loader, outlining how to report issues, submit pull requests, and improve documentation.
2025-12-20 13:35:21 +01:00
MasterAcnolo
37eeed583a Add Contributor Covenant Code of Conduct
This document outlines the Contributor Covenant Code of Conduct, including our pledge, standards of behavior, enforcement responsibilities, and consequences for violations.
2025-12-20 13:29:21 +01:00
MasterAcnolo
caf08efb90 Update issue templates 2025-12-20 13:25:58 +01:00
MasterAcnolo
7ff998e4df Merge pull request #21 from MasterAcnolo/build
1.3.1
2025-12-20 11:41:36 +01:00
MasterAcnolo
0c1e8fe899 refactor: Remove setupMenu function and its invocation from app initialization 2025-12-20 11:28:04 +01:00
MasterAcnolo
4158e8c6e3 feat: Add version badge 2025-12-20 11:18:54 +01:00
MasterAcnolo
653319e408 style: Update z-index for theme switcher and topbar; add custom scrollbar styles 2025-12-20 10:23:39 +01:00
MasterAcnolo
0a4f14fba7 fix: Update topbar width to use 100% instead of 100vw for better responsiveness 2025-12-20 10:10:42 +01:00
MasterAcnolo
fb4571fc51 Fix: Update release badge to version 1.3.1 in README 2025-12-20 09:54:35 +01:00
MasterAcnolo
c11fd40681 feat: Implement topbar with custom window controls and actions 2025-12-20 09:53:17 +01:00
MasterAcnolo
cbfde8b761 Fix: Startup verif + Update Package 2025-12-20 09:11:25 +01:00
MasterAcnolo
c774821cc4 Refactor badge links in README for improved layout and consistency 2025-12-19 14:21:35 +01:00
MasterAcnolo
2ed15a32bc Refactor dependencies section in README for improved layout and styling 2025-12-19 14:20:39 +01:00
MasterAcnolo
c252853603 Refactor layout of dependencies section in README for improved styling 2025-12-19 14:18:43 +01:00
MasterAcnolo
cfdddec073 Refactor badge links in README for improved readability 2025-12-19 14:18:03 +01:00
MasterAcnolo
97a63ab791 Update version to 1.3.1 and add update script to package.json 2025-12-19 14:15:37 +01:00
MasterAcnolo
a0e00341e1 Add native dependencies check and error handling on startup 2025-12-19 14:11:37 +01:00
MasterAcnolo
26d42491ce Bump version to 1.3.1 and fix Discord RPC state URL; enhance update notification with clickable link to releases 2025-12-19 13:50:17 +01:00
MasterAcnolo
d678ba2f12 Merge Main 2025-12-19 13:47:07 +01:00
MasterAcnolo
55b400eb05 Merge branch 'main' into build 2025-12-19 13:47:06 +01:00
MasterAcnolo
0a6313b1f3 Update Freedom Loader link in README.md 2025-12-16 16:27:51 +01:00
MasterAcnolo
b4312376bb Version 1.3.0
It's Time. Remember The Goal.
2025-11-21 14:10:26 +01:00
MasterAcnolo
5ffb143a1a Size Reduce README 2025-11-21 13:54:16 +01:00
MasterAcnolo
ee24473077 Size Reduce README 2025-11-21 13:53:55 +01:00
MasterAcnolo
2b98a63f92 ReadME Firefox 2025-11-21 13:52:09 +01:00
MasterAcnolo
2e594d87e8 ReadME Firefox 2025-11-21 13:45:29 +01:00
MasterAcnolo
b343c2869d Added Website Top Menu Section 2025-11-21 13:44:32 +01:00
MasterAcnolo
094b93f4fa Update Banner 2025-11-20 21:21:45 +01:00
MasterAcnolo
cdc8a623c0 Fix Wiki Path + Icon + Bring Back Spicy Theme + New banner (Logo) 2025-11-20 21:18:04 +01:00
MasterAcnolo
2b43c00a3a Better RPC 2025-11-20 14:42:52 +01:00
MasterAcnolo
14928798d4 Bring Back Spicy Fire Theme from darkness 2025-11-20 13:53:49 +01:00
MasterAcnolo
e5fb44fc6b ReadME Change Logo 2025-11-20 12:58:08 +01:00
MasterAcnolo
9f9e4cd1d8 ReadME Fix 2025-11-20 12:48:50 +01:00
MasterAcnolo
45fe7336fe New Logo 2025-11-20 12:47:17 +01:00
MasterAcnolo
b909e7d917 main fix 2025-11-20 10:35:50 +01:00
MasterAcnolo
ce62748ade ReadME 2025-11-20 10:28:18 +01:00
MasterAcnolo
d4ecc36f6b ReadME 2025-11-20 10:27:23 +01:00
MasterAcnolo
fee364610e ReadME 2025-11-20 10:26:07 +01:00
MasterAcnolo
492af8525b ReadME 2025-11-20 10:25:19 +01:00
MasterAcnolo
fb58b5ae06 ReadME 2025-11-20 10:24:30 +01:00
MasterAcnolo
577034b0bd English Index.html audio 2025-11-20 10:23:07 +01:00
MasterAcnolo
6fc452edb4 ReadME 2025-11-20 09:55:31 +01:00
MasterAcnolo
086ca49599 ReadME 2025-11-20 09:54:45 +01:00
MasterAcnolo
be1de35da8 ReadME 2025-11-20 09:52:21 +01:00
MasterAcnolo
3c1f57667f ReadME 2025-11-20 09:51:28 +01:00
MasterAcnolo
5997cd20b4 ReadME 2025-11-20 09:51:05 +01:00
MasterAcnolo
6220c9400e ReadME 2025-11-20 09:49:01 +01:00
MasterAcnolo
48a16b927f ReadME 2025-11-20 09:48:21 +01:00
MasterAcnolo
87545d77b4 English 2025-11-20 08:32:38 +01:00
MasterAcnolo
4e0fe29c20 Anglais Fetch Info.js + Removed Files Check 2025-11-19 18:24:50 +01:00
MasterAcnolo
f8e3bc843d Change Language Main.js 2025-11-19 18:09:42 +01:00
MasterAcnolo
b3f1edaabd Better Log Components Path 2025-11-19 16:55:26 +01:00
MasterAcnolo
ef784dd19c Translate English 2025-11-19 16:48:07 +01:00
MasterAcnolo
69bbcee885 NO AID HERE 2025-11-18 21:09:25 +01:00
MasterAcnolo
60b4a99506 NO AID HERE 2025-11-18 18:06:39 +01:00
MasterAcnolo
c1030400f5 Notify Logic 2025-11-18 13:55:56 +01:00
MasterAcnolo
3f508da501 Info Loading Bar 2025-11-17 16:54:11 +01:00
MasterAcnolo
0437cb61a2 Change Error Info Box + Clean HTML 2025-11-17 16:40:05 +01:00
MasterAcnolo
1372582c54 SongBird Theme + Playlist Background For Every Theme 2025-11-17 16:25:32 +01:00
MasterAcnolo
070c3d507f Download Speed 2025-11-17 15:33:25 +01:00
MasterAcnolo
3e9c545952 Clean Code 2025-11-15 23:54:41 +01:00
MasterAcnolo
e3845e1849 Loading Bar taskbar 2025-11-15 23:52:43 +01:00
MasterAcnolo
91703d6a91 Using Download Route MVC Model 2025-11-15 23:34:20 +01:00
MasterAcnolo
cff3101b03 Push 2025-11-15 23:14:05 +01:00
MasterAcnolo
c9a48ec64c Using MVC Model + AutoDeteck Package + Fix Download/Info 2025-11-15 15:06:19 +01:00
MasterAcnolo
e6419a9759 Fix Deno Not Using on Info.js 2025-11-15 01:26:54 +01:00
MasterAcnolo
9d1d2fc7a8 Deno Integré + Progress Bar Fix + Small Clean 2025-11-15 00:50:45 +01:00
MasterAcnolo
cdca4a82ab Removed Cyberpunk | Spicy | Vilbrequin Theme, Info Fix, Fix Progress Bar. Centralized Ressources 2025-11-14 23:41:03 +01:00
MasterAcnolo
c2e4e12512 ProgressBar + Playlist 2025-11-14 18:42:15 +01:00
MasterAcnolo
96c1c6717d Playlist Card + Safe Args 2025-11-14 13:02:55 +01:00
MasterAcnolo
abf776175c UpperCase 2025-11-12 18:23:58 +01:00
MasterAcnolo
d6de184bfd Autoupdate + FreedomKiller Démarrage 2025-11-12 18:00:07 +01:00
MasterAcnolo
12249c4e8a Fix AutoUpdate 2025-11-12 15:58:05 +01:00
MasterAcnolo
f1fbc3f0c7 Version Config Depend PackageJSON 2025-11-12 15:53:06 +01:00
MasterAcnolo
bb8cf54bf9 Auto Update GitHub 2025-11-12 15:33:07 +01:00
MasterAcnolo
dccf8bd786 Change log file path for bug reports
Updated log file path in README for bug reports.
2025-11-12 15:04:01 +01:00
MasterAcnolo
793bb79d76 Fix Logs path readme 2025-11-12 15:03:45 +01:00
MasterAcnolo
f5cdf27ce6 Log Discord RPC 2025-11-12 15:01:10 +01:00
MasterAcnolo
627c56cd25 Discord RPC Fix 2025-11-12 14:59:11 +01:00
MasterAcnolo
783871d131 Documentation Bouton + Package Lock Update 2025-11-12 14:53:49 +01:00
MasterAcnolo
138c05f995 Removed Licence Preload 2025-11-09 15:16:59 +01:00
MasterAcnolo
5009a479af Removed console.log RPC 2025-11-09 15:16:15 +01:00
MasterAcnolo
2a95a10113 Removed Else Statement 2025-11-09 13:21:04 +01:00
MasterAcnolo
9d6065c80a Notify Error + Cookies Gestion 2025-11-09 13:19:54 +01:00
MasterAcnolo
cd8b2bf007 RPC Com 2025-11-09 10:46:33 +01:00
MasterAcnolo
1faf74fbc4 Port Lisible Server.js 2025-11-09 10:46:00 +01:00
MasterAcnolo
09163afbcd YTDLP Path DevMode 2025-11-09 10:44:34 +01:00
MasterAcnolo
405d141055 Fix Double Log Start 2025-11-09 10:40:06 +01:00
MasterAcnolo
599e09fa9f Helpers + Clean Download et Info 2025-11-09 10:35:19 +01:00
MasterAcnolo
4954de5f1a Push avant Clean 2025-11-09 09:04:57 +01:00
MasterAcnolo
ac854ae559 Préparation du Champ de Bataille 2025-11-08 15:11:25 +01:00
MasterAcnolo
9944f05139 ReadME Okay for 1.3 2025-11-08 15:02:05 +01:00
MasterAcnolo
f6558b656a Fix Version Mismatch (1.3.1 -> 1.3.0) 2025-11-08 14:58:29 +01:00
MasterAcnolo
bfbf57ba38 Package UPDATE For 1.3.1 2025-11-08 14:57:45 +01:00
MasterAcnolo
d0af35377c NPM Script Clear 2025-11-08 14:53:39 +01:00
MasterAcnolo
59c263a246 Change Apercu README 2025-11-08 14:12:52 +01:00
MasterAcnolo
42700bd7d8 LatestRelease No Space 2025-11-08 14:09:59 +01:00
MasterAcnolo
5a9b364b4b LatestRelease Space 2025-11-08 14:09:38 +01:00
MasterAcnolo
3619550c74 ReadME Fix 2025-11-08 14:09:04 +01:00
MasterAcnolo
31cc4012ac ReadME Fix 2025-11-08 14:08:42 +01:00
MasterAcnolo
2e329a8584 ReadME Fix 2025-11-08 14:06:57 +01:00
MasterAcnolo
d92dd59f86 ReadME Fix 2025-11-08 14:06:46 +01:00
MasterAcnolo
5bb7ca377d Removed ReadME FR and Rework ReadME 2025-11-08 14:05:15 +01:00
MasterAcnolo
9ee9a3574c Change project license from MIT to GNU License 2025-11-08 10:31:33 +01:00
MasterAcnolo
dc50a44682 Clear NPM Scripts 2025-10-31 18:00:48 +01:00
MasterAcnolo
4be5c14999 Merge pull request #16 from MasterAcnolo/build-branch
1.2.4
2025-09-27 11:15:41 +02:00
MasterAcnolo
b92ffebdb4 YT-DLP Maj Automatique 2025-09-27 11:10:34 +02:00
MasterAcnolo
7b6e48b09e Préparation 1.2.4 2025-09-26 07:50:09 +02:00
MasterAcnolo
368851b4bb Merge pull request #15 from MasterAcnolo/build-branch
1.2.3
2025-09-25 21:51:13 +02:00
MasterAcnolo
fa2da5712d ReadME Update 2025-09-25 21:48:38 +02:00
MasterAcnolo
7df97d70bb YT-DLP Update + Fix Crash 2025-09-25 21:43:14 +02:00
MasterAcnolo
5c5f595829 Fix Spicy Image 2025-09-21 09:57:47 +02:00
MasterAcnolo
5b659e3d8a ffprobe 2025-09-20 18:22:43 +02:00
MasterAcnolo
1a526777cc FFPROBE 2025-09-20 18:22:07 +02:00
MasterAcnolo
b0d99d4c29 Update project purpose in README-FR.md 2025-09-20 17:59:17 +02:00
MasterAcnolo
c7554475a9 Update README.md 2025-09-20 17:58:53 +02:00
95 changed files with 4366 additions and 5417 deletions

View File

@@ -0,0 +1,35 @@
---
name: "[BUG] Issues Template "
about: Report something that doesn't work fine
title: "[BUG] Name of the bug / issues"
labels: bug
assignees: MasterAcnolo
---
## Check Before Reporting
Make sure this bug doesn't already exist in the issues.
## Describe the Bug
A clear and concise description of the problem.
## Steps to Reproduce
1. Go to…
2. Click on…
3. See the error…
## Expected Behavior
What you expected the program to do.
## Logs
Add logs if possible. Available at `C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\LOGS-20xx-xx-xx.log`.
## App Version
State the version of the app you are using.
## Environment
- Version: [e.g. 1.2.4]
- Browser / Other: [e.g. Chrome, Node.js 20]
- Any others information that can be usefull for me
## Screenshots (if applicable)

View File

@@ -0,0 +1,22 @@
---
name: "[FEAT] Feature Template"
about: Suggest an idea for this project
title: "[FEAT] Name of the features"
labels: enhancement
assignees: MasterAcnolo
---
## Check Before Requesting
Make sure this feature doesn't already exist in the issues.
## Feature Description
Describe the feature you want to add or improve.
## Why is this Feature Useful?
Explain the benefit for users or the project.
## Examples / Context
Provide an example or use case if possible.
## Screenshots / Mockups (if applicable)

View File

@@ -0,0 +1,29 @@
## Description
Describe the feature or improvement you are adding.
Include why it's useful and what problem it solves.
## Type of Change
- [ ] New feature
- [ ] Improvement / Refactor
- [ ] Documentation update
## Checklist
- [ ] Code tested locally
- [ ] No linting errors
- [ ] Added or updated tests if relevant
- [ ] Updated documentation if relevant
## Verification Steps
Explain how reviewers can test the new feature or improvement.
## App Version
State the version of Freedom Loader you are targeting.
## Notes / Additional Info
Any context, screenshots, or logs that can help reviewing the PR.

View File

@@ -0,0 +1,25 @@
# Freedom Loader - x.y.z
## Changelog
- List all changes made in this release (features, bug fixes, improvements)
- Use bullet points, keep it concise
- Example:
- `YT-DLP` Will now update when the application is launched
- Improved UI / UX
## Bug Reports / Issues
- Please report any bugs in the GitHub Issues section
## Next Release Roadmap (non-exhaustive)
- List upcoming features or improvements
- Example:
- More format options
- Subtitle support
- Language selection
- Parallel downloads
- Skip sponsored parts automatically
**Full Changelog**: https://github.com/MasterAcnolo/Freedom-Loader/compare/[previous-version]...[new-version]

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@
/ressources/ffmpeg.exe /ressources/ffmpeg.exe
/ressources/ffprobe.exe /ressources/ffprobe.exe
/ressources/deno.exe
logs/*.json logs/*.json
logs/*.log logs/*.log

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
masteracnolo25@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

66
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,66 @@
# Contributing to Freedom Loader
Welcome! Thanks for being interested in Freedom Loader. Your contributions-bug reports, feature ideas, or code improvements-are always appreciated. Every contribution makes Freedom Loader better 💪
---
## 1. Check Before Contributing
- Make sure your issue, bug, or feature request doesn't already exist.
- Search issues and pull requests before creating a new one.
- Follow the [Code of Conduct](CODE_OF_CONDUCT.md).
---
## 2. Report an Issue
Found a bug, weird behavior, or have an idea? Open an [**issue**](https://github.com/MasterAcnolo/Freedom-Loader/issues) using the **Bug Report template**.
Make sure to include:
- Steps to reproduce the issue
- App version and environment (OS, Node, Browser, etc.)
- Logs if possible: `C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\LOGS-20xx-xx-xx.log`
- Screenshots if relevant
---
## 3. Submit a Pull Request
To fix a bug, add a feature, or improve docs:
1. Fork the repository
2. Create a branch: `git checkout -b feature/my-awesome-feature`
3. Make your changes
4. Add tests or verification steps
5. Test everything locally
6. Open a PR to the `main` branch
**Tips for PRs:**
- Keep titles and descriptions clear and concise
- Document what you change and why
- Follow existing code style (camelCase for variables/functions)
- Small commits focused on a single purpose are easier to review
---
## 4. Improve the Documentation
Docs are important: README, wiki, guides… If something is unclear, improve it!
Small contributions like fixing typos or adding examples are always welcome.
---
## 5. Testing & Verification
- Test new features thoroughly
- Ensure bug fixes actually fix the reported issue
- Include any relevant logs or screenshots
---
## 6. Review & Merge
- After submitting a PR, it will be reviewed by maintainers
- Be open to feedback and changes
- We try to respond quickly, but sometimes have other priorities
---
## 7. Code of Conduct
Please contribute respectfully and constructively. Everyone is welcome as long as discussions remain polite and productive.
---
Thanks to Zakaria for the website icon. Check him out here: [IG Link](https://www.instagram.com/designmark_studio/) 🔥

View File

@@ -1,114 +0,0 @@
# Freedom Loader
<!-- Release -->
![Static Badge](https://img.shields.io/badge/Release-1.2.2-blue?style=flat&logo=Test)
<!-- Build (Next Release) -->
![Static Badge](https://img.shields.io/badge/Build-1.2.3--beta-yellow?style=flat&logo=Test)
> Ce projet a pour but de permettre le téléchargement de contenu Vidéo/Audio de manière gratuite, open source et libre. Il permettra à terme de choisir
## 🚀 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 linterface
- ✅ 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`.
<!-- ![Exemple Chemin](build/example-developertools.png) -->
<p align="center">
<img src="build/example-developertools.png" alt="Exemple Developer Tools" />
</p>
## 🏗️ Fonctionnalités à venir (Roadmap)
- [ ] Plus de choix de formats
- [ ] Support des sous-titres
- [ ] Meilleure UI / UX
- [ ] Choix des langues
- [ ] Téléchargement dune 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 lapplication lancée après avoir saisi une URL.
---
## À propos
Ce projet a été réalisé en utilisant [![yt-dlp](https://img.shields.io/badge/yt--dlp-Visit%20Repo-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/yt-dlp/yt-dlp)
Merci à eux de nous permettre dutiliser leur API gratuitement et en open source.
Ce projet est open source et accueille volontiers vos contributions.
Voici comment participer :
- **🪲 Signalement de bugs**
Merci dutiliser le [système dissues 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 dinstallation 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 mencourager, 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 dinformations.
---
## ⚠️ Disclaimer
Jai bien évidemment eu recours à lIA pour maider sur certaines parties du projet. Utilisez lIA avec parcimonie et soyez toujours conscient de ce que vous faites.

196
README.md
View File

@@ -1,114 +1,150 @@
# Freedom Loader <div align="center">
<a href="https://masteracnolo.github.io/FreedomLoader/"><img
src="./build/banner.png"
alt="Banner"
style="width: 50%;;"/></a>
</div>
<!-- Release --> <div align="center">
<a href="https://github.com/MasterAcnolo/Freedom-Loader/releases"><img src="https://img.shields.io/badge/Release-1.4.0-blue?style=for-the-badge"></a>
![Static Badge](https://img.shields.io/badge/Release-1.2.2-blue?style=flat&logo=Test) <a href="https://masteracnolo.github.io/FreedomLoader/index.html"><img src="https://img.shields.io/badge/Freedom%20Loader-Open%20Page-111111?style=for-the-badge&logo=terminal&logoColor=white"></a>
<a href="https://www.firefox.com/fr/?utm_campaign=SET_DEFAULT_BROWSER"><img src="https://img.shields.io/badge/Require Firefox-E66000?style=for-the-badge&logo=Firefox-Browser&logoColor=white"></a>
<!-- Build (Next Release) --> </div>
![Static Badge](https://img.shields.io/badge/Build-1.2.3--beta-yellow?style=flat&logo=Test)
> This project aims to enable free, open-source, and unrestricted downloading of Video/Audio content. Eventually, it will allow users to choose
## 🚀 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
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> <br>
### _After installing and running the program, I get an error_ Freedom Loader is a **Windows desktop** application built with **Electron**.
It provides a simple and reliable way to download video or audio content with metadata and advanced options, without ads or questionable third-party services.
The goal is to offer a clean, user-friendly, and transparent download experience. It's important for me to make media content downloading accessible to people who love music or videos and dont always have an internet connection.
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` ---
## Features
- Video download with metadata (MP4)
- Audio-only download with thumbnail and tags (MP3)
- Full playlist support and better display in UI
- Automatic metadata retrieval (title, duration, author, etc.)
- Detailed logs (console + rotating files for 7 days)
- Progress Bar for download and network speed indicator
- Better UX when fetching info
- Custom output path with persistence
- Automatic dependency updates (YT-DLP)
- Automatic Application Update
- Multiple UI themes
- Simple, responsive interface
- Notifications on download completion (click to open folder)
- Discord Rich Presence
---
## Installation
Download the latest installer from the **Releases** page and run it (Defender could stop you but click on "Run Anyway" and it's good).
Actually, **Firefox Browser** is the only browser supported for get cookies. You should install it. Follow the tutorial in the wiki page if you need some help, Firefox Section. [Link](https://masteracnolo.github.io/FreedomLoader/pages/wiki.html)
---
## FAQ
### Where are my downloaded files stored?
In your **Downloads** folder, inside **Freedom Loader**.
Example path:
`C:\Users\[YOUR NAME]\Downloads\Freedom Loader`
### I get an error after launching the app
Try restarting the application, checking the logs, or opening the developer console:
`CTRL + SHIFT + I` -> “Toggle Developer Tools”.
<!-- ![Example Path](build/example-developertools.png) -->
<p align="center"> <p align="center">
<img src="build/example-developertools.png" alt="Example Developer Tools" /> <img src="build/example-developertools.png" width="20%" />
</p> </p>
## 🏗️ Upcoming Features (Roadmap) ---
## Roadmap
- [x] ~~Website for Freedom Loader~~
- [ ] More format options - [ ] More format options
- [ ] Linux version
- [x] ~~Auto-updating YT-DLP~~
- [x] ~~Automatic project updates~~
- [x] ~~Better download status~~
- [ ] Better website support (currently only YouTube is fully supported, other site could work but i can't prove it)
- [ ] Subtitle support - [ ] Subtitle support
- [ ] Improved UI / UX - [x] ~~UI/UX improvements~~
- [ ] Language selection - [ ] Language selection
- [ ] Download a specific part of the video - [ ] Download specific parts of a video
- [ ] Rename output file - [ ] Custom file naming
- [x] ~~Extract / Split video using native chapters~~
- [ ] Parallel downloads - [ ] Parallel downloads
- [ ] Skip sponsored parts of videos - [ ] Automatic sponsor skipping
- [x] ~~Custom output path selection (default **C:\Users\Username\Downloads** with a **Freedom Loader Output** folder created)~~ - [x] ~~Extract/split video using native chapters~~
- [x] ~~Custom output path selection~~
--- ---
## 🖼️ Preview ## Preview
<p align="center"> <p align="center">
<img src="build/apercu1.0.1.png" alt="Exemple d'application" /> <img src="build/apercu1.2.4.png" width="40%" />
</p> </p>
> Here is an example of the app after launch and entering a URL
--- ---
## About ## Technologies
This project was built using [![yt-dlp](https://img.shields.io/badge/yt--dlp-Visit%20Repo-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/yt-dlp/yt-dlp) This project uses:
Thanks to them for providing a free and open-source API.
This project is open-source and welcomes your contributions.
Here is how to get involved:
- **🪲 Bug Reporting**
Please use the [GitHub issues system](https://github.com/MasterAcnolo/Freedom-Loader/issues) to report any bugs you encounter.
Try to provide clear reproduction steps and logs if possible. Logs are located in the installation folder of the program. Example path:
`C:\Users\[YOUR NAME]\Desktop\Freedom Loader\resources\app\logs`
The log file will be named something like `combined-20xx-xx-xx.log`
<br>
- **🛠️ Contribution**
Pull Requests are welcome! Please fork the repo and submit your PR after ensuring you meet the contribution guidelines (tests, style, etc.).
<br>
- **💡 Feature Suggestions**
You can propose new ideas via a [feature request issue](https://github.com/MasterAcnolo/Freedom-Loader/issues).
Any clear and detailed proposal will be reviewed.
<br>
- **💰 Support the Project**
<p> <p>
If you'd like to support me, you can donate via <a href="https://github.com/yt-dlp/yt-dlp"><img src="https://img.shields.io/badge/yt--dlp-Visit%20Repo-cf0000?style=for-the-badge&logo=github&logoColor=white"></a>
<a href="https://paypal.me/axelnicolas25"> <a href="https://github.com/FFmpeg/FFmpeg"><img src="https://img.shields.io/badge/FFmpeg-Visit%20Repo-666666?style=for-the-badge&logo=ffmpeg&logoColor=white"></a>
<img src="https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white" alt="PayPal"> <a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Node.js-Visit%20Repo-339933?style=for-the-badge&logo=node.js&logoColor=white"></a>
</a> <a href="https://github.com/electron/electron"><img src="https://img.shields.io/badge/Electron-Visit%20Repo-47848F?style=for-the-badge&logo=electron&logoColor=white"></a>
<a href="https://github.com/denoland/deno/"><img src="https://img.shields.io/badge/Deno-Visit%20Repo-14e0cc?style=for-the-badge&logo=deno&logoColor=white"></a>
</p> </p>
--- ---
## 📄 License ## Contributing
This project is licensed under the **MIT License** — see the [LICENSE](./LICENSE) file for details. Contributions are welcome.
### Bug Reports
Use the GitHub Issues system and include reproduction steps and logs when possible.
Logs can be found here:
`C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\LOGS-20xx-xx-xx.log`
### Feature Requests
Open a feature request issue with a clear description and use cases.
### Pull Requests
Fork the repository, make your changes, and submit a PR once everything is tested and clean.
--- ---
## ⚠️ Disclaimer ## Support the Project
I have obviously used AI assistance for some parts of this project. Use AI sparingly and always be aware of what you are doing. If you'd like to support development:
<a href="https://paypal.me/axelnicolas25">
<img src="https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white" />
</a>
---
## License
Freedom Loader is released under the **GNU General Public License v3.0 (GPLv3)**.
You are free to use, modify, and redistribute the software under the terms of this license.
See the full license in the [LICENSE](./LICENSE) file.
---
## Note
AI assistance was used occasionally to speed up development, but all code and decisions were reviewed and validated manually.

40
SECURITY.md Normal file
View File

@@ -0,0 +1,40 @@
# Security Policy
## Supported Versions
We actively maintain security updates for the following versions (Since the 1.3.0 the application auto-update, patch will be apply easily):
| Version | Supported |
| -------- | ----------------- |
| 1.3.x | :white_check_mark: |
| 1.2.x | :white_check_mark: |
| 1.1.x | :white_check_mark:|
| <1.1 | :white_check_mark: |
> Only versions marked with ✅ receive security updates. Older versions may be vulnerable.
---
## Reporting a Vulnerability
If you discover a security vulnerability in Freedom Loader, **do not create a public issue**. Report it privately:
- Email: **masteracnolo25@gmail.com**
- Include:
- Description of the vulnerability
- Steps to reproduce it
- App version and environment (OS, Node, Browser if relevant)
- Logs or screenshots if possible (`C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\LOGS-20xx-xx-xx.log`)
We will respond as quickly as possible, typically within **48 hours**, and keep you updated on the fix.
We may acknowledge responsible disclosures in release notes unless you request anonymity.
---
## What Happens Next
- We will confirm receipt of your report
- Investigate and reproduce the issue
- Deploy a fix and release an update
- Notify you when the fix is live

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

BIN
build/apercu1.2.4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
build/app-icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
build/banner.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

BIN
build/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
build/error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

26
config.js Normal file
View File

@@ -0,0 +1,26 @@
const packageJson = require("./package.json");
const { app } = require("electron");
const fs = require("fs");
const path = require("path");
const localMode = !app.isPackaged;
const featuresPath = localMode ? path.join(__dirname, "./config/config.json") : path.join(path.join(process.resourcesPath,"config/" ,"config.json"));
let features = {};
function loadFeatures() {
const raw = fs.readFileSync(featuresPath, "utf-8");
features = JSON.parse(raw);
return features;
}
const configFeatures = loadFeatures();
module.exports = {
version: packageJson.version,
applicationPort: "8787",
localMode,
DiscordRPCID: "1410934537051181146",
configFeatures
}

15
config/config.json Normal file
View File

@@ -0,0 +1,15 @@
{
"autoUpdate": true,
"discordRPC": true,
"customTopBar": true,
"autoCheckInfo": true,
"addThumbnail": true,
"addMetadata": true,
"verboseLogs": false,
"autoDownloadPlaylist": true,
"customCodec": "h264",
"logSystem": true,
"outputTitleCheck": true,
"downloadSystem": true,
"notifySystem": true
}

View File

@@ -1,44 +0,0 @@
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,
}),
],
};

312
main.js
View File

@@ -1,165 +1,267 @@
/* const config = require("./config.js");
This file is part of Freedom Loader.
Copyright (C) 2025 MasterAcnolo
Freedom Loader is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
Freedom Loader is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { app, BrowserWindow, ipcMain, dialog, Menu, shell } = 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 os = require("os"); const os = require("os");
const fs = require("fs");
const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger"); const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger");
const { AutoUpdater } = require("./server/update.js");
const { configFeatures } = require("./config.js");
const { startRPC } = require("./server/discordRPC");
let mainWindow; let mainWindow;
// Utilise le vrai dossier de logs défini dans logger.js
const logsFolderPath = logDir; const logsFolderPath = logDir;
app.setAppUserModelId("com.masteracnolo.freedomloader"); /* Pour la notif */
/*
Fonction principale qui crée la fenêtre principale de l'application.
Elle évite la création multiple et configure les dimensions et options de la fenêtre.
Charge l'URL locale du serveur Express et gère les erreurs éventuelles.
*/
async function createWindow() {
logger.info("Création de la fenêtre...");
const basePath = config.localMode
? path.join(__dirname )
: path.join(path.dirname(process.execPath), "resources");
const configFolderPath = path.join(basePath, "config" ,"config.json");
const defaultDownloadPath = path.join(os.homedir(), "Downloads", "Freedom Loader");
app.setAppUserModelId("com.masteracnolo.freedomloader"); // pour notifications Windows
app.disableHardwareAcceleration();
ipcMain.handle("version", () => config.version);
// Gestion single instance
const gotLock = app.requestSingleInstanceLock();
// Native dependencies check (yt-dlp.exe, ffmpeg.exe, ffprobe.exe, Deno)
function checkNativeDependencies() {
const deps = [
{ name: "yt-dlp.exe", path: path.join(process.resourcesPath, "binaries","yt-dlp.exe") },
{ name: "ffmpeg.exe", path: path.join(process.resourcesPath, "binaries", "ffmpeg.exe") },
{ name: "ffprobe.exe", path: path.join(process.resourcesPath, "binaries", "ffprobe.exe") },
{ name: "deno.exe", path: path.join(process.resourcesPath, "binaries", "deno.exe") },
];
const missing = deps.filter(dep => !fs.existsSync(dep.path));
let errorMsg = "";
if (missing.length > 0) {
const missingList = missing.map(dep => dep.name).join(", ");
logger.error(`Missing dependencies: ${missingList}`);
errorMsg += `The following files are missing in the 'ressources' folder:\n${missingList}`;
}
if (errorMsg) {
app.whenReady().then(() => {
dialog.showErrorBox(
"Missing dependencies",
`${errorMsg}\n\nThe application will now exit. Try to reinstall`
);
app.quit();
});
return false;
}
return true;
}
if(!config.localMode){
if (!gotLock) {
// Une instance existe déjà -> fermer l'ancienne et continuer la nouvelle
// Ici la nouvelle instance continue normalement
} else {
if (!checkNativeDependencies()) {
// Arrêt déjà géré dans la fonction
} else {
app.on("second-instance", () => {
// La vieille instance se ferme
if (mainWindow) { if (mainWindow) {
logger.warn("La fenêtre existe déjà, pas de nouvelle création"); logger.info("New Instance Detected, closing the older...");
mainWindow.destroy();
mainWindow = null;
}
});
}
}
}
// Création fenêtre principale
async function createMainWindow() {
if (mainWindow) {
logger.warn("Window already exists, no new creation!");
return; return;
} }
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
title: "Freedom Loader 1.2.2", // A Changer à chaque Version title: `Freedom Loader ${config.version}`,
width: 750, width: 750,
height: 800, height: 800,
minWidth: 750, minWidth: 750,
minHeight: 800, minHeight: 800,
frame: !configFeatures.customTopBar,
webPreferences: { webPreferences: {
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
preload: path.join(__dirname, "preload.js"), // Chargement du preload script sécurisé preload: path.join(__dirname, "preload.js"),
}, },
// titleBarStyle: 'hidden', // Option possible pour une barre de titre personnalisée
}); });
try { try {
await mainWindow.loadURL("http://localhost:8080"); await mainWindow.loadURL(`http://localhost:${config.applicationPort}`);
logger.info("Fenêtre chargée"); logger.info("Window Loaded");
} catch (err) { } catch (err) {
logger.error("Erreur chargement fenêtre:", err); logger.error("Window Loading Error:", err);
} }
// Événement déclenché à la fermeture de la fenêtre principale
mainWindow.on("closed", () => { mainWindow.on("closed", () => {
logger.info("Fenêtre principale fermée"); logger.info("Main Window Closed");
mainWindow = null; mainWindow = null;
}); });
} }
/* function validateDownloadPath(userPath) {
Définition du chemin par défaut pour les téléchargements. const userHome = os.homedir(); // C:\Users\<User>
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");
/* if (!userPath) return path.join(userHome, "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. // Résolution canonique et suivi des symlinks
On retourne le chemin sélectionné ou null si annulation. const resolved = fs.realpathSync(path.resolve(userPath));
*/ const normalizedHome = path.resolve(userHome) + path.sep;
if (!resolved.startsWith(normalizedHome)) {
throw new Error("Chemin non autorisé : uniquement les sous-dossiers du dossier utilisateur sont permis !");
}
return resolved;
}
// IPC
ipcMain.handle("select-download-folder", async () => { ipcMain.handle("select-download-folder", async () => {
logger.info("Demande de sélection d'un dossier reçue depuis le renderer");
try { try {
const result = await dialog.showOpenDialog({ const result = await dialog.showOpenDialog({ properties: ["openDirectory"] });
properties: ["openDirectory"]
});
if (!result.canceled && result.filePaths.length > 0) { if (!result.canceled && result.filePaths.length > 0) {
logger.info(`Dossier sélectionné : ${result.filePaths[0]}`); const validatedPath = validateDownloadPath(result.filePaths[0]);
return result.filePaths[0]; logger.info(`Folder Checked and Valid : ${validatedPath}`);
return validatedPath;
} }
return null; return null;
} catch (err) { } catch (err) {
logger.error(`Erreur lors de la sélection de dossier : ${err.message}`); logger.error(`An Error Occured when validating folder : ${err.message}`);
return null; return null;
} }
}); });
/* ipcMain.handle("validate-download-path", (event, userPath) => {
Gestionnaire IPC pour exposer au renderer le chemin par défaut de téléchargement return validateDownloadPath(userPath);
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. ipcMain.handle("get-default-download-path", () => defaultDownloadPath);
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. ipcMain.on("set-progress", (event, percent) => {
*/ if (mainWindow) mainWindow.setProgressBar(percent / 100); // Electron attend 0 → 1
});
// Topbar window controls
ipcMain.on("window-minimize", () => {
if (mainWindow) mainWindow.minimize();
});
ipcMain.on("window-maximize", () => {
if (mainWindow) {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize();
} else {
mainWindow.maximize();
}
}
});
ipcMain.on("window-close", () => {
if (mainWindow) mainWindow.close();
});
// Topbar custom actions
ipcMain.on("open-devtools", () => {
if (mainWindow) mainWindow.webContents.openDevTools({ mode: 'detach' });
});
ipcMain.on("open-logs", () => {
if (logsFolderPath) shell.openPath(logsFolderPath);
});
ipcMain.on("open-website", () => {
shell.openExternal("https://masteracnolo.github.io/FreedomLoader/index.html");
});
ipcMain.on("open-wiki", () => {
shell.openExternal("https://masteracnolo.github.io/FreedomLoader/pages/wiki.html");
});
ipcMain.on("open-config", () => {
if (configFolderPath) shell.openPath(configFolderPath);
});
// App ready
app.whenReady().then(async () => { app.whenReady().then(async () => {
logSessionStart(); logSessionStart();
logger.info("App prête, démarrage du serveur Express..."); logger.info("App Ready, Server Express starting...");
const expressServer = require("./server/server.js");
const serverPath = path.join(__dirname, "server", "server.js")
const expressServer = require(serverPath);
try { try {
await expressServer.startServer(); await expressServer.startServer();
logger.info("Serveur Express démarré"); logger.info("Express Server Started");
await createWindow();
// Ajout du menu personnalisé ipcMain.handle("features", () => {
const menuTemplate = [ return configFeatures;
// ... Menu standard Electron ... });
{
label: "Logs",
submenu: [
{
label: "Ouvrir les logs",
click: () => {
shell.openPath(logsFolderPath);
}
}
]
}
];
// Fusionner avec le menu par défaut const featureWhitelist = new Set([
const defaultMenu = Menu.getApplicationMenu(); "autoUpdate",
let template = menuTemplate; "discordRPC",
if (defaultMenu) { "customTopBar",
template = [...Menu.getApplicationMenu().items.map(item => item), ...menuTemplate]; "autoCheckInfo",
} "addThumbnail",
const finalMenu = Menu.buildFromTemplate(template); "addMetadata",
Menu.setApplicationMenu(finalMenu); "verboseLogs",
"autoDownloadPlaylist",
"customCodec"
]);
} catch (error) {
logger.error("Erreur serveur ou fenêtre :", error); ipcMain.handle("set-feature", (event, { key, value }) => {
try {
if (!featureWhitelist.has(key)) {
logger.warn(`Rejected feature (not whitelisted): ${key}`);
return false;
}
// optionnel mais propre
if (configFeatures[key] === value) {
return true;
}
configFeatures[key] = value;
fs.writeFileSync(
configFolderPath,
JSON.stringify(configFeatures, null, 2),
"utf-8"
);
logger.info(`Feature updated: ${key} = ${value}`);
return true;
} catch (err) {
logger.error(`set-feature failed (${key}): ${err.message}`);
return false;
}
});
configFeatures.discordRPC ? startRPC() : "";
await createMainWindow();
configFeatures.autoUpdate ? AutoUpdater(mainWindow) : ""; // Auto Update
} catch (err) {
logger.error("Window or Server error :", err);
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 fenêtres fermées, quitte l'app"); logger.info("All Window Closed, shuting down app");
if (process.platform !== "darwin") app.quit(); if (process.platform !== "darwin") app.quit();
}); });
/* app.on("before-quit", () => logSessionEnd());
Avant de quitter l'application, on log la fin de session pour traçabilité.
*/
app.on("before-quit", () => {
logSessionEnd();
});

5159
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,40 +1,40 @@
{ {
"name": "freedom-loader", "name": "freedom-loader",
"productName": "Freedom Loader", "productName": "Freedom Loader",
"version": "1.2.2", "version": "1.4.0",
"author": "MasterAcnolo", "author": "MasterAcnolo",
"description": "Freedom Loader", "description": "Freedom Loader",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"start": "electron-forge start", "start": "electron --trace-warnings .",
"package": "electron-forge package", "build": "electron-builder",
"make": "electron-forge make", "update": "npm update"
"dist": "electron-builder"
}, },
"dependencies": { "dependencies": {
"chalk": "^4.1.2", "chalk": "^4.1.2",
"debug": "^4.4.1", "debug": "^4.4.1",
"discord-rpc": "^4.0.1", "discord-rpc": "^4.0.1",
"electron-squirrel-startup": "^1.0.1", "electron-updater": "^6.6.2",
"express": "^5.1.0", "express": "^5.1.0",
"express-rate-limit": "^8.2.1",
"webidl-conversions": "^8.0.0",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@electron-forge/cli": "^7.8.1",
"@electron-forge/maker-deb": "^7.8.1",
"@electron-forge/maker-rpm": "^7.8.1",
"@electron-forge/maker-squirrel": "^7.8.1",
"@electron-forge/maker-zip": "^7.8.1",
"@electron-forge/plugin-auto-unpack-natives": "^7.8.1",
"@electron-forge/plugin-fuses": "^7.8.1",
"@electron/fuses": "^1.8.0",
"electron": "^37.1.0", "electron": "^37.1.0",
"electron-builder": "^26.0.12" "electron-builder": "^26.0.12"
}, },
"build": { "build": {
"appId": "com.masteracnolo.freedomloader", "appId": "com.masteracnolo.freedomloader",
"productName": "Freedom Loader", "productName": "Freedom Loader",
"publish": [
{
"provider": "github",
"owner": "MasterAcnolo",
"repo": "Freedom-Loader"
}
],
"files": [ "files": [
"**/*", "**/*",
"node_modules/**/*", "node_modules/**/*",
@@ -50,24 +50,49 @@
"to": "confirm-icon.png" "to": "confirm-icon.png"
}, },
{ {
"from": "ressources/yt-dlp 2025.08.27.exe", "from": "build/app-icon.ico",
"to": "yt-dlp 2025.08.27.exe" "to": "app-icon.ico"
},
{
"from": "build/banner.bmp",
"to": "banner.bmp"
},
{
"from": "build/error.png",
"to": "error.png"
},
{
"from": "ressources/yt-dlp.exe",
"to": "binaries/yt-dlp.exe"
}, },
{ {
"from": "ressources/ffmpeg.exe", "from": "ressources/ffmpeg.exe",
"to": "ffmpeg.exe" "to": "binaries/ffmpeg.exe"
},
{
"from": "ressources/ffprobe.exe",
"to": "binaries/ffprobe.exe"
},
{
"from": "ressources/deno.exe",
"to": "binaries/deno.exe"
},
{
"from": "config/config.json",
"to": "config/config.json"
} }
], ],
"win": { "win": {
"target": "nsis", "target": "nsis",
"icon": "build/installer-icon.ico" "icon": "build/app-icon.ico"
}, },
"nsis": { "nsis": {
"oneClick": false, "oneClick": false,
"perMachine": true, "perMachine": true,
"allowToChangeInstallationDirectory": true, "allowToChangeInstallationDirectory": true,
"installerIcon": "build/installer-icon.ico", "installerIcon": "build/app-icon.ico",
"uninstallerIcon": "build/uninstaller-icon.ico", "uninstallerIcon": "build/uninstaller-icon.ico",
"installerSidebar": "build/banner.bmp",
"createDesktopShortcut": true, "createDesktopShortcut": true,
"createStartMenuShortcut": true, "createStartMenuShortcut": true,
"shortcutName": "Freedom Loader" "shortcutName": "Freedom Loader"

View File

@@ -1,31 +1,23 @@
/*
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"); 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", { contextBridge.exposeInMainWorld("electronAPI", {
getDefaultDownloadPath: () => ipcRenderer.invoke("get-default-download-path"), getDefaultDownloadPath: () => ipcRenderer.invoke("get-default-download-path"),
selectDownloadFolder: () => ipcRenderer.invoke("select-download-folder"), selectDownloadFolder: () => ipcRenderer.invoke("select-download-folder"),
setProgress: (percent) => ipcRenderer.send("set-progress", percent),
getFeatures: () => ipcRenderer.invoke("features"),
setFeature: (key, value) => ipcRenderer.invoke("set-feature", { key, value }),
getVersion: () => ipcRenderer.invoke("version"),
getValidatedDownloadPath: (path) => ipcRenderer.invoke("validate-download-path", path)
});
// Contrôles de fenêtre et outils custom pour la topbar
contextBridge.exposeInMainWorld("topbarAPI", {
minimize: () => ipcRenderer.send("window-minimize"),
maximize: () => ipcRenderer.send("window-maximize"),
close: () => ipcRenderer.send("window-close"),
openDevTools: () => ipcRenderer.send("open-devtools"),
openLogs: () => ipcRenderer.send("open-logs"),
openWebsite: () => ipcRenderer.send("open-website"),
openWiki: () => ipcRenderer.send("open-wiki"),
openConfig: () => ipcRenderer.send("open-config")
}); });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M128 128C92.7 128 64 156.7 64 192L64 448C64 483.3 92.7 512 128 512L512 512C547.3 512 576 483.3 576 448L576 192C576 156.7 547.3 128 512 128L128 128zM231 231C240.4 221.6 255.6 221.6 264.9 231L319.9 286L374.9 231C384.3 221.6 399.5 221.6 408.8 231C418.1 240.4 418.2 255.6 408.8 264.9L353.8 319.9L408.8 374.9C418.2 384.3 418.2 399.5 408.8 408.8C399.4 418.1 384.2 418.2 374.9 408.8L319.9 353.8L264.9 408.8C255.5 418.2 240.3 418.2 231 408.8C221.7 399.4 221.6 384.2 231 374.9L286 319.9L231 264.9C221.6 255.5 221.6 240.3 231 231z"/></svg>

After

Width:  |  Height:  |  Size: 751 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M128 128C92.7 128 64 156.7 64 192L64 448C64 483.3 92.7 512 128 512L512 512C547.3 512 576 483.3 576 448L576 192C576 156.7 547.3 128 512 128L128 128zM152 192L488 192C501.3 192 512 202.7 512 216C512 229.3 501.3 240 488 240L152 240C138.7 240 128 229.3 128 216C128 202.7 138.7 192 152 192z"/></svg>

After

Width:  |  Height:  |  Size: 515 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M64 480C64 462.3 78.3 448 96 448L544 448C561.7 448 576 462.3 576 480C576 497.7 561.7 512 544 512L96 512C78.3 512 64 497.7 64 480z"/></svg>

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -1,3 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 968 B

View File

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

Before

Width:  |  Height:  |  Size: 955 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 805 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -11,15 +11,194 @@
"> ">
<title></title> <title></title>
<!--CSS-->
<link rel="stylesheet" href="styles/styles.css"> <link rel="stylesheet" href="styles/styles.css">
<link rel="stylesheet" href="styles/layout/topbar.css">
<!--Icons-->
<link rel="icon" href="assets/icons/file-audio-solid.svg" type="image/svg+xml">
</head> </head>
<body> <body>
<div class="container"> <div class="topbar" id="topbar">
<div class="custom-controls">
<button id="devtools-btn" title="Dev Tools">Tools</button>
<button id="logs-btn" title="Logs">Logs</button>
<button id="website-btn" title="Website">Website</button>
<button id="wiki-btn" title="Wiki">Wiki</button>
</div>
<div class="window-controls">
<button id="minimize-btn" title="Réduire"><img src="assets/icon/minimize.svg"></button>
<button id="maximize-btn" title="Maximiser"><img src="assets/icon/maximize.svg"></button>
<button id="close-btn" title="Fermer"><img src="assets/icon/close.svg"></button>
</div>
</div>
<button class="settings-button" id="settings-button">
<svg width="800px" height="800px" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><title>ionicons-v5-q</title><path d="M262.29,192.31a64,64,0,1,0,57.4,57.4A64.13,64.13,0,0,0,262.29,192.31ZM416.39,256a154.34,154.34,0,0,1-1.53,20.79l45.21,35.46A10.81,10.81,0,0,1,462.52,326l-42.77,74a10.81,10.81,0,0,1-13.14,4.59l-44.9-18.08a16.11,16.11,0,0,0-15.17,1.75A164.48,164.48,0,0,1,325,400.8a15.94,15.94,0,0,0-8.82,12.14l-6.73,47.89A11.08,11.08,0,0,1,298.77,470H213.23a11.11,11.11,0,0,1-10.69-8.87l-6.72-47.82a16.07,16.07,0,0,0-9-12.22,155.3,155.3,0,0,1-21.46-12.57,16,16,0,0,0-15.11-1.71l-44.89,18.07a10.81,10.81,0,0,1-13.14-4.58l-42.77-74a10.8,10.8,0,0,1,2.45-13.75l38.21-30a16.05,16.05,0,0,0,6-14.08c-.36-4.17-.58-8.33-.58-12.5s.21-8.27.58-12.35a16,16,0,0,0-6.07-13.94l-38.19-30A10.81,10.81,0,0,1,49.48,186l42.77-74a10.81,10.81,0,0,1,13.14-4.59l44.9,18.08a16.11,16.11,0,0,0,15.17-1.75A164.48,164.48,0,0,1,187,111.2a15.94,15.94,0,0,0,8.82-12.14l6.73-47.89A11.08,11.08,0,0,1,213.23,42h85.54a11.11,11.11,0,0,1,10.69,8.87l6.72,47.82a16.07,16.07,0,0,0,9,12.22,155.3,155.3,0,0,1,21.46,12.57,16,16,0,0,0,15.11,1.71l44.89-18.07a10.81,10.81,0,0,1,13.14,4.58l42.77,74a10.8,10.8,0,0,1-2.45,13.75l-38.21,30a16.05,16.05,0,0,0-6.05,14.08C416.17,247.67,416.39,251.83,416.39,256Z" style="fill:none;stroke:var(--settings-button-text-color);stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/></svg>
</button>
<div class="settings-panel" id="settings-panel">
<div class="settings-modal">
<button class="close-btn" onclick="closePanel()"></button>
<div class="settings-title">
<h2>SETTINGS</h2>
<p style="font-size: 0.8em;">Changes are saved automatically. <br> Somes changes could need a restart</p>
</div>
<!-- Basic Settings visibles -->
<div class="settings-section">
<h3>Basic Settings</h3>
<div class="setting-item" id="theme" style="margin-left: 32px;">
<div class="setting-info">
<div style="display: flex;">
<span class="setting-title">Thème</span>
<select id="themeSelect" data-key="theme" aria-label="Choisir le thème">
</select>
</div>
<small>Choose the theme of the application</small>
</div>
</div>
<div class="setting-item">
<input type="checkbox" data-key="discordRPC" id="discordRPC">
<div class="setting-info">
<span class="setting-title">Discord RPC</span>
<small>Enable/disable Discord Rich Presence for your app.</small>
</div>
</div>
<div class="setting-item">
<input type="checkbox" data-key="autoDownloadPlaylist" id="autoDownloadPlaylist">
<div class="setting-info">
<span class="setting-title">Download Playlist automatically?</span>
<small>If enabled, playlists will download without confirmation.</small>
</div>
</div>
<!-- <div class="setting-item">
<input type="checkbox" data-key="notifySystem" id="notifySystem">
<div class="setting-info">
<span class="setting-title">Notification at the end</span>
<small>Notify when a download finishes.</small>
</div>
</div> -->
<div class="setting-item">
<input type="checkbox" data-key="customTopBar" id="customTopBar">
<div class="setting-info">
<span class="setting-title">Custom Top bar</span>
<small>Use this if the topbar is missing or you prefer a custom one.</small>
</div>
</div>
<div class="setting-item">
<input type="checkbox" data-key="autoCheckInfo" id="autoCheckInfo">
<div class="setting-info">
<span class="setting-title">Auto Fetch Info</span>
<small>Automatically fetch information for new downloads.</small>
</div>
</div>
</div>
<!-- Advanced Settings -->
<details class="settings-section">
<summary>Advanced Settings</summary>
<div class="setting-item">
<input type="checkbox" data-key="autoUpdate" id="autoUpdate">
<div class="setting-info">
<span class="setting-title">Auto Update</span>
<small>Enable automatic app updates.</small>
</div>
</div>
<!-- <div class="setting-item">
<input type="checkbox" data-key="outputTitleCheck" id="outputTitleCheck">
<div class="setting-info">
<span class="setting-title">Output title (special chars)</span>
<small>Check for unusual characters in output titles.</small>
</div>
</div> -->
<div class="setting-item">
<input type="checkbox" data-key="addMetadata" id="addMetadata">
<div class="setting-info">
<span class="setting-title">Add Metadata</span>
<small>Include metadata in downloaded files.</small>
</div>
</div>
<div class="setting-item">
<input type="checkbox" data-key="addThumbnail" id="addThumbnail">
<div class="setting-info">
<span class="setting-title">Add Thumbnail</span>
<small>Include thumbnails in downloaded files.</small>
</div>
</div>
<div class="setting-item" style="margin-left: 32px; margin-top: 20px;">
<div class="setting-info">
<span class="setting-title">Custom Codec</span>
<select data-key="customCodec">
<option value="h264">H264</option>
<option value="av1">AV1</option>
<option value="av01">AV01</option>
<option value="vp9.2">VP9.2</option>
<option value="vp9">VP9</option>
<option value="h265">H265</option>
<option value="theora">Theora</option>
</select>
<small>Choose your preferred video codec.</small>
</div>
</div>
</details>
<!-- Developer Settings -->
<details class="settings-section">
<summary>Developer Settings</summary>
<!-- <div class="setting-item">
<input type="checkbox" data-key="downloadSystem" id="downloadSystem">
<div class="setting-info">
<span class="setting-title">Download System</span>
<small>Enable/disable the internal download system.</small>
</div>
</div> -->
<!-- <div class="setting-item">
<input type="checkbox" data-key="logSystem" id="logSystem">
<div class="setting-info">
<span class="setting-title">Log System</span>
<small>Enable logging system.</small>
</div>
</div> -->
<div class="setting-item">
<input type="checkbox" data-key="verboseLogs" id="verboseLogs">
<div class="setting-info">
<span class="setting-title">Verbose Logs</span>
<small>Show verbose logs for debugging purposes.</small>
</div>
</div>
</details>
<div class="settings-footer">
<button id="open-json-btn">Open JSON Configuration</button>
</div>
</div>
</div>
<div class="container" id="container">
<header> <header>
<h1 id="title"> Freedom Loader </h1> <h1 id="title"> Freedom Loader </h1>
@@ -35,56 +214,63 @@
<input type="checkbox" name="audioOnly" value="1"> <input type="checkbox" name="audioOnly" value="1">
<div class="checkmark"></div> <div class="checkmark"></div>
</label> </label>
<!-- <input type="checkbox" name="audioOnly" value="1"> --> Audio Only (MP3)
Audio seulement (MP3)
</label> </label>
<!-- <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">Best Quality</option>
<option value="medium">Qualité moyenne</option> <option value="medium">Medium Quality</option>
<option value="worst">Qualité minimale</option> <option value="worst">Lowest Quality</option>
<option value="1080">1080p</option> <option value="1080">1080p</option>
<option value="720">720p</option> <option value="720">720p</option>
<option value="480">480p</option> <option value="480">480p</option>
</select> </select>
<button type="submit">Télécharger</button> <button type="submit" onclick="startProgress()">Download</button>
</form> </form>
<!--Il faut utiliser un formulaire, parce que on doit envoyer a un serveur
les données à traiter. Pour cela on définit une route vers /download, ca va
être l'espèce de "canal" attribué a ça. Ca va faire que si jamais on veut ensuite recup
les infos du form, il faut aller sur ce canal précis (/download). La méthode POST dit
que l'on envoie des données.-->
</div> </div>
<div class="download-path"> <div class="download-path">
Sera enregistré dans <span id="savePath"></span> Will be save on <span id="savePath"></span>
<button id="changePath">Modifier</button> <button id="changePath">Edit</button>
</div>
<div id="downloadProgressWrapper">
<div id="downloadProgress"></div>
</div>
<div style="display: flex; gap: 20px;">
<div id="downloadProgressText"></div>
<div id="downloadSpeedText"></div>
</div> </div>
<div id="downloadStatus"></div> <div id="downloadStatus"></div>
<div class="infos-container"> <div class="infos-container">
<div id="loaderBox" class="loader-box" style="display:none;">
<div></div>
<div></div>
<div></div>
<span>Fetching info...</span>
</div>
<div id="videoInfo" style="margin-top: 20px;"></div> <div id="videoInfo" style="margin-top: 20px;"></div>
</div> </div>
<footer class="theme-switcher"> <span id="version-badge" class="version-badge"></span>
<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>
<h2 id="reference"> Because Why Not ? </h2>
<!-- Scripts -->
<script src="script/custompath.js"></script> <script src="script/custompath.js"></script>
<script src="script/settingsPanel.js"></script>
<script src="script/appVersion.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/progressBar.js"></script>
<script src="script/customthemes.js"></script> <script src="script/customthemes.js"></script>
<script type="module" src="script/topbar.js"></script>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,6 @@
async function versionLabel(){
const appVersion = await window.electronAPI.getVersion();
document.getElementById("version-badge").textContent = `v${appVersion}`;
};
versionLabel();

View File

@@ -1,33 +0,0 @@
/*
This file is part of Freedom Loader.
Copyright (C) 2025 MasterAcnolo
Freedom Loader is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
Freedom Loader is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
function Chirac(){
const title = document.getElementById("title");
title.innerHTML = "Chirac Loader";
const subtitle = document.getElementById("subtitle");
subtitle.innerHTML = "J'aime les pommes";
subtitle.style.color= "black";
document.body.style.backgroundImage = "url('assets/images/goat2.webp')";
document.body.style.backgroundSize = "cover";
document.body.style.backgroundPosition = "center";
document.body.style.backgroundAttachment = "fixed";
console.log("Je serai le président de tous les français");
}

View File

@@ -1,71 +1,62 @@
/*
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 () => { window.addEventListener("DOMContentLoaded", async () => {
const savePathElem = document.getElementById("savePath"); const savePathElem = document.getElementById("savePath");
const form = document.getElementById("downloadForm");
// 1Essayer de charger depuis le localStorage // input caché = DERNIER chemin validé par le back
let savedPath = localStorage.getItem("customDownloadPath");
// 2Sinon demander le chemin par défaut à l'API Electron
if (!savedPath) {
savedPath = await window.electronAPI.getDefaultDownloadPath();
}
// 3 Afficher le chemin
if (savePathElem) {
savePathElem.textContent = savedPath;
}
// Créer l'input caché s'il n'existe pas déjà
let hidden = document.getElementById("savePathInput"); let hidden = document.getElementById("savePathInput");
if (!hidden) { if (!hidden) {
hidden = document.createElement("input"); hidden = document.createElement("input");
hidden.type = "hidden"; hidden.type = "hidden";
hidden.name = "savePath"; hidden.name = "savePath";
hidden.id = "savePathInput"; hidden.id = "savePathInput";
document.getElementById("downloadForm").appendChild(hidden); form.appendChild(hidden);
} }
hidden.value = savedPath;
// Gestion du bouton de modification async function applyPathFromBack(path) {
document.getElementById("changePath").addEventListener("click", async () => { savePathElem.textContent = path;
const selectedPath = await window.electronAPI.selectDownloadFolder(); hidden.value = path;
if (selectedPath) { localStorage.setItem("customDownloadPath", path); // UX only
// Met à jour l'affichage }
savePathElem.textContent = selectedPath;
hidden.value = selectedPath;
// Et le stocke en localStorage pour la prochaine fois async function loadInitialPath() {
localStorage.setItem("customDownloadPath", selectedPath); // On affiche ce que le user a vu la dernière fois (UX)
const cached = localStorage.getItem("customDownloadPath");
if (cached) {
savePathElem.textContent = cached;
}
// MAIS la source de vérité reste le back
try {
const validatedPath = await window.electronAPI.getValidatedDownloadPath(
cached
);
await applyPathFromBack(validatedPath);
} catch {
// fallback sûr
const defaultPath =
await window.electronAPI.getDefaultDownloadPath();
await applyPathFromBack(defaultPath);
}
}
await loadInitialPath();
document
.getElementById("changePath")
.addEventListener("click", async () => {
try {
const selectedPath =
await window.electronAPI.selectDownloadFolder();
if (!selectedPath) return; // annulé
// Validation back obligatoire
const validatedPath =
await window.electronAPI.getValidatedDownloadPath(selectedPath);
await applyPathFromBack(validatedPath);
} catch (err) {
alert("Dossier non autorisé.");
} }
}); });
}); });

View File

@@ -1,104 +1,59 @@
/*
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 = { const themes = {
default: "Default", dark: { label: "Sombre", subtitle: "Darkness is my ally" },
dark: "Sombre", light: { label: "Clair", subtitle: "Qui aime ce thème ?" },
light: "Clair", neon: { label: "Purple", subtitle: "How was your day ?"},
nf : "NF", nf: { label: "NF", subtitle: "You call it music, i call it my Therapist" },
chirac: "Chirac", songbird: { label: "Songbird", subtitle: "From Her to Eternity" },
fanatic: "Fanatic", drift: { label: "Drift", subtitle: "Si la route t'appelle, contre appel" },
cyberpunk: "Cyberpunk", fanatic: { label: "Fanatic", subtitle: "Always Fnatic !" },
spicy: "Spicy", chirac: { label: "Chirac", subtitle: "J'aime les pommes" },
vilbrequin: "Vilbrequin" spicy: { label: "Spicy", subtitle: "The Spiciest One" },
};
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. Weve got a city to burn.",
spicy: "The Spiciest One",
vilbrequin: "Rend l'argent"
}; };
const themeSelect = document.getElementById("themeSelect"); const themeSelect = document.getElementById("themeSelect");
// Remplir le select avec les options
function populateThemeSelect() { function populateThemeSelect() {
for (const [key, label] of Object.entries(themes)) { for (const [themeKey, themeInfo] of Object.entries(themes)) {
const option = document.createElement("option"); const option = document.createElement("option");
option.value = key; option.value = themeKey;
option.textContent = label; option.textContent = themeInfo.label;
themeSelect.appendChild(option); themeSelect.appendChild(option);
} }
} }
// Appliquer le thème sur le body et changer le subtitle function applyTheme(themeKey) {
function applyTheme(themeName) {
// Supprime les classes thème précédentes
document.body.classList.remove(...Object.keys(themes)); document.body.classList.remove(...Object.keys(themes));
// Ajoute la classe du thème actuel document.body.classList.add(themeKey);
document.body.classList.add(themeName);
// Modifier le subtitle const subtitleElement = document.getElementById("subtitle");
const subtitle = document.getElementById("subtitle"); if (subtitleElement && themes[themeKey]) {
if (subtitle && themeSubtitles[themeName]) { subtitleElement.textContent = themes[themeKey].subtitle;
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(themeKey) {
function saveTheme(themeName) { localStorage.setItem("selectedTheme", themeKey);
localStorage.setItem("selectedTheme", themeName);
} }
// Charger le thème au démarrage
function loadTheme() { function loadTheme() {
const savedTheme = localStorage.getItem("selectedTheme"); const savedTheme = localStorage.getItem("selectedTheme");
if (savedTheme && themes[savedTheme]) { if (savedTheme && themes[savedTheme]) {
applyTheme(savedTheme); applyTheme(savedTheme);
themeSelect.value = savedTheme; themeSelect.value = savedTheme;
} else { } else {
applyTheme("dark"); applyTheme("dark"); // thème par défaut
themeSelect.value = "dark"; themeSelect.value = "dark";
} }
} }
// Quand lutilisateur change le select // Quand l'utilisateur change le thème depuis le select
themeSelect.addEventListener("change", (e) => { themeSelect.addEventListener("change", (event) => {
const selected = e.target.value; const selectedTheme = event.target.value;
if (themes[selected]) { if (themes[selectedTheme]) {
applyTheme(selected); applyTheme(selectedTheme);
saveTheme(selected); saveTheme(selectedTheme);
} }
}); });

View File

@@ -1,71 +1,35 @@
/*
This file is part of Freedom Loader.
Copyright (C) 2025 MasterAcnolo
Freedom Loader is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
Freedom Loader is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// Récupération du formulaire de téléchargement
const form = document.getElementById("downloadForm"); const form = document.getElementById("downloadForm");
// Élément où on affichera létat du téléchargement (en cours, erreur, succès)
const statusDiv = document.getElementById("downloadStatus"); const statusDiv = document.getElementById("downloadStatus");
// Récupération du bouton de soumission pour pouvoir le désactiver pendant la requête
const button = form.querySelector("button"); const button = form.querySelector("button");
// É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 automatique de la page e.preventDefault();
button.disabled = true; // Empêche les clics multiples
statusDiv.textContent = "Download in progress...";
// Désactive le bouton pour éviter les clics multiples pendant la requête
button.disabled = true;
// Affiche un message dattente pour informer lutilisateur
statusDiv.textContent = "Téléchargement en cours...";
// 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 {
// 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 nest pas un succès (code HTTP 4xx ou 5xx)
if (!res.ok) { if (!res.ok) {
statusDiv.textContent = "❌ Erreur pendant le téléchargement."; statusDiv.textContent = res;
return; return;
} }
// 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 retourné par le serveur
statusDiv.textContent = text; statusDiv.textContent = text;
} catch { } catch {
// Gestion des erreurs réseau ou autres exceptions statusDiv.textContent = "❌ An Error has Occured.";
statusDiv.textContent = "❌ Une erreur sest produite.";
} finally { } finally {
// Réactive le bouton pour permettre dautres soumissions
button.disabled = false; button.disabled = false;
// Efface le message détat après 5 secondes pour garder linterface propre
setTimeout(() => { setTimeout(() => {
statusDiv.textContent = ""; statusDiv.textContent = "";
}, 5000); }, 5000);

View File

@@ -1,136 +1,191 @@
/* function formatDate(dateStr) {
This file is part of Freedom Loader. if (!dateStr || dateStr.length !== 8) return "Inconnue";
return `${dateStr.slice(6,8)}/${dateStr.slice(4,6)}/${dateStr.slice(0,4)}`;
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/>.
*/
// On attend que le DOM soit complètement chargé
document.addEventListener("DOMContentLoaded", () => {
// Récupération des éléments du DOM pour interaction
const urlInput = document.getElementById("UrlInput");
const infoDiv = document.getElementById("videoInfo");
// Stocke la dernière URL fetchée pour éviter les requêtes répétées inutiles
let lastFetchedUrl = "";
// Active les logs DEBUG pour le développement
const DEBUG = true;
// Écoute les changements dans l'input URL
urlInput.addEventListener("input", async () => {
const url = urlInput.value.trim();
if (DEBUG) console.log("[DEBUG] Input détecté :", url);
// Si l'URL est vide ou trop courte, on reset l'affichage
if (!url || url.length < 5) {
if (DEBUG) console.log("[DEBUG] URL vide ou trop courte, reset affichage.");
infoDiv.innerHTML = "";
infoDiv.classList.remove("visible");
lastFetchedUrl = "";
return;
} }
// Si l'URL est identique à la dernière requêtée, on ne refait pas la requête function formatSize(bytes) {
if (url === lastFetchedUrl) { return bytes ? (bytes / (1024*1024)).toFixed(2) + " Mo" : "Inconnue";
if (DEBUG) console.log("[DEBUG] Même URL que précédemment, pas de fetch.");
return;
} }
// Mémorise l'URL actuelle comme dernière URL traitée async function fetchVideoInfo(url) {
lastFetchedUrl = url;
try { try {
if (DEBUG) console.log("[DEBUG] Envoi requête POST vers /info avec URL:", url);
// Envoi de la requête POST pour récupérer les infos de la vidéo
const res = await fetch("/info", { 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" },
body: new URLSearchParams({ url }), body: new URLSearchParams({ url }),
}); });
if (DEBUG) console.log("[DEBUG] Réponse status:", res.status); if (!res.ok) return { error: `An Error occured when fetching info` };
// Si la réponse HTTP est une erreur, on affiche un message
if (!res.ok) {
console.error(`[ERROR] Réponse HTTP ${res.status}: ${res.statusText}`);
infoDiv.innerHTML = "❌ Impossible de récupérer les infos (erreur serveur).";
infoDiv.classList.remove("visible");
return;
}
// Parse la réponse JSON contenant les infos vidéo
const data = await res.json(); const data = await res.json();
if (DEBUG) console.log("[DEBUG] Données reçues:", data); if (!data) return { error: "Data is Missing" };
// Vérification simple que les données attendues sont présentes return data;
if (!data || !data.title || !data.thumbnail) { } catch (e) {
console.warn("[WARNING] Données incomplètes", data); return { error: "Network or JSON Issue" };
infoDiv.innerHTML = "❌ Données incomplètes reçues."; }
infoDiv.classList.remove("visible"); }
async function init() {
document.addEventListener("DOMContentLoaded", () => {
const urlInput = document.getElementById("UrlInput");
const infoDiv = document.getElementById("videoInfo");
const loaderBox = document.getElementById("loaderBox");
let lastFetchedUrl = "";
urlInput.addEventListener("input", async () => {
const configFeatures = await window.electronAPI.getFeatures();
if (!configFeatures.autoCheckInfo) return;
const url = urlInput.value.trim();
// Si champ vide -> reset total
if (!url || url.length < 2) {
infoDiv.innerHTML = "";
infoDiv.classList.remove("visible", "playlist-mode");
lastFetchedUrl = "";
return; return;
} }
// Formatage de la durée en minutes et secondes if (url === lastFetchedUrl) return;
const minutes = Math.floor(data.duration / 60); lastFetchedUrl = url;
const seconds = data.duration % 60;
const durationStr = `${minutes}m ${seconds.toString().padStart(2, "0")}s`;
// Formatage de la date d'upload au format JJ/MM/AAAA loaderBox.style.display = "flex";
const date = data.upload_date || ""; const data = await fetchVideoInfo(url);
const readableDate = date loaderBox.style.display = "none";
? `${date.slice(6, 8)}/${date.slice(4, 6)}/${date.slice(0, 4)}`
: "Inconnue";
// Conversion taille approximative en Mo // Gestion des erreurs
const sizeMB = data.filesize_approx if (data.error) {
? (data.filesize_approx / (1024 * 1024)).toFixed(2) + " Mo" infoDiv.innerHTML = `
: "Inconnue"; <div style="
padding:7px;
background:var(----infos-box-color);
">
<strong>${data.error}</strong>
</div>
`;
infoDiv.classList.add("visible");
infoDiv.classList.remove("playlist-mode");
loaderBox.style.display = "none";
return;
}
// Gestion des catégories, affichage par défaut si absentes // ---------- PLAYLIST ----------
const categories = data.categories if (data.type === "playlist") {
? data.categories.join(", ") infoDiv.classList.add("playlist-mode");
: "Non spécifiées"; infoDiv.innerHTML = `
<h3 style="color:var(--video-info-heading-color);"><strong>Playlist Detected: ${data.title}</strong></h3>
<h3 style="color:var(--video-info-heading-color);"><strong>Video Count: ${data.count}</strong></h3>
<p><strong>Channel :</strong> ${data.channel || "Unknown"}</p>
<div id="playlistVideos"></div>
`;
const listDiv = document.getElementById("playlistVideos");
data.videos.forEach(v => {
const durationStr = v.duration
? `${Math.floor(v.duration / 60)}m ${(v.duration % 60).toString().padStart(2,"0")}s` : "Inconnue";
const videoUrl = v.id ? `https://www.youtube.com/watch?v=${v.id}` : v.url;
listDiv.innerHTML += `
<div style="margin-bottom:12px;">
<img src="${v.thumbnail}" width="160" alt="Thumbnail">
<p><strong>${v.title}</strong></p>
<p>Duration : ${durationStr}</p>
<p><a href="${videoUrl}" target="_blank">URL</a>
<button class="copy-btn" data-url="${videoUrl}">📋</button>
</p>
</div>
`;
});
// Gestion du bouton copier
listDiv.addEventListener("click", (event) => {
if (event.target.classList.contains("copy-btn")) {
const btn = event.target;
if (btn.disabled) return;
btn.disabled = true;
const url = btn.dataset.url;
navigator.clipboard.writeText(url)
.then(() => {
const original = btn.textContent;
btn.style.opacity = 0;
btn.style.transform = "scale(0.7)";
setTimeout(() => {
btn.textContent = "✅";
btn.style.opacity = 1;
btn.style.transform = "scale(1)";
setTimeout(() => {
btn.style.opacity = 0;
btn.style.transform = "scale(0.7)";
setTimeout(() => {
btn.textContent = original;
btn.style.opacity = 1;
btn.style.transform = "scale(1)";
btn.disabled = false;
}, 300);
}, 1000);
}, 300);
})
.catch(() => {
const original = btn.textContent;
btn.textContent = "❌";
setTimeout(() => {
btn.textContent = original;
btn.disabled = false;
}, 1500);
});
}
});
infoDiv.classList.add("visible");
return;
}
infoDiv.classList.remove("playlist-mode");
// ---------- VIDEO NORMALE ----------
const durationStr = data.duration
? `${Math.floor(data.duration/60)}m ${(data.duration%60).toString().padStart(2,"0")}s`
: "Unknown";
const sizeStr = formatSize(data.filesize_approx);
const readableDate = formatDate(data.upload_date);
const categories = data.categories?.join(", ") || "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">
<ul> <ul>
<li><strong>Durée :</strong> ${durationStr}</li> <li><strong>Duration :</strong> ${durationStr}</li>
<li><strong>Uploader :</strong> ${data.uploader || "Inconnu"}</li> <li><strong>Uploader :</strong> ${data.uploader || "Inconnu"}</li>
<li><strong>Date dupload :</strong> ${readableDate}</li> <li><strong>Upload Date :</strong> ${readableDate}</li>
<li><strong>Vues :</strong> ${data.view_count?.toLocaleString() || "?"}</li> <li><strong>Views :</strong> ${data.view_count?.toLocaleString() || "?"}</li>
<li><strong>Likes :</strong> ${data.like_count?.toLocaleString() || "?"}</li> <li><strong>Likes :</strong> ${data.like_count?.toLocaleString() || "?"}</li>
<li><strong>URL :</strong> <a href="${data.webpage_url}" target="_blank" rel="noopener noreferrer">${data.webpage_url}</a></li> <li><strong>URL :</strong> <a href="${data.webpage_url}" target="_blank">${data.webpage_url}</a></li>
<li><strong>Channel :</strong> <a href="${data.channel_url}" target="_blank" rel="noopener noreferrer">${data.channel_url}</a></li> <li><strong>Channel :</strong> <a href="${data.channel_url}" target="_blank">${data.channel_url}</a></li>
<li><strong>Taille estimée :</strong> ${sizeMB}</li> <li><strong>Estimed Size :</strong> ${sizeStr}</li>
<li><strong>Catégories :</strong> ${categories}</li> <li><strong>Category :</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."); })
} catch (e) {
// En cas d'erreur réseau ou JSON, on affiche un message d'erreur };
console.error("[CRITICAL] Erreur lors de la récupération:", e);
infoDiv.innerHTML = "❌ Erreur réseau ou JSON."; init();
infoDiv.classList.remove("visible");
}
});
});

View File

@@ -0,0 +1,63 @@
const progressWrapper = document.getElementById("downloadProgressWrapper");
const progressBar = document.getElementById("downloadProgress");
const progressBarText = document.getElementById("downloadProgressText")
const speedElement = document.getElementById("downloadSpeedText");
const speedEvt = new EventSource("/download/speed");
function startProgress() {
progressWrapper.style.display = "block";
progressBar.style.width = "0%";
progressBarText.style.display = "block";
progressBarText.innerHTML = "0%";
speedElement.style.display = "block";
speedElement.innerHTML = "0 Mib/s";
}
function updateProgress(percent) {
progressBar.style.width = `${percent}%`;
progressBarText.innerHTML = `${percent}%`;
}
function resetProgress() {
progressBar.style.width = "0%";
progressBarText.textContent = "";
progressBarText.style.display = "none";
progressWrapper.style.display = "none";
speedElement.textContent = "0 Mib/s";
speedElement.style.display = "none";
}
// Connexion SSE
const evtSource = new EventSource("/download/progress");
evtSource.onmessage = e => {
if (e.data === "reset") {
startProgress();
window.electronAPI.setProgress(0);
return;
}
if (e.data === "done") {
resetProgress();
window.electronAPI.setProgress(-1);
return;
}
const percent = parseFloat(e.data);
if (!isNaN(percent)) {
updateProgress(percent);
window.electronAPI.setProgress(percent); // update barre des tâches
if (percent >= 100) setTimeout(() => {
resetProgress();
window.electronAPI.setProgress(-1); // retire la barre
}, 500);
}
};
speedEvt.onmessage = e => {
speedElement.style.display = "block";
speedElement.textContent = e.data; // ex: "5.2MiB/s"
};

View File

@@ -0,0 +1,50 @@
const settingsPanel = document.getElementById("settings-panel");
const settingsModal = document.querySelector(".settings-modal");
const settingsButton = document.getElementById("settings-button");
let isOpen = false;
settingsButton.addEventListener("click", () => {
const isOpen = settingsPanel.style.display === "block";
settingsPanel.style.display = isOpen ? "none" : "block";
});
function closePanel() {
isOpen = false;
settingsPanel.style.display = "none";
}
// charge les features
async function loadSettings() {
const features = await window.electronAPI.getFeatures();
document.querySelectorAll(".settings-section input, .settings-section select").forEach(el => {
const key = el.dataset.key;
if (features[key] !== undefined) {
if (el.type === "checkbox") el.checked = features[key];
else if (el.tagName === "SELECT") el.value = features[key];
}
el.addEventListener("change", () => {
let value = el.type === "checkbox" ? el.checked : el.value;
window.electronAPI.setFeature(key, value);
});
});
}
// ouvrir le JSON
document.getElementById("open-json-btn").addEventListener("click", () => {
window.topbarAPI.openConfig(); // ton IPC existant
});
/* clic sur l'overlay = fermer */
settingsPanel.addEventListener("click", closePanel);
/* clic dans la modal = stop propagation */
settingsModal.addEventListener("click", (e) => {
e.stopPropagation();
});
loadSettings();

34
public/script/topbar.js Normal file
View File

@@ -0,0 +1,34 @@
// Ajout des listeners pour la topbar
function setupTopbarListeners() {
document.addEventListener('DOMContentLoaded', () => {
const { topbarAPI } = window;
if (!topbarAPI) return;
const minBtn = document.getElementById('minimize-btn');
const maxBtn = document.getElementById('maximize-btn');
const closeBtn = document.getElementById('close-btn');
const devtoolsBtn = document.getElementById('devtools-btn');
const logsBtn = document.getElementById('logs-btn');
const websiteBtn = document.getElementById('website-btn');
const wikiBtn = document.getElementById('wiki-btn');
const configBtn = document.getElementById('config-btn');
if (minBtn) minBtn.onclick = () => topbarAPI.minimize();
if (maxBtn) maxBtn.onclick = () => topbarAPI.maximize();
if (closeBtn) closeBtn.onclick = () => topbarAPI.close();
if (devtoolsBtn) devtoolsBtn.onclick = () => topbarAPI.openDevTools();
if (logsBtn) logsBtn.onclick = () => topbarAPI.openLogs();
if (websiteBtn) websiteBtn.onclick = () => topbarAPI.openWebsite();
if (wikiBtn) wikiBtn.onclick = () => topbarAPI.openWiki();
if (configBtn) configBtn.onclick = () => topbarAPI.openConfig();
});
}
setupTopbarListeners(); // IF it put it the if check. It don't work. Why ?
const features = await window.electronAPI.getFeatures();
if(!features.customTopBar){
document.getElementById("topbar").style.display = "none";
document.getElementById("container").style.marginTop = "0";
document.getElementById("theme-switcher").style.top = "30px";
}

View File

@@ -0,0 +1,34 @@
.loader-box {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 20px;
background: var(--form-bg-color);
border: 2px solid var(--mp3-text-color);
border-radius: 8px;
color: var(--mp3-text-color);
font-weight: bold;
animation: pulse 1.2s infinite;
}
.loader-box div {
width: 10px;
height: 10px;
background: var(--form-button-bg-color);
border-radius: 3px;
animation: bounce 1.2s infinite;
}
.loader-box div:nth-child(1) { animation-delay: 0s; }
.loader-box div:nth-child(2) { animation-delay: 0.2s; }
.loader-box div:nth-child(3) { animation-delay: 0.4s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 5px var(--form-button-bg-color); }
50% { box-shadow: 0 0 20px var(--form-button-bg-color); }
}

View File

@@ -0,0 +1,41 @@
/* Wrapper de la barre de téléchargement */
#downloadProgressWrapper {
width: 100%;
max-width: 400px;
height: 12px;
background-color: #444;
border-radius: 6px;
overflow: hidden;
margin: 10px auto;
display: none;
box-shadow: inset 0 1px 3px rgba(0,0,0,0.5);
}
/* Barre de progression */
#downloadProgress {
height: 100%;
width: 0%;
background-color: var(--video-info-link-color, #4caf50);
border-radius: 6px 0 0 6px;
transition: width 0.2s ease;
}
#downloadProgressText, #downloadSpeedText {
text-align: center;
font-size: 0.8rem;
color: #fff;
content: "";
}
/* Responsive pour mobile */
@media (max-width: 480px) {
#downloadProgressWrapper {
max-width: 90%;
height: 10px;
}
#downloadProgressText, #downloadSpeedText {
font-size: 0.7rem;
}
}

View File

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

View File

@@ -3,10 +3,23 @@
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
/* height: 10000px; */
padding: 1em; padding: 1em;
z-index: 1000; z-index: 1000;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
#downloadStatus {
margin-top: 1em;
min-height: 1.5em;
font-weight: bold;
text-align: center;
color: var(--download-status-color);
}
label {
display: flex;
align-items: center;
font-size: 0.9em;
}

View File

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

View File

@@ -0,0 +1,234 @@
/* SETTINGS BUTTON */
.settings-button {
display: flex;
align-items: center;
justify-content: center;
position: fixed;
top: 60px;
right: 20px;
opacity: 0.8;
background-color: var(--settings-button-bg-color);
border-radius: 14px;
padding: 8px;
border: none;
box-shadow: 0 6px 16px rgba(0,0,0,0.25);
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.settings-button svg {
width: 40px;
height: 40px;
stroke: var(--settings-button-text-color);
}
.settings-button:hover {
transform: scale(1.1);
cursor: pointer;
box-shadow: 0 10px 24px rgba(0,0,0,0.35);
}
.settings-button:active {
transform: scale(0.95);
}
/* SETTINGS PANEL */
.settings-panel {
position: fixed;
top: 60px;
right: 40px;
width: 40vw;
max-width: 320px;
max-height: 80vh;
background-color: var(--settings-bg-primary-color);
border-radius: 12px;
padding: 20px;
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
display: none;
overflow-y: auto;
z-index: 9999;
animation: slideIn 0.2s ease-out;
color: var(--settings-text-color);
}
.settings-panel::-webkit-scrollbar {
width: 0px;
background: transparent;
}
.settings-modal {
width: 100%;
min-height: auto;
max-height: none;
padding: 0;
background: none;
box-shadow: none;
border-radius: none;
}
/* TITLE */
.settings-title {
margin-bottom: 20px;
}
.settings-title h2 {
margin: 0;
font-size: 24px;
}
.settings-title p {
font-size: 14px;
color: #ccc;
margin-top: 4px;
}
/* SECTION */
.settings-section {
display: flex;
flex-direction: column;
margin-bottom: 20px;
padding: 10px 15px;
background: var(--settings-bg-secondary-color);
border-radius: 12px;
gap: 6px;
}
.settings-section h3 {
margin: 0 0 10px 0;
font-size: 18px;
}
/* SETTING ITEM */
.setting-item {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 8px;
margin-top: 8px;
}
.setting-item input[type="checkbox"] {
margin: 0;
flex-shrink: 0;
width: 20px;
height: 20px;
}
.setting-info {
display: flex;
flex-direction: column;
}
.setting-title {
font-size: 14px;
font-weight: 500;
}
.setting-info small {
font-size: 12px;
color: var(--settings-subtitle-color);
margin-top: 2px;
font-style: italic;
}
#theme .settings-title{
display: flex;
}
.setting-item select {
width: 70%;
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;
}
.setting-item select:hover,
.setting-item select:focus {
background: #474747;
color: #fff;
outline: none;
}
/* SELECT */
.settings-section select {
margin-left: 10px;
}
/* DETAILS / DROPDOWN */
details {
margin-bottom: 20px;
padding: 10px 15px;
background: #2a2a2a;
border-radius: 12px;
}
details[open] {
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
}
details summary {
cursor: pointer;
}
/* FOOTER */
.settings-footer {
margin-top: 20px;
display: flex;
flex-direction: column;
gap: 10px;
}
#open-json-btn {
padding: 6px 12px;
border-radius: 8px;
border: none;
cursor: pointer;
font-weight: 600;
background-color: var(--form-button-bg-color);
color: #fff;
}
#experimental-args {
width: 100%;
min-height: 80px;
background-color: #1e1e1e;
border-radius: 8px;
padding: 8px;
color: #fff;
border: 1px solid #333;
resize: vertical;
}
/* CLOSE BTN */
.close-btn {
position: absolute;
top: 20px;
right: 20px;
background: none;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
opacity: 0.6;
}
.close-btn:hover {
opacity: 1;
}
/* ANIMATION */
@keyframes popIn {
from { transform: scale(0.95); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
@keyframes slideIn {
from { transform: translateX(20px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}

View File

@@ -0,0 +1,116 @@
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
position: fixed;
top: 0;
z-index: 1100;
height: 48px;
width: 100%;
background: #1f1f1f98;
color: #eaeaea;
-webkit-app-region: drag;
user-select: none;
}
/* LEFT - custom buttons */
.custom-controls {
display: flex;
align-items: center;
gap: 6px;
padding-left: 12px;
}
/* RIGHT - window buttons */
.window-controls {
display: flex;
align-items: center;
}
/* GLOBAL BUTTON RESET */
.topbar button {
border: none;
background: transparent;
color: inherit;
cursor: pointer;
-webkit-app-region: no-drag;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.15s ease, transform 0.1s ease;
}
/* Custom buttons (Logs / Wiki / etc.) */
.custom-controls button {
height: 32px;
padding: 0 14px;
font-size: 15px;
border-radius: 8px;
}
.custom-controls button:hover {
background: rgba(255, 255, 255, 0.12);
}
/* Window buttons (Windows style) */
.window-controls button {
width: 46px;
height: 48px;
font-size: 12px;
}
.window-controls button:hover {
background: rgba(255, 255, 255, 0.1);
}
.window-controls img {
width: 27px;
height: 27px;
pointer-events: none;
filter: invert(100%);
}
#close-btn:hover {
background: #e81123;
color: #fff;
}
/* Active effect */
.topbar button:active {
background: rgba(255, 255, 255, 0.18);
transform: scale(0.9);
}
#close-btn:active {
background: #c50f1f;
transform: scale(0.9);
}
.version-badge {
position: fixed;
right: 18px;
bottom: 12px;
z-index: 2000;
font-size: 0.85em;
color: #b0b0b0;
background: none;
font-weight: 500;
border-radius: 6px;
padding: 2px 8px;
opacity: 0.7;
pointer-events: none;
box-shadow: none;
user-select: none;
}

View File

@@ -21,7 +21,6 @@
#videoInfo.visible { #videoInfo.visible {
opacity: 1; opacity: 1;
max-height: 1500px; max-height: 1500px;
overflow: visible;
} }
#videoInfo h3 { #videoInfo h3 {
@@ -68,6 +67,81 @@
text-decoration: underline; text-decoration: underline;
} }
/* --- Playlist Mode --- */
#videoInfo.playlist-mode {
max-width: 900px;
}
/* Style du bloc "Playlist détectée" */
#videoInfo.playlist-mode > p {
text-align: center;
font-size: 1.1rem;
font-weight: 600;
}
/* Layout liste des vidéos */
#playlistVideos {
margin-top: 1em;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 18px;
}
/* Un item de playlist */
#playlistVideos > div {
background: var(--playlist-background-color);
border-radius: 10px;
padding: 10px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
text-align: center;
transition: transform 0.2s ease;
}
#playlistVideos > div p {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
#videoInfo.playlist-mode {
max-width: 95%;
}
#playlistVideos > div:hover {
transform: scale(1.05);
}
#playlistVideos img {
width: 100%;
border-radius: 8px;
margin-bottom: 8px;
}
.copy-btn {
all: unset;
background: transparent;
border: none;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 4px;
vertical-align: middle;
transition: transform 0.1s ease, color 0.1s ease, opacity 0.15s ease;
font-size: 1em;
}
#playlistVideos > div p a{
text-decoration: none;
color: var(--video-info-link-color);
}
#playlistVideos > div p a:hover{
text-decoration: underline;
}
/* Responsive */ /* Responsive */
@media (max-width: 480px) { @media (max-width: 480px) {
#videoInfo { #videoInfo {

View File

@@ -1,13 +1,17 @@
/* Layout */
@import url("layout/form.css"); @import url("layout/form.css");
@import url("layout/settingsPanel.css");
@import url("layout/container.css"); @import url("layout/container.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");
/* Components */
@import url("components/checkbox.css"); @import url("components/checkbox.css");
@import url("components/themebutton.css"); @import url("components/loader.css");
/* @import url("variables.css"); */
@import url("theme/themeimport.css"); @import url("theme/themeimport.css");
@import url("components/editpathbutton.css"); @import url("components/editpathbutton.css");
@import url("components/progressBar.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');
@@ -24,7 +28,7 @@ html,body{
margin: 0; margin: 0;
padding: 0; padding: 0;
scroll-behavior: smooth; scroll-behavior: smooth;
/* outline: 2px solid red; */ /*<-- Utile pour le début ça */ /*outline: 2px solid red; /*<-- Utile pour le debug ça */
font-family: "Poppins", sans-serif; font-family: "Poppins", sans-serif;
font-weight: 500; font-weight: 500;
font-style: normal; font-style: normal;
@@ -33,10 +37,6 @@ html,body{
body { body {
margin: 0; margin: 0;
background-color: var(--background-color); background-color: var(--background-color);
/* background-image: linear-gradient(to left top, #838383, #a1a1a1, #bfbfbf, #dfdfdf, #ffffff);
background-size: cover;
background-position: center;
background-attachment: fixed; */
width: 100%; width: 100%;
height: 100%; height: 100%;
color: var(--default-text-color); color: var(--default-text-color);
@@ -47,7 +47,57 @@ body {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
}
/* Because why not */
#reference{
position: fixed;
pointer-events: none;
bottom:0vh;
left: 10px;
font-size: 1em;
color: #b0b0b02f;
}
/* Scrollbar globale pour Chromium/Electron */
body::-webkit-scrollbar {
width: 10px;
height: 10px;
}
body::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.03);
border-radius: 5px;
}
body::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.2);
border-radius: 5px;
border: 2px solid transparent;
background-clip: padding-box;
transition: background-color 0.2s ease, transform 0.2s ease;
}
body::-webkit-scrollbar-thumb:hover {
background-color: rgba(255, 255, 255, 0.35);
transform: scaleX(1.05);
}
/* Thème clair */
body.light::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
}
body.light::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.25);
}
body.light::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, 0.45);
}
.container{
margin-top: 50px;
} }
header p{ header p{

View File

@@ -1,16 +1,15 @@
body.chirac { body.chirac {
/* Background image Chirac */
background-image: url('../../assets/images/Chirac.png'); background-image: url('../../assets/images/Chirac.png');
/* background-position: right; */
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: cover; background-size: cover;
background-attachment: fixed; background-attachment: fixed;
/* Couleurs drapeau français */
--background-color: rgba(255, 255, 255, 0.85); /* blanc légèrement transparent pour lisibilité */ --background-color: rgba(255, 255, 255, 0.85);
--default-text-color: #002395; /* bleu foncé */ --default-text-color: #002395;
--subtitle-color: #EF4135; /* rouge vif */ --subtitle-color: #EF4135;
--mp3-text-color: #002395; /* bleu */ --mp3-text-color: #002395;
/* Formulaire */ /* Formulaire */
--form-bg-color: rgba(255, 255, 255, 0.9); --form-bg-color: rgba(255, 255, 255, 0.9);
@@ -19,9 +18,9 @@ body.chirac {
--form-input-border-focus-color: #EF4135; --form-input-border-focus-color: #EF4135;
--form-input-text-color: #002395; --form-input-text-color: #002395;
--form-input-placeholder-color: #999999; --form-input-placeholder-color: #999999;
--form-button-bg-color: #002395; /* bleu */ --form-button-bg-color: #002395;
--form-button-text-color: #ffffff; --form-button-text-color: #ffffff;
--form-button-bg-hover-color: #EF4135; /* rouge */ --form-button-bg-hover-color: #EF4135;
/* Checkbox */ /* Checkbox */
--checkbox-bg-default: #cccccc; --checkbox-bg-default: #cccccc;
@@ -34,21 +33,27 @@ body.chirac {
/* Video Info Box */ /* Video Info Box */
--infos-box-color: rgba(255, 255, 255, 0.9); --infos-box-color: rgba(255, 255, 255, 0.9);
--video-info-text-color: #002395; --video-info-text-color: #000000;
--video-info-heading-color: #EF4135; --video-info-heading-color: #EF4135;
--video-info-list-color: #444444; --video-info-list-color: #444444;
--video-info-list-strong-color: #000000; --video-info-list-strong-color: #000000;
--video-info-link-color: #002395; --video-info-link-color: #002395;
--video-info-link-hover-color: #EF4135; --video-info-link-hover-color: #EF4135;
/* animation d'apparition */ /* Playlist Color */
opacity: 0; --playlist-background-color: #d1d1d1ca;
transition: opacity 0.8s ease, background-color 0.8s ease;
/* Settings */
--settings-button-bg-color: #00196a;
--settings-button-text-color: #dcdcdc;
--settings-bg-primary-color: #0b0528;
--settings-bg-secondary-color: #1c1055;
--settings-text-color: #cecece;
--settings-subtitle-color: #adadad;
} }
body.chirac.theme-active {
opacity: 1;
}
/* /*

View File

@@ -1,45 +0,0 @@
body.cyberpunk {
/* Couleurs générales */
--background-color: #0d0d0d; /* noir profond */
--default-text-color: #e0e0e0; /* texte gris clair */
--subtitle-color: #ffea00; /* turquoise néon pour les sous-titres */
--mp3-text-color: #ffea00; /* jaune vif pour la checkbox */
/* Formulaire */
--form-bg-color: #141414; /* fond formulaire sombre */
--form-input-bg-color: #1a1a1a; /* inputs sombres */
--form-input-border-color: #333333; /* bordure sobre */
--form-input-border-focus-color: #ffea00; /* focus jaune néon */
--form-input-text-color: #e0e0e0; /* texte clair */
--form-input-placeholder-color: #777777; /* placeholder plus doux */
--form-button-bg-color: #ffea00; /* bouton jaune */
--form-button-text-color: #0d0d0d; /* texte bouton noir */
--form-button-bg-hover-color: #d4c000; /* hover jaune plus foncé */
/* Checkbox */
--checkbox-bg-default: #333333; /* fond checkbox sobre */
--checkbox-bg-checked: #ffea00; /* coché en jaune vif */
--checkbox-checkmark-border-color: #0d0d0d; /* checkmark noir */
--checkbox-pulse-color: rgba(255, 234, 0, 0.5); /* pulse néon jaune */
/* Download status */
--download-status-color: #fffbd1; /* turquoise néon */
/* Video Info Box */
--infos-box-color: #1a1a1a; /* fond infos */
--video-info-text-color: #e0e0e0; /* texte clair */
--video-info-heading-color: #ffea00; /* titres turquoise néon */
--video-info-list-color: #cccccc; /* texte liste gris clair */
--video-info-list-strong-color: #ffffff; /* texte important blanc */
--video-info-link-color: #ffea00; /* liens jaune néon */
--video-info-link-hover-color: #d4c000; /* liens hover plus foncé */
}
body.cyberpunk {
background-image: url('../../assets/images/Cyberpunk.webp');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
}

View File

@@ -33,4 +33,17 @@ body.dark {
--video-info-list-strong-color: #ffffff; --video-info-list-strong-color: #ffffff;
--video-info-link-color: #2196f3; --video-info-link-color: #2196f3;
--video-info-link-hover-color: #1976d2; --video-info-link-hover-color: #1976d2;
/* Playlist Color */
--playlist-background-color: #303030;
/* Settings */
--settings-button-bg-color: #1e1e1e;
--settings-button-text-color: var(--default-text-color);
--settings-bg-primary-color: #1e1e1e;
--settings-bg-secondary-color: #2a2a2a;
--settings-text-color: var(--default-text-color);
--settings-subtitle-color: #aaa;
} }

View File

@@ -1,40 +0,0 @@
:root {
/* Couleurs générales */
--background-color: #001224; /* body background bleu nuit */
--default-text-color: #eee; /* texte clair principal */
--subtitle-color: #007bff; /* sous-titres, header p */
--mp3-text-color: #000000; /* couleur texte checkbox inline */
/* Formulaire */
--form-bg-color: #ffffff; /* fond du formulaire */
--form-input-bg-color: #f9f9f9; /* fond inputs */
--form-input-border-color: #cccccc; /* bordure inputs */
--form-input-border-focus-color: #0056b3; /* bordure focus */
--form-input-text-color: #333333; /* texte inputs */
--form-input-placeholder-color: #aaaaaa; /* placeholder */
--form-button-bg-color: #007bff; /* bouton bg */
--form-button-text-color: #ffffff; /* texte bouton */
--form-button-bg-hover-color: #0056b3; /* bouton hover */
/* Checkbox */
--checkbox-bg-default: #cccccc;
--checkbox-bg-checked: #3B82F6;
--checkbox-checkmark-border-color: #E0E0E2;
--checkbox-pulse-color: rgba(59, 130, 246, 0.5);
/* Download status */
--download-status-color: #007bff;
/* Video Info Box */
--infos-box-color: #f0f0f0;
--video-info-text-color: #222222;
--video-info-heading-color: #007bff;
--video-info-list-color: #444444;
--video-info-list-strong-color: #000000;
--video-info-link-color: #007bff;
--video-info-link-hover-color: #0056b3;
}
body.default .download-path #savePath {
color: #ffffff;
}

View File

@@ -0,0 +1,61 @@
body.drift {
background-image: url('../../assets/images/Drift.jpg');
background-size: cover;
background-position: center;
background-attachment: fixed;
/* Palette principale */
--background-color: #0f1218;
--default-text-color: #3d4b61;
--subtitle-color: #1a1a1a;
--mp3-text-color: #c0d4ff;
/* Formulaire */
--form-bg-color: #1e222a;
--form-input-bg-color: #13171d;
--form-input-border-color: #3b4b6d;
--form-input-border-focus-color: #6ea8ff;
--form-input-text-color: #f0f4ff;
--form-input-placeholder-color: #7a8fae;
--form-button-bg-color: #2a2979;
--form-button-text-color: #ffffff;
--form-button-bg-hover-color: #353393;
/* Checkbox */
--checkbox-bg-default: #2a2e38;
--checkbox-bg-checked: #2a2979;
--checkbox-checkmark-border-color: #ffffff;
--checkbox-pulse-color: rgba(76,108,255,0.3);
/* Download status */
--download-status-color: #ff8e8e;
/* Video Info Box */
--infos-box-color: #1e222ad0;
--video-info-text-color: #e8e8f0;
--video-info-heading-color: #9ab6ff;
--video-info-list-color: #c0d4ff;
--video-info-list-strong-color: #ffffff;
--video-info-link-color: #4c6cff;
--video-info-link-hover-color: #6c8dff;
/* Playlist Color */
--playlist-background-color: #424b5cc2;
/* Settings */
--settings-button-bg-color: #1e222ace;
--settings-button-text-color: #b3b3b4;
--settings-bg-primary-color: #1e222a;
--settings-bg-secondary-color: #272d38;
--settings-text-color: #bababa;
--settings-subtitle-color: #cacaca;
}
body.drift .download-path{
color: #ededed;
}

View File

@@ -1,36 +1,49 @@
body.fanatic{ body.fanatic{
/* Couleurs générales */ /* Couleurs générales */
--background-color: #121212; /* fond bleu nuit/noir */ --background-color: #121212;
--default-text-color: #eee; /* texte clair principal */ --default-text-color: #eee;
--subtitle-color: #FF5900; /* sous-titres, accent orange vif */ --subtitle-color: #FF5900;
--mp3-text-color: #ffffff; /* texte checkbox inline */ --mp3-text-color: #ffffff;
/* Formulaire */ /* Formulaire */
--form-bg-color: #1e1e1e; /* fond formulaire sombre */ --form-bg-color: #1e1e1e;
--form-input-bg-color: #2b2b2b; /* fond inputs sombres */ --form-input-bg-color: #2b2b2b;
--form-input-border-color: #333333; /* bordure inputs */ --form-input-border-color: #333333;
--form-input-border-focus-color: #FF5900; /* bordure focus orange */ --form-input-border-focus-color: #FF5900;
--form-input-text-color: #eeeeee; /* texte inputs clair */ --form-input-text-color: #eeeeee;
--form-input-placeholder-color: #666666; /* placeholder gris foncé */ --form-input-placeholder-color: #666666;
--form-button-bg-color: #FF5900; /* bouton orange */ --form-button-bg-color: #FF5900;
--form-button-text-color: #121212; /* texte bouton sombre */ --form-button-text-color: #121212;
--form-button-bg-hover-color: #e65500; /* bouton hover orange foncé */ --form-button-bg-hover-color: #e65500;
/* Checkbox */ /* Checkbox */
--checkbox-bg-default: #555555; /* gris moyen */ --checkbox-bg-default: #555555;
--checkbox-bg-checked: #ff6600; /* orange vif */ --checkbox-bg-checked: #ff6600;
--checkbox-checkmark-border-color: #eee; /* blanc cassé */ --checkbox-checkmark-border-color: #eee;
--checkbox-pulse-color: rgba(255, 102, 0, 0.5); /* pulse orange translucide */ --checkbox-pulse-color: rgba(255, 102, 0, 0.5);
/* Download status */ /* Download status */
--download-status-color: #FF5900; /* orange */ --download-status-color: #FF5900;
/* Video Info Box */ /* Video Info Box */
--infos-box-color: #222222; /* fond infos sombre */ --infos-box-color: #222222;
--video-info-text-color: #eee; /* texte clair */ --video-info-text-color: #eee;
--video-info-heading-color: #FF5900; /* titres orange */ --video-info-heading-color: #FF5900;
--video-info-list-color: #ccc; /* texte liste gris clair */ --video-info-list-color: #ccc;
--video-info-list-strong-color: #fff; /* texte important blanc */ --video-info-list-strong-color: #fff;
--video-info-link-color: #FF5900; /* liens orange */ --video-info-link-color: #FF5900;
--video-info-link-hover-color: #e65500; /* liens hover orange foncé */ --video-info-link-hover-color: #e65500;
/* Playlist Color */
--playlist-background-color: #2b2b2b;
/* Settings */
--settings-button-bg-color: #1e1e1e;
--settings-button-text-color: #dcdcdc;
--settings-bg-primary-color: #1e1e1e;
--settings-bg-secondary-color: #2d2d2d;
--settings-text-color: #cecece;
--settings-subtitle-color: #adadad;
} }

View File

@@ -1,20 +1,20 @@
body.light { body.light {
/* Couleurs générales */ /* Couleurs générales */
--background-color: #dadada; /* fond clair */ --background-color: #dadada;
--default-text-color: #222222; /* texte sombre */ --default-text-color: #222222;
--subtitle-color: #007bff; /* bleu accent */ --subtitle-color: #007bff;
--mp3-text-color: #000000; /* texte checkbox */ --mp3-text-color: #000000;
/* Formulaire */ /* Formulaire */
--form-bg-color: #ffffff; /* fond formulaire blanc */ --form-bg-color: #ffffff;
--form-input-bg-color: #fafafa; /* fond inputs */ --form-input-bg-color: #fafafa;
--form-input-border-color: #cccccc; /* bordure inputs */ --form-input-border-color: #cccccc;
--form-input-border-focus-color: #007bff; /* focus bleu */ --form-input-border-focus-color: #007bff;
--form-input-text-color: #333333; /* texte inputs */ --form-input-text-color: #333333;
--form-input-placeholder-color: #888888; /* placeholder */ --form-input-placeholder-color: #888888;
--form-button-bg-color: #007bff; /* bouton bleu */ --form-button-bg-color: #007bff;
--form-button-text-color: #ffffff; /* texte bouton */ --form-button-text-color: #ffffff;
--form-button-bg-hover-color: #0056b3; /* hover bleu foncé */ --form-button-bg-hover-color: #0056b3;
/* Checkbox */ /* Checkbox */
--checkbox-bg-default: #cccccc; --checkbox-bg-default: #cccccc;
@@ -33,4 +33,16 @@ body.light {
--video-info-list-strong-color: #000000; --video-info-list-strong-color: #000000;
--video-info-link-color: #007bff; --video-info-link-color: #007bff;
--video-info-link-hover-color: #0056b3; --video-info-link-hover-color: #0056b3;
/* Playlist Color */
--playlist-background-color: #c5c5c5b2;
/* Settings */
--settings-button-bg-color: #e4e4e4;
--settings-button-text-color: var(--default-text-color);
--settings-bg-primary-color: #cbcbcb;
--settings-bg-secondary-color: #cbcbcb;
--settings-text-color: var(--default-text-color);
--settings-subtitle-color: #656565;
} }

View File

@@ -0,0 +1,59 @@
body.neon {
background: (url("../../assets/images/drift.jpg"));
background-image: linear-gradient(135deg, #0f0c29, #302b63, #24243e); /* dégradé sombre violet/bleu */
background-size: cover;
background-position: center;
background-attachment: fixed;
/* Couleurs générales */
--background-color: #1b1b2f;
--default-text-color: #e0e0ff;
--subtitle-color: #a3a3ff;
--mp3-text-color: #c0c0ff;
/* Formulaire */
--form-bg-color: #2c2c4a;
--form-input-bg-color: #1f1f3a;
--form-input-border-color: #5858f0;
--form-input-border-focus-color: #a3a3ff;
--form-input-text-color: #ffffff;
--form-input-placeholder-color: #8888ff;
--form-button-bg-color: #5858f0;
--form-button-text-color: #ffffff;
--form-button-bg-hover-color: #7a7aff;
/* Checkbox */
--checkbox-bg-default: #302b63;
--checkbox-bg-checked: #5858f0;
--checkbox-checkmark-border-color: #ffffff;
--checkbox-pulse-color: rgba(88,88,240,0.3);
/* Download status */
--download-status-color: #a3a3ff;
/* Video Info Box */
--infos-box-color: #2c2c4a;
--video-info-text-color: #e0e0ff;
--video-info-heading-color: #a3a3ff;
--video-info-list-color: #c0c0ff;
--video-info-list-strong-color: #ffffff;
--video-info-link-color: #5858f0;
--video-info-link-hover-color: #7a7aff;
/* Playlist Color */
--playlist-background-color: #3e3e64;
/* Settings */
--settings-button-bg-color: #202038;
--settings-button-text-color: var(--default-text-color);
--settings-bg-primary-color: #242454;
--settings-bg-secondary-color: #2f2f6f;
--settings-text-color: var(--default-text-color);
--settings-subtitle-color: #d2d2d2;
}
body.neon .download-path {
color: var(--default-text-color);
}

View File

@@ -1,46 +1,56 @@
body.nf { 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 */ background-image: url('../../assets/images/NF.jpg');
--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-size: cover;
background-position: 70% center; background-position: 70% center;
background-attachment: fixed; background-attachment: fixed;
/* Couleurs générales */
--background-color: #1a1a1a;
--default-text-color: #e6dfd5;
--subtitle-color: #f5f0e6;
--mp3-text-color: #d8cfc2;
/* Formulaire */
--form-bg-color: #2a2a2a;
--form-input-bg-color: #222222;
--form-input-border-color: #4a4036;
--form-input-border-focus-color: #cbb89d;
--form-input-text-color: #f0e8dd;
--form-input-placeholder-color: #9a8f80;
--form-button-bg-color: #cbb89d;
--form-button-text-color: #1a1a1a;
--form-button-bg-hover-color: #b7a588;
/* Checkbox */
--checkbox-bg-default: #4a4036;
--checkbox-bg-checked: #cbb89d;
--checkbox-checkmark-border-color: #1a1a1a;
--checkbox-pulse-color: rgba(203,184,157,0.3);
/* Download status */
--download-status-color: #cbb89d;
/* Video Info Box */
--infos-box-color: #2a2a2a;
--video-info-text-color: #e6dfd5;
--video-info-heading-color: #f5f0e6;
--video-info-list-color: #d8cfc2;
--video-info-list-strong-color: #ffffff;
--video-info-link-color: #cbb89d;
--video-info-link-hover-color: #b7a588;
/* Playlist Color */
--playlist-background-color: #4f4f4f;
/* Settings */
--settings-button-bg-color: #2a2a2a;
--settings-button-text-color: var(--default-text-color);
--settings-bg-primary-color: #2a2a2a;
--settings-bg-secondary-color: #313131;
--settings-text-color: var(--default-text-color);
--settings-subtitle-color: #7a7979;
} }
body.nf .download-path { body.nf .download-path {

View File

@@ -0,0 +1,58 @@
body.songbird {
background-image: url('../../assets/images/Songbird.webp');
background-size: cover;
background-position: center center;
background-attachment: fixed;
/* Couleurs générales */
--background-color: #0a0a0a; /* noir profond */
--default-text-color: #ff2a2a; /* rouge néon */
--subtitle-color: #f2f2f2; /* rouge sombre */
--mp3-text-color: #ff4d4d; /* rouge un peu plus clair */
/* Formulaire */
--form-bg-color: rgba(20,0,0,0.85); /* noir semi-transparent avec rouge */
--form-input-bg-color: #1a0a0a;
--form-input-border-color: #ff2a2a;
--form-input-border-focus-color: #b30000;
--form-input-text-color: #ffffff;
--form-input-placeholder-color: #950404;
--form-button-bg-color: #b30000;
--form-button-text-color: #ffffff;
--form-button-bg-hover-color: #ff2a2a;
/* Checkbox */
--checkbox-bg-default: #630000;
--checkbox-bg-checked: #ff2a2a;
--checkbox-checkmark-border-color: #ffffff;
--checkbox-pulse-color: rgba(255,42,42,0.3);
/* Download status */
--download-status-color: #ff2a2a;
/* Video Info Box */
--infos-box-color: rgba(30,0,0,0.9);
--video-info-text-color: #ff4d4d;
--video-info-heading-color: #ff2a2a;
--video-info-list-color: #b30000;
--video-info-list-strong-color: #ffffff;
--video-info-link-color: #ff2a2a;
--video-info-link-hover-color: #b30000;
/* Playlist Color */
--playlist-background-color: #3f0000;
/* Settings */
--settings-button-bg-color: rgba(29, 0, 0, 0.784);
--settings-button-text-color: var(--default-text-color);
--settings-bg-primary-color: #121212;
--settings-bg-secondary-color: #1a1a1a;
--settings-text-color: var(--default-text-color);
--settings-subtitle-color: #7a7979;
}
body.cyberpunk .download-path {
color: var(--default-text-color);
}

View File

@@ -1,14 +1,8 @@
/*
*/
body.spicy { body.spicy {
/* Couleurs générales */ /* Couleurs générales */
--background-color: #121212; /* fond noir profond */ --background-color: #121212; /* fond noir profond */
--default-text-color: #581010; /* texte clair */ --default-text-color: #ffffff; /* texte clair */
--subtitle-color: #3d0909; /* accent rouge piment */ --subtitle-color: #f87918; /* accent rouge piment */
--mp3-text-color: #ffffff; /* texte checkbox inline */ --mp3-text-color: #ffffff; /* texte checkbox inline */
/* Formulaire */ /* Formulaire */
@@ -39,12 +33,27 @@ body.spicy {
--video-info-list-strong-color: #ffffff; /* texte important blanc */ --video-info-list-strong-color: #ffffff; /* texte important blanc */
--video-info-link-color: #ff1a1a; /* liens rouges */ --video-info-link-color: #ff1a1a; /* liens rouges */
--video-info-link-hover-color: #cc0000; /* liens hover rouge foncé */ --video-info-link-hover-color: #cc0000; /* liens hover rouge foncé */
/* Playlist Color */
--playlist-background-color: #353232;
/* Settings */
--settings-button-bg-color: #1a1a1a;
--settings-button-text-color: #dcdcdc;
--settings-bg-primary-color: #1a1a1a;
--settings-bg-secondary-color: #292929;
--settings-text-color: #cecece;
--settings-subtitle-color: #adadad;
} }
/* Optionnel: background piments */ /* Optionnel: background piments */
body.spicy { body.spicy {
background-image: url('../../assets/images/Spicy.webp'); background-image: url('../../assets/images/Spicy.png');
background-size:contain; background-size:cover;
background-position: center; background-position: center;
background-attachment: fixed; background-attachment: fixed;
background-repeat: no-repeat;
} }

View File

@@ -1,9 +1,9 @@
@import url("chirac-theme.css"); @import url("chirac-theme.css");
@import url("dark-theme.css"); @import url("dark-theme.css");
@import url("default-theme.css");
@import url("fanatic-theme.css"); @import url("fanatic-theme.css");
@import url("light-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"); @import url("nf-theme.css");
@import url("drift-theme.css");
@import url("neon-theme.css");
@import url("songbird-theme.css");
@import url("spicy-theme.css")

View File

@@ -1,48 +0,0 @@
body.vilbrequin {
/* Couleurs générales */
--background-color: #0a1f3d; /* bleu Vilbrequin profond */
--default-text-color: #535353; /* texte clair */
--subtitle-color: #001696; /* bleu clair accent (turbo vibes) */
--mp3-text-color: #ffffff; /* texte checkbox inline */
/* Formulaire */
--form-bg-color: #12284f; /* fond formulaire bleu foncé */
--form-input-bg-color: #1c3760; /* fond inputs bleu + clair */
--form-input-border-color: #2a4b80; /* bordure bleue */
--form-input-border-focus-color: #00bfff; /* focus bleu clair */
--form-input-text-color: #f0f0f0; /* texte inputs clair */
--form-input-placeholder-color: #b0c4de; /* placeholder gris bleuté */
--form-button-bg-color: #ffd700; /* bouton jaune turbo */
--form-button-text-color: #0a1f3d; /* texte bouton sombre */
--form-button-bg-hover-color: #e6c200; /* hover jaune foncé */
/* Checkbox */
--checkbox-bg-default: #2a4b80; /* bleu moyen */
--checkbox-bg-checked: #00bfff; /* bleu clair accent */
--checkbox-checkmark-border-color: #f0f0f0; /* blanc cassé */
--checkbox-pulse-color: rgba(0,191,255,0.5); /* pulse bleu clair translucide */
/* Download status */
--download-status-color: #ffd700; /* jaune turbo */
/* Video Info Box */
--infos-box-color: #12284f; /* fond infos bleu foncé */
--video-info-text-color: #f0f0f0; /* texte clair */
--video-info-heading-color: #00bfff; /* titres bleu clair */
--video-info-list-color: #dcdcdc; /* texte liste gris clair */
--video-info-list-strong-color: #ffffff; /* texte important blanc */
--video-info-link-color: #00bfff; /* liens bleu clair */
--video-info-link-hover-color: #009acd; /* liens hover bleu plus foncé */
}
/* Optionnel: motif turbo ou jantes */
body.vilbrequin {
background-image: url('../../assets/images/Vilbrequin.webp'); /* adapte ton image */
background-size: cover;
background-position: center;
background-attachment: fixed;
}
body.vilbrequin .download-path{
color: white;
}

View File

@@ -1,20 +1,20 @@
:root { :root {
/* Couleurs générales */ /* Couleurs générales */
--background-color: #001224; /* body background bleu nuit */ --background-color: #001224;
--default-text-color: #eee; /* texte clair principal */ --default-text-color: #eee;
--subtitle-color: #007bff; /* sous-titres, header p */ --subtitle-color: #007bff;
--mp3-text-color: #000000; /* couleur texte checkbox inline */ --mp3-text-color: #000000;
/* Formulaire */ /* Formulaire */
--form-bg-color: #ffffff; /* fond du formulaire */ --form-bg-color: #ffffff;
--form-input-bg-color: #f9f9f9; /* fond inputs */ --form-input-bg-color: #f9f9f9;
--form-input-border-color: #cccccc; /* bordure inputs */ --form-input-border-color: #cccccc;
--form-input-border-focus-color: #0056b3; /* bordure focus */ --form-input-border-focus-color: #0056b3;
--form-input-text-color: #333333; /* texte inputs */ --form-input-text-color: #333333;
--form-input-placeholder-color: #aaaaaa; /* placeholder */ --form-input-placeholder-color: #aaaaaa;
--form-button-bg-color: #007bff; /* bouton bg */ --form-button-bg-color: #007bff;
--form-button-text-color: #ffffff; /* texte bouton */ --form-button-text-color: #ffffff;
--form-button-bg-hover-color: #0056b3; /* bouton hover */ --form-button-bg-hover-color: #0056b3;
/* Checkbox */ /* Checkbox */
--checkbox-bg-default: #cccccc; --checkbox-bg-default: #cccccc;
@@ -33,4 +33,14 @@
--video-info-list-strong-color: #000000; --video-info-list-strong-color: #000000;
--video-info-link-color: #007bff; --video-info-link-color: #007bff;
--video-info-link-hover-color: #0056b3; --video-info-link-hover-color: #0056b3;
/* Settings */
--settings-button-bg-color: var(--form-bg-color);
--settings-button-text-color: var(--default-text-color);
--settings-bg-primary-color: var(--form-bg-color);
--settings-bg-secondary-color: var(--form-bg-color);
--settings-text-color: var(--default-text-color);
--settings-subtitle-color: #aaa;
} }

View File

@@ -0,0 +1,64 @@
const { fetchDownload } = require("../services/download.services");
const { logger } = require("../logger");
const { isValidUrl, isSafePath } = require("../helpers/validation");
const { notifyDownloadFinished } = require("../helpers/notify");
const listeners = [];
const speedListeners = [];
async function downloadController(req, res) {
try {
const options = {
url: req.body.url,
audioOnly: req.body.audioOnly === "1",
quality: req.body.quality || "best",
outputFolder: req.body.savePath || undefined,
};
if (!options.url || !isValidUrl(options.url)) return res.status(400).send("❌ Invalid URL !");
if (options.outputFolder && !isSafePath(options.outputFolder)) return res.status(400).send("❌ Save Path Not Allowed.");
const filePath = await fetchDownload(options, listeners, speedListeners);
notifyDownloadFinished(filePath);
res.send("✅ Download Done !");
} catch (err) {
logger.error(`Server Error in /download : ${err.message}`);
res.status(500).send(`❌ Server Error`);
}
}
function progressController(req, res) {
res.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
const sendProgress = percent => res.write(`data: ${percent}\n\n`);
listeners.push(sendProgress);
req.on('close', () => {
listeners.splice(listeners.indexOf(sendProgress), 1);
res.end();
});
}
function speedController(req, res) {
res.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
const sendSpeed = speed => res.write(`data: ${speed}\n\n`);
speedListeners.push(sendSpeed);
req.on('close', () => {
speedListeners.splice(speedListeners.indexOf(sendSpeed), 1);
res.end();
});
}
module.exports = { downloadController, progressController, speedController};

View File

@@ -0,0 +1,46 @@
const { fetchInfo } = require("../services/info.services");
const { parseVideo, parsePlaylist } = require("../helpers/parseInfo");
const { logger } = require("../logger");
const { isValidUrl } = require("../helpers/validation");
async function infoController(req, res){
const url = req.body.url || req.query.url; // Gérer POST et GET
/* Si pas d'url ou url invalide*/
if (!url || typeof url !== "string" || !isValidUrl(url)) return res.status(400).send("❌ Invalid URL Or Missing");
logger.info(`/Info Request receive by the Info Controller. URL: ${url}`);
if (url.includes("?list")) {
logger.info("Estimated Data Type: Playlist")
} else{
logger.info("Estimated Data Type: Video")
}
try {
const data = await fetchInfo(url);
if (data._type === "playlist") {
const playlist = parsePlaylist(data);
logger.info(`Playlist : ${playlist.title} (${playlist.count} vidéos)`);
return res.json(playlist);
} else{
const video = parseVideo(data);
logger.info(`Unique Video: ${video.title}`);
return res.json(video);
}
} catch (err) {
logger.error(`Info controller error: ${err && err.message ? err.message : err}`);
return res.status(500).send(`❌ Unable to fetch info`);
}
}
module.exports = {infoController};

View File

@@ -1,47 +1,51 @@
/* const config = require('../config');
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 RPC = require("discord-rpc");
const clientId = "1410934537051181146"; const { logger } = require("./logger");
const clientId = `${config.DiscordRPCID}`;
const rpc = new RPC.Client({ transport: "ipc" }); const rpc = new RPC.Client({ transport: "ipc" });
let intervalId;
function startRPC() { function startRPC() {
rpc.on("ready", () => { rpc.on("ready", () => {
console.log("✅ Connecté à Discord !");
const presence = { const presence = {
largeImageKey: "icon", largeImageKey: "icon",
smallImageKey: "acnolo_pfp", smallImageKey: "acnolo_pfp",
smallImageText: "By MasterAcnolo", smallImageText: "By MasterAcnolo",
startTimestamp: new Date(), // compteur de temps startTimestamp: new Date(),
details: "github.com/MasterAcnolo/Freedom-Loader", details: `Open Source Download Tools - ${config.version}`,
state: "masteracnolo.github.io/FreedomLoader",
}; };
rpc.setActivity(presence); rpc.setActivity(presence);
// Mise à jour toutes les 15 secondes si tu veux garder le compteur à jour // Met à jour la présence toutes les 15s
setInterval(() => { intervalId = setInterval(() => {
rpc.setActivity(presence); rpc.setActivity(presence);
}, 15000); }, 15000);
}); });
async function cleanExit() {
try {
if (intervalId) clearInterval(intervalId);
if (rpc && rpc.transport) {
await rpc.destroy();
}
} catch (err) {
logger.error("Error while closing the RPC:", err);
} finally {
process.exit();
}
}
process.on("exit", cleanExit);
process.on("SIGINT", cleanExit);
process.on("SIGTERM", cleanExit);
rpc.login({ clientId }).catch(err => { rpc.login({ clientId }).catch(err => {
console.error("Erreur Discord RPC :", err); logger.error("Unable to connect to the RPC:", err);
}); });
} }

View File

@@ -0,0 +1,85 @@
const path = require("path");
const getUserBrowser = require("./getBrowser");
const { ffmpegPath, denoPath} = require("./path");
const { configFeatures } = require("../../config.js");
const { logger } = require("../logger");
function validateCodec(codec){
const validCodec = [
"av01","vp9.2", "vp9" , "h265" , "h264" , "vp8" , "h263", "theora"
]
if(validCodec.includes(codec)){
logger.info(`Codec Valid: ${codec}`)
return codec
} else{
logger.error(`Codec not valid: ${codec}. Using default codec`)
return "h264"
}
}
function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
logger.info("--- CONFIGURATION ---");
logger.info(`CONFIG autoUpdate: ${configFeatures.autoUpdate}`);
logger.info(`CONFIG discordRPC: ${configFeatures.discordRPC}`);
logger.info(`CONFIG customTopBar: ${configFeatures.customTopBar}`);
logger.info(`CONFIG autoCheckInfo: ${configFeatures.autoCheckInfo}`);
logger.info(`CONFIG addThumbnail: ${configFeatures.addThumbnail}`);
logger.info(`CONFIG addMetadata: ${configFeatures.addMetadata}`);
logger.info(`CONFIG verboseLogs: ${configFeatures.verboseLogs}`);
logger.info(`CONFIG autoDownloadPlaylist: ${configFeatures.autoDownloadPlaylist}`);
logger.info(`CONFIG customCodec: ${configFeatures.customCodec}`);
// ACTIVATE THEM WHEN THE FEATURE IS IMPLEMENTED
// logger.info(`CONFIG logSystem: ${configFeatures.logSystem}`);
// logger.info(`CONFIG outputTitleCheck: ${configFeatures.outputTitleCheck}`);
// logger.info(`CONFIG downloadSystem: ${configFeatures.downloadSystem}`);
// logger.info(`CONFIG notifySystem: ${configFeatures.notifySystem}`);
logger.info("---------------------");
const args = [
configFeatures.verboseLogs ? "--verbose" : null, // Verbose Logs
"--cookies-from-browser", `${getUserBrowser()}`,
"--no-continue",
"--no-overwrites",
"--newline", // YT-DLP Logs Format
configFeatures.addThumbnail ? "--embed-thumbnail" : null,
configFeatures.addMetadata ? "--add-metadata" : null,
"--concurrent-fragments", "8",
"--retries", "10",
"--fragment-retries", "10",
"--ffmpeg-location", ffmpegPath,
"--extractor-args","youtube:player_client=default",
"--js-runtimes", `deno:${denoPath}`,
"-S",
`vcodec:${validateCodec(configFeatures.customCodec) || "h264"}`+
`,acodec:aac`,
configFeatures.autoDownloadPlaylist ? "--yes-playlist" : "--no-playlist"
];
if (audioOnly) args.push("-f", "bestaudio", "--extract-audio", "--audio-format", "mp3");
else args.push("--merge-output", "mp4");
const qualityMap = {
best: "bestvideo+bestaudio/best/mp4",
medium: "bestvideo[height<=720]+bestaudio/best[height<=720]/mp4",
worst: "worstvideo+worstaudio/worst/mp4",
1080: "bestvideo[height<=1080]+bestaudio/best[height<=1080]/mp4",
720: "bestvideo[height<=720]+bestaudio/best[height<=720]/mp4",
480: "bestvideo[height<=480]+bestaudio/best[height<=480]/mp4",
};
args.push("-f", qualityMap[quality] || "best");
args.push("-o", path.join(outputFolder, "%(title)s.%(ext)s"));
args.push(url);
return args.filter(Boolean);
}
module.exports = { buildYtDlpArgs };

View File

@@ -0,0 +1,32 @@
const fs = require("fs");
const os = require("os");
const path = require("path");
const notify = require("./notify")
function getUserBrowser() {
const userProfile = os.homedir();
// Liste des navigateurs supportés par yt-dlp (Actuellement je peux que garantir Firefox, Désolé)
const browsers = [
{ name: "firefox", path: path.join(userProfile, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles") },
// { name: "chrome", path: path.join(userProfile, "AppData", "Local", "Google", "Chrome", "User Data", "Default") },
// { name: "brave", path: path.join(userProfile, "AppData", "Local", "BraveSoftware", "Brave-Browser", "User Data", "Default") },
// { name: "edge", path: path.join(userProfile, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default") },
// { name: "opera", path: path.join(userProfile, "AppData", "Roaming", "Opera Software", "Opera Stable") },
// { name: "vivaldi", path: path.join(userProfile, "AppData", "Local", "Vivaldi", "User Data", "Default") },
// { name: "safari", path: path.join(userProfile, "AppData", "Local", "Apple Computer", "Safari") },
// { name: "whale", path: path.join(userProfile, "AppData", "Local", "Naver", "Naver Whale", "User Data", "Default") }
];
for (const browser of browsers) {
if (fs.existsSync(browser.path)) {
return browser.name;
} else{
notify.notifyFirefoxBrowserMissing();
}
}
notify.notifyCookiesBrowserError()
return ;
}
module.exports = getUserBrowser

44
server/helpers/notify.js Normal file
View File

@@ -0,0 +1,44 @@
const { Notification, shell } = require("electron");
const path = require("path");
function notifyDownloadFinished(folder) {
const iconPath = path.join(process.resourcesPath, "confirm-icon.png");
const notif = new Notification({
title: "Freedom Loader",
body: "Your download is complete, click here to open it.",
icon: iconPath,
});
notif.on("click", () => shell.openPath(folder));
notif.show();
}
function notifyCookiesBrowserError(){
const iconPath = path.join(process.resourcesPath, "error.png");
const notif = new Notification({
title: "Cookies Error",
body: "Unable to retrieve cookies. Please log in to your browser and click here to view the tutorial.",
icon: iconPath,
});
notif.on("click", () => shell.openExternal("https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"));
notif.show();
}
function notifyFirefoxBrowserMissing(){
const iconPath = path.join(process.ressourcesPath, "error.png");
const notif = new Notification({
title: "Firefox Missing",
body: "Firefox was not found on your system. Click here to follow the installation guide",
icon: iconPath,
});
notif.on("click", () => shell.openExternal("https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"))
notif.show();
}
module.exports = {
notifyDownloadFinished,
notifyCookiesBrowserError,
notifyFirefoxBrowserMissing
};

View File

@@ -0,0 +1,33 @@
function parseVideo(data) {
return {
type: "video",
// id: data.id,
// title: data.title,
// url: data.webpage_url,
// duration: data.duration,
// thumbnail: data.thumbnail,
// uploader: data.uploader
...data
};
}
function parsePlaylist(data) {
return {
type: "playlist",
title: data.title || data.id,
channel: data.uploader,
count: data.entries.length,
videos: (data.entries || []).map(v => ({
id: v.id,
title: v.title,
url: v.webpage_url,
duration: v.duration,
thumbnail: v.thumbnail,
uploader: v.uploader
}))
};
}
module.exports = { parseVideo, parsePlaylist };

33
server/helpers/path.js Normal file
View File

@@ -0,0 +1,33 @@
const path = require("path");
const fs = require("fs");
const { app } = require("electron");
const config = require("../../config");
const { logger } = require("../logger.js");
let userYtDlp;
let ffmpegPath;
let denoPath;
const sourceYtDlp = path.join(process.resourcesPath, "binaries","yt-dlp.exe");
if (config.localMode) {
userYtDlp = path.join(__dirname, "../../ressources/yt-dlp.exe");
ffmpegPath = path.join(__dirname, "../../ressources/"); // <- contient ffmpeg.exe et ffprobe.exe
denoPath = path.join(__dirname, "../../ressources/deno.exe");
} else {
userYtDlp = path.join(app.getPath("userData"),"yt-dlp.exe");
ffmpegPath = path.join(process.resourcesPath, "binaries","ffmpeg.exe");
denoPath = path.join(process.resourcesPath, "binaries","deno.exe");
if (!fs.existsSync(userYtDlp)) {
fs.copyFileSync(sourceYtDlp, userYtDlp);
}
}
if (!userYtDlp){ logger.error("Missing YT-DLP")}
if (!ffmpegPath){ logger.error("Missing FFMPEG")}
if (!denoPath){ logger.error("Missing DENO")}
module.exports = { userYtDlp, ffmpegPath, denoPath };

View File

@@ -0,0 +1,14 @@
const RateLimit = require("express-rate-limit");
/* Rate Limite */
const rateLimite = RateLimit({
windowMs: 15 * 60, // 15 minutes
max: 5, // limit each IP to 100 requests per windowMs on the root path
message: "Too many requests, please try again later.",
statusCode: 429, // HTTP status code for "Too Many Requests"
});
module.exports = {
rateLimite
}

View File

@@ -0,0 +1,21 @@
const path = require("path");
function isValidUrl(url) {
try {
new URL(url);
return true;
} catch {
return false;
}
}
function isSafePath(folder) {
if (!folder || folder.length < 3) return false;
const unsafe = ["System32", "/etc", "\\Windows"];
const resolved = path.resolve(folder);
return !unsafe.some(u => resolved.includes(u));
}
module.exports = { isValidUrl, isSafePath };

View File

@@ -1,107 +1,58 @@
/*
This file is part of Freedom Loader.
Copyright (C) 2025 MasterAcnolo
Freedom Loader is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
Freedom Loader is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { createLogger, format, transports } = require("winston"); const { createLogger, format, transports } = require("winston");
const DailyRotateFile = require("winston-daily-rotate-file"); const DailyRotateFile = require("winston-daily-rotate-file");
const fs = require("fs"); // Pour vérifier/créer le dossier de logs const fs = require("fs");
const path = require("path"); // Pour gérer proprement les chemins const path = require("path");
const os = require("os"); // Pour gérer les chemins aussi const os = require("os");
const config = require("../config")
// Définition du dossier où seront stockés les logs // Dossier de logs Windows
// const logDir = path.join(__dirname, "../logs"); Désactivé parce que cela requiert d'exec en admin mode. Donc pas pratique pour les gens const logDir = path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs");
let logDir; // Création du dossier "logs" si nécessaire
if (process.platform === "win32") { fs.mkdirSync(logDir, { recursive: true });
logDir = path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs");
} else {
logDir = path.join(os.homedir(), ".freedomloader", "logs");
}
// Création du dossier logs sil nexiste pas encore const logFormat = format.combine(
//if (!fs.existsSync(logDir)) fs.mkdirSync(logDir); OBSOLETE format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.printf(({ timestamp, level, message }) => `${timestamp} | ${level.toUpperCase()} | ${message}`)
);
fs.mkdirSync(logDir, { recursive: true }); // Evite les erreurs // Configuration du logger
// Fonction utilitaire pour générer une ligne indiquant le début dune session de logs
function getSessionStartLine() {
const now = new Date().toISOString();
return `--- Démarrage de la session : ${now} ---`;
}
// Fonction utilitaire pour générer une ligne indiquant la fin dune session de logs
function getSessionEndLine() {
const now = new Date().toISOString();
return `--- Fin de la session : ${now} ---`;
}
// Configuration principale de Winston
// - Niveau minimum décriture : info (enregistre info, warn, error, etc.)
// - Formatage des logs avec timestamp lisible et format personnalisé
// - Transports : écriture dans fichier journalier + console avec couleurs
const logger = createLogger({ const logger = createLogger({
level: "info", level: "info",
format: format.combine( format: logFormat,
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.printf(({ timestamp, level, message }) =>
`${timestamp} | ${level.toUpperCase()} | ${message}`
)
),
transports: [ transports: [
// Rotation quotidienne des fichiers de logs
new DailyRotateFile({ new DailyRotateFile({
dirname: logDir, // dossier cible pour les logs dirname: logDir,
filename: "LOGS-%DATE%.log", // nom du fichier par date filename: "LOGS-%DATE%.log",
datePattern: "YYYY-MM-DD", // pattern de date dans le nom datePattern: "YYYY-MM-DD",
zippedArchive: false, // ne pas compresser les archives zippedArchive: false,
maxFiles: "7d", // conserve 14 jours dhistorique maxFiles: "7d",
format: format.combine( format: logFormat,
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), options: { flags: "a" },
format.printf(({ timestamp, level, message }) =>
`${timestamp} | ${level.toUpperCase()} | ${message}`
)
),
options: { flags: "a" }, // mode ajout à la fin (append)
}), }),
// Affichage des logs en console avec coloration et format simple
new transports.Console({ new transports.Console({
format: format.combine( format: logFormat,
format.colorize(),
format.timestamp({ format: "HH:mm:ss" }),
format.printf(({ timestamp, level, message }) =>
`${timestamp} | ${level} | ${message}`
)
),
}), }),
], ],
}); });
// Helper pour marquer clairement le début de session dans les logs function getSessionStartLine() {
function logSessionStart() { return `--- Starting session: ${new Date().toISOString()} ---`;
logger.info(getSessionStartLine()); }
function getSessionEndLine() {
return `--- Ending session: ${new Date().toISOString()} ---`;
}
function logSessionStart() {
logger.info(getSessionStartLine());
logger.info(`Application Version: ${config.version}`)
} }
// 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 dautres modules
module.exports = { module.exports = {
logger, logger,
logSessionStart, logSessionStart,

View File

@@ -1,197 +0,0 @@
/*
This file is part of Freedom Loader.
Copyright (C) 2025 MasterAcnolo
Freedom Loader is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
Freedom Loader is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
*/
const express = require("express");
const router = express.Router();
const { execFile } = require("child_process");
const path = require("path");
const fs = require("fs");
const { Notification } = require("electron");
const logger = require("../logger").logger;
// Path vers le fichier exécutable yt-dlp (outil tiers pour le téléchargement)
// const ytDlpPath = path.join(process.resourcesPath, '../../yt-dlp.exe');
const ytDlpPath = path.join(process.resourcesPath, 'yt-dlp 2025.08.27.exe');
router.post("/", (req, res) => {
try {
// Récupération des options envoyées par le frontend via le formulaire
// On vérifie la présence de l'URL, le choix audioOnly, et la qualité demandée
const options = {
url: req.body.url,
audioOnly: req.body.audioOnly === "1",
quality: req.body.quality || "best",
};
// Validation simple : si l'URL est manquante, on rejette la requête
if (!options.url) {
logger.warn("Requête POST /download sans URL");
return res.status(400).send("❌ URL manquante !");
}
// Récupération du chemin de sauvegarde personnalisé envoyé depuis Electron,
// ou on utilise un chemin par défaut stocké dans app.locals
let requestedOutputFolder = req.body.savePath || req.app.locals.outputFolder;
// On normalise le chemin pour éviter des erreurs liées aux séparateurs ou chemins relatifs
requestedOutputFolder = path.normalize(requestedOutputFolder);
/*
Sécurité basique : On refuse certains chemins sensibles ou suspects
(ex: System32, /etc, Windows) pour éviter que l'application n'écrive dans des dossiers système.
On vérifie aussi la longueur minimale du chemin pour éviter des valeurs vides ou invalides.
*/
if (
!requestedOutputFolder ||
requestedOutputFolder.length < 3 ||
requestedOutputFolder.includes("System32") ||
requestedOutputFolder.includes("/etc") ||
requestedOutputFolder.includes("\\Windows")
) {
logger.warn(`Chemin potentiellement dangereux refusé : ${requestedOutputFolder}`);
return res.status(400).send("❌ Chemin de sauvegarde non autorisé.");
}
// Vérifie que le dossier de sortie existe, sinon le crée récursivement
if (!fs.existsSync(requestedOutputFolder)) {
fs.mkdirSync(requestedOutputFolder, { recursive: true });
logger.info(`Dossier de sortie créé : ${requestedOutputFolder}`);
}
// Construction du template de sortie pour yt-dlp (nom de fichier basé sur le titre)
const outputTemplate = path.join(requestedOutputFolder, "%(title)s.%(ext)s");
// Construction du tableau d'arguments à passer à yt-dlp
// --no-continue pour forcer un téléchargement propre
// --restrict-filenames pour éviter les caractères problématiques dans les noms
const args = [
"--no-continue", // pas de reprise, c'est un choix que j'ai fait voila
//"--restrict-filenames", // noms de fichiers sans caractères spéciaux (Cyrilique, accents, etc.)
"--no-overwrites", // évite d'écraser un fichier existant
"--embed-thumbnail", // ajoute la pochette
"--add-metadata", // ajoute les tags (titre, artiste, etc.)
"--concurrent-fragments", "8",// accélère le téléchargement
"--retries", "10", // réessaie jusqu'à 10 fois en cas d'erreur
"--fragment-retries", "10" , // réessaie aussi 10 fois chaque fragment
"--ffmpeg-location", path.join(process.resourcesPath, "ffmpeg.exe")
];
// Si l'option audioOnly est activée, on ajoute les flags pour extraction audio en mp3
if (options.audioOnly) {
args.push(
"-f",
"bestaudio",
"--extract-audio",
"--audio-format",
"mp3",
);
}
// Correspondance entre la qualité choisie et le format à demander à yt-dlp
const qualityMap = {
best: "bestvideo+bestaudio/best",
medium: "bestvideo[height<=720]+bestaudio/best[height<=720]",
worst: "worstvideo+worstaudio/worst",
1080: "bestvideo[height<=1080]+bestaudio/best[height<=1080]",
720: "bestvideo[height<=720]+bestaudio/best[height<=720]",
480: "bestvideo[height<=480]+bestaudio/best[height<=480]",
};
// Sélection du format basé sur la qualité, par défaut 'best' si option invalide
const format = qualityMap[options.quality] || "best";
args.push("-f", format);
// Ajout du template de sortie et de l'URL à télécharger
args.push("-o", outputTemplate);
args.push(options.url);
// Log de la commande complète pour traçabilité et débogage
logger.info(`Téléchargement demandé : url=${options.url}, audioOnly=${options.audioOnly}, quality=${options.quality}`);
logger.info(`Commande yt-dlp : ${ytDlpPath} ${args.join(" ")}`);
// Lancement du processus yt-dlp avec les arguments définis
const child = execFile(ytDlpPath, args);
/*
Gestion des sorties standards (stdout) du processus,
on log chaque ligne pour suivre la progression ou informations diverses
*/
child.stdout.on("data", (data) => {
data.toString().split("\n").forEach(line => {
if (line.trim()) logger.info(`[yt-dlp stdout] ${line.trim()}`);
});
});
/*
Gestion des erreurs (stderr) du processus,
on log chaque ligne d'erreur pour diagnostic
*/
child.stderr.on("data", (data) => {
data.toString().split("\n").forEach(line => {
if (line.trim()) logger.error(`[yt-dlp stderr] ${line.trim()}`);
});
});
/*
En cas d'erreur lors du lancement du processus yt-dlp,
on log l'erreur et on répond au client avec un statut 500
*/
child.on("error", (err) => {
logger.error(`Erreur lancement yt-dlp : ${err.message}`);
res.status(500).send(`❌ Erreur lors de l'exécution : ${err.message}`);
});
/*
Quand le processus se termine, on vérifie le code de sortie.
Code 0 = succès, on informe le client que le téléchargement est terminé.
Sinon, on envoie une erreur au client avec le code d'échec.
*/
child.on("close", (code) => {
logger.info(`yt-dlp terminé avec code de sortie : ${code}`);
if (code === 0) {
const iconPath = path.join(process.resourcesPath, "confirm-icon.png"); // la sécurité pour toutes les machines
console.log("Icon path pour la notif :", iconPath);
const notif = new Notification({
title: "Freedom Loader",
body: "Ton téléchargement est terminé, clique ici pour l'ouvrir.",
icon: iconPath,
});
notif.on("click", () => {
console.log("Notification cliquée !");
//Pour pouvoir ouvrir le dossier de la vidéo
const { shell } = require("electron");
shell.openPath(requestedOutputFolder);
});
notif.show();
res.send("✅ Téléchargement terminé !");
} else {
res.status(500).send(`❌ yt-dlp a échoué avec le code : ${code}`);
}
});
} catch (err) {
// Capture toute autre erreur serveur non prévue, log et réponse 500
logger.error(`Erreur serveur dans /download : ${err.message}`);
res.status(500).send(`Erreur serveur : ${err.message}`);
}
});
module.exports = router;

View File

@@ -0,0 +1,11 @@
const express = require("express");
const router = express.Router();
const { downloadController, progressController, speedController } = require("../controller/download.controller");
router.post("/", downloadController);
// SSE pour la progression du téléchargement
router.get("/progress", progressController);
router.get("/speed", speedController);
module.exports = router;

View File

@@ -1,81 +0,0 @@
/*
This file is part of Freedom Loader.
Copyright (C) 2025 MasterAcnolo
Freedom Loader is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
Freedom Loader is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const express = require("express");
const router = express.Router();
const { execFile } = require("child_process");
const path = require("path");
const fs = require("fs");
const { logger } = require("../logger"); // On récupère ton logger Winston
// Path absolu vers l'exécutable yt-dlp
const ytDlpPath = path.join(__dirname, '../../ressources/yt-dlp 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) => {
const url = req.body.url;
// Validation basique : on refuse la requête sans URL
if (!url) {
logger.warn("Requête metadata sans URL");
return res.status(400).send("❌ URL manquante");
}
logger.info(`Requête metadata reçue pour ${url}`);
// Exécution de yt-dlp avec l'option --dump-json pour récupérer les infos vidéo
execFile(
ytDlpPath,
["--dump-json", url],
{ timeout: 10_000 }, // Timeout fixé à 10 secondes pour éviter blocage
(error, stdout, stderr) => {
if (error) {
// En cas d'erreur, on log l'erreur et le stderr, puis on renvoie un code 500
logger.error(`Erreur exécution yt-dlp: ${error.message}`);
logger.debug(`stderr: ${stderr}`);
return res.status(500).send("❌ Impossible de récupérer les infos.");
}
try {
// yt-dlp peut retourner plusieurs JSON séparés par des sauts de ligne
// On split et parse chacun proprement
const infos = stdout
.trim()
.split("\n")
.map(line => JSON.parse(line));
logger.info(`Infos récupérées pour ${url} (${infos.length} élément(s))`);
// Si on a un seul élément, on renvoie directement l'objet JSON, sinon un tableau
res.json(infos.length === 1 ? infos[0] : infos);
} catch (e) {
// Si parsing JSON échoue, on log et renvoie une erreur 500
logger.error(`Erreur parsing JSON: ${e.message}`);
return res.status(500).send("❌ JSON illisible.");
}
}
);
});
module.exports = router;

View File

@@ -0,0 +1,7 @@
const express = require("express");
const router = express.Router();
const { infoController } = require("../controller/info.controller");
router.post("/", infoController);
module.exports = router;

View File

@@ -1,106 +1,70 @@
/* const express = require("express");
This file is part of Freedom Loader. const fs = require("fs");
const path = require("path");
const { logger, logSessionEnd } = require("./logger");
const config = require("../config");
const { execFile } = require("child_process");
const { userYtDlp } = require("./helpers/path");
const { rateLimite } = require("./helpers/rateLimit")
Copyright (C) 2025 MasterAcnolo const app = express();
Freedom Loader is free software: you can redistribute it and/or modify app.use(express.json());
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, // Dossier de téléchargement
but WITHOUT ANY WARRANTY; without even the implied warranty of const outputFolder = path.join(process.env.USERPROFILE, "Downloads", "Freedom Loader");
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 // Création du dossier si nécessaire
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const express = require("express"); // Framework web pour créer lAPI et servir les fichiers
const { exec } = require("child_process"); // Permet dexécuter des commandes systèmes (pas utilisé directement ici)
const fs = require("fs"); // Module système pour gérer fichiers et dossiers
const path = require("path"); // Gestion propre des chemins de fichiers et dossiers
const debug = require("debug")("freedom-loader:server");
// Module de debug coloré en console, pratique pour dev
const { logger, logSessionStart, logSessionEnd } = require("./logger");
// Import du logger Winston et helpers pour marquer début/fin de session
const app = express(); // Création de linstance Express, notre serveur web
// Définition du dossier par défaut où enregistrer les téléchargements
// On prend le dossier Téléchargements de lutilisateur Windows (USERPROFILE)
const downloadsPath = path.join(process.env.USERPROFILE, "Downloads");
const outputFolder = path.join(downloadsPath, "Freedom Loader");
// Création du dossier de sortie sil nexiste pas déjà
if (!fs.existsSync(outputFolder)) {
try { try {
fs.mkdirSync(outputFolder, { recursive: true }); // création récursive au cas où fs.mkdirSync(outputFolder, { recursive: true });
logger.info("Dossier Freedom Loader cree dans Telechargements."); logger.info(`Freedom Loader folder ready in ${outputFolder}`);
} catch (err) { } catch (err) {
logger.error("Impossible de creer le dossier :", err); logger.error("Unable to create folder:", err);
process.exit(1); // Arrêt du programme si dossier non créé (critique) process.exit(1);
}
} else {
logger.info("Dossier Freedom Loader deja existant.");
} }
// On rend ce dossier accessible globalement via app.locals pour lutiliser dans les routes // Mise à jour yt-dlp au
app.locals.outputFolder = outputFolder; execFile(userYtDlp, ["-U"], (err, stdout, stderr) => {
if (err) logger.warn("yt-dlp update error:", err);
else logger.info(`Update yt-dlp : ${stdout}`);
});
// Middleware pour parser le corps des requêtes POST en application/x-www-form-urlencoded // Middlewares
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, "../public")));
// Définition du dossier contenant les fichiers statiques (frontend) // Routes
// Permet daccéder au HTML, CSS, JS côté client app.use("/download", require("./routes/download.route"));
const staticPath = path.join(__dirname, "../public"); app.use("/info", require("./routes/info.route"));
debug("Serveur statique sur", staticPath);
app.use(express.static(staticPath));
// Import et enregistrement des routes API app.get("/", rateLimite ,(req, res) => {
// Ces routes gèrent les requêtes pour les infos vidéo et téléchargement
const infoRoute = require("./routes/info");
const downloadRoute = require("./routes/download");
debug("Routes /download et /info installees");
app.use("/download", downloadRoute);
app.use("/info", infoRoute);
// Route GET / qui sert la page principale index.html
app.get("/", (req, res) => {
debug("Requete GET / servie");
res.sendFile(path.join(__dirname, "../public/index.html")); res.sendFile(path.join(__dirname, "../public/index.html"));
}); });
// Fonction pour démarrer le serveur Express // Fonction pour démarrer le serveur
// Retourne une Promise pour pouvoir await le démarrage dans dautres modules async function startServer() {
function startServer() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
logger.info("Demarrage du serveur Express..."); const server = app.listen(config.applicationPort, () => {
const serverInstance = app.listen(8080, () => { logger.info(`Express server ready at http://localhost:${config.applicationPort}`);
logger.info("Serveur Express pret sur http://localhost:8080"); resolve(server);
resolve(serverInstance); // Serveur prêt, on résout la promesse
}); });
// Gestion des erreurs serveur lors du démarrage
serverInstance.on("error", (err) => { server.on("error", (err) => {
logger.error("Erreur serveur Express :", err); logger.error("Express Server Error :", err);
reject(err); // Rejet de la promesse en cas derreur critique reject(err);
}); });
const gracefulExit = () => {
logSessionEnd();
server.close(() => {
logger.info("Express Server close cleanly.");
process.exit();
});
};
process.on("SIGINT", gracefulExit);
process.on("SIGTERM", gracefulExit);
}); });
} }
// Gestion propre de la fermeture du process pour logger la fin de session
process.on("SIGINT", () => { // Capture Ctrl+C (interruption)
logSessionEnd();
process.exit();
});
process.on("SIGTERM", () => { // Capture kill ou arrêt du process
logSessionEnd();
process.exit();
});
// Export de la fonction startServer pour permettre son appel depuis dautres fichiers
module.exports = { startServer }; module.exports = { startServer };
const { startRPC } = require("./discordRPC");
startRPC();

View File

@@ -0,0 +1,62 @@
const { execFile } = require("child_process");
const { userYtDlp } = require("../helpers/path");
const path = require("path");
const fs = require("fs");
const { logger } = require("../logger");
const { buildYtDlpArgs } = require("../helpers/buildArgs");
const notify = require("../helpers/notify")
function fetchDownload(options, listeners, speedListeners) {
return new Promise((resolve, reject) => {
const outputFolder = options.outputFolder || path.join(process.env.USERPROFILE, "Downloads", "Freedom Loader");
fs.mkdirSync(outputFolder, { recursive: true });
const args = buildYtDlpArgs({ ...options, outputFolder });
logger.info(`[yt-dlp args] ${args.join(" ")}`);
const child = execFile(userYtDlp, args);
child.on("error", err => reject(new Error(`YT-DLP Error : ${err.message}`)));
child.on("close", code => {
listeners.forEach(fn => fn("done"));
if (code === 0) resolve(outputFolder);
else reject(new Error(`YT-DLP failed with code : ${code}`));
});
child.stdout.on("data", data => {
data.toString().split("\n").forEach(line => {
if (!line.trim()) return;
logger.info(`[yt-dlp] ${line}`);
/* Barre de Chargement*/
if (line.startsWith("[download] Destination:")) listeners.forEach(fn => fn("reset"));
const match = line.match(/\[download\]\s+(\d+\.\d+)%/);
if (match) listeners.forEach(fn => fn(parseFloat(match[1])));
if (line.includes("MiB/s") || line.includes("KiB/s")) {
const match = line.match(/\d+(\.\d+)?[KM]?iB\/s/);
if (match) {
const speed = match[0];
speedListeners.forEach(fn => fn(speed));
}
}
if (line.match(/ERROR: Could not copy .* cookie database/i)) {
notify.notifyCookiesBrowserError();
}
});
});
child.stderr.on("data", data => {
data.toString().split("\n").forEach(line => line.trim() && logger.error(`[yt-dlp stderr] ${line}`));
});
});
}
module.exports = { fetchDownload };

View File

@@ -0,0 +1,51 @@
const { execFile } = require("child_process");
const { userYtDlp, denoPath } = require("../helpers/path");
const getUserBrowser = require("../helpers/getBrowser");
const { logger } = require("../logger");
function fetchInfo(url) {
const args = [
"--js-runtimes", `deno:${denoPath}`,
"--dump-single-json",
"--ignore-errors",
"--yes-playlist",
"--cookies-from-browser",`${getUserBrowser()}`,
"--extractor-args","youtube:player_client=default",
"--ignore-no-formats-error",
];
return new Promise((resolve, reject) => {
execFile(userYtDlp,[...args, url],
{ timeout: 30_000, // 30s, si jamais plus, abandon de la requête
maxBuffer: 10 * 1024 * 1024 }, // 10MO de réponse max (Par défaut: 200ko)
(error, stdout, stderr) => {
if (stderr) {
const lower = stderr.toLowerCase();
if (lower.includes("sign in to confirm") || lower.includes("failed to decrypt") || lower.includes("could not copy")) {
return reject(new Error(`Unable to extract cookies from ${getUserBrowser()}. Please log in to this browser and try again.`));
}
}
if (error) {
logger.error(`YT-DLP Error: ${error.message}`);
if (stderr) logger.debug(`stderr: ${stderr}`);
return reject(new Error("Unable to fetch Info."));
}
if (!stdout) return reject(new Error("No Data Received."));
try {
const data = JSON.parse(stdout);
resolve(data);
} catch (e) {
logger.error(`JSON Parsing Error: ${e.message}`);
reject(new Error("JSON unreadable."));
}
});
});
}
module.exports = { fetchInfo };

47
server/update.js Normal file
View File

@@ -0,0 +1,47 @@
const { autoUpdater } = require("electron-updater");
const { app, Notification, shell} = require("electron");
const { logger } = require("./logger");
function AutoUpdater() {
autoUpdater.on("update-available", (info) => {
logger.info(`New Version Available : ${info.version}`);
new Notification({
title: "Freedom Loader",
body: `New Version Available : ${info.version}. Application will restart`
}).show();
});
autoUpdater.on("update-downloaded", (info) => {
logger.info(`Update Downloaded : ${info.version}`);
new Notification({
title: "Freedom Loader",
body: `Update ${info.version} downloaded.`
}).on("click", () =>
shell.openExternal("https://github.com/MasterAcnolo/Freedom-Loader/releases/latest")
).show();
setTimeout(() => {
autoUpdater.quitAndInstall();
}, 5000);
});
autoUpdater.on("error", (err) => {
logger.error("Auto Update Error :", err.message);
new Notification({
title: "Freedom Loader - Update Error",
body: err.message
}).show();
});
app.whenReady().then(async () => {
try {
await autoUpdater.checkForUpdates();
logger.info("Update check completed");
} catch (err) {
logger.error("Error during update check :", err.message);
}
});
}
module.exports = { AutoUpdater };