Cookie Exporter | Netscape, JSON, Header

Export cookies in Netscape, JSON, or header format for curl, wget, yt-dlp — replaces Get cookies.txt LOCALLY

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Cookie Exporter | Netscape, JSON, Header
// @namespace    https://greasyfork.org/en/users/1462137-piknockyou
// @version      3.0.9
// @author       Piknockyou
// @license      AGPL-3.0
// @description  Export cookies in Netscape, JSON, or header format for curl, wget, yt-dlp — replaces Get cookies.txt LOCALLY
// @icon         https://www.google.com/s2/favicons?sz=64&domain=github.com
// @grant        GM_cookie
// @grant        GM.cookie
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @match        *://*/*
// @noframes
// @run-at       document-idle
// ==/UserScript==

(() => {
	// ═══════════════════════════════════════════════════════════════
	// COOKIE API DETECTION
	// ═══════════════════════════════════════════════════════════════

	function hasCookieApi() {
		return (
			(typeof GM_cookie === "object" && GM_cookie?.list) ||
			typeof GM_cookie === "function"
		);
	}

	// ═══════════════════════════════════════════════════════════════
	// FETCH COOKIES
	// ═══════════════════════════════════════════════════════════════

	function gmCookieList(details) {
		return new Promise((resolve, reject) => {
			if (typeof GM_cookie === "object" && GM_cookie?.list) {
				GM_cookie.list(details, (cookies, err) => {
					if (err) reject(err);
					else resolve(cookies || []);
				});
			} else if (typeof GM_cookie === "function") {
				GM_cookie("list", details, (cookies, err) => {
					if (err) reject(err);
					else resolve(cookies || []);
				});
			} else {
				reject(new Error("No callback-style GM_cookie API"));
			}
		});
	}

	async function fetchCookies(hostname) {
		for (const details of [
			{ url: `https://${hostname}/` },
			{ domain: hostname },
			{},
		]) {
			try {
				const cookies = await gmCookieList(details);
				console.log(
					"[Cookie Exporter] OK:",
					cookies.length,
					JSON.stringify(details),
				);
				return cookies;
			} catch (err) {
				console.error(
					"[Cookie Exporter] attempt",
					JSON.stringify(details),
					err,
				);
			}
		}
		throw new Error("All cookie queries failed");
	}

	async function fetchAllCookies() {
		try {
			const cookies = await gmCookieList({});
			console.log("[Cookie Exporter] All cookies:", cookies.length);
			return cookies;
		} catch (err) {
			console.error("[Cookie Exporter] All cookies failed:", err);
			throw new Error("Failed to fetch all cookies");
		}
	}

	// ═══════════════════════════════════════════════════════════════
	// FORMATS
	// ═══════════════════════════════════════════════════════════════

	const FORMATS = {
		netscape: {
			ext: "txt",
			mime: "text/plain",
			serialize(cookies) {
				const lines = cookies.map((c) => {
					const domain = c.domain || "";
					const flag = domain.startsWith(".") ? "TRUE" : "FALSE";
					const path = c.path || "/";
					const secure = c.secure ? "TRUE" : "FALSE";
					const expiry = c.expirationDate ? Math.floor(c.expirationDate) : 0;
					return `${domain}\t${flag}\t${path}\t${secure}\t${expiry}\t${c.name}\t${c.value}`;
				});
				return [
					"# Netscape HTTP Cookie File",
					"# https://curl.haxx.se/rfc/cookie_spec.html",
					"# This is a generated file! Do not edit.",
					"",
					...lines,
					"",
				].join("\n");
			},
		},
		json: {
			ext: "json",
			mime: "application/json",
			serialize(cookies) {
				return JSON.stringify(cookies, null, 2);
			},
		},
		header: {
			ext: "txt",
			mime: "text/plain",
			serialize(cookies) {
				return cookies.map(({ name, value }) => `${name}=${value}`).join(";\n");
			},
		},
	};

	const FORMAT_KEYS = Object.keys(FORMATS);

	// ═══════════════════════════════════════════════════════════════
	// FORMAT PERSISTENCE
	// ═══════════════════════════════════════════════════════════════

	function getSelectedFormat() {
		return (
			(typeof GM_getValue === "function" && GM_getValue("exportFormat")) ||
			"netscape"
		);
	}

	function setSelectedFormat(key) {
		if (typeof GM_setValue === "function") {
			GM_setValue("exportFormat", key);
		}
	}

	// ═══════════════════════════════════════════════════════════════
	// DOWNLOAD
	// ═══════════════════════════════════════════════════════════════

	function domDownload(content, filename, mime) {
		const blob = new Blob([content], { type: mime });
		const url = URL.createObjectURL(blob);

		const a = document.createElement("a");
		a.href = url;
		a.download = filename;
		a.style.display = "none";
		a.rel = "noopener";

		(document.body || document.documentElement).appendChild(a);

		a.addEventListener(
			"click",
			(e) => {
				e.stopPropagation();
				if (typeof e.stopImmediatePropagation === "function") {
					e.stopImmediatePropagation();
				}
			},
			{ capture: true, once: true },
		);

		a.click();
		a.remove();

		setTimeout(() => URL.revokeObjectURL(url), 10000);
	}

	function downloadFile(content, filename, mime = "text/plain") {
		return new Promise((resolve) => {
			domDownload(content, filename, mime);
			resolve();
		});
	}

	// ═══════════════════════════════════════════════════════════════
	// NOTIFICATION
	// ═══════════════════════════════════════════════════════════════

	function notify(msg, color = "#4CAF50") {
		const el = document.createElement("div");
		Object.assign(el.style, {
			position: "fixed",
			bottom: "20px",
			right: "20px",
			padding: "10px 18px",
			background: color,
			color: "#fff",
			borderRadius: "6px",
			fontSize: "13px",
			fontWeight: "500",
			fontFamily: "sans-serif",
			zIndex: "2147483647",
			boxShadow: "0 3px 12px rgba(0,0,0,0.3)",
			transition: "opacity 0.3s",
		});
		el.textContent = msg;
		document.body.appendChild(el);
		setTimeout(() => {
			el.style.opacity = "0";
			setTimeout(() => el.remove(), 400);
		}, 2500);
	}

	// ═══════════════════════════════════════════════════════════════
	// BANNER (when API unavailable)
	// ═══════════════════════════════════════════════════════════════

	function showBanner() {
		const host = document.createElement("div");
		Object.assign(host.style, {
			position: "fixed",
			top: "0",
			left: "0",
			width: "100vw",
			height: "100vh",
			zIndex: "2147483647",
		});
		const shadow = host.attachShadow({ mode: "closed" });

		const style = document.createElement("style");
		style.textContent = `
			:host { all: initial; }
			.overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; padding: 20px; font-family: sans-serif; }
			.box { background: #1e1e1e; border: 1px solid #444; border-radius: 12px; max-width: 520px; width: 100%; padding: 28px 32px; color: #e0e0e0; }
			.title { font-size: 18px; font-weight: 700; text-align: center; margin-bottom: 14px; color: #f44336; }
			.desc { font-size: 13px; line-height: 1.6; color: #ccc; margin-bottom: 14px; }
			.sec { background: #2a2a2a; border: 1px solid #3a3a3a; border-radius: 8px; padding: 12px 14px; margin: 10px 0; }
			.sec-title { font-size: 13px; font-weight: 600; color: #4CAF50; margin-bottom: 6px; }
			.steps { margin: 0; padding-left: 18px; font-size: 12px; line-height: 1.7; color: #ccc; }
			.btn { display: block; width: 100%; padding: 12px; background: #f44336; color: #fff; border: none; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer; margin-top: 14px; }
		`;
		shadow.appendChild(style);

		const overlay = document.createElement("div");
		overlay.className = "overlay";
		const box = document.createElement("div");
		box.className = "box";

		box.innerHTML = `
			<div class="title">Cookie API Not Available</div>
			<div class="desc">Your userscript manager doesn't expose the GM_cookie API.</div>
			<div class="sec"><div class="sec-title">Violentmonkey</div>
				<ol class="steps"><li>Violentmonkey → Settings → Advanced → Script settings</li><li>Enable "Allow GM_cookie to access HTTP-only cookies"</li><li>Also check it in this script's Settings tab</li></ol></div>
			<div class="sec"><div class="sec-title">Tampermonkey</div>
				<ol class="steps"><li>Tampermonkey → Settings → Config mode: Advanced</li><li>Security → "Allow scripts to access cookies" → All</li></ol></div>
			<button class="btn">Got it</button>
		`;

		box.querySelector(".btn").onclick = () => host.remove();
		overlay.onclick = (e) => {
			if (e.target === overlay) host.remove();
		};
		overlay.appendChild(box);
		shadow.appendChild(overlay);
		document.body.appendChild(host);
	}

	// ═══════════════════════════════════════════════════════════════
	// MAIN EXPORT
	// ═══════════════════════════════════════════════════════════════

	async function exportCookies(scope = "current") {
		if (!hasCookieApi()) {
			console.log("[Cookie Exporter] no API");
			showBanner();
			return;
		}

		const formatKey = getSelectedFormat();
		const fmt = FORMATS[formatKey];
		if (!fmt) {
			notify(`Unknown format: ${formatKey}`, "#f44336");
			return;
		}

		try {
			const cookies =
				scope === "all"
					? await fetchAllCookies()
					: await fetchCookies(window.location.hostname);

			if (!cookies || cookies.length === 0) {
				notify(
					`No cookies${scope === "all" ? "" : ` for ${window.location.hostname}`}`,
					"#FF9800",
				);
				return;
			}

			const text = fmt.serialize(cookies);
			const name =
				scope === "all" ? "cookies" : `${window.location.hostname}_cookies`;
			await downloadFile(text, `${name}.${fmt.ext}`, fmt.mime);
			notify(`Exported ${cookies.length} cookies as ${formatKey}`);
		} catch (e) {
			console.error("[Cookie Exporter] FAILED:", e);
			notify(`${e.message}`, "#f44336");
		}
	}

	// ═══════════════════════════════════════════════════════════════
	// MENU COMMANDS
	// ═══════════════════════════════════════════════════════════════

	if (typeof GM_registerMenuCommand === "function") {
		GM_registerMenuCommand("Export Cookies", () => exportCookies("current"));
		GM_registerMenuCommand("Export All Cookies", () => exportCookies("all"));

		let formatMenuId = null;
		const updateFormatMenu = () => {
			const key = getSelectedFormat();
			formatMenuId = GM_registerMenuCommand(
				`Format: ${key} (click to change)`,
				() => {
					const next =
						FORMAT_KEYS[(FORMAT_KEYS.indexOf(key) + 1) % FORMAT_KEYS.length];
					setSelectedFormat(next);
					notify(`Format: ${next}`);
					updateFormatMenu();
				},
				{ id: formatMenuId, autoClose: false },
			);
		};
		updateFormatMenu();
	}
})();