Compare commits

..

411 Commits

Author SHA1 Message Date
Axel Nicolas
e2b51d32d1 Merge pull request #43 from MasterAcnolo/1.6.0
1.5.1
2026-06-02 22:47:40 +02:00
MasterAcnolo
81515c6ab8 fix: revert version to 1.5.1 in package.json and README.md 2026-06-02 22:35:29 +02:00
MasterAcnolo
c88189b44f 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-06-01 22:39:12 +02:00
MasterAcnolo
b745ebd29f fix: display "dev" in app version if the app is not packaged 2026-05-31 21:45:56 +02:00
MasterAcnolo
13ee4c170b fix: fix width for form 2026-05-31 21:12:35 +02:00
MasterAcnolo
0218664c5a 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-05-31 20:38:42 +02:00
MasterAcnolo
6afc5e06d6 fix: app startup. Can't start another instance now. 2026-05-31 15:48:20 +02:00
MasterAcnolo
519dcad6e6 fix: rename localMode => devMode for clarity 2026-05-29 07:42:49 +02:00
MasterAcnolo
ac94d68224 fix: rename localMode => devMode for clarity 2026-05-29 07:42:38 +02:00
MasterAcnolo
0f876fcb70 fix: update dependencies 2026-05-29 07:41:54 +02:00
MasterAcnolo
a6c11ba7c4 update: version for 1.6.0 2026-05-15 19:57:33 +02:00
MasterAcnolo
44132dd7b5 fix: update dependencies 2026-05-11 21:14:00 +02:00
MasterAcnolo
15ab6892b7 Fix: remove duplication roadmap (now full in website) 2026-05-01 12:07:39 +02:00
MasterAcnolo
725b6f0ada Fix: Configuration File Location 2026-04-15 13:55:13 +02:00
MasterAcnolo
ad0e4f858b Fix: missing options in readme 2026-04-15 13:50:39 +02:00
MasterAcnolo
68108e3a66 Update Workshop link to GitHub repository 2026-04-14 11:28:53 +02:00
MasterAcnolo
eff2affd42 1.5.0 !!!!!!
IT'S TIME
2026-04-14 11:20:48 +02:00
MasterAcnolo
87cf05643f Feat: Update README with detailed project structure and architecture overview 2026-04-14 11:04:55 +02:00
MasterAcnolo
ce14b8abd7 Fix: Remove emoji from error messages in download and info controllers 2026-04-14 09:31:23 +02:00
MasterAcnolo
44ed1bbf0e Feat: Add link to the most recent roadmap in README 2026-04-14 09:25:59 +02:00
MasterAcnolo
e0d017d9fd Fix: Remove z-index from container styles and add z-index to settings button 2026-04-14 09:22:31 +02:00
MasterAcnolo
c3ea1ac0bd Feat: Add section for submitting community themes in README 2026-04-14 09:22:24 +02:00
MasterAcnolo
238d4ea717 Fix: Save Themes inside "AppData" 2026-04-13 21:49:19 +02:00
MasterAcnolo
d879a5eb1d Feat: Add dynamic icon path for main window based on local mode 2026-04-13 20:57:56 +02:00
MasterAcnolo
40bea35286 Feat: Implement notification feature for download completion 2026-04-13 20:39:23 +02:00
MasterAcnolo
83ac76381a Fix: Update links to Freedom-Loader-Site in documentation and IPC handlers 2026-04-13 20:20:47 +02:00
MasterAcnolo
6ff0c71be7 Feat: Add related repository links for website and workshop in README 2026-04-13 19:48:09 +02:00
MasterAcnolo
fd0b339bcc Fix: Update link to Freedom Loader Workshop in README 2026-04-13 19:15:33 +02:00
MasterAcnolo
bc5faf38b6 Fix: Correct typo in Workshop badge link in README 2026-04-13 19:14:35 +02:00
MasterAcnolo
e744e55475 Feat: Add Theme Workshop section to README and update features list 2026-04-13 19:13:04 +02:00
MasterAcnolo
a581c9309d Fix: Update external links for website and wiki in IPC handlers 2026-04-13 19:13:00 +02:00
MasterAcnolo
61040769ee Feat: Add "Theme Workshop" button and associated IPC handler for workshop access 2026-04-12 21:40:13 +02:00
MasterAcnolo
06f6a30797 Feat: Enhance theme settings with new button styles for open theme and JSON options 2026-04-12 19:32:05 +02:00
MasterAcnolo
a2653eef57 Feat: Implement playlist download count tracking during downloads with UI updates 2026-04-12 18:24:19 +02:00
MasterAcnolo
89573d11f7 Fix: Adjust z-index for reference 2026-04-12 18:17:43 +02:00
MasterAcnolo
f0c28a7f5e Fix: Moving Custom Top BAr Settings into advanced 2026-04-12 18:15:19 +02:00
MasterAcnolo
f3c37c2498 Feat: Add createPlaylistFolders feature to organize downloads into dedicated folders with toggle settings 2026-04-12 18:13:38 +02:00
MasterAcnolo
8fe4e7c38a Fix: Update save path display to include title attribute for better accessibility 2026-04-12 17:40:01 +02:00
MasterAcnolo
912f4042e5 Fix: Enhance error handling for playlist fetch failures by retrying with single video 2026-04-12 17:34:17 +02:00
MasterAcnolo
5dc61b4a15 Fix: Adjust stop button hover scale effect for better user experience 2026-04-12 17:28:13 +02:00
MasterAcnolo
5fb49b0c9d Fix: Update button text 2026-04-12 17:04:20 +02:00
MasterAcnolo
75fdf91fcd Refactor: auto-update functionality with user prompts and download progress 2026-04-12 16:57:39 +02:00
MasterAcnolo
011e0a703a Feat: Enhance download experience with progress indicators and toast notifications 2026-04-12 12:04:05 +02:00
MasterAcnolo
5aebc78d44 Fix: IPC button not working 2026-04-12 11:22:31 +02:00
MasterAcnolo
60137a15dc Feat: Implement download cancellation feature with stop button and progress handling 2026-04-12 11:22:18 +02:00
MasterAcnolo
d05b9e2511 Feat: Add theme reloading functionality with refresh button in settings panel 2026-04-12 09:32:15 +02:00
MasterAcnolo
ebb48f4f8b Fix: Remove themes from the theme order array 2026-04-11 22:38:25 +02:00
MasterAcnolo
8be47b9821 Feat: Implement splash screen with progress indicators during startup 2026-04-11 22:36:42 +02:00
MasterAcnolo
ca8b107f61 Fix: Display custom these with a basic order 2026-04-11 22:16:05 +02:00
MasterAcnolo
f25d07cb83 Fix: Remove empty title tag from index.html and unused app styles from styles.css 2026-04-11 22:15:49 +02:00
MasterAcnolo
49e1e1b507 Feat: Add theme folder access functionality in UI 2026-04-11 18:25:17 +02:00
MasterAcnolo
23d11d8933 Feat: Custom Theme features and clean the old one 2026-04-11 18:10:51 +02:00
MasterAcnolo
9f88fe19c3 Fix: Theme Saving 2026-04-11 16:37:35 +02:00
MasterAcnolo
9b4b0306c7 fix: move config to AppData to prevent settings loss on update, add config.dev.json for dev environment 2026-04-11 16:33:58 +02:00
MasterAcnolo
96401a6e1f Refactor: split main.js into app/ modules 2026-04-11 16:14:21 +02:00
MasterAcnolo
0c7103c5c9 Update: Readme version and file structure 2026-04-11 16:13:36 +02:00
MasterAcnolo
798084e3bf Fix: Remove unusued element 2026-04-11 16:00:51 +02:00
MasterAcnolo
5c4e6669fe Fix: Update theme JSON structure for audio and button settings 2026-04-11 15:18:11 +02:00
MasterAcnolo
00c542925c Refactor: Update CSS variables
Fix Progress bar
2026-04-11 15:17:05 +02:00
MasterAcnolo
24460cf0b5 Refactor: Restructure HTML layout and enhance CSS styles for improved UI 2026-04-11 14:20:05 +02:00
MasterAcnolo
a3c1b1d753 Fix: Settings Secondary Color in LIght THeme 2026-04-11 14:01:39 +02:00
MasterAcnolo
5ade3fe144 Merge pull request #39 from MasterAcnolo/1.5.0
1.4.7
2026-04-09 13:46:20 +02:00
MasterAcnolo
577069b471 Fix: Remove unusued pictures 2026-04-09 13:35:04 +02:00
MasterAcnolo
a4da47647a Update: App Version in ReadME 2026-04-09 13:31:58 +02:00
MasterAcnolo
c5b29a0647 Update: Dependencies 2026-04-08 15:04:56 +02:00
MasterAcnolo
90e1a5fde7 Merge pull request #36 from MasterAcnolo/1.4.6
1.4.6
2026-03-18 10:00:17 +01:00
MasterAcnolo
dcfea2073c Fix: Wrong files Path 2026-03-18 09:20:25 +01:00
MasterAcnolo
0be1ef1710 Update: YT-DLP 2026-03-18 09:13:56 +01:00
MasterAcnolo
da393b19d9 Refactor: Suffixe in helpers/ 2026-03-18 09:13:47 +01:00
MasterAcnolo
1c27cc19cf Fix: Remove unusued modules and disable devTools when packaged 2026-03-16 21:22:41 +01:00
MasterAcnolo
ff7366c46c Fix: Update YT-DLP 2026-03-16 21:22:24 +01:00
MasterAcnolo
459d4c6df4 Fix: Increment app version 2026-03-16 21:15:35 +01:00
MasterAcnolo
d2ca3e99d2 Fix: Dependencies 2026-03-16 21:15:17 +01:00
MasterAcnolo
8ad3ea55cc Docs: Update ReadME 2026-03-16 20:57:38 +01:00
MasterAcnolo
71dbd104f8 Merge pull request #35 from MasterAcnolo/working
1.4.5
2026-02-21 13:00:36 +01:00
MasterAcnolo
57a5defb08 Fix: Update Dependencies 2026-02-21 12:43:36 +01:00
MasterAcnolo
25c78ff8b7 Fix: Increment App Version 2026-02-21 12:40:27 +01:00
MasterAcnolo
792a59d8d7 Feat: You can now fetch image from all website. 2026-02-21 12:38:57 +01:00
MasterAcnolo
b13c98dda2 Refactor: Translate all comms in English 2026-02-21 12:27:13 +01:00
MasterAcnolo
1f103c20b9 Fix: Remove Discord RPC Update. It's useless and take performance for nothing. 2026-02-19 22:23:30 +01:00
MasterAcnolo
70d62959ca Fix: Reduce Line Complexity featuresPath 2026-02-19 22:08:56 +01:00
MasterAcnolo
83ca963502 Merge with false positive
Merge with False positive
2026-02-18 15:10:01 +01:00
MasterAcnolo
9fb9295093 Fix: Try to re run workflow 2026-02-18 14:51:57 +01:00
MasterAcnolo
74fcdbb738 Fix: Undo Path Validation for coming back on a classic one 2026-02-18 14:43:40 +01:00
MasterAcnolo
32c9a66a35 Fix: Centralize default download path and enhance path validation logic 2026-02-17 23:29:31 +01:00
MasterAcnolo
45e0840fea Fix: Correct windowMs value in rate limit configuration to 15 minutes 2026-02-17 23:26:01 +01:00
MasterAcnolo
19b3f8b947 Fix: Refactor conditional statements for starting Discord RPC and Auto Update 2026-02-17 21:49:52 +01:00
MasterAcnolo
cc5bde5b48 Fix: Correct comment for rate limit window duration in rateLimit.js 2026-02-17 21:49:43 +01:00
MasterAcnolo
9a48ac05e3 Fix: Uncomplete comments and remove repeated code 2026-02-17 21:42:12 +01:00
MasterAcnolo
5750444655 Fix: Update full changelog link format in release template 2026-02-17 21:35:57 +01:00
MasterAcnolo
fdb22f2342 Fix: Improve browser detection logging and error handling 2026-02-17 21:29:51 +01:00
MasterAcnolo
760550e521 Fix: Centralize resource paths and icons 2026-02-17 21:28:46 +01:00
MasterAcnolo
334e83ae1f Fix: Small coms in AppVersion.js 2026-02-17 21:12:01 +01:00
MasterAcnolo
5d776594fc Fix: App Not Stopping correctly + Better Logs for end :) 2026-02-17 21:09:09 +01:00
MasterAcnolo
adb36e3fa2 Fix: We are not on apple system. Don't care about Darwin 2026-02-17 20:59:22 +01:00
MasterAcnolo
87945fba80 Fix: Missing semi colon, Better Folder Creation, And remove the fucking bad code who was repeat too much (wtf i did) 2026-02-17 18:05:07 +01:00
MasterAcnolo
2234f9ed81 Fix: Update version numbers to 1.4.4 in package.json and ReadME 2026-02-17 15:22:38 +01:00
MasterAcnolo
7cd3b4be06 Fix: Update playlist badge text to English 2026-02-17 15:19:23 +01:00
MasterAcnolo
a42b020b96 Fix: Update URL check to correctly identify playlist links 2026-02-17 15:19:17 +01:00
MasterAcnolo
c1e70dae80 Feat: Add Better playlist layout 2026-02-17 10:27:13 +01:00
MasterAcnolo
6877c51c0e Fix: Add Small Comms for clarity 2026-02-17 10:26:54 +01:00
MasterAcnolo
5a41090175 Fix: Remove Unusued Code 2026-02-17 10:26:39 +01:00
MasterAcnolo
5983f21759 Fix: Big mistake when estimating video format (? -> &) 2026-02-17 09:50:32 +01:00
MasterAcnolo
ee9e02410f Merge pull request #31 from MasterAcnolo/fix-input
1.4.3
2026-02-16 22:34:12 +01:00
MasterAcnolo
909480083e Fix: Update Dependencies 2026-02-16 22:00:31 +01:00
MasterAcnolo
ca0492ed04 Fix: Update ReadME App Version 2026-02-16 21:37:24 +01:00
MasterAcnolo
6c73dd75be Feat: Add Paste BTN URL Input 2026-02-16 21:34:39 +01:00
MasterAcnolo
556dccec08 Fix: Increment App version 2026-02-16 21:29:24 +01:00
MasterAcnolo
f720d7c889 Merge pull request #30 from MasterAcnolo/fix
1.4.2
2026-02-06 22:55:43 +01:00
MasterAcnolo
bb23f37fb5 Fix: Update release badge to version 1.4.2 2026-02-06 22:38:19 +01:00
MasterAcnolo
220e81d8b2 Fix: 1.4.2 Version 2026-02-06 22:37:05 +01:00
MasterAcnolo
44d8772827 Fix: yt-dlp update 2026-02-06 22:23:31 +01:00
MasterAcnolo
335fb9652b Fix: Dependencies Vulnerability 2026-02-06 22:11:43 +01:00
MasterAcnolo
243b98cbf9 Merge branch 'fix' of https://github.com/MasterAcnolo/Freedom-Loader into fix 2026-02-06 22:01:01 +01:00
MasterAcnolo
23da75a17b 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-02-06 22:00:50 +01:00
MasterAcnolo
756d2c048e Update: YT-DLP 2026-02-05 16:05:33 +01:00
MasterAcnolo
659c1d4cf9 Update acknowledgments in README.md
Added acknowledgment for Deno and Jacques Chirac.
2026-02-03 17:48:59 +01:00
MasterAcnolo
443fd31170 Add: Additional dependencies section in README with required binaries 2026-02-03 14:01:59 +01:00
MasterAcnolo
6d8095ceb2 Mark completed tasks in the roadmap as strikethrough
Updated the roadmap to mark completed tasks as strikethrough.
2026-01-26 10:09:33 +01:00
MasterAcnolo
c18cb6c8aa Fix: ReadME 2026-01-25 21:34:40 +01:00
MasterAcnolo
a3c5025434 Fix: Bug Report Template 2026-01-25 21:31:35 +01:00
MasterAcnolo
3d7986cb87 Fix: Security 2026-01-25 21:28:23 +01:00
MasterAcnolo
3f8701567f Fix: Security, Contributing, Code Of Conduct 2026-01-25 21:27:28 +01:00
MasterAcnolo
ccb16c49d5 Feat: Templates for PR and Issues 2026-01-25 21:22:09 +01:00
MasterAcnolo
86d9ac3592 Update GitHub username for SpicyFire21 2026-01-25 21:08:33 +01:00
MasterAcnolo
43d87c2b47 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-01-25 21:04:38 +01:00
MasterAcnolo
19aa8ed8fa Revise README for improved structure and clarity
Updated README with new structure and content for clarity and organization.
2026-01-25 20:51:18 +01:00
MasterAcnolo
3ddfaa999e Fix: ReadME Version 1.4.1 2026-01-25 18:05:07 +01:00
MasterAcnolo
3d7f3b7481 1.4.1
1.4.1
2026-01-25 17:55:19 +01:00
MasterAcnolo
cfe2e17313 Add: App Version for 1.4.1 2026-01-25 17:45:14 +01:00
MasterAcnolo
dedf10c6b3 Fix: Discord RPC not Stoping #26 2026-01-25 17:32:29 +01:00
MasterAcnolo
ac0b4a0750 1.4.0 - It's time to sleep
1.4.0 - It's time to sleep
2026-01-22 22:09:24 +01:00
MasterAcnolo
29ca2ea496 Fix: Auto Update mispelling (Am a monkey) 2026-01-22 21:39:07 +01:00
MasterAcnolo
2a3905bdfb Add: Video Codec 2026-01-21 20:43:15 +01:00
MasterAcnolo
d3755ab57d Fix: Remove unusued "Settings.svg" icon 2026-01-21 20:25:34 +01:00
MasterAcnolo
9824e107fe Fix: Try to force AAC Audio Codec 2026-01-21 13:29:33 +01:00
MasterAcnolo
e437722937 Merge pull request #27 from MasterAcnolo/sync
Sync
2026-01-21 11:32:19 +01:00
MasterAcnolo
fd8c4ae33c Fix: Check Info config every request 2026-01-21 11:16:53 +01:00
MasterAcnolo
967144bd55 Fix: Config not working propely 2026-01-21 11:08:42 +01:00
MasterAcnolo
cd8eb05020 Fix: Remove Unusued Import 2026-01-21 11:08:20 +01:00
MasterAcnolo
590060e504 Fix: Reference position bottom 2026-01-20 22:34:33 +01:00
MasterAcnolo
26c7d4d2ee Because why not ? 2026-01-20 22:30:41 +01:00
MasterAcnolo
caa987fa42 Fix: Remove unusued files 2026-01-20 22:30:22 +01:00
MasterAcnolo
ccb090e4b2 Add: Settings theme color 2026-01-20 22:29:56 +01:00
MasterAcnolo
8d070ed10d Fix: Neon Theme rename 2026-01-20 22:29:22 +01:00
MasterAcnolo
343dbb26a2 Feat: Settings Panel 2026-01-20 22:28:53 +01:00
MasterAcnolo
fef4f6de36 Sync: Synchro Settings Panel 2026-01-18 14:23:17 +01:00
MasterAcnolo
1a61972750 Add: Settings Icon 2026-01-18 14:11:01 +01:00
MasterAcnolo
45ae5d7f98 Fix: Update Dependencies 2026-01-18 14:09:41 +01:00
MasterAcnolo
fecb123d0e Fix: Indent Index.html 2026-01-18 13:51:17 +01:00
MasterAcnolo
5c62ed081a Fix: Update ReadME Banner Redirection 2026-01-18 11:28:27 +01:00
MasterAcnolo
8f0f31041d Fix: Remove Unusued Dependencies 2026-01-16 09:42:32 +01:00
MasterAcnolo
0e48ab3b4c Fix: Default Theme Info Color 2026-01-16 09:42:09 +01:00
MasterAcnolo
d4c33bcf2d FIx: Updated Appliction Version because i did too much work to be a "minor release" 2026-01-15 22:54:40 +01:00
MasterAcnolo
2f105f5145 Fix: Put config.json file into config folder for better readability + Fix Yt-DLP Path 2026-01-15 22:01:26 +01:00
MasterAcnolo
b7a193b062 Fix: Remove unusued import server.js 2026-01-15 21:23:26 +01:00
MasterAcnolo
65364d3095 Fix: Update Dependencies 2026-01-15 21:22:57 +01:00
MasterAcnolo
c356e2e30d Fix: Using ASAR for every parts of the projects (the server is now in it) and i put the binaries in a /binaries folder. It's better for readability of the resources folder 2026-01-15 21:22:40 +01:00
MasterAcnolo
e45810b54c Add: Better logs. Write logs when a download is init 2026-01-15 16:22:29 +01:00
MasterAcnolo
55db30febc Fix: Remove Useless Console.log 2026-01-15 16:21:43 +01:00
MasterAcnolo
163faec221 Feat: Add Configuration Button. user can edit config.json as wanted. Linked and IPC 2026-01-15 16:01:24 +01:00
MasterAcnolo
5398fed928 Fix: Remove unusued Variable 2026-01-15 10:59:45 +01:00
MasterAcnolo
f959171db5 Feat: Add CustomVideo Codec Choice with verification in config.json 2026-01-15 10:57:37 +01:00
MasterAcnolo
01b6bcd02c Feat: Add Newline YT-DLP Args. Add Toggle Switches for verbose, autodownloadplaylist + Fix Thumbail misspell 2026-01-15 10:00:17 +01:00
MasterAcnolo
0ff4715b35 Fix: Correct Config.json file for packaged version 2026-01-15 09:28:46 +01:00
MasterAcnolo
745a73a5b0 Feat: Add version badge controlled. Before we needed to write with the hand for the front. But now there is an IPC event that provide App Version from package.json 2026-01-15 09:03:50 +01:00
MasterAcnolo
a69114edce Feat: Package Update + Add NSIS Installer Banner 2026-01-14 22:49:34 +01:00
MasterAcnolo
489a4766fa Feat: Path Verification (Security #4) 2026-01-14 21:47:38 +01:00
MasterAcnolo
adcee62386 Fix: ReadME Badge Version 2026-01-14 20:41:55 +01:00
MasterAcnolo
b867d819b7 Fix: Increment App Version for 1.3.2 2026-01-14 14:04:06 +01:00
MasterAcnolo
31d8b20a76 Feat: Implement Config File. Read by Front and Back. It aims to give possibility to disable some features quickly. Currenty: DiscordRPC, AutoCheckInfo, AutoUpdate, Custom TopBar, AddMetadata, Add Thumbail were put and implement. Other need to be implement. That's the begin of Settings Panel 2026-01-14 14:03:32 +01:00
MasterAcnolo
42ae0e7204 Feat: Add Codec H264 to vidéo output 2026-01-14 10:06:17 +01:00
MasterAcnolo
43869420c0 Update: Update Script + Dependencies 2026-01-14 10:04:38 +01:00
MasterAcnolo
6c9d296fbd Add: Some Variables in config.js. For Modules 2025-12-30 11:38:18 +01:00
MasterAcnolo
008428f10a Fix: Remove Unsued Import in Controller 2025-12-28 12:47:00 +01:00
MasterAcnolo
ae7d338474 Add: Rate Limit to front rendering. Using Components for reusability 2025-12-28 12:40:55 +01:00
MasterAcnolo
12ff25cc4b Fix: Error Handling Download Controller 2025-12-27 12:15:28 +01:00
MasterAcnolo
f31767e7e7 Fix: Error Handling Info Controller 2025-12-27 12:14:21 +01:00
MasterAcnolo
7c4f2841e1 Fix: Security #5 2025-12-27 11:41:52 +01:00
MasterAcnolo
a27b3555f6 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>
2025-12-27 11:39:30 +01:00
MasterAcnolo
c451ddd042 Add pull request template for consistent feature submissions 2025-12-20 13:51:55 +01:00
MasterAcnolo
da6311055f Replace feature and version templates in PULL_REQUEST_TEMPLATE directory 2025-12-20 13:46:47 +01:00
MasterAcnolo
945efb2059 Add feature issue template for improved feature requests 2025-12-20 13:46:00 +01:00
MasterAcnolo
ab2881f8e9 Add version template for changelog and roadmap 2025-12-20 13:45:39 +01:00
MasterAcnolo
f23811fff4 Create SECURITY.md 2025-12-20 13:39:50 +01:00
MasterAcnolo
19ab77501b Create CONTRIBUTING.md for project guidelines
Added a comprehensive contributing guide for Freedom Loader, outlining how to report issues, submit pull requests, and improve documentation.
2025-12-20 13:35:21 +01:00
MasterAcnolo
37eeed583a Add Contributor Covenant Code of Conduct
This document outlines the Contributor Covenant Code of Conduct, including our pledge, standards of behavior, enforcement responsibilities, and consequences for violations.
2025-12-20 13:29:21 +01:00
MasterAcnolo
caf08efb90 Update issue templates 2025-12-20 13:25:58 +01:00
MasterAcnolo
7ff998e4df Merge pull request #21 from MasterAcnolo/build
1.3.1
2025-12-20 11:41:36 +01:00
MasterAcnolo
0c1e8fe899 refactor: Remove setupMenu function and its invocation from app initialization 2025-12-20 11:28:04 +01:00
MasterAcnolo
4158e8c6e3 feat: Add version badge 2025-12-20 11:18:54 +01:00
MasterAcnolo
653319e408 style: Update z-index for theme switcher and topbar; add custom scrollbar styles 2025-12-20 10:23:39 +01:00
MasterAcnolo
0a4f14fba7 fix: Update topbar width to use 100% instead of 100vw for better responsiveness 2025-12-20 10:10:42 +01:00
MasterAcnolo
fb4571fc51 Fix: Update release badge to version 1.3.1 in README 2025-12-20 09:54:35 +01:00
MasterAcnolo
c11fd40681 feat: Implement topbar with custom window controls and actions 2025-12-20 09:53:17 +01:00
MasterAcnolo
cbfde8b761 Fix: Startup verif + Update Package 2025-12-20 09:11:25 +01:00
MasterAcnolo
c774821cc4 Refactor badge links in README for improved layout and consistency 2025-12-19 14:21:35 +01:00
MasterAcnolo
2ed15a32bc Refactor dependencies section in README for improved layout and styling 2025-12-19 14:20:39 +01:00
MasterAcnolo
c252853603 Refactor layout of dependencies section in README for improved styling 2025-12-19 14:18:43 +01:00
MasterAcnolo
cfdddec073 Refactor badge links in README for improved readability 2025-12-19 14:18:03 +01:00
MasterAcnolo
97a63ab791 Update version to 1.3.1 and add update script to package.json 2025-12-19 14:15:37 +01:00
MasterAcnolo
a0e00341e1 Add native dependencies check and error handling on startup 2025-12-19 14:11:37 +01:00
MasterAcnolo
26d42491ce Bump version to 1.3.1 and fix Discord RPC state URL; enhance update notification with clickable link to releases 2025-12-19 13:50:17 +01:00
MasterAcnolo
d678ba2f12 Merge Main 2025-12-19 13:47:07 +01:00
MasterAcnolo
55b400eb05 Merge branch 'main' into build 2025-12-19 13:47:06 +01:00
MasterAcnolo
0a6313b1f3 Update Freedom Loader link in README.md 2025-12-16 16:27:51 +01:00
MasterAcnolo
b4312376bb Version 1.3.0
It's Time. Remember The Goal.
2025-11-21 14:10:26 +01:00
MasterAcnolo
5ffb143a1a Size Reduce README 2025-11-21 13:54:16 +01:00
MasterAcnolo
ee24473077 Size Reduce README 2025-11-21 13:53:55 +01:00
MasterAcnolo
2b98a63f92 ReadME Firefox 2025-11-21 13:52:09 +01:00
MasterAcnolo
2e594d87e8 ReadME Firefox 2025-11-21 13:45:29 +01:00
MasterAcnolo
b343c2869d Added Website Top Menu Section 2025-11-21 13:44:32 +01:00
MasterAcnolo
094b93f4fa Update Banner 2025-11-20 21:21:45 +01:00
MasterAcnolo
cdc8a623c0 Fix Wiki Path + Icon + Bring Back Spicy Theme + New banner (Logo) 2025-11-20 21:18:04 +01:00
MasterAcnolo
2b43c00a3a Better RPC 2025-11-20 14:42:52 +01:00
MasterAcnolo
14928798d4 Bring Back Spicy Fire Theme from darkness 2025-11-20 13:53:49 +01:00
MasterAcnolo
e5fb44fc6b ReadME Change Logo 2025-11-20 12:58:08 +01:00
MasterAcnolo
9f9e4cd1d8 ReadME Fix 2025-11-20 12:48:50 +01:00
MasterAcnolo
45fe7336fe New Logo 2025-11-20 12:47:17 +01:00
MasterAcnolo
b909e7d917 main fix 2025-11-20 10:35:50 +01:00
MasterAcnolo
ce62748ade ReadME 2025-11-20 10:28:18 +01:00
MasterAcnolo
d4ecc36f6b ReadME 2025-11-20 10:27:23 +01:00
MasterAcnolo
fee364610e ReadME 2025-11-20 10:26:07 +01:00
MasterAcnolo
492af8525b ReadME 2025-11-20 10:25:19 +01:00
MasterAcnolo
fb58b5ae06 ReadME 2025-11-20 10:24:30 +01:00
MasterAcnolo
577034b0bd English Index.html audio 2025-11-20 10:23:07 +01:00
MasterAcnolo
6fc452edb4 ReadME 2025-11-20 09:55:31 +01:00
MasterAcnolo
086ca49599 ReadME 2025-11-20 09:54:45 +01:00
MasterAcnolo
be1de35da8 ReadME 2025-11-20 09:52:21 +01:00
MasterAcnolo
3c1f57667f ReadME 2025-11-20 09:51:28 +01:00
MasterAcnolo
5997cd20b4 ReadME 2025-11-20 09:51:05 +01:00
MasterAcnolo
6220c9400e ReadME 2025-11-20 09:49:01 +01:00
MasterAcnolo
48a16b927f ReadME 2025-11-20 09:48:21 +01:00
MasterAcnolo
87545d77b4 English 2025-11-20 08:32:38 +01:00
MasterAcnolo
4e0fe29c20 Anglais Fetch Info.js + Removed Files Check 2025-11-19 18:24:50 +01:00
MasterAcnolo
f8e3bc843d Change Language Main.js 2025-11-19 18:09:42 +01:00
MasterAcnolo
b3f1edaabd Better Log Components Path 2025-11-19 16:55:26 +01:00
MasterAcnolo
ef784dd19c Translate English 2025-11-19 16:48:07 +01:00
MasterAcnolo
69bbcee885 NO AID HERE 2025-11-18 21:09:25 +01:00
MasterAcnolo
60b4a99506 NO AID HERE 2025-11-18 18:06:39 +01:00
MasterAcnolo
c1030400f5 Notify Logic 2025-11-18 13:55:56 +01:00
MasterAcnolo
3f508da501 Info Loading Bar 2025-11-17 16:54:11 +01:00
MasterAcnolo
0437cb61a2 Change Error Info Box + Clean HTML 2025-11-17 16:40:05 +01:00
MasterAcnolo
1372582c54 SongBird Theme + Playlist Background For Every Theme 2025-11-17 16:25:32 +01:00
MasterAcnolo
070c3d507f Download Speed 2025-11-17 15:33:25 +01:00
MasterAcnolo
3e9c545952 Clean Code 2025-11-15 23:54:41 +01:00
MasterAcnolo
e3845e1849 Loading Bar taskbar 2025-11-15 23:52:43 +01:00
MasterAcnolo
91703d6a91 Using Download Route MVC Model 2025-11-15 23:34:20 +01:00
MasterAcnolo
cff3101b03 Push 2025-11-15 23:14:05 +01:00
MasterAcnolo
c9a48ec64c Using MVC Model + AutoDeteck Package + Fix Download/Info 2025-11-15 15:06:19 +01:00
MasterAcnolo
e6419a9759 Fix Deno Not Using on Info.js 2025-11-15 01:26:54 +01:00
MasterAcnolo
9d1d2fc7a8 Deno Integré + Progress Bar Fix + Small Clean 2025-11-15 00:50:45 +01:00
MasterAcnolo
cdca4a82ab Removed Cyberpunk | Spicy | Vilbrequin Theme, Info Fix, Fix Progress Bar. Centralized Ressources 2025-11-14 23:41:03 +01:00
MasterAcnolo
c2e4e12512 ProgressBar + Playlist 2025-11-14 18:42:15 +01:00
MasterAcnolo
96c1c6717d Playlist Card + Safe Args 2025-11-14 13:02:55 +01:00
MasterAcnolo
abf776175c UpperCase 2025-11-12 18:23:58 +01:00
MasterAcnolo
d6de184bfd Autoupdate + FreedomKiller Démarrage 2025-11-12 18:00:07 +01:00
MasterAcnolo
12249c4e8a Fix AutoUpdate 2025-11-12 15:58:05 +01:00
MasterAcnolo
f1fbc3f0c7 Version Config Depend PackageJSON 2025-11-12 15:53:06 +01:00
MasterAcnolo
bb8cf54bf9 Auto Update GitHub 2025-11-12 15:33:07 +01:00
MasterAcnolo
dccf8bd786 Change log file path for bug reports
Updated log file path in README for bug reports.
2025-11-12 15:04:01 +01:00
MasterAcnolo
793bb79d76 Fix Logs path readme 2025-11-12 15:03:45 +01:00
MasterAcnolo
f5cdf27ce6 Log Discord RPC 2025-11-12 15:01:10 +01:00
MasterAcnolo
627c56cd25 Discord RPC Fix 2025-11-12 14:59:11 +01:00
MasterAcnolo
783871d131 Documentation Bouton + Package Lock Update 2025-11-12 14:53:49 +01:00
MasterAcnolo
138c05f995 Removed Licence Preload 2025-11-09 15:16:59 +01:00
MasterAcnolo
5009a479af Removed console.log RPC 2025-11-09 15:16:15 +01:00
MasterAcnolo
2a95a10113 Removed Else Statement 2025-11-09 13:21:04 +01:00
MasterAcnolo
9d6065c80a Notify Error + Cookies Gestion 2025-11-09 13:19:54 +01:00
MasterAcnolo
cd8b2bf007 RPC Com 2025-11-09 10:46:33 +01:00
MasterAcnolo
1faf74fbc4 Port Lisible Server.js 2025-11-09 10:46:00 +01:00
MasterAcnolo
09163afbcd YTDLP Path DevMode 2025-11-09 10:44:34 +01:00
MasterAcnolo
405d141055 Fix Double Log Start 2025-11-09 10:40:06 +01:00
MasterAcnolo
599e09fa9f Helpers + Clean Download et Info 2025-11-09 10:35:19 +01:00
MasterAcnolo
4954de5f1a Push avant Clean 2025-11-09 09:04:57 +01:00
MasterAcnolo
ac854ae559 Préparation du Champ de Bataille 2025-11-08 15:11:25 +01:00
MasterAcnolo
9944f05139 ReadME Okay for 1.3 2025-11-08 15:02:05 +01:00
MasterAcnolo
f6558b656a Fix Version Mismatch (1.3.1 -> 1.3.0) 2025-11-08 14:58:29 +01:00
MasterAcnolo
bfbf57ba38 Package UPDATE For 1.3.1 2025-11-08 14:57:45 +01:00
MasterAcnolo
d0af35377c NPM Script Clear 2025-11-08 14:53:39 +01:00
MasterAcnolo
59c263a246 Change Apercu README 2025-11-08 14:12:52 +01:00
MasterAcnolo
42700bd7d8 LatestRelease No Space 2025-11-08 14:09:59 +01:00
MasterAcnolo
5a9b364b4b LatestRelease Space 2025-11-08 14:09:38 +01:00
MasterAcnolo
3619550c74 ReadME Fix 2025-11-08 14:09:04 +01:00
MasterAcnolo
31cc4012ac ReadME Fix 2025-11-08 14:08:42 +01:00
MasterAcnolo
2e329a8584 ReadME Fix 2025-11-08 14:06:57 +01:00
MasterAcnolo
d92dd59f86 ReadME Fix 2025-11-08 14:06:46 +01:00
MasterAcnolo
5bb7ca377d Removed ReadME FR and Rework ReadME 2025-11-08 14:05:15 +01:00
MasterAcnolo
9ee9a3574c Change project license from MIT to GNU License 2025-11-08 10:31:33 +01:00
MasterAcnolo
dc50a44682 Clear NPM Scripts 2025-10-31 18:00:48 +01:00
MasterAcnolo
4be5c14999 Merge pull request #16 from MasterAcnolo/build-branch
1.2.4
2025-09-27 11:15:41 +02:00
MasterAcnolo
b92ffebdb4 YT-DLP Maj Automatique 2025-09-27 11:10:34 +02:00
MasterAcnolo
7b6e48b09e Préparation 1.2.4 2025-09-26 07:50:09 +02:00
MasterAcnolo
368851b4bb Merge pull request #15 from MasterAcnolo/build-branch
1.2.3
2025-09-25 21:51:13 +02:00
MasterAcnolo
fa2da5712d ReadME Update 2025-09-25 21:48:38 +02:00
MasterAcnolo
7df97d70bb YT-DLP Update + Fix Crash 2025-09-25 21:43:14 +02:00
MasterAcnolo
5c5f595829 Fix Spicy Image 2025-09-21 09:57:47 +02:00
MasterAcnolo
5b659e3d8a ffprobe 2025-09-20 18:22:43 +02:00
MasterAcnolo
1a526777cc FFPROBE 2025-09-20 18:22:07 +02:00
MasterAcnolo
b0d99d4c29 Update project purpose in README-FR.md 2025-09-20 17:59:17 +02:00
MasterAcnolo
c7554475a9 Update README.md 2025-09-20 17:58:53 +02:00
MasterAcnolo
cab177e8a7 Merge pull request #14 from MasterAcnolo/build-branch
Build branch
2025-09-20 17:53:59 +02:00
MasterAcnolo
f7244a69ac ReadME Update 2025-09-20 17:42:15 +02:00
MasterAcnolo
c6ed79ed1d Title Window 2025-09-20 17:41:27 +02:00
MasterAcnolo
00e5358bbb Asar 2025-09-20 17:21:38 +02:00
MasterAcnolo
598c35eb54 Asar 2025-09-20 17:00:33 +02:00
MasterAcnolo
00c74ea6c6 Package Update + Notif Fix Issues#13 2025-09-20 16:51:46 +02:00
MasterAcnolo
0c415c9106 Merge pull request #12 from MasterAcnolo/build-branch
1.2.1
2025-09-17 12:29:53 +02:00
MasterAcnolo
b0495d033d FFMPEG Location 2025-09-17 11:10:48 +02:00
MasterAcnolo
d72cc6e0ca Correct Log Folder Name 2025-09-17 10:32:53 +02:00
MasterAcnolo
b59cb9f57f Fix Server.js Path 2025-09-17 10:30:48 +02:00
MasterAcnolo
af743be5b0 GitIgnore Fix 2025-09-13 16:18:17 +02:00
MasterAcnolo
024d039701 Package Updated for 1.2.1 + ReadME Fix 2025-09-13 15:41:19 +02:00
MasterAcnolo
e9a91656bc Package JSON Updated for 1.2.1 2025-08-31 18:09:16 +02:00
MasterAcnolo
3505606b44 ADD Licence 'discordRPC.js' 2025-08-31 18:04:07 +02:00
MasterAcnolo
815abae0a0 YT-DLP Moove 2025-08-31 16:30:54 +02:00
MasterAcnolo
004271241e Merge pull request #11 from MasterAcnolo/1.2
Update 1.2
2025-08-29 13:57:11 +02:00
MasterAcnolo
8295967ff2 ReadME Update 2025-08-29 13:45:52 +02:00
MasterAcnolo
053dca365c Package Update 2025-08-29 13:45:02 +02:00
MasterAcnolo
62110b1a5b Package Update 2025-08-29 13:38:20 +02:00
MasterAcnolo
aae9b2866c Discord RPC 2025-08-29 13:36:36 +02:00
MasterAcnolo
0d8bddbc20 ReadME Update 2025-08-29 12:17:46 +02:00
MasterAcnolo
2a7d61bab7 Inversion ReadME Principal 2025-08-29 12:16:12 +02:00
MasterAcnolo
de71035fa0 NF Theme + Ajustement Other Themes 2025-08-29 10:31:13 +02:00
MasterAcnolo
5368fb9311 Update YT-DLP 2025-08-28 10:37:14 +02:00
MasterAcnolo
3d583a9b6f Retrait '--Restrict Filenames' 2025-08-22 12:23:41 +02:00
MasterAcnolo
94de0a42f1 Merge pull request #6 from MasterAcnolo/1.2
1.1.3
2025-08-21 16:01:34 +02:00
MasterAcnolo
5ba2901273 ReadME Update 2025-08-21 15:50:51 +02:00
MasterAcnolo
f5888b8382 NPM Update 2025-08-21 15:49:09 +02:00
MasterAcnolo
6ece182629 Update YTDLP + New Icon 2025-08-21 11:27:03 +02:00
MasterAcnolo
777257b71d Clarification 2025-08-21 08:45:50 +02:00
MasterAcnolo
df0156d603 Add Metadata / Thumbail
Optimisation + Retries
2025-08-20 14:27:45 +02:00
MasterAcnolo
68cdad06ab Merge pull request #5 from MasterAcnolo/Notification-System
# Freedom Loader - 1.1.2

## Changelog

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

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

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

**Full Changelog**: https://github.com/MasterAcnolo/Freedom-Loader/compare/1.1.1...1.1.2
2025-08-13 15:28:57 +02:00
MasterAcnolo
ee7c6520b9 ReadME Fix 2025-08-13 15:22:51 +02:00
MasterAcnolo
4ed1609e41 Logs Fix + Logs Button 2025-08-13 15:20:35 +02:00
MasterAcnolo
b64e61d45d Fix Download 2025-08-13 14:56:14 +02:00
MasterAcnolo
b4c2c865d8 Logger Error Fix 2025-08-13 14:34:00 +02:00
MasterAcnolo
e32ffd8fa0 NPM Update 2025-08-13 14:25:33 +02:00
MasterAcnolo
8be6a932e5 NPM Update 2025-08-13 14:22:51 +02:00
MasterAcnolo
6ed59fd272 Merge pull request #4 from MasterAcnolo/Notification-System
Notification system + YT DLP Update
2025-07-20 18:22:10 +02:00
MasterAcnolo
4df1cb207f ReadME Update 2025-07-20 18:17:19 +02:00
MasterAcnolo
d03e3bc7e5 Notif + Update YT DLP 2025-07-20 15:27:40 +02:00
MasterAcnolo
df02acb97f Notification + Open On CLICK 2025-07-03 18:28:56 +02:00
MasterAcnolo
6ef04700ab NPM Update 2025-07-03 15:39:03 +02:00
MasterAcnolo
766694f330 ReadME Fix 2025-07-02 22:38:38 +02:00
MasterAcnolo
93380b2f36 ReadME Fix 2025-07-02 22:38:15 +02:00
MasterAcnolo
8c969d694f ReadME Fix 2025-07-02 22:37:30 +02:00
MasterAcnolo
f2f3d42e8c ReadME Fix 2025-07-02 22:36:53 +02:00
MasterAcnolo
8d8973acfc ReadME Fix 2025-07-02 22:35:59 +02:00
MasterAcnolo
281bff2379 Merge pull request #3 from MasterAcnolo/1.0.2
1.1.1
2025-07-02 22:33:09 +02:00
MasterAcnolo
e30f54050f ReadME FIX 2025-07-02 22:28:30 +02:00
MasterAcnolo
0ae808c1a0 Merge branch '1.0.2' of https://github.com/MasterAcnolo/MP3-Downloader into 1.0.2 2025-07-02 22:27:46 +02:00
MasterAcnolo
2598573844 Package Fix 2025-07-02 22:27:44 +02:00
MasterAcnolo
27782376a3 Merge pull request #2 from MasterAcnolo/1.0.2
1.1
2025-07-02 22:26:28 +02:00
MasterAcnolo
876f7e1db0 Merge branch 'main' into 1.0.2 2025-07-02 22:26:14 +02:00
MasterAcnolo
db654c5f82 1.1 2025-07-02 22:24:46 +02:00
MasterAcnolo
751f3d5c3e Commentary + Custom PATH 2025-07-02 16:50:52 +02:00
MasterAcnolo
2a8d307bc8 Chirac Easter EGG 2025-07-02 16:17:09 +02:00
MasterAcnolo
ec6963cff3 Logs FIX + Bug FIX 2025-07-02 13:49:55 +02:00
MasterAcnolo
196a07977a Package FIX for 1.0.2 2025-07-02 10:50:10 +02:00
MasterAcnolo
f20366f0c7 Logs Fix 2025-07-01 20:34:22 +02:00
MasterAcnolo
f54dc18e85 ReadME Pictures Fix 2025-07-01 20:13:12 +02:00
MasterAcnolo
251049c7fe Undo last Commit 2025-07-01 15:37:13 +02:00
MasterAcnolo
5d347967b1 ReadMe Fix 2025-07-01 15:36:33 +02:00
MasterAcnolo
d5c506c4e0 ReadME Fix %20 2025-07-01 15:35:47 +02:00
MasterAcnolo
6718f4133e ReadME Fix %20 2025-07-01 15:35:24 +02:00
MasterAcnolo
9ff6678730 ReadME Fix 2025-07-01 15:34:36 +02:00
MasterAcnolo
3957450fdb ReadME Fix 2025-07-01 15:34:06 +02:00
MasterAcnolo
25e50a6e7a ReadME Fix 2025-07-01 15:33:31 +02:00
MasterAcnolo
22f935f678 ReadME Fix 2025-07-01 15:32:04 +02:00
MasterAcnolo
58b843630b ReadME Fix 2025-07-01 15:30:54 +02:00
MasterAcnolo
dec3f3272a ReadME Fix 2025-07-01 15:30:23 +02:00
MasterAcnolo
f3252c2548 ReadME Fix 2025-07-01 15:29:18 +02:00
MasterAcnolo
ac28afb17a ReadME Fix 2025-07-01 15:27:44 +02:00
MasterAcnolo
b1a42f44fa ReadME Fix 2025-07-01 15:26:21 +02:00
MasterAcnolo
78eea2fa0f ReadME Fix 2025-07-01 15:25:21 +02:00
MasterAcnolo
4290e6db52 Merge pull request #1 from MasterAcnolo/Développement-Branch
[PR] 1.0.1
2025-07-01 15:21:11 +02:00
MasterAcnolo
0ffc1af218 1.0.1 Commit 2025-07-01 15:16:17 +02:00
MasterAcnolo
10cefdba19 CSS Variable Begin 2025-07-01 13:51:42 +02:00
MasterAcnolo
c888946588 Logs Name Ajust + 1.0.1 Prepa 2025-06-30 13:49:02 +02:00
MasterAcnolo
9026c63979 Clarification 2025-06-29 15:48:06 +02:00
MasterAcnolo
cfc2c5bbaa Update README-EN.md 2025-06-29 14:13:24 +02:00
MasterAcnolo
a41da5c4c2 Update README.md 2025-06-29 14:12:40 +02:00
MasterAcnolo
b4242e4295 Correction Git Ignore 2025-06-29 14:10:58 +02:00
MasterAcnolo
1f0650a71c MD En + FR 2025-06-29 13:59:33 +02:00
MasterAcnolo
87fe8af6f5 Push 2025-06-29 13:57:00 +02:00
MasterAcnolo
592d3a5ae4 Push 2025-06-29 13:56:10 +02:00
MasterAcnolo
dc847a5a8c Correction 2025-06-29 13:55:40 +02:00
MasterAcnolo
250e6c3f23 Correction Path 2025-06-29 13:54:46 +02:00
MasterAcnolo
1ffe9dcea0 Push ReadME + License 2025-06-29 13:52:46 +02:00
MasterAcnolo
b40a4fc033 MD Push 2025-06-29 13:00:15 +02:00
MasterAcnolo
0900e1b485 Correction Path YT DLP 2025-06-29 12:09:37 +02:00
MasterAcnolo
432633af52 Fix NPM RUN DIST 2025-06-29 11:33:14 +02:00
MasterAcnolo
152ee39535 Working on my machine 2025-06-29 11:27:52 +02:00
MasterAcnolo
9d824f5c33 It Works on my machine 2025-06-28 22:36:03 +02:00
MasterAcnolo
accb55e538 Push 2025-06-28 21:25:57 +02:00
MasterAcnolo
bfb3e0cbdf Push avant l'enfer (PRIDEFUl - NF) 2025-06-28 21:19:48 +02:00
MasterAcnolo
b9460dbceb Aled 2025-06-28 20:54:37 +02:00
MasterAcnolo
1216e5f10d Push Version 1 2025-06-28 18:33:16 +02:00
MasterAcnolo
8b4b3c84e0 Push avant modif DIV INFO 2025-06-28 17:56:36 +02:00
MasterAcnolo
f7abca90cb LE POULET 2025-06-28 09:49:36 +02:00
MasterAcnolo
83b31cb645 CSS Beginning 2025-06-27 23:21:50 +02:00
MasterAcnolo
bb967834f0 Loading Screen 2025-06-27 23:11:42 +02:00
MasterAcnolo
e9f4b80b7b Fix Bug Download 2025-06-27 22:57:43 +02:00
MasterAcnolo
abd5295395 Diviser pour mieux régner 2025-06-27 22:48:24 +02:00
MasterAcnolo
d4d36c39ca Informations Vidéo 2025-06-27 15:42:46 +02:00
MasterAcnolo
95701bf56e Working V1 2025-06-27 15:02:35 +02:00
MasterAcnolo
7b93413050 Working V1 2025-06-27 09:47:11 +02:00
MasterAcnolo
c2c69935b1 Push 2025-06-26 20:40:55 +02:00
MasterAcnolo
07485f39ba OUII 2025-06-26 16:19:26 +02:00
MasterAcnolo
0c99e48950 CA MARCHE 2025-06-26 14:27:54 +02:00
51 changed files with 2084 additions and 6728 deletions

29
.gitignore vendored
View File

@@ -1,26 +1,15 @@
# Dependencies
/node_modules /node_modules
/downloads
# Build Output Folder
/dist /dist
/out /out
/srpm-out
# Config JSON File. Create it and fill it if you want to have dev only settings /ressources/ffmpeg.exe
/ressources/ffprobe.exe
/ressources/deno.exe
/theme/**
config/config.dev.json config/config.dev.json
# IDE logs/*.json
/.idea logs/*.log
/.vscode
# Linux executable
ffmpeg
ffprobe
deno
yt-dlp
# Windows Executable
ffmpeg.exe
ffprobe.exe
deno.exe
yt-dlp.exe

View File

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

View File

@@ -105,7 +105,7 @@ Freedom-Loader/
│ ├── controller/ # Business logic │ ├── controller/ # Business logic
│ └── helpers/ # Utility functions │ └── helpers/ # Utility functions
├── public/ # Frontend (HTML, CSS, JS) ├── public/ # Frontend (HTML, CSS, JS)
└── resources/ # Binaries (yt-dlp, ffmpeg, etc.) └── ressources/ # Binaries (yt-dlp, ffmpeg, etc.)
``` ```
--- ---

174
README.md
View File

@@ -5,9 +5,9 @@
alt="Banner" alt="Banner"
style="width: 50%;"/></a> style="width: 50%;"/></a>
### **A clean, open-source multimedia downloader for Windows and Linux** ### **A clean, open-source multimedia downloader for Windows**
[![Release](https://img.shields.io/github/v/release/MasterAcnolo/Freedom-Loader?style=for-the-badge&color=blue)](https://github.com/MasterAcnolo/Freedom-Loader/releases) [![Release](https://img.shields.io/badge/Release-1.5.1-blue?style=for-the-badge)](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) [![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/) [![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/) [![Workshop](https://img.shields.io/badge/Worshop-Visit-0E05A1?style=for-the-badge)](https://masteracnolo.github.io/Freedom-Loader-Workshop/)
@@ -61,9 +61,9 @@ The primary goal is to make media downloading accessible to users who want offli
### User Interface ### User Interface
- **Custom window controls** - Frameless window with custom top bar (optional) - **Custom window controls** - Frameless window with custom top bar (optional)
- **Custom themes** - ZIP-based theme system with live preview - **Custom themes** — ZIP-based theme system with live preview
- **Theme Workshop** - web-based theme creator and browser - **Theme Workshop** — web-based theme creator and browser
- **Toast notifications** - in-app feedback system - **Toast notifications** — in-app feedback system
- **Real-time progress tracking** - Live download progress bar with network speed indicator - **Real-time progress tracking** - Live download progress bar with network speed indicator
- **Server-Sent Events (SSE)** - Non-blocking progress updates via event streaming - **Server-Sent Events (SSE)** - Non-blocking progress updates via event streaming
- **Settings panel** - In-app configuration interface with live updates - **Settings panel** - In-app configuration interface with live updates
@@ -80,90 +80,41 @@ The primary goal is to make media downloading accessible to users who want offli
- **Rate limiting** - Express-based request throttling to prevent server overload - **Rate limiting** - Express-based request throttling to prevent server overload
- **Graceful shutdown** - Proper cleanup of servers, logs, and RPC connections - **Graceful shutdown** - Proper cleanup of servers, logs, and RPC connections
- **System notifications** - Windows notifications on download completion (clickable to open folder) - **System notifications** - Windows notifications on download completion (clickable to open folder)
- **Splash screen** - animated loading screen on startup - **Splash screen** — animated loading screen on startup
- **Stop download** - ability to cancel in-progress downloads - **Stop download** — ability to cancel in-progress downloads
- **Playlist folders** - dedicated folder creation per playlist - **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 ## 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 ### Requirements
- **Windows**: Windows 10 or later - **Operating System**: Windows 10 or later
- **Linux**: any modern x86_64 distribution - **Browser**: Mozilla Firefox (required for cookie extraction)
- **Browser**: Mozilla Firefox ([download](https://www.firefox.com)) required for cookie extraction, on both platforms - [Download Firefox](https://www.firefox.com)
### Windows ### Standard Installation
1. Download the latest `.exe` from the [Releases page](https://github.com/MasterAcnolo/Freedom-Loader/releases) 1. Download the latest release 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) 2. Run the downloaded `.exe` installer
3. Follow the installation wizard 3. If Windows Defender shows a warning, click "More info" then "Run anyway"
4. 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 ### File Locations
| | Windows | Linux | Downloaded files are stored in:
|---------------|---------------------------------------------------------|--------------------------------------| ```
| **Downloads** | `C:\Users\[USERNAME]\Downloads\Freedom Loader` | `~/Downloads/Freedom Loader` | C:\Users\[USERNAME]\Downloads\Freedom Loader
| **Logs** | `C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\` | `~/.local/share/FreedomLoader/logs/` | ```
> [!NOTE] > [!NOTE]
> The download folder can be changed anytime via the "Edit" button in the UI, on both platforms. > *You can change it whenever you want. Just click the "Edit" button in the UI.*
Application logs can be found at:
```
C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\
```
## Usage ## Usage
> [!TIP]
> On Linux, you can start Freedom Loader using `freedom-loader` command
### Basic Download ### Basic Download
1. Launch Freedom Loader 1. Launch Freedom Loader
@@ -180,15 +131,14 @@ The application supports various configuration options:
- Custom download directory selection - Custom download directory selection
- Discord Rich Presence activation - Discord Rich Presence activation
- Theme customization - Theme customization
- Log level - Log level configuration
- And somes to see in the list [Configuration Options](#configuration-options) available
### Troubleshooting ### Troubleshooting
If you encounter issues: If you encounter issues:
1. Restart the application 1. Restart the application
2. Check the logs in `AppData\Local\FreedomLoader\logs\` or `~/.local/share/FreedomLoader/logs` 2. Check the logs in `AppData\Local\FreedomLoader\logs\`
3. Open the developer console: `CTRL + SHIFT + I` → "Toggle Developer Tools" 3. Open the developer console: `CTRL + SHIFT + I` → "Toggle Developer Tools"
4. Create an issue on GitHub with logs and reproduction steps 4. Create an issue on GitHub with logs and reproduction steps
@@ -214,22 +164,14 @@ If you encounter issues:
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. 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 ### Configuration File Location
#### Windows
``` ```
%APPDATA%\Freedom Loader\config.json %APPDATA%\Freedom Loader\config.json
``` ```
#### Linux
```
~/.config/Freedom\ Loader/config.json
```
### Available Options ### Available Options
```json ```json
{ {
"autoUpdate": true, "autoUpdate": true,
"systemTray": true,
"discordRPC": true, "discordRPC": true,
"customTopBar": true, "customTopBar": true,
"autoCheckInfo": true, "autoCheckInfo": true,
@@ -237,14 +179,12 @@ Freedom Loader can be configured either through the settings panel in the UI or
"addMetadata": true, "addMetadata": true,
"verboseLogs": false, "verboseLogs": false,
"autoDownloadPlaylist": true, "autoDownloadPlaylist": true,
"keepPlaylistOrder": false,
"createPlaylistFolders": true, "createPlaylistFolders": true,
"customCodec": "h264", "customCodec": "h264",
"logSystem": true, "logSystem": true,
"outputTitleCheck": true, "outputTitleCheck": true,
"downloadSystem": true, "downloadSystem": true,
"notifySystem": true, "notifySystem": true,
"enableHardwareAcceleration": true,
"theme": "dark" "theme": "dark"
} }
``` ```
@@ -252,9 +192,8 @@ Freedom Loader can be configured either through the settings panel in the UI or
### Configuration Options ### Configuration Options
| Option | Type | Default | Description | | Option | Type | Default | Description |
|------------------------------|---------|----------|------------------------------------------------------------| |--------|------|---------|-------------|
| `autoUpdate` | boolean | `true` | Enable automatic application updates | | `autoUpdate` | boolean | `true` | Enable automatic application updates |
| `systemTray` | boolean | `true` | Allow app to minimize on close [EXPERIMENTAL] |
| `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration | | `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration |
| `customTopBar` | boolean | `true` | Use custom application top bar | | `customTopBar` | boolean | `true` | Use custom application top bar |
| `autoCheckInfo` | boolean | `true` | Automatically fetch video information on URL paste | | `autoCheckInfo` | boolean | `true` | Automatically fetch video information on URL paste |
@@ -262,14 +201,13 @@ Freedom Loader can be configured either through the settings panel in the UI or
| `addMetadata` | boolean | `true` | Add metadata tags to downloaded files | | `addMetadata` | boolean | `true` | Add metadata tags to downloaded files |
| `verboseLogs` | boolean | `false` | Enable detailed logging for debugging | | `verboseLogs` | boolean | `false` | Enable detailed logging for debugging |
| `autoDownloadPlaylist` | boolean | `true` | Automatically download entire playlists | | `autoDownloadPlaylist` | boolean | `true` | Automatically download entire playlists |
| `keepPlaylistOrder` | boolean | `false` | Add index before title, usefull for album |
| `createPlaylistFolders` | boolean | `true` | Automatically create a folder for a playlist | | `createPlaylistFolders` | boolean | `true` | Automatically create a folder for a playlist |
| `customCodec` | string | `"h264"` | Video codec for encoding (supported: h264, h265, vp9, av1) | | `customCodec` | string | `"h264"` | Video codec for encoding (supported: h264, h265, vp9, av1) |
| `logSystem` | boolean | `true` | Enable application logging | | `logSystem` | boolean | `true` | Enable application logging |
| `outputTitleCheck` | boolean | `true` | Verify output file titles | | `outputTitleCheck` | boolean | `true` | Verify output file titles |
| `downloadSystem` | boolean | `true` | Enable download system | | `downloadSystem` | boolean | `true` | Enable download system |
| `notifySystem` | boolean | `true` | Enable system notifications | | `notifySystem` | boolean | `true` | Enable system notifications |
| `enableHardwareAcceleration` | boolean | `true` | Enable Hardware Acceleration | | `enableHardwareAcceleration` | boolean | `true` | Enable HArdware Acceleration |
| `theme` | string | `dark` | Current Theme Name | | `theme` | string | `dark` | Current Theme Name |
### Supported Codecs ### Supported Codecs
@@ -283,7 +221,7 @@ Freedom Loader can be configured either through the settings panel in the UI or
- `theora` - Theora (legacy open codec) - `theora` - Theora (legacy open codec)
> [!NOTE] > [!NOTE]
> Some configuration changes may require an application restart to take effect. > Configuration changes may require an application restart to take effect.
## Project Structure ## Project Structure
@@ -297,21 +235,15 @@ Freedom-Loader/
│ ├── pathValidator.js │ ├── pathValidator.js
│ ├── splashManager.js │ ├── splashManager.js
│ ├── themeManager.js │ ├── themeManager.js
│ ├── tray.js
│ ├── windowManager.js │ ├── windowManager.js
│ └── ytDlpUpdater.js │ └── ytDlpUpdater.js
├── build/ # Build resources and assets ├── build/ # Build resources and assets
│ ├── app-icon.ico
│ ├── app-icon.png
│ ├── banner.png
│ └── ...
├── config/ # Configuration files ├── config/ # Configuration files
│ ├── config.default.json │ ├── config.default.json
│ └── config.dev.json # Configuration Used when i dev mode │ └── config.dev.json
├── dist/ # Compiled executables (.exe, .AppImage, .deb, .rpm, .snap)
├── public/ # Frontend assets and UI ├── public/ # Frontend assets and UI
│ ├── index.html │ ├── index.html
│ ├── splash.html # Splash Screen (Start of the application) │ ├── splash.html
│ ├── assets/ │ ├── assets/
│ │ ├── icon/ # Application icons │ │ ├── icon/ # Application icons
│ │ └── logo/ # Logo assets │ │ └── logo/ # Logo assets
@@ -330,8 +262,19 @@ Freedom-Loader/
│ ├── styles.css │ ├── styles.css
│ ├── variables.css │ ├── variables.css
│ ├── components/ # Component-specific styles │ ├── components/ # Component-specific styles
│ │ ├── checkbox.css
│ │ ├── editpathbutton.css
│ │ ├── loader.css
│ │ ├── progressBar.css
│ │ └── toast.css
│ └── layout/ # Layout styles │ └── layout/ # Layout styles
├── resources/ # Internal resources (binaries : yt-dlp, ffmpeg...) │ ├── container.css
│ ├── form.css
│ ├── header.css
│ ├── settingsPanel.css
│ ├── topbar.css
│ └── videoinfo.css
├── ressources/ # Internal resources (binaries)
├── server/ # Express backend server ├── server/ # Express backend server
│ ├── logger.js │ ├── logger.js
│ ├── server.js │ ├── server.js
@@ -355,16 +298,16 @@ Freedom-Loader/
├── theme/ # Theme system ├── theme/ # Theme system
│ ├── template.theme.json # Theme template │ ├── template.theme.json # Theme template
│ ├── Dark/ # Default dark theme │ ├── Dark/ # Default dark theme
│ │ └── dark.theme.json
│ └── Light/ # Default light theme │ └── Light/ # Default light theme
├── BINARIES.md # External dependencies & binaries documentation │ └── light.theme.json
├── CODE_OF_CONDUCT.md # Community guidelines
├── config.js # Global configuration loader
├── CONTRIBUTING.md # Contribution guidelines
├── jest.config.js # Unit testing configuration
├── LICENSE # GPLv3 license
├── main.js # Electron main process entry point ├── main.js # Electron main process entry point
├── package.json # Project metadata and dependencies
├── preload.js # Electron preload script (IPC bridge) ├── preload.js # Electron preload script (IPC bridge)
├── config.js # Global configuration loader
├── package.json # Project metadata and dependencies
├── CODE_OF_CONDUCT.md # Community guidelines
├── CONTRIBUTING.md # Contribution guidelines
├── LICENSE # GPLv3 license
└── README.md # This file └── README.md # This file
``` ```
@@ -391,7 +334,7 @@ Freedom Loader includes a web-based theme creator available at [Freedom Loader W
### Installing a custom theme ### Installing a custom theme
1. Download a `.zip` theme file 1. Download a `.zip` theme file
2. Drop it in the `theme/` folder of your Freedom Loader installation 2. Drop it in the `theme/` folder of your Freedom Loader installation
3. Restart the app - your theme appears in the settings panel 3. Restart the app — your theme appears in the settings panel
## Technology Stack ## Technology Stack
@@ -442,7 +385,7 @@ npm run build
This project relies on several external binaries. This project relies on several external binaries.
Some of them are **not included** in the public repository and must be added manually. 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. You must download the required binaries and place them in the `ressources` folder, using the exact filenames listed below.
#### Required binaries #### Required binaries
@@ -463,7 +406,7 @@ You must download the required binaries and place them in the `resources` folder
Final folder structure: Final folder structure:
``` ```
resources/ ressources/
├── deno.exe ├── deno.exe
├── ffmpeg.exe ├── ffmpeg.exe
├── ffprobe.exe ├── ffprobe.exe
@@ -479,7 +422,7 @@ resources/
- Write clear commit messages - Write clear commit messages
- Test thoroughly before submitting changes - Test thoroughly before submitting changes
- Update documentation when adding features - Update documentation when adding features
- Maintain compatibility with Windows 10+ and Linux - Maintain compatibility with Windows 10+
## Roadmap ## Roadmap
@@ -501,7 +444,7 @@ Use the GitHub Issues system and include:
- Clear description of the issue - Clear description of the issue
- Steps to reproduce - Steps to reproduce
- Expected vs actual behavior - Expected vs actual behavior
- Relevant logs from `AppData\Local\FreedomLoader\logs\` or `~/.local/share/FreedomLoader/logs/` - Relevant logs from `AppData\Local\FreedomLoader\logs\`
- Screenshots if applicable - Screenshots if applicable
### Feature Requests ### Feature Requests
@@ -541,11 +484,10 @@ You are free to use, modify, and redistribute this software under the terms of t
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) for the robust download engine - [yt-dlp](https://github.com/yt-dlp/yt-dlp) for the robust download engine
- [FFmpeg](https://ffmpeg.org/) for media processing capabilities - [FFmpeg](https://ffmpeg.org/) for media processing capabilities
- [Deno](https://deno.com/) for helping me to bypass challenges - [Deno](https://deno.com/) for helping me bypassing challenges
- [Electron](https://www.electronjs.org/) for the desktop application framework - [Electron](https://www.electronjs.org/) for the desktop application framework
- The open-source community for continuous support and contributions - The open-source community for continuous support and contributions
- [@SpicyFire21](https://github.com/SpicyFire21) to be the spiciest one - [@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 - Zakaria for the website icon
- Jacques Chirac to love Apples - Jacques Chirac to love Apples
- All users who test, report issues, and help improve the application - All users who test, report issues, and help improve the application

View File

@@ -2,14 +2,15 @@
## Supported Versions ## Supported Versions
Freedom Loader follows a **latest-only** support model. Security fixes are applied to the current release only — no backports to older versions. We actively maintain and provide security updates for the following versions of Freedom Loader:
| Version | Supported | | Version | Supported |
| ------------- | ------------------ | | ------- | ------------------ |
| Latest stable | :white_check_mark: | | 1.4.x | :white_check_mark: |
| Older versions | :x: | | 1.3.x | :white_check_mark: |
| < 1.3.0 | :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. **Note**: We recommend always using the latest version to benefit from the most recent security patches and features.
## Reporting a Vulnerability ## Reporting a Vulnerability
@@ -31,40 +32,37 @@ Instead, please report security issues privately by:
### What to Expect ### What to Expect
- **Acknowledgment**: I will acknowledge receipt of your report within 48 hours - **Acknowledgment**: We will acknowledge receipt of your report within 48 hours
- **Assessment**: I will assess the vulnerability and determine its severity - **Assessment**: We will assess the vulnerability and determine its severity
- **Updates**: I will keep you informed of our progress - **Updates**: We will keep you informed of our progress
- **Resolution**: I aim to release a fix within 7-14 days for critical vulnerabilities - **Resolution**: We 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) - **Credit**: We will credit you in the release notes (unless you prefer to remain anonymous)
## Security Best Practices for Users ## Security Best Practices for Users
When using Freedom Loader, I recommend: When using Freedom Loader, we recommend:
### General Security ### General Security
- **Keep Updated**: Always use the latest version of Freedom Loader — older versions receive no security patches - **Keep Updated**: Always use the latest version of Freedom Loader
- **Official Sources**: Download only from official sources: - **Official Sources**: Download only from official releases on [GitHub](https://github.com/MasterAcnolo/Freedom-Loader/releases)
- [GitHub Releases](https://github.com/MasterAcnolo/Freedom-Loader/releases) - **Verify Downloads**: Check that installers are properly signed (Windows SmartScreen may show warnings for new 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 - **Antivirus**: Keep your antivirus software up to date
### Configuration Security ### Configuration Security
- **Download Path**: Only set download paths within your user directory - **Download Path**: Only set download paths within your user directory (`C:\Users\[USERNAME]\...`)
- **Cookies**: Be aware that Firefox cookies are used for authentication — keep Firefox secure - **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 - **Logs**: Logs may contain sensitive information—avoid sharing them publicly without review
### Privacy Considerations ### Privacy Considerations
Freedom Loader respects your privacy: Freedom Loader respects your privacy:
- **No Data Collection**: I don't collect, store, or transmit your personal data - **No Data Collection**: We don't collect, store, or transmit your personal data
- **No Telemetry**: No usage tracking or analytics - **No Telemetry**: No usage tracking or analytics
- **Local Operation**: All downloads are processed locally on your machine - **Local Operation**: All downloads are processed locally on your machine
- **Optional Features**: Discord RPC is optional and can be disabled in settings - **Optional Features**: Discord RPC is optional and can be disabled
## Known Security Considerations ## Known Security Considerations
@@ -73,16 +71,16 @@ Freedom Loader respects your privacy:
Freedom Loader accesses Firefox cookies to download protected content. This is: Freedom Loader accesses Firefox cookies to download protected content. This is:
- **By Design**: Required for age-restricted or member-only content - **By Design**: Required for age-restricted or member-only content
- **Local Only**: Cookies are read locally and never transmitted outside your machine - **Local Only**: Cookies are read locally and never transmitted
- **User Controlled**: You control what content you download - **User Controlled**: You can control what content you download
### Native Dependencies ### Native Dependencies
Freedom Loader bundles the following official binaries: Freedom Loader bundles native binaries:
- **yt-dlp**: Official builds from [yt-dlp/yt-dlp](https://github.com/yt-dlp/yt-dlp) — auto-updated on each launch - **yt-dlp**: Official builds from [yt-dlp/yt-dlp](https://github.com/yt-dlp/yt-dlp)
- **FFmpeg**: Official builds from [FFmpeg.org](https://ffmpeg.org/) - **FFmpeg**: Official builds from [FFmpeg.org](https://ffmpeg.org/)
- **Deno**: Official builds from [Deno.com](https://deno.com/) - **Deno**: Official builds from [Deno.land](https://deno.land/)
These dependencies are verified and updated regularly. These dependencies are verified and updated regularly.
@@ -93,13 +91,13 @@ New releases may trigger Windows Defender warnings because:
- The application is not yet widely distributed - The application is not yet widely distributed
- Code signing certificates are expensive for open-source projects - 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. This is expected behavior for new releases. The warning will decrease as more users download the software.
## Vulnerability Disclosure Policy ## Vulnerability Disclosure Policy
### Our Commitment ### Our Commitment
I am committed to: We are committed to:
- Responding promptly to security reports - Responding promptly to security reports
- Working with security researchers to verify and address issues - Working with security researchers to verify and address issues
@@ -109,9 +107,9 @@ I am committed to:
### Disclosure Timeline ### Disclosure Timeline
1. **Day 0**: Vulnerability reported privately 1. **Day 0**: Vulnerability reported privately
2. **Day 1–2**: Acknowledgment sent to reporter 2. **Day 1-2**: Acknowledgment sent to reporter
3. **Day 3–7**: Vulnerability assessed and fix developed 3. **Day 3-7**: Vulnerability assessed and fix developed
4. **Day 7–14**: Fix released (critical vulnerabilities prioritized) 4. **Day 7-14**: Fix released (critical vulnerabilities prioritized)
5. **Day 14+**: Public disclosure (coordinated with reporter) 5. **Day 14+**: Public disclosure (coordinated with reporter)
### Scope ### Scope
@@ -131,16 +129,16 @@ The following are generally not considered security vulnerabilities:
- Issues requiring physical access to the user's machine - Issues requiring physical access to the user's machine
- Social engineering attacks - Social engineering attacks
- Denial of service against third-party services - Denial of service against third-party services
- Issues in third-party dependencies (report to upstream projects directly) - Issues in third-party dependencies (report to upstream projects)
- Missing security headers on the local HTTP server (not exposed remotely) - Missing security headers on local HTTP server (no remote access)
## Security Updates ## Security Updates
Security updates are released as: Security updates are released as:
- **Patch Releases**: For critical security fixes (e.g., 1.6.0 → 1.6.1) - **Patch Releases**: For critical security fixes (e.g., 1.4.1 → 1.4.2)
- **Automatic Updates**: Users are notified via the built-in update system - **Automatic Updates**: Users are notified via the built-in update system
- **Release Notes**: Security fixes are clearly marked in the changelog - **Release Notes**: Security fixes are clearly marked in changelog
## Additional Resources ## Additional Resources
@@ -152,7 +150,6 @@ Security updates are released as:
For security-related questions or concerns: For security-related questions or concerns:
- **Email**: masteracnolo25@gmail.com
- **GitHub**: [@MasterAcnolo](https://github.com/MasterAcnolo) - **GitHub**: [@MasterAcnolo](https://github.com/MasterAcnolo)
--- ---

View File

@@ -15,7 +15,7 @@ const { logger } = require("../server/logger");
/** /**
* Configures autoUpdater behavior: * Configures autoUpdater behavior:
* - disables automatic download * - disables automatic download
* - disables automatic installation on quit * - disables automatic install on quit
* (manual user-controlled update flow) * (manual user-controlled update flow)
*/ */
autoUpdater.autoDownload = false; autoUpdater.autoDownload = false;
@@ -51,7 +51,7 @@ function initAutoUpdater(mainWindow) {
}); });
if (response === 0) { if (response === 0) {
await autoUpdater.downloadUpdate(); autoUpdater.downloadUpdate();
} else { } else {
mainWindow?.webContents.executeJavaScript( mainWindow?.webContents.executeJavaScript(
`window.showUpdateBadge && window.showUpdateBadge("${info.version}")` `window.showUpdateBadge && window.showUpdateBadge("${info.version}")`
@@ -96,20 +96,8 @@ function initAutoUpdater(mainWindow) {
* Logs failure and displays an error dialog to user. * Logs failure and displays an error dialog to user.
*/ */
autoUpdater.on("error", (err) => { autoUpdater.on("error", (err) => {
const msg = err?.message || ""; logger.error("Auto update error:", err.message);
dialog.showErrorBox("Update Error", 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:", msg);
}); });
checkForUpdates(); checkForUpdates();
@@ -120,20 +108,6 @@ function initAutoUpdater(mainWindow) {
* Separated from init for reusability and testability. * Separated from init for reusability and testability.
*/ */
async function checkForUpdates() { async function checkForUpdates() {
/**
* Prevents electron-updater from running inside sandboxed environments.
* Snap and Flatpak lock the file system in read-only mode, causing the updater to crash.
* In these environments, updates are safely handled by snapd or the Flatpak runtime.
* Standalone Linux packages (.deb, .rpm, AppImage) will bypass this and update normally.
*/
if (process.env.SNAP || process.env.FLATPAK_ID) {
logger.info("Running as Snap or Flatpak. Updates are managed by the OS store. Skipping electron-updater.");
return;
}
if (!require("electron").app.isPackaged) return ;
try { try {
await autoUpdater.checkForUpdates(); await autoUpdater.checkForUpdates();
} catch (err) { } catch (err) {

View File

@@ -25,19 +25,15 @@ function checkNativeDependencies() {
*/ */
const { binaryPaths } = require("../server/helpers/path.helpers"); 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. * List of required external executables used by the application runtime.
* Each entry defines a binary name and its resolved absolute path. * Each entry defines a binary name and its resolved absolute path.
*/ */
const deps = [ const deps = [
{ name: `yt-dlp${addExe}`, path: binaryPaths.ytDlp }, { name: "yt-dlp.exe", path: binaryPaths.ytDlp },
{ name: `ffmpeg${addExe}`, path: binaryPaths.ffmpeg }, { name: "ffmpeg.exe", path: binaryPaths.ffmpeg },
{ name: `ffprobe${addExe}`, path: binaryPaths.ffprobe }, { name: "ffprobe.exe", path: binaryPaths.ffprobe },
{ name: `deno${addExe}`, path: binaryPaths.deno }, { name: "deno.exe", path: binaryPaths.deno },
]; ];
const missing = deps.filter(d => !fs.existsSync(d.path)); const missing = deps.filter(d => !fs.existsSync(d.path));
@@ -56,7 +52,7 @@ function checkNativeDependencies() {
app.whenReady().then(() => { app.whenReady().then(() => {
dialog.showErrorBox( dialog.showErrorBox(
"Missing dependencies", "Missing dependencies",
`The following files are missing in the 'resources' folder:\n${list}\n\nThe application will now exit. Try to reinstall.` `The following files are missing in the 'ressources' folder:\n${list}\n\nThe application will now exit. Try to reinstall.`
); );
app.quit(); app.quit();
}); });

View File

@@ -1,8 +1,6 @@
const config = require('../config'); const config = require('../config');
const RPC = require("discord-rpc"); const RPC = require("discord-rpc");
const { logger } = require("../server/logger"); const { logger } = require("../server/logger");
const fs = require("fs");
const path = require("path");
/** /**
* Discord Application Client ID used to authenticate the RPC connection. * Discord Application Client ID used to authenticate the RPC connection.
@@ -22,51 +20,6 @@ const rpc = new RPC.Client({ transport: "ipc" });
*/ */
let intervalId; 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. * Initializes Discord Rich Presence (RPC) connection.
* *
@@ -79,11 +32,6 @@ function fixLinuxDiscordRPC() {
*/ */
function startRPC() { function startRPC() {
/**
* Patch that create Simlinks for Linux environment;
*/
fixLinuxDiscordRPC();
/** /**
* Rich Presence payload describing current application state * Rich Presence payload describing current application state
* shown in Discord user profile. * shown in Discord user profile.
@@ -103,9 +51,7 @@ function startRPC() {
}); });
rpc.login({ clientId }).catch(err => { rpc.login({ clientId }).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("Unable to connect to the RPC:", err);
// logger.error("Unable to connect to the RPC:", err);
logger.info("Unable to connect to the Discord RPC. Discord is maybe not launched");
}); });
} }
@@ -114,31 +60,28 @@ function startRPC() {
* *
* Responsibilities: * Responsibilities:
* - Clear any running intervals * - Clear any running intervals
* - Remove current activity from Discord if connected * - Remove current activity from Discord
* - Destroy RPC transport connection * - Destroy RPC transport connection
* - Handle cleanup errors safely * - Handle cleanup errors safely
*/ */
async function stopRPC() { async function stopRPC(){
if (rpc) {
try { try {
if (intervalId) clearInterval(intervalId); if (intervalId) clearInterval(intervalId);
/** /**
* Ensures RPC connection AND the underlying socket exist * Ensures RPC connection exists before attempting cleanup.
* before attempting to clear activity to prevent crash.
*/ */
if (rpc.transport && rpc.transport.socket) { if (rpc && rpc.transport) {
await rpc.clearActivity(); await rpc.clearActivity()
}
await rpc.destroy(); await rpc.destroy();
} else{
logger.error("Not Able to close RPC")
}
} catch (err) { } 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);
// logger.error("Error while closing the RPC:", err);
} }
} }
}
module.exports = { startRPC, stopRPC}; module.exports = { startRPC, stopRPC};

View File

@@ -12,13 +12,13 @@
const { ipcMain, dialog, shell } = require("electron"); const { ipcMain, dialog, shell } = require("electron");
const fs = require("fs"); const fs = require("fs");
const path = require("path");
const { logger, logDir } = require("../server/logger"); const { logger, logDir } = require("../server/logger");
const { configFeatures, featuresPath } = require("../config"); const { configFeatures, featuresPath } = require("../config");
const { getThemes, reloadThemes } = require("./themeManager"); const { getThemes, reloadThemes } = require("./themeManager");
const config = require("../config"); const config = require("../config");
const { validateDownloadPath, getDefaultDownloadPath } = require("./pathValidator"); const { validateDownloadPath, getDefaultDownloadPath } = require("./pathValidator");
const { userThemesPath } = require("../server/helpers/path.helpers"); const { userThemesPath } = require("../server/helpers/path.helpers");
const { createSystemTray, destroyTray } = require("./tray");
/** /**
* Security whitelist for feature flags that can be modified at runtime. * Security whitelist for feature flags that can be modified at runtime.
@@ -28,7 +28,6 @@ const { createSystemTray, destroyTray } = require("./tray");
*/ */
const FEATURE_WHITELIST = new Set([ const FEATURE_WHITELIST = new Set([
"autoUpdate", "autoUpdate",
"systemTray",
"discordRPC", "discordRPC",
"customTopBar", "customTopBar",
"autoCheckInfo", "autoCheckInfo",
@@ -36,7 +35,6 @@ const FEATURE_WHITELIST = new Set([
"addMetadata", "addMetadata",
"verboseLogs", "verboseLogs",
"autoDownloadPlaylist", "autoDownloadPlaylist",
"keepPlaylistOrder",
"customCodec", "customCodec",
"theme", "theme",
"createPlaylistFolders", "createPlaylistFolders",
@@ -215,21 +213,6 @@ function registerIpcHandlers(getMainWindow) {
return true; 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(getMainWindow(), config.devMode);
} else {
logger.info("System Tray disabled dynamically.");
destroyTray();
}
}
configFeatures[key] = value; configFeatures[key] = value;
fs.writeFileSync( fs.writeFileSync(

View File

@@ -56,23 +56,19 @@ function validateDownloadPath(userPath) {
if (!userPath) return getDefaultDownloadPath(); if (!userPath) return getDefaultDownloadPath();
try { try {
const absolutePath = path.resolve(userPath);
if (!fs.existsSync(absolutePath)) { /**
fs.mkdirSync(absolutePath, { recursive: true }); * Resolves filesystem symlinks to ensure canonical absolute path.
logger.info(`Download folder created: ${absolutePath}`); * Required to prevent path traversal or alias bypass.
} */
const resolved = fs.realpathSync(path.resolve(userPath));
const real = fs.realpathSync(absolutePath); if (!isSafePath(resolved)) {
if (!isSafePath(real)) {
throw new Error("Path not allowed: system folders are blocked!"); throw new Error("Path not allowed: system folders are blocked!");
} }
return resolved;
return real;
} }
catch (err) { catch (err) {
/** /**
* Handles invalid filesystem paths, permissions issues, or resolution failures. * Handles invalid filesystem paths, permissions issues, or resolution failures.
* Logs diagnostic information before propagating a sanitized error. * Logs diagnostic information before propagating a sanitized error.

View File

@@ -270,6 +270,7 @@ async function loadThemesFromFolder() {
if (fs.statSync(filePath).isDirectory()) { if (fs.statSync(filePath).isDirectory()) {
const theme = await loadThemeFromFolder(filePath, themeId); const theme = await loadThemeFromFolder(filePath, themeId);
if (theme) themes.push(theme); if (theme) themes.push(theme);
continue;
} }
} }

View File

@@ -1,108 +0,0 @@
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");
/**
* 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 {import('electron').BrowserWindow} mainWindow - The main application window.
* @param {boolean} devMode - Indicates whether the application is running in development mode.
* @returns {Tray} The created Tray instance.
*/
function createSystemTray(mainWindow, 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: () => {
mainWindow.show();
},
},
{ 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", () => {
if (mainWindow.isVisible()) {
mainWindow.hide();
} else {
mainWindow.show();
mainWindow.focus();
}
});
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};

View File

@@ -81,19 +81,6 @@ async function createMainWindow() {
throw 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.systemTray ) {
event.preventDefault();
mainWindow.hide();
return false;
}
});
/** /**
* Cleans up window reference when the main window is closed. * Cleans up window reference when the main window is closed.
* Prevents memory leaks and allows recreation if needed. * Prevents memory leaks and allows recreation if needed.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

View File

@@ -7,7 +7,7 @@ const path = require("path");
* Indicates whether the application is running in development mode. * Indicates whether the application is running in development mode.
* Determined by Electron's packaging state. * Determined by Electron's packaging state.
*/ */
const devMode = process.env.NODE_ENV === "test" || !app?.isPackaged; const devMode = !app.isPackaged;
/** /**
* Resolves the configuration file path depending on runtime environment. * Resolves the configuration file path depending on runtime environment.
@@ -68,30 +68,13 @@ function loadFeatures() {
return JSON.parse(raw); 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. * In-memory snapshot of application feature flags.
* *
* Note: Changes to the config file are not automatically reflected. * Note: Changes to the config file are not automatically reflected.
* A restart or reload is required. * A restart or reload is required.
*/ */
let configFeatures = loadFeatures(); const configFeatures = loadFeatures();
module.exports = { module.exports = {
/** /**
@@ -119,11 +102,6 @@ module.exports = {
*/ */
configFeatures, configFeatures,
/**
* Function to reload the config
*/
reloadFeatures,
/** /**
* Path to the active configuration file * Path to the active configuration file
*/ */

View File

@@ -1,6 +1,5 @@
{ {
"autoUpdate": true, "autoUpdate": true,
"systemTray": false,
"discordRPC": true, "discordRPC": true,
"customTopBar": true, "customTopBar": true,
"autoCheckInfo": true, "autoCheckInfo": true,
@@ -8,7 +7,6 @@
"addMetadata": true, "addMetadata": true,
"verboseLogs": false, "verboseLogs": false,
"autoDownloadPlaylist": true, "autoDownloadPlaylist": true,
"keepPlaylistOrder": false,
"createPlaylistFolders": true, "createPlaylistFolders": true,
"customCodec": "h264", "customCodec": "h264",
"logSystem": true, "logSystem": true,

View File

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

68
main.js
View File

@@ -37,14 +37,11 @@ app.setAppUserModelId("com.masteracnolo.freedomloader");
/** /**
* Load the app dependencies for hardware choice * Load the app dependencies for hardware choice
*/ */
const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger"); const { logger, logSessionStart, logSessionEnd } = require("./server/logger");
const { configFeatures } = require("./config"); const { configFeatures } = require("./config");
/** /**
* In-memory snapshot of application feature flags. * On start, enable or disable hardWareAcceleration
*
* Note: Changes to the config file are not automatically reflected.
* A restart could be required.
*/ */
if (!configFeatures.enableHardwareAcceleration){ if (!configFeatures.enableHardwareAcceleration){
app.disableHardwareAcceleration(); app.disableHardwareAcceleration();
@@ -53,7 +50,15 @@ if (!configFeatures.enableHardwareAcceleration){
logger.info("Enable Hardware Acceleration") logger.info("Enable Hardware Acceleration")
} }
if(configFeatures.devMode){ /**
* In-memory snapshot of application feature flags.
*
* Note: Changes to the config file are not automatically reflected.
* A restart could be required.
*/
const config = require("./config");
if(config.devMode){
/** /**
* Start devTron extensions - @see https://github.com/electron/devtron * Start devTron extensions - @see https://github.com/electron/devtron
*/ */
@@ -74,22 +79,8 @@ const { updateYtDlp } = require("./app/ytDlpUpdater");
const { createMainWindow, getMainWindow } = require("./app/windowManager"); const { createMainWindow, getMainWindow } = require("./app/windowManager");
const { registerIpcHandlers } = require("./app/ipcHandlers"); const { registerIpcHandlers } = require("./app/ipcHandlers");
const { createSplashWindow, closeSplashWindow, setSplashProgress } = require("./app/splashManager"); const { createSplashWindow, closeSplashWindow, setSplashProgress } = require("./app/splashManager");
const { userThemesPath, initUserThemes, isWindows, validateBinaries, defaultDownloadFolder } = require("./server/helpers/path.helpers"); const { userThemesPath, initUserThemes } = require("./server/helpers/path.helpers");
const {createSystemTray, destroyTray} = require("./app/tray");
/**
* 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;
/** /**
* If another instance want to run * If another instance want to run
@@ -108,11 +99,11 @@ app.on("second-instance", () => {
}); });
app.whenReady().then(async () => { app.whenReady().then(async () => {
logSessionStart(logDir, defaultDownloadFolder); logSessionStart();
createSplashWindow(); createSplashWindow();
if (!configFeatures.devMode && !checkNativeDependencies()) return; if (!config.devMode && !checkNativeDependencies()) return;
const { userYtDlp } = require("./server/helpers/path.helpers"); const { userYtDlp } = require("./server/helpers/path.helpers");
updateYtDlp(userYtDlp); updateYtDlp(userYtDlp);
@@ -125,9 +116,6 @@ app.whenReady().then(async () => {
registerIpcHandlers(getMainWindow); registerIpcHandlers(getMainWindow);
setSplashProgress(2); // Loading themes setSplashProgress(2); // Loading themes
validateBinaries();
initUserThemes(); initUserThemes();
await initThemes(userThemesPath); await initThemes(userThemesPath);
@@ -136,11 +124,6 @@ app.whenReady().then(async () => {
closeSplashWindow(); closeSplashWindow();
getMainWindow().show(); getMainWindow().show();
if (configFeatures.systemTray) {
createSystemTray(getMainWindow(), configFeatures.devMode);
}
if (configFeatures.discordRPC) startRPC(); if (configFeatures.discordRPC) startRPC();
if (configFeatures.autoUpdate) initAutoUpdater(getMainWindow()); if (configFeatures.autoUpdate) initAutoUpdater(getMainWindow());
@@ -152,33 +135,12 @@ app.whenReady().then(async () => {
}); });
app.on("window-all-closed", () => { app.on("window-all-closed", () => {
if (app.isQuitting || !configFeatures.systemTray) {
logger.info("Shutting down..."); logger.info("Shutting down...");
app.quit(); app.quit();
} else if (process.platform !== "darwin") {
logger.info("Main window closed, app running in background (Tray).");
}
}); });
app.on("before-quit", (event) => { app.on("before-quit", async () => {
app.isQuitting = true;
if (!isCleanedUp) {
event.preventDefault();
(async () => {
try {
destroyTray();
await stopRPC(); await stopRPC();
} catch (err) {
logger.error("Error during cleanup:", err);
} finally {
logger.info("All services stopped. Have a nice day!"); logger.info("All services stopped. Have a nice day!");
logSessionEnd(); logSessionEnd();
isCleanedUp = true;
app.quit();
}
})();
}
}); });

6149
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,39 +1,13 @@
{ {
"name": "freedom-loader", "name": "freedom-loader",
"productName": "Freedom Loader", "productName": "Freedom Loader",
"version": "1.6.2", "version": "1.5.1",
"author": "MasterAcnolo <MasterAcnolo@users.noreply.github.com>", "author": "MasterAcnolo",
"description": "Free and open-source GUI for yt-dlp — download video and audio from hundreds of platforms", "description": "Freedom Loader",
"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", "main": "main.js",
"scripts": { "scripts": {
"start": "electron --no-warnings .", "start": "electron --trace-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": "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",
"update": "npm update" "update": "npm update"
}, },
"dependencies": { "dependencies": {
@@ -49,14 +23,10 @@
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@electron/devtron": "^2.1.1", "electron": "^41.2.0",
"@jest/globals": "^30.4.1", "electron-builder": "^26.8.1",
"@playwright/test": "^1.62.1", "@electron/devtron": "^2.1.1"
"electron": "^43.3.0",
"electron-builder": "^26.15.3",
"jest": "^30.4.2",
"nodemon": "^3.1.14",
"playwright": "^1.62.1"
}, },
"build": { "build": {
"appId": "com.masteracnolo.freedomloader", "appId": "com.masteracnolo.freedomloader",
@@ -65,8 +35,7 @@
{ {
"provider": "github", "provider": "github",
"owner": "MasterAcnolo", "owner": "MasterAcnolo",
"repo": "Freedom-Loader", "repo": "Freedom-Loader"
"releaseType": "draft"
} }
], ],
"files": [ "files": [
@@ -84,18 +53,14 @@
"from": "theme", "from": "theme",
"to": "theme" "to": "theme"
}, },
{
"from": "build/app-icon.ico",
"to": "app-icon.ico"
},
{
"from": "build/app-icon-64x64.png",
"to": "app-icon-64x64.png"
},
{ {
"from": "build/confirm-icon.png", "from": "build/confirm-icon.png",
"to": "confirm-icon.png" "to": "confirm-icon.png"
}, },
{
"from": "build/app-icon.ico",
"to": "app-icon.ico"
},
{ {
"from": "build/banner.bmp", "from": "build/banner.bmp",
"to": "banner.bmp" "to": "banner.bmp"
@@ -108,6 +73,22 @@
"from": "build/error.png", "from": "build/error.png",
"to": "error.png" "to": "error.png"
}, },
{
"from": "ressources/yt-dlp.exe",
"to": "binaries/yt-dlp.exe"
},
{
"from": "ressources/ffmpeg.exe",
"to": "binaries/ffmpeg.exe"
},
{
"from": "ressources/ffprobe.exe",
"to": "binaries/ffprobe.exe"
},
{
"from": "ressources/deno.exe",
"to": "binaries/deno.exe"
},
{ {
"from": "config/config.default.json", "from": "config/config.default.json",
"to": "config/config.default.json" "to": "config/config.default.json"
@@ -115,26 +96,7 @@
], ],
"win": { "win": {
"target": "nsis", "target": "nsis",
"icon": "build/app-icon.ico", "icon": "build/app-icon.ico"
"artifactName": "Freedom-Loader-Setup-${version}.${ext}",
"extraResources": [
{
"from": "resources/binaries/win-32/yt-dlp.exe",
"to": "binaries/yt-dlp.exe"
},
{
"from": "resources/binaries/win-32/ffmpeg.exe",
"to": "binaries/ffmpeg.exe"
},
{
"from": "resources/binaries/win-32/ffprobe.exe",
"to": "binaries/ffprobe.exe"
},
{
"from": "resources/binaries/win-32/deno.exe",
"to": "binaries/deno.exe"
}
]
}, },
"nsis": { "nsis": {
"oneClick": false, "oneClick": false,
@@ -146,51 +108,6 @@
"createDesktopShortcut": true, "createDesktopShortcut": true,
"createStartMenuShortcut": true, "createStartMenuShortcut": true,
"shortcutName": "Freedom Loader" "shortcutName": "Freedom Loader"
},
"linux": {
"target": [
"AppImage",
"deb",
"snap"
],
"category": "Utility",
"icon": "build/app-icon.png",
"executableName": "freedom-loader",
"extraResources": [
{
"from": "resources/binaries/linux/yt-dlp",
"to": "binaries/yt-dlp"
},
{
"from": "resources/binaries/linux/ffmpeg",
"to": "binaries/ffmpeg"
},
{
"from": "resources/binaries/linux/ffprobe",
"to": "binaries/ffprobe"
},
{
"from": "resources/binaries/linux/deno",
"to": "binaries/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

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

View File

@@ -1,9 +0,0 @@
#!/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

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

@@ -9,11 +9,6 @@ const { contextBridge, ipcRenderer } = require("electron");
*/ */
contextBridge.exposeInMainWorld("electronAPI", { contextBridge.exposeInMainWorld("electronAPI", {
/**
* Return process.platform to renderer
*/
getProcessPlatform: () => process.platform,
/** /**
* Returns the default system download directory. * Returns the default system download directory.
*/ */

View File

@@ -56,9 +56,9 @@
<div class="setting-item" id="theme" style="margin-left: 32px;"> <div class="setting-item" id="theme" style="margin-left: 32px;">
<div class="setting-info"> <div class="setting-info">
<div style="display: flex; align-items: center; gap: 10px;"> <div style="display: flex; align-items: center; gap: 10px;">
<span class="setting-title">Theme</span> <span class="setting-title">Thème</span>
<select id="themeSelect" data-key="theme" aria-label="Choose the theme"> <select id="themeSelect" data-key="theme" aria-label="Choisir le thème">
</select> </select>
<button id="refresh-themes-btn" title="Refresh themes" class="refresh-themes-btn"> <button id="refresh-themes-btn" title="Refresh themes" class="refresh-themes-btn">
@@ -106,10 +106,10 @@
</div> </div>
<div class="setting-item"> <div class="setting-item">
<input data-key="keepPlaylistOrder" id="keepPlaylistOrder" type="checkbox"> <input type="checkbox" data-key="autoCheckInfo" id="autoCheckInfo">
<div class="setting-info"> <div class="setting-info">
<span class="setting-title">Keep Playlist Order</span> <span class="setting-title">Auto Fetch Info</span>
<small>Add index before output file, to keep the order.</small> <small>Automatically fetch information for new downloads.</small>
</div> </div>
</div> </div>
</div> </div>
@@ -127,14 +127,6 @@
</div> </div>
</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"> <!-- <div class="setting-item">
<input type="checkbox" data-key="outputTitleCheck" id="outputTitleCheck"> <input type="checkbox" data-key="outputTitleCheck" id="outputTitleCheck">
<div class="setting-info"> <div class="setting-info">
@@ -143,14 +135,6 @@
</div> </div>
</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"> <div class="setting-item">
<input type="checkbox" data-key="addMetadata" id="addMetadata"> <input type="checkbox" data-key="addMetadata" id="addMetadata">
<div class="setting-info"> <div class="setting-info">

View File

@@ -1,54 +1,21 @@
/** /**
* Retrieves the application version and build state from the backend. * Retrieves the application version from the Electron main process
* Displays a formatted string indicating packaging state and preview status. * and displays it in the UI version badge (bottom-right corner).
* 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" * This is mainly used for debugging and build identification.
*
* @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}
*/ */
async function versionLabel() {
const appVersion = await window.electronAPI.getVersion(); const appVersion = await window.electronAPI.getVersion();
/** /**
* UI element displaying the current application version. * UI element displaying the current application version.
* @type {HTMLElement | null} * Updated at runtime after IPC call resolves.
*/ */
const versionBadge = document.getElementById("version-badge"); const versionBadge = document.getElementById("version-badge");
if (!versionBadge) return; if (!versionBadge) return;
/** versionBadge.textContent = `${appVersion}`;
* 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(); versionLabel();

View File

@@ -17,7 +17,9 @@ document.addEventListener("DOMContentLoaded", () => {
*/ */
pasteBtn.addEventListener("click", async () => { pasteBtn.addEventListener("click", async () => {
try { try {
urlInput.value = await navigator.clipboard.readText(); const text = await navigator.clipboard.readText();
urlInput.value = text;
// Notify frameworks / listeners bound to input changes // Notify frameworks / listeners bound to input changes
urlInput.dispatchEvent(new Event("input", { bubbles: true })); urlInput.dispatchEvent(new Event("input", { bubbles: true }));

View File

@@ -32,52 +32,52 @@ function applyTheme(theme) {
} }
// CSS variables (design tokens) // CSS variables (design tokens)
root.style.setProperty("--background-color", style.colors?.background || "#121212"); root.style.setProperty("--background-color", style.colors?.background || "");
root.style.setProperty("--default-text-color", style.colors?.text?.default || "#e0e0e0"); root.style.setProperty("--default-text-color", style.colors?.text?.default || "");
root.style.setProperty("--subtitle-color", style.colors?.text?.subtitle || "#2196f3"); root.style.setProperty("--subtitle-color", style.colors?.text?.subtitle || "");
root.style.setProperty("--audio-only-label-color", style.colors?.text?.audioOnly || "#f5f5f5"); root.style.setProperty("--audio-only-label-color", style.colors?.text?.audioOnly || "");
root.style.setProperty("--form-bg-color", style.form?.background || "#1e1e1e"); root.style.setProperty("--form-bg-color", style.form?.background || "");
root.style.setProperty("--form-input-bg-color", style.form?.input?.background || "#2a2a2a"); root.style.setProperty("--form-input-bg-color", style.form?.input?.background || "");
root.style.setProperty("--form-input-border-color", style.form?.input?.border || "#444444"); root.style.setProperty("--form-input-border-color", style.form?.input?.border || "");
root.style.setProperty("--form-input-border-focus-color", style.form?.input?.borderFocus || "#2196f3"); root.style.setProperty("--form-input-border-focus-color", style.form?.input?.borderFocus || "");
root.style.setProperty("--form-input-text-color", style.form?.input?.text || "#f5f5f5"); root.style.setProperty("--form-input-text-color", style.form?.input?.text || "");
root.style.setProperty("--form-input-placeholder-color", style.form?.input?.placeholder || "#888888"); root.style.setProperty("--form-input-placeholder-color", style.form?.input?.placeholder || "");
root.style.setProperty("--form-button-bg-color", style.form?.button?.background || "#2196f3"); root.style.setProperty("--form-button-bg-color", style.form?.button?.background || "");
root.style.setProperty("--form-button-text-color", style.form?.button?.text || "#ffffff"); root.style.setProperty("--form-button-text-color", style.form?.button?.text || "");
root.style.setProperty("--form-button-bg-hover-color", style.form?.button?.hover || "#1976d2"); root.style.setProperty("--form-button-bg-hover-color", style.form?.button?.hover || "");
root.style.setProperty("--paste-button-icon-color", style.form?.pasteButtonIcon || "#ffffff"); root.style.setProperty("--paste-button-icon-color", style.form?.pasteButtonIcon || "");
root.style.setProperty("--checkbox-unchecked-bg-color", style.checkbox?.background?.unchecked || "#555555"); root.style.setProperty("--checkbox-unchecked-bg-color", style.checkbox?.background?.unchecked || "");
root.style.setProperty("--checkbox-checked-bg-color", style.checkbox?.background?.checked || "#2196f3"); root.style.setProperty("--checkbox-checked-bg-color", style.checkbox?.background?.checked || "");
root.style.setProperty("--checkbox-checkmark-border-color", style.checkbox?.checkmarkBorder || "#ffffff"); root.style.setProperty("--checkbox-checkmark-border-color", style.checkbox?.checkmarkBorder || "");
root.style.setProperty("--checkbox-pulse-color", style.checkbox?.pulse || "rgba(33, 150, 243, 0.5)"); root.style.setProperty("--checkbox-pulse-color", style.checkbox?.pulse || "");
root.style.setProperty("--download-status-color", style.download?.status || "#2196f3"); root.style.setProperty("--download-status-color", style.download?.status || "");
root.style.setProperty("--progress-bar-bg-color", style.progressBar?.background || "#444444"); root.style.setProperty("--progress-bar-bg-color", style.progressBar?.background || "");
root.style.setProperty("--progress-bar-fill-color", style.progressBar?.fill || "#2196f3"); root.style.setProperty("--progress-bar-fill-color", style.progressBar?.fill || "");
root.style.setProperty("--video-info-bg-color", style.videoInfo?.background || "#1a1a1a"); root.style.setProperty("--video-info-bg-color", style.videoInfo?.background || "");
root.style.setProperty("--video-info-text-color", style.videoInfo?.text || "#e0e0e0"); root.style.setProperty("--video-info-text-color", style.videoInfo?.text || "");
root.style.setProperty("--video-info-heading-color", style.videoInfo?.heading || "#2196f3"); root.style.setProperty("--video-info-heading-color", style.videoInfo?.heading || "");
root.style.setProperty("--video-info-list-color", style.videoInfo?.list || "#cccccc"); root.style.setProperty("--video-info-list-color", style.videoInfo?.list || "");
root.style.setProperty("--video-info-list-strong-color", style.videoInfo?.strong || "#ffffff"); root.style.setProperty("--video-info-list-strong-color", style.videoInfo?.strong || "");
root.style.setProperty("--video-info-link-color", style.videoInfo?.link || "#2196f3"); root.style.setProperty("--video-info-link-color", style.videoInfo?.link || "");
root.style.setProperty("--video-info-link-hover-color", style.videoInfo?.linkHover || "#1976d2"); root.style.setProperty("--video-info-link-hover-color", style.videoInfo?.linkHover || "");
root.style.setProperty("--playlist-background-color", style.playlist?.background || "#303030"); root.style.setProperty("--playlist-background-color", style.playlist?.background || "");
root.style.setProperty("--settings-button-bg-color", style.settings?.button?.background || "#1e1e1e"); root.style.setProperty("--settings-button-bg-color", style.settings?.button?.background || "");
root.style.setProperty("--settings-button-text-color", style.settings?.button?.text || "#e0e0e0"); root.style.setProperty("--settings-button-text-color", style.settings?.button?.text || "");
root.style.setProperty("--open-theme-button-bg-color", style.settings?.openThemeButton?.background || "#2196f3"); root.style.setProperty("--open-theme-button-bg-color", style.settings?.openThemeButton?.background || "");
root.style.setProperty("--open-theme-button-text-color", style.settings?.openThemeButton?.text || "#ffffff"); root.style.setProperty("--open-theme-button-text-color", style.settings?.openThemeButton?.text || "");
root.style.setProperty("--open-json-button-bg-color", style.settings?.openJsonButton?.background || style.settings?.button?.background || "#1e1e1e"); root.style.setProperty("--open-json-button-bg-color", style.settings?.openJsonButton?.background || style.settings?.button?.background || "");
root.style.setProperty("--open-json-button-text-color", style.settings?.openJsonButton?.text || style.settings?.button?.text || "#e0e0e0"); root.style.setProperty("--open-json-button-text-color", style.settings?.openJsonButton?.text || style.settings?.button?.text || "");
root.style.setProperty("--settings-modal-bg-color", style.settings?.background?.modal || "#1e1e1e"); root.style.setProperty("--settings-modal-bg-color", style.settings?.background?.modal || "");
root.style.setProperty("--settings-section-bg-color", style.settings?.background?.section || "#2a2a2a"); root.style.setProperty("--settings-section-bg-color", style.settings?.background?.section || "");
root.style.setProperty("--settings-text-color", style.settings?.text || "#e0e0e0"); root.style.setProperty("--settings-text-color", style.settings?.text || "");
root.style.setProperty("--settings-subtitle-color", style.settings?.subtitle || "#aaaaaa"); root.style.setProperty("--settings-subtitle-color", style.settings?.subtitle || "");
const subtitleEl = document.getElementById("subtitle"); const subtitleEl = document.getElementById("subtitle");
if (subtitleEl) subtitleEl.textContent = theme.subtitle || theme.name; if (subtitleEl) subtitleEl.textContent = theme.subtitle || theme.name;
@@ -116,102 +116,7 @@ function populateThemeSelect(themes) {
*/ */
async function initThemes() { async function initThemes() {
loadedThemes = await window.electronAPI.getThemes(); loadedThemes = await window.electronAPI.getThemes();
if (!loadedThemes.length) return;
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];
}
populateThemeSelect(loadedThemes); populateThemeSelect(loadedThemes);

View File

@@ -30,15 +30,15 @@ function formatSize(bytes) {
*/ */
async function fetchVideoInfo(url) { async function fetchVideoInfo(url) {
try { try {
const res = await fetch(`/info?url=${encodeURIComponent(url)}`, { const res = await fetch("/info", {
method: "GET", method: "POST",
headers: {"Accept": "application/json"} headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ url }),
}); });
if (!res.ok) return { error: `An Error occured when fetching info` };
const data = await res.json(); const data = await res.json();
if (!res.ok) return {error: `An Error occured when fetching info`};
if (!data) return { error: "Data is Missing" }; if (!data) return { error: "Data is Missing" };
return data; return data;
@@ -271,6 +271,6 @@ async function init() {
}) })
} };
init(); init();

View File

@@ -5,41 +5,47 @@
* This function is safe to call once DOM is ready. * This function is safe to call once DOM is ready.
* It silently aborts if the API or elements are missing. * It silently aborts if the API or elements are missing.
*/ */
export function initTopBar() { function setupTopbarListeners() {
const { topbarAPI } = window; const { topbarAPI } = window;
if (!topbarAPI) return; if (!topbarAPI) return;
const minBtn = document.getElementById("minimize-btn"); const minBtn = document.getElementById("minimize-btn");
if (minBtn) minBtn.onclick = () => topbarAPI.minimize();
const maxBtn = document.getElementById("maximize-btn"); const maxBtn = document.getElementById("maximize-btn");
if (maxBtn) maxBtn.onclick = () => topbarAPI.maximize();
const closeBtn = document.getElementById("close-btn"); const closeBtn = document.getElementById("close-btn");
if (closeBtn) closeBtn.onclick = () => topbarAPI.close();
const devtoolsBtn = document.getElementById("devtools-btn"); const devtoolsBtn = document.getElementById("devtools-btn");
if (devtoolsBtn) devtoolsBtn.onclick = () => topbarAPI.openDevTools();
const logsBtn = document.getElementById("logs-btn"); const logsBtn = document.getElementById("logs-btn");
if (logsBtn) logsBtn.onclick = () => topbarAPI.openLogs();
const websiteBtn = document.getElementById("website-btn"); const websiteBtn = document.getElementById("website-btn");
if (websiteBtn) websiteBtn.onclick = () => topbarAPI.openWebsite();
const wikiBtn = document.getElementById("wiki-btn"); const wikiBtn = document.getElementById("wiki-btn");
if (wikiBtn) wikiBtn.onclick = () => topbarAPI.openWiki();
const workshopBtn = document.getElementById("workshop-btn"); const workshopBtn = document.getElementById("workshop-btn");
if (workshopBtn) workshopBtn.onclick = () => topbarAPI.openWorkshop();
if (minBtn) minBtn.onclick = () => topbarAPI.minimize();
if (maxBtn) maxBtn.onclick = () => topbarAPI.maximize();
if (closeBtn) closeBtn.onclick = () => topbarAPI.close();
if (devtoolsBtn) devtoolsBtn.onclick = () => topbarAPI.openDevTools();
if (logsBtn) logsBtn.onclick = () => topbarAPI.openLogs();
if (websiteBtn) websiteBtn.onclick = () => topbarAPI.openWebsite();
if (wikiBtn) wikiBtn.onclick = () => topbarAPI.openWiki();
if (workshopBtn) workshopBtn.onclick = () => topbarAPI.openWorkshop();
} }
async function attachListeners(){ /**
try{ * Registers topbar event listeners after DOM is fully loaded.
const featuresList = await window.electronAPI.getFeatures(); */
document.addEventListener("DOMContentLoaded", setupTopbarListeners);
if (!featuresList.customTopBar) { /**
* Applies UI layout adjustments depending on feature flags
* provided by the Electron backend.
*
* Specifically handles:
* - hiding custom topbar
* - adjusting layout spacing
* - repositioning theme switcher
*/
(async function applyFeatureLayout() {
const features = await window.electronAPI.getFeatures();
if (!features.customTopBar) {
const topbar = document.getElementById("topbar"); const topbar = document.getElementById("topbar");
const container = document.getElementById("container"); const container = document.getElementById("container");
const themeSwitcher = document.getElementById("theme-switcher"); const themeSwitcher = document.getElementById("theme-switcher");
@@ -48,12 +54,4 @@ async function attachListeners(){
if (container) container.style.marginTop = "0"; if (container) container.style.marginTop = "0";
if (themeSwitcher) themeSwitcher.style.top = "30px"; if (themeSwitcher) themeSwitcher.style.top = "30px";
} }
})();
} catch (error) {
console.error("Failed to load layout features for topbar:", error);
}
}
initTopBar()
document.addEventListener("DOMContentLoaded", attachListeners)

View File

@@ -16,14 +16,6 @@
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
/* Mark element as disabled */
.disabled {
pointer-events: none;
opacity: 0.5;
filter: grayscale(40%);
cursor: not-allowed;
}
/* Because why not */ /* Because why not */
#reference{ #reference{
position: fixed; position: fixed;

BIN
ressources/yt-dlp.exe Normal file

Binary file not shown.

View File

@@ -1,116 +0,0 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
VERSION=$(node -p "require('$ROOT_DIR/package.json').version")
RPM_OUT="$ROOT_DIR/dist/freedom-loader-${VERSION}.x86_64.rpm"
# ------------------------------------------------------------
# Resolve fpm from electron-builder cache
# Excludes Ruby gem wrappers, targets standalone binaries only
# Prefers fpm@2.x over legacy fpm-1.9.x
# ------------------------------------------------------------
FPM=$(find ~/.cache/electron-builder -name "fpm" -type f 2>/dev/null \
| grep -v "/gems/bin/fpm" \
| grep -v "/gems/gems/" \
| grep -v "/lib/app/bin/fpm" \
| grep "fpm@" \
| head -1)
# Fallback to legacy fpm-1.9.x if fpm@2.x not found
if [ -z "$FPM" ]; then
FPM=$(find ~/.cache/electron-builder -name "fpm" -type f 2>/dev/null \
| grep -v "/gems/" \
| grep -v "/lib/app/bin/fpm" \
| head -1)
fi
if [ -z "$FPM" ]; then
echo "Error: fpm not found in electron-builder cache. Run 'npm run build:linux' once first to download it."
exit 1
fi
echo "Using fpm: $FPM"
# Check that linux-unpacked exists
if [ ! -d "$ROOT_DIR/dist/linux-unpacked" ]; then
echo "Error: dist/linux-unpacked not found. Run 'npm run build:linux' first."
exit 1
fi
# ------------------------------------------------------------
# Prepare staging files
# fpm requires exact file-to-destination mappings to avoid
# scanning system directories (which causes permission errors)
# ------------------------------------------------------------
# Shell wrapper for /usr/bin/freedom-loader
TMP_WRAPPER="$ROOT_DIR/.rpm-pkg-tmp-wrapper"
mkdir -p "$TMP_WRAPPER"
cat > "$TMP_WRAPPER/freedom-loader" << 'EOF'
#!/bin/sh
exec /opt/freedom-loader/freedom-loader "$@"
EOF
chmod +x "$TMP_WRAPPER/freedom-loader"
# Desktop entry file
TMP_DESKTOP="$ROOT_DIR/.rpm-pkg-tmp-desktop"
mkdir -p "$TMP_DESKTOP"
cat > "$TMP_DESKTOP/freedom-loader.desktop" << 'EOF'
[Desktop Entry]
Name=Freedom Loader
Exec=/opt/freedom-loader/freedom-loader %U
Icon=freedom-loader
Type=Application
Categories=AudioVideo;Utility;Network;
Comment=Free and open-source GUI for yt-dlp
StartupWMClass=Freedom Loader
EOF
# ------------------------------------------------------------
# Step 1 - Build .rpm from linux-unpacked via fpm
# ------------------------------------------------------------
echo "Building RPM..."
"$FPM" -s dir -t rpm --force \
--rpm-use-file-permissions \
--name freedom-loader \
--version "$VERSION" \
--architecture x86_64 \
--description "Free and open-source GUI for yt-dlp" \
--url "https://masteracnolo.github.io/Freedom-Loader-Site/" \
--maintainer "MasterAcnolo <MasterAcnolo@users.noreply.github.com>" \
--license "GPL-3.0-only" \
--depends gtk3 \
--depends nss \
--depends libXScrnSaver \
--depends at-spi2-core \
--rpm-attr "4755,root,root:/opt/freedom-loader/chrome-sandbox" \
--package "$RPM_OUT" \
"$ROOT_DIR/dist/linux-unpacked/=/opt/freedom-loader" \
"$TMP_WRAPPER/freedom-loader=/usr/bin/freedom-loader" \
"$TMP_DESKTOP/freedom-loader.desktop=/usr/share/applications/freedom-loader.desktop" \
"$ROOT_DIR/build/app-icon.png=/usr/share/icons/hicolor/512x512/apps/freedom-loader.png" \
"$ROOT_DIR/package/com.masteracnolo.freedomloader.metainfo.xml=/usr/share/metainfo/com.masteracnolo.freedomloader.metainfo.xml"
rm -rf "$TMP_WRAPPER" "$TMP_DESKTOP"
echo "RPM built: $RPM_OUT"
# ------------------------------------------------------------
# Step 2 - Build SRPM for COPR
# Wraps the .rpm above into a source RPM for COPR submission
# ------------------------------------------------------------
echo "Building SRPM for COPR..."
mkdir -p "$ROOT_DIR/srpm-out"
TMP_DIR="$ROOT_DIR/.copr-build-tmp"
mkdir -p "$TMP_DIR"
TMP_SPEC="$TMP_DIR/freedom-loader.spec"
sed "s/__VERSION__/$VERSION/g" "$ROOT_DIR/package/freedom-loader.spec" > "$TMP_SPEC"
rpmbuild -bs "$TMP_SPEC" \
--define "_sourcedir $ROOT_DIR/dist" \
--define "_srcrpmdir $ROOT_DIR/srpm-out"
rm -rf "$TMP_DIR"
echo "SRPM built: $ROOT_DIR/srpm-out/freedom-loader-$VERSION-1*.src.rpm"

View File

@@ -1,187 +0,0 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
VERSION=$(node -p "require('$ROOT_DIR/package.json').version")
TAG="$VERSION"
# Parse arguments
PUBLISH_SNAP=false
PUBLISH_COPR=false
DRY_RUN=false
for arg in "$@"; do
case $arg in
--snap) PUBLISH_SNAP=true ;;
--copr) PUBLISH_COPR=true ;;
--publish) PUBLISH_SNAP=true; PUBLISH_COPR=true ;;
--dry-run) DRY_RUN=true ;;
*) echo "⚠️ Argument inconnu : $arg" ; exit 1 ;;
esac
done
echo ""
echo "============================================"
echo " Freedom Loader - Release Pipeline"
echo " Version : $VERSION"
echo " Tag : $TAG"
echo " Snap : $PUBLISH_SNAP"
echo " COPR : $PUBLISH_COPR"
echo " Dry run : $DRY_RUN"
echo "============================================"
echo ""
# ------------------------------------------------------------
# Step 0.1 - Check Dependencies
# ------------------------------------------------------------
echo "[0/5] Checking dependencies..."
command -v gh >/dev/null 2>&1 || { echo "'gh' CLI missing, need to be installed (https://cli.github.com/)."; exit 1; }
if [ "$PUBLISH_SNAP" = true ] && [ "$DRY_RUN" = false ]; then
command -v snapcraft >/dev/null 2>&1 || { echo "'snapcraft' missing, need to be installed."; exit 1; }
fi
if [ "$PUBLISH_COPR" = true ] && [ "$DRY_RUN" = false ]; then
command -v copr-cli >/dev/null 2>&1 || { echo "'copr-cli' missing, need to be installed."; exit 1; }
fi
echo "All dependencies found"
# ------------------------------------------------------------
# Step 0.2 - Clean Workspace
# ------------------------------------------------------------
echo "[0/5] Cleaning workspace..."
# On supprime les dossiers générés lors des builds précédents
rm -rf "$ROOT_DIR/dist" "$ROOT_DIR/srpm-out"
echo "Workspace cleaned (dist/ and srpm-out/ removed)"
# ------------------------------------------------------------
# Step 1 - Build Linux packages (AppImage, deb, snap)
# ------------------------------------------------------------
echo "[1/5] Building Linux packages..."
cd "$ROOT_DIR"
npm run build:linux
echo "Linux packages built"
# ------------------------------------------------------------
# Step 2 - Build RPM binary + SRPM for COPR
# Uses the create-copr-package.sh script which handles:
# - fpm binary resolution from electron-builder cache
# - .rpm generation from linux-unpacked
# - .src.rpm generation for COPR submission
# ------------------------------------------------------------
echo "[2/5] Building RPM and SRPM..."
bash "$ROOT_DIR/scripts/create-copr-package.sh"
echo "RPM and SRPM built"
# ------------------------------------------------------------
# Step 3 - Build Windows installer
# Note: requires Windows binaries to be present locally under
# resources/binaries/win-32/ (not committed to the repo)
# ------------------------------------------------------------
echo "[3/5] Building Windows installer..."
npm run build:win
echo " Windows installer built"
# ------------------------------------------------------------
# Step 4 - Create a draft release on GitHub
# Release notes are pre-filled with the standard template.
# Edit and publish manually from the GitHub web interface.
# Requires gh CLI to be installed and authenticated.
# ------------------------------------------------------------
echo "[4/5] Creating GitHub draft release $TAG..."
# Utilise une méthode un peu plus robuste pour trouver l'ancien tag (ignore le HEAD actuel s'il est déjà tagué)
PREV_TAG=$(git rev-list --tags --skip=1 --max-count=1 | xargs git describe --tags 2>/dev/null || echo "previous")
RELEASE_NOTES="# Freedom Loader - $VERSION
## Found a bug or issue?
Please report it in the [GitHub Issues](https://github.com/MasterAcnolo/Freedom-Loader/issues) section.
## Next Release (non-exhaustive roadmap)
- More format options
- Subtitle support
- Improved UI / UX
- Language selection
- Download specific parts of videos
- File renaming options
- Parallel downloads
- Skip sponsored parts automatically
**Full Changelog**: https://github.com/MasterAcnolo/Freedom-Loader/compare/${PREV_TAG}...${TAG}"
if [ "$DRY_RUN" = false ]; then
gh release create "$TAG" \
--title "$TAG" \
--notes "$RELEASE_NOTES" \
--draft \
"$ROOT_DIR/dist/Freedom-Loader-Setup-${VERSION}.exe" \
"$ROOT_DIR/dist/Freedom-Loader-Setup-${VERSION}.exe.blockmap" \
"$ROOT_DIR/dist/freedom-loader-${VERSION}.x86_64.rpm" \
"$ROOT_DIR/dist/freedom-loader_${VERSION}_amd64.deb" \
"$ROOT_DIR/dist/Freedom Loader-${VERSION}.AppImage" \
"$ROOT_DIR/dist/freedom-loader_${VERSION}_amd64.snap" \
"$ROOT_DIR/dist/latest.yml" \
"$ROOT_DIR/dist/latest-linux.yml"
echo "Draft release created: https://github.com/MasterAcnolo/Freedom-Loader/releases"
else
echo " [DRY RUN] gh release create $TAG (skipped)"
fi
# ------------------------------------------------------------
# Step 5 - Publish to package stores
# Snap and COPR are opt-in via --snap, --copr, or --publish.
# Without these flags, this step is skipped entirely.
# Run after the GitHub release is published (not the draft),
# so store users always get the same version as GitHub users.
# ------------------------------------------------------------
echo "[5/5] Publishing to package stores..."
if [ "$PUBLISH_SNAP" = true ]; then
echo " Publishing to Snap Store..."
if [ "$DRY_RUN" = false ]; then
export PATH=$PATH:/var/lib/snapd/snap/bin
snapcraft upload \
"$ROOT_DIR/dist/freedom-loader_${VERSION}_amd64.snap" \
--release=stable
echo " Snap published"
else
echo " [DRY RUN] snapcraft upload (skipped)"
fi
else
echo " Snap: skipped (use --snap or --publish to enable)"
fi
if [ "$PUBLISH_COPR" = true ]; then
echo " Publishing to COPR..."
if [ "$DRY_RUN" = false ]; then
copr-cli build freedom-loader \
"$ROOT_DIR/srpm-out/freedom-loader-${VERSION}-1"*.src.rpm
echo "COPR build triggered"
else
echo " [DRY RUN] copr-cli build (skipped)"
fi
else
echo " COPR: skipped (use --copr or --publish to enable)"
fi
# ------------------------------------------------------------
# Done - Summary
# ------------------------------------------------------------
echo ""
echo "============================================"
echo " Release pipeline complete."
echo ""
echo " Next steps:"
echo " 1. Fill in the release notes on GitHub"
echo " 2. Publish the draft release"
if [ "$PUBLISH_SNAP" = false ] || [ "$PUBLISH_COPR" = false ]; then
echo ""
echo " Store publishing (not done yet):"
[ "$PUBLISH_SNAP" = false ] && echo " - Snap : bash scripts/release.sh --snap"
[ "$PUBLISH_COPR" = false ] && echo " - COPR : bash scripts/release.sh --copr"
fi
echo "============================================"

View File

@@ -18,9 +18,7 @@ const { isValidUrl } = require("../helpers/validation.helpers");
*/ */
async function infoController(req, res) { async function infoController(req, res) {
// Previously, i support both POST and GET element, but it's kinda weird. So now it only support GET. const url = req.body.url || req.query.url; // Supports POST and GET requests
const url = req.query.url;
const encodedUrl = encodeURIComponent(url)
/** /**
* Basic validation: * Basic validation:
@@ -32,10 +30,9 @@ async function infoController(req, res) {
return res.status(400).send("Invalid URL Or Missing"); return res.status(400).send("Invalid URL Or Missing");
} }
logger.info(`Info request received. RAW URL: ${url}`); logger.info(`Info request received. URL: ${url}`);
logger.info(`Info request received. ENCODED: ${encodedUrl}`);
// Lightweight heuristic to detect playlist URLs. It works for Youtube. // Lightweight heuristic to detect playlist URLs
const isPlaylistUrl = url.includes("&list") || url.includes("?list"); const isPlaylistUrl = url.includes("&list") || url.includes("?list");
logger.info( logger.info(
@@ -89,16 +86,6 @@ async function infoController(req, res) {
*/ */
if (data._type === "playlist") { if (data._type === "playlist") {
// If the playlist got only one video, show it as a video.
if (data.entries && data.entries.length === 1) {
const singleVideo = parseVideo(data.entries[0]);
logger.info(`Playlist with 1 item converted to video: ${singleVideo.title}`);
return res.json(singleVideo);
}
const playlist = parsePlaylist(data); const playlist = parsePlaylist(data);
logger.info( logger.info(

View File

@@ -4,7 +4,6 @@ const getUserBrowser = require("./getBrowser.helpers.js");
const { ffmpegPath, denoPath} = require("./path.helpers.js"); const { ffmpegPath, denoPath} = require("./path.helpers.js");
const { configFeatures } = require("../../config.js"); const { configFeatures } = require("../../config.js");
const { logger } = require("../logger.js"); const { logger } = require("../logger.js");
const config = require("../../config")
/** /**
* Validates whether the provided codec is supported by the application. * Validates whether the provided codec is supported by the application.
@@ -63,7 +62,7 @@ function validateCodec(codec){
* *
* @returns {string[]} Array of arguments ready to be passed to yt-dlp. * @returns {string[]} Array of arguments ready to be passed to yt-dlp.
*/ */
function buildYtDlpArgs({url, audioOnly, quality, outputFolder, isPlaylist}) { function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
logger.info("--- CONFIGURATION ---"); logger.info("--- CONFIGURATION ---");
@@ -71,7 +70,6 @@ function buildYtDlpArgs({url, audioOnly, quality, outputFolder, isPlaylist}) {
logger.info(`CONFIG discordRPC: ${configFeatures.discordRPC}`); logger.info(`CONFIG discordRPC: ${configFeatures.discordRPC}`);
logger.info(`CONFIG customTopBar: ${configFeatures.customTopBar}`); logger.info(`CONFIG customTopBar: ${configFeatures.customTopBar}`);
logger.info(`CONFIG autoCheckInfo: ${configFeatures.autoCheckInfo}`); logger.info(`CONFIG autoCheckInfo: ${configFeatures.autoCheckInfo}`);
logger.info(`CONFIG keepPlaylistOrder: ${configFeatures.keepPlaylistOrder}`);
logger.info(`CONFIG addThumbnail: ${configFeatures.addThumbnail}`); logger.info(`CONFIG addThumbnail: ${configFeatures.addThumbnail}`);
logger.info(`CONFIG addMetadata: ${configFeatures.addMetadata}`); logger.info(`CONFIG addMetadata: ${configFeatures.addMetadata}`);
logger.info(`CONFIG verboseLogs: ${configFeatures.verboseLogs}`); logger.info(`CONFIG verboseLogs: ${configFeatures.verboseLogs}`);
@@ -119,27 +117,7 @@ function buildYtDlpArgs({url, audioOnly, quality, outputFolder, isPlaylist}) {
}; };
args.push("-f", qualityMap[quality] || "best"); args.push("-f", qualityMap[quality] || "best");
args.push("-o", path.join(outputFolder, "%(title)s.%(ext)s"));
/**
* Default title output template
* @type {string}
*/
let outputTemplate = "%(title)s.%(ext)s";
// If it's a playlist, and we want to keep the playlist order. Add an index before the title.
if (isPlaylist && configFeatures.keepPlaylistOrder && configFeatures.autoDownloadPlaylist) {
outputTemplate = "%(playlist_index)02d - %(title)s.%(ext)s";
}
// Add a custom signature in each file downloaded with the app. It's a small things but i found it cool :)
if (configFeatures.addMetadata) {
args.push(
"--postprocessor-args",
`ffmpeg:-metadata comment="Downloaded with Freedom Loader v${config.version}"`
);
}
args.push("-o", path.join(outputFolder, outputTemplate));
args.push(url); args.push(url);
return args.filter(Boolean); return args.filter(Boolean);

View File

@@ -22,8 +22,7 @@ const { logger } = require("../logger");
* @returns {string} The detected browser identifier. * @returns {string} The detected browser identifier.
*/ */
function getUserBrowser() { function getUserBrowser() {
//const userProfile = os.homedir(); const userProfile = os.homedir();
const firefoxPath = getFirefoxProfilePath();
/** /**
* Browsers supported by yt-dlp cookie extraction. * Browsers supported by yt-dlp cookie extraction.
@@ -33,7 +32,7 @@ function getUserBrowser() {
* once compatibility has been verified. * once compatibility has been verified.
*/ */
const browsers = [ const browsers = [
{ name: "firefox", path: firefoxPath }, { name: "firefox", path: path.join(userProfile, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles") },
// { name: "chrome", path: path.join(userProfile, "AppData", "Local", "Google", "Chrome", "User Data", "Default") }, // { name: "chrome", path: path.join(userProfile, "AppData", "Local", "Google", "Chrome", "User Data", "Default") },
// { name: "brave", path: path.join(userProfile, "AppData", "Local", "BraveSoftware", "Brave-Browser", "User Data", "Default") }, // { name: "brave", path: path.join(userProfile, "AppData", "Local", "BraveSoftware", "Brave-Browser", "User Data", "Default") },
// { name: "edge", path: path.join(userProfile, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default") }, // { name: "edge", path: path.join(userProfile, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default") },
@@ -45,7 +44,7 @@ function getUserBrowser() {
// Search for the first available browser profile. // Search for the first available browser profile.
for (const browser of browsers) { for (const browser of browsers) {
if (browser.path && fs.existsSync(browser.path)) { if (fs.existsSync(browser.path)) {
logger.info(`Browser found: ${browser.name}`); logger.info(`Browser found: ${browser.name}`);
return browser.name; return browser.name;
} }
@@ -53,50 +52,11 @@ function getUserBrowser() {
// No supported browser found => Notify User // No supported browser found => Notify User
logger.warn("No supported browser found on the system"); logger.warn("No supported browser found on the system");
// If you somehow managed to live without Firefox and need help installing it. - Don't applied to my Linux chad
if (process.platform === "win32") {
notify.notifyFirefoxBrowserMissing(); notify.notifyFirefoxBrowserMissing();
}
// Fallback to Firefox and let yt-dlp handle the error gracefully. // Fallback to Firefox and let yt-dlp handle the error gracefully.
// This prevents the application from crashing // This prevents the application from crashing
return "firefox"; return "firefox";
} }
function getFirefoxProfilePath() {
const home = os.homedir();
if (process.platform === "win32") {
return path.join(
home,
"AppData",
"Roaming",
"Mozilla",
"Firefox",
"Profiles"
);
}
if (process.platform === "linux") {
return path.join(
"usr",
"bin",
"firefox"
);
}
if (process.platform === "darwin") {
return path.join(
home,
"Library",
"Application Support",
"Firefox",
"Profiles"
);
}
return null;
}
module.exports = getUserBrowser module.exports = getUserBrowser

View File

@@ -2,12 +2,6 @@ const { Notification, shell } = require("electron");
const { iconPaths } = require("./path.helpers"); const { iconPaths } = require("./path.helpers");
const { logger } = require("../logger"); const { logger } = require("../logger");
/**
* Set that is holding all notifications. Used to avoid Race Condition and notification crash
* @type {Set<any>}
*/
const activeNotifications = new Set();
/** /**
* Displays a system notification when a download completes successfully. * Displays a system notification when a download completes successfully.
* *
@@ -27,27 +21,7 @@ function notifyDownloadFinished(folder, notifyEnabled = true) {
icon: iconPaths.confirm, icon: iconPaths.confirm,
}); });
// Protect notification to garbage collection and race condition, by adding a 150ms timeout before trying to open the folder notif.on("click", () => shell.openPath(folder));
activeNotifications.add(notif);
notif.on("click", () => {
activeNotifications.delete(notif);
setTimeout(() => {
shell.openPath(folder).then((errorMessage) => {
if (errorMessage) {
logger.error(`Impossible d'ouvrir le dossier : ${errorMessage}`);
}
}).catch(err => {
logger.error(`Erreur inattendue de shell.openPath : ${err}`);
});
}, 150);
});
notif.on("close", () => {
activeNotifications.delete(notif);
});
notif.show(); notif.show();
} }
@@ -67,16 +41,11 @@ function notifyCookiesBrowserError(){
icon: iconPaths.error, icon: iconPaths.error,
}); });
activeNotifications.add(notif); notif.on("click", () =>
shell.openExternal(
notif.on("click", () => { "https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"
shell.openExternal("https://www.firefox.com/en-US/download/"); )
activeNotifications.delete(notif); );
});
notif.on("close", () => {
activeNotifications.delete(notif);
});
notif.show(); notif.show();
} }
@@ -96,16 +65,11 @@ function notifyFirefoxBrowserMissing() {
icon: iconPaths.error, icon: iconPaths.error,
}); });
activeNotifications.add(notif); notif.on("click", () =>
shell.openExternal(
notif.on("click", () => { "https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"
shell.openExternal("https://www.firefox.com/en-US/download/"); )
activeNotifications.delete(notif); );
});
notif.on("close", () => {
activeNotifications.delete(notif);
});
notif.show(); notif.show();
} }

View File

@@ -1,47 +1,18 @@
const path = require("path"); const path = require("path");
const fs = require("fs"); const fs = require("fs");
const os = require("os");
const { app } = require("electron"); const { app } = require("electron");
const config = require("../../config.js"); const config = require("../../config.js");
/** const { logger } = require("../logger.js");
* Is the OS windows
*/
const isWindows = process.platform === 'win32'
/** // Centralized resource paths
* On windows the binaries are name.exe on Linux/macOS no
*/
const ext = isWindows ? '.exe' : ''
/** Centralized resource paths */
const resourcesPath = config.devMode const resourcesPath = config.devMode
? path.join(__dirname, "../../resources") ? path.join(__dirname, "../../ressources")
: process.resourcesPath; : process.resourcesPath;
/** Default download folder (centralized) */ // Default download folder (centralized)
const defaultDownloadFolder = path.join(app.getPath("downloads"), "Freedom Loader"); const defaultDownloadFolder = path.join(os.homedir(), "Downloads", "Freedom Loader");
/**
* Current platform:
* win32 | linux | darwin
*/
const platform = process.platform;
/**
* Dev source tree uses platform-named subfolders (matches resources/binaries/{win-32,linux,darwin}).
* Packaged builds flatten everything directly under binaries/, since each
* platform build only ships its own binaries — no subfolder needed.
*/
const devPlatformFolder = {
win32: "win-32",
linux: "linux",
darwin: "darwin"
}[platform];
const binariesPath = config.devMode
? path.join(resourcesPath, "binaries", devPlatformFolder)
: path.join(resourcesPath, "binaries");
/** /**
* Runtime-resolved binary paths. * Runtime-resolved binary paths.
@@ -53,10 +24,7 @@ let ffmpegPath;
let denoPath; let denoPath;
/** Source binary bundled inside application resources (used for first-time copy) */ /** Source binary bundled inside application resources (used for first-time copy) */
const sourceYtDlp = path.join( const sourceYtDlp = path.join(resourcesPath, "binaries","yt-dlp.exe");
binariesPath,
`yt-dlp${ext}`
);
/** /**
* Resolve binary locations depending on environment: * Resolve binary locations depending on environment:
@@ -64,47 +32,16 @@ const sourceYtDlp = path.join(
* - prod: extracted userData + packaged resources * - prod: extracted userData + packaged resources
*/ */
if (config.devMode) { if (config.devMode) {
userYtDlp = path.join( userYtDlp = path.join(__dirname, "../../ressources/yt-dlp.exe");
binariesPath, ffmpegPath = path.join(__dirname, "../../ressources/"); // <- has ffmpeg.exe and ffprobe.exe
`yt-dlp${ext}` denoPath = path.join(__dirname, "../../ressources/deno.exe");
);
ffmpegPath = path.join(
binariesPath,
`ffmpeg${ext}`
);
denoPath = path.join(
binariesPath,
`deno${ext}`
);
} else { } else {
userYtDlp = path.join( userYtDlp = path.join(app.getPath("userData"),"yt-dlp.exe");
app.getPath("userData"), ffmpegPath = path.join(resourcesPath, "binaries","ffmpeg.exe");
`yt-dlp${ext}` denoPath = path.join(resourcesPath, "binaries","deno.exe");
);
ffmpegPath = path.join( if (!fs.existsSync(userYtDlp)) {
binariesPath,
`ffmpeg${ext}`
);
denoPath = path.join(
binariesPath,
`deno${ext}`
);
if (!fs.existsSync(userYtDlp) && fs.existsSync(sourceYtDlp)) {
fs.copyFileSync(sourceYtDlp, userYtDlp); fs.copyFileSync(sourceYtDlp, userYtDlp);
// Linux/macOS executable permission
if (!isWindows) {
try {
fs.chmodSync(userYtDlp, 0o755);
} catch (err) {
getLogger().warn(`Failed to chmod yt-dlp: ${err.message}`);
}
}
} }
} }
@@ -123,25 +60,10 @@ const iconPaths = {
* Used mainly for verification and diagnostics. * Used mainly for verification and diagnostics.
*/ */
const binaryPaths = { const binaryPaths = {
ytDlp: path.join( ytDlp: path.join(resourcesPath, "binaries", "yt-dlp.exe"),
binariesPath, ffmpeg: path.join(resourcesPath, "binaries", "ffmpeg.exe"),
`yt-dlp${ext}` ffprobe: path.join(resourcesPath, "binaries", "ffprobe.exe"),
), deno: path.join(resourcesPath, "binaries", "deno.exe")
ffmpeg: path.join(
binariesPath,
`ffmpeg${ext}`
),
ffprobe: path.join(
binariesPath,
`ffprobe${ext}`
),
deno: path.join(
binariesPath,
`deno${ext}`
)
}; };
/** /**
@@ -150,7 +72,7 @@ const binaryPaths = {
* - user themes are persisted in userData to survive updates * - user themes are persisted in userData to survive updates
*/ */
const userThemesPath = path.join(app.getPath("userData"), "themes"); const userThemesPath = path.join(app.getPath("userData"), "themes");
const defaultThemesSourcePath = path.join("theme"); const defaultThemesSourcePath = path.join(resourcesPath, "theme");
/** /**
* Initializes user theme directory and ensures default themes exist. * Initializes user theme directory and ensures default themes exist.
@@ -163,48 +85,42 @@ function initUserThemes() {
try { try {
if (!fs.existsSync(userThemesPath)) { if (!fs.existsSync(userThemesPath)) {
fs.mkdirSync(userThemesPath, { recursive: true }); fs.mkdirSync(userThemesPath, { recursive: true });
// Ensure folder is writable
fs.chmodSync(userThemesPath, 0o777); fs.chmodSync(userThemesPath, 0o777);
getLogger().info("Created user themes directory"); logger.info("Created user themes directory");
} }
// Copy default themes from resources to user directory if they don't exist
if (fs.existsSync(defaultThemesSourcePath)) { if (fs.existsSync(defaultThemesSourcePath)) {
const defaultThemes = fs.readdirSync(defaultThemesSourcePath); const defaultThemes = fs.readdirSync(defaultThemesSourcePath);
for (const theme of defaultThemes) { for (const theme of defaultThemes) {
const srcPath = path.join(defaultThemesSourcePath, theme); const srcPath = path.join(defaultThemesSourcePath, theme);
const destPath = path.join(userThemesPath, theme); const destPath = path.join(userThemesPath, theme);
const srcIsFile = !fs.statSync(srcPath).isDirectory();
if (fs.existsSync(destPath)) {
const destIsDir = fs.statSync(destPath).isDirectory();
if (srcIsFile && destIsDir) {
getLogger().warn(`Corrupted theme entry: ${theme}, fixing...`);
fs.rmSync(destPath, { recursive: true, force: true });
} else {
continue;
}
}
if (!fs.existsSync(destPath)) {
try { try {
if (srcIsFile) {
fs.copyFileSync(srcPath, destPath);
fs.chmodSync(destPath, 0o666);
} else {
copyDirectory(srcPath, destPath); copyDirectory(srcPath, destPath);
fs.chmodSync(destPath, 0o777); fs.chmodSync(destPath, 0o777);
} logger.info(`Copied default theme: ${theme}`);
getLogger().info(`Copied default theme: ${theme}`);
} catch (copyErr) { } catch (copyErr) {
getLogger().warn(`Failed to copy theme ${theme}: ${copyErr.message}`); logger.warn(`Failed to copy theme ${theme}: ${copyErr.message}. Theme folder will be created, add theme files manually.`);
try {
fs.mkdirSync(destPath, { recursive: true });
fs.chmodSync(destPath, 0o777);
} catch (mkdirErr) {
logger.warn(`Could not create theme directory ${theme}: ${mkdirErr.message}`);
}
}
} }
} }
} else { } else {
getLogger().warn(`Default themes source path not found: ${defaultThemesSourcePath}`); logger.warn(`Default themes source path not found: ${defaultThemesSourcePath}`);
} }
} catch (err) { } catch (err) {
getLogger().error(`Failed to initialize user themes: ${err.message}`); logger.error(`Failed to initialize user themes: ${err.message}`);
} }
} }
/** /**
* Recursively copies a directory and its contents. * Recursively copies a directory and its contents.
* *
@@ -215,14 +131,10 @@ function initUserThemes() {
function copyDirectory(src, dest) { function copyDirectory(src, dest) {
try { try {
if (!fs.existsSync(dest)) { if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { fs.mkdirSync(dest, { recursive: true, mode: 0o777 });
recursive: true,
mode: 0o777
});
} }
const files = fs.readdirSync(src); const files = fs.readdirSync(src);
for (const file of files) { for (const file of files) {
const srcFile = path.join(src, file); const srcFile = path.join(src, file);
const destFile = path.join(dest, file); const destFile = path.join(dest, file);
@@ -239,31 +151,8 @@ function copyDirectory(src, dest) {
} }
// Runtime validation of critical binaries // Runtime validation of critical binaries
// if (!userYtDlp) { if (!userYtDlp){ logger.error("Missing yt-dlp binary")}
// getLogger().error("Missing yt-dlp binary"); if (!ffmpegPath){ logger.error("Missing ffmpeg binary")}
// } if (!denoPath){ logger.error("Missing deno binary")}
// if (!ffmpegPath) { module.exports = { userYtDlp, ffmpegPath, denoPath, iconPaths, binaryPaths, resourcesPath, defaultDownloadFolder, userThemesPath, initUserThemes };
// getLogger().error("Missing ffmpeg binary");
// }
// if (!denoPath) {
// getLogger().error("Missing deno binary");
// }
function validateBinaries() {
if (!userYtDlp) getLogger().error("Missing yt-dlp binary");
if (!ffmpegPath) getLogger().error("Missing ffmpeg binary");
if (!denoPath) getLogger().error("Missing deno binary");
}
/**
* Helper to lazy load logger, avoid circular import
* @returns {winston.Logger}
*/
function getLogger() {
return require("../logger.js").logger;
}
module.exports = { userYtDlp, ffmpegPath, denoPath, iconPaths, binaryPaths, resourcesPath, defaultDownloadFolder, userThemesPath, initUserThemes, isWindows, validateBinaries };

View File

@@ -4,11 +4,9 @@ const fs = require("fs");
const path = require("path"); const path = require("path");
const os = require("os"); const os = require("os");
const config = require("../config"); const config = require("../config");
const {isWindows} = require("./helpers/path.helpers");
const { logSystemInfo } = require("./sysinfo");
// Logs folder in Windows // Logs folder in Windows
const logDir = isWindows ? path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs") : path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share"), "FreedomLoader", "logs"); const logDir = path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs");
// Create "Logs" folder if needed // Create "Logs" folder if needed
try { try {
@@ -22,31 +20,6 @@ const logFormat = format.combine(
format.printf(({ timestamp, level, message }) => `${timestamp} | ${level.toUpperCase()} | ${message}`) format.printf(({ timestamp, level, message }) => `${timestamp} | ${level.toUpperCase()} | ${message}`)
); );
/**
* Format used by the saved file
* @type {Format}
*/
const fileFormat = format.combine(
format.timestamp({format: "YYYY-MM-DD HH:mm:ss"}),
format.errors({stack: true}), // Capture la stack trace complète des erreurs
format.printf(({timestamp, level, message, stack}) => {
return `${timestamp} | ${level.toUpperCase()} | ${stack || message}`;
})
);
/**
* Formated used by the IDE/Console
* @type {Format}
*/
const consoleFormat = format.combine(
format.colorize({all: true}),
format.timestamp({format: "HH:mm:ss"}),
format.errors({stack: true}),
format.printf(({timestamp, level, message, stack}) => {
return `[${timestamp}] ${level}: ${stack || message}`;
})
);
/** /**
* Logger Instance with differents types * Logger Instance with differents types
* - info * - info
@@ -58,6 +31,7 @@ const consoleFormat = format.combine(
*/ */
const logger = createLogger({ const logger = createLogger({
level: "info", level: "info",
format: logFormat,
transports: [ transports: [
new DailyRotateFile({ new DailyRotateFile({
dirname: logDir, dirname: logDir,
@@ -65,11 +39,11 @@ const logger = createLogger({
datePattern: "YYYY-MM-DD", datePattern: "YYYY-MM-DD",
zippedArchive: false, zippedArchive: false,
maxFiles: "7d", maxFiles: "7d",
format: fileFormat, format: logFormat,
options: { flags: "a" }, options: { flags: "a" },
}), }),
new transports.Console({ new transports.Console({
format: consoleFormat, format: logFormat,
}), }),
], ],
}); });
@@ -77,18 +51,16 @@ const logger = createLogger({
/** /**
* Start Log Session * Start Log Session
*/ */
function logSessionStart(logDir, downloadPath) { function logSessionStart() {
logger.info(`--- Starting session: ${new Date().toLocaleString('sv-SE')} ---`); logger.info(`--- Starting session: ${new Date().toISOString()} ---`);
logger.info("============================================================")
logger.info(`Application Version: ${config.version}`) logger.info(`Application Version: ${config.version}`)
if (typeof logSystemInfo === 'function') logSystemInfo(logger, logDir, downloadPath);
} }
/** /**
* Stop Log Session * Stop Log Session
*/ */
function logSessionEnd() { function logSessionEnd() {
logger.info(`--- Ending session: ${new Date().toLocaleString('sv-SE')} ---`); logger.info(`--- Ending session: ${new Date().toISOString()} ---`);
} }
module.exports = { module.exports = {

View File

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

View File

@@ -14,8 +14,6 @@ app.use(express.static(path.join(__dirname, "../public")));
// Routes // Routes
app.use("/download", require("./routes/download.route")); app.use("/download", require("./routes/download.route"));
app.use("/info", require("./routes/info.route")); app.use("/info", require("./routes/info.route"));
// Interface
app.get("/", rateLimit, (req, res) => { app.get("/", rateLimit, (req, res) => {
res.sendFile(path.join(__dirname, "../public/index.html")); res.sendFile(path.join(__dirname, "../public/index.html"));
}); });

View File

@@ -6,7 +6,7 @@ const { buildYtDlpArgs } = require("../helpers/buildArgs.helpers");
const notify = require("../helpers/notify.helpers"); const notify = require("../helpers/notify.helpers");
const path = require("path"); const path = require("path");
const { isSafePath } = require("../helpers/validation.helpers"); const { isSafePath } = require("../helpers/validation.helpers");
const {reloadFeatures} = require("../../config"); const { configFeatures } = require("../../config.js");
/** /**
* Reference to the currently running yt-dlp process. * Reference to the currently running yt-dlp process.
@@ -123,7 +123,7 @@ function createPlaylistFolder(basePath, playlistTitle) {
* @param {string} [options.outputFolder] - Destination folder. * @param {string} [options.outputFolder] - Destination folder.
* @param {string} [options.playlistTitle] - Playlist title. * @param {string} [options.playlistTitle] - Playlist title.
* *
* @param {Function[]} progressListeners - Progress listeners. * @param {Function[]} listeners - Progress listeners.
* @param {Function[]} speedListeners - Download speed listeners. * @param {Function[]} speedListeners - Download speed listeners.
* @param {Function[]} stageListeners - Processing stage listeners. * @param {Function[]} stageListeners - Processing stage listeners.
* @param {Function[]} playlistInfoListeners - Playlist progress listeners. * @param {Function[]} playlistInfoListeners - Playlist progress listeners.
@@ -132,13 +132,10 @@ function createPlaylistFolder(basePath, playlistTitle) {
* Resolves with the final output folder path once the * Resolves with the final output folder path once the
* download has completed successfully. * download has completed successfully.
*/ */
function fetchDownload(options, progressListeners, speedListeners, stageListeners, playlistInfoListeners) { function fetchDownload(options, listeners, speedListeners, stageListeners, playlistInfoListeners) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
logger.info(`CONFIG createPlaylistFolders: ${configFeatures.createPlaylistFolders}`);
const userConfig = reloadFeatures();
logger.info(`CONFIG createPlaylistFolders: ${userConfig.createPlaylistFolders}`);
let outputFolder = options.outputFolder || defaultDownloadFolder; let outputFolder = options.outputFolder || defaultDownloadFolder;
@@ -162,7 +159,7 @@ function fetchDownload(options, progressListeners, speedListeners, stageListener
// Détecte si c'est une playlist et crée un dossier approprié // Détecte si c'est une playlist et crée un dossier approprié
const isPlaylist = options.playlistTitle || isPlaylistUrl(options.url); const isPlaylist = options.playlistTitle || isPlaylistUrl(options.url);
if (isPlaylist && userConfig.createPlaylistFolders) { if (isPlaylist && configFeatures.createPlaylistFolders) {
try { try {
const playlistName = options.playlistTitle || "Untitled Playlist"; const playlistName = options.playlistTitle || "Untitled Playlist";
safeOutputFolder = createPlaylistFolder(safeOutputFolder, playlistName); safeOutputFolder = createPlaylistFolder(safeOutputFolder, playlistName);
@@ -171,16 +168,11 @@ function fetchDownload(options, progressListeners, speedListeners, stageListener
logger.error(`Failed to create playlist folder: ${err.message}`); logger.error(`Failed to create playlist folder: ${err.message}`);
return reject(err); return reject(err);
} }
} else if (isPlaylist && !userConfig.createPlaylistFolders) { } else if (isPlaylist && !configFeatures.createPlaylistFolders) {
logger.info(`Playlist detected but createPlaylistFolders is disabled, using base folder`); logger.info(`Playlist detected but createPlaylistFolders is disabled, using base folder`);
} }
const args = buildYtDlpArgs({ const args = buildYtDlpArgs({ ...options, outputFolder: safeOutputFolder });
...options,
outputFolder: safeOutputFolder,
isPlaylist
});
logger.info(`[yt-dlp args] ${args.join(" ")}`); logger.info(`[yt-dlp args] ${args.join(" ")}`);
const child = execFile(userYtDlp, args); const child = execFile(userYtDlp, args);
@@ -190,7 +182,7 @@ function fetchDownload(options, progressListeners, speedListeners, stageListener
child.on("close", code => { child.on("close", code => {
currentDownloadProcess = null; currentDownloadProcess = null;
progressListeners.forEach(fn => fn("done")); listeners.forEach(fn => fn("done"));
if (code === 0) resolve(safeOutputFolder); if (code === 0) resolve(safeOutputFolder);
else if (code === null) reject(new Error(`Download cancelled by user`)); else if (code === null) reject(new Error(`Download cancelled by user`));
else reject(new Error(`YT-DLP failed with code : ${code}`)); else reject(new Error(`YT-DLP failed with code : ${code}`));
@@ -204,11 +196,11 @@ function fetchDownload(options, progressListeners, speedListeners, stageListener
// Progress Bar // Progress Bar
if (line.startsWith("[download] Destination:")) { if (line.startsWith("[download] Destination:")) {
progressListeners.forEach(fn => fn("reset")); listeners.forEach(fn => fn("reset"));
stageListeners.forEach(fn => fn("Downloading...")); stageListeners.forEach(fn => fn("Downloading..."));
} }
const match = line.match(/\[download\]\s+(\d+\.\d+)%/); const match = line.match(/\[download\]\s+(\d+\.\d+)%/);
if (match) progressListeners.forEach(fn => fn(parseFloat(match[1]))); if (match) listeners.forEach(fn => fn(parseFloat(match[1])));
if (line.includes("MiB/s") || line.includes("KiB/s")) { if (line.includes("MiB/s") || line.includes("KiB/s")) {
const match = line.match(/\d+(\.\d+)?[KM]?iB\/s/); const match = line.match(/\d+(\.\d+)?[KM]?iB\/s/);

View File

@@ -1,142 +0,0 @@
const os = require("os");
const fs = require("fs");
const { execSync } = require("child_process");
const { app } = require("electron");
/**
* Reads /etc/os-release to get the Linux distribution name
*/
function getLinuxDistro() {
try {
const content = fs.readFileSync("/etc/os-release", "utf-8");
const pretty = content.match(/^PRETTY_NAME="?(.+?)"?$/m);
if (pretty) return pretty[1];
const name = content.match(/^NAME="?(.+?)"?$/m);
return name ? name[1] : "Unknown Linux";
} catch {
return "Unknown Linux";
}
}
/**
* Reads the package-type file written by electron-builder
* to determine how the app was distributed (rpm, snap, AppImage, deb...)
*/
function getPackageType() {
try {
const pkgTypePath = require("path").join(process.resourcesPath, "package-type");
if (fs.existsSync(pkgTypePath)) {
return fs.readFileSync(pkgTypePath, "utf-8").trim();
}
} catch { /* ignore */ }
return "unknown";
}
/**
* Checks if Firefox is installed on the current platform
*/
function detectFirefox() {
const platform = process.platform;
if (platform === "win32") {
const paths = [
"C:\\Program Files\\Mozilla Firefox\\firefox.exe",
"C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe",
`${process.env.LOCALAPPDATA}\\Mozilla Firefox\\firefox.exe`,
];
return paths.some(p => fs.existsSync(p));
}
if (platform === "linux") {
const paths = [
"/usr/bin/firefox",
"/usr/bin/firefox-esr",
"/usr/local/bin/firefox",
"/snap/bin/firefox",
"/var/lib/flatpak/exports/bin/org.mozilla.firefox",
`${os.homedir()}/.local/share/flatpak/exports/bin/org.mozilla.firefox`,
];
if (paths.some(p => fs.existsSync(p))) return true;
try { execSync("which firefox", { stdio: "ignore" }); return true; } catch { return false; }
}
return false;
}
/**
* Builds the system info object logged at startup
*
* @param {string} logDir - path to the log directory
* @param {string} downloadPath - current configured download path
*/
function getSystemInfo(logDir, downloadPath) {
const platform = process.platform;
const cpus = os.cpus();
const totalMem = (os.totalmem() / 1024 / 1024 / 1024).toFixed(1);
const freeMem = (os.freemem() / 1024 / 1024 / 1024).toFixed(1);
let platformLabel;
if (platform === "win32") {
platformLabel = `Windows (${os.release()})`;
} else if (platform === "linux") {
platformLabel = `Linux — ${getLinuxDistro()}`;
} else {
platformLabel = `${platform} ${os.release()} (unsupported)`;
}
const info = {
"Platform": platformLabel,
"Architecture": os.arch(),
"CPU": cpus.length ? `${cpus[0].model.trim()} (${cpus.length} cores)` : "Unknown",
"Memory": `${freeMem} GB free / ${totalMem} GB total`,
"Locale": app.getLocale(),
"Electron": process.versions.electron,
"Node": process.versions.node,
"Packaged": String(app.isPackaged),
"Package type": app.isPackaged ? getPackageType() : "dev",
"Firefox present": detectFirefox() ? "Yes" : "No — cookie features unavailable",
"Log directory": logDir,
"Download path": downloadPath,
};
// Linux-specific extras
if (platform === "linux") {
const sessionType = process.env.XDG_SESSION_TYPE || "unknown";
const waylandDisplay = process.env.WAYLAND_DISPLAY;
const x11Display = process.env.DISPLAY;
info["Display server"] = waylandDisplay
? `Wayland (${waylandDisplay})`
: x11Display
? `X11 (${x11Display})`
: sessionType !== "unknown"
? sessionType
: "unknown";
info["Desktop env"] = process.env.XDG_CURRENT_DESKTOP || process.env.DESKTOP_SESSION || "unknown";
}
return info;
}
/**
* Logs system info at application startup
*
* @param {object} logger - winston logger instance
* @param {string} logDir - path to the log directory
* @param {string} downloadPath - current configured download path
*/
function logSystemInfo(logger, logDir, downloadPath) {
const info = getSystemInfo(logDir, downloadPath);
const maxKeyLen = Math.max(...Object.keys(info).map(k => k.length));
logger.info("=".repeat(60));
logger.info("SYSTEM INFO");
logger.info("=".repeat(60));
for (const [key, val] of Object.entries(info)) {
logger.info(` ${key.padEnd(maxKeyLen)} : ${val}`);
}
logger.info("=".repeat(60));
}
module.exports = { logSystemInfo };

View File

@@ -1,182 +0,0 @@
const path = require("path");
jest.mock("../../server/helpers/getBrowser.helpers.js", () =>
jest.fn(() => "firefox")
);
jest.mock("../../server/helpers/path.helpers.js", () => ({
ffmpegPath: "/mock/ffmpeg",
denoPath: "/mock/deno",
}));
jest.mock("../../config.js", () => ({
configFeatures: {
autoUpdate: false,
discordRPC: false,
customTopBar: false,
autoCheckInfo: false,
addThumbnail: true,
addMetadata: true,
verboseLogs: false,
autoDownloadPlaylist: false,
customCodec: "h264",
},
}));
jest.mock("../../server/logger.js", () => ({
logger: {
info: jest.fn(),
error: jest.fn(),
},
}));
const { buildYtDlpArgs } = require("../../server/helpers/buildArgs.helpers");
const { configFeatures } = require("../../config");
const { logger } = require("../../server/logger");
describe("buildYtDlpArgs", () => {
beforeEach(() => {
jest.clearAllMocks();
});
test("builds default video arguments", () => {
const args = buildYtDlpArgs({
url: "https://youtube.com/watch?v=123",
audioOnly: false,
quality: "best",
outputFolder: "/downloads",
});
expect(args).toContain("--cookies-from-browser");
expect(args).toContain("firefox");
expect(args).toContain("--ffmpeg-location");
expect(args).toContain("/mock/ffmpeg");
expect(args).toContain("--js-runtimes");
expect(args).toContain("deno:/mock/deno");
expect(args).toContain("--merge-output");
expect(args).toContain("mp4");
expect(args).toContain("-f");
expect(args).toContain("bestvideo+bestaudio/best/mp4");
expect(args).toContain(
path.join("/downloads", "%(title)s.%(ext)s")
);
expect(args.at(-1)).toBe("https://youtube.com/watch?v=123");
});
test("builds audio-only arguments", () => {
const args = buildYtDlpArgs({
url: "https://youtube.com/watch?v=123",
audioOnly: true,
quality: "best",
outputFolder: "/downloads",
});
expect(args).toContain("--extract-audio");
expect(args).toContain("--audio-format");
expect(args).toContain("mp3");
expect(args).toContain("bestaudio");
expect(args).not.toContain("--merge-output");
});
test("uses requested quality", () => {
const args = buildYtDlpArgs({
url: "https://youtube.com/watch?v=123",
audioOnly: false,
quality: "720",
outputFolder: "/downloads",
});
expect(args).toContain(
"bestvideo[height<=720]+bestaudio/best[height<=720]/mp4"
);
});
test("falls back to best for unknown quality", () => {
const args = buildYtDlpArgs({
url: "https://youtube.com/watch?v=123",
audioOnly: false,
quality: "unknown",
outputFolder: "/downloads",
});
expect(args).toContain("best");
});
test("uses --no-playlist by default", () => {
const args = buildYtDlpArgs({
url: "https://youtube.com/watch?v=123",
audioOnly: false,
quality: "best",
outputFolder: "/downloads",
});
expect(args).toContain("--no-playlist");
expect(args).not.toContain("--yes-playlist");
});
test("uses --yes-playlist when enabled", () => {
configFeatures.autoDownloadPlaylist = true;
const args = buildYtDlpArgs({
url: "https://youtube.com/watch?v=123",
audioOnly: false,
quality: "best",
outputFolder: "/downloads",
});
expect(args).toContain("--yes-playlist");
expect(args).not.toContain("--no-playlist");
configFeatures.autoDownloadPlaylist = false;
});
test("embeds thumbnail and metadata when enabled", () => {
const args = buildYtDlpArgs({
url: "https://youtube.com/watch?v=123",
audioOnly: false,
quality: "best",
outputFolder: "/downloads",
});
expect(args).toContain("--embed-thumbnail");
expect(args).toContain("--add-metadata");
});
test("logs codec validation", () => {
buildYtDlpArgs({
url: "https://youtube.com/watch?v=123",
audioOnly: false,
quality: "best",
outputFolder: "/downloads",
});
expect(logger.info).toHaveBeenCalledWith("Codec Valid: h264");
});
test("falls back to h264 when codec is invalid", () => {
configFeatures.customCodec = "invalid";
const args = buildYtDlpArgs({
url: "https://youtube.com/watch?v=123",
audioOnly: false,
quality: "best",
outputFolder: "/downloads",
});
expect(logger.error).toHaveBeenCalledWith(
"Codec not valid: invalid. Using default codec"
);
const sortIndex = args.indexOf("-S");
expect(args[sortIndex + 1]).toContain("vcodec:h264");
configFeatures.customCodec = "h264";
});
});

View File

@@ -1,133 +0,0 @@
const mockShow = jest.fn();
const mockOn = jest.fn();
const mockOpenPath = jest.fn();
const mockOpenExternal = jest.fn();
jest.mock("electron", () => ({
Notification: jest.fn().mockImplementation(() => ({
show: mockShow,
on: mockOn,
})),
shell: {
openPath: mockOpenPath,
openExternal: mockOpenExternal,
},
}));
jest.mock("../../server/helpers/path.helpers", () => ({
iconPaths: {
confirm: "/icons/confirm.png",
error: "/icons/error.png",
},
}));
jest.mock("../../server/logger", () => ({
logger: {
info: jest.fn(),
error: jest.fn(),
},
}));
const { Notification, shell } = require("electron");
const {
notifyDownloadFinished,
notifyCookiesBrowserError,
notifyFirefoxBrowserMissing,
} = require("../../server/helpers/notify.helpers");
describe("notify.helpers", () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe("notifyDownloadFinished", () => {
test("does nothing when notifications are disabled", () => {
notifyDownloadFinished("/downloads", false);
expect(Notification).not.toHaveBeenCalled();
});
test("does nothing when folder is missing", () => {
notifyDownloadFinished(undefined, true);
expect(Notification).not.toHaveBeenCalled();
});
test("creates and shows notification", () => {
notifyDownloadFinished("/downloads");
expect(Notification).toHaveBeenCalledWith({
title: "Freedom Loader",
body: "Your download is complete, click here to open it.",
icon: "/icons/confirm.png",
});
expect(mockOn).toHaveBeenCalledWith(
"click",
expect.any(Function)
);
expect(mockShow).toHaveBeenCalled();
});
test("opens folder when notification is clicked", () => {
notifyDownloadFinished("/downloads");
const callback = mockOn.mock.calls[0][1];
callback();
expect(shell.openPath).toHaveBeenCalledWith("/downloads");
});
});
describe("notifyCookiesBrowserError", () => {
test("creates notification", () => {
notifyCookiesBrowserError();
expect(Notification).toHaveBeenCalledWith({
title: "Cookies Error",
body: "Unable to retrieve cookies. Please log in to your browser and click here to view the tutorial.",
icon: "/icons/error.png",
});
expect(mockShow).toHaveBeenCalled();
});
test("opens tutorial when clicked", () => {
notifyCookiesBrowserError();
const callback = mockOn.mock.calls[0][1];
callback();
expect(shell.openExternal).toHaveBeenCalledWith(
"https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"
);
});
});
describe("notifyFirefoxBrowserMissing", () => {
test("creates notification", () => {
notifyFirefoxBrowserMissing();
expect(Notification).toHaveBeenCalledWith({
title: "Firefox Missing",
body: "Firefox was not found on your system. Click here to follow the installation guide",
icon: "/icons/error.png",
});
expect(mockShow).toHaveBeenCalled();
});
test("opens installation guide when clicked", () => {
notifyFirefoxBrowserMissing();
const callback = mockOn.mock.calls[0][1];
callback();
expect(shell.openExternal).toHaveBeenCalledWith(
"https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"
);
});
});
});

Binary file not shown.

Binary file not shown.