#!/usr/bin/env node /** * Extract cookies from Chrome via CDP and write to Netscape cookies.txt format. * * This script launches Chrome with a given user data directory, connects via CDP, * extracts all cookies, and writes them to a cookies.txt file in Netscape format. * * Usage: * CHROME_USER_DATA_DIR=/path/to/profile COOKIES_OUTPUT_FILE=/path/to/cookies.txt node extract_cookies.js * * Environment variables: * CHROME_USER_DATA_DIR: Path to Chrome user data directory (required) * COOKIES_OUTPUT_FILE: Path to output cookies.txt file (required) * CHROME_HEADLESS: Run in headless mode (default: true) * NODE_MODULES_DIR: Path to node_modules for module resolution */ // Add NODE_MODULES_DIR to module resolution paths if set if (process.env.NODE_MODULES_DIR) { module.paths.unshift(process.env.NODE_MODULES_DIR); } const fs = require('fs'); const path = require('path'); const { findChromium, launchChromium, killChrome, getEnv, } = require('./chrome_utils.js'); /** * Convert a cookie object to Netscape cookies.txt format line. * * Format: domain includeSubdomains path secure expiry name value * * @param {Object} cookie - CDP cookie object * @returns {string} - Netscape format cookie line */ function cookieToNetscape(cookie) { // Domain: prefix with . for domain cookies (not host-only) let domain = cookie.domain; if (!domain.startsWith('.') && !cookie.hostOnly) { domain = '.' + domain; } // Include subdomains: TRUE if domain cookie (starts with .) const includeSubdomains = domain.startsWith('.') ? 'TRUE' : 'FALSE'; // Path const cookiePath = cookie.path || '/'; // Secure flag const secure = cookie.secure ? 'TRUE' : 'FALSE'; // Expiry timestamp (0 for session cookies) let expiry = '0'; if (cookie.expires && cookie.expires > 0) { // CDP returns expiry in seconds since epoch expiry = Math.floor(cookie.expires).toString(); } // Name and value const name = cookie.name; const value = cookie.value; return `${domain}\t${includeSubdomains}\t${cookiePath}\t${secure}\t${expiry}\t${name}\t${value}`; } /** * Write cookies to Netscape cookies.txt format file. * * @param {Array} cookies - Array of CDP cookie objects * @param {string} outputPath - Path to output file */ function writeCookiesFile(cookies, outputPath) { const lines = [ '# Netscape HTTP Cookie File', '# https://curl.se/docs/http-cookies.html', '# This file was generated by ArchiveBox persona cookie extraction', '#', '# Format: domain\\tincludeSubdomains\\tpath\\tsecure\\texpiry\\tname\\tvalue', '', ]; for (const cookie of cookies) { lines.push(cookieToNetscape(cookie)); } fs.writeFileSync(outputPath, lines.join('\n') + '\n'); } async function main() { const userDataDir = getEnv('CHROME_USER_DATA_DIR'); const outputFile = getEnv('COOKIES_OUTPUT_FILE'); if (!userDataDir) { console.error('ERROR: CHROME_USER_DATA_DIR environment variable is required'); process.exit(1); } if (!outputFile) { console.error('ERROR: COOKIES_OUTPUT_FILE environment variable is required'); process.exit(1); } if (!fs.existsSync(userDataDir)) { console.error(`ERROR: User data directory does not exist: ${userDataDir}`); process.exit(1); } const binary = findChromium(); if (!binary) { console.error('ERROR: Chromium binary not found'); process.exit(1); } console.error(`[*] Extracting cookies from: ${userDataDir}`); console.error(`[*] Output file: ${outputFile}`); console.error(`[*] Using browser: ${binary}`); // Create a temporary output directory for Chrome files const outputDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'chrome-cookies-')); let chromePid = null; try { // Launch Chrome with the user data directory const result = await launchChromium({ binary, outputDir, userDataDir, headless: true, killZombies: false, // Don't kill other Chrome instances }); if (!result.success) { console.error(`ERROR: Failed to launch Chrome: ${result.error}`); process.exit(1); } chromePid = result.pid; const cdpUrl = result.cdpUrl; const port = result.port; console.error(`[*] Chrome launched (PID: ${chromePid})`); console.error(`[*] CDP URL: ${cdpUrl}`); // Connect to CDP and get cookies const http = require('http'); // Use CDP directly via HTTP to get all cookies const getCookies = () => { return new Promise((resolve, reject) => { const req = http.request( { hostname: '127.0.0.1', port: port, path: '/json/list', method: 'GET', }, (res) => { let data = ''; res.on('data', (chunk) => (data += chunk)); res.on('end', () => { try { const targets = JSON.parse(data); // Find a page target const pageTarget = targets.find(t => t.type === 'page') || targets[0]; if (!pageTarget) { reject(new Error('No page target found')); return; } // Connect via WebSocket and send CDP command const WebSocket = require('ws'); const ws = new WebSocket(pageTarget.webSocketDebuggerUrl); ws.on('open', () => { ws.send(JSON.stringify({ id: 1, method: 'Network.getAllCookies', })); }); ws.on('message', (message) => { const response = JSON.parse(message); if (response.id === 1) { ws.close(); if (response.result && response.result.cookies) { resolve(response.result.cookies); } else { reject(new Error('Failed to get cookies: ' + JSON.stringify(response))); } } }); ws.on('error', (err) => { reject(err); }); } catch (e) { reject(e); } }); } ); req.on('error', reject); req.end(); }); }; // Wait a moment for the browser to fully initialize await new Promise(r => setTimeout(r, 2000)); console.error('[*] Fetching cookies via CDP...'); const cookies = await getCookies(); console.error(`[+] Retrieved ${cookies.length} cookies`); // Write cookies to file writeCookiesFile(cookies, outputFile); console.error(`[+] Wrote cookies to: ${outputFile}`); // Clean up await killChrome(chromePid, outputDir); chromePid = null; // Remove temp directory fs.rmSync(outputDir, { recursive: true, force: true }); console.error('[+] Cookie extraction complete'); process.exit(0); } catch (error) { console.error(`ERROR: ${error.message}`); // Clean up on error if (chromePid) { await killChrome(chromePid, outputDir); } try { fs.rmSync(outputDir, { recursive: true, force: true }); } catch (e) {} process.exit(1); } } main().catch((e) => { console.error(`Fatal error: ${e.message}`); process.exit(1); });