init
This commit is contained in:
205
.opencode/skills/chrome-devtools/scripts/import-cookies.js
Normal file
205
.opencode/skills/chrome-devtools/scripts/import-cookies.js
Normal file
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Import cookies from JSON file exported by browser extensions
|
||||
* Supports: EditThisCookie, Cookie-Editor, Netscape (txt) formats
|
||||
*
|
||||
* Usage:
|
||||
* node import-cookies.js --file ./cookies.json --url https://example.com
|
||||
* node import-cookies.js --file ./cookies.txt --format netscape --url https://example.com
|
||||
*
|
||||
* Workflow:
|
||||
* 1. Install "Cookie-Editor" or "EditThisCookie" Chrome extension
|
||||
* 2. Navigate to target site and log in manually
|
||||
* 3. Export cookies as JSON via extension
|
||||
* 4. Run this script to import into puppeteer session
|
||||
* 5. Use other scripts (screenshot, navigate) with authenticated session
|
||||
*/
|
||||
import fs from 'fs';
|
||||
import { getBrowser, getPage, closeBrowser, disconnectBrowser, parseArgs, outputJSON, outputError, saveAuthSession } from './lib/browser.js';
|
||||
|
||||
/**
|
||||
* Parse cookies from EditThisCookie/Cookie-Editor JSON format
|
||||
* @param {Array} cookies - Array of cookie objects
|
||||
* @returns {Array} - Normalized cookie array for Puppeteer
|
||||
*/
|
||||
function parseJsonCookies(cookies) {
|
||||
return cookies.map(cookie => {
|
||||
// Handle different property names from various extensions
|
||||
const normalized = {
|
||||
name: cookie.name,
|
||||
value: cookie.value,
|
||||
domain: cookie.domain,
|
||||
path: cookie.path || '/',
|
||||
httpOnly: cookie.httpOnly ?? false,
|
||||
secure: cookie.secure ?? false,
|
||||
sameSite: cookie.sameSite || 'Lax'
|
||||
};
|
||||
|
||||
// Handle expiration (different extensions use different names)
|
||||
if (cookie.expirationDate) {
|
||||
normalized.expires = cookie.expirationDate;
|
||||
} else if (cookie.expires) {
|
||||
normalized.expires = typeof cookie.expires === 'number'
|
||||
? cookie.expires
|
||||
: new Date(cookie.expires).getTime() / 1000;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Netscape cookie file format (used by curl, wget, etc.)
|
||||
* Format: domain\tflags\tpath\tsecure\texpiration\tname\tvalue
|
||||
* @param {string} content - Netscape format cookie file content
|
||||
* @returns {Array} - Normalized cookie array for Puppeteer
|
||||
*/
|
||||
function parseNetscapeCookies(content) {
|
||||
const cookies = [];
|
||||
const lines = content.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
// Skip comments and empty lines
|
||||
if (line.startsWith('#') || line.trim() === '') continue;
|
||||
|
||||
const parts = line.split('\t');
|
||||
if (parts.length < 7) continue;
|
||||
|
||||
const [domain, , path, secure, expires, name, value] = parts;
|
||||
|
||||
cookies.push({
|
||||
name: name.trim(),
|
||||
value: value.trim(),
|
||||
domain: domain.trim(),
|
||||
path: path.trim() || '/',
|
||||
secure: secure.toUpperCase() === 'TRUE',
|
||||
httpOnly: false, // Netscape format doesn't include httpOnly
|
||||
expires: parseInt(expires, 10) || undefined,
|
||||
sameSite: 'Lax'
|
||||
});
|
||||
}
|
||||
|
||||
return cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect cookie file format from content
|
||||
* @param {string} content - File content
|
||||
* @returns {string} - 'json' or 'netscape'
|
||||
*/
|
||||
function detectFormat(content) {
|
||||
const trimmed = content.trim();
|
||||
if (trimmed.startsWith('[') || trimmed.startsWith('{')) {
|
||||
return 'json';
|
||||
}
|
||||
return 'netscape';
|
||||
}
|
||||
|
||||
async function importCookies() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
|
||||
if (!args.file) {
|
||||
outputError(new Error('--file is required (path to cookies file)'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.url) {
|
||||
outputError(new Error('--url is required (target URL to apply cookies)'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read cookie file
|
||||
let fileContent;
|
||||
try {
|
||||
fileContent = fs.readFileSync(args.file, 'utf8');
|
||||
} catch (e) {
|
||||
outputError(new Error(`Failed to read cookie file: ${e.message}`));
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse cookies based on format
|
||||
const format = args.format || detectFormat(fileContent);
|
||||
let cookies;
|
||||
|
||||
try {
|
||||
if (format === 'json') {
|
||||
const parsed = JSON.parse(fileContent);
|
||||
// Handle both array and object with cookies property
|
||||
const cookieArray = Array.isArray(parsed) ? parsed : (parsed.cookies || []);
|
||||
cookies = parseJsonCookies(cookieArray);
|
||||
} else {
|
||||
cookies = parseNetscapeCookies(fileContent);
|
||||
}
|
||||
} catch (e) {
|
||||
outputError(new Error(`Failed to parse cookies (${format}): ${e.message}`));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cookies.length === 0) {
|
||||
outputError(new Error('No valid cookies found in file'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const browser = await getBrowser({
|
||||
headless: args.headless
|
||||
});
|
||||
|
||||
const page = await getPage(browser);
|
||||
|
||||
// Navigate to URL first to establish domain context
|
||||
await page.goto(args.url, {
|
||||
waitUntil: args['wait-until'] || 'networkidle2',
|
||||
timeout: parseInt(args.timeout || '30000')
|
||||
});
|
||||
|
||||
// Filter cookies by domain if --strict-domain is set
|
||||
let cookiesToApply = cookies;
|
||||
if (args['strict-domain']) {
|
||||
const urlDomain = new URL(args.url).hostname;
|
||||
cookiesToApply = cookies.filter(c => {
|
||||
const cookieDomain = c.domain.startsWith('.') ? c.domain.slice(1) : c.domain;
|
||||
return urlDomain.endsWith(cookieDomain);
|
||||
});
|
||||
}
|
||||
|
||||
// Apply cookies
|
||||
await page.setCookie(...cookiesToApply);
|
||||
|
||||
// Save to auth session for persistence
|
||||
saveAuthSession({ cookies: cookiesToApply });
|
||||
|
||||
// Reload to apply cookies if --reload is set
|
||||
if (args.reload === 'true') {
|
||||
await page.reload({ waitUntil: 'networkidle2' });
|
||||
}
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
file: args.file,
|
||||
format,
|
||||
url: args.url,
|
||||
imported: {
|
||||
total: cookiesToApply.length,
|
||||
names: cookiesToApply.map(c => c.name)
|
||||
},
|
||||
persisted: true,
|
||||
finalUrl: page.url(),
|
||||
title: await page.title()
|
||||
};
|
||||
|
||||
outputJSON(result);
|
||||
|
||||
// Default: disconnect to keep browser running
|
||||
if (args.close === 'true') {
|
||||
await closeBrowser();
|
||||
} else {
|
||||
await disconnectBrowser();
|
||||
}
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
outputError(error);
|
||||
}
|
||||
}
|
||||
|
||||
importCookies();
|
||||
Reference in New Issue
Block a user