Cookie Manager (Shadow DOM & Stable Deletion) - FINAL

Full upgrade: Uses Shadow DOM for invisibility, stable drag, and robust deletion.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Cookie Manager (Shadow DOM & Stable Deletion) - FINAL
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Full upgrade: Uses Shadow DOM for invisibility, stable drag, and robust deletion.
// @author       Balta zar
// @match        *://*/*
// @grant        none
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // Use document-idle for Tampermonkey header, but keep the domReady fallback
    function domReady(cb) {
        if (/complete|interactive/.test(document.readyState)) cb();
        else document.addEventListener("DOMContentLoaded", cb);
    }

    // Wrap the entire logic in a small delay to defeat aggressive SPAs
    setTimeout(() => {
        domReady(() => {

            class CookieManager {
                constructor() {
                    this.currentDomain = location.hostname;
                    this.cookies = [];
                    this.filtered = [];
                    this.shadowRoot = null; // Will store the Shadow Root
                }

                init() {
                    this.loadCookies();
                    this.buildUI();
                    this.bindEvents();
                }

                loadCookies() {
                    this.cookies = [];

                    const raw = document.cookie;
                    if (!raw) {
                        this.filtered = [];
                        return;
                    }

                    raw.split(";").forEach(pair => {
                        const parts = pair.trim().split("=");
                        const name = parts[0];
                        const value = parts.slice(1).join("=");

                        if (!name) return;

                        this.cookies.push({
                            name: decodeURIComponent(name),
                            value: decodeURIComponent(value),
                            domain: location.hostname,
                            path: "/"
                        });
                    });

                    this.filtered = [...this.cookies];
                }

                /**
                 * Builds the UI inside a Shadow DOM for isolation.
                 */
                buildUI() {
                    if (document.getElementById("cookieManagerHost")) return;

                    /* 1. Create the Shadow Host and attach the Shadow Root */
                    const host = document.createElement("div");
                    host.id = "cookieManagerHost";
                    document.body.appendChild(host);

                    // This is the 'illusion' layer!
                    this.shadowRoot = host.attachShadow({ mode: 'open' }); 

                    /* Styles (injected into Shadow Root, isolated from website CSS) */
                    const style = document.createElement("style");
                    style.textContent = `
                        /* :host sets the style for the container element itself */
                        :host {
                            all: initial; /* Essential for isolation */
                        }

                        .cook-btn-main {
                            position: fixed; top: 20px; right: 20px;
                            background: rgba(103,58,183,0.9);
                            color: #fff;
                            border: none;
                            border-radius: 50%;
                            width: 60px; height: 60px;
                            cursor: pointer;
                            font-size: 26px;
                            z-index: 999999;
                            box-shadow: 0 4px 15px rgba(0,0,0,0.3);
                            line-height: 60px;
                            text-align: center;
                        }
                        .cookie-panel {
                            position: fixed;
                            top: 50%; left: 50%;
                            transform: translate(-50%, -50%);
                            width: 90%;
                            max-width: 900px;
                            background: rgba(20,20,30,0.95);
                            color: white;
                            border-radius: 18px;
                            padding: 20px;
                            display: none;
                            z-index: 9999999;
                            font-family: sans-serif;
                            box-sizing: border-box;
                        }
                        .cookie-header {
                            padding: 10px;
                            margin-bottom: 10px;
                            background: rgba(255,255,255,0.1);
                            border-radius: 10px;
                            cursor: move;
                            user-select: none;
                        }
                        table { width: 100%; margin-top: 15px; border-collapse: collapse; table-layout: fixed; }
                        th, td { padding: 8px 10px; border-bottom: 1px solid rgba(255,255,255,0.2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
                        tr:hover { background: rgba(255,255,255,0.07); }
                        .cbtn {
                            padding: 7px 10px;
                            border: none;
                            border-radius: 6px;
                            background: rgba(255,255,255,0.15);
                            color: #fff;
                            cursor: pointer;
                            margin-left: 5px;
                            font-size: 14px;
                        }
                        .cbtn:hover { background: rgba(255,255,255,0.25); }
                        .c-input {
                            width: 100%;
                            margin-bottom: 10px;
                            padding: 8px;
                            border-radius: 6px;
                            border: 1px solid rgba(255,255,255,0.3);
                            background: rgba(255,255,255,0.12);
                            color: #fff;
                            box-sizing: border-box;
                        }
                        td:nth-child(3) { width: 90px; text-align: right; }
                        td:nth-child(1) { width: 30%; }
                        td:nth-child(2) { width: auto; }
                    `;
                    this.shadowRoot.appendChild(style);

                    /* Toggle Button */
                    const toggle = document.createElement("button");
                    toggle.id = "cookieToggleBtn";
                    toggle.className = "cook-btn-main";
                    toggle.textContent = "🍪";
                    this.shadowRoot.appendChild(toggle);

                    /* Panel */
                    const panel = document.createElement("div");
                    panel.id = "cookiePanel";
                    panel.className = "cookie-panel";
                    panel.innerHTML = `
                        <div id="cookieHeader" class="cookie-header"><h2 style="margin:0;">Cookie Manager</h2></div>
                        <input id="cookieSearch" class="c-input" placeholder="Search cookies...">

                        <button id="addCookie" class="cbtn">Add</button>
                        <button id="refreshCookie" class="cbtn">Refresh</button>
                        <button id="deleteAllCookie" class="cbtn" style="background:rgba(255,50,50,0.5);">Delete All</button>

                        <table>
                            <thead><tr><th>Name</th><th>Value</th><th>Actions</th></tr></thead>
                            <tbody id="cookieList"></tbody>
                        </table>
                    `;
                    this.shadowRoot.appendChild(panel);

                    // Get references from the Shadow Root
                    this.panel = this.shadowRoot.getElementById("cookiePanel");
                    this.header = this.shadowRoot.getElementById("cookieHeader");

                    this.dragInit = false;

                    this.enableDrag();
                    this.render();
                }

                /* Drag System - FIXED */
                enableDrag() {
                    let dragging = false, offsetX = 0, offsetY = 0;
                    let currentX = 0, currentY = 0;

                    const start = (e) => {
                        // Prevent text selection during drag
                        e.preventDefault(); 
                        dragging = true;
                        
                        if (!this.dragInit) {
                            // On first drag, calculate initial position from transform
                            const r = this.panel.getBoundingClientRect();
                            currentX = r.left;
                            currentY = r.top;
                            this.panel.style.transform = "none";
                            this.panel.style.left = currentX + "px";
                            this.panel.style.top = currentY + "px";
                            this.dragInit = true;
                        }

                        // Calculate offset from the mouse click to the panel's current position
                        offsetX = e.clientX - parseFloat(this.panel.style.left);
                        offsetY = e.clientY - parseFloat(this.panel.style.top);

                        this.panel.style.cursor = 'grabbing';
                    };

                    const move = (e) => {
                        if (!dragging) return;
                        // Clamp the movement to avoid dragging off-screen
                        currentX = Math.max(0, e.clientX - offsetX);
                        currentY = Math.max(0, e.clientY - offsetY);
                        
                        this.panel.style.left = currentX + "px";
                        this.panel.style.top = currentY + "px";
                    };

                    const end = () => {
                        dragging = false;
                        this.panel.style.cursor = 'move';
                    };

                    this.header.addEventListener("mousedown", start);
                    // Must attach mouse events to the main document for reliable drag outside the panel
                    document.addEventListener("mousemove", move);
                    document.addEventListener("mouseup", end);
                }

                bindEvents() {
                    // All elements are now retrieved from the Shadow Root
                    this.shadowRoot.getElementById("cookieToggleBtn").onclick = () => {
                        this.panel.style.display =
                            this.panel.style.display === "block" ? "none" : "block";
                        if (this.panel.style.display === "block") {
                            this.refresh();
                        }
                    };

                    this.shadowRoot.getElementById("addCookie").onclick = () => this.addPrompt();
                    this.shadowRoot.getElementById("refreshCookie").onclick = () => this.refresh();
                    this.shadowRoot.getElementById("deleteAllCookie").onclick = () => this.deleteAll();
                    this.shadowRoot.getElementById("cookieSearch").oninput = () => this.search();
                }

                refresh() {
                    this.loadCookies();
                    this.render();
                }

                search() {
                    const q = this.shadowRoot.getElementById("cookieSearch").value.toLowerCase();
                    this.filtered = this.cookies.filter(c =>
                        c.name.toLowerCase().includes(q) ||
                        c.value.toLowerCase().includes(q)
                    );
                    this.render();
                }

                render() {
                    const body = this.shadowRoot.getElementById("cookieList");
                    if (!body) return;

                    body.innerHTML = "";

                    if (this.filtered.length === 0) {
                        body.innerHTML = `<tr><td colspan="3" style="text-align:center;">No cookies found.</td></tr>`;
                        return;
                    }

                    this.filtered.forEach((c) => {
                        const tr = document.createElement("tr");
                        const escapedName = this.escape(c.name);
                        const escapedValue = this.escape(c.value);

                        tr.innerHTML = `
                            <td>${escapedName}</td>
                            <td>${escapedValue.slice(0, 40)}${c.value.length > 40 ? "…" : ""}</td>
                            <td>
                                <button class="cbtn" data-action="edit" data-name="${escapedName}">✎</button>
                                <button class="cbtn" data-action="del" data-name="${escapedName}">×</button>
                            </td>
                        `;
                        body.appendChild(tr);
                    });

                    // Attach event listeners using the Shadow Root context
                    body.querySelectorAll("button").forEach(btn => {
                        const name = btn.dataset.name;
                        if (!name) return;

                        // Unescape the name before passing it to the action methods
                        const unescapedName = name.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#39;/g, "'");

                        if (btn.dataset.action === "del") btn.onclick = () => this.delete(unescapedName);
                        if (btn.dataset.action === "edit") btn.onclick = () => this.editPrompt(unescapedName);
                    });
                }

                addPrompt() {
                    const n = prompt("Cookie name:");
                    if (!n) return;
                    const v = prompt("Cookie value:") || "";
                    
                    document.cookie = `${encodeURIComponent(n)}=${encodeURIComponent(v)}; path=/; secure=${location.protocol === 'https:'}`;
                    this.refresh();
                }

                editPrompt(name) {
                    const cookie = this.cookies.find(c => c.name === name);
                    const v = prompt(`New value for '${name}':`, cookie?.value ?? "");
                    if (v === null) return;

                    document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(v)}; path=/; secure=${location.protocol === 'https:'}`;
                    this.refresh();
                }

                /**
                 * Deletes a cookie by trying common domain and path variations. - FIXED
                 */
                delete(name) {
                    const paths = [
                        location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1), 
                        '/'
                    ];
                    // Try the current domain and the superdomain
                    const domains = [
                        location.hostname, 
                        `.${location.hostname.split('.').slice(-2).join('.')}` // e.g. .example.com
                    ];
                    const expires = 'Thu, 01 Jan 1970 00:00:00 GMT';
                    
                    domains.forEach(d => {
                        paths.forEach(p => {
                            document.cookie = 
                                `${encodeURIComponent(name)}=; domain=${d}; path=${p}; expires=${expires}`;
                        });
                    });
                    
                    this.refresh();
                }

                deleteAll() {
                    if (!confirm(`Are you sure you want to delete ALL ${this.cookies.length} visible cookies? This cannot be undone.`)) {
                        return;
                    }
                    [...this.cookies].forEach(c => this.delete(c.name));
                }

                /**
                 * Escape HTML entities to prevent Cross-Site Scripting (XSS).
                 */
                escape(str) {
                    if (typeof str !== 'string') return '';
                    return str.replace(/[&<>"']/g, m => ({
                        "&":"&amp;", "<":"&lt;", ">":"&gt;", '"':"&quot;", "'":"&#39;"
                    }[m]));
                }
            }

            const cm = new CookieManager();
            cm.init();
        });
    }, 500); // Small 500ms delay to help against aggressive SPAs

})();