17 Commits

Author SHA1 Message Date
Axel Nicolas
4f03c5876f Merge pull request #44 from MasterAcnolo/sync
Sync
2026-07-26 15:15:52 +02:00
MasterAcnolo
a5f05f32af fix: readme missing items and [!TIP] 2026-07-26 11:21:23 +02:00
MasterAcnolo
0d1b1e4919 refactor: security.md 2026-07-26 09:37:39 +02:00
MasterAcnolo
0e77a94ec7 feat: enhance logger by displaying systeminfo at start of the app 2026-07-26 09:37:27 +02:00
MasterAcnolo
7f481d20b2 sync: update dependencies and this is ready for windows 2026-07-25 12:49:36 +02:00
MasterAcnolo
1ff32ca021 sync: to work on windows 2026-07-25 11:32:48 +02:00
MasterAcnolo
90d8a9f65d fix: correct use of logger for path.helpers.js 2026-07-22 20:41:54 +02:00
MasterAcnolo
d5965c2ce0 fix: bring back theme features for Linux 2026-07-22 18:44:30 +02:00
MasterAcnolo
15f59a6093 fix: bring back theme features for Linux 2026-07-22 18:16:33 +02:00
MasterAcnolo
8b31436e25 feat: publish package to fedora copr, add script to automaticaly build and upload the file.
Enhance readme and fix auto updater issue
2026-06-29 10:44:17 +02:00
MasterAcnolo
7d5203001b add: BINARIES.md readme file. About how to install the binaries for dev mode 2026-06-28 21:49:53 +02:00
MasterAcnolo
60709c4fad fix: don't show dialog box if no previous version is available. 2026-06-28 21:48:24 +02:00
MasterAcnolo
2fb6509edc add: dark.zip and white.zip theme to the repo 2026-06-28 21:34:26 +02:00
MasterAcnolo
eebce83976 fix: small code quality and mismatch. 2026-06-28 21:34:00 +02:00
MasterAcnolo
a56a070bc8 feat: support Linux. Refactor resources architecture. Create build script
Currently, themes are disabled and firefox notification too.
2026-06-28 21:32:49 +02:00
MasterAcnolo
e1ef7032bb add: default theme applied in app. If no theme is there 2026-06-18 21:55:50 +02:00
MasterAcnolo
e7333b7b6b fix: missing enableHardwareAcceleration settings in readme 2026-06-10 21:43:38 +02:00
32 changed files with 2376 additions and 1086 deletions

26
.gitignore vendored
View File

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

96
BINARIES.md Normal file
View File

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

View File

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

115
README.md
View File

@@ -5,9 +5,9 @@
alt="Banner"
style="width: 50%;"/></a>
### **A clean, open-source multimedia downloader for Windows**
### **A clean, open-source multimedia downloader for Windows and Linux**
[![Release](https://img.shields.io/badge/Release-1.5.1-blue?style=for-the-badge)](https://github.com/MasterAcnolo/Freedom-Loader/releases)
[![Release](https://img.shields.io/badge/Release-1.6.0-blue?style=for-the-badge)](https://github.com/MasterAcnolo/Freedom-Loader/releases)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-red.svg?style=for-the-badge)](https://www.gnu.org/licenses/gpl-3.0)
[![Website](https://img.shields.io/badge/Website-Visit-404040?style=for-the-badge)](https://masteracnolo.github.io/Freedom-Loader-Site/)
[![Workshop](https://img.shields.io/badge/Worshop-Visit-0E05A1?style=for-the-badge)](https://masteracnolo.github.io/Freedom-Loader-Workshop/)
@@ -86,35 +86,86 @@ The primary goal is to make media downloading accessible to users who want offli
## Installation
[![Fedora Copr](https://img.shields.io/badge/Fedora-Copr-294172?style=for-the-badge&logo=fedora&logoColor=white)](https://copr.fedorainfracloud.org/coprs/masteracnolo/freedom-loader/)
[![Debian/Ubuntu](https://img.shields.io/badge/.deb-Debian%2FUbuntu-A81D33?style=for-the-badge&logo=debian&logoColor=white)](https://github.com/MasterAcnolo/Freedom-Loader/releases)
[//]: # ([![Snap Store]&#40;https://img.shields.io/badge/Snap-Store-82BEA0?style=for-the-badge&logo=snapcraft&logoColor=white&#41;]&#40;https://snapcraft.io/freedom-loader&#41;)
[![AppImage](https://img.shields.io/badge/AppImage-Universal-1E88E5?style=for-the-badge&logo=linux&logoColor=white)](https://github.com/MasterAcnolo/Freedom-Loader/releases)
### Requirements
- **Operating System**: Windows 10 or later
- **Browser**: Mozilla Firefox (required for cookie extraction)
- [Download Firefox](https://www.firefox.com)
- **Windows**: Windows 10 or later
- **Linux**: any modern x86_64 distribution
- **Browser**: Mozilla Firefox ([download](https://www.firefox.com)) required for cookie extraction, on both platforms
### Standard Installation
### Windows
1. Download the latest release from the [Releases page](https://github.com/MasterAcnolo/Freedom-Loader/releases)
2. Run the downloaded `.exe` installer
3. If Windows Defender shows a warning, click "More info" then "Run anyway"
4. Follow the installation wizard
1. Download the latest `.exe` from the [Releases page](https://github.com/MasterAcnolo/Freedom-Loader/releases)
2. Run the installer (click "More info" → "Run anyway" if Windows Defender warns you)
3. Follow the installation wizard
### Linux
#### Fedora (via Copr — recommended)
Installing via Copr lets `dnf` handle future updates automatically:
```bash
sudo dnf copr enable masteracnolo/freedom-loader
sudo dnf install freedom-loader
```
#### Debian / Ubuntu (.deb)
```bash
# Download the latest .deb from the Releases page, then:
sudo apt install ./freedom-loader_1.x.x_amd64.deb
```
[//]: # (#### Snap &#40;any distro with snapd&#41;)
[//]: # ()
[//]: # (```bash)
[//]: # (sudo snap install freedom-loader)
[//]: # (```)
#### RPM (Fedora/RHEL/openSUSE, without Copr)
```bash
# Download the latest .rpm from the Releases page, then:
sudo dnf install ./freedom-loader-1.x.y.x86_64.rpm
```
#### AppImage (any distro, no installation needed)
```bash
# Download the latest .AppImage from the Releases page, then:
chmod +x Freedom-Loader-1.x.y.AppImage
./Freedom-Loader-1.x.y.AppImage
```
Once installed via any method, launch Freedom Loader from your application menu, or from a terminal with:
```bash
freedom-loader
```
### File Locations
Downloaded files are stored in:
```
C:\Users\[USERNAME]\Downloads\Freedom Loader
```
> [!NOTE]
> *You can change it whenever you want. Just click the "Edit" button in the UI.*
| | Windows | Linux |
|---------------|---------------------------------------------------------|--------------------------------------|
| **Downloads** | `C:\Users\[USERNAME]\Downloads\Freedom Loader` | `~/Downloads/Freedom Loader` |
| **Logs** | `C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\` | `~/.local/share/FreedomLoader/logs/` |
Application logs can be found at:
```
C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\
```
> [!NOTE]
> The download folder can be changed anytime via the "Edit" button in the UI, on both platforms.
## Usage
> [!TIP]
> On Linux, you can start Freedom Loader using `freedom-loader` command
### Basic Download
1. Launch Freedom Loader
@@ -138,7 +189,7 @@ The application supports various configuration options:
If you encounter issues:
1. Restart the application
2. Check the logs in `AppData\Local\FreedomLoader\logs\`
2. Check the logs in `AppData\Local\FreedomLoader\logs\` or `~/.local/share/FreedomLoader/logs`
3. Open the developer console: `CTRL + SHIFT + I` → "Toggle Developer Tools"
4. Create an issue on GitHub with logs and reproduction steps
@@ -164,10 +215,17 @@ If you encounter issues:
Freedom Loader can be configured either through the settings panel in the UI or by directly editing the `config.json` file located in the application data directory.
### Configuration File Location
#### Windows
```
%APPDATA%\Freedom Loader\config.json
```
#### Linux
```
~/.config/Freedom Loader/config.json
```
### Available Options
```json
{
@@ -185,6 +243,7 @@ Freedom Loader can be configured either through the settings panel in the UI or
"outputTitleCheck": true,
"downloadSystem": true,
"notifySystem": true,
"enableHardwareAcceleration": true,
"theme": "dark"
}
```
@@ -192,7 +251,7 @@ Freedom Loader can be configured either through the settings panel in the UI or
### Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|------------------------------|---------|----------|------------------------------------------------------------|
| `autoUpdate` | boolean | `true` | Enable automatic application updates |
| `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration |
| `customTopBar` | boolean | `true` | Use custom application top bar |
@@ -207,7 +266,7 @@ Freedom Loader can be configured either through the settings panel in the UI or
| `outputTitleCheck` | boolean | `true` | Verify output file titles |
| `downloadSystem` | boolean | `true` | Enable download system |
| `notifySystem` | boolean | `true` | Enable system notifications |
| `enableHardwareAcceleration` | boolean | `true` | Enable HArdware Acceleration |
| `enableHardwareAcceleration` | boolean | `true` | Enable Hardware Acceleration |
| `theme` | string | `dark` | Current Theme Name |
### Supported Codecs
@@ -274,7 +333,7 @@ Freedom-Loader/
│ ├── settingsPanel.css
│ ├── topbar.css
│ └── videoinfo.css
├── ressources/ # Internal resources (binaries)
├── resources/ # Internal resources (binaries)
├── server/ # Express backend server
│ ├── logger.js
│ ├── server.js
@@ -385,7 +444,7 @@ npm run build
This project relies on several external binaries.
Some of them are **not included** in the public repository and must be added manually.
You must download the required binaries and place them in the `ressources` folder, using the exact filenames listed below.
You must download the required binaries and place them in the `resources` folder, using the exact filenames listed below.
#### Required binaries
@@ -406,7 +465,7 @@ You must download the required binaries and place them in the `ressources` folde
Final folder structure:
```
ressources/
resources/
├── deno.exe
├── ffmpeg.exe
├── ffprobe.exe
@@ -422,7 +481,7 @@ ressources/
- Write clear commit messages
- Test thoroughly before submitting changes
- Update documentation when adding features
- Maintain compatibility with Windows 10+
- Maintain compatibility with Windows 10+ and Linux
## Roadmap
@@ -444,7 +503,7 @@ Use the GitHub Issues system and include:
- Clear description of the issue
- Steps to reproduce
- Expected vs actual behavior
- Relevant logs from `AppData\Local\FreedomLoader\logs\`
- Relevant logs from `AppData\Local\FreedomLoader\logs\` or `~/.local/share/FreedomLoader/logs/`
- Screenshots if applicable
### Feature Requests

View File

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

View File

@@ -15,7 +15,7 @@ const { logger } = require("../server/logger");
/**
* Configures autoUpdater behavior:
* - disables automatic download
* - disables automatic install on quit
* - disables automatic installation on quit
* (manual user-controlled update flow)
*/
autoUpdater.autoDownload = false;
@@ -51,7 +51,7 @@ function initAutoUpdater(mainWindow) {
});
if (response === 0) {
autoUpdater.downloadUpdate();
await autoUpdater.downloadUpdate();
} else {
mainWindow?.webContents.executeJavaScript(
`window.showUpdateBadge && window.showUpdateBadge("${info.version}")`
@@ -96,8 +96,20 @@ function initAutoUpdater(mainWindow) {
* Logs failure and displays an error dialog to user.
*/
autoUpdater.on("error", (err) => {
logger.error("Auto update error:", err.message);
dialog.showErrorBox("Update Error", err.message);
const msg = err?.message || "";
/**
* If no update is available, I put this because there is no Linux version before 1.6.0
* @type {boolean}
*/
const isNoUpdateAvailable = /404/.test(msg) || /Cannot find latest.*\.yml/i.test(msg);
if (isNoUpdateAvailable) {
logger.warn("Auto update: no update metadata found (probably no previous release), ignoring", msg);
return;
}
logger.error("Auto update error:", msg);
});
checkForUpdates();
@@ -108,6 +120,14 @@ function initAutoUpdater(mainWindow) {
* Separated from init for reusability and testability.
*/
async function checkForUpdates() {
if (process.env.SNAP) {
logger.info("Running as Snap, update managed by snapd, skipping electron-updater");
return;
}
if (!require("electron").app.isPackaged) return ;
try {
await autoUpdater.checkForUpdates();
} catch (err) {

View File

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

View File

@@ -12,7 +12,6 @@
const { ipcMain, dialog, shell } = require("electron");
const fs = require("fs");
const path = require("path");
const { logger, logDir } = require("../server/logger");
const { configFeatures, featuresPath } = require("../config");
const { getThemes, reloadThemes } = require("./themeManager");

View File

@@ -62,10 +62,19 @@ function validateDownloadPath(userPath) {
* Required to prevent path traversal or alias bypass.
*/
const resolved = fs.realpathSync(path.resolve(userPath));
if (!fs.existsSync(resolved)) {
fs.mkdirSync(resolved, { recursive: true });
logger.info(`Download folder created: ${resolved}`);
}
const real = fs.realpathSync(resolved);
if (!isSafePath(resolved)) {
throw new Error("Path not allowed: system folders are blocked!");
}
return resolved;
return real;
}
catch (err) {

View File

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

BIN
build/app-icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@@ -37,7 +37,7 @@ app.setAppUserModelId("com.masteracnolo.freedomloader");
/**
* Load the app dependencies for hardware choice
*/
const { logger, logSessionStart, logSessionEnd } = require("./server/logger");
const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger");
const { configFeatures } = require("./config");
/**
@@ -79,7 +79,7 @@ const { updateYtDlp } = require("./app/ytDlpUpdater");
const { createMainWindow, getMainWindow } = require("./app/windowManager");
const { registerIpcHandlers } = require("./app/ipcHandlers");
const { createSplashWindow, closeSplashWindow, setSplashProgress } = require("./app/splashManager");
const { userThemesPath, initUserThemes } = require("./server/helpers/path.helpers");
const { userThemesPath, initUserThemes, isWindows, validateBinaries, defaultDownloadFolder } = require("./server/helpers/path.helpers");
/**
@@ -99,7 +99,7 @@ app.on("second-instance", () => {
});
app.whenReady().then(async () => {
logSessionStart();
logSessionStart(logDir, defaultDownloadFolder);
createSplashWindow();
@@ -116,6 +116,9 @@ app.whenReady().then(async () => {
registerIpcHandlers(getMainWindow);
setSplashProgress(2); // Loading themes
validateBinaries();
initUserThemes();
await initThemes(userThemesPath);

2199
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,17 @@
{
"name": "freedom-loader",
"productName": "Freedom Loader",
"version": "1.5.1",
"author": "MasterAcnolo",
"version": "1.6.0",
"author": "MasterAcnolo <MasterAcnolo@users.noreply.github.com>",
"description": "Freedom Loader",
"main": "main.js",
"scripts": {
"start": "electron --trace-warnings .",
"postinstall": "chmod +x resources/binaries/linux/* 2>/dev/null || true",
"build": "electron-builder",
"build:win": "electron-builder --win",
"build:linux": "electron-builder --linux",
"build:all": "electron-builder -wl",
"update": "npm update"
},
"dependencies": {
@@ -23,10 +27,9 @@
"winston-daily-rotate-file": "^5.0.0"
},
"devDependencies": {
"@electron/devtron": "^2.1.1",
"electron": "^41.2.0",
"electron-builder": "^26.8.1",
"@electron/devtron": "^2.1.1"
"electron-builder": "^25.1.8"
},
"build": {
"appId": "com.masteracnolo.freedomloader",
@@ -35,7 +38,8 @@
{
"provider": "github",
"owner": "MasterAcnolo",
"repo": "Freedom-Loader"
"repo": "Freedom-Loader",
"releaseType": "draft"
}
],
"files": [
@@ -57,10 +61,6 @@
"from": "build/confirm-icon.png",
"to": "confirm-icon.png"
},
{
"from": "build/app-icon.ico",
"to": "app-icon.ico"
},
{
"from": "build/banner.bmp",
"to": "banner.bmp"
@@ -73,22 +73,6 @@
"from": "build/error.png",
"to": "error.png"
},
{
"from": "ressources/yt-dlp.exe",
"to": "binaries/yt-dlp.exe"
},
{
"from": "ressources/ffmpeg.exe",
"to": "binaries/ffmpeg.exe"
},
{
"from": "ressources/ffprobe.exe",
"to": "binaries/ffprobe.exe"
},
{
"from": "ressources/deno.exe",
"to": "binaries/deno.exe"
},
{
"from": "config/config.default.json",
"to": "config/config.default.json"
@@ -96,7 +80,26 @@
],
"win": {
"target": "nsis",
"icon": "build/app-icon.ico"
"icon": "build/app-icon.ico",
"artifactName": "${productName}-Setup-${version}.${ext}",
"extraResources": [
{
"from": "resources/binaries/win-32/yt-dlp.exe",
"to": "binaries/yt-dlp.exe"
},
{
"from": "resources/binaries/win-32/ffmpeg.exe",
"to": "binaries/ffmpeg.exe"
},
{
"from": "resources/binaries/win-32/ffprobe.exe",
"to": "binaries/ffprobe.exe"
},
{
"from": "resources/binaries/win-32/deno.exe",
"to": "binaries/deno.exe"
}
]
},
"nsis": {
"oneClick": false,
@@ -108,6 +111,58 @@
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "Freedom Loader"
},
"linux": {
"target": [
"AppImage",
"deb",
"rpm",
"snap"
],
"category": "Utility",
"icon": "build/app-icon.png",
"extraResources": [
{
"from": "resources/binaries/linux/yt-dlp",
"to": "binaries/yt-dlp"
},
{
"from": "resources/binaries/linux/ffmpeg",
"to": "binaries/ffmpeg"
},
{
"from": "resources/binaries/linux/ffprobe",
"to": "binaries/ffprobe"
},
{
"from": "resources/binaries/linux/deno",
"to": "binaries/deno"
}
]
},
"deb": {
"fpm": [
"./package/com.masteracnolo.freedomloader.metainfo.xml=/usr/share/metainfo/com.masteracnolo.freedomloader.metainfo.xml"
]
},
"rpm": {
"fpm": [
"./package/com.masteracnolo.freedomloader.metainfo.xml=/usr/share/metainfo/com.masteracnolo.freedomloader.metainfo.xml"
]
},
"snap": {
"confinement": "strict",
"plugs": [
"default",
"home",
"removable-media",
"network",
"audio-playback",
"desktop",
"desktop-legacy",
"x11",
"wayland"
]
}
}
}

View File

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

View File

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

View File

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

View File

@@ -290,6 +290,7 @@
<!-- Scripts -->
<script src="script/toast.js"></script>
<script src="script/customConfiguration.js"></script>
<script src="script/clipboardPaste.js"></script>
<script src="script/custompath.js"></script>
<script src="script/settingsPanel.js"></script>

View File

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

View File

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

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

View File

@@ -271,6 +271,6 @@ async function init() {
})
};
}
init();

View File

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

BIN
resources/binaries/linux/yt-dlp Executable file

Binary file not shown.

20
scripts/make-copr-srpm.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
VERSION=$(node -p "require('$ROOT_DIR/package.json').version")
mkdir -p "$ROOT_DIR/srpm-out"
TMP_DIR="$ROOT_DIR/.copr-build-tmp"
mkdir -p "$TMP_DIR"
TMP_SPEC="$TMP_DIR/freedom-loader.spec"
sed "s/__VERSION__/$VERSION/g" "$ROOT_DIR/package/freedom-loader.spec" > "$TMP_SPEC"
rpmbuild -bs "$TMP_SPEC" \
--define "_sourcedir $ROOT_DIR/dist" \
--define "_srcrpmdir $ROOT_DIR/srpm-out"
rm -rf "$TMP_DIR"
echo "SRPM généré : $ROOT_DIR/srpm-out/freedom-loader-$VERSION-1*.src.rpm"

View File

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

View File

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

View File

@@ -4,9 +4,11 @@ const fs = require("fs");
const path = require("path");
const os = require("os");
const config = require("../config");
const {isWindows} = require("./helpers/path.helpers");
const { logSystemInfo } = require("./sysinfo");
// Logs folder in Windows
const logDir = path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs");
const logDir = isWindows ? path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs") : path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share"), "FreedomLoader", "logs");
// Create "Logs" folder if needed
try {
@@ -51,9 +53,10 @@ const logger = createLogger({
/**
* Start Log Session
*/
function logSessionStart() {
function logSessionStart(logDir, downloadPath) {
logger.info(`--- Starting session: ${new Date().toISOString()} ---`);
logger.info(`Application Version: ${config.version}`)
logSystemInfo(logger, logDir, downloadPath)
}
/**

142
server/sysinfo.js Normal file
View File

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

BIN
theme/dark.zip Normal file

Binary file not shown.

BIN
theme/light.zip Normal file

Binary file not shown.