Version 2.0.0 - update of everything
This commit is contained in:
85 files changed
+13065
-2107
No files matched your search
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* [Subsonics Chopin - Backend] - Module YouTube
|
||||
* File: youtube/settings.js
|
||||
* Description: Réglages YouTube / yt-dlp (data/youtube/settings.json), les variables d'environnement l'emportent
|
||||
*/
|
||||
|
||||
const { LogType } = require('loguix');
|
||||
const clog = new LogType("Youtube-Settings");
|
||||
const { __glob } = require('../utils/GlobalVars');
|
||||
const { readJson, writeJson, emitUpdate } = require('./util');
|
||||
|
||||
const DEFAULTS = Object.freeze({
|
||||
// fallback : sans compte d'abord, cookies seulement après une vérification anti-robot / âge / connexion
|
||||
cookiesMode: "fallback",
|
||||
// Évite d'utiliser le compte depuis les IP tournantes de WARP (sauf si c'est la seule route)
|
||||
cookiesOnProxy: false,
|
||||
// "" = clients par défaut de yt-dlp (recommandé)
|
||||
playerClients: "",
|
||||
// Une tentative de plus avec ces clients après un 403 / format indisponible / SABR / PO token
|
||||
fallbackPlayerClients: "web_safari,tv_downgraded",
|
||||
potProviderUrl: "",
|
||||
forceIpv4: true,
|
||||
autoUpdate: true,
|
||||
seekStrategy: "auto",
|
||||
testUrl: "https://www.youtube.com/watch?v=jNQXAC9IVRw"
|
||||
});
|
||||
|
||||
const ENV = {
|
||||
cookiesMode: "YTDLP_COOKIES_MODE",
|
||||
cookiesOnProxy: "YTDLP_COOKIES_ON_PROXY",
|
||||
playerClients: "YTDLP_PLAYER_CLIENTS",
|
||||
fallbackPlayerClients: "YTDLP_FALLBACK_PLAYER_CLIENTS",
|
||||
potProviderUrl: "YTDLP_POT_PROVIDER",
|
||||
forceIpv4: "YTDLP_FORCE_IPV4",
|
||||
autoUpdate: "YTDLP_AUTO_UPDATE",
|
||||
seekStrategy: "YTDLP_SEEK_STRATEGY",
|
||||
testUrl: "YTDLP_TEST_URL"
|
||||
};
|
||||
|
||||
const YOUTUBE_HOSTS = /^(?:www\.|m\.|music\.)?youtube\.com$|^youtu\.be$/i;
|
||||
|
||||
function invalid(message) {
|
||||
const err = new Error(message);
|
||||
err.code = "INVALID_PARAMS";
|
||||
return err;
|
||||
}
|
||||
|
||||
function toBoolean(key, value) {
|
||||
if (typeof value === 'boolean') return value;
|
||||
if (/^(true|1|yes|on)$/i.test(String(value).trim())) return true;
|
||||
if (/^(false|0|no|off)$/i.test(String(value).trim())) return false;
|
||||
throw invalid(`${key} : valeur booléenne attendue`);
|
||||
}
|
||||
|
||||
function toClients(key, value) {
|
||||
const text = String(value ?? '').trim().toLowerCase().replace(/\s+/g, '');
|
||||
if (text.length > 200 || !/^[a-z_,\-]*$/.test(text)) {
|
||||
throw invalid(`${key} : liste de clients invalide (lettres, "_", "-" et virgules uniquement)`);
|
||||
}
|
||||
return text.split(',').filter(Boolean).join(',');
|
||||
}
|
||||
|
||||
const VALIDATORS = {
|
||||
cookiesMode: (value) => {
|
||||
if (!["fallback", "always", "never"].includes(value)) throw invalid("cookiesMode : fallback, always ou never attendu");
|
||||
return value;
|
||||
},
|
||||
cookiesOnProxy: (value) => toBoolean("cookiesOnProxy", value),
|
||||
playerClients: (value) => toClients("playerClients", value),
|
||||
fallbackPlayerClients: (value) => toClients("fallbackPlayerClients", value),
|
||||
potProviderUrl: (value) => {
|
||||
const text = String(value ?? '').trim();
|
||||
if (!text) return "";
|
||||
let parsed;
|
||||
try { parsed = new URL(text); } catch (e) { throw invalid("potProviderUrl : URL invalide"); }
|
||||
if (!/^https?:$/.test(parsed.protocol) || /[\s;"']/.test(text)) throw invalid("potProviderUrl : URL http(s) attendue");
|
||||
return text.replace(/\/+$/, '');
|
||||
},
|
||||
forceIpv4: (value) => toBoolean("forceIpv4", value),
|
||||
autoUpdate: (value) => toBoolean("autoUpdate", value),
|
||||
seekStrategy: (value) => {
|
||||
if (!["auto", "ffmpeg", "native"].includes(value)) throw invalid("seekStrategy : auto, ffmpeg ou native attendu");
|
||||
return value;
|
||||
},
|
||||
testUrl: (value) => {
|
||||
const text = String(value ?? '').trim();
|
||||
let parsed;
|
||||
try { parsed = new URL(text); } catch (e) { throw invalid("testUrl : URL invalide"); }
|
||||
if (!/^https?:$/.test(parsed.protocol) || !YOUTUBE_HOSTS.test(parsed.hostname) || /\s/.test(text)) {
|
||||
throw invalid("testUrl : lien YouTube http(s) attendu");
|
||||
}
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
let fileValues = null;
|
||||
let envValues = null;
|
||||
|
||||
function loadFile() {
|
||||
if (fileValues) return fileValues;
|
||||
const raw = readJson(__glob.YOUTUBE_SETTINGS, {});
|
||||
fileValues = {};
|
||||
for (const key of Object.keys(DEFAULTS)) {
|
||||
if (raw[key] === undefined) continue;
|
||||
try {
|
||||
fileValues[key] = VALIDATORS[key](raw[key]);
|
||||
} catch (e) {
|
||||
clog.warn(`Réglage ignoré dans settings.json : ${e.message}`);
|
||||
}
|
||||
}
|
||||
return fileValues;
|
||||
}
|
||||
|
||||
function loadEnv() {
|
||||
if (envValues) return envValues;
|
||||
envValues = {};
|
||||
for (const [key, name] of Object.entries(ENV)) {
|
||||
if (process.env[name] === undefined || process.env[name] === '') continue;
|
||||
try {
|
||||
envValues[key] = VALIDATORS[key](process.env[name]);
|
||||
} catch (e) {
|
||||
clog.warn(`Variable d'environnement ${name} ignorée : ${e.message}`);
|
||||
}
|
||||
}
|
||||
return envValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valeurs effectives (défaut < settings.json < environnement)
|
||||
*/
|
||||
function get() {
|
||||
return { ...DEFAULTS, ...loadFile(), ...loadEnv() };
|
||||
}
|
||||
|
||||
function isLocked(key) {
|
||||
return Object.prototype.hasOwnProperty.call(loadEnv(), key);
|
||||
}
|
||||
|
||||
/**
|
||||
* URL sans identifiants éventuels (pour l'affichage)
|
||||
*/
|
||||
function maskUrl(url) {
|
||||
if (!url) return "";
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
if (parsed.username || parsed.password) {
|
||||
parsed.username = "***";
|
||||
parsed.password = "";
|
||||
}
|
||||
return parsed.toString().replace(/\/$/, '');
|
||||
} catch (e) {
|
||||
return "***";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pour l'administration : { clé: { value, default, locked, env } } (URL du fournisseur PO token masquée)
|
||||
*/
|
||||
function getPublic() {
|
||||
const values = get();
|
||||
const result = {};
|
||||
for (const key of Object.keys(DEFAULTS)) {
|
||||
const value = key === "potProviderUrl" ? maskUrl(values[key]) : values[key];
|
||||
result[key] = { value, default: DEFAULTS[key], locked: isLocked(key), env: ENV[key] };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie un correctif sans l'appliquer (clés inconnues ou verrouillées refusées)
|
||||
* @param {Object} patch
|
||||
* @returns {Object} valeurs normalisées
|
||||
*/
|
||||
function validate(patch) {
|
||||
if (!patch || typeof patch !== 'object' || Array.isArray(patch)) throw invalid("Réglages invalides");
|
||||
const clean = {};
|
||||
for (const [key, value] of Object.entries(patch)) {
|
||||
if (!Object.prototype.hasOwnProperty.call(DEFAULTS, key)) throw invalid(`Réglage inconnu : ${key}`);
|
||||
if (isLocked(key)) throw invalid(`${key} est défini par la variable d'environnement ${ENV[key]}`);
|
||||
clean[key] = VALIDATORS[key](value);
|
||||
}
|
||||
return clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique et enregistre un correctif de réglages
|
||||
* @param {Object} patch
|
||||
* @param {{id?: string, username?: string}|null} by
|
||||
* @returns {Object} getPublic()
|
||||
*/
|
||||
function update(patch, by = null) {
|
||||
const clean = validate(patch);
|
||||
const changed = Object.keys(clean).filter(key => get()[key] !== clean[key]);
|
||||
if (changed.length === 0) return getPublic();
|
||||
|
||||
const next = { ...loadFile(), ...clean };
|
||||
writeJson(__glob.YOUTUBE_SETTINGS, next);
|
||||
fileValues = next;
|
||||
clog.log(`Réglages YouTube modifiés${by?.username ? ` par ${by.username}` : ''} : ${changed.join(', ')}`);
|
||||
emitUpdate();
|
||||
return getPublic();
|
||||
}
|
||||
|
||||
/**
|
||||
* Relit settings.json et l'environnement (tests)
|
||||
*/
|
||||
function reload() {
|
||||
fileValues = null;
|
||||
envValues = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste de clients yt-dlp normalisée (test de lecture), lève INVALID_PARAMS si invalide
|
||||
*/
|
||||
function normalizeClients(value) {
|
||||
return toClients("clients", value);
|
||||
}
|
||||
|
||||
module.exports = { get, getPublic, update, validate, normalizeClients, isLocked, reload, maskUrl, DEFAULTS, ENV };
|
||||
Reference in new issue
Block a user