/** * Cookie extraction + Netscape format conversion. * Direct port of GS extension/lib/cookies.js. */ async function extractCookiesForPlatform(platformKey) { const platform = PLATFORMS[platformKey]; if (!platform) throw new Error(`Unknown platform: ${platformKey}`); const all = []; for (const domain of platform.domains) { try { const cookies = await browser.cookies.getAll({ domain }); all.push(...cookies); } catch (e) { console.warn(`cookies.getAll failed for ${domain}:`, e); } } return deduplicateCookies(all); } function deduplicateCookies(cookies) { const seen = new Map(); for (const c of cookies) { const key = `${c.name}|${c.domain}|${c.path}`; if (!seen.has(key)) { seen.set(key, c); } else { const existing = seen.get(key); if (c.expirationDate && (!existing.expirationDate || c.expirationDate > existing.expirationDate)) { seen.set(key, c); } } } return Array.from(seen.values()); } function toNetscapeFormat(cookies) { const lines = ['# Netscape HTTP Cookie File']; for (const c of cookies) { // Preserve the browser's actual scope. Earlier versions rewrote // every cookie to a leading-dot subdomain-wide form, which broke // gallery-dl's HF extractor: its `cookies.get(name, // domain="www.hentai-foundry.com")` does EXACT domain matching and // missed host-only PHPSESSID rewritten to `.hentai-foundry.com`. // Operator-flagged 2026-05-27. Backend `_augment_cookies` covers // the already-stored cookies; this fix is forward-compat for fresh // captures. // // Cookie storage semantics (Firefox): // c.hostOnly === true → cookie set without a Domain= attribute; // applies to the exact host only. // c.hostOnly === false → cookie set with Domain=X; applies to // that domain and its subdomains. // // Netscape format: // leading-dot domain + TRUE flag → subdomain-wide // bare-host domain + FALSE flag → host-only const hostOnly = c.hostOnly === true; let domain = c.domain; if (!hostOnly && !domain.startsWith('.')) { domain = '.' + domain; } const subdomainFlag = hostOnly ? 'FALSE' : 'TRUE'; const secure = c.secure ? 'TRUE' : 'FALSE'; const expiration = c.expirationDate ? Math.floor(c.expirationDate) : 0; lines.push([domain, subdomainFlag, c.path || '/', secure, String(expiration), c.name, c.value].join('\t')); } return lines.join('\n'); } async function getCookieCount(platformKey) { try { return (await extractCookiesForPlatform(platformKey)).length; } catch { return 0; } } /** * Verify cookies are live by hitting an authenticated endpoint with the * browser's current cookie jar. Returns: * { ok: true, status } — verified * { ok: false, status, reason } — endpoint said we're not logged in * { ok: null, reason } — no verify config for this platform; caller * should treat as "verify not available, * proceed with upload" * * Implementation note: extensions with `host_permissions` for the target * domain get the user's cookies auto-attached to fetch() — same set * gallery-dl will later use on the backend. */ async function verifyCookiesForPlatform(platformKey) { const platform = PLATFORMS[platformKey]; if (!platform) return { ok: false, reason: `Unknown platform: ${platformKey}` }; if (!platform.verify) return { ok: null, reason: 'verify-not-configured' }; const { url, method, okStatuses } = platform.verify; let resp; try { resp = await fetch(url, { method, credentials: 'include', cache: 'no-store' }); } catch (e) { return { ok: false, reason: `Verify request failed: ${e.message}` }; } if (okStatuses.includes(resp.status)) { return { ok: true, status: resp.status }; } return { ok: false, status: resp.status, reason: `${url} returned HTTP ${resp.status} — session looks stale or logged out`, }; }