Feat: Implement Config File. Read by Front and Back. It aims to give possibility to disable some features quickly. Currenty: DiscordRPC, AutoCheckInfo, AutoUpdate, Custom TopBar, AddMetadata, Add Thumbail were put and implement. Other need to be implement. That's the begin of Settings Panel

This commit is contained in:
MasterAcnolo
2026-01-14 14:03:32 +01:00
parent 42ae0e7204
commit 31d8b20a76
8 changed files with 193 additions and 152 deletions

View File

@@ -1,5 +1,20 @@
const packageJson = require("./package.json"); const packageJson = require("./package.json");
const { app } = require("electron"); const { app } = require("electron");
const fs = require("fs");
const path = require("path");
const featuresPath = path.join(__dirname, "./config/config.json");
let features = {};
function loadFeatures() {
const raw = fs.readFileSync(featuresPath, "utf-8");
features = JSON.parse(raw);
console.log(features)
return features;
}
const configFeatures = loadFeatures();
module.exports = { module.exports = {
version: packageJson.version, version: packageJson.version,
@@ -7,17 +22,5 @@ module.exports = {
debugMode: true, debugMode: true,
localMode: !app.isPackaged, localMode: !app.isPackaged,
DiscordRPCID: "1410934537051181146", DiscordRPCID: "1410934537051181146",
configFeatures
// Variables Used to toggle main features
autoUpdate: true,
discordRPC: true,
customTopBar: true, // (Will be active on next launch)
autoDownloadPlaylist: true,
logSystem: true, // Disable = Dangerous
autoCheckInfo: true, // To Improve speed ? (NO)
outputTitleCheck: true, // For Non latin characters (Russian)
addThumbail: true, // The Pictures in the files (audio files)
addMetadata: true, // Looks Explicit
downloadSystem: true, // Why would you disable this ? I don't know but why not
notifySystem: true // Notification when download
} }

13
config/config.json Normal file
View File

@@ -0,0 +1,13 @@
{
"autoUpdate": true,
"discordRPC": true,
"customTopBar": true,
"autoDownloadPlaylist": true,
"logSystem": true,
"autoCheckInfo": true,
"outputTitleCheck": true,
"addThumbnail": true,
"addMetadata": true,
"downloadSystem": true,
"notifySystem": true
}

15
main.js
View File

@@ -3,8 +3,11 @@ const { app, BrowserWindow, ipcMain, dialog, Menu, shell } = require("electron")
const path = require("path"); const path = require("path");
const os = require("os"); const os = require("os");
const fs = require("fs"); const fs = require("fs");
const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger"); const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger");
const { AutoUpdater } = require("./server/update.js"); const { AutoUpdater } = require("./server/update.js");
const { configFeatures } = require("./config.js");
const { startRPC } = require("./server/discordRPC");
let mainWindow; let mainWindow;
const logsFolderPath = logDir; const logsFolderPath = logDir;
@@ -77,7 +80,7 @@ async function createMainWindow() {
height: 800, height: 800,
minWidth: 750, minWidth: 750,
minHeight: 800, minHeight: 800,
frame:false, frame: !configFeatures.customTopBar,
webPreferences: { webPreferences: {
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
@@ -163,11 +166,15 @@ app.whenReady().then(async () => {
await expressServer.startServer(); await expressServer.startServer();
logger.info("Express Server Started"); logger.info("Express Server Started");
const { startRPC } = require("./server/discordRPC"); ipcMain.handle("features", () => {
startRPC(); return configFeatures;
});
configFeatures.discordRPC ? startRPC() : "";
await createMainWindow(); await createMainWindow();
AutoUpdater(mainWindow); configFeatures.AutoUpdater ? AutoUpdater(mainWindow) : ""; // Auto Update
} catch (err) { } catch (err) {
logger.error("Window or Server error :", err); logger.error("Window or Server error :", err);
app.quit(); app.quit();

View File

@@ -3,7 +3,8 @@ const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("electronAPI", { contextBridge.exposeInMainWorld("electronAPI", {
getDefaultDownloadPath: () => ipcRenderer.invoke("get-default-download-path"), getDefaultDownloadPath: () => ipcRenderer.invoke("get-default-download-path"),
selectDownloadFolder: () => ipcRenderer.invoke("select-download-folder"), selectDownloadFolder: () => ipcRenderer.invoke("select-download-folder"),
setProgress: (percent) => ipcRenderer.send("set-progress", percent) setProgress: (percent) => ipcRenderer.send("set-progress", percent),
getFeatures: () => ipcRenderer.invoke("features")
}); });
// Contrôles de fenêtre et outils custom pour la topbar // Contrôles de fenêtre et outils custom pour la topbar

View File

@@ -17,7 +17,7 @@
</head> </head>
<body> <body>
<div class="topbar"> <div class="topbar" id="topbar">
<div class="custom-controls"> <div class="custom-controls">
<button id="devtools-btn" title="Dev Tools">Tools</button> <button id="devtools-btn" title="Dev Tools">Tools</button>
<button id="logs-btn" title="Logs">Logs</button> <button id="logs-btn" title="Logs">Logs</button>
@@ -31,12 +31,12 @@
</div> </div>
</div> </div>
<div class="theme-switcher"> <div class="theme-switcher" id="theme-switcher">
<label for="themeSelect">Thème :</label> <label for="themeSelect">Thème :</label>
<select id="themeSelect" aria-label="Choisir le thème"></select> <select id="themeSelect" aria-label="Choisir le thème"></select>
</div> </div>
<div class="container"> <div class="container" id="container">
<header> <header>
<h1 id="title"> Freedom Loader </h1> <h1 id="title"> Freedom Loader </h1>

View File

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

View File

@@ -21,4 +21,12 @@ function setupTopbarListeners() {
}); });
} }
setupTopbarListeners(); setupTopbarListeners(); // IF it put it the if check. It don't work. Why ?
const features = await window.electronAPI.getFeatures();
if(!features.customTopBar){
document.getElementById("topbar").style.display = "none";
document.getElementById("container").style.marginTop = "0";
document.getElementById("theme-switcher").style.top = "30px";
}

View File

@@ -2,6 +2,7 @@ const path = require("path");
const getUserBrowser = require("./getBrowser"); const getUserBrowser = require("./getBrowser");
const { ffmpegPath, denoPath} = require("./path"); const { ffmpegPath, denoPath} = require("./path");
const { configFeatures } = require("../../config.js");
function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) { function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
@@ -10,8 +11,8 @@ function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
"--cookies-from-browser", `${getUserBrowser()}`, "--cookies-from-browser", `${getUserBrowser()}`,
"--no-continue", "--no-continue",
"--no-overwrites", "--no-overwrites",
"--embed-thumbnail", configFeatures.addThumbail ? "--embed-thumbnail" : null,
"--add-metadata", configFeatures.addMetadata ? "--add-metadata" : null,
"--concurrent-fragments", "8", "--concurrent-fragments", "8",
"--retries", "10", "--retries", "10",
"--fragment-retries", "10", "--fragment-retries", "10",
@@ -37,7 +38,7 @@ function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
args.push("-o", path.join(outputFolder, "%(title)s.%(ext)s")); args.push("-o", path.join(outputFolder, "%(title)s.%(ext)s"));
args.push(url); args.push(url);
return args; return args.filter(Boolean);
} }
module.exports = { buildYtDlpArgs }; module.exports = { buildYtDlpArgs };