Cookie Exporter | Netscape, JSON, Header

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

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

You will need to install an extension such as Tampermonkey to install this script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==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();
	}
})();