mirror of
https://github.com/MasterAcnolo/Freedom-Loader.git
synced 2026-07-29 18:25:47 +02:00
LE POULET
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^5.1.0"
|
"express": "^5.1.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server/server.js",
|
||||||
|
"dev": "nodemon server/server.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
<form action="/download" method="POST" id="downloadForm">
|
<form action="/download" method="POST" id="downloadForm">
|
||||||
<input name="url" placeholder="URL YouTube" id="UrlInput">
|
<input name="url" placeholder="URL YouTube" id="UrlInput">
|
||||||
@@ -30,6 +31,8 @@
|
|||||||
|
|
||||||
<div id="videoInfo" style="margin-top: 20px;"></div>
|
<div id="videoInfo" style="margin-top: 20px;"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="script/downloadstatus.js"></script>
|
<script src="script/downloadstatus.js"></script>
|
||||||
<script src="script/fetchinfo.js"></script>
|
<script src="script/fetchinfo.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,29 +1,55 @@
|
|||||||
|
// Récupération du formulaire de téléchargement
|
||||||
const form = document.getElementById("downloadForm");
|
const form = document.getElementById("downloadForm");
|
||||||
|
|
||||||
|
// Div où on affiche l'état de progression (en cours, erreur, succès)
|
||||||
const statusDiv = document.getElementById("downloadStatus");
|
const statusDiv = document.getElementById("downloadStatus");
|
||||||
|
|
||||||
form.addEventListener("submit", async (e) => {
|
// Récupération du bouton de validation pour pouvoir le désactiver pendant la requête
|
||||||
e.preventDefault(); // empêche la soumission classique (rechargement)
|
const button = form.querySelector("button");
|
||||||
|
|
||||||
|
// On intercepte la soumission du formulaire
|
||||||
|
form.addEventListener("submit", async (e) => {
|
||||||
|
e.preventDefault(); // empêche le rechargement de la page par le navigateur
|
||||||
|
|
||||||
|
// On désactive le bouton pour éviter les clics multiples
|
||||||
|
button.disabled = true;
|
||||||
|
|
||||||
|
// Affiche un message d'attente à l'utilisateur
|
||||||
statusDiv.textContent = "⏳ Téléchargement en cours...";
|
statusDiv.textContent = "⏳ Téléchargement en cours...";
|
||||||
|
|
||||||
|
// Prépare les données du formulaire sous forme URL-encoded
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
const params = new URLSearchParams(formData);
|
const params = new URLSearchParams(formData);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Envoi de la requête POST vers /download avec les données du formulaire
|
||||||
const res = await fetch("/download", {
|
const res = await fetch("/download", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
body: params,
|
body: params,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Si la réponse n'est pas un succès (code 4xx ou 5xx)
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
statusDiv.textContent = "❌ Erreur pendant le téléchargement.";
|
statusDiv.textContent = "❌ Erreur pendant le téléchargement.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On récupère le texte de la réponse du serveur
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
statusDiv.textContent = text; // affiche « Téléchargement terminé » ou autre message serveur
|
|
||||||
|
// Affiche le message de fin de téléchargement (par exemple « Téléchargement terminé »)
|
||||||
|
statusDiv.textContent = text;
|
||||||
} catch {
|
} catch {
|
||||||
|
// Gestion d'erreur en cas de problème réseau ou autre
|
||||||
statusDiv.textContent = "❌ Une erreur s’est produite.";
|
statusDiv.textContent = "❌ Une erreur s’est produite.";
|
||||||
|
} finally {
|
||||||
|
// Réactive le bouton pour autoriser d'autres soumissions
|
||||||
|
button.disabled = false;
|
||||||
|
|
||||||
|
// Efface le message de status après 5 secondes pour garder l'UI propre
|
||||||
|
setTimeout(() => {
|
||||||
|
statusDiv.textContent = "";
|
||||||
|
}, 5000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,73 +1,78 @@
|
|||||||
const urlInput = document.getElementById("UrlInput"); // Récupère le champ input où l'utilisateur entre l'URL
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const infoDiv = document.getElementById("videoInfo"); // Récupère la div où on affichera les infos
|
const urlInput = document.getElementById("UrlInput");
|
||||||
|
const infoDiv = document.getElementById("videoInfo");
|
||||||
|
|
||||||
let lastFetchedUrl = ""; // Pour éviter de refaire plusieurs requêtes pour la même URL
|
let lastFetchedUrl = "";
|
||||||
|
|
||||||
// On écoute chaque changement dans le champ URL
|
|
||||||
urlInput.addEventListener("input", async () => {
|
urlInput.addEventListener("input", async () => {
|
||||||
const url = urlInput.value; // On prend la valeur actuelle du champ
|
const url = urlInput.value.trim();
|
||||||
|
console.log("Input détecté :", url);
|
||||||
|
|
||||||
// Si pas d'URL, ou URL trop courte, ou déjà traitée, on sort
|
// Si URL vide ou trop courte : on vide et cache la div
|
||||||
if (!url || url.length < 5 || url === lastFetchedUrl) return;
|
if (!url || url.length < 5) {
|
||||||
|
infoDiv.innerHTML = "";
|
||||||
|
infoDiv.classList.remove("visible");
|
||||||
|
lastFetchedUrl = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Si même URL que précédemment, on ne fait rien
|
||||||
|
if (url === lastFetchedUrl) return;
|
||||||
|
|
||||||
lastFetchedUrl = url; // On mémorise l'URL traitée
|
lastFetchedUrl = url;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Envoi d'une requête POST vers le backend /info avec l'URL dans le corps
|
|
||||||
const res = await fetch("/info", {
|
const res = await fetch("/info", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
body: new URLSearchParams({ url }) // Encode l'URL au format x-www-form-urlencoded
|
body: new URLSearchParams({ url }),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Si la réponse n'est pas OK (erreur 4xx ou 5xx)
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
infoDiv.innerHTML = "❌ Impossible de récupérer les infos."; // Message d'erreur utilisateur
|
infoDiv.innerHTML = "❌ Impossible de récupérer les infos.";
|
||||||
|
infoDiv.classList.remove("visible");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// On récupère la réponse JSON (les infos de la vidéo)
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
// Formatage de la durée en minutes et secondes (ex: 4m 35s)
|
|
||||||
const minutes = Math.floor(data.duration / 60);
|
const minutes = Math.floor(data.duration / 60);
|
||||||
const seconds = data.duration % 60;
|
const seconds = data.duration % 60;
|
||||||
const durationStr = `${minutes}m ${seconds.toString().padStart(2, '0')}s`;
|
const durationStr = `${minutes}m ${seconds.toString().padStart(2, "0")}s`;
|
||||||
|
|
||||||
// Formatage de la date d'upload depuis YYYYMMDD vers JJ/MM/AAAA
|
|
||||||
const date = data.upload_date || "";
|
const date = data.upload_date || "";
|
||||||
const readableDate = date
|
const readableDate = date
|
||||||
? `${date.slice(6, 8)}/${date.slice(4, 6)}/${date.slice(0, 4)}`
|
? `${date.slice(6, 8)}/${date.slice(4, 6)}/${date.slice(0, 4)}`
|
||||||
: "Inconnue";
|
: "Inconnue";
|
||||||
|
|
||||||
// Taille estimée en Mo, ou "Inconnue" si pas dispo
|
|
||||||
const sizeMB = data.filesize_approx
|
const sizeMB = data.filesize_approx
|
||||||
? (data.filesize_approx / (1024 * 1024)).toFixed(2) + " Mo"
|
? (data.filesize_approx / (1024 * 1024)).toFixed(2) + " Mo"
|
||||||
: "Inconnue";
|
: "Inconnue";
|
||||||
|
|
||||||
// Catégories en chaîne séparée par des virgules, ou "Non spécifiées"
|
|
||||||
const categories = data.categories
|
const categories = data.categories
|
||||||
? data.categories.join(", ")
|
? data.categories.join(", ")
|
||||||
: "Non spécifiées";
|
: "Non spécifiées";
|
||||||
|
|
||||||
// Mise à jour du contenu HTML de la div avec toutes les infos formatées
|
|
||||||
infoDiv.innerHTML = `
|
infoDiv.innerHTML = `
|
||||||
<h3>${data.title}</h3>
|
<h3>${data.title}</h3>
|
||||||
<img src="${data.thumbnail}" width="320">
|
<img src="${data.thumbnail}" width="320" alt="Thumbnail">
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Durée :</strong> ${durationStr}</li>
|
<li><strong>Durée :</strong> ${durationStr}</li>
|
||||||
<li><strong>Uploader :</strong> ${data.uploader || "Inconnu"}</li>
|
<li><strong>Uploader :</strong> ${data.uploader || "Inconnu"}</li>
|
||||||
<li><strong>Date d’upload :</strong> ${readableDate}</li>
|
<li><strong>Date d’upload :</strong> ${readableDate}</li>
|
||||||
<li><strong>Vues :</strong> ${data.view_count?.toLocaleString() || "?"}</li>
|
<li><strong>Vues :</strong> ${data.view_count?.toLocaleString() || "?"}</li>
|
||||||
<li><strong>Likes :</strong> ${data.like_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>URL :</strong> <a href="${data.webpage_url}" target="_blank" rel="noopener noreferrer">${data.webpage_url}</a></li>
|
||||||
<li><strong>Channel :</strong> <a href="${data.channel_url}" target="_blank">${data.channel_url}</a></li>
|
<li><strong>Channel :</strong> <a href="${data.channel_url}" target="_blank" rel="noopener noreferrer">${data.channel_url}</a></li>
|
||||||
<li><strong>Taille estimée :</strong> ${sizeMB}</li>
|
<li><strong>Taille estimée :</strong> ${sizeMB}</li>
|
||||||
<li><strong>Catégories :</strong> ${categories}</li>
|
<li><strong>Catégories :</strong> ${categories}</li>
|
||||||
</ul>
|
</ul>
|
||||||
`;
|
`;
|
||||||
|
// Affiche la div avec la classe pour transition douce
|
||||||
|
infoDiv.classList.add("visible");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Si une erreur survient lors de la récupération ou du parsing
|
console.error("Erreur lors de la récupération :", e);
|
||||||
infoDiv.innerHTML = "❌ Erreur lors de la récupération.";
|
infoDiv.innerHTML = "❌ Erreur lors de la récupération.";
|
||||||
|
infoDiv.classList.remove("visible");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
.container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #121212;
|
||||||
|
padding: 1em;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.5);
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#downloadStatus {
|
||||||
|
min-height: 1.5em; /* pour garder la place même vide */
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
44
public/styles/layout/form.css
Normal file
44
public/styles/layout/form.css
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
form {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 2em;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input[type="text"],
|
||||||
|
form input[type="url"],
|
||||||
|
form select {
|
||||||
|
padding: 0.75em;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input[type="checkbox"] {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
form button {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
padding: 0.75em;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
form button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
form#downloadForm {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
@import url("layout/form.css");
|
||||||
|
@import url("layout/container.css");
|
||||||
|
@import url("layout/ctr-infos.css");
|
||||||
|
|
||||||
*{
|
*{
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -6,5 +10,45 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color:rgb(83, 83, 83) ;
|
margin: 0;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #333;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#downloadStatus {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #eee;
|
||||||
|
min-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#videoInfo {
|
||||||
|
margin-top: 100px;
|
||||||
|
padding: 1em;
|
||||||
|
max-width: 600px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.5);
|
||||||
|
transition: opacity 0.5s ease, max-height 0.5s ease;
|
||||||
|
opacity: 0;
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#videoInfo.visible {
|
||||||
|
opacity: 1;
|
||||||
|
max-height: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user