Cookie Exporter | Netscape, JSON, Header

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

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

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