Cookie Exporter | Netscape, JSON, Header

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

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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();
	}
})();