// ==UserScript== // @name Cookies Inspector // @namespace https://scriptcat.org/zh-CN/users/184036 // @version 1.0.0 // @description 查看、复制、删除当前网站 Cookies,Via 风格弹窗 // @author DeepSeek // @license MIT // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_setClipboard // @run-at document-idle // ==/UserScript== (function () { 'use strict'; /* ---------- 工具函数 ---------- */ const clearAllCookies = () => { const parts = location.hostname.split('.'); const domains = [location.hostname]; if (parts.length > 1) domains.push('.' + parts.slice(-2).join('.')); document.cookie.split(';').forEach(c => { const k = c.split('=')[0].trim(); domains.forEach(d => { document.cookie = `${k}=;expires=Thu,01 Jan 1970 00:00:00 UTC;path=/;domain=${d};`; }); document.cookie = `${k}=;expires=Thu,01 Jan 1970 00:00:00 UTC;path=/;`; }); }; const getCookiesArray = () => { return document.cookie.split(';').map(c => c.trim()).filter(Boolean); }; /* ---------- Shadow DOM Toast 通知 ---------- */ const TOAST_HOST_ID = 'cookies-inspector-toast-host'; const showToast = (msg, duration = 2000) => { let host = document.getElementById(TOAST_HOST_ID); if (!host) { host = document.createElement('div'); host.id = TOAST_HOST_ID; const shadow = host.attachShadow({ mode: 'open' }); const style = document.createElement('style'); style.textContent = ` .toast-container { position:fixed; left:0; right:0; bottom:75px; display:flex; flex-direction:column; align-items:center; gap:8px; pointer-events:none; z-index:2147483647; } .toast-item { background:rgba(28,28,30,0.85); color:#fff; padding:14px 24px; border-radius:999px; font-size:14px; font-weight:500; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; box-shadow:0 8px 24px rgba(0,0,0,0.2); animation:toastIn 0.3s cubic-bezier(0.22,1,0.36,1); opacity:0; transform:translateY(20px); animation-fill-mode:forwards; max-width:80%; text-align:center; word-break:break-word; } .toast-item.out { animation:toastOut 0.3s cubic-bezier(0.22,1,0.36,1) forwards; } @keyframes toastIn { from { opacity:0; transform:translateY(20px); } to { opacity:1; transform:translateY(0); } } @keyframes toastOut { from { opacity:1; transform:translateY(0); } to { opacity:0; transform:translateY(20px); } } `; shadow.appendChild(style); const container = document.createElement('div'); container.className = 'toast-container'; shadow.appendChild(container); document.body.appendChild(host); } const container = host.shadowRoot.querySelector('.toast-container'); const toast = document.createElement('div'); toast.className = 'toast-item'; toast.textContent = msg; container.appendChild(toast); setTimeout(() => { toast.classList.add('out'); setTimeout(() => toast.remove(), 300); }, duration); }; // 涟漪效果:按下时铺开并保持,松手时才淡出 const createRipple = function (event) { const button = event.currentTarget; // ---- 1. 按钮按压回弹 ---- button.style.transition = 'transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1)'; button.style.transform = 'scale(0.95)'; // ---- 2. 清掉该按钮上可能残留的旧涟漪 ---- button.querySelectorAll('.ripple').forEach(r => r.remove()); // ---- 3. 计算涟漪几何尺寸 ---- const rect = button.getBoundingClientRect(); let clientX, clientY; if (event.touches) { clientX = event.touches[0].clientX; clientY = event.touches[0].clientY; } else { clientX = event.clientX; clientY = event.clientY; } const dx = Math.max(clientX - rect.left, rect.right - clientX); const dy = Math.max(clientY - rect.top, rect.bottom - clientY); const diameter = Math.sqrt(dx * dx + dy * dy) * 2; const radius = diameter / 2; // ---- 4. 创建涟漪元素(初始 scale(0),不可见)---- const circle = document.createElement('span'); circle.classList.add('ripple'); circle.style.width = circle.style.height = diameter + 'px'; circle.style.left = (clientX - rect.left - radius) + 'px'; circle.style.top = (clientY - rect.top - radius) + 'px'; circle.style.background = 'var(--ripple-color)'; /* 跟随主题自动切换 */ circle.style.borderRadius = '50%'; circle.style.position = 'absolute'; circle.style.pointerEvents = 'none'; circle.style.transform = 'scale(0)'; circle.style.transformOrigin = 'center center'; circle.style.opacity = '1'; circle.style.transition = 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)'; button.appendChild(circle); // ---- 5. 下一帧开始扩散,铺开后原地停住(不淡出)---- requestAnimationFrame(() => { circle.style.transform = 'scale(1)'; }); // ---- 6. 松手 / 离开时才淡出并清理 ---- const release = () => { button.style.transform = 'scale(1)'; circle.style.transition = 'opacity 0.25s ease-out'; circle.style.opacity = '0'; setTimeout(() => { if (circle.parentElement === button) circle.remove(); }, 300); button.removeEventListener('pointerup', release); button.removeEventListener('pointerleave', release); }; button.addEventListener('pointerup', release); button.addEventListener('pointerleave', release); }; /* ---------- 自定义 Cookies 对话框 ---------- */ const showCookiesDialog = () => { // 防止重复打开 if (document.getElementById('cookies-inspector-dialog')) { return; } const cookies = getCookiesArray(); if (cookies.length === 0) { alert('无 Cookies。'); return; } const host = document.createElement('div'); host.id = 'cookies-inspector-dialog'; const shadowRoot = host.attachShadow({ mode: 'open' }); document.body.appendChild(host); // ---- 锁死背景页面滚动 ---- const prevBodyOverflow = document.body.style.overflow; document.body.style.overflow = 'hidden'; const style = document.createElement('style'); style.textContent = ` /* ---------- 主题变量(浅色为默认) ---------- */ :host { --dialog-bg: #ffffff; --dialog-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); --title-color: #000000; --content-color: #000000; --fade-color: #ffffff; --scrollbar-thumb: #d1d1d1; --btn-color: #4A6FD4; --ripple-color: rgba(0, 0, 0, 0.15); } /* ---------- 夜间模式(跟随浏览器/系统) ---------- */ @media (prefers-color-scheme: dark) { :host { --dialog-bg: #1c1c1e; --dialog-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); --title-color: #ffffff; --content-color: #e5e5e7; --fade-color: #1c1c1e; --scrollbar-thumb: #4a4a4d; --btn-color: #6F8DE1; --ripple-color: rgba(255, 255, 255, 0.18); } } .overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.35); z-index: 2147483646; display: flex; justify-content: center; align-items: center; overscroll-behavior: contain; touch-action: none; } .cookies-dialog { background: var(--dialog-bg); border-radius: 20px; box-shadow: var(--dialog-shadow); z-index: 2147483647; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; padding: 24px 24px 16px 24px; width: 80%; max-width: 300px; color: var(--content-color); position: relative; box-sizing: border-box; touch-action: auto; /* 主题切换时平滑过渡 */ transition: background 0.2s ease, box-shadow 0.2s ease; } .cookies-title { font-size: 19px; font-weight: 500; margin-bottom: 24px; line-height: 1.4; color: var(--title-color); word-break: break-all; transition: color 0.2s ease; } /* 内容包裹层:撑到面板右边缘,容纳自定义滚动条和渐隐层 */ .cookies-content-wrap { position: relative; margin-bottom: 28px; margin-right: -24px; /* 抵消 dialog 的右 padding */ } .cookies-content { max-height: 410px; overflow-y: auto; line-height: 1.6; word-break: break-all; white-space: pre-wrap; font-family: "SF Mono", "Menlo", "Monaco", monospace; font-size: 15px; padding: 0 4px 0 0; background: transparent; border: none; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; scrollbar-width: none; } .cookies-content::-webkit-scrollbar { display: none; } /* 顶部 / 底部渐隐层:主题色到透明,让内容"溶"进面板边缘 */ .cookies-fade { position: absolute; left: 0; right: 0; height: 24px; pointer-events: none; opacity: 0; transition: opacity 0.2s ease; } .cookies-fade.visible { opacity: 1; } .cookies-fade-top { top: 0; background: linear-gradient( to bottom, var(--fade-color) 0%, transparent 100% ); } .cookies-fade-bottom { bottom: 0; background: linear-gradient( to top, var(--fade-color) 0%, transparent 100% ); } /* 自定义滚动条:默认隐藏,滚动 / 初次打开时显示 */ .cookies-scrollbar { position: absolute; top: 0; bottom: 0; right: 0; width: 4px; pointer-events: none; opacity: 0; transition: opacity 0.3s ease; z-index: 1; } .cookies-scrollbar.visible { opacity: 1; } .cookies-scrollbar-thumb { position: absolute; left: 0; right: 0; background: var(--scrollbar-thumb); border-radius: 4px; transition: background 0.2s ease; } .cookies-buttons { display: flex; justify-content: space-between; align-items: center; gap: 12px; } .left-buttons { display: flex; margin-left: -24px; } .right-buttons { display: flex; gap: 6px; margin-right: -24px; } .cookies-btn { padding: 8px 10px; border: none; border-radius: 10px; background: transparent; cursor: pointer; font-size: 16px; font-weight: 500; transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), color 0.2s ease; color: var(--btn-color); position: relative; overflow: hidden; -webkit-tap-highlight-color: transparent; outline: none; will-change: transform; } `; shadowRoot.appendChild(style); const overlay = document.createElement('div'); overlay.className = 'overlay'; const closeDialog = () => { document.body.style.overflow = prevBodyOverflow; host.remove(); }; overlay.onclick = (e) => { if (e.target === overlay) { closeDialog(); } }; const dialog = document.createElement('div'); dialog.className = 'cookies-dialog'; const title = document.createElement('div'); title.className = 'cookies-title'; title.textContent = `${location.hostname} 的 Cookies`; // ---- 内容区 + 渐隐层 + 自定义滚动条 ---- const contentWrap = document.createElement('div'); contentWrap.className = 'cookies-content-wrap'; const content = document.createElement('div'); content.className = 'cookies-content'; content.textContent = cookies.join(';\n'); const fadeTop = document.createElement('div'); fadeTop.className = 'cookies-fade cookies-fade-top'; const fadeBottom = document.createElement('div'); fadeBottom.className = 'cookies-fade cookies-fade-bottom'; const scrollbar = document.createElement('div'); scrollbar.className = 'cookies-scrollbar'; const scrollbarThumb = document.createElement('div'); scrollbarThumb.className = 'cookies-scrollbar-thumb'; scrollbar.appendChild(scrollbarThumb); contentWrap.appendChild(content); contentWrap.appendChild(fadeTop); contentWrap.appendChild(fadeBottom); contentWrap.appendChild(scrollbar); // ---- 滚动条同步逻辑 ---- const updateScrollbar = () => { const visibleH = content.clientHeight; const totalH = content.scrollHeight; if (totalH <= visibleH + 1) { scrollbar.style.display = 'none'; return; } scrollbar.style.display = 'block'; const thumbH = Math.max((visibleH / totalH) * visibleH, 20); const maxThumbTop = visibleH - thumbH; const maxScrollTop = totalH - visibleH; const thumbTop = maxScrollTop > 0 ? (content.scrollTop / maxScrollTop) * maxThumbTop : 0; scrollbarThumb.style.height = thumbH + 'px'; scrollbarThumb.style.top = thumbTop + 'px'; }; // ---- 渐隐层显隐逻辑 ---- const updateFades = () => { const visibleH = content.clientHeight; const totalH = content.scrollHeight; const scrollTop = content.scrollTop; if (totalH <= visibleH + 1) { fadeTop.classList.remove('visible'); fadeBottom.classList.remove('visible'); return; } const atTop = scrollTop <= 1; const atBottom = scrollTop + visibleH >= totalH - 1; fadeTop.classList.toggle('visible', !atTop); fadeBottom.classList.toggle('visible', !atBottom); }; let hideTimer = null; const showScrollbar = (duration = 800) => { scrollbar.classList.add('visible'); clearTimeout(hideTimer); hideTimer = setTimeout(() => { scrollbar.classList.remove('visible'); }, duration); }; content.addEventListener('scroll', () => { updateScrollbar(); updateFades(); showScrollbar(800); }); requestAnimationFrame(() => { updateScrollbar(); updateFades(); if (scrollbar.style.display !== 'none') { showScrollbar(1200); } }); const buttonsDiv = document.createElement('div'); buttonsDiv.className = 'cookies-buttons'; const leftButtons = document.createElement('div'); leftButtons.className = 'left-buttons'; const rightButtons = document.createElement('div'); rightButtons.className = 'right-buttons'; const deleteBtn = document.createElement('button'); deleteBtn.className = 'cookies-btn delete'; deleteBtn.textContent = '删除'; deleteBtn.onclick = () => { if (confirm('确定要删除全部 Cookies 吗?')) { clearAllCookies(); showToast('Cookies 已删除'); closeDialog(); } }; deleteBtn.addEventListener('pointerdown', createRipple); const copyBtn = document.createElement('button'); copyBtn.className = 'cookies-btn'; copyBtn.textContent = '复制'; copyBtn.onclick = () => { GM_setClipboard(getCookiesArray().join('; ')); showToast('已复制文本'); closeDialog(); }; copyBtn.addEventListener('pointerdown', createRipple); const cancelBtn = document.createElement('button'); cancelBtn.className = 'cookies-btn'; cancelBtn.textContent = '取消'; cancelBtn.onclick = () => closeDialog(); cancelBtn.addEventListener('pointerdown', createRipple); leftButtons.appendChild(deleteBtn); rightButtons.appendChild(cancelBtn); rightButtons.appendChild(copyBtn); buttonsDiv.appendChild(leftButtons); buttonsDiv.appendChild(rightButtons); dialog.append(title, contentWrap, buttonsDiv); overlay.appendChild(dialog); shadowRoot.appendChild(overlay); const intervalId = setInterval(() => { const currentCookies = getCookiesArray().join(';\n'); if (content.textContent !== currentCookies) { content.textContent = currentCookies; updateScrollbar(); updateFades(); if (getCookiesArray().length === 0) { clearInterval(intervalId); closeDialog(); showToast('Cookies 已清空'); } } }, 1000); const observer = new MutationObserver(() => { if (!document.contains(host)) { clearInterval(intervalId); observer.disconnect(); clearTimeout(hideTimer); document.body.style.overflow = prevBodyOverflow; } }); observer.observe(document.body, { childList: true }); }; /* ---------- 主逻辑 ---------- */ GM_registerMenuCommand('查看 Cookies', () => { const cookies = getCookiesArray(); if (cookies.length === 0) { alert('无 Cookies。'); } else { showCookiesDialog(); } }); })();