Instantly share code, notes, and snippets.
Last active
August 11, 2026 03:07
-
Star
1
(1)
You must be signed in to star a gist -
Fork
1
(1)
You must be signed in to fork a gist
-
-
Save JavaGT/9f32ff3d4772f0d09dcc886acb2de540 to your computer and use it in GitHub Desktop.
Get cookies.txt LOCALLY - Userscript port. Export cookies in Netscape/JSON/Header format. Safari-compatible.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Get cookies.txt LOCALLY | |
| // @namespace https://github.com/kairi003/Get-cookies.txt-LOCALLY | |
| // @version 0.2.0 | |
| // @description Get cookies.txt, NEVER send information outside (userscript port). Supports Safari via Userscripts app. | |
| // @author kairi003 | |
| // @match *://*/* | |
| // @grant GM_download | |
| // @grant GM_notification | |
| // @grant GM_setClipboard | |
| // @grant GM_addStyle | |
| // @grant GM_registerMenuCommand | |
| // @grant GM_cookie | |
| // @run-at document-idle | |
| // @license MIT | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| const COOKIE_PANEL_ID = 'gctl-cookie-panel'; | |
| let isTampermonkey = false; | |
| try { | |
| isTampermonkey = typeof GM_info !== 'undefined' && GM_info.scriptHandler === 'Tampermonkey'; | |
| } catch (e) {} | |
| let gmCookieAvailable = false; | |
| if (isTampermonkey) { | |
| try { | |
| gmCookieAvailable = typeof GM_cookie !== 'undefined' && typeof GM_cookie.list === 'function'; | |
| } catch (e) {} | |
| } | |
| function formatDate(ts) { | |
| if (!ts) return '0'; | |
| return Math.floor(ts).toString(); | |
| } | |
| function cookiesToNetscapeTable(cookies) { | |
| return cookies.map(c => { | |
| const domain = c.domain || location.hostname; | |
| const includeSub = domain.startsWith('.'); | |
| const path = c.path || '/'; | |
| const secure = c.secure || false; | |
| const expiry = formatDate(c.expirationDate); | |
| const name = c.name || ''; | |
| const value = c.value || ''; | |
| return [domain, includeSub ? 'TRUE' : 'FALSE', path, secure ? 'TRUE' : 'FALSE', expiry, name, value]; | |
| }); | |
| } | |
| function serializeNetscape(cookies) { | |
| const rows = cookiesToNetscapeTable(cookies); | |
| return [ | |
| '# Netscape HTTP Cookie File', | |
| '# https://curl.haxx.se/rfc/cookie_spec.html', | |
| '# This is a generated file! Do not edit.', | |
| '', | |
| ...rows.map(r => r.join('\t')), | |
| '', | |
| ].join('\n'); | |
| } | |
| function serializeJson(cookies) { | |
| return JSON.stringify(cookies, null, 2); | |
| } | |
| function serializeHeader(cookies) { | |
| return cookies.map(c => `${c.name}=${c.value};`).join(' '); | |
| } | |
| const FORMATS = { | |
| netscape: { ext: '.txt', mimeType: 'text/plain', serializer: serializeNetscape }, | |
| json: { ext: '.json', mimeType: 'application/json', serializer: serializeJson }, | |
| header: { ext: '.txt', mimeType: 'text/plain', serializer: serializeHeader }, | |
| }; | |
| function parseDocumentCookies() { | |
| const raw = document.cookie; | |
| const pairs = raw ? raw.split(/;\s*/) : []; | |
| const isSecure = location.protocol === 'https:'; | |
| return pairs.map(pair => { | |
| const eqIdx = pair.indexOf('='); | |
| const name = eqIdx > -1 ? pair.slice(0, eqIdx).trim() : pair.trim(); | |
| const value = eqIdx > -1 ? pair.slice(eqIdx + 1).trim() : ''; | |
| return { | |
| domain: location.hostname, | |
| path: '/', | |
| secure: isSecure, | |
| httpOnly: false, | |
| name, | |
| value, | |
| expirationDate: 0, | |
| session: true, | |
| sameSite: 'unspecified', | |
| }; | |
| }); | |
| } | |
| function cookiesOrFallback(gmCookies) { | |
| if (gmCookies && gmCookies.length > 0) return gmCookies; | |
| return parseDocumentCookies(); | |
| } | |
| function readCookies() { | |
| return new Promise(resolve => { | |
| if (!gmCookieAvailable) { | |
| resolve(parseDocumentCookies()); | |
| return; | |
| } | |
| let done = false; | |
| const finish = (result) => { | |
| if (done) return; | |
| done = true; | |
| resolve(result); | |
| }; | |
| try { | |
| GM_cookie.list({ url: location.href }, cookies => { | |
| finish(cookiesOrFallback(cookies)); | |
| }); | |
| setTimeout(() => finish(parseDocumentCookies()), 300); | |
| } catch (e) { | |
| finish(parseDocumentCookies()); | |
| } | |
| }); | |
| } | |
| function readAllCookies() { | |
| return new Promise(resolve => { | |
| if (!gmCookieAvailable) { | |
| resolve(parseDocumentCookies()); | |
| return; | |
| } | |
| let done = false; | |
| const finish = (result) => { | |
| if (done) return; | |
| done = true; | |
| resolve(result); | |
| }; | |
| try { | |
| GM_cookie.list({}, cookies => { | |
| finish(cookiesOrFallback(cookies)); | |
| }); | |
| setTimeout(() => finish(parseDocumentCookies()), 300); | |
| } catch (e) { | |
| finish(parseDocumentCookies()); | |
| } | |
| }); | |
| } | |
| function panelExists() { | |
| return !!document.getElementById(COOKIE_PANEL_ID); | |
| } | |
| function removePanel() { | |
| const panel = document.getElementById(COOKIE_PANEL_ID); | |
| if (panel) panel.remove(); | |
| } | |
| function showNotification(title, text) { | |
| try { | |
| GM_notification({ title, text }); | |
| } catch (e) { | |
| // silently fail | |
| } | |
| } | |
| function downloadFile(text, filename, format, saveAs) { | |
| const blob = new Blob([text], { type: format.mimeType }); | |
| const url = URL.createObjectURL(blob); | |
| try { | |
| GM_download({ url, name: filename + format.ext, saveAs }); | |
| } catch (e) { | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = filename + format.ext; | |
| a.click(); | |
| } | |
| setTimeout(() => URL.revokeObjectURL(url), 10000); | |
| } | |
| function copyToClipboard(text) { | |
| try { | |
| GM_setClipboard(text); | |
| } catch (e) { | |
| navigator.clipboard.writeText(text).catch(() => {}); | |
| } | |
| } | |
| async function handleExport(saveAs, allCookies) { | |
| const select = document.getElementById('gctl-format-select'); | |
| const formatKey = select ? select.value : 'netscape'; | |
| const fmt = FORMATS[formatKey] || FORMATS.netscape; | |
| const cookies = allCookies ? await readAllCookies() : await readCookies(); | |
| const text = fmt.serializer(cookies); | |
| const host = location.hostname; | |
| const filename = allCookies ? 'cookies' : `${host}_cookies`; | |
| downloadFile(text, filename, fmt, saveAs); | |
| } | |
| function buildPanelUI() { | |
| const panel = document.createElement('div'); | |
| panel.id = COOKIE_PANEL_ID; | |
| panel.innerHTML = ` | |
| <div class="gctl-overlay"></div> | |
| <div class="gctl-panel"> | |
| <div class="gctl-header"> | |
| <h2 class="gctl-title">Get cookies.txt for <span class="gctl-url">${location.href}</span></h2> | |
| <button class="gctl-close" id="gctl-close-btn">×</button> | |
| </div> | |
| <hr class="gctl-divider"> | |
| <div class="gctl-actions"> | |
| <button class="gctl-btn gctl-btn-primary" id="gctl-export">Export</button> | |
| <button class="gctl-btn gctl-btn-secondary" id="gctl-export-as">Export As</button> | |
| <button class="gctl-btn gctl-btn-outline" id="gctl-copy">Copy</button> | |
| <button class="gctl-btn gctl-btn-accent" id="gctl-export-all">Export All Cookies</button> | |
| </div> | |
| <div class="gctl-options"> | |
| <label class="gctl-label">Export Format:</label> | |
| <select class="gctl-select" id="gctl-format-select"> | |
| <option value="netscape" selected>Netscape</option> | |
| <option value="json">JSON</option> | |
| <option value="header">Header String</option> | |
| </select> | |
| <label class="gctl-label gctl-label-inline"> | |
| <input type="checkbox" id="gctl-nowrap" checked> Table Nowrap | |
| </label> | |
| </div> | |
| <div class="gctl-cookie-source" id="gctl-source"></div> | |
| <div class="gctl-table-wrap"> | |
| <table class="gctl-table" id="gctl-table"> | |
| <thead> | |
| <tr> | |
| <th>Domain</th> | |
| <th>Subdomains</th> | |
| <th>Path</th> | |
| <th>Secure</th> | |
| <th>Expiry</th> | |
| <th>Name</th> | |
| <th>Value</th> | |
| </tr> | |
| </thead> | |
| <tbody id="gctl-tbody"></tbody> | |
| </table> | |
| </div> | |
| <p class="gctl-footer-note" id="gctl-footer">Cookies are read locally. Nothing is sent outside.</p> | |
| </div> | |
| `; | |
| return panel; | |
| } | |
| function injectCSS() { | |
| const css = ` | |
| #${COOKIE_PANEL_ID} { | |
| all: initial; | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; | |
| } | |
| #${COOKIE_PANEL_ID} * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-overlay { | |
| position: fixed; | |
| top: 0; left: 0; right: 0; bottom: 0; | |
| background: rgba(0,0,0,0.35); | |
| z-index: 2147483646; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-panel { | |
| position: fixed; | |
| top: 50%; left: 50%; | |
| transform: translate(-50%, -50%); | |
| width: min(90vw, 800px); | |
| max-height: 85vh; | |
| background: #fff; | |
| border-radius: 12px; | |
| box-shadow: 0 20px 60px rgba(0,0,0,0.3); | |
| z-index: 2147483647; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| color: #1d1d1f; | |
| font-size: 13px; | |
| line-height: 1.4; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-header { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 14px 18px; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-title { | |
| font-size: 15px; | |
| font-weight: 600; | |
| margin: 0; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-url { | |
| font-weight: 400; | |
| font-size: 13px; | |
| color: #06c; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-close { | |
| background: none; | |
| border: none; | |
| font-size: 24px; | |
| cursor: pointer; | |
| color: #666; | |
| line-height: 1; | |
| padding: 0 4px; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-close:hover { color: #000; } | |
| #${COOKIE_PANEL_ID} .gctl-divider { | |
| border: none; | |
| border-top: 1px solid #e5e5ea; | |
| margin: 0; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-actions { | |
| display: flex; | |
| gap: 8px; | |
| padding: 12px 18px; | |
| flex-wrap: wrap; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-btn { | |
| padding: 7px 16px; | |
| border-radius: 6px; | |
| font-size: 13px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| border: 1px solid transparent; | |
| transition: background 0.15s; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-btn-primary { | |
| background: #007aff; | |
| color: #fff; | |
| border-color: #007aff; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-btn-primary:hover { background: #0056cc; } | |
| #${COOKIE_PANEL_ID} .gctl-btn-secondary { | |
| background: #f2f2f7; | |
| color: #1d1d1f; | |
| border-color: #d1d1d6; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-btn-secondary:hover { background: #e5e5ea; } | |
| #${COOKIE_PANEL_ID} .gctl-btn-outline { | |
| background: #fff; | |
| color: #007aff; | |
| border-color: #007aff; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-btn-outline:hover { background: #e8f0fe; } | |
| #${COOKIE_PANEL_ID} .gctl-btn-accent { | |
| background: #34c759; | |
| color: #fff; | |
| border-color: #34c759; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-btn-accent:hover { background: #28a745; } | |
| #${COOKIE_PANEL_ID} .gctl-options { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| padding: 0 18px 12px; | |
| flex-wrap: wrap; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-label { | |
| font-size: 12px; | |
| font-weight: 500; | |
| color: #555; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-label-inline { | |
| display: flex; | |
| align-items: center; | |
| gap: 4px; | |
| cursor: pointer; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-select { | |
| font-size: 12px; | |
| padding: 4px 8px; | |
| border-radius: 4px; | |
| border: 1px solid #d1d1d6; | |
| background: #fff; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-table-wrap { | |
| overflow: auto; | |
| flex: 1; | |
| padding: 0 18px 12px; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| font-size: 11px; | |
| font-family: 'SF Mono', 'Menlo', 'Monaco', 'Consolas', monospace; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-table th { | |
| text-align: left; | |
| padding: 6px 8px; | |
| background: #f5f5f7; | |
| font-weight: 600; | |
| font-size: 10px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| color: #555; | |
| border-bottom: 1px solid #e5e5ea; | |
| position: sticky; | |
| top: 0; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-table td { | |
| padding: 4px 8px; | |
| border-bottom: 1px solid #f0f0f0; | |
| max-width: 200px; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-table.gctl-nowrap td { | |
| white-space: nowrap; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-table tr:hover td { | |
| background: #f8f8fa; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-cookie-source { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| padding: 6px 18px 4px; | |
| flex-wrap: wrap; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-source-badge { | |
| font-size: 10px; | |
| font-weight: 600; | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.3px; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-source-full { | |
| background: #d1fae5; | |
| color: #065f46; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-source-limited { | |
| background: #fef3c7; | |
| color: #92400e; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-source-count { | |
| font-size: 11px; | |
| color: #555; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-source-hint { | |
| font-size: 10px; | |
| color: #999; | |
| font-style: italic; | |
| } | |
| #${COOKIE_PANEL_ID} .gctl-footer-note { | |
| text-align: center; | |
| padding: 6px 18px; | |
| font-size: 10px; | |
| color: #aaa; | |
| border-top: 1px solid #e5e5ea; | |
| margin: 0; | |
| font-family: 'SF Mono', 'Menlo', 'Monaco', 'Consolas', monospace; | |
| } | |
| `; | |
| GM_addStyle(css); | |
| } | |
| function updateFooter(method, count, docCookieRaw) { | |
| const source = document.getElementById('gctl-source'); | |
| const footer = document.getElementById('gctl-footer'); | |
| if (source) { | |
| const isGm = method === 'GM_cookie'; | |
| const httpOnlyNote = !isGm ? ' (HttpOnly cookies not accessible via document.cookie)' : ''; | |
| source.innerHTML = ` | |
| <span class="gctl-source-badge ${isGm ? 'gctl-source-full' : 'gctl-source-limited'}"> | |
| ${isGm ? 'Full Access (GM_cookie)' : 'Limited (document.cookie)'} | |
| </span> | |
| <span class="gctl-source-count">${count} cookie${count !== 1 ? 's' : ''} found</span> | |
| ${httpOnlyNote ? `<span class="gctl-source-hint">${httpOnlyNote}</span>` : ''} | |
| `; | |
| } | |
| if (footer) { | |
| const raw = docCookieRaw || '(empty)'; | |
| footer.textContent = `document.cookie: "${raw.length > 60 ? raw.slice(0, 60) + '...' : raw}" — read locally, never sent outside.`; | |
| } | |
| } | |
| async function populateTable() { | |
| const tbody = document.getElementById('gctl-tbody'); | |
| if (!tbody) return; | |
| tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;padding:20px;color:#999;">Loading cookies...</td></tr>'; | |
| const rawDocCookie = document.cookie; | |
| const cookies = await readCookies(); | |
| const table = cookiesToNetscapeTable(cookies); | |
| const nowrap = document.getElementById('gctl-nowrap'); | |
| const tableEl = document.getElementById('gctl-table'); | |
| if (nowrap && tableEl) { | |
| tableEl.classList.toggle('gctl-nowrap', nowrap.checked); | |
| } | |
| if (table.length === 0) { | |
| tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;padding:20px 20px 8px;color:#999;">No cookies found for this page.</td></tr>'; | |
| if (!gmCookieAvailable) { | |
| tbody.innerHTML += '<tr><td colspan="7" style="text-align:center;padding:0 20px 20px;font-size:11px;color:#bbb;line-height:1.5;">This site may use HttpOnly cookies (not accessible via document.cookie).<br>On Chromium with Tampermonkey, GM_cookie can read all cookies including HttpOnly.</td></tr>'; | |
| } | |
| } else { | |
| tbody.innerHTML = table.map(row => | |
| '<tr>' + row.map(v => `<td title="${v.replace(/"/g, '"')}">${v}</td>`).join('') + '</tr>' | |
| ).join(''); | |
| } | |
| const method = gmCookieAvailable ? 'GM_cookie' : 'document.cookie'; | |
| updateFooter(method, cookies.length, rawDocCookie); | |
| } | |
| function openPanel() { | |
| if (panelExists()) return; | |
| const panel = buildPanelUI(); | |
| document.body.appendChild(panel); | |
| populateTable(); | |
| document.getElementById('gctl-close-btn').addEventListener('click', removePanel); | |
| panel.querySelector('.gctl-overlay').addEventListener('click', removePanel); | |
| document.getElementById('gctl-nowrap').addEventListener('change', function() { | |
| const t = document.getElementById('gctl-table'); | |
| if (t) t.classList.toggle('gctl-nowrap', this.checked); | |
| }); | |
| document.getElementById('gctl-export').addEventListener('click', () => handleExport(false, false)); | |
| document.getElementById('gctl-export-as').addEventListener('click', () => handleExport(true, false)); | |
| document.getElementById('gctl-copy').addEventListener('click', async () => { | |
| const select = document.getElementById('gctl-format-select'); | |
| const fmt = FORMATS[select ? select.value : 'netscape'] || FORMATS.netscape; | |
| const cookies = await readCookies(); | |
| const text = fmt.serializer(cookies); | |
| copyToClipboard(text); | |
| showNotification('Get cookies.txt LOCALLY', 'Cookies copied to clipboard.'); | |
| }); | |
| document.getElementById('gctl-export-all').addEventListener('click', () => handleExport(true, true)); | |
| } | |
| function init() { | |
| injectCSS(); | |
| GM_registerMenuCommand('Get cookies.txt LOCALLY', openPanel); | |
| } | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', init); | |
| } else { | |
| init(); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment