Cookie Manager PRO (Optimized + Import/Export)

Faster startup, optimized DOM, draggable UI, import/export, stable deletion.

目前為 2025-11-26 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Cookie Manager PRO (Optimized + Import/Export)
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  Faster startup, optimized DOM, draggable UI, import/export, stable deletion.
// @match        *://*/*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function(){
'use strict';

const ready = f => /complete|interactive/.test(document.readyState) ? f() : document.addEventListener("DOMContentLoaded", f,{once:true});

ready(()=> setTimeout(()=> new CookieManager().init(),150));

class CookieManager {

    constructor(){
        this.cookies=[];
        this.filtered=[];
        this.shadow=null;
        this.panel=null;
    }

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

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

        this.cookies = raw.split(";").map(pair=>{
            const i = pair.indexOf("=");
            const name = decodeURIComponent(pair.slice(0,i).trim());
            const value = decodeURIComponent(pair.slice(i+1));
            return {name,value,domain:location.hostname,path:"/"};
        });

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

    buildUI(){
        if(document.getElementById("cookieManagerHost")) return;

        const host = document.createElement("div");
        host.id="cookieManagerHost";
        document.body.appendChild(host);

        const shadow = host.attachShadow({mode:"open"});
        this.shadow = shadow;

        // STYLE
        shadow.innerHTML = `
        <style>
            :host{all:initial;}
            .btn-main{
                position:fixed;top:20px;right:20px;width:60px;height:60px;
                border-radius:50%;border:none;background:#673ab7d4;color:#fff;
                cursor:grab;display:flex;align-items:center;justify-content:center;
                font-size:28px;z-index:999999;box-shadow:0 4px 15px #0003;
                user-select:none;touch-action:none;
            }
            .btn-main:active{cursor:grabbing;}
            .panel{
                position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);
                width:95vw;max-width:900px;background:#222e;color:#fff;
                border-radius:16px;padding:12px;display:none;z-index:9999999;
                font-family:sans-serif;box-sizing:border-box;
            }
            .hdr{padding:8px;background:#fff2;border-radius:10px;margin-bottom:10px;cursor:move;}
            .search{width:100%;padding:7px;margin-bottom:8px;border:1px solid #fff4;background:#fff1;color:#fff;border-radius:5px;}
            .btn{padding:6px 8px;background:#fff3;border:none;border-radius:5px;color:#fff;cursor:pointer;margin:2px;}
            .btn:hover{background:#fff5;}
            table{width:100%;border-collapse:collapse;table-layout:fixed;margin-top:10px;}
            th,td{padding:6px;border-bottom:1px solid #fff3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
            tr:hover{background:#fff1;}
        </style>
        `;

        // BUTTON
        const toggle = document.createElement("button");
        toggle.className="btn-main"; toggle.textContent="🍪";
        shadow.appendChild(toggle);

        // PANEL
        const panel = document.createElement("div");
        panel.className="panel";
        panel.innerHTML = `
            <div class="hdr" id="hdr"><h2 style="margin:0;">Cookie Manager</h2></div>
            <input class="search" id="search" placeholder="Search cookies...">
            <div>
                <button class="btn" id="add">Add</button>
                <button class="btn" id="refresh">Refresh</button>
                <button class="btn" id="export">Export</button>
                <button class="btn" id="import">Import</button>
                <button class="btn" id="delAll" style="background:#e055;">Delete All</button>
            </div>
            <table><thead><tr><th>Name</th><th>Value</th><th>Action</th></tr></thead>
            <tbody id="list"></tbody></table>
        `;
        shadow.appendChild(panel);
        this.panel=panel;

        // DRAG
        this.makeDraggable(toggle);
        this.makeDraggable(panel, panel.querySelector("#hdr"));

        // EVENTS
        toggle.onclick = (e)=> !toggle._dragged && (panel.style.display= panel.style.display==="block"?"none":"block", this.refresh());
        toggle.onmousedown = ()=> toggle._dragged=false;

        shadow.getElementById("add").onclick = ()=> this.addPrompt();
        shadow.getElementById("refresh").onclick = ()=> this.refresh();
        shadow.getElementById("delAll").onclick = ()=> this.deleteAll();
        shadow.getElementById("search").oninput = ()=> this.search();
        shadow.getElementById("export").onclick = ()=> this.exportCookies();
        shadow.getElementById("import").onclick = ()=> this.importPrompt();

        this.render();
    }

    makeDraggable(el, handle=null){
        let drag=false, moved=false, ox=0, oy=0, sx=0, sy=0;
        const h = handle||el;

        const start = e=>{
            const t = e.touches? e.touches[0]:e;
            sx=t.clientX; sy=t.clientY; moved=false; drag=true;

            const r = el.getBoundingClientRect();
            ox = t.clientX - r.left;
            oy = t.clientY - r.top;

            el.style.transform="none";
            document.addEventListener("mousemove",move);
            document.addEventListener("mouseup",end);
            document.addEventListener("touchmove",move);
            document.addEventListener("touchend",end);
        };

        const move = e=>{
            if(!drag) return;
            const t = e.touches? e.touches[0]:e;

            if(Math.abs(t.clientX-sx)>5||Math.abs(t.clientY-sy)>5) moved=true;

            const x = Math.max(0, Math.min(window.innerWidth - el.offsetWidth, t.clientX-ox));
            const y = Math.max(0, Math.min(window.innerHeight - el.offsetHeight, t.clientY-oy));
            el.style.left=x+"px"; el.style.top=y+"px"; el.style.right="auto";
        };

        const end = ()=>{
            drag=false;
            if(moved) el._dragged=true;
            document.removeEventListener("mousemove",move);
            document.removeEventListener("mouseup",end);
            document.removeEventListener("touchmove",move);
            document.removeEventListener("touchend",end);
        };

        h.addEventListener("mousedown",start);
        h.addEventListener("touchstart",start,{passive:false});
    }

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

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

    render(){
        const list = this.shadow.getElementById("list");
        if(!list) return;
        if(!this.filtered.length){
            list.innerHTML = `<tr><td colspan="3" style="text-align:center;">No cookies.</td></tr>`;
            return;
        }

        const rows=[];
        for(const c of this.filtered){
            const name=this.escape(c.name);
            const val=this.escape(c.value);
            rows.push(`
                <tr><td>${name}</td><td>${val.slice(0,40)}${c.value.length>40?'…':''}</td>
                <td><button class="btn edit" data-name="${name}">✎</button>
                    <button class="btn del" data-name="${name}">×</button></td></tr>
            `);
        }
        list.innerHTML=rows.join("");

        list.querySelectorAll(".del").forEach(b=>{
            const name=this.unescapeHTML(b.dataset.name);
            b.onclick=()=> this.delete(name);
        });

        list.querySelectorAll(".edit").forEach(b=>{
            const name=this.unescapeHTML(b.dataset.name);
            b.onclick=()=> this.editPrompt(name);
        });
    }

    addPrompt(){
        const n=prompt("Name:"); if(!n) return;
        const v=prompt("Value:")||"";
        document.cookie = `${encodeURIComponent(n)}=${encodeURIComponent(v)};path=/`;
        this.refresh();
    }

    editPrompt(name){
        const c = this.cookies.find(x=>x.name===name);
        const v = prompt("New value:", c?.value||"");
        if(v===null) return;
        document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(v)};path=/`;
        this.refresh();
    }

    delete(name){
        const paths=[];
        for(let p=location.pathname; p; ){
            paths.push(p);
            p=p.substring(0,p.lastIndexOf('/'))|| (p.length>1?'/':'');
        }
        if(!paths.includes('/')) paths.push('/');

        const domains=[];
        const parts=location.hostname.split('.');
        for(let i=0;i<parts.length-1;i++) domains.push('.'+parts.slice(i).join('.'));
        if(!domains.includes(location.hostname)) domains.push(location.hostname);

        const exp="Thu, 01 Jan 1970 00:00:00 GMT";

        for(const path of paths){
            for(const d of domains){
                const opts=[
                    `;domain=${d};path=${path};expires=${exp}`,
                    `;path=${path};expires=${exp}`
                ];
                for(const o of opts)
                    document.cookie = `${encodeURIComponent(name)}=;${o}`;
            }
        }
        this.refresh();
    }

    deleteAll(){
        if(!confirm(`Delete ALL ${this.cookies.length} cookies?`)) return;
        for(const c of this.cookies) this.delete(c.name);
    }

    exportCookies(){
        const blob = new Blob([JSON.stringify(this.cookies,null,2)],{type:"application/json"});
        const a=document.createElement("a");
        a.href=URL.createObjectURL(blob);
        a.download="cookies.json";
        a.click();
    }

    importPrompt(){
        const inp=document.createElement("input");
        inp.type="file"; inp.accept="application/json";
        inp.onchange = ()=>{
            const file=inp.files[0];
            if(!file) return;
            const r=new FileReader();
            r.onload = ()=>{
                try{
                    const arr=JSON.parse(r.result);
                    arr.forEach(c=>{
                        document.cookie=`${encodeURIComponent(c.name)}=${encodeURIComponent(c.value)};path=/`;
                    });
                    this.refresh();
                }catch(e){ alert("Invalid JSON."); }
            };
            r.readAsText(file);
        };
        inp.click();
    }

    escape(s){ return s.replace(/[&<>"']/g,m=>({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;","'":"&#39;"}[m])); }
    unescapeHTML(s){
        return s.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">")
                .replace(/&quot;/g,'"').replace(/&#39;/g,"'");
    }
}

})();