Version 2.0.0 - update of everything

This commit is contained in:
raphix committed 2026-10-07 14:05:02 +02:00
1 parent e006d48c0a
commit 6d81a8c1c0
57 files changed
+9407 -364

No files matched your search

+143
View File
@@ -0,0 +1,143 @@
/**
* [Subsonics Chopin - Frontend]
* File: adminFormat.js
* Description: Fonctions de formatage de l'interface d'administration (toutes protégées contre NaN)
*/
export function safeNum(value, fallback = 0) {
const n = Number(value);
return Number.isFinite(n) ? n : fallback;
}
// 1536 -> "1.5 Ko"
export function formatBytes(bytes) {
let value = safeNum(bytes);
const units = ["o", "Ko", "Mo", "Go", "To"];
let i = 0;
while (value >= 1024 && i < units.length - 1) {
value /= 1024;
i++;
}
return `${i === 0 ? value : value.toFixed(1)} ${units[i]}`;
}
// Durée longue en secondes -> "2j 03h 04m" / "3h 05m 12s" / "45s"
export function formatUptime(seconds) {
let s = Math.max(0, Math.floor(safeNum(seconds)));
const d = Math.floor(s / 86400); s -= d * 86400;
const h = Math.floor(s / 3600); s -= h * 3600;
const m = Math.floor(s / 60); s -= m * 60;
const pad = (n) => String(n).padStart(2, "0");
if (d > 0) return `${d}j ${pad(h)}h ${pad(m)}m`;
if (h > 0) return `${h}h ${pad(m)}m ${pad(s)}s`;
if (m > 0) return `${m}m ${pad(s)}s`;
return `${s}s`;
}
// Position en millisecondes -> "3:05" / "1:02:05"
export function formatClock(ms) {
const total = Math.max(0, Math.floor(safeNum(ms) / 1000));
const h = Math.floor(total / 3600);
const m = Math.floor((total % 3600) / 60);
const s = total % 60;
const pad = (n) => String(n).padStart(2, "0");
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
}
function toDate(value) {
if (value === null || value === undefined || value === "") return null;
const date = new Date(typeof value === 'string' && /^\d+$/.test(value) ? Number(value) : value);
return isNaN(date.getTime()) ? null : date;
}
// Date ISO / timestamp -> "06/10/2026 21:31:11"
export function formatDate(value) {
const date = toDate(value);
if (!date) return "Inconnue";
return date.toLocaleString("fr-FR");
}
// Date ISO / timestamp -> "il y a 5 min" / "dans 3 min"
export function formatRelative(value, now = Date.now()) {
const date = toDate(value);
if (!date) return "Jamais";
const diff = Math.round((date.getTime() - now) / 1000);
const abs = Math.abs(diff);
let text;
if (abs < 5) return "à l'instant";
if (abs < 60) text = `${abs} s`;
else if (abs < 3600) text = `${Math.floor(abs / 60)} min`;
else if (abs < 86400) text = `${Math.floor(abs / 3600)} h`;
else text = `${Math.floor(abs / 86400)} j`;
return diff < 0 ? `il y a ${text}` : `dans ${text}`;
}
// Statut d'un player (état v2) -> libellé + couleur de Tag
export function playerStatus(status) {
switch (status) {
case 'playing': return { label: "En lecture", color: "var(--text-success)", icon: "fa-solid fa-play" };
case 'paused': return { label: "En pause", color: "var(--text-warning)", icon: "fa-solid fa-pause" };
case 'loading': return { label: "Chargement", color: "var(--admin-color)", icon: "fa-solid fa-spinner" };
case 'stalled': return { label: "Bloqué", color: "var(--text-error)", icon: "fa-solid fa-triangle-exclamation" };
case 'idle': return { label: "Inactif", color: "var(--text-secondary)", icon: "fa-solid fa-circle" };
case 'offline': return { label: "Hors ligne", color: "var(--text-error)", icon: "fa-solid fa-circle" };
default: return { label: status || "Inconnu", color: "var(--text-secondary)", icon: "fa-solid fa-circle" };
}
}
// Avatar Discord d'un utilisateur (repli sur l'avatar par défaut si absent)
export function userAvatar(user) {
if (user?.avatar) return `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}`;
let index = 0;
try { index = Number((BigInt(user?.id || 0) >> BigInt(22)) % BigInt(6)); } catch (e) { index = 0; }
return `https://cdn.discordapp.com/embed/avatars/${index}.png`;
}
// Classes d'erreur yt-dlp (backend/src/youtube/errors.js) -> libellé français
export const YOUTUBE_CLASSES = {
BOT_CHECK: "Vérification anti-robot",
IP_BLOCKED: "IP bloquée par YouTube",
CAPTCHA: "Captcha demandé",
RATE_LIMITED: "Limite de requêtes YouTube",
HTTP_403: "Erreur 403 (accès refusé)",
HTTP_429: "Erreur 429 (trop de requêtes)",
AGE_RESTRICTED: "Restriction d'âge",
LOGIN_REQUIRED: "Connexion requise",
MEMBERS_ONLY: "Réservé aux membres",
PRIVATE: "Vidéo privée",
UNAVAILABLE: "Vidéo indisponible",
GEO_BLOCKED: "Bloquée dans ce pays",
POT_MISSING: "PO token manquant",
SABR: "Flux SABR uniquement",
JS_CHALLENGE: "Défi JavaScript non résolu",
FORMAT_UNAVAILABLE: "Format indisponible",
PAGE_RELOAD: "Page à recharger",
COOKIES_ROTATED: "Cookies invalidés (rotation)",
EMPTY_DOWNLOAD: "Téléchargement vide",
BINARY_MISSING: "yt-dlp introuvable",
TIMEOUT: "Délai dépassé",
UNKNOWN: "Erreur inconnue"
};
export function youtubeClass(cls) {
if (!cls) return "Aucune";
return YOUTUBE_CLASSES[cls] || cls;
}
// Origine des cookies YouTube ("paste", "file", "legacy", "browser:firefox")
export function cookieSource(source) {
if (!source) return "Inconnue";
if (source === "paste") return "Texte collé";
if (source === "file") return "Fichier importé";
if (source === "legacy") return "Ancien fichier (reprise automatique)";
if (source.startsWith("browser:")) return "Navigateur (" + source.slice(8) + ")";
return source;
}
// État du compte YouTube (cookies.getStatus() ou overview.ytdlp.cookies) -> libellé + couleur de Tag
export function youtubeAccount(cookies) {
if (!cookies?.present) return { key: 'none', label: "Aucun compte", color: "var(--text-secondary)", icon: "fa-solid fa-user-slash" };
if (cookies.invalidatedAt) return { key: 'invalid', label: "Invalidé", color: "var(--text-error)", icon: "fa-solid fa-circle-xmark" };
if (!cookies.loggedIn) return { key: 'anonymous', label: "Non connecté", color: "var(--text-warning)", icon: "fa-solid fa-triangle-exclamation" };
return { key: 'valid', label: "Connecté", color: "var(--text-success)", icon: "fa-solid fa-circle-check" };
}