Version 2.0.0 - update of everything
This commit is contained in:
57 files changed
+9407
-364
No files matched your search
@@ -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" };
|
||||
}
|
||||
Reference in new issue
Block a user