+
+
+
diff --git a/public/script/appVersion.js b/public/script/appVersion.js
new file mode 100644
index 0000000..ad9aaa0
--- /dev/null
+++ b/public/script/appVersion.js
@@ -0,0 +1,6 @@
+async function versionLabel(){
+ const appVersion = await window.electronAPI.getVersion();
+ document.getElementById("version-badge").textContent = `v${appVersion}`;
+};
+
+versionLabel();
\ No newline at end of file
diff --git a/public/script/custompath.js b/public/script/custompath.js
index 4bb81ad..ccf01f0 100644
--- a/public/script/custompath.js
+++ b/public/script/custompath.js
@@ -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é.");
+ }
+ });
+});
diff --git a/public/script/customthemes.js b/public/script/customthemes.js
index 34b5ec0..129a7e7 100644
--- a/public/script/customthemes.js
+++ b/public/script/customthemes.js
@@ -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" },
diff --git a/public/script/fetchinfo.js b/public/script/fetchinfo.js
index bffb928..af9c9b7 100644
--- a/public/script/fetchinfo.js
+++ b/public/script/fetchinfo.js
@@ -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 = `
-
- `;
- 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 = `
-
- `;
-
- 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 += `
-
-

-
${v.title}
-
Duration : ${durationStr}
-
URL
-
-
+ // Gestion des erreurs
+ if (data.error) {
+ infoDiv.innerHTML = `
+
+ ${data.error}
`;
- });
+ 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 = `
+
Playlist Detected: ${data.title}
+
Video Count: ${data.count}
+
Channel : ${data.channel || "Unknown"}
+
+ `;
- 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 += `
+
+

+
${v.title}
+
Duration : ${durationStr}
+
URL
+
+
+
+ `;
+ });
+
+ // 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 = `
+
${data.title}
+

+
+ - Duration : ${durationStr}
+ - Uploader : ${data.uploader || "Inconnu"}
+ - Upload Date : ${readableDate}
+ - Views : ${data.view_count?.toLocaleString() || "?"}
+ - Likes : ${data.like_count?.toLocaleString() || "?"}
+ - URL : ${data.webpage_url}
+ - Channel : ${data.channel_url}
+ - Estimed Size : ${sizeStr}
+ - Category : ${categories}
+
+ `;
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 = `
-
${data.title}
-

-
- - Duration : ${durationStr}
- - Uploader : ${data.uploader || "Inconnu"}
- - Upload Date : ${readableDate}
- - Views : ${data.view_count?.toLocaleString() || "?"}
- - Likes : ${data.like_count?.toLocaleString() || "?"}
- - URL : ${data.webpage_url}
- - Channel : ${data.channel_url}
- - Estimed Size : ${sizeStr}
- - Category : ${categories}
-
- `;
-
- infoDiv.classList.add("visible");
- });
-
-})
\ No newline at end of file
+init();
\ No newline at end of file
diff --git a/public/script/settingsPanel.js b/public/script/settingsPanel.js
new file mode 100644
index 0000000..ae8f2f9
--- /dev/null
+++ b/public/script/settingsPanel.js
@@ -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();
diff --git a/public/script/topbar.js b/public/script/topbar.js
index 0225ea8..be7f584 100644
--- a/public/script/topbar.js
+++ b/public/script/topbar.js
@@ -10,6 +10,7 @@ function setupTopbarListeners() {
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();
@@ -18,7 +19,16 @@ function setupTopbarListeners() {
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();
+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";
+}
\ No newline at end of file
diff --git a/public/styles/components/themebutton.css b/public/styles/components/themebutton.css
deleted file mode 100644
index 3031696..0000000
--- a/public/styles/components/themebutton.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.theme-switcher {
- position: absolute;
- top: 60px;
- right: 10px;
- background: #242424;
- padding: 8px 12px;
- border-radius: 8px;
- color: #fff;
- font-family: sans-serif;
- font-size: 0.9rem;
- display: flex;
- align-items: center;
- gap: 8px;
- z-index: 1050;
-}
-
-.theme-switcher label {
- cursor: pointer;
- font-weight: 600;
-}
-
-.theme-switcher select {
- background: #fff;
- border: 1px solid #242424;
- border-radius: 6px;
- padding: 4px 8px;
- color: #000;
- cursor: pointer;
- font-weight: 600;
- transition: background 0.3s ease, color 0.3s ease;
-}
-
-.theme-switcher select:hover,
-.theme-switcher select:focus {
- background: #474747;
- color: #fff;
- outline: none;
-}
diff --git a/public/styles/layout/settingsPanel.css b/public/styles/layout/settingsPanel.css
new file mode 100644
index 0000000..0fa7b3d
--- /dev/null
+++ b/public/styles/layout/settingsPanel.css
@@ -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; }
+}
\ No newline at end of file
diff --git a/public/styles/styles.css b/public/styles/styles.css
index 6472249..2a4aa3e 100644
--- a/public/styles/styles.css
+++ b/public/styles/styles.css
@@ -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,16 @@ body {
}
+/* Because why not */
+#reference{
+ position: fixed;
+ pointer-events: none;
+ bottom:0vh;
+ left: 10px;
+ font-size: 1em;
+ color: #b0b0b02f;
+}
+
/* Scrollbar globale pour Chromium/Electron */
body::-webkit-scrollbar {
width: 10px;
diff --git a/public/styles/theme/chirac-theme.css b/public/styles/theme/chirac-theme.css
index daee9f3..2645be5 100644
--- a/public/styles/theme/chirac-theme.css
+++ b/public/styles/theme/chirac-theme.css
@@ -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;
}
diff --git a/public/styles/theme/dark-theme.css b/public/styles/theme/dark-theme.css
index b259c33..e1bd48f 100644
--- a/public/styles/theme/dark-theme.css
+++ b/public/styles/theme/dark-theme.css
@@ -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;
+
}
diff --git a/public/styles/theme/default-theme.css b/public/styles/theme/default-theme.css
deleted file mode 100644
index a3d2017..0000000
--- a/public/styles/theme/default-theme.css
+++ /dev/null
@@ -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;
-}
\ No newline at end of file
diff --git a/public/styles/theme/drift-theme.css b/public/styles/theme/drift-theme.css
index 9d318a7..f55f365 100644
--- a/public/styles/theme/drift-theme.css
+++ b/public/styles/theme/drift-theme.css
@@ -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;
+}
diff --git a/public/styles/theme/fanatic-theme.css b/public/styles/theme/fanatic-theme.css
index 3f8ff0c..401fd67 100644
--- a/public/styles/theme/fanatic-theme.css
+++ b/public/styles/theme/fanatic-theme.css
@@ -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;
}
diff --git a/public/styles/theme/light-theme.css b/public/styles/theme/light-theme.css
index 862f47d..4b1d2b1 100644
--- a/public/styles/theme/light-theme.css
+++ b/public/styles/theme/light-theme.css
@@ -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;
}
diff --git a/public/styles/theme/neon-theme.css b/public/styles/theme/neon-theme.css
index 987cd83..63c87da 100644
--- a/public/styles/theme/neon-theme.css
+++ b/public/styles/theme/neon-theme.css
@@ -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 {
diff --git a/public/styles/theme/nf-theme.css b/public/styles/theme/nf-theme.css
index 65b086a..5b7ac42 100644
--- a/public/styles/theme/nf-theme.css
+++ b/public/styles/theme/nf-theme.css
@@ -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 {
diff --git a/public/styles/theme/songbird-theme.css b/public/styles/theme/songbird-theme.css
index 3eaaab3..976d0cc 100644
--- a/public/styles/theme/songbird-theme.css
+++ b/public/styles/theme/songbird-theme.css
@@ -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 {
diff --git a/public/styles/theme/spicy-theme.css b/public/styles/theme/spicy-theme.css
index 3866a59..a001407 100644
--- a/public/styles/theme/spicy-theme.css
+++ b/public/styles/theme/spicy-theme.css
@@ -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 */
diff --git a/public/styles/theme/themeimport.css b/public/styles/theme/themeimport.css
index eb320a6..7de5d07 100644
--- a/public/styles/theme/themeimport.css
+++ b/public/styles/theme/themeimport.css
@@ -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");
diff --git a/public/styles/variables.css b/public/styles/variables.css
index cfbb85d..29362d3 100644
--- a/public/styles/variables.css
+++ b/public/styles/variables.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;
+
}
diff --git a/server/controller/download.controller.js b/server/controller/download.controller.js
index 3b820ee..597b290 100644
--- a/server/controller/download.controller.js
+++ b/server/controller/download.controller.js
@@ -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`);
}
}
diff --git a/server/controller/info.controller.js b/server/controller/info.controller.js
index 187d074..949506b 100644
--- a/server/controller/info.controller.js
+++ b/server/controller/info.controller.js
@@ -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`);
}
}
diff --git a/server/helpers/buildArgs.js b/server/helpers/buildArgs.js
index 6d16353..bdabf95 100644
--- a/server/helpers/buildArgs.js
+++ b/server/helpers/buildArgs.js
@@ -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 };
diff --git a/server/helpers/path.js b/server/helpers/path.js
index d045c44..8eac1a5 100644
--- a/server/helpers/path.js
+++ b/server/helpers/path.js
@@ -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 };
diff --git a/server/helpers/rateLimit.js b/server/helpers/rateLimit.js
new file mode 100644
index 0000000..d65336f
--- /dev/null
+++ b/server/helpers/rateLimit.js
@@ -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
+}
\ No newline at end of file
diff --git a/server/server.js b/server/server.js
index fba428c..e8f1f75 100644
--- a/server/server.js
+++ b/server/server.js
@@ -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"));
});
diff --git a/server/update.js b/server/update.js
index a21908e..d38aa70 100644
--- a/server/update.js
+++ b/server/update.js
@@ -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() {