56 Commits
1.3.1 ... 1.4.0

Author SHA1 Message Date
MasterAcnolo
ac0b4a0750 1.4.0 - It's time to sleep
1.4.0 - It's time to sleep
2026-01-22 22:09:24 +01:00
MasterAcnolo
29ca2ea496 Fix: Auto Update mispelling (Am a monkey) 2026-01-22 21:39:07 +01:00
MasterAcnolo
2a3905bdfb Add: Video Codec 2026-01-21 20:43:15 +01:00
MasterAcnolo
d3755ab57d Fix: Remove unusued "Settings.svg" icon 2026-01-21 20:25:34 +01:00
MasterAcnolo
9824e107fe Fix: Try to force AAC Audio Codec 2026-01-21 13:29:33 +01:00
MasterAcnolo
e437722937 Merge pull request #27 from MasterAcnolo/sync
Sync
2026-01-21 11:32:19 +01:00
MasterAcnolo
fd8c4ae33c Fix: Check Info config every request 2026-01-21 11:16:53 +01:00
MasterAcnolo
967144bd55 Fix: Config not working propely 2026-01-21 11:08:42 +01:00
MasterAcnolo
cd8eb05020 Fix: Remove Unusued Import 2026-01-21 11:08:20 +01:00
MasterAcnolo
590060e504 Fix: Reference position bottom 2026-01-20 22:34:33 +01:00
MasterAcnolo
26c7d4d2ee Because why not ? 2026-01-20 22:30:41 +01:00
MasterAcnolo
caa987fa42 Fix: Remove unusued files 2026-01-20 22:30:22 +01:00
MasterAcnolo
ccb090e4b2 Add: Settings theme color 2026-01-20 22:29:56 +01:00
MasterAcnolo
8d070ed10d Fix: Neon Theme rename 2026-01-20 22:29:22 +01:00
MasterAcnolo
343dbb26a2 Feat: Settings Panel 2026-01-20 22:28:53 +01:00
MasterAcnolo
fef4f6de36 Sync: Synchro Settings Panel 2026-01-18 14:23:17 +01:00
MasterAcnolo
1a61972750 Add: Settings Icon 2026-01-18 14:11:01 +01:00
MasterAcnolo
45ae5d7f98 Fix: Update Dependencies 2026-01-18 14:09:41 +01:00
MasterAcnolo
fecb123d0e Fix: Indent Index.html 2026-01-18 13:51:17 +01:00
MasterAcnolo
5c62ed081a Fix: Update ReadME Banner Redirection 2026-01-18 11:28:27 +01:00
MasterAcnolo
8f0f31041d Fix: Remove Unusued Dependencies 2026-01-16 09:42:32 +01:00
MasterAcnolo
0e48ab3b4c Fix: Default Theme Info Color 2026-01-16 09:42:09 +01:00
MasterAcnolo
d4c33bcf2d FIx: Updated Appliction Version because i did too much work to be a "minor release" 2026-01-15 22:54:40 +01:00
MasterAcnolo
2f105f5145 Fix: Put config.json file into config folder for better readability + Fix Yt-DLP Path 2026-01-15 22:01:26 +01:00
MasterAcnolo
b7a193b062 Fix: Remove unusued import server.js 2026-01-15 21:23:26 +01:00
MasterAcnolo
65364d3095 Fix: Update Dependencies 2026-01-15 21:22:57 +01:00
MasterAcnolo
c356e2e30d Fix: Using ASAR for every parts of the projects (the server is now in it) and i put the binaries in a /binaries folder. It's better for readability of the resources folder 2026-01-15 21:22:40 +01:00
MasterAcnolo
e45810b54c Add: Better logs. Write logs when a download is init 2026-01-15 16:22:29 +01:00
MasterAcnolo
55db30febc Fix: Remove Useless Console.log 2026-01-15 16:21:43 +01:00
MasterAcnolo
163faec221 Feat: Add Configuration Button. user can edit config.json as wanted. Linked and IPC 2026-01-15 16:01:24 +01:00
MasterAcnolo
5398fed928 Fix: Remove unusued Variable 2026-01-15 10:59:45 +01:00
MasterAcnolo
f959171db5 Feat: Add CustomVideo Codec Choice with verification in config.json 2026-01-15 10:57:37 +01:00
MasterAcnolo
01b6bcd02c Feat: Add Newline YT-DLP Args. Add Toggle Switches for verbose, autodownloadplaylist + Fix Thumbail misspell 2026-01-15 10:00:17 +01:00
MasterAcnolo
0ff4715b35 Fix: Correct Config.json file for packaged version 2026-01-15 09:28:46 +01:00
MasterAcnolo
745a73a5b0 Feat: Add version badge controlled. Before we needed to write with the hand for the front. But now there is an IPC event that provide App Version from package.json 2026-01-15 09:03:50 +01:00
MasterAcnolo
a69114edce Feat: Package Update + Add NSIS Installer Banner 2026-01-14 22:49:34 +01:00
MasterAcnolo
489a4766fa Feat: Path Verification (Security #4) 2026-01-14 21:47:38 +01:00
MasterAcnolo
adcee62386 Fix: ReadME Badge Version 2026-01-14 20:41:55 +01:00
MasterAcnolo
b867d819b7 Fix: Increment App Version for 1.3.2 2026-01-14 14:04:06 +01:00
MasterAcnolo
31d8b20a76 Feat: Implement Config File. Read by Front and Back. It aims to give possibility to disable some features quickly. Currenty: DiscordRPC, AutoCheckInfo, AutoUpdate, Custom TopBar, AddMetadata, Add Thumbail were put and implement. Other need to be implement. That's the begin of Settings Panel 2026-01-14 14:03:32 +01:00
MasterAcnolo
42ae0e7204 Feat: Add Codec H264 to vidéo output 2026-01-14 10:06:17 +01:00
MasterAcnolo
43869420c0 Update: Update Script + Dependencies 2026-01-14 10:04:38 +01:00
MasterAcnolo
6c9d296fbd Add: Some Variables in config.js. For Modules 2025-12-30 11:38:18 +01:00
MasterAcnolo
008428f10a Fix: Remove Unsued Import in Controller 2025-12-28 12:47:00 +01:00
MasterAcnolo
ae7d338474 Add: Rate Limit to front rendering. Using Components for reusability 2025-12-28 12:40:55 +01:00
MasterAcnolo
12ff25cc4b Fix: Error Handling Download Controller 2025-12-27 12:15:28 +01:00
MasterAcnolo
f31767e7e7 Fix: Error Handling Info Controller 2025-12-27 12:14:21 +01:00
MasterAcnolo
7c4f2841e1 Fix: Security #5 2025-12-27 11:41:52 +01:00
MasterAcnolo
c451ddd042 Add pull request template for consistent feature submissions 2025-12-20 13:51:55 +01:00
MasterAcnolo
da6311055f Replace feature and version templates in PULL_REQUEST_TEMPLATE directory 2025-12-20 13:46:47 +01:00
MasterAcnolo
945efb2059 Add feature issue template for improved feature requests 2025-12-20 13:46:00 +01:00
MasterAcnolo
ab2881f8e9 Add version template for changelog and roadmap 2025-12-20 13:45:39 +01:00
MasterAcnolo
f23811fff4 Create SECURITY.md 2025-12-20 13:39:50 +01:00
MasterAcnolo
19ab77501b Create CONTRIBUTING.md for project guidelines
Added a comprehensive contributing guide for Freedom Loader, outlining how to report issues, submit pull requests, and improve documentation.
2025-12-20 13:35:21 +01:00
MasterAcnolo
37eeed583a Add Contributor Covenant Code of Conduct
This document outlines the Contributor Covenant Code of Conduct, including our pledge, standards of behavior, enforcement responsibilities, and consequences for violations.
2025-12-20 13:29:21 +01:00
MasterAcnolo
caf08efb90 Update issue templates 2025-12-20 13:25:58 +01:00
44 changed files with 1940 additions and 1010 deletions

View File

@@ -0,0 +1,35 @@
---
name: "[BUG] Issues Template "
about: Report something that doesn't work fine
title: "[BUG] Name of the bug / issues"
labels: bug
assignees: MasterAcnolo
---
## Check Before Reporting
Make sure this bug doesn't already exist in the issues.
## Describe the Bug
A clear and concise description of the problem.
## Steps to Reproduce
1. Go to…
2. Click on…
3. See the error…
## Expected Behavior
What you expected the program to do.
## Logs
Add logs if possible. Available at `C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\LOGS-20xx-xx-xx.log`.
## App Version
State the version of the app you are using.
## Environment
- Version: [e.g. 1.2.4]
- Browser / Other: [e.g. Chrome, Node.js 20]
- Any others information that can be usefull for me
## Screenshots (if applicable)

View File

@@ -0,0 +1,22 @@
---
name: "[FEAT] Feature Template"
about: Suggest an idea for this project
title: "[FEAT] Name of the features"
labels: enhancement
assignees: MasterAcnolo
---
## Check Before Requesting
Make sure this feature doesn't already exist in the issues.
## Feature Description
Describe the feature you want to add or improve.
## Why is this Feature Useful?
Explain the benefit for users or the project.
## Examples / Context
Provide an example or use case if possible.
## Screenshots / Mockups (if applicable)

View File

@@ -0,0 +1,29 @@
## Description
Describe the feature or improvement you are adding.
Include why it's useful and what problem it solves.
## Type of Change
- [ ] New feature
- [ ] Improvement / Refactor
- [ ] Documentation update
## Checklist
- [ ] Code tested locally
- [ ] No linting errors
- [ ] Added or updated tests if relevant
- [ ] Updated documentation if relevant
## Verification Steps
Explain how reviewers can test the new feature or improvement.
## App Version
State the version of Freedom Loader you are targeting.
## Notes / Additional Info
Any context, screenshots, or logs that can help reviewing the PR.

View File

@@ -0,0 +1,25 @@
# Freedom Loader - x.y.z
## Changelog
- List all changes made in this release (features, bug fixes, improvements)
- Use bullet points, keep it concise
- Example:
- `YT-DLP` Will now update when the application is launched
- Improved UI / UX
## Bug Reports / Issues
- Please report any bugs in the GitHub Issues section
## Next Release Roadmap (non-exhaustive)
- List upcoming features or improvements
- Example:
- More format options
- Subtitle support
- Language selection
- Parallel downloads
- Skip sponsored parts automatically
**Full Changelog**: https://github.com/MasterAcnolo/Freedom-Loader/compare/[previous-version]...[new-version]

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
masteracnolo25@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

66
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,66 @@
# Contributing to Freedom Loader
Welcome! Thanks for being interested in Freedom Loader. Your contributions-bug reports, feature ideas, or code improvements-are always appreciated. Every contribution makes Freedom Loader better 💪
---
## 1. Check Before Contributing
- Make sure your issue, bug, or feature request doesn't already exist.
- Search issues and pull requests before creating a new one.
- Follow the [Code of Conduct](CODE_OF_CONDUCT.md).
---
## 2. Report an Issue
Found a bug, weird behavior, or have an idea? Open an [**issue**](https://github.com/MasterAcnolo/Freedom-Loader/issues) using the **Bug Report template**.
Make sure to include:
- Steps to reproduce the issue
- App version and environment (OS, Node, Browser, etc.)
- Logs if possible: `C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\LOGS-20xx-xx-xx.log`
- Screenshots if relevant
---
## 3. Submit a Pull Request
To fix a bug, add a feature, or improve docs:
1. Fork the repository
2. Create a branch: `git checkout -b feature/my-awesome-feature`
3. Make your changes
4. Add tests or verification steps
5. Test everything locally
6. Open a PR to the `main` branch
**Tips for PRs:**
- Keep titles and descriptions clear and concise
- Document what you change and why
- Follow existing code style (camelCase for variables/functions)
- Small commits focused on a single purpose are easier to review
---
## 4. Improve the Documentation
Docs are important: README, wiki, guides… If something is unclear, improve it!
Small contributions like fixing typos or adding examples are always welcome.
---
## 5. Testing & Verification
- Test new features thoroughly
- Ensure bug fixes actually fix the reported issue
- Include any relevant logs or screenshots
---
## 6. Review & Merge
- After submitting a PR, it will be reviewed by maintainers
- Be open to feedback and changes
- We try to respond quickly, but sometimes have other priorities
---
## 7. Code of Conduct
Please contribute respectfully and constructively. Everyone is welcome as long as discussions remain polite and productive.
---
Thanks to Zakaria for the website icon. Check him out here: [IG Link](https://www.instagram.com/designmark_studio/) 🔥

View File

@@ -1,13 +1,12 @@
<div align="center"> <div align="center">
<img <a href="https://masteracnolo.github.io/FreedomLoader/"><img
src="./build/banner.png" src="./build/banner.png"
alt="Banner" alt="Banner"
style="width: 50%;;"/> style="width: 50%;;"/></a>
</div> </div>
<div align="center"> <div align="center">
<a href="https://github.com/MasterAcnolo/Freedom-Loader/releases"><img src="https://img.shields.io/badge/Release-1.3.1-blue?style=for-the-badge"></a> <a href="https://github.com/MasterAcnolo/Freedom-Loader/releases"><img src="https://img.shields.io/badge/Release-1.4.0-blue?style=for-the-badge"></a>
<a href="https://masteracnolo.github.io/FreedomLoader/index.html"><img src="https://img.shields.io/badge/Freedom%20Loader-Open%20Page-111111?style=for-the-badge&logo=terminal&logoColor=white"></a> <a href="https://masteracnolo.github.io/FreedomLoader/index.html"><img src="https://img.shields.io/badge/Freedom%20Loader-Open%20Page-111111?style=for-the-badge&logo=terminal&logoColor=white"></a>
<a href="https://www.firefox.com/fr/?utm_campaign=SET_DEFAULT_BROWSER"><img src="https://img.shields.io/badge/Require Firefox-E66000?style=for-the-badge&logo=Firefox-Browser&logoColor=white"></a> <a href="https://www.firefox.com/fr/?utm_campaign=SET_DEFAULT_BROWSER"><img src="https://img.shields.io/badge/Require Firefox-E66000?style=for-the-badge&logo=Firefox-Browser&logoColor=white"></a>
</div> </div>

40
SECURITY.md Normal file
View File

@@ -0,0 +1,40 @@
# Security Policy
## Supported Versions
We actively maintain security updates for the following versions (Since the 1.3.0 the application auto-update, patch will be apply easily):
| Version | Supported |
| -------- | ----------------- |
| 1.3.x | :white_check_mark: |
| 1.2.x | :white_check_mark: |
| 1.1.x | :white_check_mark:|
| <1.1 | :white_check_mark: |
> Only versions marked with ✅ receive security updates. Older versions may be vulnerable.
---
## Reporting a Vulnerability
If you discover a security vulnerability in Freedom Loader, **do not create a public issue**. Report it privately:
- Email: **masteracnolo25@gmail.com**
- Include:
- Description of the vulnerability
- Steps to reproduce it
- App version and environment (OS, Node, Browser if relevant)
- Logs or screenshots if possible (`C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\LOGS-20xx-xx-xx.log`)
We will respond as quickly as possible, typically within **48 hours**, and keep you updated on the fix.
We may acknowledge responsible disclosures in release notes unless you request anonymity.
---
## What Happens Next
- We will confirm receipt of your report
- Investigate and reproduce the issue
- Deploy a fix and release an update
- Notify you when the fix is live

BIN
build/banner.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View File

@@ -1,10 +1,26 @@
const packageJson = require("./package.json"); const packageJson = require("./package.json");
const { app } = require("electron"); const { app } = require("electron");
const fs = require("fs");
const path = require("path");
const localMode = !app.isPackaged;
const featuresPath = localMode ? path.join(__dirname, "./config/config.json") : path.join(path.join(process.resourcesPath,"config/" ,"config.json"));
let features = {};
function loadFeatures() {
const raw = fs.readFileSync(featuresPath, "utf-8");
features = JSON.parse(raw);
return features;
}
const configFeatures = loadFeatures();
module.exports = { module.exports = {
version: packageJson.version, version: packageJson.version,
applicationPort: "8787", applicationPort: "8787",
debugMode: true, localMode,
localMode: !app.isPackaged,
DiscordRPCID: "1410934537051181146", DiscordRPCID: "1410934537051181146",
configFeatures
} }

15
config/config.json Normal file
View File

@@ -0,0 +1,15 @@
{
"autoUpdate": true,
"discordRPC": true,
"customTopBar": true,
"autoCheckInfo": true,
"addThumbnail": true,
"addMetadata": true,
"verboseLogs": false,
"autoDownloadPlaylist": true,
"customCodec": "h264",
"logSystem": true,
"outputTitleCheck": true,
"downloadSystem": true,
"notifySystem": true
}

109
main.js
View File

@@ -3,16 +3,27 @@ const { app, BrowserWindow, ipcMain, dialog, Menu, shell } = require("electron")
const path = require("path"); const path = require("path");
const os = require("os"); const os = require("os");
const fs = require("fs"); const fs = require("fs");
const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger"); const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger");
const { AutoUpdater } = require("./server/update.js"); const { AutoUpdater } = require("./server/update.js");
const { configFeatures } = require("./config.js");
const { startRPC } = require("./server/discordRPC");
let mainWindow; let mainWindow;
const logsFolderPath = logDir; const logsFolderPath = logDir;
const basePath = config.localMode
? path.join(__dirname )
: path.join(path.dirname(process.execPath), "resources");
const configFolderPath = path.join(basePath, "config" ,"config.json");
const defaultDownloadPath = path.join(os.homedir(), "Downloads", "Freedom Loader"); const defaultDownloadPath = path.join(os.homedir(), "Downloads", "Freedom Loader");
app.setAppUserModelId("com.masteracnolo.freedomloader"); // pour notifications Windows app.setAppUserModelId("com.masteracnolo.freedomloader"); // pour notifications Windows
app.disableHardwareAcceleration(); app.disableHardwareAcceleration();
ipcMain.handle("version", () => config.version);
// Gestion single instance // Gestion single instance
const gotLock = app.requestSingleInstanceLock(); const gotLock = app.requestSingleInstanceLock();
@@ -20,10 +31,10 @@ const gotLock = app.requestSingleInstanceLock();
// Native dependencies check (yt-dlp.exe, ffmpeg.exe, ffprobe.exe, Deno) // Native dependencies check (yt-dlp.exe, ffmpeg.exe, ffprobe.exe, Deno)
function checkNativeDependencies() { function checkNativeDependencies() {
const deps = [ const deps = [
{ name: "yt-dlp.exe", path: path.join(process.resourcesPath, "yt-dlp.exe") }, { name: "yt-dlp.exe", path: path.join(process.resourcesPath, "binaries","yt-dlp.exe") },
{ name: "ffmpeg.exe", path: path.join(process.resourcesPath, "ffmpeg.exe") }, { name: "ffmpeg.exe", path: path.join(process.resourcesPath, "binaries", "ffmpeg.exe") },
{ name: "ffprobe.exe", path: path.join(process.resourcesPath, "ffprobe.exe") }, { name: "ffprobe.exe", path: path.join(process.resourcesPath, "binaries", "ffprobe.exe") },
{ name: "deno.exe", path: path.join(process.resourcesPath, "deno.exe") }, { name: "deno.exe", path: path.join(process.resourcesPath, "binaries", "deno.exe") },
]; ];
const missing = deps.filter(dep => !fs.existsSync(dep.path)); const missing = deps.filter(dep => !fs.existsSync(dep.path));
let errorMsg = ""; let errorMsg = "";
@@ -77,7 +88,7 @@ async function createMainWindow() {
height: 800, height: 800,
minWidth: 750, minWidth: 750,
minHeight: 800, minHeight: 800,
frame:false, frame: !configFeatures.customTopBar,
webPreferences: { webPreferences: {
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
@@ -98,21 +109,44 @@ async function createMainWindow() {
}); });
} }
function validateDownloadPath(userPath) {
const userHome = os.homedir(); // C:\Users\<User>
if (!userPath) return path.join(userHome, "Downloads", "Freedom Loader");
// Résolution canonique et suivi des symlinks
const resolved = fs.realpathSync(path.resolve(userPath));
const normalizedHome = path.resolve(userHome) + path.sep;
if (!resolved.startsWith(normalizedHome)) {
throw new Error("Chemin non autorisé : uniquement les sous-dossiers du dossier utilisateur sont permis !");
}
return resolved;
}
// IPC // IPC
ipcMain.handle("select-download-folder", async () => { ipcMain.handle("select-download-folder", async () => {
try { try {
const result = await dialog.showOpenDialog({ properties: ["openDirectory"] }); const result = await dialog.showOpenDialog({ properties: ["openDirectory"] });
if (!result.canceled && result.filePaths.length > 0) { if (!result.canceled && result.filePaths.length > 0) {
logger.info(`Dossier sélectionné : ${result.filePaths[0]}`); const validatedPath = validateDownloadPath(result.filePaths[0]);
return result.filePaths[0]; logger.info(`Folder Checked and Valid : ${validatedPath}`);
return validatedPath;
} }
return null; return null;
} catch (err) { } catch (err) {
logger.error(`Error when creating Output Folder : ${err.message}`); logger.error(`An Error Occured when validating folder : ${err.message}`);
return null; return null;
} }
}); });
ipcMain.handle("validate-download-path", (event, userPath) => {
return validateDownloadPath(userPath);
});
ipcMain.handle("get-default-download-path", () => defaultDownloadPath); ipcMain.handle("get-default-download-path", () => defaultDownloadPath);
ipcMain.on("set-progress", (event, percent) => { ipcMain.on("set-progress", (event, percent) => {
@@ -149,6 +183,9 @@ ipcMain.on("open-website", () => {
ipcMain.on("open-wiki", () => { ipcMain.on("open-wiki", () => {
shell.openExternal("https://masteracnolo.github.io/FreedomLoader/pages/wiki.html"); shell.openExternal("https://masteracnolo.github.io/FreedomLoader/pages/wiki.html");
}); });
ipcMain.on("open-config", () => {
if (configFolderPath) shell.openPath(configFolderPath);
});
// App ready // App ready
@@ -156,18 +193,66 @@ app.whenReady().then(async () => {
logSessionStart(); logSessionStart();
logger.info("App Ready, Server Express starting..."); logger.info("App Ready, Server Express starting...");
const serverPath = path.join(__dirname, "server", "server.js"); const serverPath = path.join(__dirname, "server", "server.js")
const expressServer = require(serverPath); const expressServer = require(serverPath);
try { try {
await expressServer.startServer(); await expressServer.startServer();
logger.info("Express Server Started"); logger.info("Express Server Started");
const { startRPC } = require("./server/discordRPC"); ipcMain.handle("features", () => {
startRPC(); return configFeatures;
});
const featureWhitelist = new Set([
"autoUpdate",
"discordRPC",
"customTopBar",
"autoCheckInfo",
"addThumbnail",
"addMetadata",
"verboseLogs",
"autoDownloadPlaylist",
"customCodec"
]);
ipcMain.handle("set-feature", (event, { key, value }) => {
try {
if (!featureWhitelist.has(key)) {
logger.warn(`Rejected feature (not whitelisted): ${key}`);
return false;
}
// optionnel mais propre
if (configFeatures[key] === value) {
return true;
}
configFeatures[key] = value;
fs.writeFileSync(
configFolderPath,
JSON.stringify(configFeatures, null, 2),
"utf-8"
);
logger.info(`Feature updated: ${key} = ${value}`);
return true;
} catch (err) {
logger.error(`set-feature failed (${key}): ${err.message}`);
return false;
}
});
configFeatures.discordRPC ? startRPC() : "";
await createMainWindow(); await createMainWindow();
AutoUpdater(mainWindow); configFeatures.autoUpdate ? AutoUpdater(mainWindow) : ""; // Auto Update
} catch (err) { } catch (err) {
logger.error("Window or Server error :", err); logger.error("Window or Server error :", err);
app.quit(); app.quit();

1299
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,22 @@
{ {
"name": "freedom-loader", "name": "freedom-loader",
"productName": "Freedom Loader", "productName": "Freedom Loader",
"version": "1.3.1", "version": "1.4.0",
"author": "MasterAcnolo", "author": "MasterAcnolo",
"description": "Freedom Loader", "description": "Freedom Loader",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"start": "electron --trace-warnings .", "start": "electron --trace-warnings .",
"dist": "electron-builder", "build": "electron-builder",
"update": "npm update" "update": "npm update"
}, },
"dependencies": { "dependencies": {
"chalk": "^4.1.2", "chalk": "^4.1.2",
"debug": "^4.4.1", "debug": "^4.4.1",
"discord-rpc": "^4.0.1", "discord-rpc": "^4.0.1",
"electron-squirrel-startup": "^1.0.1",
"electron-updater": "^6.6.2", "electron-updater": "^6.6.2",
"express": "^5.1.0", "express": "^5.1.0",
"express-rate-limit": "^8.2.1",
"webidl-conversions": "^8.0.0", "webidl-conversions": "^8.0.0",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"
@@ -38,9 +38,7 @@
"files": [ "files": [
"**/*", "**/*",
"node_modules/**/*", "node_modules/**/*",
"!logs/*.json", "!logs/*.json"
"server/**/*",
"server/node_modules/**/*"
], ],
"directories": { "directories": {
"buildResources": "build" "buildResources": "build"
@@ -55,29 +53,33 @@
"from": "build/app-icon.ico", "from": "build/app-icon.ico",
"to": "app-icon.ico" "to": "app-icon.ico"
}, },
{
"from": "build/banner.bmp",
"to": "banner.bmp"
},
{ {
"from": "build/error.png", "from": "build/error.png",
"to": "error.png" "to": "error.png"
}, },
{ {
"from": "ressources/yt-dlp.exe", "from": "ressources/yt-dlp.exe",
"to": "yt-dlp.exe" "to": "binaries/yt-dlp.exe"
}, },
{ {
"from": "ressources/ffmpeg.exe", "from": "ressources/ffmpeg.exe",
"to": "ffmpeg.exe" "to": "binaries/ffmpeg.exe"
}, },
{ {
"from": "ressources/ffprobe.exe", "from": "ressources/ffprobe.exe",
"to": "ffprobe.exe" "to": "binaries/ffprobe.exe"
}, },
{ {
"from": "ressources/deno.exe", "from": "ressources/deno.exe",
"to": "deno.exe" "to": "binaries/deno.exe"
}, },
{ {
"from": "server", "from": "config/config.json",
"to": "server" "to": "config/config.json"
} }
], ],
"win": { "win": {
@@ -90,6 +92,7 @@
"allowToChangeInstallationDirectory": true, "allowToChangeInstallationDirectory": true,
"installerIcon": "build/app-icon.ico", "installerIcon": "build/app-icon.ico",
"uninstallerIcon": "build/uninstaller-icon.ico", "uninstallerIcon": "build/uninstaller-icon.ico",
"installerSidebar": "build/banner.bmp",
"createDesktopShortcut": true, "createDesktopShortcut": true,
"createStartMenuShortcut": true, "createStartMenuShortcut": true,
"shortcutName": "Freedom Loader" "shortcutName": "Freedom Loader"

View File

@@ -3,7 +3,11 @@ const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("electronAPI", { contextBridge.exposeInMainWorld("electronAPI", {
getDefaultDownloadPath: () => ipcRenderer.invoke("get-default-download-path"), getDefaultDownloadPath: () => ipcRenderer.invoke("get-default-download-path"),
selectDownloadFolder: () => ipcRenderer.invoke("select-download-folder"), selectDownloadFolder: () => ipcRenderer.invoke("select-download-folder"),
setProgress: (percent) => ipcRenderer.send("set-progress", percent) setProgress: (percent) => ipcRenderer.send("set-progress", percent),
getFeatures: () => ipcRenderer.invoke("features"),
setFeature: (key, value) => ipcRenderer.invoke("set-feature", { key, value }),
getVersion: () => ipcRenderer.invoke("version"),
getValidatedDownloadPath: (path) => ipcRenderer.invoke("validate-download-path", path)
}); });
// Contrôles de fenêtre et outils custom pour la topbar // Contrôles de fenêtre et outils custom pour la topbar
@@ -14,5 +18,6 @@ contextBridge.exposeInMainWorld("topbarAPI", {
openDevTools: () => ipcRenderer.send("open-devtools"), openDevTools: () => ipcRenderer.send("open-devtools"),
openLogs: () => ipcRenderer.send("open-logs"), openLogs: () => ipcRenderer.send("open-logs"),
openWebsite: () => ipcRenderer.send("open-website"), openWebsite: () => ipcRenderer.send("open-website"),
openWiki: () => ipcRenderer.send("open-wiki") openWiki: () => ipcRenderer.send("open-wiki"),
openConfig: () => ipcRenderer.send("open-config")
}); });

View File

@@ -17,7 +17,7 @@
</head> </head>
<body> <body>
<div class="topbar"> <div class="topbar" id="topbar">
<div class="custom-controls"> <div class="custom-controls">
<button id="devtools-btn" title="Dev Tools">Tools</button> <button id="devtools-btn" title="Dev Tools">Tools</button>
<button id="logs-btn" title="Logs">Logs</button> <button id="logs-btn" title="Logs">Logs</button>
@@ -30,13 +30,175 @@
<button id="close-btn" title="Fermer"><img src="assets/icon/close.svg"></button> <button id="close-btn" title="Fermer"><img src="assets/icon/close.svg"></button>
</div> </div>
</div> </div>
<button class="settings-button" id="settings-button">
<svg width="800px" height="800px" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><title>ionicons-v5-q</title><path d="M262.29,192.31a64,64,0,1,0,57.4,57.4A64.13,64.13,0,0,0,262.29,192.31ZM416.39,256a154.34,154.34,0,0,1-1.53,20.79l45.21,35.46A10.81,10.81,0,0,1,462.52,326l-42.77,74a10.81,10.81,0,0,1-13.14,4.59l-44.9-18.08a16.11,16.11,0,0,0-15.17,1.75A164.48,164.48,0,0,1,325,400.8a15.94,15.94,0,0,0-8.82,12.14l-6.73,47.89A11.08,11.08,0,0,1,298.77,470H213.23a11.11,11.11,0,0,1-10.69-8.87l-6.72-47.82a16.07,16.07,0,0,0-9-12.22,155.3,155.3,0,0,1-21.46-12.57,16,16,0,0,0-15.11-1.71l-44.89,18.07a10.81,10.81,0,0,1-13.14-4.58l-42.77-74a10.8,10.8,0,0,1,2.45-13.75l38.21-30a16.05,16.05,0,0,0,6-14.08c-.36-4.17-.58-8.33-.58-12.5s.21-8.27.58-12.35a16,16,0,0,0-6.07-13.94l-38.19-30A10.81,10.81,0,0,1,49.48,186l42.77-74a10.81,10.81,0,0,1,13.14-4.59l44.9,18.08a16.11,16.11,0,0,0,15.17-1.75A164.48,164.48,0,0,1,187,111.2a15.94,15.94,0,0,0,8.82-12.14l6.73-47.89A11.08,11.08,0,0,1,213.23,42h85.54a11.11,11.11,0,0,1,10.69,8.87l6.72,47.82a16.07,16.07,0,0,0,9,12.22,155.3,155.3,0,0,1,21.46,12.57,16,16,0,0,0,15.11,1.71l44.89-18.07a10.81,10.81,0,0,1,13.14,4.58l42.77,74a10.8,10.8,0,0,1-2.45,13.75l-38.21,30a16.05,16.05,0,0,0-6.05,14.08C416.17,247.67,416.39,251.83,416.39,256Z" style="fill:none;stroke:var(--settings-button-text-color);stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/></svg>
</button>
<div class="settings-panel" id="settings-panel">
<div class="settings-modal">
<button class="close-btn" onclick="closePanel()"></button>
<div class="settings-title">
<h2>SETTINGS</h2>
<p style="font-size: 0.8em;">Changes are saved automatically. <br> Somes changes could need a restart</p>
</div>
<!-- Basic Settings visibles -->
<div class="settings-section">
<h3>Basic Settings</h3>
<div class="setting-item" id="theme" style="margin-left: 32px;">
<div class="setting-info">
<div style="display: flex;">
<span class="setting-title">Thème</span>
<select id="themeSelect" data-key="theme" aria-label="Choisir le thème">
</select>
</div>
<small>Choose the theme of the application</small>
</div>
</div>
<div class="setting-item">
<input type="checkbox" data-key="discordRPC" id="discordRPC">
<div class="setting-info">
<span class="setting-title">Discord RPC</span>
<small>Enable/disable Discord Rich Presence for your app.</small>
</div>
</div>
<div class="setting-item">
<input type="checkbox" data-key="autoDownloadPlaylist" id="autoDownloadPlaylist">
<div class="setting-info">
<span class="setting-title">Download Playlist automatically?</span>
<small>If enabled, playlists will download without confirmation.</small>
</div>
</div>
<!-- <div class="setting-item">
<input type="checkbox" data-key="notifySystem" id="notifySystem">
<div class="setting-info">
<span class="setting-title">Notification at the end</span>
<small>Notify when a download finishes.</small>
</div>
</div> -->
<div class="setting-item">
<input type="checkbox" data-key="customTopBar" id="customTopBar">
<div class="setting-info">
<span class="setting-title">Custom Top bar</span>
<small>Use this if the topbar is missing or you prefer a custom one.</small>
</div>
</div>
<div class="setting-item">
<input type="checkbox" data-key="autoCheckInfo" id="autoCheckInfo">
<div class="setting-info">
<span class="setting-title">Auto Fetch Info</span>
<small>Automatically fetch information for new downloads.</small>
</div>
</div>
</div>
<!-- Advanced Settings -->
<details class="settings-section">
<summary>Advanced Settings</summary>
<div class="setting-item">
<input type="checkbox" data-key="autoUpdate" id="autoUpdate">
<div class="setting-info">
<span class="setting-title">Auto Update</span>
<small>Enable automatic app updates.</small>
</div>
</div>
<!-- <div class="setting-item">
<input type="checkbox" data-key="outputTitleCheck" id="outputTitleCheck">
<div class="setting-info">
<span class="setting-title">Output title (special chars)</span>
<small>Check for unusual characters in output titles.</small>
</div>
</div> -->
<div class="setting-item">
<input type="checkbox" data-key="addMetadata" id="addMetadata">
<div class="setting-info">
<span class="setting-title">Add Metadata</span>
<small>Include metadata in downloaded files.</small>
</div>
</div>
<div class="setting-item">
<input type="checkbox" data-key="addThumbnail" id="addThumbnail">
<div class="setting-info">
<span class="setting-title">Add Thumbnail</span>
<small>Include thumbnails in downloaded files.</small>
</div>
</div>
<div class="setting-item" style="margin-left: 32px; margin-top: 20px;">
<div class="setting-info">
<span class="setting-title">Custom Codec</span>
<select data-key="customCodec">
<option value="h264">H264</option>
<option value="av1">AV1</option>
<option value="av01">AV01</option>
<option value="vp9.2">VP9.2</option>
<option value="vp9">VP9</option>
<option value="h265">H265</option>
<option value="theora">Theora</option>
</select>
<small>Choose your preferred video codec.</small>
</div>
</div>
</details>
<!-- Developer Settings -->
<details class="settings-section">
<summary>Developer Settings</summary>
<!-- <div class="setting-item">
<input type="checkbox" data-key="downloadSystem" id="downloadSystem">
<div class="setting-info">
<span class="setting-title">Download System</span>
<small>Enable/disable the internal download system.</small>
</div>
</div> -->
<!-- <div class="setting-item">
<input type="checkbox" data-key="logSystem" id="logSystem">
<div class="setting-info">
<span class="setting-title">Log System</span>
<small>Enable logging system.</small>
</div>
</div> -->
<div class="setting-item">
<input type="checkbox" data-key="verboseLogs" id="verboseLogs">
<div class="setting-info">
<span class="setting-title">Verbose Logs</span>
<small>Show verbose logs for debugging purposes.</small>
</div>
</div>
</details>
<div class="settings-footer">
<button id="open-json-btn">Open JSON Configuration</button>
</div>
<div class="theme-switcher">
<label for="themeSelect">Thème :</label>
<select id="themeSelect" aria-label="Choisir le thème"></select>
</div> </div>
</div>
<div class="container">
<div class="container" id="container">
<header> <header>
<h1 id="title"> Freedom Loader </h1> <h1 id="title"> Freedom Loader </h1>
@@ -95,10 +257,15 @@
<div id="videoInfo" style="margin-top: 20px;"></div> <div id="videoInfo" style="margin-top: 20px;"></div>
</div> </div>
<span id="version-badge" class="version-badge">v1.3.1</span> <span id="version-badge" class="version-badge"></span>
<h2 id="reference"> Because Why Not ? </h2>
<!-- Scripts --> <!-- Scripts -->
<script src="script/custompath.js"></script> <script src="script/custompath.js"></script>
<script src="script/settingsPanel.js"></script>
<script src="script/appVersion.js"></script>
<script src="script/downloadstatus.js"></script> <script src="script/downloadstatus.js"></script>
<script src="script/fetchinfo.js"></script> <script src="script/fetchinfo.js"></script>
<script src="script/progressBar.js"></script> <script src="script/progressBar.js"></script>

View File

@@ -0,0 +1,6 @@
async function versionLabel(){
const appVersion = await window.electronAPI.getVersion();
document.getElementById("version-badge").textContent = `v${appVersion}`;
};
versionLabel();

View File

@@ -1,41 +1,62 @@
window.addEventListener("DOMContentLoaded", async () => { window.addEventListener("DOMContentLoaded", async () => {
const savePathElem = document.getElementById("savePath"); const savePathElem = document.getElementById("savePath");
const form = document.getElementById("downloadForm");
// Essayer de charger depuis le localStorage // input caché = DERNIER chemin validé par le back
let savedPath = localStorage.getItem("customDownloadPath");
// Sinon demander le chemin par défaut à l'API Electron
if (!savedPath) {
savedPath = await window.electronAPI.getDefaultDownloadPath();
}
// Afficher le chemin
if (savePathElem) {
savePathElem.textContent = savedPath;
}
// Créer l'input caché s'il n'existe pas déjà
let hidden = document.getElementById("savePathInput"); let hidden = document.getElementById("savePathInput");
if (!hidden) { if (!hidden) {
hidden = document.createElement("input"); hidden = document.createElement("input");
hidden.type = "hidden"; hidden.type = "hidden";
hidden.name = "savePath"; hidden.name = "savePath";
hidden.id = "savePathInput"; hidden.id = "savePathInput";
document.getElementById("downloadForm").appendChild(hidden); form.appendChild(hidden);
} }
hidden.value = savedPath;
// Gestion du bouton de modification async function applyPathFromBack(path) {
document.getElementById("changePath").addEventListener("click", async () => { savePathElem.textContent = path;
const selectedPath = await window.electronAPI.selectDownloadFolder(); hidden.value = path;
if (selectedPath) { localStorage.setItem("customDownloadPath", path); // UX only
// Met à jour l'affichage }
savePathElem.textContent = selectedPath;
hidden.value = selectedPath;
// Et le stocke en localStorage pour la prochaine fois async function loadInitialPath() {
localStorage.setItem("customDownloadPath", selectedPath); // On affiche ce que le user a vu la dernière fois (UX)
const cached = localStorage.getItem("customDownloadPath");
if (cached) {
savePathElem.textContent = cached;
} }
});
});
// MAIS la source de vérité reste le back
try {
const validatedPath = await window.electronAPI.getValidatedDownloadPath(
cached
);
await applyPathFromBack(validatedPath);
} catch {
// fallback sûr
const defaultPath =
await window.electronAPI.getDefaultDownloadPath();
await applyPathFromBack(defaultPath);
}
}
await loadInitialPath();
document
.getElementById("changePath")
.addEventListener("click", async () => {
try {
const selectedPath =
await window.electronAPI.selectDownloadFolder();
if (!selectedPath) return; // annulé
// Validation back obligatoire
const validatedPath =
await window.electronAPI.getValidatedDownloadPath(selectedPath);
await applyPathFromBack(validatedPath);
} catch (err) {
alert("Dossier non autorisé.");
}
});
});

View File

@@ -1,8 +1,7 @@
const themes = { const themes = {
default: { label: "Default", subtitle: "Because why not?" },
dark: { label: "Sombre", subtitle: "Darkness is my ally" }, dark: { label: "Sombre", subtitle: "Darkness is my ally" },
light: { label: "Clair", subtitle: "Qui aime ce thème ?" }, light: { label: "Clair", subtitle: "Qui aime ce thème ?" },
neon: { label: "Néon", subtitle: "How was your day ?"}, neon: { label: "Purple", subtitle: "How was your day ?"},
nf: { label: "NF", subtitle: "You call it music, i call it my Therapist" }, nf: { label: "NF", subtitle: "You call it music, i call it my Therapist" },
songbird: { label: "Songbird", subtitle: "From Her to Eternity" }, songbird: { label: "Songbird", subtitle: "From Her to Eternity" },
drift: { label: "Drift", subtitle: "Si la route t'appelle, contre appel" }, drift: { label: "Drift", subtitle: "Si la route t'appelle, contre appel" },

View File

@@ -26,155 +26,166 @@ async function fetchVideoInfo(url) {
} }
} }
document.addEventListener("DOMContentLoaded", () => {
const urlInput = document.getElementById("UrlInput");
const infoDiv = document.getElementById("videoInfo");
const loaderBox = document.getElementById("loaderBox");
let lastFetchedUrl = ""; async function init() {
urlInput.addEventListener("input", async () => { document.addEventListener("DOMContentLoaded", () => {
const url = urlInput.value.trim(); const urlInput = document.getElementById("UrlInput");
const infoDiv = document.getElementById("videoInfo");
const loaderBox = document.getElementById("loaderBox");
// Si champ vide -> reset total let lastFetchedUrl = "";
if (!url || url.length < 2) {
infoDiv.innerHTML = "";
infoDiv.classList.remove("visible", "playlist-mode");
lastFetchedUrl = "";
return;
}
if (url === lastFetchedUrl) return; urlInput.addEventListener("input", async () => {
lastFetchedUrl = url;
loaderBox.style.display = "flex"; const configFeatures = await window.electronAPI.getFeatures();
const data = await fetchVideoInfo(url); if (!configFeatures.autoCheckInfo) return;
loaderBox.style.display = "none";
// Gestion des erreurs const url = urlInput.value.trim();
if (data.error) {
infoDiv.innerHTML = ` // Si champ vide -> reset total
<div style=" if (!url || url.length < 2) {
padding:7px; infoDiv.innerHTML = "";
background:var(----infos-box-color); infoDiv.classList.remove("visible", "playlist-mode");
"> lastFetchedUrl = "";
<strong>${data.error}</strong> return;
</div> }
`;
infoDiv.classList.add("visible"); if (url === lastFetchedUrl) return;
infoDiv.classList.remove("playlist-mode"); lastFetchedUrl = url;
loaderBox.style.display = "flex";
const data = await fetchVideoInfo(url);
loaderBox.style.display = "none"; loaderBox.style.display = "none";
return;
}
// ---------- PLAYLIST ---------- // Gestion des erreurs
if (data.type === "playlist") { if (data.error) {
infoDiv.classList.add("playlist-mode"); infoDiv.innerHTML = `
infoDiv.innerHTML = ` <div style="
<h3 style="color:var(--video-info-heading-color);"><strong>Playlist Detected: ${data.title}</strong></h3> padding:7px;
<h3 style="color:var(--video-info-heading-color);"><strong>Video Count: ${data.count}</strong></h3> background:var(----infos-box-color);
<p><strong>Channel :</strong> ${data.channel || "Unknown"}</p> ">
<div id="playlistVideos"></div> <strong>${data.error}</strong>
`;
const listDiv = document.getElementById("playlistVideos");
data.videos.forEach(v => {
const durationStr = v.duration
? `${Math.floor(v.duration / 60)}m ${(v.duration % 60).toString().padStart(2,"0")}s` : "Inconnue";
const videoUrl = v.id ? `https://www.youtube.com/watch?v=${v.id}` : v.url;
listDiv.innerHTML += `
<div style="margin-bottom:12px;">
<img src="${v.thumbnail}" width="160" alt="Thumbnail">
<p><strong>${v.title}</strong></p>
<p>Duration : ${durationStr}</p>
<p><a href="${videoUrl}" target="_blank">URL</a>
<button class="copy-btn" data-url="${videoUrl}">📋</button>
</p>
</div> </div>
`; `;
}); infoDiv.classList.add("visible");
infoDiv.classList.remove("playlist-mode");
loaderBox.style.display = "none";
return;
}
// Gestion du bouton copier // ---------- PLAYLIST ----------
listDiv.addEventListener("click", (event) => { if (data.type === "playlist") {
if (event.target.classList.contains("copy-btn")) { infoDiv.classList.add("playlist-mode");
const btn = event.target; infoDiv.innerHTML = `
if (btn.disabled) return; <h3 style="color:var(--video-info-heading-color);"><strong>Playlist Detected: ${data.title}</strong></h3>
<h3 style="color:var(--video-info-heading-color);"><strong>Video Count: ${data.count}</strong></h3>
<p><strong>Channel :</strong> ${data.channel || "Unknown"}</p>
<div id="playlistVideos"></div>
`;
btn.disabled = true; const listDiv = document.getElementById("playlistVideos");
const url = btn.dataset.url;
navigator.clipboard.writeText(url)
.then(() => {
const original = btn.textContent;
btn.style.opacity = 0; data.videos.forEach(v => {
btn.style.transform = "scale(0.7)"; const durationStr = v.duration
? `${Math.floor(v.duration / 60)}m ${(v.duration % 60).toString().padStart(2,"0")}s` : "Inconnue";
setTimeout(() => { const videoUrl = v.id ? `https://www.youtube.com/watch?v=${v.id}` : v.url;
btn.textContent = "✅";
btn.style.opacity = 1; listDiv.innerHTML += `
btn.style.transform = "scale(1)"; <div style="margin-bottom:12px;">
<img src="${v.thumbnail}" width="160" alt="Thumbnail">
<p><strong>${v.title}</strong></p>
<p>Duration : ${durationStr}</p>
<p><a href="${videoUrl}" target="_blank">URL</a>
<button class="copy-btn" data-url="${videoUrl}">📋</button>
</p>
</div>
`;
});
// Gestion du bouton copier
listDiv.addEventListener("click", (event) => {
if (event.target.classList.contains("copy-btn")) {
const btn = event.target;
if (btn.disabled) return;
btn.disabled = true;
const url = btn.dataset.url;
navigator.clipboard.writeText(url)
.then(() => {
const original = btn.textContent;
btn.style.opacity = 0;
btn.style.transform = "scale(0.7)";
setTimeout(() => { setTimeout(() => {
btn.style.opacity = 0; btn.textContent = "✅";
btn.style.transform = "scale(0.7)"; btn.style.opacity = 1;
btn.style.transform = "scale(1)";
setTimeout(() => { setTimeout(() => {
btn.textContent = original; btn.style.opacity = 0;
btn.style.opacity = 1; btn.style.transform = "scale(0.7)";
btn.style.transform = "scale(1)";
btn.disabled = false;
}, 300);
}, 1000); setTimeout(() => {
btn.textContent = original;
btn.style.opacity = 1;
btn.style.transform = "scale(1)";
btn.disabled = false;
}, 300);
}, 300); }, 1000);
})
.catch(() => { }, 300);
const original = btn.textContent; })
btn.textContent = "❌"; .catch(() => {
setTimeout(() => { const original = btn.textContent;
btn.textContent = original; btn.textContent = "❌";
btn.disabled = false; setTimeout(() => {
}, 1500); btn.textContent = original;
}); btn.disabled = false;
} }, 1500);
}); });
}
});
infoDiv.classList.add("visible");
return;
}
infoDiv.classList.remove("playlist-mode");
// ---------- VIDEO NORMALE ----------
const durationStr = data.duration
? `${Math.floor(data.duration/60)}m ${(data.duration%60).toString().padStart(2,"0")}s`
: "Unknown";
const sizeStr = formatSize(data.filesize_approx);
const readableDate = formatDate(data.upload_date);
const categories = data.categories?.join(", ") || "Non spécifiées";
infoDiv.innerHTML = `
<h3>${data.title}</h3>
<img src="${data.thumbnail}" width="320" alt="Thumbnail">
<ul>
<li><strong>Duration :</strong> ${durationStr}</li>
<li><strong>Uploader :</strong> ${data.uploader || "Inconnu"}</li>
<li><strong>Upload Date :</strong> ${readableDate}</li>
<li><strong>Views :</strong> ${data.view_count?.toLocaleString() || "?"}</li>
<li><strong>Likes :</strong> ${data.like_count?.toLocaleString() || "?"}</li>
<li><strong>URL :</strong> <a href="${data.webpage_url}" target="_blank">${data.webpage_url}</a></li>
<li><strong>Channel :</strong> <a href="${data.channel_url}" target="_blank">${data.channel_url}</a></li>
<li><strong>Estimed Size :</strong> ${sizeStr}</li>
<li><strong>Category :</strong> ${categories}</li>
</ul>
`;
infoDiv.classList.add("visible"); infoDiv.classList.add("visible");
return; });
}
infoDiv.classList.remove("playlist-mode"); })
// ---------- VIDEO NORMALE ---------- };
const durationStr = data.duration
? `${Math.floor(data.duration/60)}m ${(data.duration%60).toString().padStart(2,"0")}s`
: "Unknown";
const sizeStr = formatSize(data.filesize_approx); init();
const readableDate = formatDate(data.upload_date);
const categories = data.categories?.join(", ") || "Non spécifiées";
infoDiv.innerHTML = `
<h3>${data.title}</h3>
<img src="${data.thumbnail}" width="320" alt="Thumbnail">
<ul>
<li><strong>Duration :</strong> ${durationStr}</li>
<li><strong>Uploader :</strong> ${data.uploader || "Inconnu"}</li>
<li><strong>Upload Date :</strong> ${readableDate}</li>
<li><strong>Views :</strong> ${data.view_count?.toLocaleString() || "?"}</li>
<li><strong>Likes :</strong> ${data.like_count?.toLocaleString() || "?"}</li>
<li><strong>URL :</strong> <a href="${data.webpage_url}" target="_blank">${data.webpage_url}</a></li>
<li><strong>Channel :</strong> <a href="${data.channel_url}" target="_blank">${data.channel_url}</a></li>
<li><strong>Estimed Size :</strong> ${sizeStr}</li>
<li><strong>Category :</strong> ${categories}</li>
</ul>
`;
infoDiv.classList.add("visible");
});
})

View File

@@ -0,0 +1,50 @@
const settingsPanel = document.getElementById("settings-panel");
const settingsModal = document.querySelector(".settings-modal");
const settingsButton = document.getElementById("settings-button");
let isOpen = false;
settingsButton.addEventListener("click", () => {
const isOpen = settingsPanel.style.display === "block";
settingsPanel.style.display = isOpen ? "none" : "block";
});
function closePanel() {
isOpen = false;
settingsPanel.style.display = "none";
}
// charge les features
async function loadSettings() {
const features = await window.electronAPI.getFeatures();
document.querySelectorAll(".settings-section input, .settings-section select").forEach(el => {
const key = el.dataset.key;
if (features[key] !== undefined) {
if (el.type === "checkbox") el.checked = features[key];
else if (el.tagName === "SELECT") el.value = features[key];
}
el.addEventListener("change", () => {
let value = el.type === "checkbox" ? el.checked : el.value;
window.electronAPI.setFeature(key, value);
});
});
}
// ouvrir le JSON
document.getElementById("open-json-btn").addEventListener("click", () => {
window.topbarAPI.openConfig(); // ton IPC existant
});
/* clic sur l'overlay = fermer */
settingsPanel.addEventListener("click", closePanel);
/* clic dans la modal = stop propagation */
settingsModal.addEventListener("click", (e) => {
e.stopPropagation();
});
loadSettings();

View File

@@ -10,6 +10,7 @@ function setupTopbarListeners() {
const logsBtn = document.getElementById('logs-btn'); const logsBtn = document.getElementById('logs-btn');
const websiteBtn = document.getElementById('website-btn'); const websiteBtn = document.getElementById('website-btn');
const wikiBtn = document.getElementById('wiki-btn'); const wikiBtn = document.getElementById('wiki-btn');
const configBtn = document.getElementById('config-btn');
if (minBtn) minBtn.onclick = () => topbarAPI.minimize(); if (minBtn) minBtn.onclick = () => topbarAPI.minimize();
if (maxBtn) maxBtn.onclick = () => topbarAPI.maximize(); if (maxBtn) maxBtn.onclick = () => topbarAPI.maximize();
@@ -18,7 +19,16 @@ function setupTopbarListeners() {
if (logsBtn) logsBtn.onclick = () => topbarAPI.openLogs(); if (logsBtn) logsBtn.onclick = () => topbarAPI.openLogs();
if (websiteBtn) websiteBtn.onclick = () => topbarAPI.openWebsite(); if (websiteBtn) websiteBtn.onclick = () => topbarAPI.openWebsite();
if (wikiBtn) wikiBtn.onclick = () => topbarAPI.openWiki(); if (wikiBtn) wikiBtn.onclick = () => topbarAPI.openWiki();
if (configBtn) configBtn.onclick = () => topbarAPI.openConfig();
}); });
} }
setupTopbarListeners(); setupTopbarListeners(); // IF it put it the if check. It don't work. Why ?
const features = await window.electronAPI.getFeatures();
if(!features.customTopBar){
document.getElementById("topbar").style.display = "none";
document.getElementById("container").style.marginTop = "0";
document.getElementById("theme-switcher").style.top = "30px";
}

View File

@@ -1,38 +0,0 @@
.theme-switcher {
position: absolute;
top: 60px;
right: 10px;
background: #242424;
padding: 8px 12px;
border-radius: 8px;
color: #fff;
font-family: sans-serif;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 8px;
z-index: 1050;
}
.theme-switcher label {
cursor: pointer;
font-weight: 600;
}
.theme-switcher select {
background: #fff;
border: 1px solid #242424;
border-radius: 6px;
padding: 4px 8px;
color: #000;
cursor: pointer;
font-weight: 600;
transition: background 0.3s ease, color 0.3s ease;
}
.theme-switcher select:hover,
.theme-switcher select:focus {
background: #474747;
color: #fff;
outline: none;
}

View File

@@ -0,0 +1,234 @@
/* SETTINGS BUTTON */
.settings-button {
display: flex;
align-items: center;
justify-content: center;
position: fixed;
top: 60px;
right: 20px;
opacity: 0.8;
background-color: var(--settings-button-bg-color);
border-radius: 14px;
padding: 8px;
border: none;
box-shadow: 0 6px 16px rgba(0,0,0,0.25);
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.settings-button svg {
width: 40px;
height: 40px;
stroke: var(--settings-button-text-color);
}
.settings-button:hover {
transform: scale(1.1);
cursor: pointer;
box-shadow: 0 10px 24px rgba(0,0,0,0.35);
}
.settings-button:active {
transform: scale(0.95);
}
/* SETTINGS PANEL */
.settings-panel {
position: fixed;
top: 60px;
right: 40px;
width: 40vw;
max-width: 320px;
max-height: 80vh;
background-color: var(--settings-bg-primary-color);
border-radius: 12px;
padding: 20px;
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
display: none;
overflow-y: auto;
z-index: 9999;
animation: slideIn 0.2s ease-out;
color: var(--settings-text-color);
}
.settings-panel::-webkit-scrollbar {
width: 0px;
background: transparent;
}
.settings-modal {
width: 100%;
min-height: auto;
max-height: none;
padding: 0;
background: none;
box-shadow: none;
border-radius: none;
}
/* TITLE */
.settings-title {
margin-bottom: 20px;
}
.settings-title h2 {
margin: 0;
font-size: 24px;
}
.settings-title p {
font-size: 14px;
color: #ccc;
margin-top: 4px;
}
/* SECTION */
.settings-section {
display: flex;
flex-direction: column;
margin-bottom: 20px;
padding: 10px 15px;
background: var(--settings-bg-secondary-color);
border-radius: 12px;
gap: 6px;
}
.settings-section h3 {
margin: 0 0 10px 0;
font-size: 18px;
}
/* SETTING ITEM */
.setting-item {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 8px;
margin-top: 8px;
}
.setting-item input[type="checkbox"] {
margin: 0;
flex-shrink: 0;
width: 20px;
height: 20px;
}
.setting-info {
display: flex;
flex-direction: column;
}
.setting-title {
font-size: 14px;
font-weight: 500;
}
.setting-info small {
font-size: 12px;
color: var(--settings-subtitle-color);
margin-top: 2px;
font-style: italic;
}
#theme .settings-title{
display: flex;
}
.setting-item select {
width: 70%;
background: #fff;
border: 1px solid #242424;
border-radius: 6px;
padding: 4px 8px;
color: #000;
cursor: pointer;
font-weight: 600;
transition: background 0.3s ease, color 0.3s ease;
}
.setting-item select:hover,
.setting-item select:focus {
background: #474747;
color: #fff;
outline: none;
}
/* SELECT */
.settings-section select {
margin-left: 10px;
}
/* DETAILS / DROPDOWN */
details {
margin-bottom: 20px;
padding: 10px 15px;
background: #2a2a2a;
border-radius: 12px;
}
details[open] {
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
}
details summary {
cursor: pointer;
}
/* FOOTER */
.settings-footer {
margin-top: 20px;
display: flex;
flex-direction: column;
gap: 10px;
}
#open-json-btn {
padding: 6px 12px;
border-radius: 8px;
border: none;
cursor: pointer;
font-weight: 600;
background-color: var(--form-button-bg-color);
color: #fff;
}
#experimental-args {
width: 100%;
min-height: 80px;
background-color: #1e1e1e;
border-radius: 8px;
padding: 8px;
color: #fff;
border: 1px solid #333;
resize: vertical;
}
/* CLOSE BTN */
.close-btn {
position: absolute;
top: 20px;
right: 20px;
background: none;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
opacity: 0.6;
}
.close-btn:hover {
opacity: 1;
}
/* ANIMATION */
@keyframes popIn {
from { transform: scale(0.95); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
@keyframes slideIn {
from { transform: translateX(20px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}

View File

@@ -1,12 +1,12 @@
/* Layout */ /* Layout */
@import url("layout/form.css"); @import url("layout/form.css");
@import url("layout/settingsPanel.css");
@import url("layout/container.css"); @import url("layout/container.css");
@import url("layout/header.css"); @import url("layout/header.css");
@import url("layout/videoinfo.css"); @import url("layout/videoinfo.css");
/* Components */ /* Components */
@import url("components/checkbox.css"); @import url("components/checkbox.css");
@import url("components/themebutton.css");
@import url("components/loader.css"); @import url("components/loader.css");
@import url("theme/themeimport.css"); @import url("theme/themeimport.css");
@@ -49,6 +49,16 @@ body {
} }
/* Because why not */
#reference{
position: fixed;
pointer-events: none;
bottom:0vh;
left: 10px;
font-size: 1em;
color: #b0b0b02f;
}
/* Scrollbar globale pour Chromium/Electron */ /* Scrollbar globale pour Chromium/Electron */
body::-webkit-scrollbar { body::-webkit-scrollbar {
width: 10px; width: 10px;

View File

@@ -42,6 +42,16 @@ body.chirac {
/* Playlist Color */ /* Playlist Color */
--playlist-background-color: #d1d1d1ca; --playlist-background-color: #d1d1d1ca;
/* Settings */
--settings-button-bg-color: #00196a;
--settings-button-text-color: #dcdcdc;
--settings-bg-primary-color: #0b0528;
--settings-bg-secondary-color: #1c1055;
--settings-text-color: #cecece;
--settings-subtitle-color: #adadad;
} }

View File

@@ -36,4 +36,14 @@ body.dark {
/* Playlist Color */ /* Playlist Color */
--playlist-background-color: #303030; --playlist-background-color: #303030;
/* Settings */
--settings-button-bg-color: #1e1e1e;
--settings-button-text-color: var(--default-text-color);
--settings-bg-primary-color: #1e1e1e;
--settings-bg-secondary-color: #2a2a2a;
--settings-text-color: var(--default-text-color);
--settings-subtitle-color: #aaa;
} }

View File

@@ -1,43 +0,0 @@
:root {
/* Couleurs générales */
--background-color: #001224; /* body background bleu nuit */
--default-text-color: #eee; /* texte clair principal */
--subtitle-color: #007bff; /* sous-titres, header p */
--mp3-text-color: #000000; /* couleur texte checkbox inline */
/* Formulaire */
--form-bg-color: #ffffff; /* fond du formulaire */
--form-input-bg-color: #f9f9f9; /* fond inputs */
--form-input-border-color: #cccccc; /* bordure inputs */
--form-input-border-focus-color: #0056b3; /* bordure focus */
--form-input-text-color: #333333; /* texte inputs */
--form-input-placeholder-color: #aaaaaa; /* placeholder */
--form-button-bg-color: #007bff; /* bouton bg */
--form-button-text-color: #ffffff; /* texte bouton */
--form-button-bg-hover-color: #0056b3; /* bouton hover */
/* Checkbox */
--checkbox-bg-default: #cccccc;
--checkbox-bg-checked: #3B82F6;
--checkbox-checkmark-border-color: #E0E0E2;
--checkbox-pulse-color: rgba(59, 130, 246, 0.5);
/* Download status */
--download-status-color: #007bff;
/* Video Info Box */
--infos-box-color: #0c0140;
--video-info-text-color: #ffffff;
--video-info-heading-color: #007bff;
--video-info-list-color: #444444;
--video-info-list-strong-color: #000000;
--video-info-link-color: #007bff;
--video-info-link-hover-color: #0056b3;
/* Playlist Color */
--playlist-background-color: #070823a3;
}
body.default .download-path #savePath {
color: #ffffff;
}

View File

@@ -42,7 +42,17 @@ body.drift {
/* Playlist Color */ /* Playlist Color */
--playlist-background-color: #424b5cc2; --playlist-background-color: #424b5cc2;
}
/* Settings */
--settings-button-bg-color: #1e222ace;
--settings-button-text-color: #b3b3b4;
--settings-bg-primary-color: #1e222a;
--settings-bg-secondary-color: #272d38;
--settings-text-color: #bababa;
--settings-subtitle-color: #cacaca;
}

View File

@@ -36,4 +36,14 @@ body.fanatic{
/* Playlist Color */ /* Playlist Color */
--playlist-background-color: #2b2b2b; --playlist-background-color: #2b2b2b;
/* Settings */
--settings-button-bg-color: #1e1e1e;
--settings-button-text-color: #dcdcdc;
--settings-bg-primary-color: #1e1e1e;
--settings-bg-secondary-color: #2d2d2d;
--settings-text-color: #cecece;
--settings-subtitle-color: #adadad;
} }

View File

@@ -36,4 +36,13 @@ body.light {
/* Playlist Color */ /* Playlist Color */
--playlist-background-color: #c5c5c5b2; --playlist-background-color: #c5c5c5b2;
/* Settings */
--settings-button-bg-color: #e4e4e4;
--settings-button-text-color: var(--default-text-color);
--settings-bg-primary-color: #cbcbcb;
--settings-bg-secondary-color: #cbcbcb;
--settings-text-color: var(--default-text-color);
--settings-subtitle-color: #656565;
} }

View File

@@ -43,6 +43,15 @@ body.neon {
/* Playlist Color */ /* Playlist Color */
--playlist-background-color: #3e3e64; --playlist-background-color: #3e3e64;
/* Settings */
--settings-button-bg-color: #202038;
--settings-button-text-color: var(--default-text-color);
--settings-bg-primary-color: #242454;
--settings-bg-secondary-color: #2f2f6f;
--settings-text-color: var(--default-text-color);
--settings-subtitle-color: #d2d2d2;
} }
body.neon .download-path { body.neon .download-path {

View File

@@ -42,6 +42,15 @@ body.nf {
/* Playlist Color */ /* Playlist Color */
--playlist-background-color: #4f4f4f; --playlist-background-color: #4f4f4f;
/* Settings */
--settings-button-bg-color: #2a2a2a;
--settings-button-text-color: var(--default-text-color);
--settings-bg-primary-color: #2a2a2a;
--settings-bg-secondary-color: #313131;
--settings-text-color: var(--default-text-color);
--settings-subtitle-color: #7a7979;
} }
body.nf .download-path { body.nf .download-path {

View File

@@ -42,6 +42,15 @@ body.songbird {
/* Playlist Color */ /* Playlist Color */
--playlist-background-color: #3f0000; --playlist-background-color: #3f0000;
/* Settings */
--settings-button-bg-color: rgba(29, 0, 0, 0.784);
--settings-button-text-color: var(--default-text-color);
--settings-bg-primary-color: #121212;
--settings-bg-secondary-color: #1a1a1a;
--settings-text-color: var(--default-text-color);
--settings-subtitle-color: #7a7979;
} }
body.cyberpunk .download-path { body.cyberpunk .download-path {

View File

@@ -36,6 +36,17 @@ body.spicy {
/* Playlist Color */ /* Playlist Color */
--playlist-background-color: #353232; --playlist-background-color: #353232;
/* Settings */
--settings-button-bg-color: #1a1a1a;
--settings-button-text-color: #dcdcdc;
--settings-bg-primary-color: #1a1a1a;
--settings-bg-secondary-color: #292929;
--settings-text-color: #cecece;
--settings-subtitle-color: #adadad;
} }
/* Optionnel: background piments */ /* Optionnel: background piments */

View File

@@ -1,6 +1,5 @@
@import url("chirac-theme.css"); @import url("chirac-theme.css");
@import url("dark-theme.css"); @import url("dark-theme.css");
@import url("default-theme.css");
@import url("fanatic-theme.css"); @import url("fanatic-theme.css");
@import url("light-theme.css"); @import url("light-theme.css");
@import url("nf-theme.css"); @import url("nf-theme.css");

View File

@@ -33,4 +33,14 @@
--video-info-list-strong-color: #000000; --video-info-list-strong-color: #000000;
--video-info-link-color: #007bff; --video-info-link-color: #007bff;
--video-info-link-hover-color: #0056b3; --video-info-link-hover-color: #0056b3;
/* Settings */
--settings-button-bg-color: var(--form-bg-color);
--settings-button-text-color: var(--default-text-color);
--settings-bg-primary-color: var(--form-bg-color);
--settings-bg-secondary-color: var(--form-bg-color);
--settings-text-color: var(--default-text-color);
--settings-subtitle-color: #aaa;
} }

View File

@@ -1,7 +1,6 @@
const { fetchDownload } = require("../services/download.services"); const { fetchDownload } = require("../services/download.services");
const { logger } = require("../logger"); const { logger } = require("../logger");
const { isValidUrl, isSafePath } = require("../helpers/validation"); const { isValidUrl, isSafePath } = require("../helpers/validation");
const { buildYtDlpArgs } = require("../helpers/buildArgs");
const { notifyDownloadFinished } = require("../helpers/notify"); const { notifyDownloadFinished } = require("../helpers/notify");
const listeners = []; const listeners = [];
@@ -25,7 +24,7 @@ async function downloadController(req, res) {
} catch (err) { } catch (err) {
logger.error(`Server Error in /download : ${err.message}`); logger.error(`Server Error in /download : ${err.message}`);
res.status(500).send(`${err.message}`); res.status(500).send(`Server Error`);
} }
} }

View File

@@ -1,6 +1,3 @@
const express = require("express");
const router = express.Router();
const { fetchInfo } = require("../services/info.services"); const { fetchInfo } = require("../services/info.services");
const { parseVideo, parsePlaylist } = require("../helpers/parseInfo"); const { parseVideo, parsePlaylist } = require("../helpers/parseInfo");
const { logger } = require("../logger"); const { logger } = require("../logger");
@@ -12,7 +9,7 @@ async function infoController(req, res){
/* Si pas d'url ou url invalide*/ /* Si pas d'url ou url invalide*/
if (!url || !isValidUrl(url)) return res.status(400).send("❌ Invalid URL Or Missing"); if (!url || typeof url !== "string" || !isValidUrl(url)) return res.status(400).send("❌ Invalid URL Or Missing");
logger.info(`/Info Request receive by the Info Controller. URL: ${url}`); logger.info(`/Info Request receive by the Info Controller. URL: ${url}`);
@@ -40,7 +37,8 @@ async function infoController(req, res){
} catch (err) { } catch (err) {
return res.status(500).send(`${err.message}`); logger.error(`Info controller error: ${err && err.message ? err.message : err}`);
return res.status(500).send(`❌ Unable to fetch info`);
} }
} }

View File

@@ -2,22 +2,64 @@ const path = require("path");
const getUserBrowser = require("./getBrowser"); const getUserBrowser = require("./getBrowser");
const { ffmpegPath, denoPath} = require("./path"); const { ffmpegPath, denoPath} = require("./path");
const { configFeatures } = require("../../config.js");
const { logger } = require("../logger");
function validateCodec(codec){
const validCodec = [
"av01","vp9.2", "vp9" , "h265" , "h264" , "vp8" , "h263", "theora"
]
if(validCodec.includes(codec)){
logger.info(`Codec Valid: ${codec}`)
return codec
} else{
logger.error(`Codec not valid: ${codec}. Using default codec`)
return "h264"
}
}
function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) { function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
logger.info("--- CONFIGURATION ---");
logger.info(`CONFIG autoUpdate: ${configFeatures.autoUpdate}`);
logger.info(`CONFIG discordRPC: ${configFeatures.discordRPC}`);
logger.info(`CONFIG customTopBar: ${configFeatures.customTopBar}`);
logger.info(`CONFIG autoCheckInfo: ${configFeatures.autoCheckInfo}`);
logger.info(`CONFIG addThumbnail: ${configFeatures.addThumbnail}`);
logger.info(`CONFIG addMetadata: ${configFeatures.addMetadata}`);
logger.info(`CONFIG verboseLogs: ${configFeatures.verboseLogs}`);
logger.info(`CONFIG autoDownloadPlaylist: ${configFeatures.autoDownloadPlaylist}`);
logger.info(`CONFIG customCodec: ${configFeatures.customCodec}`);
// ACTIVATE THEM WHEN THE FEATURE IS IMPLEMENTED
// logger.info(`CONFIG logSystem: ${configFeatures.logSystem}`);
// logger.info(`CONFIG outputTitleCheck: ${configFeatures.outputTitleCheck}`);
// logger.info(`CONFIG downloadSystem: ${configFeatures.downloadSystem}`);
// logger.info(`CONFIG notifySystem: ${configFeatures.notifySystem}`);
logger.info("---------------------");
const args = [ const args = [
configFeatures.verboseLogs ? "--verbose" : null, // Verbose Logs
"--cookies-from-browser", `${getUserBrowser()}`, "--cookies-from-browser", `${getUserBrowser()}`,
"--no-continue", "--no-continue",
"--no-overwrites", "--no-overwrites",
"--embed-thumbnail", "--newline", // YT-DLP Logs Format
"--add-metadata", configFeatures.addThumbnail ? "--embed-thumbnail" : null,
configFeatures.addMetadata ? "--add-metadata" : null,
"--concurrent-fragments", "8", "--concurrent-fragments", "8",
"--retries", "10", "--retries", "10",
"--fragment-retries", "10", "--fragment-retries", "10",
"--ffmpeg-location", ffmpegPath, "--ffmpeg-location", ffmpegPath,
"--extractor-args","youtube:player_client=default", "--extractor-args","youtube:player_client=default",
"--js-runtimes", `deno:${denoPath}` "--js-runtimes", `deno:${denoPath}`,
"-S",
`vcodec:${validateCodec(configFeatures.customCodec) || "h264"}`+
`,acodec:aac`,
configFeatures.autoDownloadPlaylist ? "--yes-playlist" : "--no-playlist"
]; ];
if (audioOnly) args.push("-f", "bestaudio", "--extract-audio", "--audio-format", "mp3"); if (audioOnly) args.push("-f", "bestaudio", "--extract-audio", "--audio-format", "mp3");
@@ -36,7 +78,7 @@ function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
args.push("-o", path.join(outputFolder, "%(title)s.%(ext)s")); args.push("-o", path.join(outputFolder, "%(title)s.%(ext)s"));
args.push(url); args.push(url);
return args; return args.filter(Boolean);
} }
module.exports = { buildYtDlpArgs }; module.exports = { buildYtDlpArgs };

View File

@@ -3,24 +3,31 @@ const fs = require("fs");
const { app } = require("electron"); const { app } = require("electron");
const config = require("../../config"); const config = require("../../config");
const { logger } = require("../logger.js");
let userYtDlp; let userYtDlp;
let ffmpegPath; let ffmpegPath;
let denoPath; let denoPath;
const sourceYtDlp = path.join(process.resourcesPath, "yt-dlp.exe"); const sourceYtDlp = path.join(process.resourcesPath, "binaries","yt-dlp.exe");
if (config.localMode) { if (config.localMode) {
userYtDlp = path.join(__dirname, "../../ressources/yt-dlp.exe"); userYtDlp = path.join(__dirname, "../../ressources/yt-dlp.exe");
ffmpegPath = path.join(__dirname, "../../ressources"); // <- contient ffmpeg.exe et ffprobe.exe ffmpegPath = path.join(__dirname, "../../ressources/"); // <- contient ffmpeg.exe et ffprobe.exe
denoPath = path.join(__dirname, "../../ressources/deno.exe"); denoPath = path.join(__dirname, "../../ressources/deno.exe");
} else { } else {
userYtDlp = path.join(app.getPath("userData"), "yt-dlp.exe"); userYtDlp = path.join(app.getPath("userData"),"yt-dlp.exe");
ffmpegPath = path.join(process.resourcesPath, "ffmpeg.exe"); ffmpegPath = path.join(process.resourcesPath, "binaries","ffmpeg.exe");
denoPath = path.join(process.resourcesPath, "deno.exe"); denoPath = path.join(process.resourcesPath, "binaries","deno.exe");
if (!fs.existsSync(userYtDlp)) { if (!fs.existsSync(userYtDlp)) {
fs.copyFileSync(sourceYtDlp, userYtDlp); fs.copyFileSync(sourceYtDlp, userYtDlp);
} }
} }
if (!userYtDlp){ logger.error("Missing YT-DLP")}
if (!ffmpegPath){ logger.error("Missing FFMPEG")}
if (!denoPath){ logger.error("Missing DENO")}
module.exports = { userYtDlp, ffmpegPath, denoPath }; module.exports = { userYtDlp, ffmpegPath, denoPath };

View File

@@ -0,0 +1,14 @@
const RateLimit = require("express-rate-limit");
/* Rate Limite */
const rateLimite = RateLimit({
windowMs: 15 * 60, // 15 minutes
max: 5, // limit each IP to 100 requests per windowMs on the root path
message: "Too many requests, please try again later.",
statusCode: 429, // HTTP status code for "Too Many Requests"
});
module.exports = {
rateLimite
}

View File

@@ -1,11 +1,11 @@
const express = require("express"); const express = require("express");
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const { logger, logSessionStart, logSessionEnd } = require("./logger"); const { logger, logSessionEnd } = require("./logger");
const config = require("../config"); const config = require("../config");
const { execFile } = require("child_process"); const { execFile } = require("child_process");
const { userYtDlp } = require("./helpers/path"); const { userYtDlp } = require("./helpers/path");
const { rateLimite } = require("./helpers/rateLimit")
const app = express(); const app = express();
@@ -37,7 +37,7 @@ app.use(express.static(path.join(__dirname, "../public")));
app.use("/download", require("./routes/download.route")); app.use("/download", require("./routes/download.route"));
app.use("/info", require("./routes/info.route")); app.use("/info", require("./routes/info.route"));
app.get("/", (req, res) => { app.get("/", rateLimite ,(req, res) => {
res.sendFile(path.join(__dirname, "../public/index.html")); res.sendFile(path.join(__dirname, "../public/index.html"));
}); });

View File

@@ -1,5 +1,5 @@
const { autoUpdater } = require("electron-updater"); const { autoUpdater } = require("electron-updater");
const { app, Notification } = require("electron"); const { app, Notification, shell} = require("electron");
const { logger } = require("./logger"); const { logger } = require("./logger");
function AutoUpdater() { function AutoUpdater() {