Version 2.0.0 - update of everything

This commit is contained in:
raphix committed 2026-10-07 14:04:26 +02:00
1 parent 164a1da009
commit 6017e03eac
85 files changed
+13065 -2107

No files matched your search

+183
View File
@@ -0,0 +1,183 @@
const fs = require('fs');
const path = require('path');
const { BACKEND, __glob, isolateData, check, done, req, chopinTmpDirs } = require('./_setup');
const dataDir = isolateData('cookies');
const cookies = req('src/youtube/cookies');
const NOW = 1791360000; // 2026-10-07
const FUT = 1830000000; // 2027-12
const PAST = 1700000000;
const T = (...f) => f.join('\t');
let events = 0;
process.on('YOUTUBE_UPDATE', () => events++);
function throwsInvalid(fn) {
try { fn(); return false; } catch (e) { return e.code === 'INVALID_COOKIES' && /[a-zé]/.test(e.message); }
}
console.log('# Netscape');
{
const loggedIn = [
'# Netscape HTTP Cookie File',
'# commentaire',
'',
T('.youtube.com', 'TRUE', '/', 'TRUE', FUT, 'LOGIN_INFO', 'AAA'),
T('.youtube.com', 'TRUE', '/', 'TRUE', FUT, 'SAPISID', 'BBB'),
'#HttpOnly_' + T('.youtube.com', 'TRUE', '/', 'TRUE', FUT, '__Secure-3PSID', 'CCC'),
T('.youtube.com', 'TRUE', '/', 'FALSE', 0, 'PREF', 'f6=40000000'),
T('.youtube.com', 'TRUE', '/', 'TRUE', PAST, 'GPS', '1'),
T('.example.com', 'TRUE', '/', 'FALSE', FUT, 'tracker', 'x'),
T('accounts.google.com', 'FALSE', '/', 'TRUE', FUT, '__Host-GAPS', 'g'),
'ligne cassée sans tabulation',
T('.youtube.com', 'MAYBE', '/', 'TRUE', FUT, 'X', 'y'),
].join('\r\n');
const r = cookies.parseCookies(loggedIn, { now: NOW });
const names = r.cookies.map(c => c.name);
check('CRLF + en-tête : cookies valides lus', names.includes('LOGIN_INFO') && names.includes('SAPISID') && names.includes('PREF'), names);
check('#HttpOnly_ reconnu', r.cookies.find(c => c.name === '__Secure-3PSID')?.httpOnly === true);
check('cookie de session (0) gardé', r.cookies.find(c => c.name === 'PREF')?.expires === 0);
check('expiré retiré', !names.includes('GPS'));
check('domaine étranger retiré', !names.includes('tracker'));
check('google.com gardé (hostOnly)', r.cookies.find(c => c.name === '__Host-GAPS')?.includeSubdomains === false);
check('avertissements (lignes, domaines, expirés)', r.warnings.length === 3, r.warnings);
const a = cookies.analyze(r.cookies);
check('analyze : loggedIn', a.loggedIn === true);
check('analyze : jamais de valeur', !JSON.stringify(a).includes('AAA') && !JSON.stringify(a).includes('BBB'));
check('analyze : authNames', a.authNames.includes('LOGIN_INFO') && a.authNames.includes('SAPISID') && a.authNames.includes('__Secure-3PSID'), a.authNames);
check('analyze : earliestAuthExpiry ISO', a.earliestAuthExpiry === new Date(FUT * 1000).toISOString());
// Sans en-tête, LF, valeur vide, tabulations converties en espaces
const noHeader = [
T('.youtube.com', 'TRUE', '/', 'TRUE', FUT, 'LOGIN_INFO', 'AAA'),
'.youtube.com TRUE / TRUE ' + FUT + ' __Secure-3PAPISID ZZZ',
T('.youtube.com', 'TRUE', '/', 'TRUE', FUT, 'EMPTY', ''),
].join('\n');
const r2 = cookies.parseCookies(noHeader, { now: NOW });
check('sans en-tête + espaces au lieu de tabulations', r2.cookies.length === 3, r2.cookies.map(c => c.name));
check('valeur vide acceptée', r2.cookies.find(c => c.name === 'EMPTY')?.value === '');
check('loggedIn via __Secure-3PAPISID', cookies.analyze(r2.cookies).loggedIn === true);
// Règle yt-dlp : LOGIN_INFO seul ou SAPISID seul ne suffit pas
const onlySapisid = T('.youtube.com', 'TRUE', '/', 'TRUE', FUT, '__Secure-3PAPISID', 'x');
check('pas connecté sans LOGIN_INFO', cookies.analyze(cookies.parseCookies(onlySapisid, { now: NOW }).cookies).loggedIn === false);
const onlyLogin = T('.youtube.com', 'TRUE', '/', 'TRUE', FUT, 'LOGIN_INFO', 'x');
const aLogin = cookies.analyze(cookies.parseCookies(onlyLogin, { now: NOW }).cookies);
check('pas connecté sans SAPISID', aLogin.loggedIn === false && aLogin.missing.includes('SAPISID'));
// Cookies google.com seuls : ne comptent pas pour youtube.com
const googleOnly = [T('.google.com', 'TRUE', '/', 'TRUE', FUT, 'LOGIN_INFO', 'x'), T('.google.com', 'TRUE', '/', 'TRUE', FUT, 'SAPISID', 'x')].join('\n');
check('LOGIN_INFO sur google.com ne compte pas', cookies.analyze(cookies.parseCookies(googleOnly, { now: NOW }).cookies).loggedIn === false);
// Sérialisation : relue à l'identique, LF, en-tête
const text = cookies.serializeCookies(r.cookies);
check('sérialisation : en-tête Netscape + LF', text.startsWith('# Netscape HTTP Cookie File\n') && !text.includes('\r'));
const back = cookies.parseCookies(text, { now: NOW }).cookies;
check('sérialisation : aller-retour', JSON.stringify(back) === JSON.stringify(r.cookies));
}
console.log('# JSON');
{
const arr = [
{ domain: '.youtube.com', expirationDate: FUT + 0.512229, hostOnly: false, httpOnly: true, name: 'LOGIN_INFO', path: '/', sameSite: 'no_restriction', secure: true, session: false, storeId: '0', value: 'AAA', id: 1 },
{ domain: '.youtube.com', expirationDate: FUT + 0.9, hostOnly: false, httpOnly: false, name: 'SAPISID', path: '/', secure: true, session: false, value: 'BBB' },
{ domain: 'www.youtube.com', hostOnly: true, httpOnly: false, name: 'ST-xyz', path: '/', secure: false, session: true, value: 'S' },
{ domain: 'youtube.com', hostOnly: false, name: 'NODOT', path: '/', secure: true, expires: (FUT * 1000), value: 'ms' },
{ domain: '.youtube.com', name: 'OLD', expirationDate: PAST, value: 'x' },
{ domain: '.facebook.com', name: 'fb', expirationDate: FUT, value: 'x' },
{ nope: true },
];
const r = cookies.parseCookies(JSON.stringify(arr), { now: NOW });
const by = Object.fromEntries(r.cookies.map(c => [c.name, c]));
check('JSON : expirationDate décimal arrondi', by.LOGIN_INFO?.expires === FUT);
check('JSON : session -> 0', by['ST-xyz']?.expires === 0);
check('JSON : hostOnly -> sans sous-domaines', by['ST-xyz']?.includeSubdomains === false && by['ST-xyz']?.domain === 'www.youtube.com');
check('JSON : domaine non hostOnly sans point -> .youtube.com', by.NODOT?.domain === '.youtube.com' && by.NODOT?.includeSubdomains === true);
check('JSON : expires en millisecondes', by.NODOT?.expires === FUT);
check('JSON : httpOnly', by.LOGIN_INFO?.httpOnly === true && by.SAPISID?.httpOnly === false);
check('JSON : expiré, étranger et invalide retirés', !by.OLD && !by.fb && r.warnings.length === 3, r.warnings);
check('JSON : loggedIn', cookies.analyze(r.cookies).loggedIn === true);
check('JSON : hasSessionOnly faux (le cookie de session n\'est pas un cookie de connexion)', cookies.analyze(r.cookies).hasSessionOnly === false);
const wrapped = cookies.parseCookies(JSON.stringify({ url: 'https://www.youtube.com', cookies: arr.slice(0, 2) }), { now: NOW });
check('{cookies:[...]} accepté', wrapped.cookies.length === 2 && cookies.analyze(wrapped.cookies).loggedIn);
const withBom = cookies.parseCookies('' + JSON.stringify(arr.slice(0, 2)), { now: NOW });
check('BOM ignoré', withBom.cookies.length === 2);
const expiry = cookies.parseCookies(JSON.stringify([{ domain: '.youtube.com', name: 'A', value: 'b', expiry: FUT }]), { now: NOW });
check('champ expiry (Selenium)', expiry.cookies[0].expires === FUT);
const sessionAuth = cookies.parseCookies(JSON.stringify([{ domain: '.youtube.com', name: 'LOGIN_INFO', value: 'b', session: true }]), { now: NOW });
check('hasSessionOnly vrai pour un cookie de connexion de session', cookies.analyze(sessionAuth.cookies).hasSessionOnly === true);
}
console.log('# Contenus invalides');
check('vide', throwsInvalid(() => cookies.parseCookies(' ')));
check('non-chaîne', throwsInvalid(() => cookies.parseCookies(null)));
check('texte quelconque', throwsInvalid(() => cookies.parseCookies('bonjour\nceci n\'est pas un fichier de cookies')));
check('JSON tronqué', throwsInvalid(() => cookies.parseCookies('[{"domain": ".youtube.com", "name": ')));
check('JSON objet sans cookies', throwsInvalid(() => cookies.parseCookies('{"a": 1}')));
check('JSON liste vide', throwsInvalid(() => cookies.parseCookies('[]')));
check('HTML', throwsInvalid(() => cookies.parseCookies('<html><body>Login</body></html>')));
check('commentaires seuls', throwsInvalid(() => cookies.parseCookies('# Netscape HTTP Cookie File\n# rien\n')));
check('valeur avec retour à la ligne refusée', throwsInvalid(() => cookies.parseCookies(JSON.stringify([{ domain: '.youtube.com', name: 'A', value: 'x\ny' }]))));
console.log('# Stockage');
const goodText = [T('.youtube.com', 'TRUE', '/', 'TRUE', FUT, 'LOGIN_INFO', 'AAA'), T('.youtube.com', 'TRUE', '/', 'TRUE', FUT, 'SAPISID', 'BBB')].join('\n');
let threw = false;
try { cookies.importCookies(T('.google.com', 'TRUE', '/', 'TRUE', FUT, 'SID', 'x')); } catch (e) { threw = e.code === 'INVALID_COOKIES'; }
check('import refusé sans cookie youtube.com', threw && !fs.existsSync(__glob.YOUTUBE_COOKIES));
const before = events;
const res = cookies.importCookies(goodText, { source: 'paste', by: { id: '42', username: 'raphix' } });
check('import : statut valide', res.status.valid && res.status.loggedIn && res.status.count === 2 && res.status.source === 'paste', res.status);
check('import : importedBy', res.status.importedBy?.username === 'raphix');
check('import : YOUTUBE_UPDATE émis', events > before);
check('import : fichier maître LF + en-tête', fs.readFileSync(__glob.YOUTUBE_COOKIES, 'utf8').startsWith('# Netscape HTTP Cookie File\n'));
check('statut : jamais de valeur', !JSON.stringify(cookies.getStatus()).includes('AAA'));
const notLogged = cookies.importCookies(T('.youtube.com', 'TRUE', '/', 'TRUE', FUT, 'PREF', 'x'), { source: 'file' });
check('import non connecté : stocké + avertissement', notLogged.status.present && !notLogged.status.loggedIn && notLogged.warnings.some(w => /connexion/.test(w)), notLogged.warnings);
cookies.importCookies(goodText, { source: 'paste' });
check('markInvalid', cookies.markInvalid('test') === true && cookies.getStatus().valid === false && cookies.getStatus().invalidReason === 'test');
check('markInvalid idempotent', cookies.markInvalid('encore') === false && cookies.getStatus().invalidReason === 'test');
cookies.setLastCheck({ at: new Date().toISOString(), ok: true, loggedIn: true, premium: false, message: 'ok' });
check('vérification réussie : invalidation levée', cookies.getStatus().valid === true);
cookies.reload();
check('rechargement depuis le disque', cookies.getStatus().valid === true && cookies.getStatus().count === 2);
(async () => {
const copy = await cookies.acquireCopy();
check('acquireCopy : fichier identique au maître', fs.readFileSync(copy.path, 'utf8') === fs.readFileSync(__glob.YOUTUBE_COOKIES, 'utf8'));
check('acquireCopy : dossier chopin-ytc-', path.basename(path.dirname(copy.path)).startsWith('chopin-ytc-') && cookies.liveCopyCount() === 1);
copy.release(); copy.release();
check('release : dossier supprimé', !fs.existsSync(path.dirname(copy.path)) && cookies.liveCopyCount() === 0);
const del = cookies.deleteCookies();
check('deleteCookies', !del.present && !fs.existsSync(__glob.YOUTUBE_COOKIES));
check('acquireCopy sans cookies -> null', (await cookies.acquireCopy()) === null);
check('aucune copie temporaire restante', chopinTmpDirs().length === 0, chopinTmpDirs());
console.log('# Fichiers réels (lecture seule, analyse sans valeurs)');
const realTxt = path.join(BACKEND, 'data', 'cookies.txt');
const realJson = path.join(BACKEND, 'data', 'cookies.json');
const mtimes = () => [realTxt, realJson].map(f => fs.existsSync(f) ? fs.statSync(f).mtimeMs : null).join(',');
const mtimesBefore = mtimes();
for (const file of [realTxt, realJson]) {
if (!fs.existsSync(file)) { console.log(` (absent) ${path.basename(file)}`); continue; }
try {
const r = cookies.parseCookies(fs.readFileSync(file, 'utf8'));
console.log(` ${path.basename(file)} :`, JSON.stringify(cookies.analyze(r.cookies)), '| avertissements :', JSON.stringify(r.warnings));
} catch (e) {
console.log(` ${path.basename(file)} : erreur ${e.code} ${e.message}`);
}
}
// Migration : les anciens fichiers réels servent de source, la destination est le dossier isolé
cookies.reload();
__glob.COOKIES = realTxt;
__glob.COOKIES_JSON = realJson;
const mig = cookies.migrateLegacy();
const st = cookies.getStatus();
console.log(' migration :', JSON.stringify(mig), '| statut :', JSON.stringify({ present: st.present, loggedIn: st.loggedIn, valid: st.valid, source: st.source, count: st.count, authNames: st.authNames, missing: st.missing, earliestAuthExpiry: st.earliestAuthExpiry }));
check('migration : fichiers d\'origine non modifiés', mtimes() === mtimesBefore);
check('migration : pas de seconde migration', cookies.migrateLegacy().migrated === false);
fs.rmSync(dataDir, { recursive: true, force: true });
done();
})();