mirror of
https://github.com/MasterAcnolo/Freedom-Loader.git
synced 2026-09-28 04:21:15 +02:00
Compare commits
36 Commits
1993566040
...
1.6.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd43ae10fc | ||
|
|
6978277b4b | ||
|
|
b91c20d450 | ||
|
|
014b1ad52a | ||
|
|
ec93d20a39 | ||
|
|
09c511fcb8 | ||
|
|
f343fbd148 | ||
|
|
6d99d5f91c | ||
|
|
0f0bb874bf | ||
|
|
b9267ec408 | ||
|
|
311bff6df5 | ||
|
|
e4243a01ae | ||
|
|
52be9c6b4f | ||
|
|
e541eb442a | ||
|
|
e2c2dde4a2 | ||
|
|
ed4022dfd1 | ||
|
|
fcbc3f95ac | ||
|
|
f3293ef89a | ||
|
|
05e864b93a | ||
|
|
8ec901dc07 | ||
|
|
104500a82a | ||
|
|
3444f2fc0b | ||
|
|
86b595beff | ||
|
|
ed6efcf812 | ||
|
|
2c84ac7a70 | ||
|
|
3c649173a2 | ||
|
|
b4d4ff2665 | ||
|
|
c2584b6df3 | ||
|
|
ec16b5209d | ||
|
|
f2f1649493 | ||
|
|
e5076b0e0b | ||
|
|
4573917541 | ||
|
|
62c109939a | ||
|
|
b56ec1e62e | ||
|
|
1a68c58ee3 | ||
|
|
39c4a69be1 |
3
.github/ISSUE_TEMPLATE/performance-issue.md
vendored
3
.github/ISSUE_TEMPLATE/performance-issue.md
vendored
@@ -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]
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/question.md
vendored
4
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -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
|
||||||
|
|||||||
@@ -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
46
.github/workflows/codeql.yml
vendored
Normal 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}}"
|
||||||
@@ -93,4 +93,5 @@ chmod +x resources/binaries/linux/deno
|
|||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- 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
|
||||||
@@ -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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -161,4 +125,4 @@ Check out his work: [IG @designmark_studio](https://www.instagram.com/designmark
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Thank you for contributing to Freedom Loader!**
|
**Thank you for contributing to Freedom Loader!**
|
||||||
|
|||||||
388
DEVELOPMENT.md
Normal file
388
DEVELOPMENT.md
Normal 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
166
README.md
@@ -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:
|
|
||||||
|
|
||||||
[](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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ function startRPC() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
rpc.login({ clientId }).catch(err => {
|
rpc.login({ clientId }).catch(() => {
|
||||||
// Since 1.6.2, it no longer print the error. This is because if discord is not opened, it will send an error.
|
// 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");
|
logger.info("Unable to connect to the Discord RPC. Discord is maybe not launched");
|
||||||
@@ -123,14 +123,6 @@ 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) {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
23
app/tray.js
23
app/tray.js
@@ -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)")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
48
main.js
48
main.js
@@ -38,7 +38,7 @@ 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");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In-memory snapshot of application feature flags.
|
* In-memory snapshot of application feature flags.
|
||||||
@@ -53,7 +53,7 @@ if (!configFeatures.enableHardwareAcceleration){
|
|||||||
logger.info("Enable Hardware Acceleration")
|
logger.info("Enable Hardware Acceleration")
|
||||||
}
|
}
|
||||||
|
|
||||||
if(configFeatures.devMode){
|
if(devMode){
|
||||||
/**
|
/**
|
||||||
* Start devTron extensions - @see https://github.com/electron/devtron
|
* Start devTron extensions - @see https://github.com/electron/devtron
|
||||||
*/
|
*/
|
||||||
@@ -74,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.
|
||||||
@@ -91,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
|
||||||
*/
|
*/
|
||||||
@@ -112,7 +135,7 @@ app.whenReady().then(async () => {
|
|||||||
|
|
||||||
createSplashWindow();
|
createSplashWindow();
|
||||||
|
|
||||||
if (!configFeatures.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);
|
||||||
@@ -138,7 +161,7 @@ app.whenReady().then(async () => {
|
|||||||
getMainWindow().show();
|
getMainWindow().show();
|
||||||
|
|
||||||
if (configFeatures.systemTray) {
|
if (configFeatures.systemTray) {
|
||||||
createSystemTray(getMainWindow(), configFeatures.devMode);
|
createSystemTray(devMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configFeatures.discordRPC) startRPC();
|
if (configFeatures.discordRPC) startRPC();
|
||||||
@@ -166,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
12
package-lock.json
generated
@@ -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": {
|
||||||
|
|||||||
90
package.json
90
package.json
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "freedom-loader",
|
"name": "freedom-loader",
|
||||||
|
"desktopName": "com.masteracnolo.freedomloader",
|
||||||
"productName": "Freedom Loader",
|
"productName": "Freedom Loader",
|
||||||
"version": "1.6.2",
|
"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 |
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
f [[ "$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
|
||||||
|
|
||||||
@@ -101,16 +108,6 @@ RELEASE_NOTES="# Freedom Loader - $VERSION
|
|||||||
## 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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}"`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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;
|
||||||
|
|||||||
12
server/helpers/playlist.helpers.js
Normal file
12
server/helpers/playlist.helpers.js
Normal 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("@");
|
||||||
|
}
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -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 };
|
||||||
@@ -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,23 +37,23 @@ 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);
|
||||||
|
|
||||||
const playlistPath = path.join(basePath, sanitizedTitle);
|
const playlistPath = path.join(basePath, sanitizedTitle);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(playlistPath)) {
|
if (!fs.existsSync(playlistPath)) {
|
||||||
fs.mkdirSync(playlistPath, { recursive: true });
|
fs.mkdirSync(playlistPath, { recursive: true });
|
||||||
logger.info(`Playlist folder created: ${playlistPath}`);
|
logger.info(`Playlist folder created: ${playlistPath}`);
|
||||||
return playlistPath;
|
return playlistPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
let counter = 1;
|
let counter = 1;
|
||||||
let newPath;
|
let newPath;
|
||||||
|
|
||||||
while (counter <= 1000) {
|
while (counter <= 1000) {
|
||||||
newPath = path.join(basePath, `${sanitizedTitle} ${counter}`);
|
newPath = path.join(basePath, `${sanitizedTitle} ${counter}`);
|
||||||
if (!fs.existsSync(newPath)) {
|
if (!fs.existsSync(newPath)) {
|
||||||
@@ -75,17 +63,17 @@ function createPlaylistFolder(basePath, playlistTitle) {
|
|||||||
}
|
}
|
||||||
counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
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}`);
|
||||||
|
|
||||||
// Fallback: create folder "Untitled Playlist X"
|
// Fallback: create folder "Untitled Playlist X"
|
||||||
let counter = 1;
|
let counter = 1;
|
||||||
let fallbackPath;
|
let fallbackPath;
|
||||||
|
|
||||||
while (counter <= 1000) {
|
while (counter <= 1000) {
|
||||||
fallbackPath = path.join(basePath, `Untitled Playlist ${counter}`);
|
fallbackPath = path.join(basePath, `Untitled Playlist ${counter}`);
|
||||||
if (!fs.existsSync(fallbackPath)) {
|
if (!fs.existsSync(fallbackPath)) {
|
||||||
@@ -100,7 +88,7 @@ function createPlaylistFolder(basePath, playlistTitle) {
|
|||||||
}
|
}
|
||||||
counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.error(`Could not create playlist folder after 1000 attempts`);
|
logger.error(`Could not create playlist folder after 1000 attempts`);
|
||||||
throw new Error("Unable to create playlist folder");
|
throw new Error("Unable to create playlist folder");
|
||||||
}
|
}
|
||||||
@@ -139,17 +127,17 @@ function fetchDownload(options, progressListeners, speedListeners, stageListener
|
|||||||
const userConfig = reloadFeatures();
|
const userConfig = reloadFeatures();
|
||||||
|
|
||||||
logger.info(`CONFIG createPlaylistFolders: ${userConfig.createPlaylistFolders}`);
|
logger.info(`CONFIG createPlaylistFolders: ${userConfig.createPlaylistFolders}`);
|
||||||
|
|
||||||
let outputFolder = options.outputFolder || defaultDownloadFolder;
|
let outputFolder = options.outputFolder || defaultDownloadFolder;
|
||||||
|
|
||||||
// Normalize path and validate it's safe (within Users folder)
|
// Normalize path and validate it's safe (within Users folder)
|
||||||
let safeOutputFolder = path.resolve(outputFolder);
|
let safeOutputFolder = path.resolve(outputFolder);
|
||||||
|
|
||||||
if (!isSafePath(safeOutputFolder)) {
|
if (!isSafePath(safeOutputFolder)) {
|
||||||
logger.warn(`Path not allowed, using default instead: ${safeOutputFolder}`);
|
logger.warn(`Path not allowed, using default instead: ${safeOutputFolder}`);
|
||||||
safeOutputFolder = path.resolve(defaultDownloadFolder);
|
safeOutputFolder = path.resolve(defaultDownloadFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create download folder if it doesn't exist
|
// Create download folder if it doesn't exist
|
||||||
try {
|
try {
|
||||||
fs.mkdirSync(safeOutputFolder, { recursive: true });
|
fs.mkdirSync(safeOutputFolder, { recursive: true });
|
||||||
@@ -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 {
|
||||||
@@ -196,13 +184,13 @@ function fetchDownload(options, progressListeners, speedListeners, stageListener
|
|||||||
else reject(new Error(`YT-DLP failed with code : ${code}`));
|
else reject(new Error(`YT-DLP failed with code : ${code}`));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
child.stdout.on("data", data => {
|
child.stdout.on("data", data => {
|
||||||
data.toString().split("\n").forEach(line => {
|
data.toString().split("\n").forEach(line => {
|
||||||
if (!line.trim()) return;
|
if (!line.trim()) return;
|
||||||
logger.info(`[yt-dlp] ${line}`);
|
logger.info(`[yt-dlp] ${line}`);
|
||||||
|
|
||||||
// Progress Bar
|
// Progress Bar
|
||||||
if (line.startsWith("[download] Destination:")) {
|
if (line.startsWith("[download] Destination:")) {
|
||||||
progressListeners.forEach(fn => fn("reset"));
|
progressListeners.forEach(fn => fn("reset"));
|
||||||
stageListeners.forEach(fn => fn("Downloading..."));
|
stageListeners.forEach(fn => fn("Downloading..."));
|
||||||
@@ -253,7 +241,7 @@ function fetchDownload(options, progressListeners, speedListeners, stageListener
|
|||||||
data.toString().split("\n").forEach(line => line.trim() && logger.error(`[yt-dlp stderr] ${line}`));
|
data.toString().split("\n").forEach(line => line.trim() && logger.error(`[yt-dlp stderr] ${line}`));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +258,7 @@ function fetchDownload(options, progressListeners, speedListeners, stageListener
|
|||||||
function cancelDownload() {
|
function cancelDownload() {
|
||||||
if (currentDownloadProcess) {
|
if (currentDownloadProcess) {
|
||||||
logger.info("Cancelling download and killing all child processes...");
|
logger.info("Cancelling download and killing all child processes...");
|
||||||
|
|
||||||
// Force kill the process and all its children with SIGKILL
|
// Force kill the process and all its children with SIGKILL
|
||||||
try {
|
try {
|
||||||
// Try to kill with SIGKILL on Windows (process group) or Unix
|
// Try to kill with SIGKILL on Windows (process group) or Unix
|
||||||
@@ -286,7 +274,7 @@ function cancelDownload() {
|
|||||||
// Fallback to regular kill
|
// Fallback to regular kill
|
||||||
currentDownloadProcess.kill('SIGKILL');
|
currentDownloadProcess.kill('SIGKILL');
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDownloadProcess = null;
|
currentDownloadProcess = null;
|
||||||
logger.info("Download cancelled successfully");
|
logger.info("Download cancelled successfully");
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user