mirror of
https://github.com/MasterAcnolo/Freedom-Loader.git
synced 2026-07-29 18:25:47 +02:00
Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d7f3b7481 | ||
|
|
cfe2e17313 | ||
|
|
dedf10c6b3 | ||
|
|
ac0b4a0750 | ||
|
|
29ca2ea496 | ||
|
|
2a3905bdfb | ||
|
|
d3755ab57d | ||
|
|
9824e107fe | ||
|
|
e437722937 | ||
|
|
fd8c4ae33c | ||
|
|
967144bd55 | ||
|
|
cd8eb05020 | ||
|
|
590060e504 | ||
|
|
26c7d4d2ee | ||
|
|
caa987fa42 | ||
|
|
ccb090e4b2 | ||
|
|
8d070ed10d | ||
|
|
343dbb26a2 | ||
|
|
fef4f6de36 | ||
|
|
1a61972750 | ||
|
|
45ae5d7f98 | ||
|
|
fecb123d0e | ||
|
|
5c62ed081a | ||
|
|
8f0f31041d | ||
|
|
0e48ab3b4c | ||
|
|
d4c33bcf2d | ||
|
|
2f105f5145 | ||
|
|
b7a193b062 | ||
|
|
65364d3095 | ||
|
|
c356e2e30d | ||
|
|
e45810b54c | ||
|
|
55db30febc | ||
|
|
163faec221 | ||
|
|
5398fed928 | ||
|
|
f959171db5 | ||
|
|
01b6bcd02c | ||
|
|
0ff4715b35 | ||
|
|
745a73a5b0 | ||
|
|
a69114edce | ||
|
|
489a4766fa | ||
|
|
adcee62386 | ||
|
|
b867d819b7 | ||
|
|
31d8b20a76 | ||
|
|
42ae0e7204 | ||
|
|
43869420c0 | ||
|
|
6c9d296fbd | ||
|
|
008428f10a | ||
|
|
ae7d338474 | ||
|
|
12ff25cc4b | ||
|
|
f31767e7e7 | ||
|
|
7c4f2841e1 | ||
|
|
c451ddd042 | ||
|
|
da6311055f | ||
|
|
945efb2059 | ||
|
|
ab2881f8e9 | ||
|
|
f23811fff4 | ||
|
|
19ab77501b | ||
|
|
37eeed583a | ||
|
|
caf08efb90 | ||
|
|
7ff998e4df | ||
|
|
0c1e8fe899 | ||
|
|
4158e8c6e3 | ||
|
|
653319e408 | ||
|
|
0a4f14fba7 | ||
|
|
fb4571fc51 | ||
|
|
c11fd40681 | ||
|
|
cbfde8b761 | ||
|
|
c774821cc4 | ||
|
|
2ed15a32bc | ||
|
|
c252853603 | ||
|
|
cfdddec073 | ||
|
|
97a63ab791 | ||
|
|
a0e00341e1 | ||
|
|
26d42491ce | ||
|
|
d678ba2f12 | ||
|
|
55b400eb05 | ||
|
|
0a6313b1f3 | ||
|
|
dc50a44682 |
35
.github/ISSUE_TEMPLATE/-bug--issues-template-.md
vendored
Normal file
35
.github/ISSUE_TEMPLATE/-bug--issues-template-.md
vendored
Normal 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)
|
||||
22
.github/ISSUE_TEMPLATE/-feat--feature-template.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/-feat--feature-template.md
vendored
Normal 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)
|
||||
29
.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
vendored
Normal file
29
.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
vendored
Normal 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.
|
||||
25
.github/PULL_REQUEST_TEMPLATE/version.md
vendored
Normal file
25
.github/PULL_REQUEST_TEMPLATE/version.md
vendored
Normal 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]
|
||||
15
.vscode/settings.json
vendored
15
.vscode/settings.json
vendored
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"editor.quickSuggestions": {
|
||||
"comments": "off",
|
||||
"strings": "off",
|
||||
"other": "off"
|
||||
},
|
||||
"editor.suggestOnTriggerCharacters": false,
|
||||
"editor.parameterHints.enabled": false,
|
||||
"editor.codeLens": false,
|
||||
"editor.lightbulb.enabled": "off",
|
||||
"editor.inlineSuggest.enabled": false,
|
||||
"github.copilot.enable": {
|
||||
"*": false
|
||||
}
|
||||
}
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal 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
66
CONTRIBUTING.md
Normal 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/) 🔥
|
||||
47
README.md
47
README.md
@@ -1,21 +1,14 @@
|
||||
<div align="center">
|
||||
<img
|
||||
<a href="https://masteracnolo.github.io/FreedomLoader/"><img
|
||||
src="./build/banner.png"
|
||||
alt="Banner"
|
||||
style="width: 50%;;"/>
|
||||
|
||||
style="width: 50%;;"/></a>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/MasterAcnolo/Freedom-Loader/releases">
|
||||
<img src="https://img.shields.io/badge/Release-1.3.0-blue?style=for-the-badge">
|
||||
</a>
|
||||
<a href="https://masteracnolo.github.io/No-Sense/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://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://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>
|
||||
<br>
|
||||
|
||||
@@ -103,29 +96,13 @@ Try restarting the application, checking the logs, or opening the developer cons
|
||||
|
||||
This project uses:
|
||||
|
||||
<div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
|
||||
|
||||
<a href="https://github.com/yt-dlp/yt-dlp">
|
||||
<img src="https://img.shields.io/badge/yt--dlp-Visit%20Repo-cf0000?style=for-the-badge&logo=github&logoColor=white">
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/FFmpeg/FFmpeg">
|
||||
<img src="https://img.shields.io/badge/FFmpeg-Visit%20Repo-666666?style=for-the-badge&logo=ffmpeg&logoColor=white" />
|
||||
</a>
|
||||
|
||||
<a href="https://nodejs.org/">
|
||||
<img src="https://img.shields.io/badge/Node.js-Visit%20Repo-339933?style=for-the-badge&logo=node.js&logoColor=white" />
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/electron/electron">
|
||||
<img src="https://img.shields.io/badge/Electron-Visit%20Repo-47848F?style=for-the-badge&logo=electron&logoColor=white" />
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/denoland/deno/">
|
||||
<img src="https://img.shields.io/badge/Deno-Visit%20Repo-14e0cc?style=for-the-badge&logo=deno&logoColor=white" />
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<p>
|
||||
<a href="https://github.com/yt-dlp/yt-dlp"><img src="https://img.shields.io/badge/yt--dlp-Visit%20Repo-cf0000?style=for-the-badge&logo=github&logoColor=white"></a>
|
||||
<a href="https://github.com/FFmpeg/FFmpeg"><img src="https://img.shields.io/badge/FFmpeg-Visit%20Repo-666666?style=for-the-badge&logo=ffmpeg&logoColor=white"></a>
|
||||
<a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Node.js-Visit%20Repo-339933?style=for-the-badge&logo=node.js&logoColor=white"></a>
|
||||
<a href="https://github.com/electron/electron"><img src="https://img.shields.io/badge/Electron-Visit%20Repo-47848F?style=for-the-badge&logo=electron&logoColor=white"></a>
|
||||
<a href="https://github.com/denoland/deno/"><img src="https://img.shields.io/badge/Deno-Visit%20Repo-14e0cc?style=for-the-badge&logo=deno&logoColor=white"></a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
|
||||
40
SECURITY.md
Normal file
40
SECURITY.md
Normal 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
BIN
build/banner.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 151 KiB |
20
config.js
20
config.js
@@ -1,10 +1,26 @@
|
||||
const packageJson = require("./package.json");
|
||||
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 = {
|
||||
version: packageJson.version,
|
||||
applicationPort: "8787",
|
||||
debugMode: true,
|
||||
localMode: !app.isPackaged,
|
||||
localMode,
|
||||
DiscordRPCID: "1410934537051181146",
|
||||
configFeatures
|
||||
}
|
||||
15
config/config.json
Normal file
15
config/config.json
Normal 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
|
||||
}
|
||||
232
main.js
232
main.js
@@ -2,33 +2,79 @@ const config = require("./config.js");
|
||||
const { app, BrowserWindow, ipcMain, dialog, Menu, shell } = require("electron");
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
const fs = require("fs");
|
||||
|
||||
const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger");
|
||||
const { AutoUpdater } = require("./server/update.js");
|
||||
const { configFeatures } = require("./config.js");
|
||||
const { startRPC, stopRPC} = require("./server/discordRPC");
|
||||
|
||||
let mainWindow;
|
||||
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");
|
||||
|
||||
app.setAppUserModelId("com.masteracnolo.freedomloader"); // pour notifications Windows
|
||||
app.disableHardwareAcceleration();
|
||||
|
||||
ipcMain.handle("version", () => config.version);
|
||||
|
||||
// Gestion single instance
|
||||
const gotLock = app.requestSingleInstanceLock();
|
||||
|
||||
if (!gotLock) {
|
||||
// Une instance existe déjà -> fermer l'ancienne et continuer la nouvelle
|
||||
// Ici la nouvelle instance continue normalement
|
||||
} else {
|
||||
app.on("second-instance", () => {
|
||||
// La vieille instance se ferme
|
||||
if (mainWindow) {
|
||||
logger.info("New Instance Detected, closing the older...");
|
||||
mainWindow.destroy();
|
||||
mainWindow = null;
|
||||
}
|
||||
});
|
||||
// Native dependencies check (yt-dlp.exe, ffmpeg.exe, ffprobe.exe, Deno)
|
||||
function checkNativeDependencies() {
|
||||
const deps = [
|
||||
{ name: "yt-dlp.exe", path: path.join(process.resourcesPath, "binaries","yt-dlp.exe") },
|
||||
{ name: "ffmpeg.exe", path: path.join(process.resourcesPath, "binaries", "ffmpeg.exe") },
|
||||
{ name: "ffprobe.exe", path: path.join(process.resourcesPath, "binaries", "ffprobe.exe") },
|
||||
{ name: "deno.exe", path: path.join(process.resourcesPath, "binaries", "deno.exe") },
|
||||
];
|
||||
const missing = deps.filter(dep => !fs.existsSync(dep.path));
|
||||
let errorMsg = "";
|
||||
if (missing.length > 0) {
|
||||
const missingList = missing.map(dep => dep.name).join(", ");
|
||||
logger.error(`Missing dependencies: ${missingList}`);
|
||||
errorMsg += `The following files are missing in the 'ressources' folder:\n${missingList}`;
|
||||
}
|
||||
if (errorMsg) {
|
||||
app.whenReady().then(() => {
|
||||
dialog.showErrorBox(
|
||||
"Missing dependencies",
|
||||
`${errorMsg}\n\nThe application will now exit. Try to reinstall`
|
||||
);
|
||||
app.quit();
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!config.localMode){
|
||||
if (!gotLock) {
|
||||
// Une instance existe déjà -> fermer l'ancienne et continuer la nouvelle
|
||||
// Ici la nouvelle instance continue normalement
|
||||
} else {
|
||||
if (!checkNativeDependencies()) {
|
||||
// Arrêt déjà géré dans la fonction
|
||||
} else {
|
||||
app.on("second-instance", () => {
|
||||
// La vieille instance se ferme
|
||||
if (mainWindow) {
|
||||
logger.info("New Instance Detected, closing the older...");
|
||||
mainWindow.destroy();
|
||||
mainWindow = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Création fenêtre principale
|
||||
async function createMainWindow() {
|
||||
if (mainWindow) {
|
||||
@@ -42,6 +88,7 @@ async function createMainWindow() {
|
||||
height: 800,
|
||||
minWidth: 750,
|
||||
minHeight: 800,
|
||||
frame: !configFeatures.customTopBar,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
@@ -62,86 +109,150 @@ 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
|
||||
ipcMain.handle("select-download-folder", async () => {
|
||||
try {
|
||||
const result = await dialog.showOpenDialog({ properties: ["openDirectory"] });
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
logger.info(`Dossier sélectionné : ${result.filePaths[0]}`);
|
||||
return result.filePaths[0];
|
||||
const validatedPath = validateDownloadPath(result.filePaths[0]);
|
||||
logger.info(`Folder Checked and Valid : ${validatedPath}`);
|
||||
return validatedPath;
|
||||
}
|
||||
return null;
|
||||
} catch (err) {
|
||||
logger.error(`Error when creating Output Folder : ${err.message}`);
|
||||
logger.error(`An Error Occured when validating folder : ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("validate-download-path", (event, userPath) => {
|
||||
return validateDownloadPath(userPath);
|
||||
});
|
||||
|
||||
|
||||
ipcMain.handle("get-default-download-path", () => defaultDownloadPath);
|
||||
|
||||
ipcMain.on("set-progress", (event, percent) => {
|
||||
if (mainWindow) mainWindow.setProgressBar(percent / 100); // Electron attend 0 → 1
|
||||
});
|
||||
|
||||
// Menu
|
||||
function setupMenu() {
|
||||
const menuTemplate = [
|
||||
{
|
||||
label: "Logs",
|
||||
submenu: [
|
||||
{
|
||||
label: "Open Logs",
|
||||
click: () => shell.openPath(logsFolderPath),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Website",
|
||||
submenu: [
|
||||
{
|
||||
label: "Go to Website",
|
||||
click: () => shell.openExternal("https://masteracnolo.github.io/FreedomLoader/"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Documentation",
|
||||
submenu: [
|
||||
{
|
||||
label: "Go to Wiki",
|
||||
click: () => shell.openExternal("https://masteracnolo.github.io/FreedomLoader/pages/wiki.html"),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
];
|
||||
// Topbar window controls
|
||||
ipcMain.on("window-minimize", () => {
|
||||
if (mainWindow) mainWindow.minimize();
|
||||
});
|
||||
ipcMain.on("window-maximize", () => {
|
||||
if (mainWindow) {
|
||||
if (mainWindow.isMaximized()) {
|
||||
mainWindow.unmaximize();
|
||||
} else {
|
||||
mainWindow.maximize();
|
||||
}
|
||||
}
|
||||
});
|
||||
ipcMain.on("window-close", () => {
|
||||
if (mainWindow) mainWindow.close();
|
||||
});
|
||||
|
||||
const defaultMenu = Menu.getApplicationMenu();
|
||||
const mergedTemplate = defaultMenu
|
||||
? [...defaultMenu.items.map(item => item), ...menuTemplate]
|
||||
: menuTemplate;
|
||||
// Topbar custom actions
|
||||
ipcMain.on("open-devtools", () => {
|
||||
if (mainWindow) mainWindow.webContents.openDevTools({ mode: 'detach' });
|
||||
});
|
||||
ipcMain.on("open-logs", () => {
|
||||
if (logsFolderPath) shell.openPath(logsFolderPath);
|
||||
});
|
||||
ipcMain.on("open-website", () => {
|
||||
shell.openExternal("https://masteracnolo.github.io/FreedomLoader/index.html");
|
||||
});
|
||||
ipcMain.on("open-wiki", () => {
|
||||
shell.openExternal("https://masteracnolo.github.io/FreedomLoader/pages/wiki.html");
|
||||
});
|
||||
ipcMain.on("open-config", () => {
|
||||
if (configFolderPath) shell.openPath(configFolderPath);
|
||||
});
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(mergedTemplate));
|
||||
}
|
||||
|
||||
// App ready
|
||||
app.whenReady().then(async () => {
|
||||
logSessionStart();
|
||||
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);
|
||||
|
||||
try {
|
||||
await expressServer.startServer();
|
||||
logger.info("Express Server Started");
|
||||
|
||||
const { startRPC } = require("./server/discordRPC");
|
||||
startRPC();
|
||||
ipcMain.handle("features", () => {
|
||||
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();
|
||||
AutoUpdater(mainWindow);
|
||||
setupMenu();
|
||||
configFeatures.autoUpdate ? AutoUpdater(mainWindow) : ""; // Auto Update
|
||||
|
||||
} catch (err) {
|
||||
logger.error("Window or Server error :", err);
|
||||
app.quit();
|
||||
@@ -153,4 +264,7 @@ app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
||||
|
||||
app.on("before-quit", () => logSessionEnd());
|
||||
app.on("before-quit", () => {
|
||||
logSessionEnd()
|
||||
stopRPC()
|
||||
});
|
||||
|
||||
1486
package-lock.json
generated
1486
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@@ -1,28 +1,29 @@
|
||||
{
|
||||
"name": "freedom-loader",
|
||||
"productName": "Freedom Loader",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.1",
|
||||
"author": "MasterAcnolo",
|
||||
"description": "Freedom Loader",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron --trace-warnings .",
|
||||
"dist": "electron-builder"
|
||||
"build": "electron-builder",
|
||||
"update": "npm update"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2",
|
||||
"debug": "^4.4.1",
|
||||
"discord-rpc": "^4.0.1",
|
||||
"electron-squirrel-startup": "^1.0.1",
|
||||
"electron-updater": "^6.6.2",
|
||||
"express": "^5.1.0",
|
||||
"express-rate-limit": "^8.2.1",
|
||||
"webidl-conversions": "^8.0.0",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^37.1.0",
|
||||
"electron-builder": "^26.0.12"
|
||||
"electron-builder": "^26.4.0"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.masteracnolo.freedomloader",
|
||||
@@ -37,9 +38,7 @@
|
||||
"files": [
|
||||
"**/*",
|
||||
"node_modules/**/*",
|
||||
"!logs/*.json",
|
||||
"server/**/*",
|
||||
"server/node_modules/**/*"
|
||||
"!logs/*.json"
|
||||
],
|
||||
"directories": {
|
||||
"buildResources": "build"
|
||||
@@ -54,29 +53,33 @@
|
||||
"from": "build/app-icon.ico",
|
||||
"to": "app-icon.ico"
|
||||
},
|
||||
{
|
||||
"from": "build/banner.bmp",
|
||||
"to": "banner.bmp"
|
||||
},
|
||||
{
|
||||
"from": "build/error.png",
|
||||
"to": "error.png"
|
||||
},
|
||||
{
|
||||
"from": "ressources/yt-dlp.exe",
|
||||
"to": "yt-dlp.exe"
|
||||
"to": "binaries/yt-dlp.exe"
|
||||
},
|
||||
{
|
||||
"from": "ressources/ffmpeg.exe",
|
||||
"to": "ffmpeg.exe"
|
||||
"to": "binaries/ffmpeg.exe"
|
||||
},
|
||||
{
|
||||
"from": "ressources/ffprobe.exe",
|
||||
"to": "ffprobe.exe"
|
||||
"to": "binaries/ffprobe.exe"
|
||||
},
|
||||
{
|
||||
"from": "ressources/deno.exe",
|
||||
"to": "deno.exe"
|
||||
"to": "binaries/deno.exe"
|
||||
},
|
||||
{
|
||||
"from": "server",
|
||||
"to": "server"
|
||||
"from": "config/config.json",
|
||||
"to": "config/config.json"
|
||||
}
|
||||
],
|
||||
"win": {
|
||||
@@ -89,6 +92,7 @@
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"installerIcon": "build/app-icon.ico",
|
||||
"uninstallerIcon": "build/uninstaller-icon.ico",
|
||||
"installerSidebar": "build/banner.bmp",
|
||||
"createDesktopShortcut": true,
|
||||
"createStartMenuShortcut": true,
|
||||
"shortcutName": "Freedom Loader"
|
||||
|
||||
18
preload.js
18
preload.js
@@ -3,5 +3,21 @@ const { contextBridge, ipcRenderer } = require("electron");
|
||||
contextBridge.exposeInMainWorld("electronAPI", {
|
||||
getDefaultDownloadPath: () => ipcRenderer.invoke("get-default-download-path"),
|
||||
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
|
||||
contextBridge.exposeInMainWorld("topbarAPI", {
|
||||
minimize: () => ipcRenderer.send("window-minimize"),
|
||||
maximize: () => ipcRenderer.send("window-maximize"),
|
||||
close: () => ipcRenderer.send("window-close"),
|
||||
openDevTools: () => ipcRenderer.send("open-devtools"),
|
||||
openLogs: () => ipcRenderer.send("open-logs"),
|
||||
openWebsite: () => ipcRenderer.send("open-website"),
|
||||
openWiki: () => ipcRenderer.send("open-wiki"),
|
||||
openConfig: () => ipcRenderer.send("open-config")
|
||||
});
|
||||
|
||||
1
public/assets/icon/close.svg
Normal file
1
public/assets/icon/close.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M128 128C92.7 128 64 156.7 64 192L64 448C64 483.3 92.7 512 128 512L512 512C547.3 512 576 483.3 576 448L576 192C576 156.7 547.3 128 512 128L128 128zM231 231C240.4 221.6 255.6 221.6 264.9 231L319.9 286L374.9 231C384.3 221.6 399.5 221.6 408.8 231C418.1 240.4 418.2 255.6 408.8 264.9L353.8 319.9L408.8 374.9C418.2 384.3 418.2 399.5 408.8 408.8C399.4 418.1 384.2 418.2 374.9 408.8L319.9 353.8L264.9 408.8C255.5 418.2 240.3 418.2 231 408.8C221.7 399.4 221.6 384.2 231 374.9L286 319.9L231 264.9C221.6 255.5 221.6 240.3 231 231z"/></svg>
|
||||
|
After Width: | Height: | Size: 751 B |
1
public/assets/icon/maximize.svg
Normal file
1
public/assets/icon/maximize.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M128 128C92.7 128 64 156.7 64 192L64 448C64 483.3 92.7 512 128 512L512 512C547.3 512 576 483.3 576 448L576 192C576 156.7 547.3 128 512 128L128 128zM152 192L488 192C501.3 192 512 202.7 512 216C512 229.3 501.3 240 488 240L152 240C138.7 240 128 229.3 128 216C128 202.7 138.7 192 152 192z"/></svg>
|
||||
|
After Width: | Height: | Size: 515 B |
1
public/assets/icon/minimize.svg
Normal file
1
public/assets/icon/minimize.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M64 480C64 462.3 78.3 448 96 448L544 448C561.7 448 576 462.3 576 480C576 497.7 561.7 512 544 512L96 512C78.3 512 64 497.7 64 480z"/></svg>
|
||||
|
After Width: | Height: | Size: 360 B |
@@ -12,16 +12,193 @@
|
||||
<title></title>
|
||||
|
||||
<link rel="stylesheet" href="styles/styles.css">
|
||||
<link rel="stylesheet" href="styles/layout/topbar.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="theme-switcher">
|
||||
<label for="themeSelect">Thème :</label>
|
||||
<select id="themeSelect" aria-label="Choisir le thème"></select>
|
||||
<div class="topbar" id="topbar">
|
||||
<div class="custom-controls">
|
||||
<button id="devtools-btn" title="Dev Tools">Tools</button>
|
||||
<button id="logs-btn" title="Logs">Logs</button>
|
||||
<button id="website-btn" title="Website">Website</button>
|
||||
<button id="wiki-btn" title="Wiki">Wiki</button>
|
||||
</div>
|
||||
<div class="window-controls">
|
||||
<button id="minimize-btn" title="Réduire"><img src="assets/icon/minimize.svg"></button>
|
||||
<button id="maximize-btn" title="Maximiser"><img src="assets/icon/maximize.svg"></button>
|
||||
<button id="close-btn" title="Fermer"><img src="assets/icon/close.svg"></button>
|
||||
</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="container">
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="container" id="container">
|
||||
|
||||
<header>
|
||||
<h1 id="title"> Freedom Loader </h1>
|
||||
@@ -80,12 +257,20 @@
|
||||
<div id="videoInfo" style="margin-top: 20px;"></div>
|
||||
</div>
|
||||
|
||||
<span id="version-badge" class="version-badge"></span>
|
||||
|
||||
<h2 id="reference"> Because Why Not ? </h2>
|
||||
|
||||
|
||||
<!-- Scripts -->
|
||||
<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/fetchinfo.js"></script>
|
||||
<script src="script/progressBar.js"></script>
|
||||
<script src="script/customthemes.js"></script>
|
||||
<script type="module" src="script/topbar.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
6
public/script/appVersion.js
Normal file
6
public/script/appVersion.js
Normal file
@@ -0,0 +1,6 @@
|
||||
async function versionLabel(){
|
||||
const appVersion = await window.electronAPI.getVersion();
|
||||
document.getElementById("version-badge").textContent = `v${appVersion}`;
|
||||
};
|
||||
|
||||
versionLabel();
|
||||
@@ -1,41 +1,62 @@
|
||||
window.addEventListener("DOMContentLoaded", async () => {
|
||||
const savePathElem = document.getElementById("savePath");
|
||||
const form = document.getElementById("downloadForm");
|
||||
|
||||
// Essayer de charger depuis le localStorage
|
||||
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à
|
||||
// input caché = DERNIER chemin validé par le back
|
||||
let hidden = document.getElementById("savePathInput");
|
||||
if (!hidden) {
|
||||
hidden = document.createElement("input");
|
||||
hidden.type = "hidden";
|
||||
hidden.name = "savePath";
|
||||
hidden.id = "savePathInput";
|
||||
document.getElementById("downloadForm").appendChild(hidden);
|
||||
form.appendChild(hidden);
|
||||
}
|
||||
hidden.value = savedPath;
|
||||
|
||||
// Gestion du bouton de modification
|
||||
document.getElementById("changePath").addEventListener("click", async () => {
|
||||
const selectedPath = await window.electronAPI.selectDownloadFolder();
|
||||
if (selectedPath) {
|
||||
// Met à jour l'affichage
|
||||
savePathElem.textContent = selectedPath;
|
||||
hidden.value = selectedPath;
|
||||
async function applyPathFromBack(path) {
|
||||
savePathElem.textContent = path;
|
||||
hidden.value = path;
|
||||
localStorage.setItem("customDownloadPath", path); // UX only
|
||||
}
|
||||
|
||||
// Et le stocke en localStorage pour la prochaine fois
|
||||
localStorage.setItem("customDownloadPath", selectedPath);
|
||||
async function loadInitialPath() {
|
||||
// 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é.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
const themes = {
|
||||
default: { label: "Default", subtitle: "Because why not?" },
|
||||
dark: { label: "Sombre", subtitle: "Darkness is my ally" },
|
||||
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" },
|
||||
songbird: { label: "Songbird", subtitle: "From Her to Eternity" },
|
||||
drift: { label: "Drift", subtitle: "Si la route t'appelle, contre appel" },
|
||||
|
||||
@@ -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 () => {
|
||||
const url = urlInput.value.trim();
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const urlInput = document.getElementById("UrlInput");
|
||||
const infoDiv = document.getElementById("videoInfo");
|
||||
const loaderBox = document.getElementById("loaderBox");
|
||||
|
||||
// Si champ vide -> reset total
|
||||
if (!url || url.length < 2) {
|
||||
infoDiv.innerHTML = "";
|
||||
infoDiv.classList.remove("visible", "playlist-mode");
|
||||
lastFetchedUrl = "";
|
||||
return;
|
||||
}
|
||||
let lastFetchedUrl = "";
|
||||
|
||||
if (url === lastFetchedUrl) return;
|
||||
lastFetchedUrl = url;
|
||||
urlInput.addEventListener("input", async () => {
|
||||
|
||||
loaderBox.style.display = "flex";
|
||||
const data = await fetchVideoInfo(url);
|
||||
loaderBox.style.display = "none";
|
||||
const configFeatures = await window.electronAPI.getFeatures();
|
||||
if (!configFeatures.autoCheckInfo) return;
|
||||
|
||||
// Gestion des erreurs
|
||||
if (data.error) {
|
||||
infoDiv.innerHTML = `
|
||||
<div style="
|
||||
padding:7px;
|
||||
background:var(----infos-box-color);
|
||||
">
|
||||
<strong>${data.error}</strong>
|
||||
</div>
|
||||
`;
|
||||
infoDiv.classList.add("visible");
|
||||
infoDiv.classList.remove("playlist-mode");
|
||||
const url = urlInput.value.trim();
|
||||
|
||||
// Si champ vide -> reset total
|
||||
if (!url || url.length < 2) {
|
||||
infoDiv.innerHTML = "";
|
||||
infoDiv.classList.remove("visible", "playlist-mode");
|
||||
lastFetchedUrl = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (url === lastFetchedUrl) return;
|
||||
lastFetchedUrl = url;
|
||||
|
||||
loaderBox.style.display = "flex";
|
||||
const data = await fetchVideoInfo(url);
|
||||
loaderBox.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
// ---------- PLAYLIST ----------
|
||||
if (data.type === "playlist") {
|
||||
infoDiv.classList.add("playlist-mode");
|
||||
infoDiv.innerHTML = `
|
||||
<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>
|
||||
`;
|
||||
|
||||
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>
|
||||
// Gestion des erreurs
|
||||
if (data.error) {
|
||||
infoDiv.innerHTML = `
|
||||
<div style="
|
||||
padding:7px;
|
||||
background:var(----infos-box-color);
|
||||
">
|
||||
<strong>${data.error}</strong>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
infoDiv.classList.add("visible");
|
||||
infoDiv.classList.remove("playlist-mode");
|
||||
loaderBox.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
// Gestion du bouton copier
|
||||
listDiv.addEventListener("click", (event) => {
|
||||
if (event.target.classList.contains("copy-btn")) {
|
||||
const btn = event.target;
|
||||
if (btn.disabled) return;
|
||||
// ---------- PLAYLIST ----------
|
||||
if (data.type === "playlist") {
|
||||
infoDiv.classList.add("playlist-mode");
|
||||
infoDiv.innerHTML = `
|
||||
<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 url = btn.dataset.url;
|
||||
navigator.clipboard.writeText(url)
|
||||
.then(() => {
|
||||
const original = btn.textContent;
|
||||
const listDiv = document.getElementById("playlistVideos");
|
||||
|
||||
btn.style.opacity = 0;
|
||||
btn.style.transform = "scale(0.7)";
|
||||
data.videos.forEach(v => {
|
||||
const durationStr = v.duration
|
||||
? `${Math.floor(v.duration / 60)}m ${(v.duration % 60).toString().padStart(2,"0")}s` : "Inconnue";
|
||||
|
||||
setTimeout(() => {
|
||||
btn.textContent = "✅";
|
||||
btn.style.opacity = 1;
|
||||
btn.style.transform = "scale(1)";
|
||||
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>
|
||||
`;
|
||||
});
|
||||
|
||||
// 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(() => {
|
||||
btn.style.opacity = 0;
|
||||
btn.style.transform = "scale(0.7)";
|
||||
btn.textContent = "✅";
|
||||
btn.style.opacity = 1;
|
||||
btn.style.transform = "scale(1)";
|
||||
|
||||
setTimeout(() => {
|
||||
btn.textContent = original;
|
||||
btn.style.opacity = 1;
|
||||
btn.style.transform = "scale(1)";
|
||||
btn.disabled = false;
|
||||
}, 300);
|
||||
btn.style.opacity = 0;
|
||||
btn.style.transform = "scale(0.7)";
|
||||
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
btn.textContent = original;
|
||||
btn.style.opacity = 1;
|
||||
btn.style.transform = "scale(1)";
|
||||
btn.disabled = false;
|
||||
}, 300);
|
||||
|
||||
}, 300);
|
||||
})
|
||||
.catch(() => {
|
||||
const original = btn.textContent;
|
||||
btn.textContent = "❌";
|
||||
setTimeout(() => {
|
||||
btn.textContent = original;
|
||||
btn.disabled = false;
|
||||
}, 1500);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
}, 300);
|
||||
})
|
||||
.catch(() => {
|
||||
const original = btn.textContent;
|
||||
btn.textContent = "❌";
|
||||
setTimeout(() => {
|
||||
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");
|
||||
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");
|
||||
});
|
||||
|
||||
})
|
||||
init();
|
||||
50
public/script/settingsPanel.js
Normal file
50
public/script/settingsPanel.js
Normal 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();
|
||||
34
public/script/topbar.js
Normal file
34
public/script/topbar.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// Ajout des listeners pour la topbar
|
||||
function setupTopbarListeners() {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const { topbarAPI } = window;
|
||||
if (!topbarAPI) return;
|
||||
const minBtn = document.getElementById('minimize-btn');
|
||||
const maxBtn = document.getElementById('maximize-btn');
|
||||
const closeBtn = document.getElementById('close-btn');
|
||||
const devtoolsBtn = document.getElementById('devtools-btn');
|
||||
const logsBtn = document.getElementById('logs-btn');
|
||||
const websiteBtn = document.getElementById('website-btn');
|
||||
const wikiBtn = document.getElementById('wiki-btn');
|
||||
const configBtn = document.getElementById('config-btn');
|
||||
|
||||
if (minBtn) minBtn.onclick = () => topbarAPI.minimize();
|
||||
if (maxBtn) maxBtn.onclick = () => topbarAPI.maximize();
|
||||
if (closeBtn) closeBtn.onclick = () => topbarAPI.close();
|
||||
if (devtoolsBtn) devtoolsBtn.onclick = () => topbarAPI.openDevTools();
|
||||
if (logsBtn) logsBtn.onclick = () => topbarAPI.openLogs();
|
||||
if (websiteBtn) websiteBtn.onclick = () => topbarAPI.openWebsite();
|
||||
if (wikiBtn) wikiBtn.onclick = () => topbarAPI.openWiki();
|
||||
if (configBtn) configBtn.onclick = () => topbarAPI.openConfig();
|
||||
});
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
.theme-switcher {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
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: 1000;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
234
public/styles/layout/settingsPanel.css
Normal file
234
public/styles/layout/settingsPanel.css
Normal 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; }
|
||||
}
|
||||
116
public/styles/layout/topbar.css
Normal file
116
public/styles/layout/topbar.css
Normal file
@@ -0,0 +1,116 @@
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1100;
|
||||
|
||||
height: 48px;
|
||||
width: 100%;
|
||||
|
||||
background: #1f1f1f98;
|
||||
color: #eaeaea;
|
||||
|
||||
-webkit-app-region: drag;
|
||||
user-select: none;
|
||||
|
||||
}
|
||||
|
||||
/* LEFT - custom buttons */
|
||||
.custom-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
/* RIGHT - window buttons */
|
||||
.window-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* GLOBAL BUTTON RESET */
|
||||
.topbar button {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
transition: background 0.15s ease, transform 0.1s ease;
|
||||
|
||||
}
|
||||
|
||||
/* Custom buttons (Logs / Wiki / etc.) */
|
||||
.custom-controls button {
|
||||
height: 32px;
|
||||
padding: 0 14px;
|
||||
|
||||
font-size: 15px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.custom-controls button:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
/* Window buttons (Windows style) */
|
||||
.window-controls button {
|
||||
width: 46px;
|
||||
height: 48px;
|
||||
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.window-controls button:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.window-controls img {
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
pointer-events: none;
|
||||
filter: invert(100%);
|
||||
}
|
||||
|
||||
|
||||
#close-btn:hover {
|
||||
background: #e81123;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Active effect */
|
||||
.topbar button:active {
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
#close-btn:active {
|
||||
background: #c50f1f;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.version-badge {
|
||||
position: fixed;
|
||||
right: 18px;
|
||||
bottom: 12px;
|
||||
z-index: 2000;
|
||||
font-size: 0.85em;
|
||||
color: #b0b0b0;
|
||||
background: none;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
padding: 2px 8px;
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
box-shadow: none;
|
||||
user-select: none;
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
/* Layout */
|
||||
@import url("layout/form.css");
|
||||
@import url("layout/settingsPanel.css");
|
||||
@import url("layout/container.css");
|
||||
@import url("layout/header.css");
|
||||
@import url("layout/videoinfo.css");
|
||||
|
||||
/* Components */
|
||||
@import url("components/checkbox.css");
|
||||
@import url("components/themebutton.css");
|
||||
@import url("components/loader.css");
|
||||
|
||||
@import url("theme/themeimport.css");
|
||||
@@ -49,6 +49,57 @@ body {
|
||||
|
||||
}
|
||||
|
||||
/* Because why not */
|
||||
#reference{
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
bottom:0vh;
|
||||
left: 10px;
|
||||
font-size: 1em;
|
||||
color: #b0b0b02f;
|
||||
}
|
||||
|
||||
/* Scrollbar globale pour Chromium/Electron */
|
||||
body::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 5px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: padding-box;
|
||||
transition: background-color 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(255, 255, 255, 0.35);
|
||||
transform: scaleX(1.05);
|
||||
}
|
||||
|
||||
/* Thème clair */
|
||||
body.light::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
body.light::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
body.light::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.container{
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
header p{
|
||||
color: var(--subtitle-color);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,16 @@ body.chirac {
|
||||
|
||||
/* Playlist Color */
|
||||
--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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -36,4 +36,14 @@ body.dark {
|
||||
|
||||
/* Playlist Color */
|
||||
--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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -42,7 +42,17 @@ body.drift {
|
||||
|
||||
/* Playlist Color */
|
||||
--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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -36,4 +36,14 @@ body.fanatic{
|
||||
|
||||
/* Playlist Color */
|
||||
--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;
|
||||
}
|
||||
|
||||
@@ -36,4 +36,13 @@ body.light {
|
||||
|
||||
/* Playlist Color */
|
||||
--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;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,15 @@ body.neon {
|
||||
|
||||
/* Playlist Color */
|
||||
--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 {
|
||||
|
||||
@@ -42,6 +42,15 @@ body.nf {
|
||||
|
||||
/* Playlist Color */
|
||||
--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 {
|
||||
|
||||
@@ -42,6 +42,15 @@ body.songbird {
|
||||
|
||||
/* Playlist Color */
|
||||
--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 {
|
||||
|
||||
@@ -36,6 +36,17 @@ body.spicy {
|
||||
|
||||
/* Playlist Color */
|
||||
--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 */
|
||||
|
||||
1
public/styles/theme/themeimport.css
vendored
1
public/styles/theme/themeimport.css
vendored
@@ -1,6 +1,5 @@
|
||||
@import url("chirac-theme.css");
|
||||
@import url("dark-theme.css");
|
||||
@import url("default-theme.css");
|
||||
@import url("fanatic-theme.css");
|
||||
@import url("light-theme.css");
|
||||
@import url("nf-theme.css");
|
||||
|
||||
@@ -33,4 +33,14 @@
|
||||
--video-info-list-strong-color: #000000;
|
||||
--video-info-link-color: #007bff;
|
||||
--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;
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -1,7 +1,6 @@
|
||||
const { fetchDownload } = require("../services/download.services");
|
||||
const { logger } = require("../logger");
|
||||
const { isValidUrl, isSafePath } = require("../helpers/validation");
|
||||
const { buildYtDlpArgs } = require("../helpers/buildArgs");
|
||||
const { notifyDownloadFinished } = require("../helpers/notify");
|
||||
|
||||
const listeners = [];
|
||||
@@ -25,7 +24,7 @@ async function downloadController(req, res) {
|
||||
|
||||
} catch (err) {
|
||||
logger.error(`Server Error in /download : ${err.message}`);
|
||||
res.status(500).send(`❌ ${err.message}`);
|
||||
res.status(500).send(`❌ Server Error`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
|
||||
const { fetchInfo } = require("../services/info.services");
|
||||
const { parseVideo, parsePlaylist } = require("../helpers/parseInfo");
|
||||
const { logger } = require("../logger");
|
||||
@@ -12,7 +9,7 @@ async function infoController(req, res){
|
||||
|
||||
|
||||
/* 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}`);
|
||||
|
||||
@@ -40,7 +37,8 @@ async function infoController(req, res){
|
||||
|
||||
|
||||
} 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`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ function startRPC() {
|
||||
smallImageText: "By MasterAcnolo",
|
||||
startTimestamp: new Date(),
|
||||
details: `Open Source Download Tools - ${config.version}`,
|
||||
state: "masteracnolo.github.io/No-Sense/FreedomLoader",
|
||||
state: "masteracnolo.github.io/FreedomLoader",
|
||||
};
|
||||
|
||||
rpc.clearActivity()
|
||||
rpc.setActivity(presence);
|
||||
|
||||
// Met à jour la présence toutes les 15s
|
||||
@@ -26,27 +26,26 @@ function startRPC() {
|
||||
}, 15000);
|
||||
});
|
||||
|
||||
async function cleanExit() {
|
||||
try {
|
||||
if (intervalId) clearInterval(intervalId);
|
||||
|
||||
if (rpc && rpc.transport) {
|
||||
await rpc.destroy();
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error("Error while closing the RPC:", err);
|
||||
} finally {
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
|
||||
process.on("exit", cleanExit);
|
||||
process.on("SIGINT", cleanExit);
|
||||
process.on("SIGTERM", cleanExit);
|
||||
|
||||
rpc.login({ clientId }).catch(err => {
|
||||
logger.error("Unable to connect to the RPC:", err);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { startRPC };
|
||||
async function stopRPC(){
|
||||
try {
|
||||
if (intervalId) clearInterval(intervalId);
|
||||
|
||||
if (rpc && rpc.transport) {
|
||||
await rpc.clearActivity()
|
||||
await rpc.destroy();
|
||||
} else{
|
||||
logger.error("Not Able to close RPC")
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.error("Error while closing the RPC:", err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = { startRPC, stopRPC};
|
||||
|
||||
@@ -2,22 +2,64 @@ const path = require("path");
|
||||
|
||||
const getUserBrowser = require("./getBrowser");
|
||||
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 }) {
|
||||
|
||||
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 = [
|
||||
configFeatures.verboseLogs ? "--verbose" : null, // Verbose Logs
|
||||
"--cookies-from-browser", `${getUserBrowser()}`,
|
||||
"--no-continue",
|
||||
"--no-overwrites",
|
||||
"--embed-thumbnail",
|
||||
"--add-metadata",
|
||||
"--newline", // YT-DLP Logs Format
|
||||
configFeatures.addThumbnail ? "--embed-thumbnail" : null,
|
||||
configFeatures.addMetadata ? "--add-metadata" : null,
|
||||
"--concurrent-fragments", "8",
|
||||
"--retries", "10",
|
||||
"--fragment-retries", "10",
|
||||
"--ffmpeg-location", ffmpegPath,
|
||||
"--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");
|
||||
@@ -36,7 +78,7 @@ function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
|
||||
args.push("-o", path.join(outputFolder, "%(title)s.%(ext)s"));
|
||||
args.push(url);
|
||||
|
||||
return args;
|
||||
return args.filter(Boolean);
|
||||
}
|
||||
|
||||
module.exports = { buildYtDlpArgs };
|
||||
|
||||
@@ -3,24 +3,31 @@ const fs = require("fs");
|
||||
const { app } = require("electron");
|
||||
const config = require("../../config");
|
||||
|
||||
const { logger } = require("../logger.js");
|
||||
|
||||
let userYtDlp;
|
||||
let ffmpegPath;
|
||||
let denoPath;
|
||||
|
||||
const sourceYtDlp = path.join(process.resourcesPath, "yt-dlp.exe");
|
||||
const sourceYtDlp = path.join(process.resourcesPath, "binaries","yt-dlp.exe");
|
||||
|
||||
if (config.localMode) {
|
||||
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");
|
||||
} else {
|
||||
userYtDlp = path.join(app.getPath("userData"), "yt-dlp.exe");
|
||||
ffmpegPath = path.join(process.resourcesPath, "ffmpeg.exe");
|
||||
denoPath = path.join(process.resourcesPath, "deno.exe");
|
||||
userYtDlp = path.join(app.getPath("userData"),"yt-dlp.exe");
|
||||
ffmpegPath = path.join(process.resourcesPath, "binaries","ffmpeg.exe");
|
||||
denoPath = path.join(process.resourcesPath, "binaries","deno.exe");
|
||||
|
||||
if (!fs.existsSync(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 };
|
||||
|
||||
14
server/helpers/rateLimit.js
Normal file
14
server/helpers/rateLimit.js
Normal 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
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
const express = require("express");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { logger, logSessionStart, logSessionEnd } = require("./logger");
|
||||
const { logger, logSessionEnd } = require("./logger");
|
||||
const config = require("../config");
|
||||
const { execFile } = require("child_process");
|
||||
const { userYtDlp } = require("./helpers/path");
|
||||
|
||||
const { rateLimite } = require("./helpers/rateLimit")
|
||||
|
||||
const app = express();
|
||||
|
||||
@@ -37,7 +37,7 @@ app.use(express.static(path.join(__dirname, "../public")));
|
||||
app.use("/download", require("./routes/download.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"));
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { autoUpdater } = require("electron-updater");
|
||||
const { app, Notification } = require("electron");
|
||||
const { app, Notification, shell} = require("electron");
|
||||
const { logger } = require("./logger");
|
||||
|
||||
function AutoUpdater() {
|
||||
@@ -17,7 +17,9 @@ function AutoUpdater() {
|
||||
new Notification({
|
||||
title: "Freedom Loader",
|
||||
body: `Update ${info.version} downloaded.`
|
||||
}).show();
|
||||
}).on("click", () =>
|
||||
shell.openExternal("https://github.com/MasterAcnolo/Freedom-Loader/releases/latest")
|
||||
).show();
|
||||
|
||||
setTimeout(() => {
|
||||
autoUpdater.quitAndInstall();
|
||||
|
||||
Reference in New Issue
Block a user