Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save jcchikikomori/e4f4dc1ba160b8d1d4ceb88145e4736b to your computer and use it in GitHub Desktop.

Select an option

Save jcchikikomori/e4f4dc1ba160b8d1d4ceb88145e4736b to your computer and use it in GitHub Desktop.
Get cookies.txt LOCALLY - Userscript port. Export cookies in Netscape/JSON/Header format. Safari-compatible.
// ==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">&times;</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, '&quot;')}">${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