Compare commits

..

47 Commits

Author SHA1 Message Date
MasterAcnolo
7dee94da6f refactor: extract CHANGELOG into a separated file. Should avoid characters issue 2026-09-11 11:01:12 +02:00
MasterAcnolo
cd43ae10fc fix: bump app version to 1.6.3 since it's no longer a preview 2026-09-10 21:36:33 +02:00
Axel Nicolas
6978277b4b Update codeql.yml 2026-09-10 15:13:02 +02:00
MasterAcnolo
b91c20d450 fix: release.sh release message ending 2026-09-10 15:09:54 +02:00
Axel Nicolas
014b1ad52a Update codeql.yml 2026-09-10 15:09:19 +02:00
Axel Nicolas
ec93d20a39 Create codeql.yml 2026-09-10 15:06:52 +02:00
MasterAcnolo
09c511fcb8 fix: docs in path.helpers 2026-09-10 15:01:50 +02:00
MasterAcnolo
f343fbd148 fix(discord-rpc): remove unusued code 2026-09-10 15:00:00 +02:00
Axel Nicolas
6d99d5f91c Update README.md 2026-09-10 13:55:09 +02:00
MasterAcnolo
0f0bb874bf feat: add logs when minimize and maximize (tray and not) 2026-09-10 12:01:07 +02:00
MasterAcnolo
b9267ec408 fix: enhance package.json with metadata used by electron-builder on Linux 2026-09-10 12:00:20 +02:00
MasterAcnolo
311bff6df5 refactor: systemTray window logic. Window reference is now dynamic and guarded against destroyed states. Minimize can now put window in the systemTray if option is active 2026-09-10 10:27:11 +02:00
MasterAcnolo
e4243a01ae fix: devMode logic 2026-09-10 10:25:03 +02:00
MasterAcnolo
52be9c6b4f update: electron from 43.3.0 to 44.3.0 2026-09-10 10:23:29 +02:00
MasterAcnolo
e541eb442a fix:
- replace unusued throw new Error by logger.error
- remove unusued variables isWindows, err
- remove unusued logFormat
- remove unusued imports logSessionEnd
2026-09-09 09:21:11 +02:00
MasterAcnolo
e2c2dde4a2 fix: README.md 2026-09-09 08:59:55 +02:00
MasterAcnolo
ed4022dfd1 fix: website link in README 2026-09-04 20:36:03 +02:00
MasterAcnolo
fcbc3f95ac fix: remove old and unusued icons 2026-09-03 20:56:04 +02:00
MasterAcnolo
f3293ef89a fix: stopServer message logic 2026-09-01 16:17:58 +02:00
MasterAcnolo
05e864b93a refactor: getBrowser function, and better firefox management on linux 2026-08-22 22:29:38 +02:00
MasterAcnolo
8ec901dc07 feat: centralize isPlaylistUrl function as a helper that could be upgraded in the future 2026-08-22 22:09:01 +02:00
MasterAcnolo
104500a82a fix: english miss spell 2026-08-21 20:20:12 +02:00
MasterAcnolo
3444f2fc0b Fix: default config in README that was not accurate 2026-08-20 19:39:21 +02:00
MasterAcnolo
86b595beff Fix: default config in README that was not accurate 2026-08-20 19:33:08 +02:00
MasterAcnolo
ed6efcf812 fix: toggle to false the createPlaylistFolder feature 2026-08-19 19:44:56 +02:00
MasterAcnolo
2c84ac7a70 fix: metadata comment displayed version 2026-08-19 17:31:36 +02:00
Axel Nicolas
3c649173a2 Update CONTRIBUTING.md 2026-08-16 10:56:54 +02:00
Axel Nicolas
b4d4ff2665 Merge pull request #47 from MasterAcnolo/refactor-back
Documentation Enhancement
2026-08-16 10:54:30 +02:00
MasterAcnolo
c2584b6df3 fix: remove SUPPORT section 2026-08-16 10:51:26 +02:00
MasterAcnolo
ec16b5209d fix: reduce size of package.json 2026-08-16 10:50:12 +02:00
MasterAcnolo
f2f1649493 add: small notes about build:all script 2026-08-16 10:50:01 +02:00
MasterAcnolo
e5076b0e0b remove: project structure from README 2026-08-16 10:37:42 +02:00
MasterAcnolo
4573917541 add: DEVELOPMENT.md file and refactor contributing and README 2026-08-16 10:36:36 +02:00
MasterAcnolo
62c109939a fix: bump version to 1.6.3-preview and add a check in release.sh, we could'nt publish a release if it's still marked as a preview 2026-08-16 10:03:27 +02:00
MasterAcnolo
b56ec1e62e refactor: some tweaks in .github md templates 2026-08-16 09:58:32 +02:00
MasterAcnolo
1a68c58ee3 refactor: extract shutdown logic in a function, rename server => serverInstance and now we stop the server smoothly 2026-08-16 09:47:25 +02:00
MasterAcnolo
39c4a69be1 add: notes in BINARIES.md about yt-dlp 2026-08-16 09:06:35 +02:00
MasterAcnolo
1993566040 fix: undo last commit 2026-08-15 22:41:17 +02:00
MasterAcnolo
2d19a35ef8 fix: missing element and rename snap to snapcraft in package.json 2026-08-15 22:31:51 +02:00
MasterAcnolo
02c39d53b6 update: version because it's no longer a preview ! 2026-08-15 22:23:31 +02:00
MasterAcnolo
26059aa436 fix: change dark.zip quotes 2026-08-15 21:46:33 +02:00
MasterAcnolo
524cee1bac refactor: topbar.js name and order. And remove 'customconfiguration' declaration in index.html 2026-08-15 21:31:07 +02:00
MasterAcnolo
4de05fcd52 fix: rename appVersion to displayVersionLabel to enhance readability 2026-08-15 21:30:32 +02:00
MasterAcnolo
c2158d99cd fix: remove duplicate import of /config 2026-08-15 21:30:03 +02:00
MasterAcnolo
d84e63fb56 remove: useless code that was used for disable theme on Linux 2026-08-15 18:48:26 +02:00
MasterAcnolo
12d77abbd3 fix: discordRPC error display. I voluntarily disabled raw error printing, because it could have false positive. Could be reenable on purpose 2026-08-15 17:27:50 +02:00
MasterAcnolo
398de8113d fix: the defaultThemesRessourcesPath variable was aiming to and old path, causing warn at every launch 2026-08-15 17:27:08 +02:00
38 changed files with 748 additions and 537 deletions

View File

@@ -29,6 +29,9 @@ Clear description of the performance issue.
- **Storage**: [e.g. SSD/HDD] - **Storage**: [e.g. SSD/HDD]
- **Other Applications Opened**: [e.g. Discord/Opera GX] - **Other Applications Opened**: [e.g. Discord/Opera GX]
> [!INFO]
> At the start of the application, SystemInfo is printed in the logs, so you can just copy and paste it!
## Reproduction Details ## Reproduction Details
- **Download URL**: [if applicable] - **Download URL**: [if applicable]

View File

@@ -9,8 +9,8 @@ assignees: 'MasterAcnolo'
## Pre-submission Checklist ## Pre-submission Checklist
- [ ] I have checked the [FAQ](https://masteracnolo.github.io/Freedom-Loader-Site/pages/faq) - [ ] I have checked the [FAQ](https://masteracnolo.github.io/Freedom-Loader-Site/#/faq)
- [ ] I have reviewed the [Wiki](https://masteracnolo.github.io/Freedom-Loader-Site/pages/wiki) - [ ] I have reviewed the [Wiki](https://masteracnolo.github.io/Freedom-Loader-Site/#/wiki)
- [ ] I have searched existing issues - [ ] I have searched existing issues
## Question ## Question

View File

@@ -2,7 +2,7 @@
Brief description of what this PR does. Brief description of what this PR does.
## Related Issue ## Related Issue (if applicable)
Fixes #(issue number) Fixes #(issue number)
Closes #(issue number) Closes #(issue number)
@@ -30,7 +30,7 @@ Related to #(issue number)
- [ ] I have tested with different download scenarios (video/audio, playlists) - [ ] I have tested with different download scenarios (video/audio, playlists)
- [ ] I have checked the application logs for errors - [ ] I have checked the application logs for errors
- [ ] I have verified the UI changes (if applicable) - [ ] I have verified the UI changes (if applicable)
- [ ] I have tested on Windows 10 and/or Windows 11 - [ ] I have tested on Windows 10 and/or Windows 11 or in any Linux Distribution
## Screenshots (if applicable) ## Screenshots (if applicable)

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

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

View File

@@ -94,3 +94,4 @@ chmod +x resources/binaries/linux/deno
- These binaries are excluded from version control via `.gitignore`. - These binaries are excluded from version control via `.gitignore`.
- Always use the latest stable release of each tool. - 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

@@ -6,6 +6,7 @@ Welcome! Thanks for your interest in Freedom Loader. Your contributions—bug re
## Quick Links ## Quick Links
- [Development Guide](DEVELOPMENT.md)
- [Code of Conduct](CODE_OF_CONDUCT.md) - [Code of Conduct](CODE_OF_CONDUCT.md)
- [Issue Templates](.github/ISSUE_TEMPLATE/) - [Issue Templates](.github/ISSUE_TEMPLATE/)
- [Pull Request Templates](.github/PULL_REQUEST_TEMPLATE/) - [Pull Request Templates](.github/PULL_REQUEST_TEMPLATE/)
@@ -23,7 +24,7 @@ Found a bug or unexpected behavior? Open an [**issue**](https://github.com/Maste
- Clear description of the bug - Clear description of the bug
- Steps to reproduce - Steps to reproduce
- App version and environment (Windows version, Firefox version if relevant) - App version and environment (Windows version, Firefox version if relevant)
- Logs from: `C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\LOGS-YYYY-MM-DD.log` - Logs from: `C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\LOGS-YYYY-MM-DD.log` (Windows) or `~/.local/share/FreedomLoader/logs/` (Linux)
- Screenshots if applicable - Screenshots if applicable
### 2. Request a Feature ### 2. Request a Feature
@@ -43,13 +44,11 @@ Have an idea to improve Freedom Loader? Open a [**Feature Request**](https://git
- Follow the project's code style and conventions - Follow the project's code style and conventions
**Process:** **Process:**
1. Fork the repository 1. Fork the repository.
2. Create a feature branch: `git checkout -b feature/your-feature-name` 2. Check out our [**Developer Guide (DEVELOPMENT.md)**](./DEVELOPMENT.md) to set up your environment.
3. Make your changes 3. Create a feature branch following our branching conventions.
4. Test thoroughly on Windows 10/11 4. Make your changes and test thoroughly on Windows and/or Linux.
5. Commit with clear messages: `git commit -m "Add feature X"` 5. Open a Pull Request using the appropriate template.
6. Push to your fork: `git push origin feature/your-feature-name`
7. Open a Pull Request using the appropriate template
**Code Guidelines:** **Code Guidelines:**
- Use camelCase for variables and functions - Use camelCase for variables and functions
@@ -72,41 +71,9 @@ Small contributions matter—don't hesitate to submit documentation PRs.
## Development Setup ## Development Setup
### Prerequisites Want to write some code? Awesome!
- Node.js 16.x or higher
- npm or yarn
- Git
- Windows 10/11 (for testing)
### Setup Please read our [**Developer Guide (DEVELOPMENT.md)**](./DEVELOPMENT.md) for instructions on how to set up your local environment, our project architecture, and our Git branching rules.
```bash
# Clone your fork
git clone https://github.com/YOUR-USERNAME/Freedom-Loader.git
cd Freedom-Loader
# Install dependencies
npm install
# Run in development mode
npm start
# Build for production
npm run build
```
### Project Structure
```
Freedom-Loader/
├── main.js # Electron main process
├── preload.js # Electron preload script
├── config.js # Global configuration
├── server/ # Express backend
│ ├── routes/ # API routes
│ ├── controller/ # Business logic
│ └── helpers/ # Utility functions
├── public/ # Frontend (HTML, CSS, JS)
└── resources/ # Binaries (yt-dlp, ffmpeg, etc.)
```
--- ---
@@ -114,12 +81,9 @@ Freedom-Loader/
Before submitting a PR, verify: Before submitting a PR, verify:
- [ ] Download functionality works (video/audio) - [ ] Download functionality works (video/audio)
- [ ] Playlist downloads work
- [ ] Settings panel functions correctly
- [ ] Custom path selection works
- [ ] No errors in application logs - [ ] No errors in application logs
- [ ] UI changes work on all themes (if applicable) - [ ] Any features wasn't break during the development
- [ ] Tested on Windows 10 and/or Windows 11 - [ ] Tested on Windows 10/11 and/or any Linux Distribution
--- ---

388
DEVELOPMENT.md Normal file
View File

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

166
README.md
View File

@@ -15,8 +15,6 @@
</div> </div>
---
## Overview ## Overview
Freedom Loader is a desktop application built with Electron that provides a straightforward way to download video and audio content with full metadata support. The application focuses on reliability, transparency, and user privacy-no ads, no tracking, no questionable third-party services. Freedom Loader is a desktop application built with Electron that provides a straightforward way to download video and audio content with full metadata support. The application focuses on reliability, transparency, and user privacy-no ads, no tracking, no questionable third-party services.
@@ -25,7 +23,7 @@ The primary goal is to make media downloading accessible to users who want offli
### See related repo: ### See related repo:
- **[Website](https://masteracnolo.github.io/Freedom-Loader-Site/)** - Official project website and documentation - **[Website](https://github.com/MasterAcnolo/Freedom-Loader-Site/)** - Official project website and documentation
- **[Workshop](https://github.com/MasterAcnolo/Freedom-Loader-Workshop)** - Theme creation tool and theme browser - **[Workshop](https://github.com/MasterAcnolo/Freedom-Loader-Workshop)** - Theme creation tool and theme browser
## Table of Contents ## Table of Contents
@@ -35,14 +33,12 @@ The primary goal is to make media downloading accessible to users who want offli
- [Usage](#usage) - [Usage](#usage)
- [Preview](#preview) - [Preview](#preview)
- [Configuration](#configuration) - [Configuration](#configuration)
- [Project Structure](#project-structure)
- [Theme Workshop](#Theme-Workshop) - [Theme Workshop](#Theme-Workshop)
- [Technology Stack](#technology-stack) - [Technology Stack](#technology-stack)
- [Development](#development) - [Development](#development)
- [Roadmap](#roadmap) - [Roadmap](#roadmap)
- [Contributing](#contributing) - [Contributing](#contributing)
- [Submitting a community theme](#Submitting-a-community-theme) - [Submitting a community theme](#Submitting-a-community-theme)
- [Support](#support)
- [License](#license) - [License](#license)
## Features ## Features
@@ -254,7 +250,7 @@ Freedom Loader can be configured either through the settings panel in the UI or
| 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] | | `systemTray` | boolean | `false` | 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 |
@@ -263,7 +259,7 @@ Freedom Loader can be configured either through the settings panel in the UI or
| `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 | | `keepPlaylistOrder` | boolean | `false` | Add index before title, usefull for album |
| `createPlaylistFolders` | boolean | `true` | Automatically create a folder for a playlist | | `createPlaylistFolders` | boolean | `false` | 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 |
@@ -285,89 +281,6 @@ Freedom Loader can be configured either through the settings panel in the UI or
> [!NOTE] > [!NOTE]
> Some configuration changes may require an application restart to take effect. > Some configuration changes may require an application restart to take effect.
## Project Structure
```
Freedom-Loader/
├── app/ # Electron main process modules
│ ├── autoUpdater.js
│ ├── dependencyCheck.js
│ ├── discordRPC.js
│ ├── ipcHandlers.js
│ ├── pathValidator.js
│ ├── splashManager.js
│ ├── themeManager.js
│ ├── tray.js
│ ├── windowManager.js
│ └── ytDlpUpdater.js
├── build/ # Build resources and assets
│ ├── app-icon.ico
│ ├── app-icon.png
│ ├── banner.png
│ └── ...
├── config/ # Configuration files
│ ├── config.default.json
│ └── config.dev.json # Configuration Used when i dev mode
├── dist/ # Compiled executables (.exe, .AppImage, .deb, .rpm, .snap)
├── public/ # Frontend assets and UI
│ ├── index.html
│ ├── splash.html # Splash Screen (Start of the application)
│ ├── assets/
│ │ ├── icon/ # Application icons
│ │ └── logo/ # Logo assets
│ ├── script/ # Frontend JavaScript modules
│ │ ├── appVersion.js
│ │ ├── clipboardPaste.js
│ │ ├── custompath.js
│ │ ├── customthemes.js
│ │ ├── downloadstatus.js
│ │ ├── fetchinfo.js
│ │ ├── progressBar.js
│ │ ├── settingsPanel.js
│ │ ├── toast.js
│ │ └── topbar.js
│ └── styles/ # CSS stylesheets
│ ├── styles.css
│ ├── variables.css
│ ├── components/ # Component-specific styles
│ └── layout/ # Layout styles
├── resources/ # Internal resources (binaries : yt-dlp, ffmpeg...)
├── server/ # Express backend server
│ ├── logger.js
│ ├── server.js
│ ├── controller/ # Request handlers
│ │ ├── download.controller.js
│ │ └── info.controller.js
│ ├── helpers/ # Utility functions
│ │ ├── buildArgs.helpers.js
│ │ ├── getBrowser.helpers.js
│ │ ├── notify.helpers.js
│ │ ├── parseInfo.helpers.js
│ │ ├── path.helpers.js
│ │ ├── rateLimit.helpers.js
│ │ └── validation.helpers.js
│ ├── routes/ # API route definitions
│ │ ├── download.route.js
│ │ └── info.route.js
│ └── services/ # Business logic layer
│ ├── download.services.js
│ └── info.services.js
├── theme/ # Theme system
│ ├── template.theme.json # Theme template
│ ├── Dark/ # Default dark theme
│ └── Light/ # Default light theme
├── BINARIES.md # External dependencies & binaries documentation
├── 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
├── package.json # Project metadata and dependencies
├── preload.js # Electron preload script (IPC bridge)
└── README.md # This file
```
### Architecture Overview ### Architecture Overview
Freedom Loader uses a client-server architecture within a single Electron application: Freedom Loader uses a client-server architecture within a single Electron application:
@@ -414,28 +327,7 @@ Freedom Loader includes a web-based theme creator available at [Freedom Loader W
## Development ## Development
### Prerequisites If you are interested by running a local non compiled version of Freedom Loader or just interested by contributing, see [DEVELOPMENT](./DEVELOPMENT.md)
- Node.js 16.x or higher
- npm or yarn
- Git
### Setup
```bash
# Clone the repository
git clone https://github.com/MasterAcnolo/Freedom-Loader.git
cd Freedom-Loader
# Install dependencies
npm install
# Run in development mode
npm start
# Build for production
npm run build
```
### Additional Dependencies ### Additional Dependencies
@@ -446,40 +338,19 @@ You must download the required binaries and place them in the `resources` folder
#### Required binaries #### Required binaries
- **Deno** See [BINARIES.md](./BINARIES.md) for how to setup the binaries.
- Download from: https://sourceforge.net/projects/deno.mirror/files/latest/download
- Rename to: `deno.exe`
- **FFmpeg**
- Download from: https://www.ffmpeg.org/download.html
- Required files:
- `ffmpeg.exe`
- `ffprobe.exe`
- **yt-dlp**
- Already bundled with the project
- No manual installation required
Final folder structure:
```
resources/
├── deno.exe
├── ffmpeg.exe
├── ffprobe.exe
└── yt-dlp.exe
```
> These binaries are required for the application to start correctly.
> If any of them are missing, an error message will be displayed at application startup.
### Development Guidelines ### Development Guidelines
- Follow existing code style and conventions - Follow existing code style and conventions
- Write clear commit messages - Write clear, imperative commit messages (e.g., "Add playlist indexing" not "Added")
- Test thoroughly before submitting changes - Test thoroughly on both Windows and Linux before submitting changes
- Update documentation when adding features - Update documentation (README, DEVELOPMENT.md) when adding features
- Maintain compatibility with Windows 10+ and Linux - Maintain compatibility with:
- Windows 10+
- Fedora 43+ / Debian 11+ / Ubuntu 20.04+
- GNOME and KDE desktop environments (when possible)
- Consider Linux distribution differences early (see [DEVELOPMENT.md](./DEVELOPMENT.md#linux-development-considerations))
## Roadmap ## Roadmap
@@ -521,15 +392,7 @@ Open a feature request issue with:
5. Update documentation as needed 5. Update documentation as needed
6. Submit a pull request with a detailed description 6. Submit a pull request with a detailed description
Please read [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) before contributing. Please read [CONTRIBUTING.md](CONTRIBUTING.md), [DEVELOPMENT.md](./DEVELOPMENT.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) before contributing.
## Support
If you find this project useful and want to support its development:
[![PayPal](https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white)](https://paypal.me/axelnicolas25)
Your support helps maintain infrastructure, develop new features, and keep the project free and ad-free.
## License ## License
@@ -547,7 +410,6 @@ You are free to use, modify, and redistribute this software under the terms of t
- [@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! - [@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
- All users who test, report issues, and help improve the application - All users who test, report issues, and help improve the application
--- ---

View File

@@ -102,8 +102,10 @@ function startRPC() {
}); });
rpc.login({ clientId }).catch(err => { rpc.login({ clientId }).catch(() => {
logger.error("Unable to connect to the RPC:", 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.info("Unable to connect to the Discord RPC. Discord is maybe not launched");
}); });
} }
@@ -121,18 +123,11 @@ async function stopRPC() {
try { try {
if (intervalId) clearInterval(intervalId); if (intervalId) clearInterval(intervalId);
/**
* Ensures RPC connection AND the underlying socket exist
* before attempting to clear activity to prevent crash.
*/
if (rpc.transport && rpc.transport.socket) {
await rpc.clearActivity();
}
await rpc.destroy(); await rpc.destroy();
} catch (err) { } catch (err) {
logger.error("Error while closing the RPC:", 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);
} }
} }
} }

View File

@@ -13,7 +13,7 @@
const { ipcMain, dialog, shell } = require("electron"); const { ipcMain, dialog, shell } = require("electron");
const fs = require("fs"); const fs = require("fs");
const { logger, logDir } = require("../server/logger"); const { logger, logDir } = require("../server/logger");
const { configFeatures, featuresPath } = require("../config"); const {configFeatures, featuresPath, devMode} = 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");
@@ -114,7 +114,18 @@ function registerIpcHandlers(getMainWindow) {
/** /**
* Window minimize request from renderer. * Window minimize request from renderer.
*/ */
ipcMain.on("window-minimize", () => getMainWindow()?.minimize()); ipcMain.on("window-minimize", () => {
// Minimize to tray
if (configFeatures.systemTray) {
getMainWindow()?.hide()
logger.info("Window Minimized in SystemTray (Using Minimize Button)");
} else {
// Native minimize
getMainWindow()?.minimize()
logger.info("Window minimized normally");
}
}
);
/** /**
* Toggles maximize/unmaximize state of main window. * Toggles maximize/unmaximize state of main window.
@@ -223,7 +234,7 @@ function registerIpcHandlers(getMainWindow) {
if (key === "systemTray") { if (key === "systemTray") {
if (value === true) { if (value === true) {
logger.info("System Tray enabled dynamically."); logger.info("System Tray enabled dynamically.");
createSystemTray(getMainWindow(), config.devMode); createSystemTray(devMode);
} else { } else {
logger.info("System Tray disabled dynamically."); logger.info("System Tray disabled dynamically.");
destroyTray(); destroyTray();

View File

@@ -66,7 +66,7 @@ function validateDownloadPath(userPath) {
const real = fs.realpathSync(absolutePath); const real = fs.realpathSync(absolutePath);
if (!isSafePath(real)) { if (!isSafePath(real)) {
throw new Error("Path not allowed: system folders are blocked!"); logger.error("Path not allowed: system folders are blocked!");
} }
return real; return real;

View File

@@ -2,6 +2,7 @@ const {app, Menu, Tray, nativeImage} = require("electron");
const path = require("path"); const path = require("path");
const {logger} = require("../server/logger"); // Ajuste le chemin si besoin const {logger} = require("../server/logger"); // Ajuste le chemin si besoin
const fs = require("fs"); const fs = require("fs");
const {getMainWindow} = require("./windowManager");
/** /**
* Global reference to the Tray instance to prevent garbage collection. * Global reference to the Tray instance to prevent garbage collection.
@@ -17,11 +18,10 @@ let tray = null;
* - Builds the right-click context menu. * - Builds the right-click context menu.
* - Handles left-click behavior to toggle main window visibility. * - 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. * @param {boolean} devMode - Indicates whether the application is running in development mode.
* @returns {Tray} The created Tray instance. * @returns {Tray} The created Tray instance.
*/ */
function createSystemTray(mainWindow, devMode) { function createSystemTray(devMode) {
// Prevent creating multiple tray instances // Prevent creating multiple tray instances
if (tray) return tray; if (tray) return tray;
@@ -58,7 +58,12 @@ function createSystemTray(mainWindow, devMode) {
{ {
label: "Show Freedom Loader", label: "Show Freedom Loader",
click: () => { click: () => {
mainWindow.show(); const window = getMainWindow();
if (window && !window.isDestroyed()) {
window.show();
window.focus();
logger.info("Window Restored from SystemTray (Using Show Action)");
}
}, },
}, },
{ type: "separator" }, { type: "separator" },
@@ -82,11 +87,15 @@ function createSystemTray(mainWindow, devMode) {
* but it remains standard practice for Windows and Linux environments. * but it remains standard practice for Windows and Linux environments.
*/ */
tray.on("click", () => { tray.on("click", () => {
if (mainWindow.isVisible()) { const window = getMainWindow();
mainWindow.hide(); if (!window || window.isDestroyed()) return;
if (window.isVisible()) {
window.hide();
} else { } else {
mainWindow.show(); window.show();
mainWindow.focus(); window.focus();
logger.info("Window Restored from SystemTray (Double Click on Tray Icon)")
} }
}); });

View File

@@ -87,9 +87,11 @@ async function createMainWindow() {
* cancels the destruction and hides the window instead. * cancels the destruction and hides the window instead.
*/ */
mainWindow.on('close', (event) => { mainWindow.on('close', (event) => {
if (!app.isQuitting && config.systemTray ) {
if (!app.isQuitting && config.configFeatures.systemTray) {
event.preventDefault(); event.preventDefault();
mainWindow.hide(); mainWindow.hide();
logger.info("Window Minimized in SystemTray (Using Close Button)");
return false; return false;
} }
}); });

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 || process.env.NODE_ENV === "test";
/** /**
* Resolves the configuration file path depending on runtime environment. * Resolves the configuration file path depending on runtime environment.

View File

@@ -9,7 +9,7 @@
"verboseLogs": false, "verboseLogs": false,
"autoDownloadPlaylist": true, "autoDownloadPlaylist": true,
"keepPlaylistOrder": false, "keepPlaylistOrder": false,
"createPlaylistFolders": true, "createPlaylistFolders": false,
"customCodec": "h264", "customCodec": "h264",
"logSystem": true, "logSystem": true,
"outputTitleCheck": true, "outputTitleCheck": true,

61
main.js
View File

@@ -38,10 +38,13 @@ 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, logDir } = require("./server/logger");
const { configFeatures } = require("./config"); const { configFeatures, devMode} = require("./config");
/** /**
* On start, enable or disable hardWareAcceleration * In-memory snapshot of application feature flags.
*
* Note: Changes to the config file are not automatically reflected.
* A restart could be required.
*/ */
if (!configFeatures.enableHardwareAcceleration){ if (!configFeatures.enableHardwareAcceleration){
app.disableHardwareAcceleration(); app.disableHardwareAcceleration();
@@ -50,15 +53,7 @@ if (!configFeatures.enableHardwareAcceleration){
logger.info("Enable Hardware Acceleration") logger.info("Enable Hardware Acceleration")
} }
/** if(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
*/ */
@@ -79,8 +74,9 @@ 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, validateBinaries, defaultDownloadFolder } = require("./server/helpers/path.helpers");
const {createSystemTray, destroyTray} = require("./app/tray"); const {createSystemTray, destroyTray} = require("./app/tray");
const { stopServer } = require("./server/server");
/** /**
* Global flag indicating if the application is intentionally shutting down. * Global flag indicating if the application is intentionally shutting down.
@@ -96,6 +92,28 @@ app.isQuitting = false;
*/ */
let isCleanedUp = false; let isCleanedUp = false;
/**
* Performs a graceful shutdown of all application services asynchronously.
* Once finished, it re-triggers the app.quit() process.
*/
async function cleanShutdown() {
try {
destroyTray();
await stopRPC();
stopServer();
} catch (err) {
logger.error("Error during cleanup:", err);
} finally {
logger.info("All services stopped. Have a nice day!");
logSessionEnd();
isCleanedUp = true;
app.quit();
}
}
/** /**
* If another instance want to run * If another instance want to run
*/ */
@@ -117,7 +135,7 @@ app.whenReady().then(async () => {
createSplashWindow(); createSplashWindow();
if (!config.devMode && !checkNativeDependencies()) return; if (!devMode && !checkNativeDependencies()) return;
const { userYtDlp } = require("./server/helpers/path.helpers"); const { userYtDlp } = require("./server/helpers/path.helpers");
updateYtDlp(userYtDlp); updateYtDlp(userYtDlp);
@@ -143,7 +161,7 @@ app.whenReady().then(async () => {
getMainWindow().show(); getMainWindow().show();
if (configFeatures.systemTray) { if (configFeatures.systemTray) {
createSystemTray(getMainWindow(), config.devMode); createSystemTray(devMode);
} }
if (configFeatures.discordRPC) startRPC(); if (configFeatures.discordRPC) startRPC();
@@ -171,19 +189,6 @@ app.on("before-quit", (event) => {
if (!isCleanedUp) { if (!isCleanedUp) {
event.preventDefault(); event.preventDefault();
(async () => { cleanShutdown();
try {
destroyTray();
await stopRPC();
} catch (err) {
logger.error("Error during cleanup:", err);
} finally {
logger.info("All services stopped. Have a nice day!");
logSessionEnd();
isCleanedUp = true;
app.quit();
}
})();
} }
}); });

12
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "freedom-loader", "name": "freedom-loader",
"version": "1.6.2-preview", "version": "1.6.3-preview",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "freedom-loader", "name": "freedom-loader",
"version": "1.6.2-preview", "version": "1.6.3-preview",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"dependencies": { "dependencies": {
"chalk": "^4.1.2", "chalk": "^4.1.2",
@@ -24,7 +24,7 @@
"@electron/devtron": "^2.1.1", "@electron/devtron": "^2.1.1",
"@jest/globals": "^30.4.1", "@jest/globals": "^30.4.1",
"@playwright/test": "^1.62.1", "@playwright/test": "^1.62.1",
"electron": "^43.3.0", "electron": "^44.3.0",
"electron-builder": "^26.15.3", "electron-builder": "^26.15.3",
"jest": "^30.4.2", "jest": "^30.4.2",
"nodemon": "^3.1.14", "nodemon": "^3.1.14",
@@ -3910,9 +3910,9 @@
} }
}, },
"node_modules/electron": { "node_modules/electron": {
"version": "43.3.0", "version": "44.3.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-43.3.0.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-44.3.0.tgz",
"integrity": "sha512-nLlvu0WFjftWsSaTkV2B/c4NDuJBspTyXu8vKSQ6vLvFt8uG3NgN49LLKcXddwX0GqVvAQDhciWp+4xOdTdhew==", "integrity": "sha512-St9EV7F2VtYaYWD2qaAjBwUgKxx39eJOUsUJ5+/1113sqbVfNqv4Dbm/W1rN7qmYSPa+mWwR6yr+b7MfgjgVfQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@@ -1,7 +1,8 @@
{ {
"name": "freedom-loader", "name": "freedom-loader",
"desktopName": "com.masteracnolo.freedomloader",
"productName": "Freedom Loader", "productName": "Freedom Loader",
"version": "1.6.2-preview", "version": "1.6.3",
"author": "MasterAcnolo <MasterAcnolo@users.noreply.github.com>", "author": "MasterAcnolo <MasterAcnolo@users.noreply.github.com>",
"description": "Free and open-source GUI for yt-dlp — download video and audio from hundreds of platforms", "description": "Free and open-source GUI for yt-dlp — download video and audio from hundreds of platforms",
"homepage": "https://masteracnolo.github.io/Freedom-Loader-Site/", "homepage": "https://masteracnolo.github.io/Freedom-Loader-Site/",
@@ -21,7 +22,6 @@
"dev": "nodemon --watch server --watch app --watch main.js --watch config.js --ext js,json --exec \"electron --no-warnings .\"", "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: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 .\"", "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:win": "electron-builder --win",
"build:linux": "electron-builder --linux", "build:linux": "electron-builder --linux",
@@ -30,10 +30,8 @@
"release": "bash scripts/release.sh", "release": "bash scripts/release.sh",
"release:publish": "bash scripts/release.sh --publish", "release:publish": "bash scripts/release.sh --publish",
"release:dry-run": "bash scripts/release.sh --dry-run", "release:dry-run": "bash scripts/release.sh --dry-run",
"test": "jest", "test": "jest",
"test:unit": "jest test/unit", "test:unit": "jest test/unit",
"update": "npm update" "update": "npm update"
}, },
"dependencies": { "dependencies": {
@@ -52,7 +50,7 @@
"@electron/devtron": "^2.1.1", "@electron/devtron": "^2.1.1",
"@jest/globals": "^30.4.1", "@jest/globals": "^30.4.1",
"@playwright/test": "^1.62.1", "@playwright/test": "^1.62.1",
"electron": "^43.3.0", "electron": "^44.3.0",
"electron-builder": "^26.15.3", "electron-builder": "^26.15.3",
"jest": "^30.4.2", "jest": "^30.4.2",
"nodemon": "^3.1.14", "nodemon": "^3.1.14",
@@ -84,33 +82,21 @@
"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",
"to": "confirm-icon.png"
},
{
"from": "build/banner.bmp",
"to": "banner.bmp"
},
{
"from": "build/banner.png",
"to": "banner.png"
},
{
"from": "build/error.png",
"to": "error.png"
},
{ {
"from": "config/config.default.json", "from": "config/config.default.json",
"to": "config/config.default.json" "to": "config/config.default.json"
},
{
"from": "build",
"to": ".",
"filter": [
"app-icon.ico",
"app-icon-64x64.png",
"confirm-icon.png",
"banner.bmp",
"banner.png",
"error.png"
]
} }
], ],
"win": { "win": {
@@ -119,20 +105,14 @@
"artifactName": "Freedom-Loader-Setup-${version}.${ext}", "artifactName": "Freedom-Loader-Setup-${version}.${ext}",
"extraResources": [ "extraResources": [
{ {
"from": "resources/binaries/win-32/yt-dlp.exe", "from": "resources/binaries/win-32",
"to": "binaries/yt-dlp.exe" "to": "binaries",
}, "filter": [
{ "yt-dlp.exe",
"from": "resources/binaries/win-32/ffmpeg.exe", "ffmpeg.exe",
"to": "binaries/ffmpeg.exe" "ffprobe.exe",
}, "deno.exe"
{ ]
"from": "resources/binaries/win-32/ffprobe.exe",
"to": "binaries/ffprobe.exe"
},
{
"from": "resources/binaries/win-32/deno.exe",
"to": "binaries/deno.exe"
} }
] ]
}, },
@@ -154,24 +134,20 @@
"snap" "snap"
], ],
"category": "Utility", "category": "Utility",
"synopsis": "GUI for yt-dlp to download video and audio with metadata",
"maintainer": "MasterAcnolo",
"icon": "build/app-icon.png", "icon": "build/app-icon.png",
"executableName": "freedom-loader", "executableName": "freedom-loader",
"extraResources": [ "extraResources": [
{ {
"from": "resources/binaries/linux/yt-dlp", "from": "resources/binaries/linux",
"to": "binaries/yt-dlp" "to": "binaries",
}, "filter": [
{ "yt-dlp",
"from": "resources/binaries/linux/ffmpeg", "ffmpeg",
"to": "binaries/ffmpeg" "ffprobe",
}, "deno"
{ ]
"from": "resources/binaries/linux/ffprobe",
"to": "binaries/ffprobe"
},
{
"from": "resources/binaries/linux/deno",
"to": "binaries/deno"
} }
] ]
}, },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

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">Thème</span> <span class="setting-title">Theme</span>
<select id="themeSelect" data-key="theme" aria-label="Choisir le thème"> <select id="themeSelect" data-key="theme" aria-label="Choose the theme">
</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">
@@ -262,7 +262,7 @@
</div> </div>
<div class="download-path"> <div class="download-path">
Will be save on <span id="savePath"></span> Will be saved in <span id="savePath"></span>
<button id="changePath">Edit</button> <button id="changePath">Edit</button>
</div> </div>
@@ -306,7 +306,6 @@
<!-- Scripts --> <!-- Scripts -->
<script src="script/toast.js"></script> <script src="script/toast.js"></script>
<script src="script/customConfiguration.js"></script>
<script src="script/clipboardPaste.js"></script> <script src="script/clipboardPaste.js"></script>
<script src="script/custompath.js"></script> <script src="script/custompath.js"></script>
<script src="script/settingsPanel.js"></script> <script src="script/settingsPanel.js"></script>

View File

@@ -7,7 +7,7 @@
* *
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async function versionLabel() { async function displayVersionLabel() {
/** /**
* Raw version string from the backend (e.g., "dev-1.6.2-preview" or "v1.6.2") * Raw version string from the backend (e.g., "dev-1.6.2-preview" or "v1.6.2")
* @type {string} * @type {string}
@@ -51,4 +51,4 @@ async function versionLabel() {
versionBadge.textContent = `${packageStateStr}${previewStr}v${baseVersion}`; versionBadge.textContent = `${packageStateStr}${previewStr}v${baseVersion}`;
} }
versionLabel(); displayVersionLabel();

View File

@@ -1,27 +0,0 @@
async function initPlatformRestrictions() {
const platform = await window.electronAPI.getProcessPlatform();
const themeSelect = document.getElementById("themeSelect");
const refreshBtn = document.getElementById("refresh-themes-btn");
/**
* Is the OS windows
* @type {boolean}
*/
const isWindows = platform === "win32";
console.log(isWindows);
// If it's windows, the button is not disabled.
themeSelect.disabled = !isWindows;
refreshBtn.disabled = !isWindows;
themeSelect.classList.toggle("disabled", !isWindows);
refreshBtn.classList.toggle("disabled", !isWindows);
if (!isWindows) {
themeSelect.title = "Themes are only available on Windows";
refreshBtn.title = "Themes are only available on Windows";
}
}

View File

@@ -121,7 +121,7 @@ async function initThemes() {
const defaultTheme = { const defaultTheme = {
id: "dark", id: "dark",
name: "Dark", name: "Dark",
subtitle: "Darkness is my ally", subtitle: "Because Why Not ?",
style: { style: {
background: { background: {
size: "cover", size: "cover",

View File

@@ -5,53 +5,55 @@
* 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.
*/ */
function setupTopbarListeners() { export function initTopBar() {
const { topbarAPI } = window; const { topbarAPI } = window;
if (!topbarAPI) return; if (!topbarAPI) return;
const minBtn = document.getElementById("minimize-btn"); const minBtn = document.getElementById("minimize-btn");
const maxBtn = document.getElementById("maximize-btn");
const closeBtn = document.getElementById("close-btn");
const devtoolsBtn = document.getElementById("devtools-btn");
const logsBtn = document.getElementById("logs-btn");
const websiteBtn = document.getElementById("website-btn");
const wikiBtn = document.getElementById("wiki-btn");
const workshopBtn = document.getElementById("workshop-btn");
if (minBtn) minBtn.onclick = () => topbarAPI.minimize(); if (minBtn) minBtn.onclick = () => topbarAPI.minimize();
const maxBtn = document.getElementById("maximize-btn");
if (maxBtn) maxBtn.onclick = () => topbarAPI.maximize(); if (maxBtn) maxBtn.onclick = () => topbarAPI.maximize();
const closeBtn = document.getElementById("close-btn");
if (closeBtn) closeBtn.onclick = () => topbarAPI.close(); if (closeBtn) closeBtn.onclick = () => topbarAPI.close();
const devtoolsBtn = document.getElementById("devtools-btn");
if (devtoolsBtn) devtoolsBtn.onclick = () => topbarAPI.openDevTools(); if (devtoolsBtn) devtoolsBtn.onclick = () => topbarAPI.openDevTools();
const logsBtn = document.getElementById("logs-btn");
if (logsBtn) logsBtn.onclick = () => topbarAPI.openLogs(); if (logsBtn) logsBtn.onclick = () => topbarAPI.openLogs();
const websiteBtn = document.getElementById("website-btn");
if (websiteBtn) websiteBtn.onclick = () => topbarAPI.openWebsite(); if (websiteBtn) websiteBtn.onclick = () => topbarAPI.openWebsite();
const wikiBtn = document.getElementById("wiki-btn");
if (wikiBtn) wikiBtn.onclick = () => topbarAPI.openWiki(); if (wikiBtn) wikiBtn.onclick = () => topbarAPI.openWiki();
const workshopBtn = document.getElementById("workshop-btn");
if (workshopBtn) workshopBtn.onclick = () => topbarAPI.openWorkshop(); if (workshopBtn) workshopBtn.onclick = () => topbarAPI.openWorkshop();
} }
/** async function attachListeners(){
* Registers topbar event listeners after DOM is fully loaded. try{
*/ const featuresList = await window.electronAPI.getFeatures();
document.addEventListener("DOMContentLoaded", setupTopbarListeners);
/** if (!featuresList.customTopBar) {
* Applies UI layout adjustments depending on feature flags const topbar = document.getElementById("topbar");
* provided by the Electron backend. const container = document.getElementById("container");
* const themeSwitcher = document.getElementById("theme-switcher");
* Specifically handles:
* - hiding custom topbar
* - adjusting layout spacing
* - repositioning theme switcher
*/
(async function applyFeatureLayout() {
const features = await window.electronAPI.getFeatures();
if (!features.customTopBar) { if (topbar) topbar.style.display = "none";
const topbar = document.getElementById("topbar"); if (container) container.style.marginTop = "0";
const container = document.getElementById("container"); if (themeSwitcher) themeSwitcher.style.top = "30px";
const themeSwitcher = document.getElementById("theme-switcher"); }
if (topbar) topbar.style.display = "none"; } catch (error) {
if (container) container.style.marginTop = "0"; console.error("Failed to load layout features for topbar:", error);
if (themeSwitcher) themeSwitcher.style.top = "30px";
} }
})();
}
initTopBar()
document.addEventListener("DOMContentLoaded", attachListeners)

1
scripts/CHANGELOG.md Normal file
View File

@@ -0,0 +1 @@
[//]: # (This file is used for the release CHANGELOG. Please use Markdown to describe changes.)

View File

@@ -4,6 +4,13 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
VERSION=$(node -p "require('$ROOT_DIR/package.json').version") VERSION=$(node -p "require('$ROOT_DIR/package.json').version")
if [[ "$VERSION" == *"-preview"* ]]; then
echo "Error: The current version ($VERSION) is a 'preview"
echo "Unable to launch release pipeline. Please update the package.json with a stable version.."
exit 1
fi
TAG="$VERSION" TAG="$VERSION"
# Parse arguments # Parse arguments
@@ -17,7 +24,7 @@ for arg in "$@"; do
--copr) PUBLISH_COPR=true ;; --copr) PUBLISH_COPR=true ;;
--publish) PUBLISH_SNAP=true; PUBLISH_COPR=true ;; --publish) PUBLISH_SNAP=true; PUBLISH_COPR=true ;;
--dry-run) DRY_RUN=true ;; --dry-run) DRY_RUN=true ;;
*) echo "⚠️ Argument inconnu : $arg" ; exit 1 ;; *) echo "Unknown Argument : $arg" ; exit 1 ;;
esac esac
done done
@@ -57,6 +64,16 @@ echo "[0/5] Cleaning workspace..."
rm -rf "$ROOT_DIR/dist" "$ROOT_DIR/srpm-out" rm -rf "$ROOT_DIR/dist" "$ROOT_DIR/srpm-out"
echo "Workspace cleaned (dist/ and srpm-out/ removed)" echo "Workspace cleaned (dist/ and srpm-out/ removed)"
# ------------------------------------------------------------
# Step 0.3 - Retrieve Changelog
# ------------------------------------------------------------
if [ ! -f "$ROOT_DIR/CHANGELOG.md" ]; then
echo "Warning: CHANGELOG.md not found, release notes will be empty."
CHANGELOG=""
else
CHANGELOG="$(cat "$ROOT_DIR/CHANGELOG.md")"
echo "Changelog retrieved successfully"
fi
# ------------------------------------------------------------ # ------------------------------------------------------------
# Step 1 - Build Linux packages (AppImage, deb, snap) # Step 1 - Build Linux packages (AppImage, deb, snap)
# ------------------------------------------------------------ # ------------------------------------------------------------
@@ -98,19 +115,13 @@ PREV_TAG=$(git rev-list --tags --skip=1 --max-count=1 | xargs git describe --tag
RELEASE_NOTES="# Freedom Loader - $VERSION RELEASE_NOTES="# Freedom Loader - $VERSION
## Changelog
$CHANGELOG
## Found a bug or issue? ## Found a bug or issue?
Please report it in the [GitHub Issues](https://github.com/MasterAcnolo/Freedom-Loader/issues) section. 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}" **Full Changelog**: https://github.com/MasterAcnolo/Freedom-Loader/compare/${PREV_TAG}...${TAG}"
if [ "$DRY_RUN" = false ]; then if [ "$DRY_RUN" = false ]; then

View File

@@ -2,6 +2,7 @@ const { fetchInfo } = require("../services/info.services");
const { parseVideo, parsePlaylist } = require("../helpers/parseInfo.helpers"); const { parseVideo, parsePlaylist } = require("../helpers/parseInfo.helpers");
const { logger } = require("../logger"); const { logger } = require("../logger");
const { isValidUrl } = require("../helpers/validation.helpers"); const { isValidUrl } = require("../helpers/validation.helpers");
const {isUrlPlaylist} = require("../helpers/playlist.helpers");
/** /**
* Handles metadata retrieval for a video or playlist. * Handles metadata retrieval for a video or playlist.
@@ -36,7 +37,7 @@ async function infoController(req, res) {
logger.info(`Info request received. ENCODED: ${encodedUrl}`); logger.info(`Info request received. ENCODED: ${encodedUrl}`);
// Lightweight heuristic to detect playlist URLs. It works for Youtube. // Lightweight heuristic to detect playlist URLs. It works for Youtube.
const isPlaylistUrl = url.includes("&list") || url.includes("?list"); const isPlaylistUrl = isUrlPlaylist(url);
logger.info( logger.info(
isPlaylistUrl isPlaylistUrl
@@ -76,11 +77,11 @@ async function infoController(req, res) {
); );
// Preserve original error context // Preserve original error context
throw err; logger.error(err);
} }
} else { } else {
throw err; logger.error(err);
} }
} }

View File

@@ -1,6 +1,6 @@
const path = require("path"); const path = require("path");
const getUserBrowser = require("./getBrowser.helpers.js"); 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");
@@ -135,7 +135,7 @@ function buildYtDlpArgs({url, audioOnly, quality, outputFolder, isPlaylist}) {
if (configFeatures.addMetadata) { if (configFeatures.addMetadata) {
args.push( args.push(
"--postprocessor-args", "--postprocessor-args",
`ffmpeg:-metadata comment="Downloaded with Freedom Loader v${config.version}"` `ffmpeg:-metadata comment="Downloaded with Freedom Loader ${config.version}"`
); );
} }

View File

@@ -1,7 +1,6 @@
const fs = require("fs"); const fs = require("fs");
const os = require("os"); const os = require("os");
const path = require("path"); const notify = require("./notify.helpers");
const notify = require("./notify.helpers")
const { logger } = require("../logger"); const { logger } = require("../logger");
/** /**
@@ -22,41 +21,38 @@ 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 platform = process.platform;
const firefoxPath = getFirefoxProfilePath(); let isFirefoxHere = false;
/** if (platform === "win32") {
* Browsers supported by yt-dlp cookie extraction. const paths = [
* "C:\\Program Files\\Mozilla Firefox\\firefox.exe",
* Only Firefox is currently enabled and tested. "C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe",
* Additional browsers can be enabled in the future `${process.env.LOCALAPPDATA}\\Mozilla Firefox\\firefox.exe`,
* once compatibility has been verified. ];
*/
const browsers = [
{ name: "firefox", path: firefoxPath },
// { name: "chrome", path: path.join(userProfile, "AppData", "Local", "Google", "Chrome", "User Data", "Default") },
// { name: "brave", path: path.join(userProfile, "AppData", "Local", "BraveSoftware", "Brave-Browser", "User Data", "Default") },
// { name: "edge", path: path.join(userProfile, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default") },
// { name: "opera", path: path.join(userProfile, "AppData", "Roaming", "Opera Software", "Opera Stable") },
// { name: "vivaldi", path: path.join(userProfile, "AppData", "Local", "Vivaldi", "User Data", "Default") },
// { name: "safari", path: path.join(userProfile, "AppData", "Local", "Apple Computer", "Safari") },
// { name: "whale", path: path.join(userProfile, "AppData", "Local", "Naver", "Naver Whale", "User Data", "Default") }
];
// Search for the first available browser profile. isFirefoxHere = paths.some(p => fs.existsSync(p));
for (const browser of browsers) {
if (browser.path && fs.existsSync(browser.path)) {
logger.info(`Browser found: ${browser.name}`);
return browser.name;
}
} }
// No supported browser found => Notify User if (platform === "linux") {
logger.warn("No supported browser found on the system"); 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`,
];
isFirefoxHere = paths.some(p => fs.existsSync(p));
}
// If you somehow managed to live without Firefox and need help installing it. - Don't applied to my Linux chad if (!isFirefoxHere) {
if (process.platform === "win32") { // If you somehow managed to live without Firefox and need help installing it. - Don't applied to my Linux chad
// No supported browser found => Notify User
logger.warn("No supported browser found on the system");
notify.notifyFirefoxBrowserMissing(); notify.notifyFirefoxBrowserMissing();
} else {
logger.info("Browser found: firefox");
} }
// Fallback to Firefox and let yt-dlp handle the error gracefully. // Fallback to Firefox and let yt-dlp handle the error gracefully.
@@ -64,39 +60,4 @@ function getUserBrowser() {
return "firefox"; return "firefox";
} }
function getFirefoxProfilePath() { module.exports = {getUserBrowser};
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

View File

@@ -150,7 +150,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(resourcesPath, "theme"); const defaultThemesSourcePath = path.join("theme");
/** /**
* Initializes user theme directory and ensures default themes exist. * Initializes user theme directory and ensures default themes exist.
@@ -260,7 +260,7 @@ function validateBinaries() {
/** /**
* Helper to lazy load logger, avoid circular import * Helper to lazy load logger, avoid circular import
* @returns {winston.Logger} * @returns {import("winston").Logger}
*/ */
function getLogger() { function getLogger() {
return require("../logger.js").logger; return require("../logger.js").logger;

View File

@@ -0,0 +1,12 @@
/**
* Determines whether a URL targets a playlist.
*
* The detection is based on the presence of the YouTube
* playlist query parameter (`list`).
*
* @param {string} url - URL to inspect.
* @returns {boolean} True if the URL appears to be a playlist.
*/
export function isUrlPlaylist(url) {
return url.includes("?list=") || url.includes("&list=") || url.includes("@");
}

View File

@@ -17,11 +17,6 @@ try {
console.error(`Failed to create log directory: ${error.message}`); console.error(`Failed to create log directory: ${error.message}`);
} }
const logFormat = format.combine(
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.printf(({ timestamp, level, message }) => `${timestamp} | ${level.toUpperCase()} | ${message}`)
);
/** /**
* Format used by the saved file * Format used by the saved file
* @type {Format} * @type {Format}

View File

@@ -1,11 +1,17 @@
const express = require("express"); const express = require("express");
const path = require("path"); const path = require("path");
const { logger, logSessionEnd } = require("./logger");
const config = require("../config"); const config = require("../config");
const {logger} = require("./logger");
const { rateLimit } = require("./helpers/rateLimit.helpers"); const { rateLimit } = require("./helpers/rateLimit.helpers");
const app = express(); const app = express();
/**
* Store the state of the server
*/
let serverInstance = null;
// Middlewares // Middlewares
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
@@ -30,33 +36,32 @@ app.get("/", rateLimit, (req, res) => {
*/ */
async function startServer() { async function startServer() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const server = app.listen(config.applicationPort, () => { const serverInstance = app.listen(config.applicationPort, () => {
logger.info(`Express server ready at http://localhost:${config.applicationPort}`); logger.info(`Express server ready at http://localhost:${config.applicationPort}`);
resolve(server); resolve(serverInstance);
}); });
server.on("error", (err) => { serverInstance.on("error", (err) => {
logger.error("Express server error:", err); logger.error("Express server error:", err);
reject(err); reject(err);
}); });
/**
* Clean exit function
* - Stop Log
* - Stop Express Server
* - Stop Electron App
*/
const gracefulExit = () => {
logSessionEnd();
server.close(() => {
logger.info("Express server closed cleanly.");
process.exit();
});
};
process.on("SIGINT", gracefulExit);
process.on("SIGTERM", gracefulExit);
}); });
} }
module.exports = { startServer }; /**
* Clean exit function called by Electron
*/
function stopServer() {
if (serverInstance) {
try{
serverInstance.close();
logger.info("Express server closed cleanly");
} catch (err){
logger.error("Express server was not closed cleanly:", err)
}
} else {
logger.info("Express server was already closed");
}
}
module.exports = { startServer, stopServer };

View File

@@ -7,6 +7,7 @@ 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 {reloadFeatures} = require("../../config");
const {isUrlPlaylist} = require("../helpers/playlist.helpers");
/** /**
* Reference to the currently running yt-dlp process. * Reference to the currently running yt-dlp process.
@@ -18,19 +19,6 @@ const {reloadFeatures} = require("../../config");
*/ */
let currentDownloadProcess = null; let currentDownloadProcess = null;
/**
* Determines whether a URL targets a playlist.
*
* The detection is based on the presence of the YouTube
* playlist query parameter (`list`).
*
* @param {string} url - URL to inspect.
* @returns {boolean} True if the URL appears to be a playlist.
*/
function isPlaylistUrl(url) {
return url.includes("?list=") || url.includes("&list=");
}
/** /**
* Creates a dedicated download folder for a playlist. * Creates a dedicated download folder for a playlist.
* *
@@ -49,8 +37,8 @@ function isPlaylistUrl(url) {
*/ */
function createPlaylistFolder(basePath, playlistTitle) { function createPlaylistFolder(basePath, playlistTitle) {
const sanitizedTitle = playlistTitle const sanitizedTitle = playlistTitle
.replace(/[<>:"|?*]/g, '') .replace(/[<>:"|?*]/g, '')
.replace(/\s+/g, ' ') .replace(/\s+/g, ' ')
.trim() .trim()
.substring(0, 200); .substring(0, 200);
@@ -77,7 +65,7 @@ function createPlaylistFolder(basePath, playlistTitle) {
} }
logger.error(`Could not find available playlist folder after 1000 attempts`); logger.error(`Could not find available playlist folder after 1000 attempts`);
throw new Error("Unable to create playlist folder"); logger.error("Unable to create playlist folder");
} catch (err) { } catch (err) {
logger.warn(`Failed to create playlist folder with title "${sanitizedTitle}": ${err.message}`); logger.warn(`Failed to create playlist folder with title "${sanitizedTitle}": ${err.message}`);
@@ -160,7 +148,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 || isUrlPlaylist(options.url);
if (isPlaylist && userConfig.createPlaylistFolders) { if (isPlaylist && userConfig.createPlaylistFolders) {
try { try {

View File

@@ -1,6 +1,6 @@
const { execFile } = require("child_process"); const { execFile } = require("child_process");
const { userYtDlp, denoPath } = require("../helpers/path.helpers"); const { userYtDlp, denoPath } = require("../helpers/path.helpers");
const getUserBrowser = require("../helpers/getBrowser.helpers"); const {getUserBrowser} = require("../helpers/getBrowser.helpers");
const { logger } = require("../logger"); const { logger } = require("../logger");
/** /**
@@ -61,6 +61,7 @@ function fetchInfo(url) {
try { try {
const data = JSON.parse(stdout); const data = JSON.parse(stdout);
resolve(data); resolve(data);
} catch (e) { } catch (e) {
logger.error(`JSON Parsing Error: ${e.message}`); logger.error(`JSON Parsing Error: ${e.message}`);

Binary file not shown.