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,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();
|
||||
})();
|
||||
Reference in new issue
Block a user