From 5983f2175907626a0a37aba7c140d34f2aa76696 Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 09:50:32 +0100 Subject: [PATCH 01/21] Fix: Big mistake when estimating video format (? -> &) --- server/controller/info.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/controller/info.controller.js b/server/controller/info.controller.js index 7002cbf..761470f 100644 --- a/server/controller/info.controller.js +++ b/server/controller/info.controller.js @@ -13,7 +13,7 @@ async function infoController(req, res){ logger.info(`/Info Request receive by the Info Controller. URL: ${url}`); - if (url.includes("?list")) { + if (url.includes("&list")) { logger.info("Estimated Data Type: Playlist") } else{ From 5a41090175ad39f73d2f4ef9cf1bdde0cf63c66b Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 10:26:39 +0100 Subject: [PATCH 02/21] Fix: Remove Unusued Code --- server/helpers/parseInfo.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/server/helpers/parseInfo.js b/server/helpers/parseInfo.js index c397493..6449061 100644 --- a/server/helpers/parseInfo.js +++ b/server/helpers/parseInfo.js @@ -2,12 +2,6 @@ function parseVideo(data) { return { type: "video", - // id: data.id, - // title: data.title, - // url: data.webpage_url, - // duration: data.duration, - // thumbnail: data.thumbnail, - // uploader: data.uploader ...data }; } From 6877c51c0e3495dce39ef86b1825dd6dbb5a04d1 Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 10:26:54 +0100 Subject: [PATCH 03/21] Fix: Add Small Comms for clarity --- server/controller/download.controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/controller/download.controller.js b/server/controller/download.controller.js index 597b290..bf4bd6b 100644 --- a/server/controller/download.controller.js +++ b/server/controller/download.controller.js @@ -18,6 +18,7 @@ async function downloadController(req, res) { if (!options.url || !isValidUrl(options.url)) return res.status(400).send("❌ Invalid URL !"); if (options.outputFolder && !isSafePath(options.outputFolder)) return res.status(400).send("❌ Save Path Not Allowed."); + // Get output folder when the download is finished, const filePath = await fetchDownload(options, listeners, speedListeners); notifyDownloadFinished(filePath); res.send("✅ Download Done !"); From c1e70dae80994a8c7b64c5e267fd2439326f894e Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 10:27:13 +0100 Subject: [PATCH 04/21] Feat: Add Better playlist layout --- public/script/fetchinfo.js | 77 ++++++-- public/styles/layout/videoinfo.css | 306 +++++++++++++++++++++++++---- 2 files changed, 326 insertions(+), 57 deletions(-) diff --git a/public/script/fetchinfo.js b/public/script/fetchinfo.js index af9c9b7..25c79a6 100644 --- a/public/script/fetchinfo.js +++ b/public/script/fetchinfo.js @@ -78,9 +78,27 @@ async function init() { if (data.type === "playlist") { infoDiv.classList.add("playlist-mode"); infoDiv.innerHTML = ` -

Playlist Detected: ${data.title}

-

Video Count: ${data.count}

-

Channel : ${data.channel || "Unknown"}

+
+
+
Playlist détectée
+

${data.title}

+
+ + + + + ${data.count} vidéos + + ${data.channel ? ` + + + + + ${data.channel} + ` : ''} +
+
+
`; @@ -88,39 +106,58 @@ async function init() { data.videos.forEach(v => { const durationStr = v.duration - ? `${Math.floor(v.duration / 60)}m ${(v.duration % 60).toString().padStart(2,"0")}s` : "Inconnue"; + ? `${Math.floor(v.duration / 60)}:${(v.duration % 60).toString().padStart(2,"0")}` : "--:--"; const videoUrl = v.id ? `https://www.youtube.com/watch?v=${v.id}` : v.url; listDiv.innerHTML += ` -
- Thumbnail -

${v.title}

-

Duration : ${durationStr}

-

URL - -

+
+
+ ${v.title} + ${durationStr} +
+
+

${v.title}

+ ${v.uploader ? `

${v.uploader}

` : ''} +
+ + + + + + Ouvrir + + +
+
`; }); // Gestion du bouton copier listDiv.addEventListener("click", (event) => { - if (event.target.classList.contains("copy-btn")) { - const btn = event.target; + if (event.target.classList.contains("copy-btn") || event.target.closest(".copy-btn")) { + const btn = event.target.closest(".copy-btn") || event.target; if (btn.disabled) return; btn.disabled = true; const url = btn.dataset.url; navigator.clipboard.writeText(url) .then(() => { - const original = btn.textContent; + const original = btn.innerHTML; btn.style.opacity = 0; btn.style.transform = "scale(0.7)"; setTimeout(() => { - btn.textContent = "✅"; + btn.innerHTML = ` + + `; btn.style.opacity = 1; btn.style.transform = "scale(1)"; @@ -129,7 +166,7 @@ async function init() { btn.style.transform = "scale(0.7)"; setTimeout(() => { - btn.textContent = original; + btn.innerHTML = original; btn.style.opacity = 1; btn.style.transform = "scale(1)"; btn.disabled = false; @@ -140,10 +177,12 @@ async function init() { }, 300); }) .catch(() => { - const original = btn.textContent; - btn.textContent = "❌"; + const original = btn.innerHTML; + btn.innerHTML = ` + + `; setTimeout(() => { - btn.textContent = original; + btn.innerHTML = original; btn.disabled = false; }, 1500); }); diff --git a/public/styles/layout/videoinfo.css b/public/styles/layout/videoinfo.css index 743dc00..79ecb98 100644 --- a/public/styles/layout/videoinfo.css +++ b/public/styles/layout/videoinfo.css @@ -69,80 +69,293 @@ /* --- Playlist Mode --- */ #videoInfo.playlist-mode { - max-width: 900px; + max-width: 95%; } -/* Style du bloc "Playlist détectée" */ -#videoInfo.playlist-mode > p { - text-align: center; - font-size: 1.1rem; +/* Playlist Header */ +.playlist-header { + display: flex; + flex-direction: column; + padding: 2rem; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.03)); + border-radius: 16px; + margin-bottom: 2rem; + border: 1px solid rgba(255, 255, 255, 0.12); + backdrop-filter: blur(10px); +} + +.playlist-badge { + display: inline-flex; + align-items: center; + width: fit-content; + padding: 0.4rem 0.9rem; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 6px; + font-size: 0.8rem; font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--video-info-text-color); + margin-bottom: 1rem; +} + +.playlist-info { + flex: 1; +} + +.playlist-title { + font-size: 1.9rem; + margin: 0 0 0.6rem 0; + color: var(--video-info-heading-color); + font-weight: 700; + line-height: 1.2; +} + +.playlist-meta { + display: flex; + gap: 1.5rem; + font-size: 0.95rem; + color: var(--video-info-text-color); + opacity: 0.85; + flex-wrap: wrap; +} + +.playlist-count, +.playlist-channel { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.4rem 0.8rem; + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.08); + font-weight: 500; +} + +.playlist-count svg, +.playlist-channel svg { + width: 16px; + height: 16px; + flex-shrink: 0; } /* Layout liste des vidéos */ #playlistVideos { margin-top: 1em; display: grid; - grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); - gap: 18px; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1.5rem; } -/* Un item de playlist */ -#playlistVideos > div { +/* Carte vidéo playlist */ +.playlist-video-card { background: var(--playlist-background-color); - border-radius: 10px; - padding: 10px; - box-shadow: 0 4px 12px rgba(0,0,0,0.15); - text-align: center; - transition: transform 0.2s ease; -} - -#playlistVideos > div p { + border-radius: 14px; + overflow: hidden; + box-shadow: 0 2px 12px rgba(0,0,0,0.12); + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; + flex-direction: column; + border: 1px solid rgba(255, 255, 255, 0.1); + position: relative; } -#videoInfo.playlist-mode { - max-width: 95%; +.playlist-video-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, var(--video-info-link-color), transparent); + opacity: 0; + transition: opacity 0.25s ease; } -#playlistVideos > div:hover { +.playlist-video-card:hover { + transform: translateY(-6px); + box-shadow: 0 12px 32px rgba(0,0,0,0.25); + border-color: rgba(255, 255, 255, 0.15); +} + +.playlist-video-card:hover::before { + opacity: 1; +} + +/* Thumbnail wrapper avec durée */ +.video-thumbnail-wrapper { + position: relative; + width: 100%; + aspect-ratio: 16 / 9; + overflow: hidden; + background: #000; +} + +.video-thumbnail { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + transition: transform 0.3s ease; +} + +.playlist-video-card:hover .video-thumbnail { transform: scale(1.05); } -#playlistVideos img { - width: 100%; +.video-duration { + position: absolute; + bottom: 8px; + right: 8px; + background: rgba(0, 0, 0, 0.9); + color: white; + padding: 4px 8px; + border-radius: 6px; + font-size: 0.85rem; + font-weight: 700; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', monospace; + letter-spacing: 0.5px; + backdrop-filter: blur(4px); +} + +/* Contenu vidéo */ +.video-content { + padding: 1.2rem; + display: flex; + flex-direction: column; + gap: 0.6rem; + flex: 1; + background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.02)); +} + +.video-title { + font-size: 1rem; + font-weight: 600; + margin: 0; + color: var(--video-info-heading-color); + line-height: 1.4; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + min-height: 2.8em; + transition: color 0.2s ease; +} + +.playlist-video-card:hover .video-title { + color: var(--video-info-link-color); +} + +.video-uploader { + font-size: 0.9rem; + color: var(--video-info-text-color); + opacity: 0.7; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Actions vidéo */ +.video-actions { + display: flex; + align-items: center; + gap: 0.6rem; + margin-top: auto; + padding-top: 0.8rem; + border-top: 1px solid rgba(255, 255, 255, 0.08); +} + +.video-link { + flex: 1; + text-decoration: none; + color: var(--video-info-link-color); + font-size: 0.9rem; + padding: 0.5rem 0.9rem; border-radius: 8px; - margin-bottom: 8px; + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.08); + transition: all 0.2s ease; + text-align: center; + font-weight: 500; + display: flex; + align-items: center; + justify-content: center; + gap: 0.4rem; +} + +.video-link svg { + width: 16px; + height: 16px; + flex-shrink: 0; +} + +.video-link:hover { + background: rgba(255, 255, 255, 0.12); + border-color: rgba(255, 255, 255, 0.15); + text-decoration: none; + transform: translateY(-1px); } .copy-btn { - all: unset; - background: transparent; - border: none; + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.08); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; - padding: 4px; - vertical-align: middle; - transition: transform 0.1s ease, color 0.1s ease, opacity 0.15s ease; - font-size: 1em; + padding: 0.5rem; + border-radius: 8px; + transition: all 0.2s ease; + width: 40px; + height: 40px; + flex-shrink: 0; } -#playlistVideos > div p a{ - text-decoration: none; - color: var(--video-info-link-color); +.copy-btn svg { + width: 18px; + height: 18px; + color: var(--video-info-text-color); } -#playlistVideos > div p a:hover{ - text-decoration: underline; +.copy-btn:hover { + background: rgba(255, 255, 255, 0.12); + border-color: rgba(255, 255, 255, 0.15); + transform: translateY(-1px); +} + +.copy-btn:active { + transform: scale(0.95); +} + +.copy-btn:disabled { + cursor: not-allowed; + opacity: 0.6; } /* Responsive */ +@media (max-width: 768px) { + #playlistVideos { + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 1rem; + } + + .playlist-header { + padding: 1.5rem; + } + + .playlist-title { + font-size: 1.5rem; + } + + .playlist-meta { + gap: 0.8rem; + } +} + @media (max-width: 480px) { #videoInfo { padding: 1em; @@ -152,4 +365,21 @@ #videoInfo h3 { font-size: 1.4rem; } + + #playlistVideos { + grid-template-columns: 1fr; + } + + .playlist-header { + padding: 1.2rem; + } + + .playlist-title { + font-size: 1.3rem; + } + + .playlist-badge { + font-size: 0.75rem; + padding: 0.4rem 0.8rem; + } } From a42b020b9649fb702cc9d13d81e7f000a2ff817b Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 15:19:17 +0100 Subject: [PATCH 05/21] Fix: Update URL check to correctly identify playlist links --- server/controller/info.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/controller/info.controller.js b/server/controller/info.controller.js index 761470f..81837a9 100644 --- a/server/controller/info.controller.js +++ b/server/controller/info.controller.js @@ -13,7 +13,7 @@ async function infoController(req, res){ logger.info(`/Info Request receive by the Info Controller. URL: ${url}`); - if (url.includes("&list")) { + if (url.includes("&list") || url.includes("?list")) { logger.info("Estimated Data Type: Playlist") } else{ From 7cd3b4be067d9b541a143a76ff29bda927e28350 Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 15:19:23 +0100 Subject: [PATCH 06/21] Fix: Update playlist badge text to English --- public/script/fetchinfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/script/fetchinfo.js b/public/script/fetchinfo.js index 25c79a6..d4e1a2c 100644 --- a/public/script/fetchinfo.js +++ b/public/script/fetchinfo.js @@ -80,7 +80,7 @@ async function init() { infoDiv.innerHTML = `
-
Playlist détectée
+
Detected Playlist

${data.title}

From 2234f9ed81f701cf85584a53dfa24067744f82ac Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 15:22:38 +0100 Subject: [PATCH 07/21] Fix: Update version numbers to 1.4.4 in package.json and ReadME --- README.md | 2 +- package-lock.json | 10 +++++----- package.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b55dea8..28117e4 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ### **A clean, open-source multimedia downloader for Windows** -[![Release](https://img.shields.io/badge/Release-1.4.3-blue?style=for-the-badge)](https://github.com/MasterAcnolo/Freedom-Loader/releases) +[![Release](https://img.shields.io/badge/Release-1.4.4-blue?style=for-the-badge)](https://github.com/MasterAcnolo/Freedom-Loader/releases) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-red.svg?style=for-the-badge)](https://www.gnu.org/licenses/gpl-3.0) [![Website](https://img.shields.io/badge/Website-Visit-404040?style=for-the-badge)](https://masteracnolo.github.io/FreedomLoader/) diff --git a/package-lock.json b/package-lock.json index d089214..a4e13dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "freedom-loader", - "version": "1.4.3", + "version": "1.4.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "freedom-loader", - "version": "1.4.3", + "version": "1.4.4", "dependencies": { "chalk": "^4.1.2", "debug": "^4.4.1", @@ -3990,9 +3990,9 @@ } }, "node_modules/minimatch": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz", - "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz", + "integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { diff --git a/package.json b/package.json index 5f7e2a9..e1d54f1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "freedom-loader", "productName": "Freedom Loader", - "version": "1.4.3", + "version": "1.4.4", "author": "MasterAcnolo", "description": "Freedom Loader", "main": "main.js", From 87945fba80f0dabe5f91c05f36d36480c308d5dd Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 18:05:07 +0100 Subject: [PATCH 08/21] Fix: Missing semi colon, Better Folder Creation, And remove the fucking bad code who was repeat too much (wtf i did) --- server/logger.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/server/logger.js b/server/logger.js index 632d553..9e4274c 100644 --- a/server/logger.js +++ b/server/logger.js @@ -3,13 +3,17 @@ const DailyRotateFile = require("winston-daily-rotate-file"); const fs = require("fs"); const path = require("path"); const os = require("os"); -const config = require("../config") +const config = require("../config"); // Dossier de logs Windows const logDir = path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs"); // Création du dossier "logs" si nécessaire -fs.mkdirSync(logDir, { recursive: true }); +try { + fs.mkdirSync(logDir, { recursive: true }); +} catch (error) { + console.error(`Failed to create log directory: ${error.message}`); +} const logFormat = format.combine( format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), @@ -36,21 +40,13 @@ const logger = createLogger({ ], }); -function getSessionStartLine() { - return `--- Starting session: ${new Date().toISOString()} ---`; -} - -function getSessionEndLine() { - return `--- Ending session: ${new Date().toISOString()} ---`; -} - function logSessionStart() { - logger.info(getSessionStartLine()); + logger.info(`--- Starting session: ${new Date().toISOString()} ---`); logger.info(`Application Version: ${config.version}`) } function logSessionEnd() { - logger.info(getSessionEndLine()); + logger.info(`--- Ending session: ${new Date().toISOString()} ---`); } module.exports = { From adb36e3fa23b681967d9fd5ed0bbf08af7943f41 Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:59:22 +0100 Subject: [PATCH 09/21] Fix: We are not on apple system. Don't care about Darwin --- main.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/main.js b/main.js index c44438b..972e3a7 100644 --- a/main.js +++ b/main.js @@ -157,6 +157,8 @@ ipcMain.on("set-progress", (event, percent) => { ipcMain.on("window-minimize", () => { if (mainWindow) mainWindow.minimize(); }); + +// Toggle Maximize -> UnMaximize ipcMain.on("window-maximize", () => { if (mainWindow) { if (mainWindow.isMaximized()) { @@ -166,6 +168,7 @@ ipcMain.on("window-maximize", () => { } } }); + ipcMain.on("window-close", () => { if (mainWindow) mainWindow.close(); }); @@ -248,7 +251,7 @@ app.whenReady().then(async () => { }); - configFeatures.discordRPC ? startRPC() : ""; + configFeatures.discordRPC ? startRPC() : ""; // Discord RPC await createMainWindow(); configFeatures.autoUpdate ? AutoUpdater(mainWindow) : ""; // Auto Update @@ -261,7 +264,6 @@ app.whenReady().then(async () => { app.on("window-all-closed", () => { logger.info("All Window Closed, shuting down app"); - if (process.platform !== "darwin") app.quit(); }); app.on("before-quit", () => { From 5d776594fcd44a8734245e247962ee90cb21e15f Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:09:09 +0100 Subject: [PATCH 10/21] Fix: App Not Stopping correctly + Better Logs for end :) --- main.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/main.js b/main.js index 972e3a7..47de995 100644 --- a/main.js +++ b/main.js @@ -263,10 +263,12 @@ app.whenReady().then(async () => { }); app.on("window-all-closed", () => { - logger.info("All Window Closed, shuting down app"); + logger.info("Shuting Down App..."); + app.quit(); }); app.on("before-quit", () => { - logSessionEnd() stopRPC() + logger.info("All Services Stopped. Have a nice day!") + logSessionEnd() }); From 334e83ae1f4548680fd03898dbb5913167d01343 Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:12:01 +0100 Subject: [PATCH 11/21] Fix: Small coms in AppVersion.js --- public/script/appVersion.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/script/appVersion.js b/public/script/appVersion.js index ad9aaa0..6ad1775 100644 --- a/public/script/appVersion.js +++ b/public/script/appVersion.js @@ -1,5 +1,7 @@ async function versionLabel(){ const appVersion = await window.electronAPI.getVersion(); + + // Write in front the app version for debugging (bottom right) document.getElementById("version-badge").textContent = `v${appVersion}`; }; From 760550e5214c2738298b6b314734ede3fbfaf214 Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:28:46 +0100 Subject: [PATCH 12/21] Fix: Centralize resource paths and icons --- main.js | 36 ++++++++++++++++++++++-------------- server/helpers/notify.js | 11 ++++------- server/helpers/path.js | 29 +++++++++++++++++++++++++---- 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/main.js b/main.js index 47de995..609e4e2 100644 --- a/main.js +++ b/main.js @@ -30,11 +30,14 @@ const gotLock = app.requestSingleInstanceLock(); // Native dependencies check (yt-dlp.exe, ffmpeg.exe, ffprobe.exe, Deno) function checkNativeDependencies() { + // Import des chemins centralisés après l'initialisation de l'app + const { binaryPaths } = require("./server/helpers/path"); + 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") }, + { name: "yt-dlp.exe", path: binaryPaths.ytDlp }, + { name: "ffmpeg.exe", path: binaryPaths.ffmpeg }, + { name: "ffprobe.exe", path: binaryPaths.ffprobe }, + { name: "deno.exe", path: binaryPaths.deno }, ]; const missing = deps.filter(dep => !fs.existsSync(dep.path)); let errorMsg = ""; @@ -114,15 +117,20 @@ function validateDownloadPath(userPath) { 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; + try { + // 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 !"); + if (!resolved.startsWith(normalizedHome)) { + throw new Error("Chemin non autorisé : uniquement les sous-dossiers du dossier utilisateur sont permis !"); + } + + return resolved; + } catch (err) { + logger.error(`Invalid download path: ${userPath} - ${err.message}`); + throw new Error(`Chemin invalide ou inaccessible : ${err.message}`); } - - return resolved; } @@ -267,8 +275,8 @@ app.on("window-all-closed", () => { app.quit(); }); -app.on("before-quit", () => { - stopRPC() +app.on("before-quit", async () => { + await stopRPC(); logger.info("All Services Stopped. Have a nice day!") - logSessionEnd() + logSessionEnd(); }); diff --git a/server/helpers/notify.js b/server/helpers/notify.js index f9a24b7..026cc81 100644 --- a/server/helpers/notify.js +++ b/server/helpers/notify.js @@ -1,12 +1,11 @@ const { Notification, shell } = require("electron"); -const path = require("path"); +const { iconPaths } = require("./path"); function notifyDownloadFinished(folder) { - const iconPath = path.join(process.resourcesPath, "confirm-icon.png"); const notif = new Notification({ title: "Freedom Loader", body: "Your download is complete, click here to open it.", - icon: iconPath, + icon: iconPaths.confirm, }); notif.on("click", () => shell.openPath(folder)); @@ -14,11 +13,10 @@ function notifyDownloadFinished(folder) { } function notifyCookiesBrowserError(){ - const iconPath = path.join(process.resourcesPath, "error.png"); const notif = new Notification({ title: "Cookies Error", body: "Unable to retrieve cookies. Please log in to your browser and click here to view the tutorial.", - icon: iconPath, + icon: iconPaths.error, }); notif.on("click", () => shell.openExternal("https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1")); @@ -26,11 +24,10 @@ function notifyCookiesBrowserError(){ } function notifyFirefoxBrowserMissing(){ - const iconPath = path.join(process.ressourcesPath, "error.png"); const notif = new Notification({ title: "Firefox Missing", body: "Firefox was not found on your system. Click here to follow the installation guide", - icon: iconPath, + icon: iconPaths.error, }); notif.on("click", () => shell.openExternal("https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1")) diff --git a/server/helpers/path.js b/server/helpers/path.js index 8eac1a5..a22bb0f 100644 --- a/server/helpers/path.js +++ b/server/helpers/path.js @@ -5,11 +5,17 @@ const config = require("../../config"); const { logger } = require("../logger.js"); +// Centralisation de tous les chemins de ressources +const resourcesPath = config.localMode + ? path.join(__dirname, "../../ressources") + : process.resourcesPath; + +// Chemins des binaires let userYtDlp; let ffmpegPath; let denoPath; -const sourceYtDlp = path.join(process.resourcesPath, "binaries","yt-dlp.exe"); +const sourceYtDlp = path.join(resourcesPath, "binaries","yt-dlp.exe"); if (config.localMode) { userYtDlp = path.join(__dirname, "../../ressources/yt-dlp.exe"); @@ -17,8 +23,8 @@ if (config.localMode) { denoPath = path.join(__dirname, "../../ressources/deno.exe"); } else { 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"); + ffmpegPath = path.join(resourcesPath, "binaries","ffmpeg.exe"); + denoPath = path.join(resourcesPath, "binaries","deno.exe"); if (!fs.existsSync(userYtDlp)) { fs.copyFileSync(sourceYtDlp, userYtDlp); @@ -26,8 +32,23 @@ if (config.localMode) { } +// Chemins des icônes de notification +const iconPaths = { + confirm: path.join(resourcesPath, "confirm-icon.png"), + error: path.join(resourcesPath, "error.png"), + app: path.join(resourcesPath, "app-icon.ico") +}; + +// Chemins des binaires pour vérification +const binaryPaths = { + ytDlp: path.join(resourcesPath, "binaries", "yt-dlp.exe"), + ffmpeg: path.join(resourcesPath, "binaries", "ffmpeg.exe"), + ffprobe: path.join(resourcesPath, "binaries", "ffprobe.exe"), + deno: path.join(resourcesPath, "binaries", "deno.exe") +}; + if (!userYtDlp){ logger.error("Missing YT-DLP")} if (!ffmpegPath){ logger.error("Missing FFMPEG")} if (!denoPath){ logger.error("Missing DENO")} -module.exports = { userYtDlp, ffmpegPath, denoPath }; +module.exports = { userYtDlp, ffmpegPath, denoPath, iconPaths, binaryPaths, resourcesPath }; From fdb22f2342592329bbe121b16ee84a756dddee2d Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:29:51 +0100 Subject: [PATCH 13/21] Fix: Improve browser detection logging and error handling --- server/helpers/getBrowser.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/server/helpers/getBrowser.js b/server/helpers/getBrowser.js index f51c8f2..a362a55 100644 --- a/server/helpers/getBrowser.js +++ b/server/helpers/getBrowser.js @@ -2,6 +2,7 @@ const fs = require("fs"); const os = require("os"); const path = require("path"); const notify = require("./notify") +const { logger } = require("../logger"); function getUserBrowser() { const userProfile = os.homedir(); @@ -18,15 +19,21 @@ function getUserBrowser() { // { name: "whale", path: path.join(userProfile, "AppData", "Local", "Naver", "Naver Whale", "User Data", "Default") } ]; + // Chercher un navigateur disponible for (const browser of browsers) { if (fs.existsSync(browser.path)) { + logger.info(`Browser found: ${browser.name}`); return browser.name; - } else{ - notify.notifyFirefoxBrowserMissing(); } } - notify.notifyCookiesBrowserError() - return ; + + // Aucun navigateur trouvé - notifier l'utilisateur + logger.warn("No supported browser found on the system"); + notify.notifyFirefoxBrowserMissing(); + + // Fallback: retourner "firefox" pour laisser yt-dlp gérer l'erreur + // plutôt que de crasher l'application + return "firefox"; } module.exports = getUserBrowser \ No newline at end of file From 5750444655aa44895dbd38298c9119c820c79cda Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:35:57 +0100 Subject: [PATCH 14/21] Fix: Update full changelog link format in release template --- .github/PULL_REQUEST_TEMPLATE/release.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE/release.md b/.github/PULL_REQUEST_TEMPLATE/release.md index c0a1b81..225f7ee 100644 --- a/.github/PULL_REQUEST_TEMPLATE/release.md +++ b/.github/PULL_REQUEST_TEMPLATE/release.md @@ -7,4 +7,4 @@ ## Found a bug or issue? - Please report it in the GitHub Issues section -**Full Changelog**: https://github.com/MasterAcnolo/Freedom-Loader/compare/1.4.0...1.4.1 \ No newline at end of file +**Full Changelog**: https://github.com/MasterAcnolo/Freedom-Loader/compare/1.W.X...1.Y.Z \ No newline at end of file From 9a48ac05e3f2ea41258b5242477abfa67f4d99c5 Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:42:12 +0100 Subject: [PATCH 15/21] Fix: Uncomplete comments and remove repeated code --- server/server.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/server/server.js b/server/server.js index e8f1f75..2f229ed 100644 --- a/server/server.js +++ b/server/server.js @@ -1,5 +1,4 @@ const express = require("express"); -const fs = require("fs"); const path = require("path"); const { logger, logSessionEnd } = require("./logger"); const config = require("../config"); @@ -11,19 +10,7 @@ const app = express(); app.use(express.json()); -// Dossier de téléchargement -const outputFolder = path.join(process.env.USERPROFILE, "Downloads", "Freedom Loader"); - -// Création du dossier si nécessaire -try { - fs.mkdirSync(outputFolder, { recursive: true }); - logger.info(`Freedom Loader folder ready in ${outputFolder}`); -} catch (err) { - logger.error("Unable to create folder:", err); - process.exit(1); -} - -// Mise à jour yt-dlp au +// Mise à jour yt-dlp au démarrage execFile(userYtDlp, ["-U"], (err, stdout, stderr) => { if (err) logger.warn("yt-dlp update error:", err); else logger.info(`Update yt-dlp : ${stdout}`); From cc5bde5b48c4efdbf04111d8e9f50d57796290bd Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:49:43 +0100 Subject: [PATCH 16/21] Fix: Correct comment for rate limit window duration in rateLimit.js --- server/helpers/rateLimit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/helpers/rateLimit.js b/server/helpers/rateLimit.js index d65336f..9100b0b 100644 --- a/server/helpers/rateLimit.js +++ b/server/helpers/rateLimit.js @@ -3,7 +3,7 @@ const RateLimit = require("express-rate-limit"); /* Rate Limite */ const rateLimite = RateLimit({ - windowMs: 15 * 60, // 15 minutes + windowMs: 15 * 60, // 15 ms 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" From 19b3f8b94751b1b9b6118238655cff009879e19f Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:49:52 +0100 Subject: [PATCH 17/21] Fix: Refactor conditional statements for starting Discord RPC and Auto Update --- main.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main.js b/main.js index 609e4e2..b218ea1 100644 --- a/main.js +++ b/main.js @@ -259,10 +259,11 @@ app.whenReady().then(async () => { }); - configFeatures.discordRPC ? startRPC() : ""; // Discord RPC + if (configFeatures.discordRPC) startRPC(); // Discord RPC await createMainWindow(); - configFeatures.autoUpdate ? AutoUpdater(mainWindow) : ""; // Auto Update + + if (configFeatures.autoUpdate) AutoUpdater(mainWindow); // Auto Update } catch (err) { logger.error("Window or Server error :", err); From 45e0840fea56685f8e88d82ef9c5f891b18734a9 Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 23:26:01 +0100 Subject: [PATCH 18/21] Fix: Correct windowMs value in rate limit configuration to 15 minutes --- server/helpers/rateLimit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/helpers/rateLimit.js b/server/helpers/rateLimit.js index 9100b0b..8d85534 100644 --- a/server/helpers/rateLimit.js +++ b/server/helpers/rateLimit.js @@ -3,7 +3,7 @@ const RateLimit = require("express-rate-limit"); /* Rate Limite */ const rateLimite = RateLimit({ - windowMs: 15 * 60, // 15 ms + windowMs: 15 * 60 * 1000, // 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" From 32c9a66a35837d6654669bdc29135dbb782913de Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Tue, 17 Feb 2026 23:29:31 +0100 Subject: [PATCH 19/21] Fix: Centralize default download path and enhance path validation logic --- main.js | 48 ++++++++++++++++-------- public/script/custompath.js | 11 ++---- server/controller/download.controller.js | 11 ++++-- server/helpers/path.js | 14 ++++--- server/helpers/validation.js | 33 ++++++++++++++-- server/services/download.services.js | 17 ++++++--- 6 files changed, 94 insertions(+), 40 deletions(-) diff --git a/main.js b/main.js index b218ea1..5f815a8 100644 --- a/main.js +++ b/main.js @@ -18,7 +18,8 @@ const basePath = config.localMode const configFolderPath = path.join(basePath, "config" ,"config.json"); -const defaultDownloadPath = path.join(os.homedir(), "Downloads", "Freedom Loader"); +// Default download path (centralized, lazy loaded) +let defaultDownloadPath; app.setAppUserModelId("com.masteracnolo.freedomloader"); // pour notifications Windows app.disableHardwareAcceleration(); @@ -30,7 +31,7 @@ const gotLock = app.requestSingleInstanceLock(); // Native dependencies check (yt-dlp.exe, ffmpeg.exe, ffprobe.exe, Deno) function checkNativeDependencies() { - // Import des chemins centralisés après l'initialisation de l'app + // Import centralized paths after app initialization const { binaryPaths } = require("./server/helpers/path"); const deps = [ @@ -113,23 +114,29 @@ async function createMainWindow() { } function validateDownloadPath(userPath) { - const userHome = os.homedir(); // C:\Users\ + const { isSafePath } = require("./server/helpers/validation"); + + // Lazy load default path + if (!defaultDownloadPath) { + const { defaultDownloadFolder } = require("./server/helpers/path"); + defaultDownloadPath = defaultDownloadFolder; + } - if (!userPath) return path.join(userHome, "Downloads", "Freedom Loader"); + if (!userPath) return defaultDownloadPath; try { - // Résolution canonique et suivi des symlinks + // Canonical resolution and symlink following 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 !"); + + // Use the same validation as backend (allows all drives except system folders) + if (!isSafePath(resolved)) { + throw new Error("Path not allowed: system folders are blocked!"); } return resolved; } catch (err) { logger.error(`Invalid download path: ${userPath} - ${err.message}`); - throw new Error(`Chemin invalide ou inaccessible : ${err.message}`); + throw new Error(`Invalid or inaccessible path: ${err.message}`); } } @@ -138,14 +145,19 @@ function validateDownloadPath(userPath) { ipcMain.handle("select-download-folder", async () => { try { const result = await dialog.showOpenDialog({ properties: ["openDirectory"] }); - if (!result.canceled && result.filePaths.length > 0) { - const validatedPath = validateDownloadPath(result.filePaths[0]); - logger.info(`Folder Checked and Valid : ${validatedPath}`); + if (result.canceled) { + logger.info("Folder selection cancelled by user"); + return null; + } + if (result.filePaths.length > 0) { + const selectedPath = result.filePaths[0]; + const validatedPath = validateDownloadPath(selectedPath); + logger.info(`Folder selected and validated: ${validatedPath}`); return validatedPath; } return null; } catch (err) { - logger.error(`An Error Occured when validating folder : ${err.message}`); + logger.warn(`Unsafe or invalid folder rejected: ${err.message}`); return null; } }); @@ -155,7 +167,13 @@ ipcMain.handle("validate-download-path", (event, userPath) => { }); -ipcMain.handle("get-default-download-path", () => defaultDownloadPath); +ipcMain.handle("get-default-download-path", () => { + if (!defaultDownloadPath) { + const { defaultDownloadFolder } = require("./server/helpers/path"); + defaultDownloadPath = defaultDownloadFolder; + } + return defaultDownloadPath; +}); ipcMain.on("set-progress", (event, percent) => { if (mainWindow) mainWindow.setProgressBar(percent / 100); // Electron attend 0 → 1 diff --git a/public/script/custompath.js b/public/script/custompath.js index ccf01f0..992f896 100644 --- a/public/script/custompath.js +++ b/public/script/custompath.js @@ -45,18 +45,15 @@ window.addEventListener("DOMContentLoaded", async () => { .getElementById("changePath") .addEventListener("click", async () => { try { - const selectedPath = + // selectDownloadFolder already returns a validated path + const validatedPath = await window.electronAPI.selectDownloadFolder(); - if (!selectedPath) return; // annulé - - // Validation back obligatoire - const validatedPath = - await window.electronAPI.getValidatedDownloadPath(selectedPath); + if (!validatedPath) return; // cancelled await applyPathFromBack(validatedPath); } catch (err) { - alert("Dossier non autorisé."); + alert("Folder not allowed."); } }); }); diff --git a/server/controller/download.controller.js b/server/controller/download.controller.js index bf4bd6b..f875859 100644 --- a/server/controller/download.controller.js +++ b/server/controller/download.controller.js @@ -16,11 +16,14 @@ async function downloadController(req, res) { }; if (!options.url || !isValidUrl(options.url)) return res.status(400).send("❌ Invalid URL !"); - if (options.outputFolder && !isSafePath(options.outputFolder)) return res.status(400).send("❌ Save Path Not Allowed."); + if (options.outputFolder && !isSafePath(options.outputFolder)) { + logger.warn(`Unsafe download path rejected: ${options.outputFolder}`); + return res.status(400).send("❌ Save Path Not Allowed."); + } - // Get output folder when the download is finished, - const filePath = await fetchDownload(options, listeners, speedListeners); - notifyDownloadFinished(filePath); + // Get output folder when the download is finished + const outputFolder = await fetchDownload(options, listeners, speedListeners); + notifyDownloadFinished(outputFolder); res.send("✅ Download Done !"); } catch (err) { diff --git a/server/helpers/path.js b/server/helpers/path.js index a22bb0f..2a7d823 100644 --- a/server/helpers/path.js +++ b/server/helpers/path.js @@ -1,16 +1,20 @@ const path = require("path"); const fs = require("fs"); +const os = require("os"); const { app } = require("electron"); const config = require("../../config"); const { logger } = require("../logger.js"); -// Centralisation de tous les chemins de ressources +// Centralized resource paths const resourcesPath = config.localMode ? path.join(__dirname, "../../ressources") : process.resourcesPath; -// Chemins des binaires +// Default download folder (centralized) +const defaultDownloadFolder = path.join(os.homedir(), "Downloads", "Freedom Loader"); + +// Binary paths let userYtDlp; let ffmpegPath; let denoPath; @@ -32,14 +36,14 @@ if (config.localMode) { } -// Chemins des icônes de notification +// Notification icon paths const iconPaths = { confirm: path.join(resourcesPath, "confirm-icon.png"), error: path.join(resourcesPath, "error.png"), app: path.join(resourcesPath, "app-icon.ico") }; -// Chemins des binaires pour vérification +// Binary paths for verification const binaryPaths = { ytDlp: path.join(resourcesPath, "binaries", "yt-dlp.exe"), ffmpeg: path.join(resourcesPath, "binaries", "ffmpeg.exe"), @@ -51,4 +55,4 @@ if (!userYtDlp){ logger.error("Missing YT-DLP")} if (!ffmpegPath){ logger.error("Missing FFMPEG")} if (!denoPath){ logger.error("Missing DENO")} -module.exports = { userYtDlp, ffmpegPath, denoPath, iconPaths, binaryPaths, resourcesPath }; +module.exports = { userYtDlp, ffmpegPath, denoPath, iconPaths, binaryPaths, resourcesPath, defaultDownloadFolder }; diff --git a/server/helpers/validation.js b/server/helpers/validation.js index b6ede30..37f1a05 100644 --- a/server/helpers/validation.js +++ b/server/helpers/validation.js @@ -12,10 +12,35 @@ function isValidUrl(url) { } function isSafePath(folder) { - if (!folder || folder.length < 3) return false; - const unsafe = ["System32", "/etc", "\\Windows"]; - const resolved = path.resolve(folder); - return !unsafe.some(u => resolved.includes(u)); + if (!folder || typeof folder !== "string") return false; + + try { + // Normalize path and resolve symlinks + const resolved = path.resolve(folder).toLowerCase().replace(/\//g, "\\"); + + // Block Windows system directories (on any drive) + const unsafePaths = [ + "\\windows\\", + "\\system32\\", + "\\program files\\", + "\\program files (x86)\\", + "\\programdata\\", + "\\$recycle.bin\\", + "\\system volume information\\" + ]; + + // Check if path contains any unsafe directory + if (unsafePaths.some(unsafe => resolved.includes(unsafe))) { + return false; + } + + // Allow all drives (C:, D:, E:, etc.) but block system folders + return true; + + } catch (err) { + // In case of path resolution error + return false; + } } module.exports = { isValidUrl, isSafePath }; diff --git a/server/services/download.services.js b/server/services/download.services.js index 789fbd7..8957a71 100644 --- a/server/services/download.services.js +++ b/server/services/download.services.js @@ -1,6 +1,5 @@ const { execFile } = require("child_process"); -const { userYtDlp } = require("../helpers/path"); -const path = require("path"); +const { userYtDlp, defaultDownloadFolder } = require("../helpers/path"); const fs = require("fs"); const { logger } = require("../logger"); const { buildYtDlpArgs } = require("../helpers/buildArgs"); @@ -9,8 +8,16 @@ const notify = require("../helpers/notify") function fetchDownload(options, listeners, speedListeners) { return new Promise((resolve, reject) => { - const outputFolder = options.outputFolder || path.join(process.env.USERPROFILE, "Downloads", "Freedom Loader"); - fs.mkdirSync(outputFolder, { recursive: true }); + const outputFolder = options.outputFolder || defaultDownloadFolder; + + // Create download folder if it doesn't exist + try { + fs.mkdirSync(outputFolder, { recursive: true }); + logger.info(`Output folder ready: ${outputFolder}`); + } catch (err) { + logger.error(`Failed to create output folder: ${err.message}`); + return reject(new Error(`Unable to create download folder: ${err.message}`)); + } const args = buildYtDlpArgs({ ...options, outputFolder }); logger.info(`[yt-dlp args] ${args.join(" ")}`); @@ -31,7 +38,7 @@ function fetchDownload(options, listeners, speedListeners) { if (!line.trim()) return; logger.info(`[yt-dlp] ${line}`); - /* Barre de Chargement*/ + /* Progress Bar */ if (line.startsWith("[download] Destination:")) listeners.forEach(fn => fn("reset")); const match = line.match(/\[download\]\s+(\d+\.\d+)%/); if (match) listeners.forEach(fn => fn(parseFloat(match[1]))); From 74fcdbb738add4ab9c8eaf32574568c0f6dcb54f Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Wed, 18 Feb 2026 14:43:40 +0100 Subject: [PATCH 20/21] Fix: Undo Path Validation for coming back on a classic one --- main.js | 24 ++++++++++---------- public/script/custompath.js | 2 +- server/helpers/validation.js | 33 ++++------------------------ server/services/download.services.js | 20 ++++++++++++----- 4 files changed, 32 insertions(+), 47 deletions(-) diff --git a/main.js b/main.js index 5f815a8..ed75eb1 100644 --- a/main.js +++ b/main.js @@ -143,23 +143,23 @@ function validateDownloadPath(userPath) { // IPC ipcMain.handle("select-download-folder", async () => { - try { - const result = await dialog.showOpenDialog({ properties: ["openDirectory"] }); - if (result.canceled) { - logger.info("Folder selection cancelled by user"); - return null; - } - if (result.filePaths.length > 0) { - const selectedPath = result.filePaths[0]; + const result = await dialog.showOpenDialog({ properties: ["openDirectory"] }); + if (result.canceled) { + logger.info("Folder selection cancelled by user"); + return null; + } + if (result.filePaths.length > 0) { + const selectedPath = result.filePaths[0]; + try { const validatedPath = validateDownloadPath(selectedPath); logger.info(`Folder selected and validated: ${validatedPath}`); return validatedPath; + } catch (err) { + logger.warn(`Unsafe or invalid folder rejected: ${err.message}`); + throw err; // Propagate error to UI } - return null; - } catch (err) { - logger.warn(`Unsafe or invalid folder rejected: ${err.message}`); - return null; } + return null; }); ipcMain.handle("validate-download-path", (event, userPath) => { diff --git a/public/script/custompath.js b/public/script/custompath.js index 992f896..e530c1e 100644 --- a/public/script/custompath.js +++ b/public/script/custompath.js @@ -53,7 +53,7 @@ window.addEventListener("DOMContentLoaded", async () => { await applyPathFromBack(validatedPath); } catch (err) { - alert("Folder not allowed."); + alert("This folder is not allowed. Only specific folders (Users, Downloads, Documents) are authorized for downloads."); } }); }); diff --git a/server/helpers/validation.js b/server/helpers/validation.js index 37f1a05..b7e3da1 100644 --- a/server/helpers/validation.js +++ b/server/helpers/validation.js @@ -12,35 +12,10 @@ function isValidUrl(url) { } function isSafePath(folder) { - if (!folder || typeof folder !== "string") return false; - - try { - // Normalize path and resolve symlinks - const resolved = path.resolve(folder).toLowerCase().replace(/\//g, "\\"); - - // Block Windows system directories (on any drive) - const unsafePaths = [ - "\\windows\\", - "\\system32\\", - "\\program files\\", - "\\program files (x86)\\", - "\\programdata\\", - "\\$recycle.bin\\", - "\\system volume information\\" - ]; - - // Check if path contains any unsafe directory - if (unsafePaths.some(unsafe => resolved.includes(unsafe))) { - return false; - } - - // Allow all drives (C:, D:, E:, etc.) but block system folders - return true; - - } catch (err) { - // In case of path resolution error - return false; - } + if (!folder || folder.length < 3) return false; + const unsafe = ["System32", "\\Windows"]; + const resolved = path.resolve(folder); + return !unsafe.some(u => resolved.includes(u)); } module.exports = { isValidUrl, isSafePath }; diff --git a/server/services/download.services.js b/server/services/download.services.js index 8957a71..d869c4a 100644 --- a/server/services/download.services.js +++ b/server/services/download.services.js @@ -3,23 +3,33 @@ const { userYtDlp, defaultDownloadFolder } = require("../helpers/path"); const fs = require("fs"); const { logger } = require("../logger"); const { buildYtDlpArgs } = require("../helpers/buildArgs"); -const notify = require("../helpers/notify") +const notify = require("../helpers/notify"); +const path = require("path"); +const { isSafePath } = require("../helpers/validation"); function fetchDownload(options, listeners, speedListeners) { return new Promise((resolve, reject) => { const outputFolder = options.outputFolder || defaultDownloadFolder; + // Normalize path and validate it's safe (within Users folder) + let safeOutputFolder = path.resolve(outputFolder); + + if (!isSafePath(safeOutputFolder)) { + logger.warn(`Path not allowed, using default instead: ${safeOutputFolder}`); + safeOutputFolder = path.resolve(defaultDownloadFolder); + } + // Create download folder if it doesn't exist try { - fs.mkdirSync(outputFolder, { recursive: true }); - logger.info(`Output folder ready: ${outputFolder}`); + fs.mkdirSync(safeOutputFolder, { recursive: true }); + logger.info(`Output folder ready: ${safeOutputFolder}`); } catch (err) { logger.error(`Failed to create output folder: ${err.message}`); return reject(new Error(`Unable to create download folder: ${err.message}`)); } - const args = buildYtDlpArgs({ ...options, outputFolder }); + const args = buildYtDlpArgs({ ...options, outputFolder: safeOutputFolder }); logger.info(`[yt-dlp args] ${args.join(" ")}`); const child = execFile(userYtDlp, args); @@ -28,7 +38,7 @@ function fetchDownload(options, listeners, speedListeners) { child.on("close", code => { listeners.forEach(fn => fn("done")); - if (code === 0) resolve(outputFolder); + if (code === 0) resolve(safeOutputFolder); else reject(new Error(`YT-DLP failed with code : ${code}`)); }); From 9fb929509331c0364734ff5593d9897fb5b20c2f Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Wed, 18 Feb 2026 14:51:57 +0100 Subject: [PATCH 21/21] Fix: Try to re run workflow --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 28117e4..5015289 100644 --- a/README.md +++ b/README.md @@ -419,4 +419,4 @@ You are free to use, modify, and redistribute this software under the terms of t [Website](https://masteracnolo.github.io/FreedomLoader/) • [Download](https://github.com/MasterAcnolo/Freedom-Loader/releases) • [Documentation](https://masteracnolo.github.io/FreedomLoader/pages/wiki.html) • [Report Bug](https://github.com/MasterAcnolo/Freedom-Loader/issues) -
+
\ No newline at end of file