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 { 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 = {
version: packageJson.version,
@@ -7,17 +22,5 @@ module.exports = {
debugMode: true,
localMode: !app.isPackaged,
DiscordRPCID: "1410934537051181146",
// 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
configFeatures
}

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 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 } = require("./server/discordRPC");
let mainWindow;
const logsFolderPath = logDir;
@@ -77,7 +80,7 @@ async function createMainWindow() {
height: 800,
minWidth: 750,
minHeight: 800,
frame:false,
frame: !configFeatures.customTopBar,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
@@ -163,11 +166,15 @@ app.whenReady().then(async () => {
await expressServer.startServer();
logger.info("Express Server Started");
const { startRPC } = require("./server/discordRPC");
startRPC();
ipcMain.handle("features", () => {
return configFeatures;
});
configFeatures.discordRPC ? startRPC() : "";
await createMainWindow();
AutoUpdater(mainWindow);
configFeatures.AutoUpdater ? AutoUpdater(mainWindow) : ""; // Auto Update
} catch (err) {
logger.error("Window or Server error :", err);
app.quit();

View File

@@ -3,7 +3,8 @@ 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")
});
// Contrôles de fenêtre et outils custom pour la topbar

View File

@@ -17,7 +17,7 @@
</head>
<body>
<div class="topbar">
<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>
@@ -31,12 +31,12 @@
</div>
</div>
<div class="theme-switcher">
<div class="theme-switcher" id="theme-switcher">
<label for="themeSelect">Thème :</label>
<select id="themeSelect" aria-label="Choisir le thème"></select>
</div>
<div class="container">
<div class="container" id="container">
<header>
<h1 id="title"> Freedom Loader </h1>

View File

@@ -25,156 +25,164 @@ async function fetchVideoInfo(url) {
return { error: "Network or JSON Issue" };
}
}
async function init() {
const configFeatures = await window.electronAPI.getFeatures();
document.addEventListener("DOMContentLoaded", () => {
const urlInput = document.getElementById("UrlInput");
const infoDiv = document.getElementById("videoInfo");
const loaderBox = document.getElementById("loaderBox");
if (!configFeatures.autoCheckInfo) return;
let lastFetchedUrl = "";
document.addEventListener("DOMContentLoaded", () => {
const urlInput = document.getElementById("UrlInput");
const infoDiv = document.getElementById("videoInfo");
const loaderBox = document.getElementById("loaderBox");
urlInput.addEventListener("input", async () => {
const url = urlInput.value.trim();
let lastFetchedUrl = "";
// Si champ vide -> reset total
if (!url || url.length < 2) {
infoDiv.innerHTML = "";
infoDiv.classList.remove("visible", "playlist-mode");
lastFetchedUrl = "";
return;
}
urlInput.addEventListener("input", async () => {
const url = urlInput.value.trim();
if (url === lastFetchedUrl) return;
lastFetchedUrl = url;
// Si champ vide -> reset total
if (!url || url.length < 2) {
infoDiv.innerHTML = "";
infoDiv.classList.remove("visible", "playlist-mode");
lastFetchedUrl = "";
return;
}
loaderBox.style.display = "flex";
const data = await fetchVideoInfo(url);
loaderBox.style.display = "none";
if (url === lastFetchedUrl) return;
lastFetchedUrl = url;
// 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 = "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();

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 { ffmpegPath, denoPath} = require("./path");
const { configFeatures } = require("../../config.js");
function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
@@ -10,8 +11,8 @@ function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
"--cookies-from-browser", `${getUserBrowser()}`,
"--no-continue",
"--no-overwrites",
"--embed-thumbnail",
"--add-metadata",
configFeatures.addThumbail ? "--embed-thumbnail" : null,
configFeatures.addMetadata ? "--add-metadata" : null,
"--concurrent-fragments", "8",
"--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(url);
return args;
return args.filter(Boolean);
}
module.exports = { buildYtDlpArgs };