504 Commits
1.2 ... 1.6.4

Author SHA1 Message Date
MasterAcnolo
187e3af559 fix: log transport names files local date 2026-09-27 20:03:25 +02:00
MasterAcnolo
e8c499fe23 fix: sendReport wrong IPC instance 2026-09-27 20:02:56 +02:00
MasterAcnolo
99b7164ff8 fix(update): don't check for update if the app is installed with SNAP or Flatpak 2026-09-27 16:23:28 +02:00
MasterAcnolo
d11c0f9839 add: .zed and OS Generated files to .gitignore, and better exclusion of IDE folder 2026-09-27 11:30:57 +02:00
MasterAcnolo
1e0b2affee update: bump app version to 1.6.4 since it's no longer a preview 2026-09-27 11:28:05 +02:00
MasterAcnolo
9975b2b583 refactor(update): add an UI indicator in frontend
disable OS native pop up menu and fix autoupdate crash on rpm and snap

#50
2026-09-25 21:08:18 +02:00
MasterAcnolo
b319a1e445 fix(ipc): send report IPC API 2026-09-16 22:16:59 +02:00
MasterAcnolo
4a64a40226 fix(front): settings menu transition 2026-09-16 20:17:42 +02:00
MasterAcnolo
2c0f5b315b refactor: toast notification style 2026-09-13 15:53:21 +02:00
MasterAcnolo
58b6637eeb feat: implement linter with config file 2026-09-13 12:06:33 +02:00
MasterAcnolo
8af5a4a383 update: bump app version to 1.6.4-preview 2026-09-13 11:56:03 +02:00
Axel Nicolas
23f04bafc3 Merge pull request #56 from MasterAcnolo/feature/send-logs
Feature/send logs
2026-09-13 11:54:43 +02:00
MasterAcnolo
35ffd7077e fix: onClick attribute on Report a Bug
(#53)
2026-09-13 11:44:37 +02:00
MasterAcnolo
408df0e2f6 feat: send logs feature that send logs to a distant service. Full integration in ui
+ expose logger method to frontEnd (#53)
2026-09-13 11:41:09 +02:00
MasterAcnolo
7dee94da6f refactor: extract CHANGELOG into a separated file. Should avoid characters issue 2026-09-11 11:01:12 +02:00
MasterAcnolo
cd43ae10fc fix: bump app version to 1.6.3 since it's no longer a preview 2026-09-10 21:36:33 +02:00
Axel Nicolas
6978277b4b Update codeql.yml 2026-09-10 15:13:02 +02:00
MasterAcnolo
b91c20d450 fix: release.sh release message ending 2026-09-10 15:09:54 +02:00
Axel Nicolas
014b1ad52a Update codeql.yml 2026-09-10 15:09:19 +02:00
Axel Nicolas
ec93d20a39 Create codeql.yml 2026-09-10 15:06:52 +02:00
MasterAcnolo
09c511fcb8 fix: docs in path.helpers 2026-09-10 15:01:50 +02:00
MasterAcnolo
f343fbd148 fix(discord-rpc): remove unusued code 2026-09-10 15:00:00 +02:00
Axel Nicolas
6d99d5f91c Update README.md 2026-09-10 13:55:09 +02:00
MasterAcnolo
0f0bb874bf feat: add logs when minimize and maximize (tray and not) 2026-09-10 12:01:07 +02:00
MasterAcnolo
b9267ec408 fix: enhance package.json with metadata used by electron-builder on Linux 2026-09-10 12:00:20 +02:00
MasterAcnolo
311bff6df5 refactor: systemTray window logic. Window reference is now dynamic and guarded against destroyed states. Minimize can now put window in the systemTray if option is active 2026-09-10 10:27:11 +02:00
MasterAcnolo
e4243a01ae fix: devMode logic 2026-09-10 10:25:03 +02:00
MasterAcnolo
52be9c6b4f update: electron from 43.3.0 to 44.3.0 2026-09-10 10:23:29 +02:00
MasterAcnolo
e541eb442a fix:
- replace unusued throw new Error by logger.error
- remove unusued variables isWindows, err
- remove unusued logFormat
- remove unusued imports logSessionEnd
2026-09-09 09:21:11 +02:00
MasterAcnolo
e2c2dde4a2 fix: README.md 2026-09-09 08:59:55 +02:00
MasterAcnolo
ed4022dfd1 fix: website link in README 2026-09-04 20:36:03 +02:00
MasterAcnolo
fcbc3f95ac fix: remove old and unusued icons 2026-09-03 20:56:04 +02:00
MasterAcnolo
f3293ef89a fix: stopServer message logic 2026-09-01 16:17:58 +02:00
MasterAcnolo
05e864b93a refactor: getBrowser function, and better firefox management on linux 2026-08-22 22:29:38 +02:00
MasterAcnolo
8ec901dc07 feat: centralize isPlaylistUrl function as a helper that could be upgraded in the future 2026-08-22 22:09:01 +02:00
MasterAcnolo
104500a82a fix: english miss spell 2026-08-21 20:20:12 +02:00
MasterAcnolo
3444f2fc0b Fix: default config in README that was not accurate 2026-08-20 19:39:21 +02:00
MasterAcnolo
86b595beff Fix: default config in README that was not accurate 2026-08-20 19:33:08 +02:00
MasterAcnolo
ed6efcf812 fix: toggle to false the createPlaylistFolder feature 2026-08-19 19:44:56 +02:00
MasterAcnolo
2c84ac7a70 fix: metadata comment displayed version 2026-08-19 17:31:36 +02:00
Axel Nicolas
3c649173a2 Update CONTRIBUTING.md 2026-08-16 10:56:54 +02:00
Axel Nicolas
b4d4ff2665 Merge pull request #47 from MasterAcnolo/refactor-back
Documentation Enhancement
2026-08-16 10:54:30 +02:00
MasterAcnolo
c2584b6df3 fix: remove SUPPORT section 2026-08-16 10:51:26 +02:00
MasterAcnolo
ec16b5209d fix: reduce size of package.json 2026-08-16 10:50:12 +02:00
MasterAcnolo
f2f1649493 add: small notes about build:all script 2026-08-16 10:50:01 +02:00
MasterAcnolo
e5076b0e0b remove: project structure from README 2026-08-16 10:37:42 +02:00
MasterAcnolo
4573917541 add: DEVELOPMENT.md file and refactor contributing and README 2026-08-16 10:36:36 +02:00
MasterAcnolo
62c109939a fix: bump version to 1.6.3-preview and add a check in release.sh, we could'nt publish a release if it's still marked as a preview 2026-08-16 10:03:27 +02:00
MasterAcnolo
b56ec1e62e refactor: some tweaks in .github md templates 2026-08-16 09:58:32 +02:00
MasterAcnolo
1a68c58ee3 refactor: extract shutdown logic in a function, rename server => serverInstance and now we stop the server smoothly 2026-08-16 09:47:25 +02:00
MasterAcnolo
39c4a69be1 add: notes in BINARIES.md about yt-dlp 2026-08-16 09:06:35 +02:00
MasterAcnolo
1993566040 fix: undo last commit 2026-08-15 22:41:17 +02:00
MasterAcnolo
2d19a35ef8 fix: missing element and rename snap to snapcraft in package.json 2026-08-15 22:31:51 +02:00
MasterAcnolo
02c39d53b6 update: version because it's no longer a preview ! 2026-08-15 22:23:31 +02:00
MasterAcnolo
26059aa436 fix: change dark.zip quotes 2026-08-15 21:46:33 +02:00
MasterAcnolo
524cee1bac refactor: topbar.js name and order. And remove 'customconfiguration' declaration in index.html 2026-08-15 21:31:07 +02:00
MasterAcnolo
4de05fcd52 fix: rename appVersion to displayVersionLabel to enhance readability 2026-08-15 21:30:32 +02:00
MasterAcnolo
c2158d99cd fix: remove duplicate import of /config 2026-08-15 21:30:03 +02:00
MasterAcnolo
d84e63fb56 remove: useless code that was used for disable theme on Linux 2026-08-15 18:48:26 +02:00
MasterAcnolo
12d77abbd3 fix: discordRPC error display. I voluntarily disabled raw error printing, because it could have false positive. Could be reenable on purpose 2026-08-15 17:27:50 +02:00
MasterAcnolo
398de8113d fix: the defaultThemesRessourcesPath variable was aiming to and old path, causing warn at every launch 2026-08-15 17:27:08 +02:00
MasterAcnolo
5b13f5d3e0 fix: yt-dlp mismatch in gitignore 2026-08-15 15:21:40 +02:00
MasterAcnolo
7d27889743 refactor: delete yt-dlp binary from repo. 2026-08-15 14:32:50 +02:00
MasterAcnolo
dd4c93e293 add: systemTray information in README and add EXPERIMENTAL Tag 2026-08-15 14:31:12 +02:00
MasterAcnolo
e0d426aa9f add: experimental tag to SYSTEMTRAY Features 2026-08-15 14:20:27 +02:00
MasterAcnolo
c51f3eb25e fix: autoupdater disabled on Flatpak and Snap env 2026-08-15 13:44:47 +02:00
MasterAcnolo
4f8600dbbc add: Aurelien reference in README 2026-08-15 13:20:49 +02:00
MasterAcnolo
63d30720a8 fix(tray): dynamically create/destroy tray icon on setting toggle
Previously, toggling the system tray in the settings updated the configuration but did not instantiate or destroy the tray immediately. This caused a desynchronization bug where the application could minimize to a non-existent tray, creating a ghost instance.

The `set-feature` IPC handler now intercepts the `systemTray` key to apply the state at runtime before persisting the config.
2026-08-15 13:20:49 +02:00
MasterAcnolo
acf7e67d0d feat: custom signature in every file download, as a comment 2026-08-15 13:20:49 +02:00
MasterAcnolo
1a1acb53d1 fix: tray behaviror. App will shutdown gracefully 2026-08-15 13:20:49 +02:00
MasterAcnolo
c153db0244 fix: rename "listeners" in "progressListeners", that enhance readability 2026-08-15 13:20:49 +02:00
MasterAcnolo
4fda512ab5 fix: disable systemTray by default. Could be reenable by default on a next release. 2026-08-15 13:20:49 +02:00
MasterAcnolo
66471ce5ce update: package-lock.json 2026-08-15 13:20:49 +02:00
MasterAcnolo
027aaf7564 fix: log session date format (W Sweden) 2026-08-15 13:20:49 +02:00
MasterAcnolo
7f8d018bcb fix: false warn about DownloadPath at startup 2026-08-15 13:20:49 +02:00
MasterAcnolo
6c517255e3 feat: system tray. Working on Windows, should be tested on Linux (GNOME) env. More features to systemTray coming soon 2026-08-15 13:20:49 +02:00
MasterAcnolo
dc7239379d fix: missing config reference in main.js and tray.js 2026-08-15 13:20:49 +02:00
MasterAcnolo
225ccf4d2d feat: allow app to minimize on the systemTray. Could be disable on purpose. 2026-08-15 13:20:49 +02:00
MasterAcnolo
ce2a0485ab fix: discord RPC on Linux, creating symlink between Discord and Freedom Loader at start if needed 2026-08-15 13:20:49 +02:00
MasterAcnolo
1690cc18b2 refactor: app display in front end. To make difference more obvious between bundled and not 2026-08-15 13:20:49 +02:00
MasterAcnolo
a8c2cf4c81 add: somes startup configuration for dev 2026-08-15 13:20:48 +02:00
MasterAcnolo
f1962f7cd3 update: bump version to 1.6.2 2026-08-15 13:20:48 +02:00
MasterAcnolo
245a463d52 fix: small fixes in README and update project structure 2026-08-15 13:20:48 +02:00
MasterAcnolo
b404e1ba8c add: nodemon configuration for developpment env 2026-08-15 13:20:48 +02:00
MasterAcnolo
b7e54806fd add: nodemon configuration for developpment env 2026-08-15 13:20:48 +02:00
MasterAcnolo
4df373d52b fix: release.sh version format 2026-08-15 13:20:48 +02:00
MasterAcnolo
73aa81673b refactor: enhance log format by adding color when app is run in an IDE or a Terminal 2026-08-15 13:20:48 +02:00
MasterAcnolo
df0952c231 refactor: changed /info router from POST to GET 2026-08-15 13:20:48 +02:00
MasterAcnolo
c67a1511a5 refactor: moved "Auto Fetch Info" settings to advanced settings 2026-08-15 13:20:48 +02:00
MasterAcnolo
d3058a66cf fix: log start format and smalls coms fix 2026-08-15 13:20:48 +02:00
MasterAcnolo
90939e968a update: dependencies 2026-08-15 13:20:48 +02:00
MasterAcnolo
ec83c20e16 feat: automatic release creation with scripts 2026-08-15 13:20:48 +02:00
MasterAcnolo
3f3696e54e refactor: notify.helpers.js logic. Add a timeout and a better notification management to avoid race condition on Unix Systems 2026-08-15 13:20:48 +02:00
MasterAcnolo
321309e294 refactor: notify.helpers.js logic. Add a timeout and a better notification management to avoid race condition on Unix Systems 2026-08-15 13:20:48 +02:00
MasterAcnolo
b9334461bf fix: enhance readme with keepPlaylistOrder information. And fix some typo to fix Linux Env 2026-08-15 13:20:48 +02:00
MasterAcnolo
e09f1a8913 feat: Keep Playlist Order features. Add index before title if it's a playlist and we activate this feature. 2026-08-15 13:20:48 +02:00
MasterAcnolo
bbb0b58d17 fix: unit test wrong folder 2026-08-15 13:20:48 +02:00
MasterAcnolo
47f5b0efef fix: bump version to 1.6.1 and update electron to 43.3.0 2026-08-15 13:20:48 +02:00
MasterAcnolo
6b7645804a fix: force artifact name for exe 2026-08-15 13:20:48 +02:00
MasterAcnolo
9f85acbb59 fix: refresh config at every download. Used by CreatePlaylistFolder 2026-08-15 13:20:48 +02:00
MasterAcnolo
32eb849808 fix: exclude dist, release and build folder from jest config 2026-08-15 13:20:48 +02:00
MasterAcnolo
bc1c62dc35 fix: better dev mode comparaison, useful for tests 2026-08-15 13:20:48 +02:00
MasterAcnolo
72c152d964 feat: unit tests for buildArgs and Notify 2026-08-15 13:20:48 +02:00
MasterAcnolo
596a06a59d fix: snap Store package.json arguments 2026-08-15 13:20:48 +02:00
MasterAcnolo
6d4692486e fix: snap Store mention in SECURITY.md 2026-08-15 13:20:48 +02:00
MasterAcnolo
7e162debcc refactor: rpm package publish 2026-08-15 13:20:48 +02:00
MasterAcnolo
ae6b849aac refactor: rpm package publish 2026-08-15 13:20:48 +02:00
MasterAcnolo
bb8ddabb24 refactor: rpm package publish 2026-08-15 13:20:47 +02:00
MasterAcnolo
620ce7a40c fix: enhance package.json and rename linux package to freedom-loader 2026-08-15 13:20:47 +02:00
MasterAcnolo
9892c6a7dc fix: readme missing items and [!TIP] 2026-08-15 13:20:47 +02:00
MasterAcnolo
bc6283ec5c refactor: security.md 2026-08-15 13:20:47 +02:00
MasterAcnolo
b1377fe21e feat: enhance logger by displaying systeminfo at start of the app 2026-08-15 13:20:47 +02:00
MasterAcnolo
e62ef6bd0c sync: update dependencies and this is ready for windows 2026-08-15 13:20:47 +02:00
MasterAcnolo
49650ecb36 sync: to work on windows 2026-08-15 13:20:47 +02:00
MasterAcnolo
4a56af2491 fix: correct use of logger for path.helpers.js 2026-08-15 13:20:47 +02:00
MasterAcnolo
3cc9f90fdd fix: bring back theme features for Linux 2026-08-15 13:20:47 +02:00
MasterAcnolo
3b555d465e fix: bring back theme features for Linux 2026-08-15 13:20:47 +02:00
MasterAcnolo
857ff773b0 feat: publish package to fedora copr, add script to automaticaly build and upload the file.
Enhance readme and fix auto updater issue
2026-08-15 13:20:47 +02:00
MasterAcnolo
5f2bba2c85 add: BINARIES.md readme file. About how to install the binaries for dev mode 2026-08-15 13:20:47 +02:00
MasterAcnolo
c9a5820076 fix: don't show dialog box if no previous version is available. 2026-08-15 13:20:47 +02:00
MasterAcnolo
336aa33485 add: dark.zip and white.zip theme to the repo 2026-08-15 13:20:47 +02:00
MasterAcnolo
76ce2a8019 fix: small code quality and mismatch. 2026-08-15 13:20:47 +02:00
MasterAcnolo
5bdd13ad8e feat: support Linux. Refactor resources architecture. Create build script
Currently, themes are disabled and firefox notification too.
2026-08-15 13:20:46 +02:00
MasterAcnolo
14b7d944d1 add: default theme applied in app. If no theme is there 2026-08-15 13:20:46 +02:00
MasterAcnolo
3835806e1a fix: missing enableHardwareAcceleration settings in readme 2026-08-15 13:20:46 +02:00
MasterAcnolo
398e9acf66 fix: revert version to 1.5.1 in package.json and README.md 2026-08-15 13:20:46 +02:00
MasterAcnolo
2f203a0bc1 feat: add Devtron in dev mode
remove unused settings item
add setting to disable hardware acceleration (enabled by default)
optimize main.js startup loading
2026-08-15 13:20:46 +02:00
MasterAcnolo
acce91bed8 fix: display "dev" in app version if the app is not packaged 2026-08-15 13:20:46 +02:00
MasterAcnolo
6b33662f79 fix: fix width for form 2026-08-15 13:20:46 +02:00
MasterAcnolo
2805f5f0db refactor: add full documentation and fix theme loading robustness
- Add JSDoc comments across frontend and backend modules
- Improve code readability and maintainability with consistent documentation style
- Document Electron IPC handlers, UI utilities, and theme system
- Add missing function/class documentation in theme loader and app services
- Minor structural improvements and cleanup across Electron main process modules
- Minor fixes on variable names
2026-08-15 13:20:46 +02:00
MasterAcnolo
7ae0ef9a99 fix: app startup. Can't start another instance now. 2026-08-15 13:20:46 +02:00
MasterAcnolo
83dc0a3f2f fix: rename localMode => devMode for clarity 2026-08-15 13:20:46 +02:00
MasterAcnolo
3179b52908 fix: rename localMode => devMode for clarity 2026-08-15 13:20:46 +02:00
MasterAcnolo
9564260c57 fix: update dependencies 2026-08-15 13:20:46 +02:00
MasterAcnolo
d7c2339f61 update: version for 1.6.0 2026-08-15 13:20:46 +02:00
MasterAcnolo
12a6709391 fix: update dependencies 2026-08-15 13:20:46 +02:00
MasterAcnolo
6b625326a4 Fix: remove duplication roadmap (now full in website) 2026-08-15 13:20:46 +02:00
MasterAcnolo
8c40ac2b1c Fix: Configuration File Location 2026-08-15 13:20:46 +02:00
MasterAcnolo
71081163a6 Fix: missing options in readme 2026-08-15 13:20:46 +02:00
MasterAcnolo
4ab6257b39 Update Workshop link to GitHub repository 2026-08-15 13:20:45 +02:00
MasterAcnolo
0d0d60c692 Feat: Update README with detailed project structure and architecture overview 2026-08-15 13:20:45 +02:00
MasterAcnolo
504149a220 Fix: Remove emoji from error messages in download and info controllers 2026-08-15 13:20:45 +02:00
MasterAcnolo
a6ac2333c6 Feat: Add link to the most recent roadmap in README 2026-08-15 13:20:45 +02:00
MasterAcnolo
eb232de34e Fix: Remove z-index from container styles and add z-index to settings button 2026-08-15 13:20:45 +02:00
MasterAcnolo
cda79bd28f Feat: Add section for submitting community themes in README 2026-08-15 13:20:45 +02:00
MasterAcnolo
78e0bcab37 Fix: Save Themes inside "AppData" 2026-08-15 13:20:45 +02:00
MasterAcnolo
dbe313fac4 Feat: Add dynamic icon path for main window based on local mode 2026-08-15 13:20:45 +02:00
MasterAcnolo
2316b73adb Feat: Implement notification feature for download completion 2026-08-15 13:20:45 +02:00
MasterAcnolo
62140be696 Fix: Update links to Freedom-Loader-Site in documentation and IPC handlers 2026-08-15 13:20:45 +02:00
MasterAcnolo
aa3cb24b20 Feat: Add related repository links for website and workshop in README 2026-08-15 13:20:45 +02:00
MasterAcnolo
cbb847800a Fix: Update link to Freedom Loader Workshop in README 2026-08-15 13:20:45 +02:00
MasterAcnolo
8643ed2867 Fix: Correct typo in Workshop badge link in README 2026-08-15 13:20:45 +02:00
MasterAcnolo
e712163c14 Feat: Add Theme Workshop section to README and update features list 2026-08-15 13:20:45 +02:00
MasterAcnolo
edcd7c8f70 Fix: Update external links for website and wiki in IPC handlers 2026-08-15 13:20:45 +02:00
MasterAcnolo
e6b1c8ba62 Feat: Add "Theme Workshop" button and associated IPC handler for workshop access 2026-08-15 13:20:45 +02:00
MasterAcnolo
d90ae6f236 Feat: Enhance theme settings with new button styles for open theme and JSON options 2026-08-15 13:20:45 +02:00
MasterAcnolo
9d4eeca7ee Feat: Implement playlist download count tracking during downloads with UI updates 2026-08-15 13:20:45 +02:00
MasterAcnolo
68c0f24ebb Fix: Adjust z-index for reference 2026-08-15 13:20:45 +02:00
MasterAcnolo
ef8fe131f6 Fix: Moving Custom Top BAr Settings into advanced 2026-08-15 13:20:45 +02:00
MasterAcnolo
596a563e06 Feat: Add createPlaylistFolders feature to organize downloads into dedicated folders with toggle settings 2026-08-15 13:20:45 +02:00
MasterAcnolo
f583a006f2 Fix: Update save path display to include title attribute for better accessibility 2026-08-15 13:20:45 +02:00
MasterAcnolo
3e2b7e8190 Fix: Enhance error handling for playlist fetch failures by retrying with single video 2026-08-15 13:20:45 +02:00
MasterAcnolo
4b725bed8b Fix: Adjust stop button hover scale effect for better user experience 2026-08-15 13:20:45 +02:00
MasterAcnolo
cf596ddb13 Fix: Update button text 2026-08-15 13:20:45 +02:00
MasterAcnolo
6812296dbc Refactor: auto-update functionality with user prompts and download progress 2026-08-15 13:20:45 +02:00
MasterAcnolo
94003135fd Feat: Enhance download experience with progress indicators and toast notifications 2026-08-15 13:20:45 +02:00
MasterAcnolo
95688a5dff Fix: IPC button not working 2026-08-15 13:20:44 +02:00
MasterAcnolo
592cc3314a Feat: Implement download cancellation feature with stop button and progress handling 2026-08-15 13:20:44 +02:00
MasterAcnolo
67b058e1ac Feat: Add theme reloading functionality with refresh button in settings panel 2026-08-15 13:20:44 +02:00
MasterAcnolo
264ac071df Fix: Remove themes from the theme order array 2026-08-15 13:20:44 +02:00
MasterAcnolo
0bd3e10094 Feat: Implement splash screen with progress indicators during startup 2026-08-15 13:20:44 +02:00
MasterAcnolo
c656b30172 Fix: Display custom these with a basic order 2026-08-15 13:20:44 +02:00
MasterAcnolo
3d1920e6e9 Fix: Remove empty title tag from index.html and unused app styles from styles.css 2026-08-15 13:20:44 +02:00
MasterAcnolo
372fc921f0 Feat: Add theme folder access functionality in UI 2026-08-15 13:20:44 +02:00
MasterAcnolo
c32e382685 Feat: Custom Theme features and clean the old one 2026-08-15 13:20:44 +02:00
MasterAcnolo
4d77e843af Fix: Theme Saving 2026-08-15 13:20:44 +02:00
MasterAcnolo
3c30e49523 fix: move config to AppData to prevent settings loss on update, add config.dev.json for dev environment 2026-08-15 13:20:44 +02:00
MasterAcnolo
6b4310500e Refactor: split main.js into app/ modules 2026-08-15 13:20:44 +02:00
MasterAcnolo
252618dfd1 Update: Readme version and file structure 2026-08-15 13:20:44 +02:00
MasterAcnolo
fb7f0329b6 Fix: Remove unusued element 2026-08-15 13:20:44 +02:00
MasterAcnolo
4feea9c530 Fix: Update theme JSON structure for audio and button settings 2026-08-15 13:20:44 +02:00
MasterAcnolo
e8fe990e01 Refactor: Update CSS variables
Fix Progress bar
2026-08-15 13:20:44 +02:00
MasterAcnolo
086247068e Refactor: Restructure HTML layout and enhance CSS styles for improved UI 2026-08-15 13:20:44 +02:00
MasterAcnolo
48a218efca Fix: Settings Secondary Color in LIght THeme 2026-08-15 13:20:44 +02:00
MasterAcnolo
24fc685bf6 Fix: Remove unusued pictures 2026-08-15 13:20:44 +02:00
MasterAcnolo
731cdc38ec Update: App Version in ReadME 2026-08-15 13:20:44 +02:00
MasterAcnolo
391c343b6a Update: Dependencies 2026-08-15 13:20:44 +02:00
MasterAcnolo
cbe689e38e Fix: Wrong files Path 2026-08-15 13:20:44 +02:00
MasterAcnolo
1ac035e160 Update: YT-DLP 2026-08-15 13:20:43 +02:00
MasterAcnolo
fb8e2acda0 Refactor: Suffixe in helpers/ 2026-08-15 13:20:43 +02:00
MasterAcnolo
3dd9f53e80 Fix: Remove unusued modules and disable devTools when packaged 2026-08-15 13:20:43 +02:00
MasterAcnolo
269b9e69ba Fix: Update YT-DLP 2026-08-15 13:20:43 +02:00
MasterAcnolo
5f3b2a80a9 Fix: Increment app version 2026-08-15 13:20:43 +02:00
MasterAcnolo
fdda78c802 Fix: Dependencies 2026-08-15 13:20:43 +02:00
MasterAcnolo
a82e8ceefd Docs: Update ReadME 2026-08-15 13:20:42 +02:00
MasterAcnolo
c9dd09d341 Fix: Update Dependencies 2026-08-15 13:20:42 +02:00
MasterAcnolo
78191d27ad Fix: Increment App Version 2026-08-15 13:20:42 +02:00
MasterAcnolo
33290e029b Feat: You can now fetch image from all website. 2026-08-15 13:20:42 +02:00
MasterAcnolo
245cf01a43 Refactor: Translate all comms in English 2026-08-15 13:20:42 +02:00
MasterAcnolo
2689424d01 Fix: Remove Discord RPC Update. It's useless and take performance for nothing. 2026-08-15 13:20:42 +02:00
MasterAcnolo
bfb0873b3d Fix: Reduce Line Complexity featuresPath 2026-08-15 13:20:42 +02:00
MasterAcnolo
3a11395301 Fix: Try to re run workflow 2026-08-15 13:20:42 +02:00
MasterAcnolo
5dc7c229b0 Fix: Undo Path Validation for coming back on a classic one 2026-08-15 13:20:42 +02:00
MasterAcnolo
95497d252f Fix: Centralize default download path and enhance path validation logic 2026-08-15 13:20:42 +02:00
MasterAcnolo
784834cbbc Fix: Correct windowMs value in rate limit configuration to 15 minutes 2026-08-15 13:20:42 +02:00
MasterAcnolo
b6d77444e1 Fix: Refactor conditional statements for starting Discord RPC and Auto Update 2026-08-15 13:20:42 +02:00
MasterAcnolo
dd4d770102 Fix: Correct comment for rate limit window duration in rateLimit.js 2026-08-15 13:20:42 +02:00
MasterAcnolo
a374993ab1 Fix: Uncomplete comments and remove repeated code 2026-08-15 13:20:42 +02:00
MasterAcnolo
d99fad1c3d Fix: Update full changelog link format in release template 2026-08-15 13:20:42 +02:00
MasterAcnolo
9f4563dbd8 Fix: Improve browser detection logging and error handling 2026-08-15 13:20:42 +02:00
MasterAcnolo
5bb6b6c165 Fix: Centralize resource paths and icons 2026-08-15 13:20:42 +02:00
MasterAcnolo
f8e346b3cb Fix: Small coms in AppVersion.js 2026-08-15 13:20:42 +02:00
MasterAcnolo
ee685695de Fix: App Not Stopping correctly + Better Logs for end :) 2026-08-15 13:20:42 +02:00
MasterAcnolo
3e2ff74876 Fix: We are not on apple system. Don't care about Darwin 2026-08-15 13:20:42 +02:00
MasterAcnolo
bbdb6a37fb Fix: Missing semi colon, Better Folder Creation, And remove the fucking bad code who was repeat too much (wtf i did) 2026-08-15 13:20:42 +02:00
MasterAcnolo
7b55a13ce6 Fix: Update version numbers to 1.4.4 in package.json and ReadME 2026-08-15 13:20:42 +02:00
MasterAcnolo
a4efedbe19 Fix: Update playlist badge text to English 2026-08-15 13:20:42 +02:00
MasterAcnolo
c3d43cafbf Fix: Update URL check to correctly identify playlist links 2026-08-15 13:20:42 +02:00
MasterAcnolo
bf91df3a27 Feat: Add Better playlist layout 2026-08-15 13:20:42 +02:00
MasterAcnolo
28928ec174 Fix: Add Small Comms for clarity 2026-08-15 13:20:42 +02:00
MasterAcnolo
f5c51e254a Fix: Remove Unusued Code 2026-08-15 13:20:42 +02:00
MasterAcnolo
36deddd823 Fix: Big mistake when estimating video format (? -> &) 2026-08-15 13:20:42 +02:00
MasterAcnolo
f6eca7f3f6 Fix: Update Dependencies 2026-08-15 13:20:42 +02:00
MasterAcnolo
ccf9e5bc03 Fix: Update ReadME App Version 2026-08-15 13:20:42 +02:00
MasterAcnolo
082f19bda4 Feat: Add Paste BTN URL Input 2026-08-15 13:20:42 +02:00
MasterAcnolo
67ecd28aeb Fix: Increment App version 2026-08-15 13:20:41 +02:00
MasterAcnolo
02d60bddd2 Fix: Update release badge to version 1.4.2 2026-08-15 13:20:41 +02:00
MasterAcnolo
5236165b47 Fix: 1.4.2 Version 2026-08-15 13:20:41 +02:00
MasterAcnolo
044091a9c3 Fix: yt-dlp update 2026-08-15 13:20:41 +02:00
MasterAcnolo
a04730243d Fix: Dependencies Vulnerability 2026-08-15 13:20:41 +02:00
MasterAcnolo
b9fa931cd7 Potential fix for code scanning alert no. 5: Type confusion through parameter tampering
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-08-15 13:20:25 +02:00
MasterAcnolo
c1c1f9a870 Update: YT-DLP 2026-08-15 13:20:24 +02:00
MasterAcnolo
538cb6b51a Update acknowledgments in README.md
Added acknowledgment for Deno and Jacques Chirac.
2026-08-15 13:20:24 +02:00
MasterAcnolo
5319bee9ee Add: Additional dependencies section in README with required binaries 2026-08-15 13:20:24 +02:00
MasterAcnolo
1edec6509e Mark completed tasks in the roadmap as strikethrough
Updated the roadmap to mark completed tasks as strikethrough.
2026-08-15 13:20:24 +02:00
MasterAcnolo
22a3eb7524 Fix: ReadME 2026-08-15 13:20:24 +02:00
MasterAcnolo
b5d058902c Fix: Bug Report Template 2026-08-15 13:20:24 +02:00
MasterAcnolo
0e4134b172 Fix: Security 2026-08-15 13:20:24 +02:00
MasterAcnolo
02973067b1 Fix: Security, Contributing, Code Of Conduct 2026-08-15 13:20:24 +02:00
MasterAcnolo
b1064cceba Feat: Templates for PR and Issues 2026-08-15 13:20:24 +02:00
MasterAcnolo
536fb3678e Update GitHub username for SpicyFire21 2026-08-15 13:20:24 +02:00
MasterAcnolo
dc3f19e95e Enhance README with detailed features list
Expanded features section with detailed descriptions of new functionalities including multi-format downloads, video information preview, quality selection, and more.
2026-08-15 13:20:24 +02:00
MasterAcnolo
a575416801 Revise README for improved structure and clarity
Updated README with new structure and content for clarity and organization.
2026-08-15 13:20:24 +02:00
MasterAcnolo
e74ef502d4 Fix: ReadME Version 1.4.1 2026-08-15 13:20:24 +02:00
MasterAcnolo
ee7c4a1f09 Add: App Version for 1.4.1 2026-08-15 13:20:24 +02:00
MasterAcnolo
8771291e61 Fix: Discord RPC not Stoping #26 2026-08-15 13:20:24 +02:00
MasterAcnolo
769753f25e Fix: Auto Update mispelling (Am a monkey) 2026-08-15 13:20:24 +02:00
MasterAcnolo
455d51e7cd Add: Video Codec 2026-08-15 13:20:24 +02:00
MasterAcnolo
5ef9f42bd7 Fix: Remove unusued "Settings.svg" icon 2026-08-15 13:20:24 +02:00
MasterAcnolo
0c8322db13 Fix: Try to force AAC Audio Codec 2026-08-15 13:20:24 +02:00
MasterAcnolo
e653a7f82f Fix: Check Info config every request 2026-08-15 13:20:24 +02:00
MasterAcnolo
e5e9d90552 Fix: Config not working propely 2026-08-15 13:20:24 +02:00
MasterAcnolo
6a14056804 Fix: Remove Unusued Import 2026-08-15 13:20:24 +02:00
MasterAcnolo
1dd9bd59b5 Fix: Reference position bottom 2026-08-15 13:20:24 +02:00
MasterAcnolo
fb34eb9f80 Because why not ? 2026-08-15 13:20:24 +02:00
MasterAcnolo
ccbfccb76a Fix: Remove unusued files 2026-08-15 13:20:24 +02:00
MasterAcnolo
416f3ac4c5 Add: Settings theme color 2026-08-15 13:20:23 +02:00
MasterAcnolo
df1047c21e Fix: Neon Theme rename 2026-08-15 13:20:23 +02:00
MasterAcnolo
0a2fe5500c Feat: Settings Panel 2026-08-15 13:20:23 +02:00
MasterAcnolo
698d75184c Sync: Synchro Settings Panel 2026-08-15 13:20:23 +02:00
MasterAcnolo
2fa8951f67 Add: Settings Icon 2026-08-15 13:20:23 +02:00
MasterAcnolo
a480cecf39 Fix: Update Dependencies 2026-08-15 13:20:23 +02:00
MasterAcnolo
558408b12b Fix: Indent Index.html 2026-08-15 13:20:23 +02:00
MasterAcnolo
95909e7ba4 Fix: Update ReadME Banner Redirection 2026-08-15 13:20:23 +02:00
MasterAcnolo
c70218a966 Fix: Remove Unusued Dependencies 2026-08-15 13:20:23 +02:00
MasterAcnolo
de352039ea Fix: Default Theme Info Color 2026-08-15 13:20:23 +02:00
MasterAcnolo
0169f61614 FIx: Updated Appliction Version because i did too much work to be a "minor release" 2026-08-15 13:20:23 +02:00
MasterAcnolo
da2fd72984 Fix: Put config.json file into config folder for better readability + Fix Yt-DLP Path 2026-08-15 13:20:23 +02:00
MasterAcnolo
1c43c795af Fix: Remove unusued import server.js 2026-08-15 13:20:23 +02:00
MasterAcnolo
e7fa3d9639 Fix: Update Dependencies 2026-08-15 13:20:23 +02:00
MasterAcnolo
ecffb3eab1 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-08-15 13:20:23 +02:00
MasterAcnolo
e99b16eac6 Add: Better logs. Write logs when a download is init 2026-08-15 13:20:23 +02:00
MasterAcnolo
d3478b18f7 Fix: Remove Useless Console.log 2026-08-15 13:20:23 +02:00
MasterAcnolo
42a235d916 Feat: Add Configuration Button. user can edit config.json as wanted. Linked and IPC 2026-08-15 13:20:23 +02:00
MasterAcnolo
f716316244 Fix: Remove unusued Variable 2026-08-15 13:20:23 +02:00
MasterAcnolo
f059361b4e Feat: Add CustomVideo Codec Choice with verification in config.json 2026-08-15 13:20:23 +02:00
MasterAcnolo
5b4cabdbd0 Feat: Add Newline YT-DLP Args. Add Toggle Switches for verbose, autodownloadplaylist + Fix Thumbail misspell 2026-08-15 13:20:23 +02:00
MasterAcnolo
429720ae06 Fix: Correct Config.json file for packaged version 2026-08-15 13:20:23 +02:00
MasterAcnolo
4f1f4c948d 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-08-15 13:20:23 +02:00
MasterAcnolo
5855d7a933 Feat: Package Update + Add NSIS Installer Banner 2026-08-15 13:20:23 +02:00
MasterAcnolo
4c85c138b8 Feat: Path Verification (Security #4) 2026-08-15 13:20:23 +02:00
MasterAcnolo
9b10fab27b Fix: ReadME Badge Version 2026-08-15 13:20:23 +02:00
MasterAcnolo
0933cae8e1 Fix: Increment App Version for 1.3.2 2026-08-15 13:20:23 +02:00
MasterAcnolo
d16eed54ea 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-08-15 13:20:22 +02:00
MasterAcnolo
83f7f843b0 Feat: Add Codec H264 to vidéo output 2026-08-15 13:20:22 +02:00
MasterAcnolo
a02264b8c9 Update: Update Script + Dependencies 2026-08-15 13:20:22 +02:00
MasterAcnolo
2eda434a78 Add: Some Variables in config.js. For Modules 2026-08-15 13:20:22 +02:00
MasterAcnolo
b8542b9c51 Fix: Remove Unsued Import in Controller 2026-08-15 13:20:22 +02:00
MasterAcnolo
c8376464e5 Add: Rate Limit to front rendering. Using Components for reusability 2026-08-15 13:20:22 +02:00
MasterAcnolo
d41006e5cb Fix: Error Handling Download Controller 2026-08-15 13:20:22 +02:00
MasterAcnolo
b3c13e59a7 Fix: Error Handling Info Controller 2026-08-15 13:20:22 +02:00
MasterAcnolo
d54120ba5a Fix: Security #5 2026-08-15 13:20:22 +02:00
MasterAcnolo
f1ce53a833 Add pull request template for consistent feature submissions 2026-08-15 13:20:22 +02:00
MasterAcnolo
2321acfe7d Replace feature and version templates in PULL_REQUEST_TEMPLATE directory 2026-08-15 13:20:22 +02:00
MasterAcnolo
c07ff64177 Add feature issue template for improved feature requests 2026-08-15 13:20:22 +02:00
MasterAcnolo
1ffc213fdd Add version template for changelog and roadmap 2026-08-15 13:20:22 +02:00
MasterAcnolo
aba339116c Create SECURITY.md 2026-08-15 13:20:22 +02:00
MasterAcnolo
0e3e65d36f 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.
2026-08-15 13:20:22 +02:00
MasterAcnolo
d4aa1f3b94 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.
2026-08-15 13:20:22 +02:00
MasterAcnolo
3af1542ccd Update issue templates 2026-08-15 13:20:22 +02:00
MasterAcnolo
bfeb4da529 refactor: Remove setupMenu function and its invocation from app initialization 2026-08-15 13:20:22 +02:00
MasterAcnolo
28f7459d9c feat: Add version badge 2026-08-15 13:20:22 +02:00
MasterAcnolo
9a59479b5c style: Update z-index for theme switcher and topbar; add custom scrollbar styles 2026-08-15 13:20:22 +02:00
MasterAcnolo
07387351da fix: Update topbar width to use 100% instead of 100vw for better responsiveness 2026-08-15 13:20:22 +02:00
MasterAcnolo
295971ef68 Fix: Update release badge to version 1.3.1 in README 2026-08-15 13:20:22 +02:00
MasterAcnolo
967583894f feat: Implement topbar with custom window controls and actions 2026-08-15 13:20:22 +02:00
MasterAcnolo
170bfc13f6 Fix: Startup verif + Update Package 2026-08-15 13:20:22 +02:00
MasterAcnolo
54a81b8e81 Refactor badge links in README for improved layout and consistency 2026-08-15 13:20:22 +02:00
MasterAcnolo
34343ef692 Refactor dependencies section in README for improved layout and styling 2026-08-15 13:20:22 +02:00
MasterAcnolo
3152ae17dd Refactor layout of dependencies section in README for improved styling 2026-08-15 13:20:22 +02:00
MasterAcnolo
750b07b69a Refactor badge links in README for improved readability 2026-08-15 13:20:22 +02:00
MasterAcnolo
118e4aa6a7 Update version to 1.3.1 and add update script to package.json 2026-08-15 13:20:21 +02:00
MasterAcnolo
776105ce48 Add native dependencies check and error handling on startup 2026-08-15 13:20:21 +02:00
MasterAcnolo
838f5481b6 Bump version to 1.3.1 and fix Discord RPC state URL; enhance update notification with clickable link to releases 2026-08-15 13:20:21 +02:00
MasterAcnolo
2a66728f40 Merge Main 2026-08-15 13:20:21 +02:00
MasterAcnolo
07899d0a43 Update Freedom Loader link in README.md 2026-08-15 13:20:21 +02:00
MasterAcnolo
2dd4f016a7 Size Reduce README 2026-08-15 13:20:20 +02:00
MasterAcnolo
fdeca41ec7 Size Reduce README 2026-08-15 13:20:20 +02:00
MasterAcnolo
9d2ddc33df ReadME Firefox 2026-08-15 13:20:20 +02:00
MasterAcnolo
c17ab8c934 ReadME Firefox 2026-08-15 13:20:20 +02:00
MasterAcnolo
12464f84dd Added Website Top Menu Section 2026-08-15 13:20:20 +02:00
MasterAcnolo
b6583dc7ee Update Banner 2026-08-15 13:20:20 +02:00
MasterAcnolo
9fadb4339e Fix Wiki Path + Icon + Bring Back Spicy Theme + New banner (Logo) 2026-08-15 13:20:20 +02:00
MasterAcnolo
7c3fa0f69d Better RPC 2026-08-15 13:20:20 +02:00
MasterAcnolo
c27da25a2b Bring Back Spicy Fire Theme from darkness 2026-08-15 13:20:20 +02:00
MasterAcnolo
f36e456d1d ReadME Change Logo 2026-08-15 13:20:20 +02:00
MasterAcnolo
0af4c6e0a3 ReadME Fix 2026-08-15 13:20:20 +02:00
MasterAcnolo
4ee4053faf New Logo 2026-08-15 13:20:20 +02:00
MasterAcnolo
6f91a77692 main fix 2026-08-15 13:20:20 +02:00
MasterAcnolo
937866c60c ReadME 2026-08-15 13:20:20 +02:00
MasterAcnolo
4cc2dbe3b7 ReadME 2026-08-15 13:20:20 +02:00
MasterAcnolo
20f887d07f ReadME 2026-08-15 13:20:20 +02:00
MasterAcnolo
4dbbdb94d5 ReadME 2026-08-15 13:20:19 +02:00
MasterAcnolo
3b01c1d560 ReadME 2026-08-15 13:20:19 +02:00
MasterAcnolo
f86aaaff46 English Index.html audio 2026-08-15 13:20:19 +02:00
MasterAcnolo
778218aaca ReadME 2026-08-15 13:20:19 +02:00
MasterAcnolo
be44762483 ReadME 2026-08-15 13:20:19 +02:00
MasterAcnolo
689aa72e55 ReadME 2026-08-15 13:20:19 +02:00
MasterAcnolo
2d03c785ac ReadME 2026-08-15 13:20:19 +02:00
MasterAcnolo
37913b4edb ReadME 2026-08-15 13:20:19 +02:00
MasterAcnolo
399a50e64d ReadME 2026-08-15 13:20:19 +02:00
MasterAcnolo
50c93493a7 ReadME 2026-08-15 13:20:19 +02:00
MasterAcnolo
9206091cda English 2026-08-15 13:20:19 +02:00
MasterAcnolo
db5e3ec9ec Anglais Fetch Info.js + Removed Files Check 2026-08-15 13:20:19 +02:00
MasterAcnolo
3a9d4ecb80 Change Language Main.js 2026-08-15 13:20:19 +02:00
MasterAcnolo
57a9634ba6 Better Log Components Path 2026-08-15 13:20:19 +02:00
MasterAcnolo
fea24e4cb8 Translate English 2026-08-15 13:20:19 +02:00
MasterAcnolo
e5d06b25f4 NO AID HERE 2026-08-15 13:20:19 +02:00
MasterAcnolo
cd71d9ecbd NO AID HERE 2026-08-15 13:20:19 +02:00
MasterAcnolo
fcc58e54d3 Notify Logic 2026-08-15 13:20:19 +02:00
MasterAcnolo
8208bab1b5 Info Loading Bar 2026-08-15 13:20:19 +02:00
MasterAcnolo
67e42d6e78 Change Error Info Box + Clean HTML 2026-08-15 13:20:19 +02:00
MasterAcnolo
c5aefaef7f SongBird Theme + Playlist Background For Every Theme 2026-08-15 13:20:19 +02:00
MasterAcnolo
3d788285d7 Download Speed 2026-08-15 13:20:19 +02:00
MasterAcnolo
dec4f721cf Clean Code 2026-08-15 13:20:19 +02:00
MasterAcnolo
f0e91ef0a9 Loading Bar taskbar 2026-08-15 13:20:19 +02:00
MasterAcnolo
c53c9b9d43 Using Download Route MVC Model 2026-08-15 13:20:19 +02:00
MasterAcnolo
7c8b37a35d Push 2026-08-15 13:20:18 +02:00
MasterAcnolo
17e5157d9a Using MVC Model + AutoDeteck Package + Fix Download/Info 2026-08-15 13:20:18 +02:00
MasterAcnolo
027572d666 Fix Deno Not Using on Info.js 2026-08-15 13:20:18 +02:00
MasterAcnolo
2ed4d99c6f Deno Integré + Progress Bar Fix + Small Clean 2026-08-15 13:20:18 +02:00
MasterAcnolo
8fd3404064 Removed Cyberpunk | Spicy | Vilbrequin Theme, Info Fix, Fix Progress Bar. Centralized Ressources 2026-08-15 13:20:18 +02:00
MasterAcnolo
fcbc3b3b77 ProgressBar + Playlist 2026-08-15 13:20:18 +02:00
MasterAcnolo
7ea3e316ac Playlist Card + Safe Args 2026-08-15 13:20:18 +02:00
MasterAcnolo
cb20028119 UpperCase 2026-08-15 13:20:18 +02:00
MasterAcnolo
9f5a63042d Autoupdate + FreedomKiller Démarrage 2026-08-15 13:20:18 +02:00
MasterAcnolo
0708edeabc Fix AutoUpdate 2026-08-15 13:20:18 +02:00
MasterAcnolo
3190b7a835 Version Config Depend PackageJSON 2026-08-15 13:20:18 +02:00
MasterAcnolo
922315883d Auto Update GitHub 2026-08-15 13:20:18 +02:00
MasterAcnolo
2eff372377 Log Discord RPC 2026-08-15 12:33:16 +02:00
MasterAcnolo
ccf852fc97 Discord RPC Fix 2026-08-15 12:33:16 +02:00
MasterAcnolo
9139fa4545 Documentation Bouton + Package Lock Update 2026-08-15 12:33:16 +02:00
MasterAcnolo
21eccfaae2 Removed Licence Preload 2026-08-15 12:33:16 +02:00
MasterAcnolo
9e7e600645 Removed console.log RPC 2026-08-15 12:33:16 +02:00
MasterAcnolo
4bf0e55d70 Removed Else Statement 2026-08-15 12:33:16 +02:00
MasterAcnolo
d3191b3c3e Notify Error + Cookies Gestion 2026-08-15 12:33:15 +02:00
MasterAcnolo
f91cdbefca RPC Com 2026-08-15 12:33:15 +02:00
MasterAcnolo
3fdefad69b Port Lisible Server.js 2026-08-15 12:33:15 +02:00
MasterAcnolo
f910bf7543 YTDLP Path DevMode 2026-08-15 12:33:15 +02:00
MasterAcnolo
cfc29956d0 Fix Double Log Start 2026-08-15 12:33:15 +02:00
MasterAcnolo
737ed2b14e Helpers + Clean Download et Info 2026-08-15 12:33:14 +02:00
MasterAcnolo
338f712bd4 Push avant Clean 2026-08-15 12:33:14 +02:00
MasterAcnolo
0501496ee4 Préparation du Champ de Bataille 2026-08-15 12:33:12 +02:00
MasterAcnolo
05d0ad77f8 ReadME Okay for 1.3 2026-08-15 12:33:10 +02:00
MasterAcnolo
6403cb3c71 Fix Version Mismatch (1.3.1 -> 1.3.0) 2026-08-15 12:33:10 +02:00
MasterAcnolo
3d7eadab21 Package UPDATE For 1.3.1 2026-08-15 12:33:10 +02:00
MasterAcnolo
4759040daf Change log file path for bug reports
Updated log file path in README for bug reports.
2026-08-15 12:32:53 +02:00
MasterAcnolo
377f41e052 Change Apercu README 2026-08-15 12:32:53 +02:00
MasterAcnolo
581e8bc3af LatestRelease No Space 2026-08-15 12:32:53 +02:00
MasterAcnolo
5428e67451 LatestRelease Space 2026-08-15 12:32:53 +02:00
MasterAcnolo
fcad2e707a ReadME Fix 2026-08-15 12:32:53 +02:00
MasterAcnolo
ca66d9f668 ReadME Fix 2026-08-15 12:32:53 +02:00
MasterAcnolo
6e22a01e0f ReadME Fix 2026-08-15 12:32:53 +02:00
MasterAcnolo
d99b5ae2b3 ReadME Fix 2026-08-15 12:32:53 +02:00
MasterAcnolo
146ee1e17f Removed ReadME FR and Rework ReadME 2026-08-15 12:32:53 +02:00
MasterAcnolo
567e1dfb04 Change project license from MIT to GNU License 2026-08-15 12:32:53 +02:00
MasterAcnolo
6e047fb386 Update project purpose in README-FR.md 2026-08-15 12:32:06 +02:00
MasterAcnolo
f4db1381da Update README.md 2026-08-15 12:32:06 +02:00
MasterAcnolo
0f84bf275f Clear NPM Scripts 2026-08-15 12:32:06 +02:00
MasterAcnolo
01daca1d76 YT-DLP Maj Automatique 2026-08-15 12:32:06 +02:00
MasterAcnolo
c8a13dc671 Préparation 1.2.4 2026-08-15 12:32:06 +02:00
MasterAcnolo
15163009f6 ReadME Update 2026-08-15 12:32:05 +02:00
MasterAcnolo
b3b3b99a68 YT-DLP Update + Fix Crash 2026-08-15 12:32:05 +02:00
MasterAcnolo
5d7c592758 Fix Spicy Image 2026-08-15 12:32:05 +02:00
MasterAcnolo
30d7bc683a FFPROBE 2026-08-15 12:32:05 +02:00
MasterAcnolo
150505b8bb ReadME Update 2026-08-15 12:32:05 +02:00
MasterAcnolo
874007b229 Title Window 2026-08-15 12:32:05 +02:00
MasterAcnolo
7e5facdf2a Asar 2026-08-15 12:32:05 +02:00
MasterAcnolo
4a8422d002 Asar 2026-08-15 12:32:05 +02:00
MasterAcnolo
4d4211d288 Package Update + Notif Fix Issues#13 2026-08-15 12:32:05 +02:00
MasterAcnolo
6a945eb413 FFMPEG Location 2026-08-15 12:32:05 +02:00
MasterAcnolo
7a0122d160 Correct Log Folder Name 2026-08-15 12:32:05 +02:00
MasterAcnolo
c06f0355f2 Fix Server.js Path 2026-08-15 12:32:05 +02:00
MasterAcnolo
7bd4c7cf68 GitIgnore Fix 2026-08-15 12:32:05 +02:00
MasterAcnolo
7abdc30cb0 Package Updated for 1.2.1 + ReadME Fix 2026-08-15 12:32:05 +02:00
MasterAcnolo
6b8b5434f9 Package JSON Updated for 1.2.1 2026-08-15 12:32:04 +02:00
MasterAcnolo
54031e7ead ADD Licence 'discordRPC.js' 2026-08-15 12:32:04 +02:00
MasterAcnolo
d70e6fead7 YT-DLP Moove 2026-08-15 12:32:04 +02:00
MasterAcnolo
530c7f31e6 ReadME Update 2026-08-15 12:32:04 +02:00
MasterAcnolo
82ad264743 Package Update 2026-08-15 12:32:04 +02:00
MasterAcnolo
699696b4cf Package Update 2026-08-15 12:32:04 +02:00
MasterAcnolo
a3355341c1 Discord RPC 2026-08-15 12:32:04 +02:00
MasterAcnolo
a5e0c1d685 ReadME Update 2026-08-15 12:32:04 +02:00
MasterAcnolo
cdb4160411 Inversion ReadME Principal 2026-08-15 12:32:03 +02:00
MasterAcnolo
ba2b51fddc NF Theme + Ajustement Other Themes 2026-08-15 12:32:03 +02:00
MasterAcnolo
180b8f2407 Update YT-DLP 2026-08-15 12:32:03 +02:00
MasterAcnolo
f8eca37f7e Retrait '--Restrict Filenames' 2026-08-15 12:32:03 +02:00
MasterAcnolo
c9a3c0bda2 ReadME Update 2026-08-15 12:32:03 +02:00
MasterAcnolo
6787970343 NPM Update 2026-08-15 12:32:02 +02:00
MasterAcnolo
7eda7bbf96 Update YTDLP + New Icon 2026-08-15 12:32:02 +02:00
MasterAcnolo
7c3771e8eb Clarification 2026-08-15 12:32:02 +02:00
MasterAcnolo
35f8b5fbdd Add Metadata / Thumbail
Optimisation + Retries
2026-08-15 12:32:02 +02:00
MasterAcnolo
4a3a893f41 ReadME Fix 2026-08-15 12:32:02 +02:00
MasterAcnolo
9317f7b999 Logs Fix + Logs Button 2026-08-15 12:32:02 +02:00
MasterAcnolo
30ca987bf9 Fix Download 2026-08-15 12:32:02 +02:00
MasterAcnolo
296bb3762b Logger Error Fix 2026-08-15 12:32:02 +02:00
MasterAcnolo
0f712e9c42 NPM Update 2026-08-15 12:32:02 +02:00
MasterAcnolo
2b96ce0038 NPM Update 2026-08-15 12:32:02 +02:00
MasterAcnolo
1e3239e0d2 ReadME Update 2026-08-15 12:32:01 +02:00
MasterAcnolo
11e5da54e0 Notif + Update YT DLP 2026-08-15 12:32:01 +02:00
MasterAcnolo
a0cf89481b Notification + Open On CLICK 2026-08-15 12:32:01 +02:00
MasterAcnolo
5512e7b3ec NPM Update 2026-08-15 12:32:01 +02:00
MasterAcnolo
6219d07e35 ReadME Fix 2026-08-15 12:32:01 +02:00
MasterAcnolo
2d611ac06c ReadME Fix 2026-08-15 12:32:01 +02:00
MasterAcnolo
abc488b566 ReadME Fix 2026-08-15 12:32:01 +02:00
MasterAcnolo
824077a5b0 ReadME Fix 2026-08-15 12:32:01 +02:00
MasterAcnolo
a0401623fb ReadME Fix 2026-08-15 12:32:01 +02:00
MasterAcnolo
34d577d22a ReadME FIX 2026-08-15 12:31:54 +02:00
MasterAcnolo
4ac97a0221 Package Fix 2026-08-15 12:31:35 +02:00
MasterAcnolo
b3d11bb07c Logs Fix 2026-08-15 12:31:35 +02:00
MasterAcnolo
03c374493c ReadME Pictures Fix 2026-08-15 12:31:34 +02:00
MasterAcnolo
9aef213aa4 Undo last Commit 2026-08-15 12:31:34 +02:00
MasterAcnolo
ab53ff6661 ReadMe Fix 2026-08-15 12:31:34 +02:00
MasterAcnolo
476e5362bd ReadME Fix %20 2026-08-15 12:31:34 +02:00
MasterAcnolo
1686c872de ReadME Fix %20 2026-08-15 12:31:34 +02:00
MasterAcnolo
80564f59ce ReadME Fix 2026-08-15 12:31:34 +02:00
MasterAcnolo
5ed2b79781 ReadME Fix 2026-08-15 12:31:34 +02:00
MasterAcnolo
0504890686 ReadME Fix 2026-08-15 12:31:34 +02:00
MasterAcnolo
9a074e817d ReadME Fix 2026-08-15 12:31:34 +02:00
MasterAcnolo
a86ecda352 ReadME Fix 2026-08-15 12:31:34 +02:00
MasterAcnolo
5873fdff0f ReadME Fix 2026-08-15 12:31:34 +02:00
MasterAcnolo
0ea5f7fb57 ReadME Fix 2026-08-15 12:31:34 +02:00
MasterAcnolo
f2c18e427c ReadME Fix 2026-08-15 12:31:34 +02:00
MasterAcnolo
d859d04abf ReadME Fix 2026-08-15 12:31:34 +02:00
MasterAcnolo
62da2f862e ReadME Fix 2026-08-15 12:31:27 +02:00
MasterAcnolo
4942c9109e Update README-EN.md 2026-08-15 12:30:24 +02:00
MasterAcnolo
fedaa47d0e Update README.md 2026-08-15 12:30:24 +02:00
MasterAcnolo
35fc0c370b 1.1 2026-08-15 12:30:24 +02:00
MasterAcnolo
b6203f3546 Commentary + Custom PATH 2026-08-15 12:30:24 +02:00
MasterAcnolo
9e5c2ffddf Chirac Easter EGG 2026-08-15 12:30:24 +02:00
MasterAcnolo
343aa99fea Logs FIX + Bug FIX 2026-08-15 12:30:24 +02:00
MasterAcnolo
7dba6d8c6a Package FIX for 1.0.2 2026-08-15 12:30:24 +02:00
MasterAcnolo
4786bcee59 1.0.1 Commit 2026-08-15 12:30:24 +02:00
MasterAcnolo
191fa7627a CSS Variable Begin 2026-08-15 12:30:24 +02:00
MasterAcnolo
6606a5f9c1 Logs Name Ajust + 1.0.1 Prepa 2026-08-15 12:30:24 +02:00
MasterAcnolo
a2871fc8e2 Clarification 2026-08-15 12:30:24 +02:00
MasterAcnolo
97fc95eb44 Correction Git Ignore 2026-08-15 12:30:24 +02:00
MasterAcnolo
55a118f388 MD En + FR 2026-08-15 12:30:24 +02:00
MasterAcnolo
6294c4a764 Push 2026-08-15 12:30:24 +02:00
MasterAcnolo
8c15ce28bc Push 2026-08-15 12:30:24 +02:00
MasterAcnolo
521a0e41fe Correction 2026-08-15 12:30:23 +02:00
MasterAcnolo
90b5c14bd1 Correction Path 2026-08-15 12:30:23 +02:00
MasterAcnolo
4c5b963342 Push ReadME + License 2026-08-15 12:30:23 +02:00
MasterAcnolo
c42002d5db MD Push 2026-08-15 12:30:23 +02:00
MasterAcnolo
8e12ad742f Correction Path YT DLP 2026-08-15 12:30:23 +02:00
MasterAcnolo
c82dda321c Fix NPM RUN DIST 2026-08-15 12:30:23 +02:00
MasterAcnolo
05f6a892e0 Working on my machine 2026-08-15 12:30:23 +02:00
MasterAcnolo
2bd5369524 It Works on my machine 2026-08-15 12:30:23 +02:00
MasterAcnolo
fdd0a2fa16 Push 2026-08-15 12:30:23 +02:00
MasterAcnolo
d413e47d58 Push avant l'enfer (PRIDEFUl - NF) 2026-08-15 12:30:23 +02:00
MasterAcnolo
26949ef6e9 Aled 2026-08-15 12:30:23 +02:00
MasterAcnolo
1063494070 Push Version 1 2026-08-15 12:30:23 +02:00
MasterAcnolo
844b994eb8 Push avant modif DIV INFO 2026-08-15 12:30:23 +02:00
MasterAcnolo
72b9e45366 LE POULET 2026-08-15 12:30:23 +02:00
MasterAcnolo
65cf3fad21 CSS Beginning 2026-08-15 12:30:23 +02:00
MasterAcnolo
942e4032a0 Loading Screen 2026-08-15 12:30:23 +02:00
MasterAcnolo
f840d327f9 Fix Bug Download 2026-08-15 12:30:23 +02:00
MasterAcnolo
1da81e10d5 Diviser pour mieux régner 2026-08-15 12:30:23 +02:00
MasterAcnolo
d46c92d1e0 Informations Vidéo 2026-08-15 12:30:23 +02:00
MasterAcnolo
bf5d1642ed Working V1 2026-08-15 12:30:23 +02:00
MasterAcnolo
e8c1c2a5b1 Working V1 2026-08-15 12:30:23 +02:00
MasterAcnolo
b3d39b5322 Push 2026-08-15 12:30:15 +02:00
MasterAcnolo
e7bbc109ba OUII 2026-08-15 12:29:43 +02:00
MasterAcnolo
4ad514b064 CA MARCHE 2026-08-15 12:28:33 +02:00
133 changed files with 15586 additions and 5812 deletions

2
.env.example Normal file
View File

@@ -0,0 +1,2 @@
BUG_REPORT_URL=
BUG_REPORT_API_KEY=

35
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View File

@@ -0,0 +1,35 @@
---
name: "Bug Report"
about: Report something that doesn't work fine
title: "[BUG]"
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,44 @@
---
name: Feature Request
about: Suggest a new feature or enhancement
title: "[FEATURE] "
labels: enhancement
assignees: 'MasterAcnolo'
---
## Feature Description
A clear and concise description of the feature you'd like to see.
## Problem it Solves
Describe the problem or limitation this feature would address.
## Proposed Solution
Describe how you envision this feature working.
## Alternative Solutions
Have you considered any alternative approaches? Describe them here.
## Use Case
Provide specific examples of how you would use this feature.
## Implementation Considerations
Are there any technical challenges or dependencies you're aware of?
## Priority
How important is this feature to you?
- [ ] Critical - Blocking my workflow
- [ ] High - Would significantly improve my experience
- [ ] Medium - Nice to have
- [ ] Low - Minor improvement
## Additional Context
Add any other context, mockups, or screenshots about the feature request here.

View File

@@ -0,0 +1,59 @@
---
name: Performance Issue
about: Report slow downloads, high CPU/memory usage, or crashes
title: "[PERFORMANCE] "
labels: performance
assignees: MasterAcnolo
---
## Performance Issue Type
- [ ] Slow download speed
- [ ] High CPU usage
- [ ] High memory usage
- [ ] Application freeze/hang
- [ ] Application crash
- [ ] Other (specify below)
## Description
Clear description of the performance issue.
## System Information
- **Freedom Loader Version**: [e.g. 1.4.1]
- **Windows Version**: [e.g. Windows 11 22H2]
- **CPU**: [e.g. Intel i7-9700K]
- **RAM**: [e.g. 16GB]
- **Storage**: [e.g. SSD/HDD]
- **Other Applications Opened**: [e.g. Discord/Opera GX]
> [!INFO]
> At the start of the application, SystemInfo is printed in the logs, so you can just copy and paste it!
## Reproduction Details
- **Download URL**: [if applicable]
- **File Size/Length**: [e.g. 2GB / 2 hours]
- **Quality Setting**: [e.g. 1080p, Best]
- **Audio Only**: Yes / No
## Expected Performance
What performance did you expect?
## Actual Performance
What performance are you experiencing? (Include metrics if possible: download speed, CPU %, memory usage)
## Logs
Attach logs with verbose mode enabled if possible:
```
C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\LOGS-YYYY-MM-DD.log
```
## Additional Context
Any other relevant information (network speed, antivirus software, other running applications).

30
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@@ -0,0 +1,30 @@
---
name: Question or Support
about: Ask a question about using Freedom Loader
title: "[QUESTION] "
labels: question
assignees: 'MasterAcnolo'
---
## Pre-submission Checklist
- [ ] I have checked the [FAQ](https://masteracnolo.github.io/Freedom-Loader-Site/#/faq)
- [ ] I have reviewed the [Wiki](https://masteracnolo.github.io/Freedom-Loader-Site/#/wiki)
- [ ] I have searched existing issues
## Question
What would you like to know?
## What I've Tried
Describe what you've already attempted to solve this yourself.
## Environment (if relevant)
- **Freedom Loader Version**: [e.g. 1.4.1]
## Additional Context
Any other information that might be helpful.

View File

@@ -0,0 +1,60 @@
## Description
Brief description of what this PR does.
## Related Issue (if applicable)
Fixes #(issue number)
Closes #(issue number)
Related to #(issue number)
## Type of Change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Code refactoring (no functional changes)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] UI/UX enhancement
## Changes Made
- Change 1
- Change 2
- Change 3
## Testing
- [ ] I have tested this code locally
- [ ] I have tested with different download scenarios (video/audio, playlists)
- [ ] I have checked the application logs for errors
- [ ] I have verified the UI changes (if applicable)
- [ ] I have tested on Windows 10 and/or Windows 11 or in any Linux Distribution
## Screenshots (if applicable)
**Before:**
[Add screenshot]
**After:**
[Add screenshot]
## Checklist
- [ ] My code follows the project's code style
- [ ] I have performed a self-review of my code
- [ ] I have commented my code where necessary
- [ ] I have updated the documentation (if applicable)
- [ ] My changes generate no new warnings or errors
- [ ] I have not modified the version number (this will be done during release)
## Additional Notes
Any additional information or context about the PR.
---
**Note for Maintainer:** This is a contribution PR and should NOT include version bumps.

View File

@@ -0,0 +1,10 @@
# Freedom Loader - 1.X.Y
## Changelog
-
## Found a bug or issue?
- Please report it in the GitHub Issues section
**Full Changelog**: https://github.com/MasterAcnolo/Freedom-Loader/compare/1.W.X...1.Y.Z

46
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: "CodeQL PR"
on:
pull_request:
branches: [ "main" ]
schedule:
- cron: '00 00 * * 1'
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
security-events: write
packages: read
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: javascript-typescript
build-mode: none
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
- name: Run manual build steps
if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'Manual build required'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"

37
.gitignore vendored
View File

@@ -1,6 +1,35 @@
# Dependencies
/node_modules
/downloads
# Build Output Folder
/dist
logs/*.json
logs/*.log
/out
/out
/srpm-out
# .env
*.env
# Config JSON File. Create it and fill it if you want to have dev only settings
config/config.dev.json
# IDE
.idea/
.vscode/
.zed/
# OS generated files
.DS_Store
.DS_Store?
Thumbs.db
# Linux executable
ffmpeg
ffprobe
deno
yt-dlp
# Windows Executable
ffmpeg.exe
ffprobe.exe
deno.exe
yt-dlp.exe

97
BINARIES.md Normal file
View File

@@ -0,0 +1,97 @@
# External binaries
This project depends on three external tools that are **not bundled in the repository**.
You must download them manually and place each binary in the correct folder before building or running the app locally.
---
## Required tools
| Tool | Role |
|---------|---------------------------------------------|
| yt-dlp | Video and audio download engine |
| ffmpeg | Media encoding, transcoding, muxing |
| ffprobe | Media file analysis and metadata extraction |
| deno | JavaScript / TypeScript runtime |
---
## Folder structure
Place the binaries exactly as shown below.
Do not rename them — the application resolves them by these exact names.
```
resources/
└── binaries/
├── linux/
│ ├── yt-dlp
│ ├── ffmpeg
│ ├── ffprobe
│ └── deno
└── win-32/
├── yt-dlp.exe
├── ffmpeg.exe
├── ffprobe.exe
└── deno.exe
```
---
## Download links
### yt-dlp
| Platform | URL |
|----------|----------------------------------------------------------------------|
| Windows | https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe |
| Linux | https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp |
Rename the downloaded file to `yt-dlp` (or `yt-dlp.exe` on Windows).
---
### ffmpeg and ffprobe
**Windows and Linux** — both binaries are included in the same archive.
| Platform | URL |
|-------------|-----------------------------------------------------------------------------------------------------------|
| Windows x64 | https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip |
| Linux x64 | https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl.tar.xz |
| Linux arm64 | https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linuxarm64-gpl.tar.xz |
Extract the archive and copy `ffmpeg` and `ffprobe` from the `bin/` subfolder.
---
### deno
| Platform | URL |
|-------------|----------------------------------------------------------------------------------------------|
| Windows x64 | https://github.com/denoland/deno/releases/latest/download/deno-x86_64-pc-windows-msvc.zip |
| Linux x64 | https://github.com/denoland/deno/releases/latest/download/deno-x86_64-unknown-linux-gnu.zip |
| Linux arm64 | https://github.com/denoland/deno/releases/latest/download/deno-aarch64-unknown-linux-gnu.zip |
Each archive contains a single binary. Rename it to `deno` (or `deno.exe` on Windows).
---
## Permissions (Linux )
After placing the binaries, make them executable:
```bash
chmod +x resources/binaries/linux/yt-dlp
chmod +x resources/binaries/linux/ffmpeg
chmod +x resources/binaries/linux/ffprobe
chmod +x resources/binaries/linux/deno
```
---
## Notes
- These binaries are excluded from version control via `.gitignore`.
- Always use the latest stable release of each tool.
- **Yt-dlp** binaries don't need to be updated manually. At every startup of the application, the binary tries to update itself

111
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,111 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in the Freedom Loader 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
* Supporting fellow contributors and helping newcomers feel welcome
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
* Sustained disruption of discussions, issues, or pull requests
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders and project maintainers 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, including:
- GitHub repository (issues, pull requests, discussions, wiki)
- Project documentation and website
- Official communication channels (Discord, email, etc.)
- Social media accounts representing the project
- Project events and meetings (online or offline)
This Code of Conduct also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email 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.
## Our Commitment to Contributors
At Freedom Loader, we value every contribution—whether it's a bug report, feature suggestion, code improvement, or documentation fix. We strive to:
- Respond to issues and pull requests in a timely manner
- Provide constructive feedback on contributions
- Help new contributors get started
- Maintain a welcoming environment for all skill levels
- Credit contributors appropriately for their work
## Questions and Clarifications
If you have questions about this Code of Conduct or need clarification on what constitutes acceptable behavior, please:
- Open a [discussion](https://github.com/MasterAcnolo/Freedom-Loader/discussions) on GitHub
- Contact the maintainers at masteracnolo25@gmail.com
- Review the [Contributing Guidelines](CONTRIBUTING.md)
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), 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).
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.
---
**Remember**: Freedom Loader is built by people who care. Let's keep this community a place where everyone feels welcome to contribute.

128
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributing to Freedom Loader
Welcome! Thanks for your interest in Freedom Loader. Your contributions—bug reports, feature ideas, or code improvements—are always appreciated. Every contribution makes Freedom Loader better.
---
## Quick Links
- [Development Guide](DEVELOPMENT.md)
- [Code of Conduct](CODE_OF_CONDUCT.md)
- [Issue Templates](.github/ISSUE_TEMPLATE/)
- [Pull Request Templates](.github/PULL_REQUEST_TEMPLATE/)
- [Security Policy](SECURITY.md)
---
## Ways to Contribute
### 1. Report a Bug
Found a bug or unexpected behavior? Open an [**issue**](https://github.com/MasterAcnolo/Freedom-Loader/issues) using the **Bug Report** template.
**Please include:**
- Clear description of the bug
- Steps to reproduce
- App version and environment (Windows version, Firefox version if relevant)
- Logs from: `C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\LOGS-YYYY-MM-DD.log` (Windows) or `~/.local/share/FreedomLoader/logs/` (Linux)
- Screenshots if applicable
### 2. Request a Feature
Have an idea to improve Freedom Loader? Open a [**Feature Request**](https://github.com/MasterAcnolo/Freedom-Loader/issues/new/choose).
**Tips:**
- Describe the problem it solves
- Explain your proposed solution
- Include use cases and examples
### 3. Submit Code Changes
**Before starting:**
- Check existing issues and PRs to avoid duplicates
- For major changes, open an issue first to discuss
- Follow the project's code style and conventions
**Process:**
1. Fork the repository.
2. Check out our [**Developer Guide (DEVELOPMENT.md)**](./DEVELOPMENT.md) to set up your environment.
3. Create a feature branch following our branching conventions.
4. Make your changes and test thoroughly on Windows and/or Linux.
5. Open a Pull Request using the appropriate template.
**Code Guidelines:**
- Use camelCase for variables and functions
- Comment complex logic
- Keep commits focused and atomic
- Update documentation if needed
- Don't modify version numbers (done during release)
### 4. Improve Documentation
Documentation improvements are always welcome:
- Fix typos or unclear sections
- Add missing examples
- Update outdated information
- Improve README, wiki, or guides
Small contributions matter—don't hesitate to submit documentation PRs.
---
## Development Setup
Want to write some code? Awesome!
Please read our [**Developer Guide (DEVELOPMENT.md)**](./DEVELOPMENT.md) for instructions on how to set up your local environment, our project architecture, and our Git branching rules.
---
## Testing
Before submitting a PR, verify:
- [ ] Download functionality works (video/audio)
- [ ] No errors in application logs
- [ ] Any features wasn't break during the development
- [ ] Tested on Windows 10/11 and/or any Linux Distribution
---
## Pull Request Review Process
1. **Submission**: Open PR with clear description using the template
2. **Review**: Maintainers review code and provide feedback
3. **Updates**: Address feedback and push updates
4. **Approval**: Once approved, PR will be merged
5. **Release**: Changes included in next release
**Response times:**
- Bug fixes: Usually reviewed within 2-3 days
- Features: May take longer depending on complexity
- Documentation: Often reviewed quickly
---
## Code of Conduct
Be respectful and constructive in all interactions. We welcome everyone as long as discussions remain polite and productive.
See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for details.
---
## Questions?
- Check the [FAQ](https://masteracnolo.github.io/Freedom-Loader-Site/faq)
- Review the [Wiki](https://masteracnolo.github.io/Freedom-Loader-Site/wiki)
- Open a [Question issue](https://github.com/MasterAcnolo/Freedom-Loader/issues/new/choose)
---
## Credits
Thanks to **Zakaria** for the website icon design.
Check out his work: [IG @designmark_studio](https://www.instagram.com/designmark_studio/)
---
**Thank you for contributing to Freedom Loader!**

388
DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,388 @@
# Freedom Loader - Development Guide
Welcome to the Developer Guide! This document explains how to set up your local environment, project architecture, Git workflow, and the release process.
## Prerequisites
Before you begin, ensure you have the following installed on your machine:
- **Node.js** (v22 or higher recommended)
- **npm** (comes with Node.js)
- **Git**
- **yt-dlp binaries**: Freedom Loader requires native binaries to function. Read [BINARIES.md](./BINARIES.md) for download instructions and placement in `resources/binaries/`.
## Setup Instructions
### 1. Clone the repository
```bash
git clone https://github.com/MasterAcnolo/Freedom-Loader.git
cd Freedom-Loader
```
### 2. Install dependencies
```bash
npm install
```
### 3. Install the Binaries
Follow the instructions in `BINARIES.md` to place `yt-dlp`, `ffmpeg`, `ffprobe`, and `deno` in the correct `resources/binaries/` folder for your OS.
### 4. Run in Development Mode
```bash
npm start
```
**Note**: Development mode automatically enables detailed colored logs and the DevTron extension for debugging.
---
## Available Scripts
### Development
- `npm start` — Start the app in development mode (no warnings)
- `npm run start:warn` — Start with deprecation warnings visible
- `npm run start:debug` — Start with full debug logging (Wayland, Electron logging, stack dumps)
- `npm run dev` — Start with auto-reload on file changes (watches server/, app/, main.js, config.js)
- `npm run dev:warn` — Dev mode with warnings
- `npm run dev:debug` — Dev mode with full debug output and auto-reload
### Building
- `npm run build:linux` — Build Linux packages (AppImage, deb, snap)
- `npm run build:win` — Build Windows installer (.exe)
- `npm run build:rpm` — Build RPM and SRPM for COPR (Fedora)
- `npm run build:all` — Build both Windows and Linux (not recommended because it adds both OS binaries into a single package. The output file is 2x bigger than expected).
### Testing
- `npm test` — Run all tests (unit + integration)
- `npm run test:unit` — Run unit tests only
### Release Pipeline
- `npm run release` — Build all packages and create a draft GitHub release (no store publishing)
- `npm run release:publish` — Build all packages and publish to Snap Store + COPR
- `npm run release:dry-run` — Simulate the full release pipeline without publishing anything
### Maintenance
- `npm update` — Update npm dependencies
---
## Project Architecture
Freedom Loader is built with **Electron** (frontend) and **Node.js + Express** (backend), using a modular structure:
---
## Git & Branching Workflow
I use **Trunk-Based Development** (GitHub Flow):
### Core Principles
- **`main` is always deployable**: The `main` branch contains the latest stable code and must never be broken.
- **No long-lived branches**: I no longer maintain version branches like `v1.6` or `v1.7`.
- **Pull Requests for review**: All code changes go through PR review before merging to `main`.
### How to Contribute
#### 1. Create a feature branch
```bash
git checkout -b feat/add-new-button
# or for fixes:
git checkout -b fix/ui-bug
```
**Branch naming convention**:
- `feat/` — New features
- `fix/` — Bug fixes
- `refactor/` — Code cleanup (no behavior change)
- `docs/` — Documentation updates
- `chore/` — Dependency updates, build config, etc.
#### 2. Make commits
```bash
# Make changes
git add .
git commit -m "Brief, imperative description of the change"
```
**Commit message tips**:
- Use imperative mood: "Add theme caching" not "Added theme caching"
- Keep commits logical and focused (one feature per commit if possible)
- Link to issues if relevant: "Fix crash on download (fixes #42)"
#### 3. Push and open a PR
```bash
git push origin feat/add-new-button
```
Then open a PR on GitHub against `main`.
#### 4. Review and merge
- Address feedback in new commits (don't rebase — easier to review)
- Once approved, merge via GitHub (use "Squash and merge" for clean history if many small commits and you find it relevant)
---
## Release Process
**Note**: Only project maintainers release to production. This section documents the process for transparency and for future maintainers.
Releases are fully automated via the `release.sh` script. The process handles building, packaging, and publishing to **all distribution channels**:
- **GitHub Releases** (Windows .exe, Linux packages)
- **Fedora COPR** (automatic RPM builds for Fedora 43+)
- **Snap Store** (universal Linux)
### Before You Release
1. **Ensure `main` is green**: All tests pass, features are stable.
2. **Update `package.json` version**:
```json
"version": "1.6.1"
```
3. **Commit the version bump**: `git add package.json && git commit -m "chore: v1.6.1"`
4. **Push to `main`**: `git push origin main`
### Step 1: Local Build & Draft Release
```bash
npm run release
```
This:
1. Builds Linux packages (AppImage, deb, snap)
2. Builds RPM and SRPM for COPR
3. Builds Windows installer
4. Creates a draft GitHub release
### Step 2: Edit & Publish on GitHub
1. Go to https://github.com/MasterAcnolo/Freedom-Loader/releases
2. Edit the draft release — fill in changelog
3. Click **"Publish"** to make it live
### Step 3: Publish to Distribution Channels
```bash
npm run release:publish
```
This automatically:
- **Uploads to Snap Store**: Makes the app available via `snap install freedom-loader`
- **Submits to Fedora COPR**: Builds and publishes RPMs for Fedora 43+ (users can `dnf install freedom-loader` from the COPR repo)
Both happen in parallel — users across all platforms get the release simultaneously.
---
## Linux Development Considerations
### Cross-Distribution Compatibility
Freedom Loader targets **Debian-based** (Ubuntu, Debian) and **Fedora-based** (Fedora, RHEL, openSUSE) distributions. When developing features, keep this in mind:
#### What to test
- **Feature works on Fedora 44+** (primary Linux development environment)
- **Feature works on Ubuntu/Debian** (via deb package or AppImage)
- **Feature doesn't break Snap confinement** (Snap has restricted filesystem/IPC access)
- **Feature gracefully degrades on missing system dependencies**
#### Desktop Environment (DE) Compatibility
Test on at least **GNOME** and **KDE** (the most common DEs). Common pain points:
- **Themes**: May render differently on KDE vs GNOME — test both if possible
- **File dialogs**: Some DEs use native file pickers, others fall back to Electron's
- **Notifications**: System notification APIs vary (D-Bus, libnotify)
- **Tray icons**: May not work identically across DEs
#### How to check locally
```bash
# Test on Fedora (if available)
npm run build:linux
sudo dnf install dist/freedom-loader-*.x86_64.rpm
freedom-loader
# Test AppImage (works on any distro)
chmod +x dist/Freedom\ Loader-*.AppImage
./dist/Freedom\ Loader-*.AppImage
```
### CI/CD Gap
**Currently**: No automated testing across distros or DEs. Releases rely on manual testing before publish.
**Future improvement**: Automated tests via GitHub Actions (Ubuntu) + local testing on Fedora would catch cross-distro issues early. This is planned but not yet implemented.
For now, if you fix a Linux-specific bug or add a DE-dependent feature, **please mention it in your PR description** so reviewers can test extra carefully.
---
## Testing
### Unit Tests
Test individual functions in isolation (no Electron required).
```bash
npm run test:unit
```
Covered:
(Tests will come soon)
### End-to-End Tests (Playwright)
Test the actual Electron app launching and basic UI interactions.
```bash
npm run test
```
Covered:
(Tests will come soon)
### Running All Tests
```bash
npm test
```
Or include tests before release:
```bash
npm test && npm run release
```
---
## Common Tasks
### Add a new feature
1. Create a feature branch: `git checkout -b feat/my-feature`
2. Make changes, commit: `git add . && git commit -m "feat: my feature"`
3. Push: `git push origin feat/my-feature`
4. Open a PR on GitHub
5. Once approved, merge to `main`
6. (Later) Cut a release when ready: `npm run release`
### Fix a bug
Same as above, but use `fix/bug-name` branch and `git commit -m "fix: description"`.
### Test the app before releasing
```bash
npm run dev # Auto-reload on file changes
# Make changes, test in the UI
npm test # Run all tests
npm run release:dry-run # Simulate release (builds but doesn't publish)
```
### Update dependencies
```bash
npm update
git add package.json package-lock.json
git commit -m "chore: update dependencies"
git push origin main
```
### Rebuild only Linux (after quick fixes)
```bash
npm run build:linux
# Or just the RPM:
npm run build:rpm
```
### Publish to stores manually
If `npm run release:publish` fails partway:
```bash
# Just Snap:
snapcraft upload dist/freedom-loader_*.snap --release=stable
# Just COPR:
copr-cli build freedom-loader srpm-out/*.src.rpm
```
---
## Troubleshooting
### App won't start in dev mode
```bash
npm run start:debug
```
Check the debug output for errors. Most common:
- Missing binaries in `resources/binaries/`
- Port 8787 already in use
- A dead instance is still running — open Task Manager and kill the "Freedom Loader" process
### (Linux) Binaries in the right place but app still won't launch
You probably forgot to make them executable:
```bash
chmod +x resources/binaries/linux/*
```
The app will fail silently if binaries lack execute permissions.
### Build fails with "permission denied"
Make sure you have write access to `dist/` and `srpm-out/`:
```bash
chmod -R u+w dist srpm-out
npm run build:linux
```
### Tests fail
Check for:
- Missing test files in `tests/unit/`
- Node modules out of sync: `rm -rf node_modules && npm install`
### GitHub release publish fails
Verify `gh` CLI is installed and authenticated:
```bash
gh auth login
gh release list
```
---
## Questions?
For more info:
- **Electron docs**: https://www.electronjs.org/docs
- **electron-builder**: https://www.electron.build/
- **This repo**: https://github.com/MasterAcnolo/Freedom-Loader
Happy coding! 🎉

View File

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

527
README.md
View File

@@ -1,114 +1,439 @@
# Freedom Loader
<div align="center">
<!-- Release -->
<a href="https://masteracnolo.github.io/Freedom-Loader-Site/"><img
src="./build/banner.png"
alt="Banner"
style="width: 50%;"/></a>
![Static Badge](https://img.shields.io/badge/Release-1.2-blue?style=flat&logo=Test)
### **A clean, open-source multimedia downloader for Windows and Linux**
<!-- Build (Next Release) -->
[![Release](https://img.shields.io/github/v/release/MasterAcnolo/Freedom-Loader?style=for-the-badge&color=blue)](https://github.com/MasterAcnolo/Freedom-Loader/releases)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-red.svg?style=for-the-badge)](https://www.gnu.org/licenses/gpl-3.0)
[![Website](https://img.shields.io/badge/Website-Visit-404040?style=for-the-badge)](https://masteracnolo.github.io/Freedom-Loader-Site/)
[![Workshop](https://img.shields.io/badge/Worshop-Visit-0E05A1?style=for-the-badge)](https://masteracnolo.github.io/Freedom-Loader-Workshop/)
<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>
![Static Badge](https://img.shields.io/badge/Build-1.2.1--beta-yellow?style=flat&logo=Test)
</div>
> This project aims to enable free, open-source, and unrestricted downloading of Video/Audio content. Eventually, it will allow users to choose
## Overview
## 🚀 Features
Freedom Loader is a desktop application built with Electron that provides a straightforward way to download video and audio content with full metadata support. The application focuses on reliability, transparency, and user privacy-no ads, no tracking, no questionable third-party services.
- ✅ 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
The primary goal is to make media downloading accessible to users who want offline access to their favorite content, particularly in situations where internet connectivity is unreliable or unavailable.
### See related repo:
- **[Website](https://github.com/MasterAcnolo/Freedom-Loader-Site/)** - Official project website and documentation
- **[Workshop](https://github.com/MasterAcnolo/Freedom-Loader-Workshop)** - Theme creation tool and theme browser
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
- [Preview](#preview)
- [Configuration](#configuration)
- [Theme Workshop](#Theme-Workshop)
- [Technology Stack](#technology-stack)
- [Development](#development)
- [Roadmap](#roadmap)
- [Contributing](#contributing)
- [Submitting a community theme](#Submitting-a-community-theme)
- [License](#license)
## Features
### Core Functionality
- **Multi-format downloads** - MP4 video with full metadata or MP3 audio with embedded artwork
- **Video information preview** - Fetch and display video metadata before downloading
- **Quality selection** - Choose from best, medium, lowest quality or specific resolutions (1080p, 720p, 480p)
- **Playlist support** - Download entire playlists with optional auto-download mode
- **Metadata embedding** - Automatic retrieval and embedding of title, artist, duration, and thumbnail
- **Video codec options** - Support for H.264, H.265, AV1, VP9, VP9.2, and Theora codecs
- **Custom output directory** - User-configurable download location with path validation
- **Cookie integration** - Firefox cookie support for accessing age-restricted and member-only content
### User Interface
- **Custom window controls** - Frameless window with custom top bar (optional)
- **Custom themes** - ZIP-based theme system with live preview
- **Theme Workshop** - web-based theme creator and browser
- **Toast notifications** - in-app feedback system
- **Real-time progress tracking** - Live download progress bar with network speed indicator
- **Server-Sent Events (SSE)** - Non-blocking progress updates via event streaming
- **Settings panel** - In-app configuration interface with live updates
- **Quick access toolbar** - Direct links to logs, website, wiki, and developer tools
### System Features
- **Automatic updates** - Electron-updater integration with GitHub releases
- **YT-DLP auto-update** - Keeps the download engine up-to-date on startup
- **Structured logging** - Winston-based logging with daily file rotation (7-day retention)
- **Discord Rich Presence** - Optional integration showing current download activity
- **Single instance enforcement** - Prevents multiple app instances from running simultaneously
- **Native dependency validation** - Startup checks for yt-dlp, FFmpeg, FFprobe, and Deno binaries
- **Rate limiting** - Express-based request throttling to prevent server overload
- **Graceful shutdown** - Proper cleanup of servers, logs, and RPC connections
- **System notifications** - Windows notifications on download completion (clickable to open folder)
- **Splash screen** - animated loading screen on startup
- **Stop download** - ability to cancel in-progress downloads
- **Playlist folders** - dedicated folder creation per playlist
- **Keep Playlist Order** - Add an Index before the title, could be useful for an album for example.
- **System Tray** - Could minimize the application in the system Tray, allow it to run in the background.
## Installation
[![Fedora Copr](https://img.shields.io/badge/Fedora-Copr-294172?style=for-the-badge&logo=fedora&logoColor=white)](https://copr.fedorainfracloud.org/coprs/masteracnolo/freedom-loader/)
[![Debian/Ubuntu](https://img.shields.io/badge/.deb-Debian%2FUbuntu-A81D33?style=for-the-badge&logo=debian&logoColor=white)](https://github.com/MasterAcnolo/Freedom-Loader/releases)
[![Snap Store](https://img.shields.io/badge/Snap-Store-82BEA0?style=for-the-badge&logo=snapcraft&logoColor=white)](https://snapcraft.io/freedom-loader)
[![AppImage](https://img.shields.io/badge/AppImage-Universal-1E88E5?style=for-the-badge&logo=linux&logoColor=white)](https://github.com/MasterAcnolo/Freedom-Loader/releases)
### Requirements
- **Windows**: Windows 10 or later
- **Linux**: any modern x86_64 distribution
- **Browser**: Mozilla Firefox ([download](https://www.firefox.com)) required for cookie extraction, on both platforms
### Windows
1. Download the latest `.exe` from the [Releases page](https://github.com/MasterAcnolo/Freedom-Loader/releases)
2. Run the installer (click "More info" → "Run anyway" if Windows Defender warns you)
3. Follow the installation wizard
### Linux
#### Fedora (via Copr - recommended)
Installing via Copr lets `dnf` handle future updates automatically:
```bash
sudo dnf copr enable masteracnolo/freedom-loader
sudo dnf install freedom-loader
```
#### Debian / Ubuntu (.deb)
```bash
# Download the latest .deb from the Releases page, then:
sudo apt install ./freedom-loader_1.x.x_amd64.deb
```
#### Snap (any distro with snapd)
```bash
sudo snap install freedom-loader
```
#### RPM (Fedora/RHEL/openSUSE, without Copr)
```bash
# Download the latest .rpm from the Releases page, then:
sudo dnf install ./freedom-loader-1.x.y.x86_64.rpm
```
#### AppImage (any distro, no installation needed)
```bash
# Download the latest .AppImage from the Releases page, then:
chmod +x Freedom-Loader-1.x.y.AppImage
./Freedom-Loader-1.x.y.AppImage
```
Once installed via any method, launch Freedom Loader from your application menu, or from a terminal with:
```bash
freedom-loader
```
### File Locations
| | Windows | Linux |
|---------------|---------------------------------------------------------|--------------------------------------|
| **Downloads** | `C:\Users\[USERNAME]\Downloads\Freedom Loader` | `~/Downloads/Freedom Loader` |
| **Logs** | `C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\` | `~/.local/share/FreedomLoader/logs/` |
> [!NOTE]
> The download folder can be changed anytime via the "Edit" button in the UI, on both platforms.
## Usage
> [!TIP]
> On Linux, you can start Freedom Loader using `freedom-loader` command
### Basic Download
1. Launch Freedom Loader
2. Paste a video or playlist URL into the input field
3. Select the desired format (Video MP4 or Audio MP3)
4. Click the download button
5. Monitor progress in real-time
6. Receive a notification when the download completes
### Advanced Configuration
The application supports various configuration options:
- Custom download directory selection
- Discord Rich Presence activation
- Theme customization
- Log level
- And somes to see in the list [Configuration Options](#configuration-options) available
### Troubleshooting
If you encounter issues:
1. Restart the application
2. Check the logs in `AppData\Local\FreedomLoader\logs\` or `~/.local/share/FreedomLoader/logs`
3. Open the developer console: `CTRL + SHIFT + I` → "Toggle Developer Tools"
4. Create an issue on GitHub with logs and reproduction steps
## Preview
<div align="center">
<table>
<tr>
<td width="50%" align="center">
<img src="https://github.com/user-attachments/assets/d4de9079-7c82-44d3-ba22-8246bb467db3" alt="Download Preview" width="75%" />
<p><em>Real-time download progress with network speed indicator</em></p>
</td>
<td width="50%" align="center">
<img src="https://github.com/user-attachments/assets/d59aff95-6754-486a-b880-d70730146dc3" alt="Settings Panel" width="40%" />
<p><em>Settings panel with theme and feature customization</em></p>
</td>
</tr>
</table>
</div>
## Configuration
Freedom Loader can be configured either through the settings panel in the UI or by directly editing the `config.json` file located in the application data directory.
### Configuration File Location
#### Windows
```
%APPDATA%\Freedom Loader\config.json
```
#### Linux
```
~/.config/Freedom\ Loader/config.json
```
### Available Options
```json
{
"autoUpdate": true,
"systemTray": true,
"discordRPC": true,
"customTopBar": true,
"autoCheckInfo": true,
"addThumbnail": true,
"addMetadata": true,
"verboseLogs": false,
"autoDownloadPlaylist": true,
"keepPlaylistOrder": false,
"createPlaylistFolders": true,
"customCodec": "h264",
"logSystem": true,
"outputTitleCheck": true,
"downloadSystem": true,
"notifySystem": true,
"enableHardwareAcceleration": true,
"theme": "dark"
}
```
### Configuration Options
| Option | Type | Default | Description |
|------------------------------|---------|----------|------------------------------------------------------------|
| `autoUpdate` | boolean | `true` | Enable automatic application updates |
| `systemTray` | boolean | `false` | Allow app to minimize on close [EXPERIMENTAL] |
| `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration |
| `customTopBar` | boolean | `true` | Use custom application top bar |
| `autoCheckInfo` | boolean | `true` | Automatically fetch video information on URL paste |
| `addThumbnail` | boolean | `true` | Embed thumbnail in downloaded audio files |
| `addMetadata` | boolean | `true` | Add metadata tags to downloaded files |
| `verboseLogs` | boolean | `false` | Enable detailed logging for debugging |
| `autoDownloadPlaylist` | boolean | `true` | Automatically download entire playlists |
| `keepPlaylistOrder` | boolean | `false` | Add index before title, usefull for album |
| `createPlaylistFolders` | boolean | `false` | Automatically create a folder for a playlist |
| `customCodec` | string | `"h264"` | Video codec for encoding (supported: h264, h265, vp9, av1) |
| `logSystem` | boolean | `true` | Enable application logging |
| `outputTitleCheck` | boolean | `true` | Verify output file titles |
| `downloadSystem` | boolean | `true` | Enable download system |
| `notifySystem` | boolean | `true` | Enable system notifications |
| `enableHardwareAcceleration` | boolean | `true` | Enable Hardware Acceleration |
| `theme` | string | `dark` | Current Theme Name |
### Supported Codecs
- `h264` - H.264/AVC (widely compatible, recommended default)
- `h265` - H.265/HEVC (better compression, less compatible)
- `av1` - AV1 (modern codec, excellent compression)
- `av01` - AV1 variant
- `vp9` - VP9 (open format, good quality)
- `vp9.2` - VP9 Profile 2 (HDR support)
- `theora` - Theora (legacy open codec)
> [!NOTE]
> Some configuration changes may require an application restart to take effect.
### Architecture Overview
Freedom Loader uses a client-server architecture within a single Electron application:
- **Frontend**: HTML/CSS/JavaScript served via Electron's renderer process
- **Backend**: Express.js server running locally for download management
- **App modules**: Electron-specific logic (window, IPC, updates, RPC) isolated in `app/`
- **IPC Bridge**: Secure communication via Electron's preload script
- **Logging**: Winston-based structured logging with file rotation
- **Updates**: Automatic checking and installation via electron-updater
## Theme Workshop
Freedom Loader includes a web-based theme creator available at [Freedom Loader Workshop](https://masteracnolo.github.io/Freedom-Loader-Workshop/).
- Create custom themes with a live preview of the actual UI
- Browse and download community themes
- Export themes as `.zip` files ready to drop into the `theme/` folder
- Supports background images, custom colors for every UI element
### Installing a custom theme
1. Download a `.zip` theme file
2. Drop it in the `theme/` folder of your Freedom Loader installation
3. Restart the app - your theme appears in the settings panel
## Technology Stack
### Core Technologies
[![yt-dlp](https://img.shields.io/badge/yt--dlp-cf0000?style=for-the-badge&logo=youtube&logoColor=white)](https://github.com/yt-dlp/yt-dlp)
[![FFmpeg](https://img.shields.io/badge/FFmpeg-007808?style=for-the-badge&logo=ffmpeg&logoColor=white)](https://ffmpeg.org/)
[![Electron](https://img.shields.io/badge/Electron-47848F?style=for-the-badge&logo=electron&logoColor=white)](https://www.electronjs.org/)
[![Node.js](https://img.shields.io/badge/Node.js-339933?style=for-the-badge&logo=node.js&logoColor=white)](https://nodejs.org/)
### Key Dependencies
- **Electron**: Cross-platform desktop application framework
- **Express**: Web framework for local HTTP server
- **yt-dlp**: Media download engine
- **FFmpeg**: Media processing and conversion
- **Winston**: Logging library
- **discord-rpc**: Discord Rich Presence integration
- **electron-updater**: Automatic application updates
## Development
If you are interested by running a local non compiled version of Freedom Loader or just interested by contributing, see [DEVELOPMENT](./DEVELOPMENT.md)
### Additional Dependencies
This project relies on several external binaries.
Some of them are **not included** in the public repository and must be added manually.
You must download the required binaries and place them in the `resources` folder, using the exact filenames listed below.
#### Required binaries
See [BINARIES.md](./BINARIES.md) for how to setup the binaries.
### Development Guidelines
- Follow existing code style and conventions
- Write clear, imperative commit messages (e.g., "Add playlist indexing" not "Added")
- Test thoroughly on both Windows and Linux before submitting changes
- Update documentation (README, DEVELOPMENT.md) when adding features
- Maintain compatibility with:
- Windows 10+
- Fedora 43+ / Debian 11+ / Ubuntu 20.04+
- GNOME and KDE desktop environments (when possible)
- Consider Linux distribution differences early (see [DEVELOPMENT.md](./DEVELOPMENT.md#linux-development-considerations))
## Roadmap
See the most recent Roadmap [Here](https://masteracnolo.github.io/Freedom-Loader-Site/Roadmap).
## Contributing
Contributions are welcome and appreciated. This project benefits from community involvement.
## Submitting a community theme
Want your theme featured in the Workshop? Please **do not open an issue on this repository** for theme submissions.
Head over to the [Freedom Loader Workshop repository](https://github.com/MasterAcnolo/Freedom-Loader-Workshop) and open an issue there with your theme files, preview screenshots, and description.
### Bug Reports
You can report bugs either using the built-in application system or via the GitHub Issues system.
#### Using the Built-in Report Tool
1. Open Freedom Loader.
2. Trigger the bug report interface.
3. Fill in the title and description of the issue.
4. Choose whether to include today's logs automatically.
5. Submit the report.
> [!NOTE]
> If you need to attach extra context like screenshots or screen recordings, it is recommended to use the GitHub Issues
system instead.
#### Using GitHub Issues
Open a new issue on the GitHub repository and include:
- Clear description of the issue
- Steps to reproduce
- Expected vs actual behavior
- Relevant logs from `AppData\Local\FreedomLoader\logs\` or `~/.local/share/FreedomLoader/logs/`
- Screenshots or recordings if applicable
### Feature Requests
Open a feature request issue with:
- Clear description of the proposed feature
- Use cases and benefits
- Potential implementation approach (if applicable)
### Pull Requests
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/your-feature`)
3. Make your changes with clear, descriptive commits
4. Test thoroughly
5. Update documentation as needed
6. Submit a pull request with a detailed description
Please read [CONTRIBUTING.md](CONTRIBUTING.md), [DEVELOPMENT.md](./DEVELOPMENT.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) before contributing.
## License
Freedom Loader is released under the **GNU General Public License v3.0 (GPLv3)**.
You are free to use, modify, and redistribute this software under the terms of the GPL-3.0 license. See the [LICENSE](LICENSE) file for complete details.
## Acknowledgments
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) for the robust download engine
- [FFmpeg](https://ffmpeg.org/) for media processing capabilities
- [Deno](https://deno.com/) for helping me to bypass challenges
- [Electron](https://www.electronjs.org/) for the desktop application framework
- The open-source community for continuous support and contributions
- [@SpicyFire21](https://github.com/SpicyFire21) to be the spiciest one
- [@AureCX](https://github.com/AureCX) for being the Official Windows Tester!
- Zakaria for the website icon
- All users who test, report issues, and help improve the application
---
## 🔧 Installation
<div align="center">
To install the application, simply download the installer from the releases section. Take the latest version and run it.
**Freedom Loader** - Put freedom in your downloads
---
[Website](https://masteracnolo.github.io/Freedom-Loader-Site/) • [Download](https://github.com/MasterAcnolo/Freedom-Loader/releases) • [Documentation](https://masteracnolo.github.io/Freedom-Loader-Site/wiki) • [Report Bug](https://github.com/MasterAcnolo/Freedom-Loader/issues)
## ❓ Frequently Asked Questions
### _Where are my downloaded files located?_
In your computer's Downloads folder (**C:\Users\YOUR NAME\Downloads**), inside a folder named **Freedom Loader Output**
<br>
### _After installing and running the program, I get an error_
Try restarting the application, checking the logs, or opening the browser's developer console by pressing `CTRL + SHIFT + I` or by going to `View > Toggle Developer Tools`
<!-- ![Example Path](build/example-developertools.png) -->
<p align="center">
<img src="build/example-developertools.png" alt="Example Developer Tools" />
</p>
## 🏗️ Upcoming Features (Roadmap)
- [ ] More format options
- [ ] Subtitle support
- [ ] Improved UI / UX
- [ ] Language selection
- [ ] Download a specific part of the video
- [ ] Rename output file
- [x] ~~Extract / Split video using native chapters~~
- [ ] Parallel downloads
- [ ] Skip sponsored parts of videos
- [x] ~~Custom output path selection (default **C:\Users\Username\Downloads** with a **Freedom Loader Output** folder created)~~
---
## 🖼️ Preview
<p align="center">
<img src="build/apercu1.0.1.png" alt="Exemple d'application" />
</p>
> Here is an example of the app after launch and entering a URL
---
## ℹ️ About
This project was built using [![yt-dlp](https://img.shields.io/badge/yt--dlp-Visit%20Repo-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/yt-dlp/yt-dlp)
Thanks to them for providing a free and open-source API.
This project is open-source and welcomes your contributions.
Here is how to get involved:
- **🪲 Bug Reporting**
Please use the [GitHub issues system](https://github.com/MasterAcnolo/Freedom-Loader/issues) to report any bugs you encounter.
Try to provide clear reproduction steps and logs if possible. Logs are located in the installation folder of the program. Example path:
`C:\Users\[YOUR NAME]\Desktop\Freedom Loader\resources\app\logs`
The log file will be named something like `combined-20xx-xx-xx.log`
<br>
- **🛠️ Contribution**
Pull Requests are welcome! Please fork the repo and submit your PR after ensuring you meet the contribution guidelines (tests, style, etc.).
<br>
- **💡 Feature Suggestions**
You can propose new ideas via a [feature request issue](https://github.com/MasterAcnolo/Freedom-Loader/issues).
Any clear and detailed proposal will be reviewed.
<br>
- **💰 Support the Project**
<p>
If you'd like to support me, you can donate via
<a href="https://paypal.me/axelnicolas25">
<img src="https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white" alt="PayPal">
</a>
</p>
---
## 📄 License
This project is licensed under the **MIT License** — see the [LICENSE](./LICENSE) file for details.
---
## ⚠️ Disclaimer
I have obviously used AI assistance for some parts of this project. Use AI sparingly and always be aware of what you are doing.
</div>

160
SECURITY.md Normal file
View File

@@ -0,0 +1,160 @@
# Security Policy
## Supported Versions
Freedom Loader follows a **latest-only** support model. Security fixes are applied to the current release only — no backports to older versions.
| Version | Supported |
| ------------- | ------------------ |
| Latest stable | :white_check_mark: |
| Older versions | :x: |
I recommend always running the latest version. The built-in auto-updater will notify you when a new release is available. Older versions will not receive security patches regardless of severity.
## Reporting a Vulnerability
If you discover a security vulnerability in Freedom Loader, please help us keep the project and its users safe by reporting it responsibly.
### How to Report
**DO NOT** open a public issue for security vulnerabilities.
Instead, please report security issues privately by:
1. **Email**: Send details to **masteracnolo25@gmail.com** with the subject line: `[SECURITY] Vulnerability Report`
2. **Include**:
- Description of the vulnerability
- Steps to reproduce the issue
- Potential impact and severity
- Suggested fix (if available)
- Your contact information for follow-up
### What to Expect
- **Acknowledgment**: I will acknowledge receipt of your report within 48 hours
- **Assessment**: I will assess the vulnerability and determine its severity
- **Updates**: I will keep you informed of our progress
- **Resolution**: I aim to release a fix within 7-14 days for critical vulnerabilities
- **Credit**: I will credit you in the release notes (unless you prefer to remain anonymous)
## Security Best Practices for Users
When using Freedom Loader, I recommend:
### General Security
- **Keep Updated**: Always use the latest version of Freedom Loader — older versions receive no security patches
- **Official Sources**: Download only from official sources:
- [GitHub Releases](https://github.com/MasterAcnolo/Freedom-Loader/releases)
- [Fedora COPR](https://copr.fedorainfracloud.org/coprs/masteracnolo/freedom-loader/)
- [Snap Store](https://snapcraft.io/freedom-loader)
- **Verify Downloads**: Check that installers are from one of the above sources
- **Antivirus**: Keep your antivirus software up to date
### Configuration Security
- **Download Path**: Only set download paths within your user directory
- **Cookies**: Be aware that Firefox cookies are used for authentication — keep Firefox secure
- **Logs**: Logs may contain sensitive information such as URLs or local paths — avoid sharing them publicly without review
### Privacy Considerations
Freedom Loader respects your privacy:
- **No Data Collection**: I don't collect, store, or transmit your personal data
- **No Telemetry**: No usage tracking or analytics
- **Local Operation**: All downloads are processed locally on your machine
- **Optional Features**: Discord RPC is optional and can be disabled in settings
## Known Security Considerations
### Browser Cookie Access
Freedom Loader accesses Firefox cookies to download protected content. This is:
- **By Design**: Required for age-restricted or member-only content
- **Local Only**: Cookies are read locally and never transmitted outside your machine
- **User Controlled**: You control what content you download
### Native Dependencies
Freedom Loader bundles the following official binaries:
- **yt-dlp**: Official builds from [yt-dlp/yt-dlp](https://github.com/yt-dlp/yt-dlp) — auto-updated on each launch
- **FFmpeg**: Official builds from [FFmpeg.org](https://ffmpeg.org/)
- **Deno**: Official builds from [Deno.com](https://deno.com/)
These dependencies are verified and updated regularly.
### Windows Defender Warnings
New releases may trigger Windows Defender warnings because:
- The application is not yet widely distributed
- Code signing certificates are expensive for open-source projects
This is expected behavior for new releases and will diminish as more users download the software.
## Vulnerability Disclosure Policy
### Our Commitment
I am committed to:
- Responding promptly to security reports
- Working with security researchers to verify and address issues
- Keeping users informed about security updates
- Crediting researchers who report vulnerabilities responsibly
### Disclosure Timeline
1. **Day 0**: Vulnerability reported privately
2. **Day 1–2**: Acknowledgment sent to reporter
3. **Day 3–7**: Vulnerability assessed and fix developed
4. **Day 7–14**: Fix released (critical vulnerabilities prioritized)
5. **Day 14+**: Public disclosure (coordinated with reporter)
### Scope
Security issues we're most interested in:
- **Code Execution**: Arbitrary code execution vulnerabilities
- **Path Traversal**: Issues with file system access controls
- **Injection**: Command injection or similar vulnerabilities
- **Authentication**: Bypass of security controls
- **Data Exposure**: Unintended exposure of sensitive data
### Out of Scope
The following are generally not considered security vulnerabilities:
- Issues requiring physical access to the user's machine
- Social engineering attacks
- Denial of service against third-party services
- Issues in third-party dependencies (report to upstream projects directly)
- Missing security headers on the local HTTP server (not exposed remotely)
## Security Updates
Security updates are released as:
- **Patch Releases**: For critical security fixes (e.g., 1.6.0 → 1.6.1)
- **Automatic Updates**: Users are notified via the built-in update system
- **Release Notes**: Security fixes are clearly marked in the changelog
## Additional Resources
- [Contributing Guidelines](CONTRIBUTING.md)
- [Code of Conduct](CODE_OF_CONDUCT.md)
- [GitHub Security Advisories](https://github.com/MasterAcnolo/Freedom-Loader/security/advisories)
## Contact
For security-related questions or concerns:
- **Email**: masteracnolo25@gmail.com
- **GitHub**: [@MasterAcnolo](https://github.com/MasterAcnolo)
---
**Thank you for helping keep Freedom Loader and its users safe!**

163
app/autoUpdater.js Normal file
View File

@@ -0,0 +1,163 @@
/**
* Electron auto-update system using electron-updater.
*
* Handles:
* - update detection
* - user confirmation dialogs
* - download progress streaming to renderer
* - application restart and installation
*/
const { autoUpdater } = require("electron-updater");
const { logger } = require("../server/logger");
/**
* Configures autoUpdater behavior:
* - disables automatic download
* - disables automatic installation on quit
* (manual user-controlled update flow)
*/
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = false;
let updateAvailable = false;
/**
* Determines how the update should be handled based on the platform
* @returns {"auto" | "snap" | "flatpak" | "linux-native"}
*/
function getUpdateEnvironment() {
if (
process.env.APPIMAGE ||
process.platform === "win32" ||
process.platform === "darwin"
) {
return "auto";
}
if (process.env.SNAP) return "snap";
if (process.env.FLATPAK_ID) return "flatpak";
return "linux-native";
}
/**
* Initializes application auto-update lifecycle.
*
* Registers update event listeners and binds them to:
* - UI dialogs (Electron dialog)
* - renderer communication (webContents)
* - download/install flow control
*
* @param {BrowserWindow} mainWindow - main Electron window instance
*/
function initAutoUpdater(mainWindow) {
/**
* Triggered when a new version is detected.
* Prompts user to install or defer update.
*/
autoUpdater.on("update-available", async (info) => {
logger.info(`Update available: ${info.version}`);
mainWindow?.webContents.send("update-available", {
version: info.version,
environment: getUpdateEnvironment(),
});
updateAvailable = true;
});
/**
* Streams update download progress to renderer process.
* Used to update UI progress bar and status indicators.
*/
autoUpdater.on("download-progress", (progress) => {
logger.info(`Download progress: ${Math.round(progress.percent)}%`);
mainWindow?.webContents.send("update-progress", {
percent: progress.percent,
bytesPerSecond: progress.bytesPerSecond,
transferred: progress.transferred,
total: progress.total,
});
});
/**
* Triggered when update has been fully downloaded.
* Prompts user to restart application and install update.
*/
autoUpdater.on("update-downloaded", async (info) => {
logger.info(`Update downloaded: ${info.version}`);
mainWindow?.webContents.send("update-downloaded", info);
});
/**
* Handles update system errors.
* Logs failure and displays an error dialog to user.
*/
autoUpdater.on("error", (err) => {
const msg = err?.message || "";
/**
* If no update is available, I put this because there is no Linux version before 1.6.0
* @type {boolean}
*/
const isNoUpdateAvailable =
/404/.test(msg) || /Cannot find latest.*\.yml/i.test(msg);
if (isNoUpdateAvailable) {
logger.warn("Auto update: no update metadata found (probably no previous release), ignoring", msg);
return;
}
logger.error("Auto update error:", err);
});
checkForUpdates();
}
/**
* Manually triggers update check on startup.
* Separated from init for reusability and testability.
*/
async function checkForUpdates() {
if (process.env.SNAP || process.env.FLATPAK_ID) return;
if (!require("electron").app.isPackaged) return;
try {
await autoUpdater.checkForUpdates();
} catch (err) {
logger.error("Update check failed:", err.message);
}
}
/**
* Manually triggers update download.
* Used when user accepts update prompt.
*/
async function downloadUpdate() {
try {
await autoUpdater.downloadUpdate();
} catch (err) {
logger.error("Download failed:", err.message);
}
}
/**
* Immediately quits application and installs downloaded update.
*/
function installUpdate() {
autoUpdater.quitAndInstall();
}
function isUpdateAvailable() {
return updateAvailable;
}
module.exports = {
initAutoUpdater,
downloadUpdate,
installUpdate,
isUpdateAvailable,
};

67
app/dependencyCheck.js Normal file
View File

@@ -0,0 +1,67 @@
const fs = require("fs");
const { app, dialog } = require("electron");
const { logger } = require("../server/logger");
/**
* Verifies that all required native binaries exist before the app starts.
*
* This includes:
* - yt-dlp (download engine)
* - ffmpeg (media processing)
* - ffprobe (media inspection)
* - deno (JS runtime used by yt-dlp hooks)
*
* If any dependency is missing:
* - logs the error
* - shows a blocking Electron error dialog
* - exits the application
*
* @returns {boolean} true if all dependencies are present, false otherwise
*/
function checkNativeDependencies() {
/**
* Native dependency paths are resolved at runtime from the server helpers.
* This ensures correct resolution in both dev and packaged builds.
*/
const { binaryPaths } = require("../server/helpers/path.helpers");
const isWindows = process.platform === "win32";
const addExe = isWindows ? ".exe" : "";
/**
* List of required external executables used by the application runtime.
* Each entry defines a binary name and its resolved absolute path.
*/
const deps = [
{ name: `yt-dlp${addExe}`, path: binaryPaths.ytDlp },
{ name: `ffmpeg${addExe}`, path: binaryPaths.ffmpeg },
{ name: `ffprobe${addExe}`, path: binaryPaths.ffprobe },
{ name: `deno${addExe}`, path: binaryPaths.deno },
];
const missing = deps.filter(d => !fs.existsSync(d.path));
if (missing.length === 0) return true;
/**
* Human-readable list of missing binaries for logging and user dialog display.
*/
const list = missing.map(d => d.name).join(", ");
logger.error(`Missing dependencies: ${list}`);
/**
* Defers error dialog until Electron app is fully initialized.
* Required to safely display modal dialogs before quit.
*/
app.whenReady().then(() => {
dialog.showErrorBox(
"Missing dependencies",
`The following files are missing in the 'resources' folder:\n${list}\n\nThe application will now exit. Try to reinstall.`
);
app.quit();
});
return false;
}
module.exports = { checkNativeDependencies };

136
app/discordRPC.js Normal file
View File

@@ -0,0 +1,136 @@
const config = require('../config');
const RPC = require("discord-rpc");
const { logger } = require("../server/logger");
const fs = require("fs");
const path = require("path");
/**
* Discord Application Client ID used to authenticate the RPC connection.
* Comes from the application configuration.
*/
const clientId = `${config.DiscordRPCID}`;
/**
* Discord RPC client instance using IPC transport.
* Maintains a persistent connection with Discord desktop client.
*/
const rpc = new RPC.Client({ transport: "ipc" });
/**
* Optional interval reference used for future RPC refresh logic.
* Currently reserved for potential periodic activity updates.
*/
let intervalId;
/**
* Automagically fixes Discord RPC on Linux for Sandboxed versions (Flatpak & Snap).
* It creates a symlink from the sandbox IPC socket to the standard system location.
*
* @returns {void}
*/
function fixLinuxDiscordRPC() {
// Only execute on Linux
if (process.platform !== "linux") return;
/**
* Get the user's runtime directory (e.g., /run/user/1000).
* @type {string}
*/
const xdgDir = process.env.XDG_RUNTIME_DIR || `/run/user/${process.getuid()}`;
const standardSocket = path.join(xdgDir, "discord-ipc-0");
// If the standard socket already exists (Native Discord), do nothing.
if (fs.existsSync(standardSocket)) return;
/**
* Paths for known sandboxed Discord IPC sockets.
*/
const flatpakSocket = path.join(xdgDir, "app/com.discordapp.Discord/discord-ipc-0");
const snapSocket = path.join(xdgDir, "snap.discord/discord-ipc-0");
let targetSocket = null;
if (fs.existsSync(flatpakSocket)) {
targetSocket = flatpakSocket;
} else if (fs.existsSync(snapSocket)) {
targetSocket = snapSocket;
}
// If a sandboxed socket is found, create the symlink stealthily.
if (targetSocket) {
try {
fs.symlinkSync(targetSocket, standardSocket);
logger.info(`Discord RPC bridge created automatically targeting: ${targetSocket}`);
} catch (err) {
logger.warn(`Failed to create Discord RPC bridge: ${err.message}`);
}
}
}
/**
* Initializes Discord Rich Presence (RPC) connection.
*
* Responsibilities:
* - Connect to Discord via IPC transport
* - Set application presence (title, state, timestamps, assets)
* - Handle connection errors gracefully via logger
*
* Triggered once during application startup.
*/
function startRPC() {
/**
* Patch that create Simlinks for Linux environment;
*/
fixLinuxDiscordRPC();
/**
* Rich Presence payload describing current application state
* shown in Discord user profile.
*/
rpc.on("ready", () => {
const presence = {
largeImageKey: "icon",
smallImageKey: "acnolo_pfp",
smallImageText: "By MasterAcnolo",
startTimestamp: new Date(),
details: `Open Source Download Tools - ${config.version}`,
state: "masteracnolo.github.io/FreedomLoader",
};
rpc.clearActivity();
rpc.setActivity(presence);
});
rpc.login({ clientId }).catch(() => {
// Since 1.6.2, it no longer print the error. This is because if discord is not opened, it will send an error.
// logger.error("Unable to connect to the RPC:", err);
logger.info("Unable to connect to the Discord RPC. Discord is maybe not launched");
});
}
/**
* Gracefully stops Discord Rich Presence connection.
*
* Responsibilities:
* - Clear any running intervals
* - Remove current activity from Discord if connected
* - Destroy RPC transport connection
* - Handle cleanup errors safely
*/
async function stopRPC() {
if (rpc) {
try {
if (intervalId) clearInterval(intervalId);
await rpc.destroy();
} catch (err) {
// Since 1.6.2, it no longer print the error. This is because if discord is not opened, it will send an error.
// logger.error("Error while closing the RPC:", err);
}
}
}
module.exports = { startRPC, stopRPC};

290
app/ipcHandlers.js Normal file
View File

@@ -0,0 +1,290 @@
/**
* Electron IPC main process registry.
*
* Centralizes all IPC channels exposed to renderer process:
* - Application info (version, features)
* - Window controls (topbar actions)
* - File system interactions (theme, config, download paths)
* - UI state synchronization (progress bar)
*
* Acts as the bridge between renderer and privileged Node/Electron APIs.
*/
const { ipcMain, dialog, shell } = require("electron");
const fs = require("fs");
const { logger, logDir } = require("../server/logger");
const { configFeatures, featuresPath, devMode } = require("../config");
const { getThemes, reloadThemes } = require("./themeManager");
const config = require("../config");
const { validateDownloadPath, getDefaultDownloadPath } = require("./pathValidator");
const { userThemesPath } = require("../server/helpers/path.helpers");
const { createSystemTray, destroyTray } = require("./tray");
const { sendReport } = require("./sendReport");
const { isUpdateAvailable, downloadUpdate, installUpdate } = require("./autoUpdater");
/**
* Security whitelist for feature flags that can be modified at runtime.
*
* Prevents unauthorized or unexpected configuration keys from being persisted.
* Acts as a basic validation layer for IPC "set-feature".
*/
const FEATURE_WHITELIST = new Set([
"autoUpdate",
"systemTray",
"discordRPC",
"customTopBar",
"autoCheckInfo",
"addThumbnail",
"addMetadata",
"verboseLogs",
"autoDownloadPlaylist",
"keepPlaylistOrder",
"customCodec",
"theme",
"createPlaylistFolders",
"notifySystem",
"enableHardwareAcceleration"
]);
/**
* Absolute path to the configuration file storing feature flags.
*/
const configFolderPath = featuresPath;
/**
* Directory containing user-installed themes.
*/
const themeFolderPath = userThemesPath;
/**
* Registers all IPC handlers and event listeners for Electron main process.
*
* This function wires renderer → main communication channels:
* - synchronous queries (handle)
* - fire-and-forget events (on)
*
* @param {Function} getMainWindow - Function returning current BrowserWindow instance
*/
function registerIpcHandlers(getMainWindow) {
/**
* Returns application version from config.
*/
ipcMain.handle("version", () => config.version);
/**
* Returns runtime feature configuration object.
*/
ipcMain.handle("features", () => configFeatures);
/**
* Opens native folder selection dialog and validates selected path.
*
* Ensures:
* - user cancellation is handled safely
* - selected path is validated against security rules
* - unsafe directories are rejected
*/
ipcMain.handle("select-download-folder", async () => {
const result = await dialog.showOpenDialog({ properties: ["openDirectory"] });
if (result.canceled) {
logger.info("Folder selection cancelled by user");
return null;
}
try {
const validated = validateDownloadPath(result.filePaths[0]);
logger.info(`Folder selected and validated: ${validated}`);
return validated;
} catch (err) {
logger.warn(`Unsafe or invalid folder rejected: ${err.message}`);
throw err;
}
});
ipcMain.handle("validate-download-path", (_, userPath) => validateDownloadPath(userPath));
ipcMain.handle("get-default-download-path", () => getDefaultDownloadPath());
/**
* Updates Windows/macOS taskbar progress indicator.
*
* @param {number} percent - Progress value (0–100)
*/
ipcMain.on("set-progress", (_, percent) => {
getMainWindow()?.setProgressBar(percent / 100);
});
/**
* Window minimize request from renderer.
*/
ipcMain.on("window-minimize", () => {
// Minimize to tray
if (configFeatures.systemTray) {
getMainWindow()?.hide();
logger.info("Window Minimized in SystemTray (Using Minimize Button)");
} else {
// Native minimize
getMainWindow()?.minimize();
logger.info("Window minimized normally");
}
});
/**
* Toggles maximize/unmaximize state of main window.
*/
ipcMain.on("window-maximize", () => {
const win = getMainWindow();
if (!win) return;
win.isMaximized() ? win.unmaximize() : win.maximize();
});
/**
* Closes the main application window.
*/
ipcMain.on("window-close", () => getMainWindow()?.close());
/**
* Opens Chromium DevTools in detached mode.
*/
ipcMain.on("open-devtools", () =>
getMainWindow()?.webContents.openDevTools({ mode: "detach" })
);
/**
* Opens application logs directory in system file explorer.
*/
ipcMain.on("open-logs", () => logDir && shell.openPath(logDir));
/**
* Opens external website in default browser.
*/
ipcMain.on("open-website", () =>
shell.openExternal("https://masteracnolo.github.io/Freedom-Loader-Site/")
);
/**
* Opens official wiki page in external browser.
*/
ipcMain.on("open-wiki", () =>
shell.openExternal("https://masteracnolo.github.io/Freedom-Loader-Site/wiki")
);
/**
* Opens workshop page in external browser.
*/
ipcMain.on("open-workshop", () =>
shell.openExternal("https://masteracnolo.github.io/Freedom-Loader-Workshop")
);
/**
* Opens configuration folder in system file explorer.
*/
ipcMain.on("open-config", () => shell.openPath(configFolderPath));
/**
* Front end logger
*/
ipcMain.on("log-error", (_, message) => logger.error(`[Frontend] ${message}`));
ipcMain.on("log-info", (_, message) => logger.info(`[Frontend] ${message}`));
ipcMain.on("log-warn", (_, message) => logger.warn(`[Frontend] ${message}`));
/**
* Retrieves available themes from filesystem.
*/
ipcMain.handle("get-themes", () => getThemes());
/**
* Opens theme directory in file explorer.
*/
ipcMain.on("open-theme", () => shell.openPath(themeFolderPath));
/**
* Reloads themes from disk dynamically.
*/
ipcMain.handle("reload-themes", async () => {
return await reloadThemes();
});
/**
* Send bug report
*/
ipcMain.handle("send-report", async (_, params) => {
return await sendReport(params);
});
ipcMain.handle("is-update-available", () => {
return isUpdateAvailable();
});
ipcMain.handle("download-update", async () => {
return await downloadUpdate();
});
ipcMain.handle("install-update", async () => {
return installUpdate();
});
ipcMain.on("open-release-page", () => {
shell.openExternal(
"https://github.com/MasterAcnolo/Freedom-Loader/releases/latest",
);
});
/**
* Updates a runtime feature flag and persists it to disk.
*
* Flow:
* - Validates key against whitelist
* - Prevents unnecessary writes if value is unchanged
* - Updates in-memory config object
* - Persists to configuration file
*
* Security note:
* Only whitelisted keys can be modified via IPC to prevent config injection.
*
* @param {Electron.IpcMainEvent} event
* @param {{key: string, value: any}} payload
* @returns {boolean} success state
*/
ipcMain.handle("set-feature", (event, { key, value }) => {
try {
if (!FEATURE_WHITELIST.has(key)) {
logger.warn(`Rejected feature (not whitelisted): ${key}`);
return false;
}
if (configFeatures[key] === value) {
return true;
}
/**
* Dynamically intercepts and applies System Tray state changes at runtime.
* Instantiates or destroys the tray icon immediately to prevent window
* lifecycle desynchronization (e.g., minimizing a window to a non-existent tray).
*/
if (key === "systemTray") {
if (value === true) {
logger.info("System Tray enabled dynamically.");
createSystemTray(devMode);
} else {
logger.info("System Tray disabled dynamically.");
destroyTray();
}
}
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;
}
});
}
module.exports = { registerIpcHandlers };

85
app/pathValidator.js Normal file
View File

@@ -0,0 +1,85 @@
const fs = require("fs");
const path = require("path");
const { logger } = require("../server/logger");
/**
* Cached resolved default download path.
* Initialized lazily on first access to avoid unnecessary imports.
*/
let _defaultPath = null;
/**
* Returns the application's default download directory.
*
* This value is lazily loaded and cached after first call.
* The path originates from server configuration helpers.
*
* @returns {string} Absolute default download path
*/
function getDefaultDownloadPath() {
if (!_defaultPath) {
/**
* Lazy-loaded dependency providing the configured default download folder.
* Required only when function is executed to reduce startup cost.
*/
const { defaultDownloadFolder } = require("../server/helpers/path.helpers");
_defaultPath = defaultDownloadFolder;
}
return _defaultPath;
}
/**
* Validates and sanitizes a user-provided download path.
*
* Steps performed:
* - Resolves absolute real filesystem path
* - Normalizes symbolic links via realpathSync
* - Checks path safety rules (blocks system-critical directories)
* - Falls back to default path if input is empty
*
* @param {string} userPath - User-provided directory path
* @returns {string} Sanitized absolute path
* @throws Error If path is invalid, unsafe, or inaccessible
*/
function validateDownloadPath(userPath) {
/**
* Lazy-loaded helper used to enforce safe directory constraints.
* Prevents writes to protected system folders.
*/
const { isSafePath } = require("../server/helpers/validation.helpers");
if (!userPath) return getDefaultDownloadPath();
try {
const absolutePath = path.resolve(userPath);
if (!fs.existsSync(absolutePath)) {
fs.mkdirSync(absolutePath, { recursive: true });
logger.info(`Download folder created: ${absolutePath}`);
}
const real = fs.realpathSync(absolutePath);
if (!isSafePath(real)) {
logger.error("Path not allowed: system folders are blocked!");
}
return real;
}
catch (err) {
/**
* Handles invalid filesystem paths, permissions issues, or resolution failures.
* Logs diagnostic information before propagating a sanitized error.
*/
logger.error(`Invalid download path: ${userPath} — ${err.message}`);
throw new Error(`Invalid or inaccessible path: ${err.message}`);
}
}
module.exports = { validateDownloadPath, getDefaultDownloadPath };

59
app/sendReport.js Normal file
View File

@@ -0,0 +1,59 @@
const fs = require("fs");
const path = require("path");
const {logger, logDir} = require("../server/logger");
async function sendReport(params) {
try {
const {title, description, includeLogs} = params;
if (!title || !description) {
throw new Error("Title and description are required.");
}
const formData = new FormData();
formData.append("title", title);
formData.append("context", description);
if (includeLogs === "yes" && logDir) {
// YYYY-MM-DD
const now = new Date();
const today = [
now.getFullYear(),
String(now.getMonth() + 1).padStart(2, "0"),
String(now.getDate()).padStart(2, "0")
].join("-");
const logFilePath = path.join(logDir, `LOGS-${today}.log`);
if (fs.existsSync(logFilePath)) {
const logFileContent = fs.readFileSync(logFilePath);
const blob = new Blob([logFileContent], {type: "text/plain"});
formData.append("file", blob, `LOGS-${today}.log`);
logger.info("Logs attached to bug report.");
} else {
logger.warn(`Log file not found for today: ${logFilePath}`);
}
}
const response = await fetch(process.env.BUG_REPORT_URL, {
method: "POST",
headers: {
"X-Api-Key": process.env.BUG_REPORT_API_KEY
},
body: formData
});
if (!response.ok) {
logger.error(`Server returned an error: ${response.status}`);
throw new Error(`Server responded with status ${response.status}`);
}
logger.info("Bug report successfully sent.");
return true;
} catch (err) {
logger.error(`Failed to send bug report: ${err.message}`);
throw err;
}
}
module.exports = {sendReport};

107
app/splashManager.js Normal file
View File

@@ -0,0 +1,107 @@
const { BrowserWindow, app } = require("electron");
const path = require("path");
/**
* Reference to the splash screen BrowserWindow instance.
* Used during application startup before main window is ready.
*/
let splashWindow = null;
/**
* Creates and displays the splash screen window.
*
* Responsibilities:
* - Initializes a frameless, always-on-top splash window
* - Loads splash HTML UI
* - Dynamically injects banner image depending on dev/production mode
* - Acts as startup visual feedback before main window loads
*/
function createSplashWindow() {
/**
* Configuration for splash screen BrowserWindow.
* Optimized for minimal UI and fast startup display.
*/
const splashOptions = {
width: 400,
height: 300,
frame: false,
transparent: true,
alwaysOnTop: true,
resizable: false,
skipTaskbar: true,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
},
};
/**
* Instantiates the splash screen window.
*/
splashWindow = new BrowserWindow(splashOptions);
/**
* Absolute path to splash HTML file used as initial UI.
* Loaded locally from application bundle or project folder.
*/
const splashPath = path.join(__dirname, "../public/splash.html");
splashWindow.loadFile(splashPath);
/**
* Executes once splash HTML is fully loaded.
* Used here to inject dynamic assets (banner image path).
*/
splashWindow.webContents.on('did-finish-load', () => {
/**
* Resolves splash banner image path depending on runtime mode:
* - Production: bundled resources directory
* - Development: local build directory
*/
let bannerPath;
// Check if app is packaged
if (app.isPackaged) {
bannerPath = path.join(process.resourcesPath, 'banner.png');
} else {
// In dev, use build folder
bannerPath = path.join(__dirname, '../build/banner.png');
}
/**
* Injects runtime image source into splash DOM.
* Uses file:// protocol to load local image safely in Electron context.
*/
splashWindow.webContents.executeJavaScript(`
document.querySelector('img[alt="Freedom Loader"]').src = 'file:///${bannerPath.replace(/\\/g, '/')}';
`);
});
}
/**
* Closes and cleans up the splash screen window.
*
* Should be called once main window is ready to display.
*/
function closeSplashWindow() {
if (splashWindow) {
splashWindow.close();
splashWindow = null;
}
}
/**
* Updates splash screen progress indicator from main process.
*
* Sends a step value to renderer via injected JS function.
*
* @param {number} step - Current initialization step or progress value
*/
function setSplashProgress(step) {
if (splashWindow) {
splashWindow.webContents.executeJavaScript(`window.setProgress(${step})`);
}
}
module.exports = { createSplashWindow, closeSplashWindow, setSplashProgress };

337
app/themeManager.js Normal file
View File

@@ -0,0 +1,337 @@
/**
* Theme loading and parsing system.
*
* Supports:
* - Folder-based themes
* - ZIP-based themes
* - Theme validation against required schema
* - Image embedding as base64 data URIs
* - Cached theme registry with sorted priority order
*
* Acts as the core theme ingestion pipeline for the application UI.
*/
const fs = require("fs");
const path = require("path");
const { logger } = require("../server/logger");
const JSZip = require("jszip");
/**
* Maximum allowed size (in bytes) for theme images.
* Prevents memory abuse from large embedded assets.
*/
const MAX_IMAGE_SIZE = 10 * 1024 * 1024;
/**
* List of supported image file extensions for theme assets.
*/
const ALLOWED_IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".webp", ".gif", ".avif"];
/**
* Allowed base filenames for theme background/cover images.
* Restricts theme asset resolution to known conventions.
*/
const ALLOWED_IMAGE_NAMES = ["cover", "background"];
/**
* Priority order used to sort themes in UI.
* Themes not listed are placed at the end.
*/
const THEME_ORDER = ["dark", "light"];
/**
* Required JSON schema paths for a valid theme definition.
* Each entry represents a nested property path that must exist.
*/
const REQUIRED_KEYS = [
["meta", "name"],
["meta", "author"],
["meta", "version"],
["meta", "formatVersion"],
["style", "colors", "background"],
["style", "colors", "text", "default"],
["style", "form", "button", "background"],
["style", "progressBar", "fill"],
];
/**
* Safely retrieves a deeply nested value from an object using a key path array.
*
* @param {object} obj - Target object
* @param {string[]} keys - Property path segments
* @returns {*} value or undefined if path is invalid
*/
function getNestedValue(obj, keys) {
return keys.reduce((acc, key) => acc?.[key], obj);
}
/**
* Validates theme JSON structure against required schema.
*
* Ensures all mandatory configuration keys exist before theme is accepted.
*
* @param {object} json - Parsed theme JSON
* @returns {{valid: boolean, reason?: string}}
*/
function validateThemeJson(json) {
for (const keyPath of REQUIRED_KEYS) {
if (getNestedValue(json, keyPath) === undefined) {
return { valid: false, reason: `Missing key: ${keyPath.join(".")}` };
}
}
return { valid: true };
}
/**
* Normalizes raw theme JSON into application-ready theme object.
*
* @param {string} themeId
* @param {object} themeJson
* @param {string|null} imageData - Base64 encoded theme image
* @returns {object} normalized theme object
*/
function buildThemeObject(themeId, themeJson, imageData) {
return {
id: themeId.toLowerCase(),
name: themeJson.meta.name || themeId,
author: themeJson.meta.author || "Unknown",
version: themeJson.meta.version,
subtitle: themeJson.meta.subtitle || "",
style: themeJson.style,
image: imageData,
};
}
/**
* Extracts and encodes an image from theme archive into base64 data URI.
*
* Applies safety checks:
* - Rejects images exceeding MAX_IMAGE_SIZE
* - Converts file extension into valid MIME type
*
* @param {Buffer} buffer
* @param {string} filename
* @returns {string|null} data URI or null if invalid
*/
function extractImage(buffer, filename) {
if (buffer.length > MAX_IMAGE_SIZE) {
logger.warn(`Image too large, ignoring`);
return null;
}
const ext = path.extname(filename).toLowerCase().replace(".", "");
const mime = ext === "jpg" || ext === "jpeg" ? "jpeg" : ext;
return `data:image/${mime};base64,${buffer.toString("base64")}`;
}
/**
* Loads a theme from a ZIP archive.
*
* Steps:
* - Extract ZIP content
* - Locate .theme.json file
* - Validate JSON structure
* - Extract optional image asset
* - Build normalized theme object
*
* @param {string} zipPath
* @param {string} themeId
* @returns {Promise<object|null>}
*/
async function loadThemeFromZip(zipPath, themeId) {
try {
const zipBuffer = fs.readFileSync(zipPath);
const zip = await JSZip.loadAsync(zipBuffer);
const jsonFile = Object.keys(zip.files).find(f => f.endsWith(".theme.json"));
if (!jsonFile) { logger.warn(`Theme ${themeId}: no .theme.json, skipping`); return null; }
let themeJson;
try { themeJson = JSON.parse(await zip.files[jsonFile].async("string")); }
catch { logger.warn(`Theme ${themeId}: invalid JSON, skipping`); return null; }
const validation = validateThemeJson(themeJson);
if (!validation.valid) { logger.warn(`Theme ${themeId}: ${validation.reason}, skipping`); return null; }
let imageData = null;
const imageFile = Object.keys(zip.files).find(f => {
const ext = path.extname(f).toLowerCase();
const name = path.basename(f, ext).toLowerCase();
return ALLOWED_IMAGE_NAMES.includes(name) && ALLOWED_IMAGE_EXTENSIONS.includes(ext);
});
if (imageFile) {
const buf = await zip.files[imageFile].async("nodebuffer");
imageData = extractImage(buf, imageFile);
}
logger.info(`Theme loaded (zip): ${themeId}`);
return buildThemeObject(themeId, themeJson, imageData);
} catch (err) {
logger.warn(`Theme ${themeId}: failed to load zip — ${err.message}`);
return null;
}
}
/**
* Loads a theme from a directory structure.
*
* Equivalent to ZIP loader but works on filesystem directly.
*
* @param {string} folderPath
* @param {string} themeId
* @returns {Promise<object|null>}
*/
async function loadThemeFromFolder(folderPath, themeId) {
try {
const jsonFile = fs.readdirSync(folderPath).find(f => f.endsWith(".theme.json"));
if (!jsonFile) { logger.warn(`Theme ${themeId}: no .theme.json, skipping`); return null; }
let themeJson;
try { themeJson = JSON.parse(fs.readFileSync(path.join(folderPath, jsonFile), "utf-8")); }
catch { logger.warn(`Theme ${themeId}: invalid JSON, skipping`); return null; }
const validation = validateThemeJson(themeJson);
if (!validation.valid) { logger.warn(`Theme ${themeId}: ${validation.reason}, skipping`); return null; }
let imageData = null;
const imageFile = fs.readdirSync(folderPath).find(f => {
const ext = path.extname(f).toLowerCase();
const name = path.basename(f, ext).toLowerCase();
return ALLOWED_IMAGE_NAMES.includes(name) && ALLOWED_IMAGE_EXTENSIONS.includes(ext);
});
if (imageFile) {
const buf = fs.readFileSync(path.join(folderPath, imageFile));
imageData = extractImage(buf, imageFile);
}
logger.info(`Theme loaded (folder): ${themeId}`);
return buildThemeObject(themeId, themeJson, imageData);
} catch (err) {
logger.warn(`Theme ${themeId}: failed to load folder — ${err.message}`);
return null;
}
}
/**
* In-memory cache of loaded themes.
* Avoids re-parsing filesystem on every request.
*/
let cachedThemes = null;
/**
* Active theme directory path used as source of truth.
*/
let themeFolderPath = null;
/**
* Sets the active theme directory path.
*
* @param {string} folderPath
*/
function setThemeFolderPath(folderPath) {
themeFolderPath = folderPath;
}
/**
* Loads all themes from the configured theme directory.
*
* Supports:
* - ZIP themes (.zip)
* - Folder themes (directory-based)
*
* Iterates through filesystem entries and delegates parsing
* to the appropriate loader (ZIP or folder).
*
* @returns {Promise<object[]>} List of valid theme objects
*/
async function loadThemesFromFolder() {
const themes = [];
if (!fs.existsSync(themeFolderPath)) {
logger.warn(`Theme folder not found: ${themeFolderPath}`);
return themes;
}
const files = fs.readdirSync(themeFolderPath);
for (const file of files) {
const filePath = path.join(themeFolderPath, file);
const themeId = path.basename(file, path.extname(file));
if (file.endsWith(".zip")) {
const theme = await loadThemeFromZip(filePath, themeId);
if (theme) themes.push(theme);
continue;
}
if (fs.statSync(filePath).isDirectory()) {
const theme = await loadThemeFromFolder(filePath, themeId);
if (theme) themes.push(theme);
}
}
return themes;
}
/**
* Returns sorting priority for a theme ID.
*
* Lower index = higher priority.
* Themes not in THEME_ORDER are placed at the end.
*
* @param {string} id
* @returns {number}
*/
function getThemeOrder(id) {
const index = THEME_ORDER.indexOf(id);
return index === -1 ? Infinity : index;
}
/**
* Initializes the theme system.
*
* Steps:
* - Sets theme folder path
* - Loads all themes from disk
* - Sorts themes by priority order
* - Caches result in memory
*
* @param {string} folderPath
*/
async function initThemes(folderPath) {
setThemeFolderPath(folderPath);
const themes = await loadThemesFromFolder();
logger.info(`Themes before sort: ${themes.map(t => t.id).join(", ")}`);
cachedThemes = themes.sort((a, b) => getThemeOrder(a.id) - getThemeOrder(b.id));
logger.info(`Themes after sort: ${cachedThemes.map(t => t.id).join(", ")}`);
}
/**
* Returns cached themes.
*
* If themes are not loaded yet, returns empty array.
*
* @returns {object[]}
*/
function getThemes() {
return cachedThemes ?? [];
}
/**
* Reloads themes from disk and refreshes cache.
*
* Used when themes are modified at runtime without restart.
*
* @returns {Promise<object[]>}
*/
async function reloadThemes() {
const themes = await loadThemesFromFolder();
cachedThemes = themes.sort((a, b) => getThemeOrder(a.id) - getThemeOrder(b.id));
logger.info(`Themes reloaded: ${cachedThemes.length} theme(s)`);
return cachedThemes;
}
module.exports = { initThemes, getThemes , reloadThemes };

117
app/tray.js Normal file
View File

@@ -0,0 +1,117 @@
const {app, Menu, Tray, nativeImage} = require("electron");
const path = require("path");
const {logger} = require("../server/logger"); // Ajuste le chemin si besoin
const fs = require("fs");
const {getMainWindow} = require("./windowManager");
/**
* Global reference to the Tray instance to prevent garbage collection.
* @type {Tray | null}
*/
let tray = null;
/**
* Creates and configures the cross-platform System Tray.
*
* Responsibilities:
* - Loads the appropriate icon format based on the operating system.
* - Builds the right-click context menu.
* - Handles left-click behavior to toggle main window visibility.
*
* @param {boolean} devMode - Indicates whether the application is running in development mode.
* @returns {Tray} The created Tray instance.
*/
function createSystemTray(devMode) {
// Prevent creating multiple tray instances
if (tray) return tray;
const isWin = process.platform === "win32";
const iconName = isWin ? "app-icon.ico" : "app-icon-64x64.png";
// Attempt to load the icon from the local build directory first
let iconPath = path.join(__dirname, "..", "build", iconName);
// Fallback to the extracted resources path if running in production
if (!devMode && !fs.existsSync(iconPath)) {
iconPath = path.join(process.resourcesPath, iconName);
}
if (!fs.existsSync(iconPath)) {
logger.error(`System Tray icon not found at resolved path: ${iconPath}`);
} else {
logger.info("System Tray icon resolved successfully.");
}
const icon = nativeImage.createFromPath(iconPath);
if (icon.isEmpty()) {
logger.error("System Tray icon file was found but could not be decoded by Electron (image is empty).");
}
tray = new Tray(icon);
tray.setToolTip("Freedom Loader");
/**
* System Tray Context Menu (Right-click action).
*/
const contextMenu = Menu.buildFromTemplate([
{
label: "Show Freedom Loader",
click: () => {
const window = getMainWindow();
if (window && !window.isDestroyed()) {
window.show();
window.focus();
logger.info("Window Restored from SystemTray (Using Show Action)");
}
},
},
{ type: "separator" },
{
label: "Quit",
click: () => {
/**
* Flags the application for a deliberate exit,
* bypassing the default "minimize to tray" behavior.
*/
app.isQuitting = true;
app.quit();
},
},
]);
tray.setContextMenu(contextMenu);
/**
* Left-click behavior: Toggles main window visibility.
* but it remains standard practice for Windows and Linux environments.
*/
tray.on("click", () => {
const window = getMainWindow();
if (!window || window.isDestroyed()) return;
if (window.isVisible()) {
window.hide();
} else {
window.show();
window.focus();
logger.info("Window Restored from SystemTray (Double Click on Tray Icon)");
}
});
return tray;
}
/**
* Safely destroys the tray icon to prevent OS-level crashes (especially on Linux/Wayland).
* Should be called right before the application quits.
*/
function destroyTray() {
if (tray && !tray.isDestroyed()) {
tray.destroy();
tray = null;
logger.info("System Tray destroyed cleanly.");
}
}
module.exports = {createSystemTray, destroyTray};

123
app/windowManager.js Normal file
View File

@@ -0,0 +1,123 @@
/**
* Electron main window module dependencies.
* Handles BrowserWindow lifecycle and application UI shell.
*/
const { BrowserWindow, app } = require("electron");
const path = require("path");
const { logger } = require("../server/logger");
const { configFeatures } = require("../config");
const config = require("../config");
/**
* Singleton reference to the main Electron BrowserWindow instance.
* Ensures only one application window exists at runtime.
*/
let mainWindow = null;
/**
* Creates and initializes the main Electron application window.
*
* Responsibilities:
* - Ensures singleton window instance (prevents duplicates)
* - Configures window options (size, icon, preload, security settings)
* - Loads frontend application from local dev server
* - Handles lifecycle events (close, errors)
*
* @returns {Promise<BrowserWindow>} The created or existing window instance
*/
async function createMainWindow() {
/**
* Prevents multiple instances of the main window.
* Returns existing instance if already created.
*/
if (mainWindow) {
logger.warn("Window already exists, no new creation!");
return mainWindow;
}
/**
* Resolves application icon path depending on runtime mode:
* - Development: local build folder
* - Production: packaged Electron resources
*/
const iconPath = config.devMode
? path.join(__dirname, "../build/app-icon.ico")
: path.join(process.resourcesPath, "build/app-icon.ico");
/**
* Electron BrowserWindow configuration object.
* Defines UI behavior, security settings, and preload bridge.
*/
const windowOptions = {
title: `Freedom Loader ${config.version}`,
icon: iconPath,
width: 750,
height: 800,
minWidth: 750,
minHeight: 800,
frame: !configFeatures.customTopBar,
devTools: !app.isPackaged,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, "../preload.js"),
},
};
mainWindow = new BrowserWindow(windowOptions);
/**
* Loads the frontend application served by the local dev server.
* In production, this could be replaced with a file:// build.
*/
try {
await mainWindow.loadURL(`http://localhost:${config.applicationPort}`);
logger.info("Window loaded");
} catch (err) {
logger.error("Window loading error:", err);
throw err;
}
/**
* Intercepts the window close event.
* If the app is not explicitly quitting (via Tray menu),
* cancels the destruction and hides the window instead.
*/
mainWindow.on('close', (event) => {
if (!app.isQuitting && config.configFeatures.systemTray) {
event.preventDefault();
mainWindow.hide();
logger.info("Window Minimized in SystemTray (Using Close Button)");
return false;
}
});
/**
* Cleans up window reference when the main window is closed.
* Prevents memory leaks and allows recreation if needed.
*/
mainWindow.on("closed", () => {
logger.info("Main window closed");
mainWindow = null;
});
return mainWindow;
}
/**
* Returns the current main window instance.
*
* Useful for IPC handlers and background services
* needing access to the renderer process.
*
* @returns {BrowserWindow | null}
*/
function getMainWindow() {
return mainWindow;
}
module.exports = { createMainWindow, getMainWindow };

35
app/ytDlpUpdater.js Normal file
View File

@@ -0,0 +1,35 @@
const { execFile } = require("child_process");
const { logger } = require("../server/logger");
/**
* Attempts to update the local yt-dlp binary using its built-in update command.
*
* This function:
* - Executes yt-dlp with the `-U` flag
* - Logs update output or warnings if update fails
* - Never throws: update failure is non-blocking by design
*
* @param {string} ytDlpPath - Absolute path to the yt-dlp executable
*/
function updateYtDlp(ytDlpPath) {
logger.info("yt-dlp update check starting...");
/**
* Executes yt-dlp CLI process in a non-interactive mode.
* Used here specifically for self-update via `-U` flag.
*/
execFile(ytDlpPath, ["-U"], (err, stdout) => {
/**
* Handles yt-dlp update result:
* - Logs warning if update fails (network, binary issues, permissions)
* - Logs stdout output when update succeeds
*/
if (err) logger.warn("yt-dlp update failed (continuing):", err.message);
else logger.info(`yt-dlp update: ${stdout.trim()}`);
});
}
module.exports = { updateYtDlp };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

BIN
build/app-icon-64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
build/app-icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
build/app-icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 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

131
config.js Normal file
View File

@@ -0,0 +1,131 @@
const packageJson = require("./package.json");
const { app } = require("electron");
const fs = require("fs");
const path = require("path");
/**
* Indicates whether the application is running in development mode.
* Determined by Electron's packaging state.
*/
const devMode = !app.isPackaged || process.env.NODE_ENV === "test";
/**
* Resolves the configuration file path depending on runtime environment.
*
* In development:
* - Uses local config/config.dev.json
* - Creates it from default if missing
*
* In production:
* - Uses app userData/config.json
* - Creates it from bundled default config if missing
*
* @returns {string} Absolute path to the active configuration file
*/
function resolveConfigPath() {
if (devMode) {
const devConfigPath = path.join(__dirname, "config", "config.dev.json");
if (!fs.existsSync(devConfigPath)) {
const defaultConfigPath = path.join(
__dirname,
"config",
"config.default.json"
);
fs.copyFileSync(defaultConfigPath, devConfigPath);
}
return devConfigPath;
}
const userConfigPath = path.join(app.getPath("userData"), "config.json");
if (!fs.existsSync(userConfigPath)) {
const defaultConfigPath = path.join(
process.resourcesPath,
"config",
"config.default.json"
);
fs.copyFileSync(defaultConfigPath, userConfigPath);
}
return userConfigPath;
}
/**
* Path to the active configuration file used at runtime.
*/
const featuresPath = resolveConfigPath();
/**
* Loads and parses the feature configuration file.
*
* @returns {Object} Parsed feature flags configuration
* @throws {Error} If the configuration file is missing or invalid
*/
function loadFeatures() {
const raw = fs.readFileSync(featuresPath, "utf-8");
return JSON.parse(raw);
}
/**
* Reloads feature flags from disk.
* Use this instead of the cached configFeatures snapshot
* when you need the latest user settings at call time.
*
* @returns {Object} Fresh feature flags from config file
*/
function reloadFeatures() {
try {
const raw = fs.readFileSync(featuresPath, "utf-8");
return JSON.parse(raw);
} catch (err) {
// Fallback to cached snapshot if file read fails
return configFeatures;
}
}
/**
* In-memory snapshot of application feature flags.
*
* Note: Changes to the config file are not automatically reflected.
* A restart or reload is required.
*/
let configFeatures = loadFeatures();
module.exports = {
/**
* Application version from package.json
*/
version: devMode ? `dev-${packageJson.version}` : `v${packageJson.version}` ,
/**
* Backend Express / internal server port
*/
applicationPort: "8787",
/**
* Indicates whether the app is running in development mode
*/
devMode,
/**
* Discord RPC application identifier
*/
DiscordRPCID: "1410934537051181146",
/**
* Runtime feature flags loaded from configuration file
*/
configFeatures,
/**
* Function to reload the config
*/
reloadFeatures,
/**
* Path to the active configuration file
*/
featuresPath,
};

View File

@@ -0,0 +1,20 @@
{
"autoUpdate": true,
"systemTray": false,
"discordRPC": true,
"customTopBar": true,
"autoCheckInfo": true,
"addThumbnail": true,
"addMetadata": true,
"verboseLogs": false,
"autoDownloadPlaylist": true,
"keepPlaylistOrder": false,
"createPlaylistFolders": false,
"customCodec": "h264",
"logSystem": true,
"outputTitleCheck": true,
"downloadSystem": true,
"notifySystem": true,
"enableHardwareAcceleration": true,
"theme": "dark"
}

40
eslint.config.js Normal file
View File

@@ -0,0 +1,40 @@
const js = require("@eslint/js");
const globals = require("globals");
module.exports = [
js.configs.recommended,
{ignores: ["node_modules/**", "srpm-out/**", "dist/**", "test/**"]},
{
languageOptions: {
ecmaVersion: "latest",
sourceType: "commonjs",
},
rules: {
"no-unused-vars": ["error", {argsIgnorePattern: "^_"}],
"no-console": "off",
"semi": ["error", "always"]
},
},
{
files: ["app/**", "server/**", "main.js"],
languageOptions: {
globals: {...globals.node}
}
},
{
files: ["public/**"],
languageOptions: {
globals: {...globals.browser}
}
},
{
files: ["preload.js"],
languageOptions: {
globals: {...globals.node, ...globals.browser}
}
}
];

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,
}),
],
};

21
jest.config.js Normal file
View File

@@ -0,0 +1,21 @@
module.exports = {
testEnvironment: "node",
testMatch: ["**/test/unit/**/*.test.js"],
collectCoverageFrom: [
"server/**/*.js",
"app/**/*.js",
"!app/autoUpdater.js",
],
modulePathIgnorePatterns: [
"<rootDir>/dist",
"<rootDir>/release",
"<rootDir>/build",
],
watchPathIgnorePatterns: [
"<rootDir>/dist",
"<rootDir>/release",
"<rootDir>/build",
],
};

312
main.js
View File

@@ -1,165 +1,199 @@
/*
This file is part of Freedom Loader.
/**
* Handle crash application and avoid ghost instance
*/
const { app } = require("electron");
Copyright (C) 2025 MasterAcnolo
process.on("uncaughtException", (err) => {
console.error("Uncaught exception:", err);
app.quit();
});
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.
process.on("unhandledRejection", (reason) => {
console.error("Unhandled rejection:", reason);
app.quit();
});
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/>.
*/
/**
* True if this is the primary instance (lock acquired successfully)
*/
const isPrimaryInstance = app.requestSingleInstanceLock();
const { app, BrowserWindow, ipcMain, dialog, Menu, shell } = require("electron");
app.disableHardwareAcceleration(); // Désactive l'accélération GPU pour éviter les erreurs GPU sur certains systèmes
const path = require("path");
const os = require("os");
const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger");
let mainWindow;
// Utilise le vrai dossier de logs défini dans logger.js
const logsFolderPath = logDir;
/*
Fonction principale qui crée la fenêtre principale de l'application.
Elle évite la création multiple et configure les dimensions et options de la fenêtre.
Charge l'URL locale du serveur Express et gère les erreurs éventuelles.
*/
async function createWindow() {
logger.info("Création de la fenêtre...");
if (mainWindow) {
logger.warn("La fenêtre existe déjà, pas de nouvelle création");
return;
}
mainWindow = new BrowserWindow({
title: "Freedom Loader",
width: 750,
height: 800,
minWidth: 750,
minHeight: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, "preload.js"), // Chargement du preload script sécurisé
},
// titleBarStyle: 'hidden', // Option possible pour une barre de titre personnalisée
});
try {
await mainWindow.loadURL("http://localhost:8080");
logger.info("Fenêtre chargée");
} catch (err) {
logger.error("Erreur chargement fenêtre:", err);
}
// Événement déclenché à la fermeture de la fenêtre principale
mainWindow.on("closed", () => {
logger.info("Fenêtre principale fermée");
mainWindow = null;
});
/**
* If we are on a second instance
*/
if (!isPrimaryInstance) {
app.quit();
process.exit(0);
}
/*
Définition du chemin par défaut pour les téléchargements.
Ici, on utilise le dossier 'Downloads' de l'utilisateur avec un sous-dossier spécifique à l'application.
*/
const defaultDownloadPath = path.join(os.homedir(), "Downloads", "Freedom Loader");
/**
* Set App Name and AppID
*/
app.setName("Freedom Loader");
app.setAppUserModelId("com.masteracnolo.freedomloader");
/*
Gestionnaire IPC qui permet au renderer de demander à l'utilisateur
de sélectionner un dossier via une boîte de dialogue native.
On retourne le chemin sélectionné ou null si annulation.
*/
ipcMain.handle("select-download-folder", async () => {
logger.info("Demande de sélection d'un dossier reçue depuis le renderer");
/**
* Load the app dependencies for hardware choice
*/
const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger");
const { configFeatures, devMode} = require("./config");
/**
* In-memory snapshot of application feature flags.
*
* Note: Changes to the config file are not automatically reflected.
* A restart could be required.
*/
if (!configFeatures.enableHardwareAcceleration){
app.disableHardwareAcceleration();
logger.info("Disabled Hardware Acceleration");
} else {
logger.info("Enable Hardware Acceleration");
}
if(devMode){
/**
* Start devTron extensions - @see https://github.com/electron/devtron
*/
const { devtron } = require('@electron/devtron');
devtron.install();
logger.info("Loaded DevTron Extension");
}
/**
* Load the app dependencies
*/
const { initThemes } = require("./app/themeManager");
const { initAutoUpdater } = require("./app/autoUpdater");
const { startRPC, stopRPC } = require("./app/discordRPC");
const { checkNativeDependencies } = require("./app/dependencyCheck");
const { updateYtDlp } = require("./app/ytDlpUpdater");
const { createMainWindow, getMainWindow } = require("./app/windowManager");
const { registerIpcHandlers } = require("./app/ipcHandlers");
const { createSplashWindow, closeSplashWindow, setSplashProgress } = require("./app/splashManager");
const { userThemesPath, initUserThemes, validateBinaries, defaultDownloadFolder } = require("./server/helpers/path.helpers");
const {createSystemTray, destroyTray} = require("./app/tray");
const { stopServer } = require("./server/server");
/**
* Expose .env in process.env
*/
require("dotenv").config();
/**
* Global flag indicating if the application is intentionally shutting down.
* Used across the app to bypass the "minimize to tray on close" behavior.
* @type {boolean}
*/
app.isQuitting = false;
/**
* State flag to track the asynchronous cleanup process during shutdown.
* Prevents the application from exiting before services (RPC, Tray, Logs) are properly closed.
* @type {boolean}
*/
let isCleanedUp = false;
/**
* Performs a graceful shutdown of all application services asynchronously.
* Once finished, it re-triggers the app.quit() process.
*/
async function cleanShutdown() {
try {
const result = await dialog.showOpenDialog({
properties: ["openDirectory"]
});
if (!result.canceled && result.filePaths.length > 0) {
logger.info(`Dossier sélectionné : ${result.filePaths[0]}`);
return result.filePaths[0];
}
return null;
destroyTray();
await stopRPC();
stopServer();
} catch (err) {
logger.error(`Erreur lors de la sélection de dossier : ${err.message}`);
return null;
logger.error("Error during cleanup:", err);
} finally {
logger.info("All services stopped. Have a nice day!");
logSessionEnd();
isCleanedUp = true;
app.quit();
}
}
/**
* If another instance want to run
*/
app.on("second-instance", () => {
logger.info("Second instance detected");
const mainWindow = require("./app/windowManager").getMainWindow();
if (!mainWindow) return;
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.show();
mainWindow.focus();
});
/*
Gestionnaire IPC pour exposer au renderer le chemin par défaut de téléchargement
afin qu'il puisse l'afficher ou l'utiliser comme valeur initiale.
*/
ipcMain.handle("get-default-download-path", () => {
return defaultDownloadPath;
});
/*
Événement déclenché quand l'application est prête.
On démarre le serveur Express, puis on crée la fenêtre principale.
En cas d'erreur, on log et on quitte l'application proprement.
*/
app.whenReady().then(async () => {
logSessionStart();
logger.info("App prête, démarrage du serveur Express...");
const expressServer = require("./server/server.js");
logSessionStart(logDir, defaultDownloadFolder);
createSplashWindow();
if (!devMode && !checkNativeDependencies()) return;
const { userYtDlp } = require("./server/helpers/path.helpers");
updateYtDlp(userYtDlp);
try {
await expressServer.startServer();
logger.info("Serveur Express démarré");
await createWindow();
setSplashProgress(0); // Checking dependencies
await require("./server/server").startServer();
// Ajout du menu personnalisé
const menuTemplate = [
// ... Menu standard Electron ...
{
label: "Logs",
submenu: [
{
label: "Ouvrir les logs",
click: () => {
shell.openPath(logsFolderPath);
}
}
]
}
];
setSplashProgress(1); // Starting server
registerIpcHandlers(getMainWindow);
// Fusionner avec le menu par défaut
const defaultMenu = Menu.getApplicationMenu();
let template = menuTemplate;
if (defaultMenu) {
template = [...Menu.getApplicationMenu().items.map(item => item), ...menuTemplate];
setSplashProgress(2); // Loading themes
validateBinaries();
initUserThemes();
await initThemes(userThemesPath);
setSplashProgress(3); // Almost ready
await createMainWindow();
closeSplashWindow();
getMainWindow().show();
if (configFeatures.systemTray) {
createSystemTray(devMode);
}
const finalMenu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(finalMenu);
} catch (error) {
logger.error("Erreur serveur ou fenêtre :", error);
if (configFeatures.discordRPC) startRPC();
if (configFeatures.autoUpdate) initAutoUpdater(getMainWindow());
} catch (err) {
logger.error("Boot error:", err);
app.quit();
}
});
/*
Quitte l'application lorsque toutes les fenêtres sont fermées,
sauf sous macOS où il est habituel de garder l'application active.
*/
app.on("window-all-closed", () => {
logger.info("Toutes fenêtres fermées, quitte l'app");
if (process.platform !== "darwin") app.quit();
if (app.isQuitting || !configFeatures.systemTray) {
logger.info("Shutting down...");
app.quit();
} else if (process.platform !== "darwin") {
logger.info("Main window closed, app running in background (Tray).");
}
});
/*
Avant de quitter l'application, on log la fin de session pour traçabilité.
*/
app.on("before-quit", () => {
logSessionEnd();
});
app.on("before-quit", (event) => {
app.isQuitting = true;
if (!isCleanedUp) {
event.preventDefault();
cleanShutdown();
}
});

10027
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,68 +1,179 @@
{
"name": "freedom-loader",
"desktopName": "com.masteracnolo.freedomloader",
"productName": "Freedom Loader",
"version": "1.2.0",
"author": "MasterAcnolo",
"description": "Freedom Loader",
"version": "1.6.4",
"author": "MasterAcnolo <MasterAcnolo@users.noreply.github.com>",
"description": "Free and open-source GUI for yt-dlp — download video and audio from hundreds of platforms",
"homepage": "https://masteracnolo.github.io/Freedom-Loader-Site/",
"repository": {
"type": "git",
"url": "https://github.com/MasterAcnolo/Freedom-Loader.git"
},
"license": "GPL-3.0-only",
"bugs": {
"url": "https://github.com/MasterAcnolo/Freedom-Loader/issues"
},
"main": "main.js",
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make",
"dist": "electron-builder"
"start": "electron --no-warnings .",
"start:warn": "electron --trace-warnings .",
"start:debug": "WAYLAND_DEBUG=1 ELECTRON_ENABLE_LOGGING=1 ELECTRON_ENABLE_STACK_DUMPING=1 electron --enable-logging --v=1 .",
"dev": "nodemon --watch server --watch app --watch main.js --watch config.js --ext js,json --exec \"electron --no-warnings .\"",
"dev:warn": "nodemon --watch server --watch app --watch main.js --watch config.js --ext js,json --exec \"electron --trace-warnings .\"",
"dev:debug": "nodemon --watch server --watch app --watch main.js --watch config.js --ext js,json --exec \"WAYLAND_DEBUG=1 ELECTRON_ENABLE_LOGGING=1 ELECTRON_ENABLE_STACK_DUMPING=1 electron --enable-logging --v=1 .\"",
"build": "electron-builder",
"build:win": "electron-builder --win",
"build:linux": "electron-builder --linux",
"build:rpm": "bash scripts/create-copr-package.sh",
"build:all": "electron-builder -wl",
"release": "bash scripts/release.sh",
"release:publish": "bash scripts/release.sh --publish",
"release:dry-run": "bash scripts/release.sh --dry-run",
"test": "jest",
"test:unit": "jest test/unit",
"lint": "eslint . --format pretty",
"lint:fix": "eslint . --fix --format pretty",
"update": "npm update"
},
"dependencies": {
"chalk": "^4.1.2",
"debug": "^4.4.1",
"discord-rpc": "^4.0.1",
"electron-squirrel-startup": "^1.0.1",
"dotenv": "^17.4.2",
"electron-updater": "^6.6.2",
"express": "^5.1.0",
"express-rate-limit": "^8.2.1",
"jszip": "^3.10.1",
"webidl-conversions": "^8.0.0",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0"
},
"devDependencies": {
"@electron-forge/cli": "^7.8.1",
"@electron-forge/maker-deb": "^7.8.1",
"@electron-forge/maker-rpm": "^7.8.1",
"@electron-forge/maker-squirrel": "^7.8.1",
"@electron-forge/maker-zip": "^7.8.1",
"@electron-forge/plugin-auto-unpack-natives": "^7.8.1",
"@electron-forge/plugin-fuses": "^7.8.1",
"@electron/fuses": "^1.8.0",
"electron": "^37.1.0",
"electron-builder": "^26.0.12"
"@electron/devtron": "^2.1.1",
"@eslint/js": "^10.0.1",
"@jest/globals": "^30.4.1",
"@playwright/test": "^1.62.1",
"electron": "^44.3.0",
"electron-builder": "^26.15.3",
"eslint": "^10.10.0",
"eslint-formatter-pretty": "^7.1.0",
"globals": "^17.12.0",
"jest": "^30.4.2",
"nodemon": "^3.1.14",
"playwright": "^1.62.1"
},
"build": {
"appId": "com.masteracnolo.freedomloader",
"productName": "Freedom Loader",
"publish": [
{
"provider": "github",
"owner": "MasterAcnolo",
"repo": "Freedom-Loader",
"releaseType": "draft"
}
],
"files": [
"**/*",
"node_modules/**/*",
"!logs/*.json"
"!config/config.dev.json",
"!theme/**/*"
],
"directories": {
"buildResources": "build"
},
"asar": false,
"asar": true,
"extraResources": [
{
"from": "build/confirm-icon.png",
"to": "confirm-icon.png"
"from": "theme",
"to": "theme"
},
{
"from": "config/config.default.json",
"to": "config/config.default.json"
},
{
"from": "build",
"to": ".",
"filter": [
"app-icon.ico",
"app-icon-64x64.png",
"confirm-icon.png",
"banner.bmp",
"banner.png",
"error.png"
]
}
],
"win": {
"target": "nsis",
"icon": "build/installer-icon.ico"
"icon": "build/app-icon.ico",
"artifactName": "Freedom-Loader-Setup-${version}.${ext}",
"extraResources": [
{
"from": "resources/binaries/win-32",
"to": "binaries",
"filter": [
"yt-dlp.exe",
"ffmpeg.exe",
"ffprobe.exe",
"deno.exe"
]
}
]
},
"nsis": {
"oneClick": false,
"perMachine": true,
"allowToChangeInstallationDirectory": true,
"installerIcon": "build/installer-icon.ico",
"installerIcon": "build/app-icon.ico",
"uninstallerIcon": "build/uninstaller-icon.ico",
"installerSidebar": "build/banner.bmp",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "Freedom Loader"
},
"linux": {
"target": [
"AppImage",
"deb",
"snap"
],
"category": "Utility",
"synopsis": "GUI for yt-dlp to download video and audio with metadata",
"maintainer": "MasterAcnolo",
"icon": "build/app-icon.png",
"executableName": "freedom-loader",
"extraResources": [
{
"from": "resources/binaries/linux",
"to": "binaries",
"filter": [
"yt-dlp",
"ffmpeg",
"ffprobe",
"deno"
]
}
]
},
"deb": {
"fpm": [
"./package/com.masteracnolo.freedomloader.metainfo.xml=/usr/share/metainfo/com.masteracnolo.freedomloader.metainfo.xml"
]
},
"rpm": {
"fpm": [
"./package/com.masteracnolo.freedomloader.metainfo.xml=/usr/share/metainfo/com.masteracnolo.freedomloader.metainfo.xml"
]
},
"snap": {
"confinement": "strict",
"plugs": [
"default",
"removable-media"
]
}
}
}

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>com.masteracnolo.freedomloader</id>
<name>Freedom Loader</name>
<summary>GUI for yt-dlp to download video and audio with metadata</summary>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-only</project_license>
<developer id="com.masteracnolo">
<name>MasterAcnolo</name>
</developer>
<description>
<p>
Freedom Loader is a free and open-source graphical interface built
around yt-dlp, the powerful command-line media downloader. It lets
you download video and audio from hundreds of supported websites
without ever touching a terminal, with no ads, no tracking, and
no questionable third-party services.
</p>
<p>
Under the hood, Freedom Loader bundles yt-dlp and ffmpeg directly,
so there is nothing extra to install: paste a link, choose a
format and quality, and the app handles fetching, converting
and tagging the file for you.
</p>
<p>Features include:</p>
<ul>
<li>MP4 video or MP3 audio downloads with embedded metadata and thumbnails</li>
<li>Quality selection from best to specific resolutions (1080p, 720p, 480p)</li>
<li>Codec choice: H.264, H.265, AV1, VP9 and Theora</li>
<li>Playlist downloads with automatic per-playlist folders</li>
<li>Cookie-based access to age-restricted and member-only content</li>
<li>Custom theme system with a web-based theme creator</li>
<li>Real-time download progress with network speed indicator</li>
<li>Optional Discord Rich Presence integration</li>
<li>Automatic application and download-engine updates</li>
</ul>
<p>
DRM-protected content is not supported and cannot be downloaded.
</p>
</description>
<launchable type="desktop-id">freedom-loader.desktop</launchable>
<url type="homepage">https://github.com/MasterAcnolo/Freedom-Loader</url>
<url type="bugtracker">https://github.com/MasterAcnolo/Freedom-Loader/issues</url>
<url type="vcs-browser">https://github.com/MasterAcnolo/Freedom-Loader</url>
<categories>
<category>AudioVideo</category>
<category>Utility</category>
<category>Network</category>
</categories>
<releases>
<release version="1.6.0" date="2026-06-23">
<description>
<p>Latest stable release.</p>
</description>
</release>
</releases>
<content_rating type="oars-1.1"/>
<provides>
<binary>freedom-loader</binary>
</provides>
</component>

9
package/copr-build.sh Normal file
View File

@@ -0,0 +1,9 @@
#!/bin/sh -xe
git clone --depth 1 https://github.com/MasterAcnolo/Freedom-Loader.git src
cd src
npm ci
npx electron-builder --linux dir
VERSION=$(node -p "require('./package.json').version")
tar czf "$resultdir/freedom-loader-$VERSION.tar.gz" -C dist linux-unpacked
sed "s/__VERSION__/$VERSION/" package/freedom-loader.spec > "$resultdir/freedom-loader.spec"

View File

@@ -0,0 +1,25 @@
Name: freedom-loader
Version: __VERSION__
Release: 1%{?dist}
Summary: GUI for yt-dlp media downloads
License: GPL-3.0-only
Source0: freedom-loader-__VERSION__.x86_64.rpm
BuildArch: x86_64
Requires: glibc, gtk3, nss, libXScrnSaver
%description
GUI for yt-dlp media downloads.
%install
rpm2cpio %{SOURCE0} | (cd %{buildroot} && cpio -idmv)
mkdir -p %{buildroot}/usr/bin
ln -sf "/opt/Freedom Loader/freedom-loader" %{buildroot}/usr/bin/freedom-loader
%files
/opt/*
/usr/bin/freedom-loader
/usr/share/applications/*
/usr/share/metainfo/*
/usr/share/icons/hicolor
%changelog

View File

@@ -1,31 +1,208 @@
/*
This file is part of Freedom Loader.
Copyright (C) 2025 MasterAcnolo
Freedom Loader is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
Freedom Loader is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { contextBridge, ipcRenderer } = require("electron");
/*
Expose dans le contexte global (window.electronAPI) deux méthodes sécurisées
pour que le renderer puisse interagir avec le main process via IPC.
- getDefaultDownloadPath : récupère le chemin de téléchargement par défaut.
- selectDownloadFolder : ouvre la boîte de dialogue pour choisir un dossier.
*/
/**
* Exposes the main application API to the renderer process.
*
* This API acts as a secure bridge between the renderer and the
* Electron main process using IPC (Inter-Process Communication).
* All calls are explicitly whitelisted.
*/
contextBridge.exposeInMainWorld("electronAPI", {
/**
* Sends error log message.
*
* @param {...any} args - Values to log
*/
logError: (...args) => ipcRenderer.send("log-error", args.map(arg => typeof arg === "object" ? JSON.stringify(arg) : arg).join(" ")),
/**
* Sends info log message.
*
* @param {...any} args - Values to log
*/
logInfo: (...args) => ipcRenderer.send("log-info", args.map(arg => typeof arg === "object" ? JSON.stringify(arg) : arg).join(" ")),
/**
* Sends warning log message.
*
* @param {...any} args - Values to log
*/
logWarn: (...args) => ipcRenderer.send("log-warn", args.map(arg => typeof arg === "object" ? JSON.stringify(arg) : arg).join(" ")),
/**
* Return process.platform to renderer
*/
getProcessPlatform: () => process.platform,
/**
* Returns the default system download directory.
*/
getDefaultDownloadPath: () => ipcRenderer.invoke("get-default-download-path"),
/**
* Opens a native dialog allowing the user to select a download folder.
*/
selectDownloadFolder: () => ipcRenderer.invoke("select-download-folder"),
/**
* Sends download progress value to the main process (used for taskbar / UI sync).
*
* @param {number} percent - Download progress percentage (0–100)
*/
setProgress: (percent) => ipcRenderer.send("set-progress", percent),
/**
* Retrieves application feature flags (runtime configuration).
*/
getFeatures: () => ipcRenderer.invoke("features"),
/**
* Updates a feature flag dynamically at runtime.
*
* @param {string} key - Feature name
* @param {any} value - Feature value
*/
setFeature: (key, value) => ipcRenderer.invoke("set-feature", { key, value }),
/**
* Returns the current application version.
*/
getVersion: () => ipcRenderer.invoke("version"),
/**
* Validates a download path before using it for file operations.
*
* @param {string} path - Path to validate
*/
getValidatedDownloadPath: (path) =>
ipcRenderer.invoke("validate-download-path", path),
/**
* Retrieves available themes from the filesystem or config layer.
*/
getThemes: () => ipcRenderer.invoke("get-themes"),
/**
* Forces a reload of theme files (useful after modification/import).
*/
reloadThemes: () => ipcRenderer.invoke("reload-themes"),
/*
* ==========================================
* Auto Updater IPC
* ==========================================
*/
/**
* Checks if an update has already been detected by the main process.
*
* @returns {Promise<boolean>} Resolves to true if an update is available.
*/
isUpdateAvailable: () => ipcRenderer.invoke("is-update-available"),
/**
* Subscribes to the update available event pushed by the main process.
*
* @param {Function} callback - Function executed with update info when an update is found.
*/
onUpdateAvailable: (callback) =>
ipcRenderer.on("update-available", (_, info) => callback(info)),
/**
* Instructs the main process to start downloading the available update.
*
* @returns {Promise<void>}
*/
downloadUpdate: () => ipcRenderer.invoke("download-update"),
/**
* Instructs the main process to quit the application and install the downloaded update.
*
* @returns {Promise<void>}
*/
installUpdate: () => ipcRenderer.invoke("install-update"),
/**
* Subscribes to the download progress event.
*
* @param {Function} callback - Function executed with progress metrics (percent, bytesPerSecond, transferred, total).
*/
onDownloadProgress: (callback) =>
ipcRenderer.on("update-progress", (_, progress) => callback(progress)),
/**
* Subscribes to the update downloaded event.
* Triggered when the update file is fully downloaded and ready to install.
*
* @param {Function} callback - Function executed with update info.
*/
onUpdateDownloaded: (callback) =>
ipcRenderer.on("update-downloaded", (_, info) => callback(info)),
openReleasePage: () => ipcRenderer.send("open-release-page"),
});
/**
* Exposes window control and developer utilities for the custom topbar UI.
*
* These methods forward commands to the Electron main process via IPC.
*/
contextBridge.exposeInMainWorld("topbarAPI", {
/**
* Minimizes the application window.
*/
minimize: () => ipcRenderer.send("window-minimize"),
/**
* Toggles maximize/restore state of the application window.
*/
maximize: () => ipcRenderer.send("window-maximize"),
/**
* Closes the application window.
*/
close: () => ipcRenderer.send("window-close"),
/**
* Opens Chromium DevTools for debugging.
*/
openDevTools: () => ipcRenderer.send("open-devtools"),
/**
* Opens application logs directory or log viewer.
*/
openLogs: () => ipcRenderer.send("open-logs"),
/**
* Opens the official website or project homepage.
*/
openWebsite: () => ipcRenderer.send("open-website"),
/**
* Opens the theme directory or theme editor interface.
*/
openTheme: () => ipcRenderer.send("open-theme"),
/**
* Opens project wiki/documentation.
*/
openWiki: () => ipcRenderer.send("open-wiki"),
/**
* Opens the workshop or external content library.
*/
openWorkshop: () => ipcRenderer.send("open-workshop"),
/**
* Opens configuration/settings panel.
*/
openConfig: () => ipcRenderer.send("open-config"),
/**
* Send Bug Report
*
* @param params
* @returns {Promise<any>}
*/
sendReport: (params) => ipcRenderer.invoke("send-report", params),
});

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: 3.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 805 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -7,84 +7,343 @@
default-src 'self' 'unsafe-inline' data:;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src https://fonts.gstatic.com;
img-src 'self' data: https://i.ytimg.com;
img-src 'self' data: https:;
">
<title>Freedom Loader</title>
<!--CSS-->
<link rel="stylesheet" href="styles/styles.css">
<!--Icons-->
<link rel="icon" href="assets/icons/file-audio-solid.svg" type="image/svg+xml">
<link rel="stylesheet" href="styles/layout/topbar.css">
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet">
</head>
<body>
<div class="container">
<header>
<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="report-bug-btn" title="Report a bug">Report a Bug</button> <!-- TODO: Find a better place -->
<button id="website-btn" title="Website">Website</button>
<button id="wiki-btn" title="Wiki">Wiki</button>
<button id="workshop-btn" title="Workshop">Theme Workshop</button>
<!-- <button id="theme-btn" title="Theme">Theme</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>
</header>
<header>
<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; align-items: center; gap: 10px;">
<span class="setting-title">Theme</span>
<select id="themeSelect" data-key="theme" aria-label="Choose the theme">
</select>
<button id="refresh-themes-btn" title="Refresh themes" class="refresh-themes-btn">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 4v6h6M23 20v-6h-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</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="createPlaylistFolders" id="createPlaylistFolders">
<div class="setting-info">
<span class="setting-title">Create Playlist Folders</span>
<small>Create a dedicated folder for each playlist with its name.</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 data-key="keepPlaylistOrder" id="keepPlaylistOrder" type="checkbox">
<div class="setting-info">
<span class="setting-title">Keep Playlist Order</span>
<small>Add index before output file, to keep the order.</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 data-key="systemTray" id="systemTray" type="checkbox">
<div class="setting-info">
<span class="setting-title">System Tray</span>
<small>Minimize app on close, in the System Tray. [EXPERIMENTAL]</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 data-key="autoCheckInfo" id="autoCheckInfo" type="checkbox">
<div class="setting-info">
<span class="setting-title">Auto Fetch Info</span>
<small>Automatically fetch information for new downloads.</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">
<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" 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>
<div class="setting-item">
<input type="checkbox" data-key="enableHardwareAcceleration" id="enableHardwareAcceleration">
<div class="setting-info">
<span class="setting-title">Enable Hardware Acceleration</span>
<small>Uses GPU acceleration for rendering when available. <br> (Be effective on next launch)</small>
</div>
</div>
</details>
<!-- Developer Settings -->
<details class="settings-section">
<summary>Developer Settings</summary>
<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-theme">Open Theme Folder</button>
<button id="open-json-btn">Open JSON Configuration</button>
</div>
</div>
</div>
<main>
<div class="container" id="container">
<h1 id="title"> Freedom Loader </h1>
<p id="subtitle"> Because why not? </p>
</header>
<form action="/download" method="POST" id="downloadForm">
<input name="url" placeholder="URL" id="UrlInput">
<form action="/download" method="POST" id="downloadForm">
<div class="url-input-container">
<input type="text" name="url" placeholder="URL" id="UrlInput">
<button type="button" id="pasteBtn" class="paste-btn" title="Paste from clipboard">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 5H7C5.89543 5 5 5.89543 5 7V19C5 20.1046 5.89543 21 7 21H17C18.1046 21 19 20.1046 19 19V7C19 5.89543 18.1046 5 17 5H15M9 5C9 6.10457 9.89543 7 11 7H13C14.1046 7 15 6.10457 15 5M9 5C9 3.89543 9.89543 3 11 3H13C14.1046 3 15 3.89543 15 5M12 12H15M12 16H15M9 12H9.01M9 16H9.01" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<label class="checkbox-inline">
<label class="checkbox-inline">
<label class="checkbox-mp3">
<input type="checkbox" name="audioOnly" value="1">
<div class="checkmark"></div>
<label class="checkbox-mp3">
<input type="checkbox" name="audioOnly" value="1">
<div class="checkmark"></div>
</label>
Audio Only (MP3)
</label>
<!-- <input type="checkbox" name="audioOnly" value="1"> -->
Audio seulement (MP3)
</label>
<!-- <input type="checkbox" name="audioOnly" value="1"> Audio seulement (MP3) -->
<select name="quality">
<option value="best">Meilleure qualité</option>
<option value="medium">Qualité moyenne</option>
<option value="worst">Qualité minimale</option>
<option value="1080">1080p</option>
<option value="720">720p</option>
<option value="480">480p</option>
</select>
<select name="quality">
<option value="best">Best Quality</option>
<option value="medium">Medium Quality</option>
<option value="worst">Lowest Quality</option>
<option value="1080">1080p</option>
<option value="720">720p</option>
<option value="480">480p</option>
</select>
<button type="submit">Télécharger</button>
</form>
<button type="submit" onclick="startProgress()">Download</button>
</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">
Sera enregistré dans <span id="savePath"></span>
<button id="changePath">Modifier</button>
</div>
<div class="download-path">
Will be saved in <span id="savePath"></span>
<button id="changePath">Edit</button>
</div>
<div id="downloadStatus"></div>
<div class="progress-container">
<div id="downloadStage" style="display: none; font-size: 0.85rem; color: var(--default-text-color); margin-top: 8px;"></div>
<div id="downloadProgressWrapper">
<div id="downloadProgress"></div>
</div>
<div style="display: flex; gap: 20px; align-items: center;">
<div id="downloadProgressText"></div>
<div id="downloadSpeedText"></div>
<div id="playlistInfoText" style="display: none; font-size: 0.9rem;"></div>
<button type="button" id="stopBtn" onclick="stopDownload()" class="stop-btn" style="display:none;" title="Stop download">■</button>
</div>
</div>
<div class="infos-container">
<div id="videoInfo" style="margin-top: 20px;"></div>
<div id="downloadStatus" style="display: none;"></div>
<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>
<span id="version-badge" class="version-badge"></span>
</main>
<!-- Toast Container -->
<div id="toast-container"></div>
<h2 id="reference"> Because Why Not ? </h2>
<div id="updateAvailable" style="display:none">
<div class="update-banner">
<div class="banner-row">
<div class="banner-left">
<div class="icon-wrap">
<span class="material-symbols-outlined" id="update-icon">download</span>
</div>
<div class="text-block">
<span class="update-title" id="update-title">Update available</span>
<span class="update-sub" id="update-sub">A new version is ready</span>
</div>
</div>
<button class="btn-update" id="btn-update">Download</button>
</div>
<div class="progress-wrap" id="progress-wrap" style="display:none">
<div class="progress-track">
<div class="progress-fill" id="progress-fill"></div>
</div>
<div class="progress-meta">
<span id="progress-pct">0%</span>
<span id="progress-eta"></span>
</div>
</div>
</div>
</div>
<footer class="theme-switcher">
<label for="themeSelect">Thème :</label>
<select id="themeSelect" aria-label="Choisir le thème">
<!-- <option value="dark">Sombre</option>
<option value="light">Clair</option>
<option value="Chirac">Chirac</option>
<option value="Fanatic">Fanatic</option> -->
</select>
</footer>
<!-- Scripts -->
<script src="script/toast.js"></script>
<script src="script/clipboardPaste.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/fetchinfo.js"></script>
<script src="script/chirac.js"></script>
<script src="script/progressBar.js"></script>
<script src="script/customthemes.js"></script>
<script src="script/reportBug.js"></script>
<script src="script/autoUpdate.js"></script>
<script type="module" src="script/topbar.js"></script>
</body>
</html>

View File

@@ -0,0 +1,54 @@
/**
* Retrieves the application version and build state from the backend.
* Displays a formatted string indicating packaging state and preview status.
* Hides the "Packaged" state for production builds to keep the UI clean.
*
* Example outputs: "Not Packaged Preview v1.6.2" or "Preview v1.6.2"
*
* @returns {Promise<void>}
*/
async function displayVersionLabel() {
/**
* Raw version string from the backend (e.g., "dev-1.6.2-preview" or "v1.6.2")
* @type {string}
*/
const appVersion = await window.electronAPI.getVersion();
/**
* UI element displaying the current application version.
* @type {HTMLElement | null}
*/
const versionBadge = document.getElementById("version-badge");
if (!versionBadge) return;
/**
* Extracts the numeric semantic version (e.g., "1.6.2").
* @type {RegExpMatchArray | null}
*/
const versionMatch = appVersion.match(/(\d+\.\d+\.\d+)/);
const baseVersion = versionMatch ? versionMatch[0] : "Unknown";
/**
* Determines the packaging state. The backend prepends "dev-" if unpackaged.
* @type {boolean}
*/
const isPackaged = !appVersion.includes("dev-");
/**
* Determines if the current build is a preview version.
* @type {boolean}
*/
const isPreview = appVersion.includes("preview");
/**
* Construct the final display string.
* If packaged, we don't mention it. If not, we explicitly say "Not Packaged ".
*/
const packageStateStr = isPackaged ? "" : "Not Packaged ";
const previewStr = isPreview ? "Preview " : "";
versionBadge.textContent = `${packageStateStr}${previewStr}v${baseVersion}`;
}
displayVersionLabel();

View File

@@ -0,0 +1,81 @@
async function initUpdateUI() {
const container = document.getElementById("updateAvailable");
const hasUpdate = await window.electronAPI.isUpdateAvailable();
if (hasUpdate) {
setupAndShowUpdateUI();
}
window.electronAPI.onUpdateAvailable((info) => {
setupAndShowUpdateUI(info);
});
function setupAndShowUpdateUI(info) {
container.style.display = "block";
const btn = document.getElementById("btn-update");
const icon = document.getElementById("update-icon");
const title = document.getElementById("update-title");
const sub = document.getElementById("update-sub");
const progressWrap = document.getElementById("progress-wrap");
const progressFill = document.getElementById("progress-fill");
const progressPct = document.getElementById("progress-pct");
const progressEta = document.getElementById("progress-eta");
if (info && info.version) {
sub.textContent = `A new version (${info.version}) is ready`;
}
if (info && info.environment && info.environment !== "auto") {
icon.textContent = "open_in_new";
if (info.environment === "snap") {
sub.textContent = `Version ${info.version} is out. Use the Snap Store or run "snap refresh".`;
btn.textContent = "View Release";
} else {
sub.textContent = `Version ${info.version} is out. Update via your package manager or download it manually.`;
btn.textContent = "Download manually";
}
btn.onclick = () => {
window.electronAPI.openReleasePage();
container.style.display = "none";
};
return;
}
btn.onclick = () => {
window.electronAPI.downloadUpdate();
btn.disabled = true;
btn.textContent = "Downloading...";
icon.textContent = "download";
title.textContent = "Downloading update";
progressWrap.style.display = "flex";
};
window.electronAPI.onDownloadProgress((progress) => {
const pct = Math.round(progress.percent);
progressFill.style.width = pct + "%";
progressPct.textContent = pct + "%";
const remaining = (progress.total - progress.transferred) / progress.bytesPerSecond;
progressEta.textContent =
remaining > 0 ? `~${Math.ceil(remaining)}s left` : "almost done";
});
window.electronAPI.onUpdateDownloaded(() => {
progressFill.style.width = "100%";
progressPct.textContent = "100%";
progressEta.textContent = "Done";
icon.textContent = "check";
title.textContent = "Ready to install";
sub.textContent = "Restart to apply the update";
btn.textContent = "Restart";
btn.disabled = false;
btn.onclick = () => window.electronAPI.installUpdate();
});
}
}
initUpdateUI();

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

@@ -0,0 +1,40 @@
/**
* Initializes clipboard paste functionality for the URL input field.
*
* When the user clicks the paste button, the system attempts to read
* the clipboard content and inject it into the URL input field.
* It also triggers an input event to notify any bound listeners.
*/
document.addEventListener("DOMContentLoaded", () => {
const pasteBtn = document.getElementById("pasteBtn");
const urlInput = document.getElementById("UrlInput");
if (!pasteBtn || !urlInput) return;
/**
* Handles clipboard paste action triggered by the paste button.
* Reads clipboard text and inserts it into the URL input field.
*/
pasteBtn.addEventListener("click", async () => {
try {
urlInput.value = await navigator.clipboard.readText();
// Notify frameworks / listeners bound to input changes
urlInput.dispatchEvent(new Event("input", { bubbles: true }));
urlInput.focus();
// Temporary UI feedback on paste action
pasteBtn.style.backgroundColor = "var(--form-button-bg-hover-color)";
setTimeout(() => {
pasteBtn.style.backgroundColor = "";
}, 200);
} catch (err) {
console.error("Clipboard access failed:", err);
alert(
"Unable to access clipboard. Please paste manually using Ctrl+V."
);
}
});
});

View File

@@ -1,71 +1,97 @@
/*
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é.
*/
/**
* Initializes the download path system once the UI is ready.
*
* Responsibilities:
* - Restore last known user-selected download path (localStorage)
* - Validate it against backend rules
* - Synchronize with backend authoritative path
* - Setup folder selection UI interaction
*/
window.addEventListener("DOMContentLoaded", async () => {
const savePathElem = document.getElementById("savePath");
const form = document.getElementById("downloadForm");
// 1️Essayer de charger depuis le localStorage
let savedPath = localStorage.getItem("customDownloadPath");
// 2️Sinon demander le chemin par défaut à l'API Electron
if (!savedPath) {
savedPath = await window.electronAPI.getDefaultDownloadPath();
}
// 3 Afficher le chemin
if (savePathElem) {
savePathElem.textContent = savedPath;
}
// Créer l'input caché s'il n'existe pas déjà
/**
* Hidden form field storing the validated download path
* used during download submission.
*
* Created dynamically if not already present in DOM.
*/
let hidden = document.getElementById("savePathInput");
if (!hidden) {
hidden = document.createElement("input");
hidden.type = "hidden";
hidden.name = "savePath";
hidden.id = "savePathInput";
document.getElementById("downloadForm").appendChild(hidden);
form.appendChild(hidden);
}
hidden.value = savedPath;
// Gestion du bouton de modification
document.getElementById("changePath").addEventListener("click", async () => {
const selectedPath = await window.electronAPI.selectDownloadFolder();
if (selectedPath) {
// Met à jour l'affichage
savePathElem.textContent = selectedPath;
hidden.value = selectedPath;
/**
* Applies a validated download path received from backend.
*
* Updates:
* - UI display (visible path)
* - tooltip (full path)
* - hidden form input (used for submission)
* - localStorage cache (UX persistence only, not authoritative)
*
* @param {string} path - validated absolute download path
*/
async function applyPathFromBack(path) {
savePathElem.textContent = path;
savePathElem.title = path;
hidden.value = path;
localStorage.setItem("customDownloadPath", path); // UX only
}
// Et le stocke en localStorage pour la prochaine fois
localStorage.setItem("customDownloadPath", selectedPath);
/**
* Loads the initial download path on application startup.
*
* Priority order:
* 1. cached localStorage value (UX only, not trusted)
* 2. backend validated version of cached path
* 3. backend default fallback path if validation fails
*/
async function loadInitialPath() {
// 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();
/**
* Opens system folder selector and updates download path.
*
* The selected folder is validated by Electron backend
* before being applied to the UI and persisted.
*/
document.getElementById("changePath").addEventListener("click", async () => {
try {
// selectDownloadFolder already returns a validated path
const validatedPath = await window.electronAPI.selectDownloadFolder();
if (!validatedPath) return; // cancelled
await applyPathFromBack(validatedPath);
} catch (err) {
alert("This folder is not allowed. Only specific folders (Users, Downloads, Documents) are authorized for downloads.");
}
});
});

View File

@@ -1,107 +1,256 @@
/*
This file is part of Freedom Loader.
Copyright (C) 2025 MasterAcnolo
Freedom Loader is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
Freedom Loader is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// Liste des thèmes disponibles (clé = nom, valeur = label affiché)
const themes = {
default: "Default",
dark: "Sombre",
light: "Clair",
nf : "NF",
chirac: "Chirac",
fanatic: "Fanatic",
cyberpunk: "Cyberpunk",
spicy: "Spicy",
vilbrequin: "Vilbrequin"
};
const themeSubtitles = {
default: "Because why not?",
dark: "Darkness is my ally",
light: "Qui aime ce thème ?",
nf : "You call it music, i call it my Therapist",
chirac: "J'aime les pommes",
fanatic: "Always Fnatic !",
cyberpunk: "Wake up, choom. We’ve got a city to burn.",
spicy: "The Spiciest One",
vilbrequin: "Rend l'argent"
};
const themeSelect = document.getElementById("themeSelect");
/**
* Currently selected themes loaded from backend.
* Used as cache for UI theme switching.
*/
let loadedThemes = [];
// Remplir le select avec les options
function populateThemeSelect() {
for (const [key, label] of Object.entries(themes)) {
/**
* Applies a theme object to the application UI by mapping
* theme configuration values to CSS variables and DOM properties.
*
* This function mutates:
* - document.body (background styling)
* - :root CSS variables (design system tokens)
* - subtitle DOM element (theme metadata display)
*
* @param {Object} theme - Theme definition object
* @param {Object} theme.style - Style configuration
*/
function applyTheme(theme) {
const root = document.documentElement;
const style = theme.style;
// Background
if (theme.image) {
document.body.style.backgroundImage = `url('${theme.image}')`;
document.body.style.backgroundSize = style.background?.size || "cover";
document.body.style.backgroundPosition = style.background?.position || "center";
document.body.style.backgroundAttachment = style.background?.attachment || "fixed";
} else {
document.body.style.backgroundImage = "";
}
// CSS variables (design tokens)
root.style.setProperty("--background-color", style.colors?.background || "#121212");
root.style.setProperty("--default-text-color", style.colors?.text?.default || "#e0e0e0");
root.style.setProperty("--subtitle-color", style.colors?.text?.subtitle || "#2196f3");
root.style.setProperty("--audio-only-label-color", style.colors?.text?.audioOnly || "#f5f5f5");
root.style.setProperty("--form-bg-color", style.form?.background || "#1e1e1e");
root.style.setProperty("--form-input-bg-color", style.form?.input?.background || "#2a2a2a");
root.style.setProperty("--form-input-border-color", style.form?.input?.border || "#444444");
root.style.setProperty("--form-input-border-focus-color", style.form?.input?.borderFocus || "#2196f3");
root.style.setProperty("--form-input-text-color", style.form?.input?.text || "#f5f5f5");
root.style.setProperty("--form-input-placeholder-color", style.form?.input?.placeholder || "#888888");
root.style.setProperty("--form-button-bg-color", style.form?.button?.background || "#2196f3");
root.style.setProperty("--form-button-text-color", style.form?.button?.text || "#ffffff");
root.style.setProperty("--form-button-bg-hover-color", style.form?.button?.hover || "#1976d2");
root.style.setProperty("--paste-button-icon-color", style.form?.pasteButtonIcon || "#ffffff");
root.style.setProperty("--checkbox-unchecked-bg-color", style.checkbox?.background?.unchecked || "#555555");
root.style.setProperty("--checkbox-checked-bg-color", style.checkbox?.background?.checked || "#2196f3");
root.style.setProperty("--checkbox-checkmark-border-color", style.checkbox?.checkmarkBorder || "#ffffff");
root.style.setProperty("--checkbox-pulse-color", style.checkbox?.pulse || "rgba(33, 150, 243, 0.5)");
root.style.setProperty("--download-status-color", style.download?.status || "#2196f3");
root.style.setProperty("--progress-bar-bg-color", style.progressBar?.background || "#444444");
root.style.setProperty("--progress-bar-fill-color", style.progressBar?.fill || "#2196f3");
root.style.setProperty("--video-info-bg-color", style.videoInfo?.background || "#1a1a1a");
root.style.setProperty("--video-info-text-color", style.videoInfo?.text || "#e0e0e0");
root.style.setProperty("--video-info-heading-color", style.videoInfo?.heading || "#2196f3");
root.style.setProperty("--video-info-list-color", style.videoInfo?.list || "#cccccc");
root.style.setProperty("--video-info-list-strong-color", style.videoInfo?.strong || "#ffffff");
root.style.setProperty("--video-info-link-color", style.videoInfo?.link || "#2196f3");
root.style.setProperty("--video-info-link-hover-color", style.videoInfo?.linkHover || "#1976d2");
root.style.setProperty("--playlist-background-color", style.playlist?.background || "#303030");
root.style.setProperty("--settings-button-bg-color", style.settings?.button?.background || "#1e1e1e");
root.style.setProperty("--settings-button-text-color", style.settings?.button?.text || "#e0e0e0");
root.style.setProperty("--open-theme-button-bg-color", style.settings?.openThemeButton?.background || "#2196f3");
root.style.setProperty("--open-theme-button-text-color", style.settings?.openThemeButton?.text || "#ffffff");
root.style.setProperty("--open-json-button-bg-color", style.settings?.openJsonButton?.background || style.settings?.button?.background || "#1e1e1e");
root.style.setProperty("--open-json-button-text-color", style.settings?.openJsonButton?.text || style.settings?.button?.text || "#e0e0e0");
root.style.setProperty("--settings-modal-bg-color", style.settings?.background?.modal || "#1e1e1e");
root.style.setProperty("--settings-section-bg-color", style.settings?.background?.section || "#2a2a2a");
root.style.setProperty("--settings-text-color", style.settings?.text || "#e0e0e0");
root.style.setProperty("--settings-subtitle-color", style.settings?.subtitle || "#aaaaaa");
const subtitleEl = document.getElementById("subtitle");
if (subtitleEl) subtitleEl.textContent = theme.subtitle || theme.name;
}
/**
* Persists selected theme ID into Electron backend features storage.
*
* @param {string} themeId
*/
function saveTheme(themeId) {
window.electronAPI.setFeature("theme", themeId);
}
/**
* Populates theme dropdown selector with available themes.
*
* @param {Array} themes
*/
function populateThemeSelect(themes) {
themeSelect.innerHTML = "";
for (const theme of themes) {
const option = document.createElement("option");
option.value = key;
option.textContent = label;
option.value = theme.id;
option.textContent = theme.name;
themeSelect.appendChild(option);
}
}
// Appliquer le thème sur le body et changer le subtitle
function applyTheme(themeName) {
// Supprime les classes thème précédentes
document.body.classList.remove(...Object.keys(themes));
// Ajoute la classe du thème actuel
document.body.classList.add(themeName);
/**
* Initializes theme system:
* - loads themes from backend
* - restores saved theme
* - applies selected theme
*/
async function initThemes() {
loadedThemes = await window.electronAPI.getThemes();
// Modifier le subtitle
const subtitle = document.getElementById("subtitle");
if (subtitle && themeSubtitles[themeName]) {
subtitle.textContent = themeSubtitles[themeName];
if (!loadedThemes.length) {
const defaultTheme = {
id: "dark",
name: "Dark",
subtitle: "Because Why Not ?",
style: {
background: {
size: "cover",
position: "center",
attachment: "fixed"
},
colors: {
background: "#121212",
text: {
default: "#e0e0e0",
subtitle: "#2196f3",
audioOnly: "#f5f5f5"
}
},
form: {
background: "#1e1e1e",
input: {
background: "#2a2a2a",
border: "#444444",
borderFocus: "#2196f3",
text: "#f5f5f5",
placeholder: "#888888"
},
button: {
background: "#2196f3",
text: "#ffffff",
hover: "#1976d2"
},
pasteButtonIcon: "#ffffff"
},
checkbox: {
background: {
unchecked: "#555555",
checked: "#2196f3"
},
checkmarkBorder: "#ffffff",
pulse: "rgba(33, 150, 243, 0.5)"
},
download: {
status: "#2196f3"
},
progressBar: {
background: "#444444",
fill: "#2196f3"
},
videoInfo: {
background: "#1a1a1a",
text: "#e0e0e0",
heading: "#2196f3",
list: "#cccccc",
strong: "#ffffff",
link: "#2196f3",
linkHover: "#1976d2"
},
playlist: {
background: "#303030"
},
settings: {
button: {
background: "#1e1e1e",
text: "#e0e0e0"
},
openThemeButton: {
background: "#2196f3",
text: "#ffffff"
},
openJsonButton: {
background: "#2196f3",
text: "#ffffff"
},
background: {
modal: "#1e1e1e",
section: "#2a2a2a"
},
text: "#e0e0e0",
subtitle: "#aaaaaa"
}
}
};
loadedThemes = [defaultTheme];
}
// Optionnel : animation spéciale Chirac
if (themeName === "chirac") {
requestAnimationFrame(() => {
document.body.classList.add("theme-active");
});
} else {
document.body.classList.remove("theme-active");
}
populateThemeSelect(loadedThemes);
const features = await window.electronAPI.getFeatures();
const savedId = features.theme;
const theme = loadedThemes.find(t => t.id === savedId) || loadedThemes[0];
themeSelect.value = theme.id;
applyTheme(theme);
}
// Sauvegarder le thème en localStorage
function saveTheme(themeName) {
localStorage.setItem("selectedTheme", themeName);
}
// Charger le thème au démarrage
function loadTheme() {
const savedTheme = localStorage.getItem("selectedTheme");
if (savedTheme && themes[savedTheme]) {
applyTheme(savedTheme);
themeSelect.value = savedTheme;
} else {
applyTheme("dark");
themeSelect.value = "dark";
}
}
// Quand l’utilisateur change le select
/**
* Handles theme selection change from UI dropdown.
*/
themeSelect.addEventListener("change", (e) => {
const selected = e.target.value;
if (themes[selected]) {
applyTheme(selected);
saveTheme(selected);
}
const theme = loadedThemes.find(t => t.id === e.target.value);
if (!theme) return;
applyTheme(theme);
saveTheme(theme.id);
});
// Initialisation
populateThemeSelect();
loadTheme();
/**
* Reloads themes from backend and reapplies current selection.
*/
async function refreshThemes() {
loadedThemes = await window.electronAPI.reloadThemes();
populateThemeSelect(loadedThemes);
const features = await window.electronAPI.getFeatures();
const savedId = features.theme;
const theme = loadedThemes.find(t => t.id === savedId) || loadedThemes[0];
themeSelect.value = theme.id;
applyTheme(theme);
}
window.refreshThemes = refreshThemes;
initThemes();

View File

@@ -1,73 +1,174 @@
/*
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");
// Élément où on affichera l’état du téléchargement (en cours, erreur, succès)
const statusDiv = document.getElementById("downloadStatus");
const button = form.querySelector("button[type=\"submit\"]");
const stopBtn = document.getElementById("stopBtn");
// Récupération du bouton de soumission pour pouvoir le désactiver pendant la requête
const button = form.querySelector("button");
/**
* Indicates whether a download process is currently active.
* Used to prevent duplicate submissions and to gate UI actions
* such as disabling the form or showing progress.
*/
let isDownloading = false;
// Écouteur d’événement pour intercepter la soumission du formulaire
/**
* Indicates whether the current download was explicitly
* stopped by the user via the cancel action.
*
* Used to differentiate between:
* - user-initiated cancellation
* - failures or natural completion
*/
let userStoppedDownload = false;
/**
* Handles download form submission.
*
* - Validates user input (URL)
* - Disables UI during download
* - Sends request to backend `/download`
* - Manages success / error states
* - Restores UI state after completion
*/
form.addEventListener("submit", async (e) => {
e.preventDefault(); // Empêche le rechargement automatique de la page
// Désactive le bouton pour éviter les clics multiples pendant la requête
e.preventDefault();
const urlInput = form.querySelector("input[name='url']");
const url = urlInput.value.trim();
// Basic URL validation
if (!url) {
window.showError("Please enter a URL");
return;
}
button.disabled = true;
statusDiv.style.display = "none";
isDownloading = true;
userStoppedDownload = false;
// Affiche un message d’attente pour informer l’utilisateur
statusDiv.textContent = "Téléchargement en cours...";
// Show progress UI immediately with initial message
showInitialProgress();
// Prépare les données du formulaire au format URL-encoded
const formData = new FormData(form);
const params = new URLSearchParams(formData);
try {
// Envoie la requête POST vers /download avec les données du formulaire
const res = await fetch("/download", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: params,
});
// Si la réponse n’est pas un succès (code HTTP 4xx ou 5xx)
if (!res.ok) {
statusDiv.textContent = "❌ Erreur pendant le téléchargement.";
if (!userStoppedDownload) {
const errorText = await res.text();
window.showError(errorText);
resetProgressBar();
}
return;
}
// Récupère le texte envoyé par le serveur (message de succès ou erreur)
const text = await res.text();
// Download successful - check if notifications are enabled
const features = await window.electronAPI.getFeatures();
if (features.notifySystem) {
window.showSuccess("Download completed!");
}
// Affiche le message retourné par le serveur
statusDiv.textContent = text;
} catch {
// Gestion des erreurs réseau ou autres exceptions
statusDiv.textContent = "❌ Une erreur s’est produite.";
} catch (err) {
if (!isDownloading && !userStoppedDownload) {
statusDiv.style.display = "none";
}
} finally {
// Réactive le bouton pour permettre d’autres soumissions
isDownloading = false;
button.disabled = false;
// Efface le message d’état après 5 secondes pour garder l’interface propre
setTimeout(() => {
statusDiv.textContent = "";
}, 5000);
stopBtn.style.display = "none";
}
});
/**
* Displays initial download progress UI immediately
* when a download starts.
*
* Initializes progress bar, speed indicator, stage text
* and shows the stop button.
*/
function showInitialProgress() {
const progressWrapper = document.getElementById("downloadProgressWrapper");
const progressBarText = document.getElementById("downloadProgressText");
const speedElement = document.getElementById("downloadSpeedText");
const stageElement = document.getElementById("downloadStage");
const stopBtn = document.getElementById("stopBtn");
if (progressWrapper) progressWrapper.style.display = "block";
if (progressBarText) {
progressBarText.style.display = "block";
progressBarText.innerHTML = "0%";
}
if (speedElement) {
speedElement.style.display = "block";
speedElement.textContent = "0 Mib/s";
}
if (stageElement) {
stageElement.style.display = "block";
stageElement.textContent = "Retrieving data...";
}
if (stopBtn) stopBtn.style.display = "inline-flex";
}
/**
* Cancels the current download process.
*
* Sends a request to backend `/download/cancel`,
* updates UI state and resets progress indicators.
*
* Prevents duplicate calls if no download is active.
*/
async function stopDownload() {
if (!isDownloading) return;
userStoppedDownload = true;
button.disabled = true;
stopBtn.disabled = true;
try {
await fetch("/download/cancel", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
window.showWarning("Download stopped");
} catch (err) {
console.error("Error stopping download:", err);
} finally {
isDownloading = false;
button.disabled = false;
stopBtn.disabled = false;
stopBtn.style.display = "none";
resetProgressBar();
}
}
/**
* Resets all download progress UI elements to their
* default hidden state.
*
* Used after completion or cancellation of a download.
*/
function resetProgressBar() {
const progressWrapper = document.getElementById("downloadProgressWrapper");
if (progressWrapper) progressWrapper.style.display = "none";
const progressBar = document.getElementById("downloadProgress");
if (progressBar) progressBar.style.width = "0%";
const progressBarText = document.getElementById("downloadProgressText");
if (progressBarText) progressBarText.style.display = "none";
const speedElement = document.getElementById("downloadSpeedText");
if (speedElement) speedElement.style.display = "none";
const playlistInfoElement = document.getElementById("playlistInfoText");
if (playlistInfoElement) playlistInfoElement.style.display = "none";
}

View File

@@ -1,136 +1,276 @@
/*
This file is part of Freedom Loader.
/**
* Converts a raw upload date string (format YYYYMMDD)
* into a human-readable format (DD/MM/YYYY).
*
* Returns "Inconnue" if the input is invalid or malformed.
*/
function formatDate(dateStr) {
if (!dateStr || dateStr.length !== 8) return "Inconnue";
return `${dateStr.slice(6,8)}/${dateStr.slice(4,6)}/${dateStr.slice(0,4)}`;
}
Copyright (C) 2025 MasterAcnolo
/**
* Converts a file size in bytes into megabytes (MB).
*
* Returns a formatted string with 2 decimals (e.g. "12.34 Mo").
* If input is falsy, returns "Inconnue".
*/
function formatSize(bytes) {
return bytes ? (bytes / (1024*1024)).toFixed(2) + " Mo" : "Inconnue";
}
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.
/**
* Fetches video or playlist metadata from the backend `/info` endpoint.
*
* Sends a POST request with URL-encoded body.
* Handles network errors, HTTP errors and JSON parsing safely.
*
* @param {string} url - Video or playlist URL
* @returns {Promise<Object>} Parsed metadata or error object
*/
async function fetchVideoInfo(url) {
try {
const res = await fetch(`/info?url=${encodeURIComponent(url)}`, {
method: "GET",
headers: {"Accept": "application/json"}
});
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 data = await res.json();
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
if (!res.ok) return {error: `An Error occured when fetching info`};
// 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");
if (!data) return { error: "Data is Missing" };
// Stocke la dernière URL fetchée pour éviter les requêtes répétées inutiles
let lastFetchedUrl = "";
return data;
} catch (e) {
return { error: "Network or JSON Issue" };
}
}
// Active les logs DEBUG pour le développement
const DEBUG = true;
/**
* Initializes the video info system UI.
*
* - Listens to URL input changes
* - Automatically fetches video/playlist metadata if enabled
* - Renders either playlist UI or single video UI
* - Handles loading states and caching (lastFetchedUrl)
*
* Also manages dynamic playlist title injection into the form.
*/
async function init() {
// Écoute les changements dans l'input URL
urlInput.addEventListener("input", async () => {
const url = urlInput.value.trim();
// Main input listener: triggers auto-fetch when URL changes
document.addEventListener("DOMContentLoaded", () => {
const urlInput = document.getElementById("UrlInput");
const infoDiv = document.getElementById("videoInfo");
const loaderBox = document.getElementById("loaderBox");
const form = document.getElementById("downloadForm");
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;
// Input caché pour stocker le titre de la playlist
let playlistTitleInput = document.getElementById("playlistTitle");
if (!playlistTitleInput) {
playlistTitleInput = document.createElement("input");
playlistTitleInput.type = "hidden";
playlistTitleInput.name = "playlistTitle";
playlistTitleInput.id = "playlistTitle";
form.appendChild(playlistTitleInput);
}
// Si l'URL est identique à la dernière requêtée, on ne refait pas la requête
if (url === lastFetchedUrl) {
if (DEBUG) console.log("[DEBUG] Même URL que précédemment, pas de fetch.");
return;
}
let lastFetchedUrl = "";
// Mémorise l'URL actuelle comme dernière URL traitée
lastFetchedUrl = url;
urlInput.addEventListener("input", async () => {
try {
if (DEBUG) console.log("[DEBUG] Envoi requête POST vers /info avec URL:", url);
const configFeatures = await window.electronAPI.getFeatures();
if (!configFeatures.autoCheckInfo) return;
// Envoi de la requête POST pour récupérer les infos de la vidéo
const res = await fetch("/info", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ url }),
});
const url = urlInput.value.trim();
if (DEBUG) console.log("[DEBUG] Réponse status:", res.status);
// Si la réponse HTTP est une erreur, on affiche un message
if (!res.ok) {
console.error(`[ERROR] Réponse HTTP ${res.status}: ${res.statusText}`);
infoDiv.innerHTML = "❌ Impossible de récupérer les infos (erreur serveur).";
infoDiv.classList.remove("visible");
// IF field empty -> total reset
if (!url || url.length < 2) {
infoDiv.innerHTML = "";
infoDiv.classList.remove("visible", "playlist-mode");
playlistTitleInput.value = "";
lastFetchedUrl = "";
return;
}
// Parse la réponse JSON contenant les infos vidéo
const data = await res.json();
if (DEBUG) console.log("[DEBUG] Données reçues:", data);
if (url === lastFetchedUrl) return;
lastFetchedUrl = url;
// Vérification simple que les données attendues sont présentes
if (!data || !data.title || !data.thumbnail) {
console.warn("[WARNING] Données incomplètes", data);
infoDiv.innerHTML = "❌ Données incomplètes reçues.";
infoDiv.classList.remove("visible");
loaderBox.style.display = "flex";
const data = await fetchVideoInfo(url);
loaderBox.style.display = "none";
if (data.error) {
infoDiv.innerHTML = `
<div style="
padding:7px;
background:var(----infos-box-color);
">
<strong>${data.error}</strong>
</div>
`;
infoDiv.classList.add("visible");
infoDiv.classList.remove("playlist-mode");
playlistTitleInput.value = "";
loaderBox.style.display = "none";
return;
}
// Formatage de la durée en minutes et secondes
const minutes = Math.floor(data.duration / 60);
const seconds = data.duration % 60;
const durationStr = `${minutes}m ${seconds.toString().padStart(2, "0")}s`;
// ---------- PLAYLIST ----------
if (data.type === "playlist") {
playlistTitleInput.value = data.title;
infoDiv.classList.add("playlist-mode");
infoDiv.innerHTML = `
<div class="playlist-header">
<div class="playlist-info">
<div class="playlist-badge">Detected Playlist</div>
<h3 class="playlist-title">${data.title}</h3>
<div class="playlist-meta">
<span class="playlist-count">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 6H20M4 12H20M4 18H11" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
${data.count} vidéos
</span>
${data.channel ? `<span class="playlist-channel">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="2"/>
<path d="M5 20C5 16.134 8.134 13 12 13C15.866 13 19 16.134 19 20" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
${data.channel}
</span>` : ''}
</div>
</div>
</div>
<div id="playlistVideos"></div>
`;
// Formatage de la date d'upload au format JJ/MM/AAAA
const date = data.upload_date || "";
const readableDate = date
? `${date.slice(6, 8)}/${date.slice(4, 6)}/${date.slice(0, 4)}`
: "Inconnue";
const listDiv = document.getElementById("playlistVideos");
// Conversion taille approximative en Mo
const sizeMB = data.filesize_approx
? (data.filesize_approx / (1024 * 1024)).toFixed(2) + " Mo"
: "Inconnue";
data.videos.forEach(v => {
const durationStr = v.duration
? `${Math.floor(v.duration / 60)}:${(v.duration % 60).toString().padStart(2,"0")}` : "--:--";
// Gestion des catégories, affichage par défaut si absentes
const categories = data.categories
? data.categories.join(", ")
: "Non spécifiées";
const videoUrl = v.id ? `https://www.youtube.com/watch?v=${v.id}` : v.url;
listDiv.innerHTML += `
<div class="playlist-video-card">
<div class="video-thumbnail-wrapper">
<img src="${v.thumbnail}" alt="${v.title}" class="video-thumbnail">
<span class="video-duration">${durationStr}</span>
</div>
<div class="video-content">
<h4 class="video-title" title="${v.title}">${v.title}</h4>
${v.uploader ? `<p class="video-uploader">${v.uploader}</p>` : ''}
<div class="video-actions">
<a href="${videoUrl}" target="_blank" class="video-link">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
Ouvrir
</a>
<button class="copy-btn" data-url="${videoUrl}" title="Copier l'URL">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="8" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"/>
<path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2" stroke="currentColor" stroke-width="2"/>
</svg>
</button>
</div>
</div>
</div>
`;
});
// Copy Button
listDiv.addEventListener("click", (event) => {
if (event.target.classList.contains("copy-btn") || event.target.closest(".copy-btn")) {
const btn = event.target.closest(".copy-btn") || event.target;
if (btn.disabled) return;
btn.disabled = true;
const url = btn.dataset.url;
navigator.clipboard.writeText(url)
.then(() => {
const original = btn.innerHTML;
btn.style.opacity = 0;
btn.style.transform = "scale(0.7)";
setTimeout(() => {
btn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`;
btn.style.opacity = 1;
btn.style.transform = "scale(1)";
setTimeout(() => {
btn.style.opacity = 0;
btn.style.transform = "scale(0.7)";
setTimeout(() => {
btn.innerHTML = original;
btn.style.opacity = 1;
btn.style.transform = "scale(1)";
btn.disabled = false;
}, 300);
}, 1000);
}, 300);
})
.catch(() => {
const original = btn.innerHTML;
btn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/>
</svg>`;
setTimeout(() => {
btn.innerHTML = original;
btn.disabled = false;
}, 1500);
});
}
});
infoDiv.classList.add("visible");
return;
}
infoDiv.classList.remove("playlist-mode");
playlistTitleInput.value = "";
// ---------- 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 = `
<h3>${data.title}</h3>
<img src="${data.thumbnail}" width="320" alt="Thumbnail">
<ul>
<li><strong>Durée :</strong> ${durationStr}</li>
<li><strong>Duration :</strong> ${durationStr}</li>
<li><strong>Uploader :</strong> ${data.uploader || "Inconnu"}</li>
<li><strong>Date d’upload :</strong> ${readableDate}</li>
<li><strong>Vues :</strong> ${data.view_count?.toLocaleString() || "?"}</li>
<li><strong>Upload Date :</strong> ${readableDate}</li>
<li><strong>Views :</strong> ${data.view_count?.toLocaleString() || "?"}</li>
<li><strong>Likes :</strong> ${data.like_count?.toLocaleString() || "?"}</li>
<li><strong>URL :</strong> <a href="${data.webpage_url}" target="_blank" rel="noopener noreferrer">${data.webpage_url}</a></li>
<li><strong>Channel :</strong> <a href="${data.channel_url}" target="_blank" rel="noopener noreferrer">${data.channel_url}</a></li>
<li><strong>Taille estimée :</strong> ${sizeMB}</li>
<li><strong>Catégories :</strong> ${categories}</li>
<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">${data.channel_url}</a></li>
<li><strong>Estimed Size :</strong> ${sizeStr}</li>
<li><strong>Category :</strong> ${categories}</li>
</ul>
`;
// Affiche la div contenant les infos
infoDiv.classList.add("visible");
});
if (DEBUG) console.log("[DEBUG] Info affichée avec succès.");
} catch (e) {
// En cas d'erreur réseau ou JSON, on affiche un message d'erreur
console.error("[CRITICAL] Erreur lors de la récupération:", e);
infoDiv.innerHTML = "❌ Erreur réseau ou JSON.";
infoDiv.classList.remove("visible");
}
});
});
}
init();

View File

@@ -0,0 +1,136 @@
/**
* DOM elements used for download progress UI rendering.
* These elements are updated in real-time via SSE streams.
*/
const progressWrapper = document.getElementById("downloadProgressWrapper");
const progressBar = document.getElementById("downloadProgress");
const progressBarText = document.getElementById("downloadProgressText");
const speedElement = document.getElementById("downloadSpeedText");
const stageElement = document.getElementById("downloadStage");
const playlistInfoElement = document.getElementById("playlistInfoText");
/**
* Server-Sent Events streams for real-time download updates.
* Each stream is responsible for a specific aspect of the download state.
*/
const speedEvt = new EventSource("/download/speed");
const stageEvt = new EventSource("/download/stage");
const playlistInfoEvt = new EventSource("/download/playlist-info");
/**
* Resets the progress UI to its initial state.
* Hides all dynamic elements and clears displayed values.
*/
function startProgress() {
progressBar.style.width = "0%";
progressBarText.innerHTML = "0%";
speedElement.innerHTML = "0 Mib/s";
}
/**
* Makes the progress UI visible and enables related controls
* such as the stop button.
*/
function showProgress() {
progressWrapper.style.display = "block";
progressBarText.style.display = "block";
speedElement.style.display = "block";
const stopBtn = document.getElementById("stopBtn");
if (stopBtn) stopBtn.style.display = "inline-flex";
}
/**
* Updates the progress bar UI with the given percentage.
* Automatically triggers UI visibility if not already shown.
*
* @param {number} percent - Download progress percentage (0–100)
*/
function updateProgress(percent) {
if (percent > 0 && progressWrapper.style.display !== "block") {
showProgress();
}
progressBar.style.width = `${percent}%`;
progressBarText.innerHTML = `${percent}%`;
}
/**
* Fully resets the progress UI and hides all download indicators.
* Called after completion or cancellation.
*/
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";
stageElement.textContent = "";
stageElement.style.display = "none";
playlistInfoElement.textContent = "";
playlistInfoElement.style.display = "none";
const stopBtn = document.getElementById("stopBtn");
if (stopBtn) stopBtn.style.display = "none";
}
/**
* Main SSE stream handling download progress updates.
* Supports:
* - reset signal
* - done signal
* - percentage updates
*/
const evtSource = new EventSource("/download/progress");
evtSource.onmessage = e => {
if (e.data === "reset") {
startProgress();
window.electronAPI.setProgress(0);
return;
}
if (e.data === "done") {
setTimeout(() => {
resetProgress();
window.electronAPI.setProgress(-1);
}, 2000);
return;
}
const percent = parseFloat(e.data);
if (!isNaN(percent)) {
updateProgress(percent);
window.electronAPI.setProgress(percent);
}
};
/**
* SSE stream: download speed updates (e.g. "5.2 MiB/s").
*/
speedEvt.onmessage = e => {
speedElement.style.display = "block";
speedElement.textContent = e.data;
};
/**
* SSE stream: download stage updates (e.g. downloading, merging, extracting).
*/
stageEvt.onmessage = e => {
stageElement.style.display = "block";
stageElement.textContent = e.data;
};
/**
* SSE stream: playlist progress updates (e.g. "Item 1 of 15").
*/
playlistInfoEvt.onmessage = e => {
playlistInfoElement.style.display = "block";
playlistInfoElement.textContent = e.data;
};

121
public/script/reportBug.js Normal file
View File

@@ -0,0 +1,121 @@
class BugReportModal {
constructor() {
this.modal = null;
this.form = null;
this.init();
}
init() {
this.modal = document.createElement("div");
this.modal.className = "bug-modal-overlay";
this.modal.style.cssText = "display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.7); justify-content:center; align-items:center; z-index:99999; font-family:sans-serif;";
this.modal.innerHTML = `
<div class="bug-modal-content" style="background:#14141491 !important; color:#ffffff !important; padding:24px !important; border-radius:10px !important; width:420px !important; box-shadow: 0 10px 25px rgba(0,0,0,0.8) !important; display:flex !important; flex-direction:column !important; gap:16px !important; border: 1px solid #333 !important; box-sizing: border-box !important;">
<h3 style="margin:0 !important; font-size:1.2rem !important; font-weight:600 !important; color:#ffffff !important;">Report a Bug</h3>
<form id="bugReportForm" style="display:flex !important; flex-direction:column !important; gap:12px !important; width:unset !important; margin:0 !important; background:transparent !important; box-shadow: unset !important;">
<div style="display:flex !important; flex-direction:column !important; gap:4px !important;">
<label style="font-size:0.85rem !important; color:#aaaaaa !important;">Title <span style="color: red; font-weight: bold" >*</span> </label>
<input type="text" id="bugTitle" required style="background:#2a2a2a !important; border:1px solid #444 !important; color:#ffffff !important; border-radius:6px !important; padding:8px 10px !important; outline:none !important; width:100% !important; box-sizing: border-box !important;" />
</div>
<div style="display:flex !important; flex-direction:column !important; gap:4px !important;">
<label style="font-size:0.85rem !important; color:#aaaaaa !important;">Description <span style="color: red; font-weight: bold" >*</span></label>
<textarea id="bugDescription" placeholder="Provide some details about the bug" required rows="4" style="background:#2a2a2a !important; border:1px solid #444 !important; color:#ffffff !important; border-radius:6px !important; padding:8px 10px !important; outline:none !important; resize:vertical !important; width:100% !important; box-sizing: border-box !important;"></textarea>
</div>
<div style="display:flex !important; flex-direction:column !important; gap:4px !important;">
<label style="font-size:0.85rem !important; color:#aaaaaa !important;">Include Logs From Today</label>
<select id="bugIncludeLogs" style="background:#2a2a2a !important; border:1px solid #444 !important; color:#ffffff !important; border-radius:6px !important; padding:8px 10px !important; outline:none !important; width:100% !important; box-sizing: border-box !important;">
<option value="yes" style="background:#2a2a2a !important; color:#ffffff !important;">Yes</option>
<option value="no" style="background:#2a2a2a !important; color:#ffffff !important;">No</option>
</select>
</div>
<div style="display:flex !important; justify-content:flex-end !important; gap:8px !important; margin-top:8px !important;">
<button type="button" id="bugCancelBtn" style="background:#333333 !important; border:none !important; color:#cccccc !important; padding:8px 14px !important; border-radius:6px !important; cursor:pointer !important;">Cancel</button>
<button type="submit" style="background:#4f46e5 !important; border:none !important; color:#ffffff !important; padding:8px 16px !important; border-radius:6px !important; cursor:pointer !important; font-weight:500 !important;">Send</button>
</div>
</form>
</div>
`;
document.body.appendChild(this.modal);
this.form = this.modal.querySelector("#bugReportForm");
this.bindEvents();
}
bindEvents() {
const titleInput = this.modal.querySelector("#bugTitle");
const descInput = this.modal.querySelector("#bugDescription");
titleInput.addEventListener("input", () => {
localStorage.setItem("draft_bug_title", titleInput.value);
});
descInput.addEventListener("input", () => {
localStorage.setItem("draft_bug_desc", descInput.value);
});
this.modal.querySelector("#bugCancelBtn").addEventListener("click", () => {
this.close();
});
this.modal.addEventListener("click", (e) => {
if (e.target === this.modal) {
this.close();
}
});
this.form.addEventListener("submit", async (e) => {
e.preventDefault();
const data = {
title: titleInput.value,
description: descInput.value,
includeLogs: this.modal.querySelector("#bugIncludeLogs").value
};
window.electronAPI.logInfo("Submitting:", data);
try {
const success = await window.topbarAPI.sendReport(data);
if (success) {
localStorage.removeItem("draft_bug_title");
localStorage.removeItem("draft_bug_desc");
this.form.reset();
this.close();
window.showSuccess("Bug report sent successfully!");
}
} catch (err) {
window.showError("Failed to send bug report.");
window.electronAPI.logError("Failed to send bug report:", err.message);
}
});
}
loadState() {
const savedTitle = localStorage.getItem("draft_bug_title");
const savedDesc = localStorage.getItem("draft_bug_desc");
if (savedTitle) {
this.modal.querySelector("#bugTitle").value = savedTitle;
}
if (savedDesc) {
this.modal.querySelector("#bugDescription").value = savedDesc;
}
}
open() {
this.loadState();
this.modal.style.display = "flex";
}
close() {
this.modal.style.display = "none";
}
}
window.bugModal = new BugReportModal();
document.getElementById("report-bug-btn").addEventListener("click", () => {
window.bugModal.open();
});

View File

@@ -0,0 +1,104 @@
/**
* Settings panel DOM elements used to control application feature flags
* and configuration UI interactions.
*/
const settingsPanel = document.getElementById("settings-panel");
const settingsModal = document.querySelector(".settings-modal");
const settingsButton = document.getElementById("settings-button");
/**
* Tracks current settings panel state.
* NOTE: actual visibility is derived from DOM style in current implementation.
*/
let isOpen = false;
/**
* Toggles visibility of the settings panel when clicking the settings button.
*/
settingsButton.addEventListener("click", () => {
settingsPanel.classList.toggle("open");
});
/**
* Closes the settings panel and resets internal state.
*/
function closePanel() {
isOpen = false;
settingsPanel.classList.remove("open");
}
/**
* Loads feature flags from Electron backend and syncs them
* with corresponding UI controls (checkboxes, selects).
*
* Also registers change listeners to persist updates in real time.
*/
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 (!key) return;
if (features[key] !== undefined) {
if (el.type === "checkbox") el.checked = features[key];
else if (el.tagName === "SELECT") el.value = features[key];
}
el.addEventListener("change", () => {
if (key === "theme") return;
const value = el.type === "checkbox" ? el.checked : el.value;
window.electronAPI.setFeature(key, value);
});
});
}
/**
* Opens configuration JSON file via Electron topbar API.
*/
document.getElementById("open-json-btn")
?.addEventListener("click", () => {
window.topbarAPI.openConfig();
});
/**
* Opens theme directory via Electron topbar API.
*/
document.getElementById("open-theme")
?.addEventListener("click", () => {
window.topbarAPI.openTheme();
});
/**
* Refreshes theme list and updates UI selector with a short animation.
*/
document.getElementById("refresh-themes-btn")
?.addEventListener("click", function () {
this.classList.add("spinning");
window.refreshThemes();
setTimeout(() => {
this.classList.remove("spinning");
}, 600);
});
/**
* Clicking the overlay outside the modal closes the settings panel.
*/
settingsPanel?.addEventListener("click", closePanel);
/**
* Prevents modal clicks from closing the settings panel
* by stopping event propagation to the overlay.
*/
settingsModal?.addEventListener("click", (e) => {
e.stopPropagation();
});
/**
* Initializes settings UI by loading backend feature flags.
*/
loadSettings();

85
public/script/toast.js Normal file
View File

@@ -0,0 +1,85 @@
/**
* Displays a toast notification in the global UI container.
*
* Toasts are automatically appended to `#toast-container`,
* support multiple visual types, and can auto-dismiss after a delay.
*
* @param {string} message - Message displayed inside the toast
* @param {"success"|"error"|"warning"|"info"} [type="info"] - Visual style of the toast
* @param {number} [duration=4000] - Auto-dismiss delay in milliseconds (0 disables auto removal)
* @returns {HTMLElement|null} The created toast element or null if container is missing
*/
window.showToast = function (message, type = "info", duration = 4000) {
const container = document.getElementById("toast-container");
if (!container) return null;
const toast = document.createElement("div");
toast.classList.add("toast", type);
const icons = {
success: "check_circle",
error: "error",
warning: "warning",
info: "info"
};
const icon = icons[type] || icons.info;
toast.style.setProperty("--toast-duration", `${duration}ms`);
toast.innerHTML = `
<span class="toast-icon material-icons-round">${icon}</span>
<span class="toast-message">${message}</span>
<button class="toast-close" title="Close">×</button>
<div class="toast-progress"></div>
`;
const closeBtn = toast.querySelector(".toast-close");
closeBtn.addEventListener("click", () => removeToast(toast));
container.appendChild(toast);
if (duration > 0) {
setTimeout(() => removeToast(toast), duration);
}
return toast;
};
/**
* Removes a toast element with a small exit animation.
*
* @param {HTMLElement} toast
*/
function removeToast(toast) {
if (!toast?.parentElement) return;
toast.classList.add("removing");
setTimeout(() => {
toast.remove();
}, 300);
}
/**
* Shortcut: success toast
*/
window.showSuccess = (message, duration = 4000) =>
window.showToast(message, "success", duration);
/**
* Shortcut: error toast
*/
window.showError = (message, duration = 5000) =>
window.showToast(message, "error", duration);
/**
* Shortcut: warning toast
*/
window.showWarning = (message, duration = 4000) =>
window.showToast(message, "warning", duration);
/**
* Shortcut: info toast
*/
window.showInfo = (message, duration = 4000) =>
window.showToast(message, "info", duration);

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

@@ -0,0 +1,59 @@
/**
* Initializes topbar button event listeners and binds them
* to the Electron preload API exposed in `window.topbarAPI`.
*
* This function is safe to call once DOM is ready.
* It silently aborts if the API or elements are missing.
*/
export function initTopBar() {
const { topbarAPI } = window;
if (!topbarAPI) return;
const minBtn = document.getElementById("minimize-btn");
if (minBtn) minBtn.onclick = () => topbarAPI.minimize();
const maxBtn = document.getElementById("maximize-btn");
if (maxBtn) maxBtn.onclick = () => topbarAPI.maximize();
const closeBtn = document.getElementById("close-btn");
if (closeBtn) closeBtn.onclick = () => topbarAPI.close();
const devtoolsBtn = document.getElementById("devtools-btn");
if (devtoolsBtn) devtoolsBtn.onclick = () => topbarAPI.openDevTools();
const logsBtn = document.getElementById("logs-btn");
if (logsBtn) logsBtn.onclick = () => topbarAPI.openLogs();
const websiteBtn = document.getElementById("website-btn");
if (websiteBtn) websiteBtn.onclick = () => topbarAPI.openWebsite();
const wikiBtn = document.getElementById("wiki-btn");
if (wikiBtn) wikiBtn.onclick = () => topbarAPI.openWiki();
const workshopBtn = document.getElementById("workshop-btn");
if (workshopBtn) workshopBtn.onclick = () => topbarAPI.openWorkshop();
}
async function attachListeners(){
try{
const featuresList = await window.electronAPI.getFeatures();
if (!featuresList.customTopBar) {
const topbar = document.getElementById("topbar");
const container = document.getElementById("container");
const themeSwitcher = document.getElementById("theme-switcher");
if (topbar) topbar.style.display = "none";
if (container) container.style.marginTop = "0";
if (themeSwitcher) themeSwitcher.style.top = "30px";
}
} catch (error) {
console.error("Failed to load layout features for topbar:", error);
}
}
initTopBar()
document.addEventListener("DOMContentLoaded", attachListeners)

94
public/splash.html Normal file
View File

@@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%;
height: 100%;
background: transparent;
overflow: hidden;
}
.splash-container {
width: 100%;
height: 100%;
background: #D9D9D9;
border-radius: 12px;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 28px;
font-family: "Segoe UI", sans-serif;
}
.progress-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.progress-track {
width: 180px;
height: 3px;
background: #d0d0d0;
border-radius: 99px;
overflow: hidden;
}
.progress-bar {
height: 100%;
width: 0%;
background: #1a1a1a;
border-radius: 99px;
transition: width 0.3s ease;
}
.status {
font-size: 14px;
color: #6d6d6d;
letter-spacing: 0.5px;
}
</style>
</head>
<body>
<div class="splash-container">
<img src="./banner.png" alt="Freedom Loader" style="width: 90%;">
<div class="progress-wrapper">
<div class="progress-track">
<div class="progress-bar" id="progress-bar"></div>
</div>
<span class="status" id="status">Starting...</span>
</div>
</div>
<script>
const steps = [
"Checking dependencies...",
"Starting server...",
"Loading themes...",
"Almost ready...",
];
let current = 0;
const bar = document.getElementById("progress-bar");
const status = document.getElementById("status");
function advance(label, percent) {
bar.style.width = percent + "%";
status.textContent = label;
}
window.setProgress = (step) => {
if (step >= steps.length) {
advance("", 100);
return;
}
const percent = Math.round(((step + 1) / steps.length) * 100);
advance(steps[step], percent);
};
window.setProgress(0);
</script>
</body>
</html>

View File

@@ -0,0 +1,128 @@
:root {
--border-accent: #0ea5e966;
--bg-accent: #0ea5e915;
--fill-accent: #0ea5e9;
--text-accent: #0369a1;
--text-secondary: #64748b;
}
#updateAvailable {
display: none;
width: fit-content;
max-width: 420px;
position: absolute;
right: 18px;
bottom: 5%;
z-index: 50;
}
.update-banner {
display: flex;
flex-direction: column;
gap: 12px;
padding: 12px 16px;
border-radius: 10px;
border: 0.5px solid var(--border-accent);
background: var(--bg-accent);
backdrop-filter: blur(8px);
}
.banner-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.banner-left {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.icon-wrap {
width: 32px;
height: 32px;
border-radius: 8px;
background: var(--fill-accent);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.icon-wrap i {
font-size: 16px;
color: #fff;
}
.text-block {
display: flex;
flex-direction: column;
gap: 4px;
}
.update-title {
font-size: 13px;
font-weight: 600;
color: var(--text-accent);
}
.update-sub {
font-size: 11px;
color: var(--text-secondary);
line-height: 1.4;
}
.btn-update {
font-size: 12px;
font-weight: 500;
padding: 6px 14px;
border-radius: 8px;
background: var(--fill-accent);
color: #fff;
border: none;
cursor: pointer;
white-space: nowrap;
flex-shrink: 0;
transition: opacity 0.2s;
}
.btn-update:hover:not(:disabled) {
opacity: 0.9;
}
.btn-update:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.progress-wrap {
display: flex;
flex-direction: column;
gap: 6px;
}
.progress-track {
height: 4px;
border-radius: 99px;
background: var(--border-accent);
overflow: hidden;
width: 100%;
}
.progress-fill {
height: 100%;
border-radius: 99px;
background: var(--fill-accent);
width: 0%;
transition: width 0.3s ease;
}
.progress-meta {
font-size: 11px;
color: var(--text-secondary);
display: flex;
justify-content: space-between;
}

View File

@@ -15,13 +15,13 @@
}
.checkmark {
--clr: var(--checkbox-bg-checked);
--clr: var(--checkbox-checked-bg-color);
position: relative;
top: 0;
left: 0;
height: 1.3em;
width: 1.3em;
background-color: var(--checkbox-bg-default);
background-color: var(--checkbox-unchecked-bg-color);
border-radius: .5rem;
transition: background-color 300ms, border-radius 300ms;
}

View File

@@ -17,9 +17,11 @@
border-radius: 6px;
font-family: monospace;
border: 1px dashed var(--form-input-border-color);
word-break: break-all;
display: inline-block;
max-width: 80%;
display: inline-flex;
max-width: clamp(200px, 60vw, 600px);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

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(--audio-only-label-color);
border-radius: 8px;
color: var(--audio-only-label-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,89 @@
/* Container pour isoler la progress bar */
.progress-container {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 10px 0;
margin-top: 20px;
}
/* Wrapper de la barre de téléchargement */
#downloadProgressWrapper {
width: 100%;
max-width: 400px;
height: 12px;
background-color: var(--progress-bar-bg-color, #cccccc);
border-radius: 10px;
overflow: hidden;
margin: 0;
display: none;
box-shadow: inset 0 1px 3px rgba(0,0,0,0.5);
box-sizing: border-box;
}
/* Barre de progression */
#downloadProgress {
height: 100%;
width: 0%;
background-color: var(--progress-bar-fill-color, #007bff);
border-radius: 10px;
transition: width 0.2s ease;
}
#downloadProgressText, #downloadSpeedText {
text-align: center;
font-size: 0.8rem;
color: #fff;
content: "";
display: none;
}
/* Responsive pour mobile */
@media (max-width: 480px) {
#downloadProgressWrapper {
max-width: 90%;
height: 10px;
}
#downloadProgressText, #downloadSpeedText {
font-size: 0.7rem;
}
}
/* Stop Button */
.stop-btn {
width: 40px;
height: 40px;
min-width: 40px;
padding: 0;
border: none;
border-radius: 8px;
background-color: var(--form-button-bg-color);
color: var(--form-button-text-color);
font-size: 20px;
font-weight: bold;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
.stop-btn:hover:not(:disabled) {
background-color: var(--form-button-bg-hover-color);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);
transform: scale(1.05);
}
.stop-btn:active:not(:disabled) {
transform: scale(0.96);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
.stop-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}

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

@@ -0,0 +1,162 @@
/* Toast Container */
#toast-container {
position: fixed;
top: 140px;
right: 20px;
display: flex;
flex-direction: column;
gap: 12px;
z-index: 9999;
pointer-events: none;
}
/* Toast */
.toast {
position: relative;
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
background-color: rgba(30, 30, 30, 0.9);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 10px;
color: #f3f4f6;
font-family: inherit;
font-size: 0.875rem;
max-width: 320px;
min-width: 220px;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 8px 10px -6px rgba(0, 0, 0, 0.2);
animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
pointer-events: auto;
overflow: hidden;
}
/* Toast Icon */
.toast-icon {
flex-shrink: 0;
font-size: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.toast-icon svg {
width: 100%;
height: 100%;
}
.toast.success .toast-icon {
color: #22c55e;
}
.toast.error .toast-icon {
color: #ef4444;
}
.toast.warning .toast-icon {
color: #f59e0b;
}
.toast.info .toast-icon {
color: #0ea5e9;
}
/* Toast Message */
.toast-message {
flex: 1;
font-weight: 400;
line-height: 1.4;
word-break: break-word;
}
/* Toast Close Button */
.toast-close {
flex-shrink: 0;
background: none;
border: none;
color: #9ca3af;
cursor: pointer;
font-size: 16px;
width: 24px;
height: 24px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.15s ease, color 0.15s ease;
}
.toast-close:hover {
background-color: rgba(255, 255, 255, 0.1);
color: #ffffff;
}
.toast-progress {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
width: 100%;
background-color: rgba(255, 255, 255, 0.1);
animation: progress var(--toast-duration, 4000ms) linear forwards;
}
.toast.success .toast-progress {
background-color: #22c55e;
}
.toast.error .toast-progress {
background-color: #ef4444;
}
/* Animations */
@keyframes slideIn {
from {
transform: translateY(-20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateY(0);
opacity: 1;
}
to {
transform: translateY(-20px);
opacity: 0;
}
}
@keyframes progress {
from {
width: 100%;
}
to {
width: 0%;
}
}
.toast.removing {
animation: slideOut 0.25s ease-in forwards;
}
@media (max-width: 480px) {
#toast-container {
top: auto;
bottom: 16px;
right: 16px;
left: 16px;
}
.toast {
max-width: none;
min-width: auto;
}
}

View File

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

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

@@ -40,8 +40,7 @@ form button:hover {
form#downloadForm {
display: flex;
gap: 0.5em;
max-width: 600px;
width: 100%;
width: 350px;
justify-content: center;
}
@@ -50,7 +49,7 @@ form#downloadForm {
align-items: center;
align-self: center;
gap: 0.5em;
color: var(--form-input-text-color);
color: var(--audio-only-label-color);
}
#UrlInput {
@@ -62,6 +61,7 @@ form#downloadForm {
transition: border-color 0.3s ease, box-shadow 0.3s ease;
background-color: var(--form-input-bg-color);
color: var(--form-input-text-color);
flex: 1;
}
#UrlInput:focus {
@@ -74,3 +74,40 @@ form#downloadForm {
#UrlInput::placeholder {
color: var(--form-input-placeholder-color);
}
.url-input-container {
display: flex;
align-items: center;
gap: 0.5em;
width: 100%;
}
.paste-btn {
background-color: var(--form-button-bg-color);
color: var(--form-button-text-color);
border: none;
padding: 0.75em;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 44px;
height: 44px;
}
.paste-btn:hover {
background-color: var(--form-button-bg-hover-color);
transform: scale(1.05);
}
.paste-btn:active {
transform: scale(0.95);
}
.paste-btn svg {
color: var(--paste-button-icon-color);
width: 30px;
height: 30px;
}

View File

@@ -1,11 +1,13 @@
header {
position: fixed;
top: 0;
width: 100%;
height: 48px;
z-index: 1100;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 2vw;
align-items: center;
}
header h1{
margin-bottom: 0.2vw;
#title {
color: var(--default-text-color);
}

View File

@@ -0,0 +1,288 @@
/* SETTINGS BUTTON */
.settings-button {
display: flex;
align-items: center;
justify-content: center;
position: fixed;
top: 60px;
right: 20px;
opacity: 0.8;
z-index: 15;
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-modal-bg-color);
border-radius: 12px;
padding: 20px;
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
overflow-y: auto;
z-index: 9999;
/*animation: slideIn 0.2s ease-out; */
color: var(--settings-text-color);
display: block;
opacity: 0;
pointer-events: none;
transform: translateX(20px);
transition: opacity 0.2s ease-out, transform 0.2s ease-out;
}
.settings-panel.open {
opacity: 1;
pointer-events: auto;
transform: translateX(0);
}
.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-section-bg-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(--open-json-button-bg-color);
color: var(--open-json-button-text-color);
}
#open-theme {
padding: 10px 14px;
border-radius: 8px;
border: none;
cursor: pointer;
font-weight: 600;
background-color: var(--open-theme-button-bg-color);
color: var(--open-theme-button-text-color);
}
#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;
}
/* REFRESH THEMES BUTTON */
.refresh-themes-btn {
padding: 6px 8px;
border-radius: 6px;
border: 1px solid var(--form-input-border-color);
background-color: var(--form-input-bg-color);
color: var(--form-input-text-color);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.refresh-themes-btn:hover {
background-color: var(--form-button-bg-hover-color);
}
.refresh-themes-btn.spinning svg {
animation: spin 0.6s ease-in-out;
}
.refresh-themes-btn svg {
color: currentColor;
transition: transform 0.6s ease-in-out;
}
/* 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; }
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

View File

@@ -0,0 +1,108 @@
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 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

@@ -4,7 +4,7 @@
margin-left: auto;
margin-right: auto;
padding: 1.5em 2em;
background-color: var(--infos-box-color);
background-color: var(--video-info-bg-color);
border-radius: 12px;
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
color: var(--video-info-text-color);
@@ -21,7 +21,6 @@
#videoInfo.visible {
opacity: 1;
max-height: 1500px;
overflow: visible;
}
#videoInfo h3 {
@@ -68,7 +67,295 @@
text-decoration: underline;
}
/* --- Playlist Mode --- */
#videoInfo.playlist-mode {
max-width: 95%;
}
/* Playlist Header */
.playlist-header {
display: flex;
flex-direction: column;
padding: 2rem;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.03));
border-radius: 16px;
margin-bottom: 2rem;
border: 1px solid rgba(255, 255, 255, 0.12);
backdrop-filter: blur(10px);
}
.playlist-badge {
display: inline-flex;
align-items: center;
width: fit-content;
padding: 0.4rem 0.9rem;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px;
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--video-info-text-color);
margin-bottom: 1rem;
}
.playlist-info {
flex: 1;
}
.playlist-title {
font-size: 1.9rem;
margin: 0 0 0.6rem 0;
color: var(--video-info-heading-color);
font-weight: 700;
line-height: 1.2;
}
.playlist-meta {
display: flex;
gap: 1.5rem;
font-size: 0.95rem;
color: var(--video-info-text-color);
opacity: 0.85;
flex-wrap: wrap;
}
.playlist-count,
.playlist-channel {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.4rem 0.8rem;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.08);
font-weight: 500;
}
.playlist-count svg,
.playlist-channel svg {
width: 16px;
height: 16px;
flex-shrink: 0;
}
/* Layout liste des vidéos */
#playlistVideos {
margin-top: 1em;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}
/* Carte vidéo playlist */
.playlist-video-card {
background: var(--playlist-background-color);
border-radius: 14px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0,0,0,0.12);
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
flex-direction: column;
border: 1px solid rgba(255, 255, 255, 0.1);
position: relative;
}
.playlist-video-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--video-info-link-color), transparent);
opacity: 0;
transition: opacity 0.25s ease;
}
.playlist-video-card:hover {
transform: translateY(-6px);
box-shadow: 0 12px 32px rgba(0,0,0,0.25);
border-color: rgba(255, 255, 255, 0.15);
}
.playlist-video-card:hover::before {
opacity: 1;
}
/* Thumbnail wrapper avec durée */
.video-thumbnail-wrapper {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
background: #000;
}
.video-thumbnail {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: transform 0.3s ease;
}
.playlist-video-card:hover .video-thumbnail {
transform: scale(1.05);
}
.video-duration {
position: absolute;
bottom: 8px;
right: 8px;
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 4px 8px;
border-radius: 6px;
font-size: 0.85rem;
font-weight: 700;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', monospace;
letter-spacing: 0.5px;
backdrop-filter: blur(4px);
}
/* Contenu vidéo */
.video-content {
padding: 1.2rem;
display: flex;
flex-direction: column;
gap: 0.6rem;
flex: 1;
background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.02));
}
.video-title {
font-size: 1rem;
font-weight: 600;
margin: 0;
color: var(--video-info-heading-color);
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
min-height: 2.8em;
transition: color 0.2s ease;
}
.playlist-video-card:hover .video-title {
color: var(--video-info-link-color);
}
.video-uploader {
font-size: 0.9rem;
color: var(--video-info-text-color);
opacity: 0.7;
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Actions vidéo */
.video-actions {
display: flex;
align-items: center;
gap: 0.6rem;
margin-top: auto;
padding-top: 0.8rem;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.video-link {
flex: 1;
text-decoration: none;
color: var(--video-info-link-color);
font-size: 0.9rem;
padding: 0.5rem 0.9rem;
border-radius: 8px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.08);
transition: all 0.2s ease;
text-align: center;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
}
.video-link svg {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.video-link:hover {
background: rgba(255, 255, 255, 0.12);
border-color: rgba(255, 255, 255, 0.15);
text-decoration: none;
transform: translateY(-1px);
}
.copy-btn {
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.08);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.5rem;
border-radius: 8px;
transition: all 0.2s ease;
width: 40px;
height: 40px;
flex-shrink: 0;
}
.copy-btn svg {
width: 18px;
height: 18px;
color: var(--video-info-text-color);
}
.copy-btn:hover {
background: rgba(255, 255, 255, 0.12);
border-color: rgba(255, 255, 255, 0.15);
transform: translateY(-1px);
}
.copy-btn:active {
transform: scale(0.95);
}
.copy-btn:disabled {
cursor: not-allowed;
opacity: 0.6;
}
/* Responsive */
@media (max-width: 768px) {
#playlistVideos {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 1rem;
}
.playlist-header {
padding: 1.5rem;
}
.playlist-title {
font-size: 1.5rem;
}
.playlist-meta {
gap: 0.8rem;
}
}
@media (max-width: 480px) {
#videoInfo {
padding: 1em;
@@ -78,4 +365,21 @@
#videoInfo h3 {
font-size: 1.4rem;
}
#playlistVideos {
grid-template-columns: 1fr;
}
.playlist-header {
padding: 1.2rem;
}
.playlist-title {
font-size: 1.3rem;
}
.playlist-badge {
font-size: 0.75rem;
padding: 0.4rem 0.8rem;
}
}

View File

@@ -1,63 +1,124 @@
/* Layout */
@import url("layout/topbar.css");
@import url("layout/header.css");
@import url("layout/form.css");
@import url("layout/container.css");
@import url("layout/ctr-infos.css");
@import url("layout/header.css");
@import url("layout/settingsPanel.css");
@import url("layout/videoinfo.css");
/* Components */
@import url("components/checkbox.css");
@import url("components/themebutton.css");
/* @import url("variables.css"); */
@import url("theme/themeimport.css");
@import url("components/loader.css");
@import url("components/editpathbutton.css");
@import url("components/progressBar.css");
@import url("components/toast.css");
@import url("components/autoUpdate.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');
#app {
display: block;
opacity: 0;
transition: opacity 0.6s ease;
/* Mark element as disabled */
.disabled {
pointer-events: none;
opacity: 0.5;
filter: grayscale(40%);
cursor: not-allowed;
}
#app.visible {
opacity: 1;
/* Because why not */
#reference{
position: fixed;
pointer-events: none;
bottom:0vh;
left: 10px;
font-size: 1em;
color: #b0b0b02f;
z-index: 9999;
}
html,body{
margin: 0;
padding: 0;
scroll-behavior: smooth;
/* outline: 2px solid red; */ /*<-- Utile pour le début ça */
/*outline: 2px solid red; /*<-- Utile pour le debug ça */
font-family: "Poppins", sans-serif;
font-weight: 500;
font-style: normal;
}
body {
margin: 0;
background-color: var(--background-color);
/* background-image: linear-gradient(to left top, #838383, #a1a1a1, #bfbfbf, #dfdfdf, #ffffff);
background-size: cover;
background-position: center;
background-attachment: fixed; */
body{
background-color: var(--background-color);
color: var(--default-text-color);
}
header {
position: fixed;
top: 0;
width: 100%;
height: 100%;
color: var(--default-text-color);
display: flex;
justify-content: center;
align-items: center;
z-index: 1100;
height: 48px;
}
main {
width: 100%;
height: calc(100vh - 48px);
margin-top: 48px;
overflow-y: auto;
overflow-x: hidden;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding: 0;
gap: 0;
}
header p{
/* Scrollbar pour main */
main::-webkit-scrollbar {
width: 10px;
height: 10px;
}
main::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.03);
border-radius: 5px;
}
main::-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;
}
main::-webkit-scrollbar-thumb:hover {
background-color: rgba(255, 255, 255, 0.35);
transform: scaleX(1.05);
}
/* Thème clair */
body.light main::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
}
body.light main::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.25);
}
body.light main::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, 0.45);
}
.container{
margin-top: 0px;
}
#subtitle{
color: var(--subtitle-color);
}
.checkbox-inline{
color: var(--mp3-text-color);
}
body,
.container,
input,

View File

@@ -1,98 +0,0 @@
body.chirac {
/* Background image Chirac */
background-image: url('../../assets/images/Chirac.png');
/* background-position: right; */
background-repeat: no-repeat;
background-size: cover;
background-attachment: fixed;
/* Couleurs drapeau français */
--background-color: rgba(255, 255, 255, 0.85); /* blanc légèrement transparent pour lisibilité */
--default-text-color: #002395; /* bleu foncé */
--subtitle-color: #EF4135; /* rouge vif */
--mp3-text-color: #002395; /* bleu */
/* Formulaire */
--form-bg-color: rgba(255, 255, 255, 0.9);
--form-input-bg-color: #f5f5f5;
--form-input-border-color: #002395;
--form-input-border-focus-color: #EF4135;
--form-input-text-color: #002395;
--form-input-placeholder-color: #999999;
--form-button-bg-color: #002395; /* bleu */
--form-button-text-color: #ffffff;
--form-button-bg-hover-color: #EF4135; /* rouge */
/* Checkbox */
--checkbox-bg-default: #cccccc;
--checkbox-bg-checked: #002395;
--checkbox-checkmark-border-color: #ffffff;
--checkbox-pulse-color: rgba(0, 35, 149, 0.5);
/* Download status */
--download-status-color: #002395;
/* Video Info Box */
--infos-box-color: rgba(255, 255, 255, 0.9);
--video-info-text-color: #002395;
--video-info-heading-color: #EF4135;
--video-info-list-color: #444444;
--video-info-list-strong-color: #000000;
--video-info-link-color: #002395;
--video-info-link-hover-color: #EF4135;
/* animation d'apparition */
opacity: 0;
transition: opacity 0.8s ease, background-color 0.8s ease;
}
body.chirac.theme-active {
opacity: 1;
}
/*
Je serai le président de tous les Français
Tous les Français
Tous les Français
Tous les Français
Tous les Français
Tous les Français
Tous les Français
Tous les Français
Tous les Français
Tous les Français
Mes chers compatriotes
Je serai le président de tous les Français, de tous les Français
Je mesure la difficulté de la tâche qui nous attend
Tous les Français
Tous les Français
Je veux un État, je veux les Français
Je veux un État, je veux
Je veux un État-tat-tat-tat vigoureux
Un État-tat-tat-tat impartial
Un État-tat-tat-tat vigoureux
Un État-tat-tat-tat exigeant
Un État-tat-tat-tat
Je serai le président de tous les Français
Tous les Français
Tous les Français
Tous les Français
Un État qui n'isole pas, tous, pas, pas
Tous, pas, tous, pas, pas
Tous, pas, tous, pas, pas (les Français)
Tous, tous, pas, tous, tous, pas, pas
Tous, tous, pas, tous, tous, pas, pas
Tous, tous, tous, pas, tous, pas, pas (les Français)
Tous, pas, tous, tous, pas, pas
Tous, tous, tous, pas, tous, tous, pas, pas
Tous, tous, pas, tous, tous, pas, pas
Tous, tous, tous-tous, tous-tous, pas, pas
Tous, tous-tous, tous-tous, pas, pas
Un État-tat-tat-tat vigoureux
Un État-tat-tat-tat impartial
Un État-tat-tat-tat exigeant
Un État-tat-tat-tat
Je serai le président de tous les Français
*/

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

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

View File

@@ -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

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

View File

@@ -1,36 +0,0 @@
body.light {
/* Couleurs générales */
--background-color: #dadada; /* fond clair */
--default-text-color: #222222; /* texte sombre */
--subtitle-color: #007bff; /* bleu accent */
--mp3-text-color: #000000; /* texte checkbox */
/* Formulaire */
--form-bg-color: #ffffff; /* fond formulaire blanc */
--form-input-bg-color: #fafafa; /* fond inputs */
--form-input-border-color: #cccccc; /* bordure inputs */
--form-input-border-focus-color: #007bff; /* focus bleu */
--form-input-text-color: #333333; /* texte inputs */
--form-input-placeholder-color: #888888; /* placeholder */
--form-button-bg-color: #007bff; /* bouton bleu */
--form-button-text-color: #ffffff; /* texte bouton */
--form-button-bg-hover-color: #0056b3; /* hover bleu foncé */
/* Checkbox */
--checkbox-bg-default: #cccccc;
--checkbox-bg-checked: #007bff;
--checkbox-checkmark-border-color: #ffffff;
--checkbox-pulse-color: rgba(0, 123, 255, 0.4);
/* Download status */
--download-status-color: #007bff;
/* Video Info Box */
--infos-box-color: #ffffff;
--video-info-text-color: #222222;
--video-info-heading-color: #007bff;
--video-info-list-color: #444444;
--video-info-list-strong-color: #000000;
--video-info-link-color: #007bff;
--video-info-link-hover-color: #0056b3;
}

View File

@@ -1,48 +0,0 @@
body.nf {
/* Couleurs générales */
--background-color: #1a1a1a; /* fond sombre neutre */
--default-text-color: #e6dfd5; /* texte beige clair */
--subtitle-color: #f5f0e6; /* beige très clair (titres) */
--mp3-text-color: #d8cfc2; /* texte checkbox */
/* Formulaire */
--form-bg-color: #2a2a2a; /* fond formulaire gris sombre */
--form-input-bg-color: #222222; /* fond inputs sombre */
--form-input-border-color: #4a4036; /* bordure beige/brun */
--form-input-border-focus-color: #cbb89d; /* beige doré en focus */
--form-input-text-color: #f0e8dd; /* texte clair/beige */
--form-input-placeholder-color: #9a8f80; /* placeholder beige/gris */
--form-button-bg-color: #cbb89d; /* bouton beige doré */
--form-button-text-color: #1a1a1a; /* texte sombre */
--form-button-bg-hover-color: #b7a588; /* hover beige plus foncé */
/* Checkbox */
--checkbox-bg-default: #4a4036; /* brun/gris */
--checkbox-bg-checked: #cbb89d; /* beige doré */
--checkbox-checkmark-border-color: #1a1a1a; /* noir profond */
--checkbox-pulse-color: rgba(203,184,157,0.3); /* halo beige doux */
/* Download status */
--download-status-color: #cbb89d; /* beige doré */
/* Video Info Box */
--infos-box-color: #2a2a2a; /* fond sombre */
--video-info-text-color: #e6dfd5; /* texte beige clair */
--video-info-heading-color: #f5f0e6; /* titres beige clair */
--video-info-list-color: #d8cfc2; /* texte liste */
--video-info-list-strong-color: #ffffff; /* texte fort blanc cassé */
--video-info-link-color: #cbb89d; /* liens beige doré */
--video-info-link-hover-color: #b7a588; /* hover beige plus foncé */
}
/* Fond inspiré de ton image beige */
body.nf {
background-image: url('../../assets/images/NF.jpg'); /* mets ton image ici */
background-size: cover;
background-position: 70% center;
background-attachment: fixed;
}
body.nf .download-path {
color: var(--default-text-color);
}

View File

@@ -1,50 +0,0 @@
/*
*/
body.spicy {
/* Couleurs générales */
--background-color: #121212; /* fond noir profond */
--default-text-color: #581010; /* texte clair */
--subtitle-color: #3d0909; /* accent rouge piment */
--mp3-text-color: #ffffff; /* texte checkbox inline */
/* Formulaire */
--form-bg-color: #1a1a1a; /* fond formulaire gris très foncé */
--form-input-bg-color: #2b2b2b; /* fond inputs gris foncé */
--form-input-border-color: #444444; /* bordure gris */
--form-input-border-focus-color: #ff1a1a; /* focus rouge piment */
--form-input-text-color: #eeeeee; /* texte inputs */
--form-input-placeholder-color: #888888; /* placeholder gris moyen */
--form-button-bg-color: #ff1a1a; /* bouton rouge vif */
--form-button-text-color: #ffffff; /* texte bouton blanc */
--form-button-bg-hover-color: #cc0000; /* hover rouge foncé */
/* Checkbox */
--checkbox-bg-default: #555555; /* gris moyen */
--checkbox-bg-checked: #ff1a1a; /* rouge piment */
--checkbox-checkmark-border-color: #f5f5f5; /* blanc cassé */
--checkbox-pulse-color: rgba(255, 26, 26, 0.5); /* pulse rouge translucide */
/* Download status */
--download-status-color: #ffffff; /* rouge vif */
/* Video Info Box */
--infos-box-color: #1a1a1a; /* fond infos gris foncé */
--video-info-text-color: #f5f5f5; /* texte clair */
--video-info-heading-color: #ff1a1a; /* titres rouges */
--video-info-list-color: #cccccc; /* texte liste gris clair */
--video-info-list-strong-color: #ffffff; /* texte important blanc */
--video-info-link-color: #ff1a1a; /* liens rouges */
--video-info-link-hover-color: #cc0000; /* liens hover rouge foncé */
}
/* Optionnel: background piments */
body.spicy {
background-image: url('../../assets/images/Spicy.webp');
background-size:contain;
background-position: center;
background-attachment: fixed;
}

View File

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

Some files were not shown because too many files have changed in this diff Show More