Cookie 管理器 (V9.3 最终完美版)

最终版!修复页脚UI布局,优化按钮文本,锁定所有功能,为您呈现最完美的最终形态。

当前为 2025-07-03 提交的版本,查看 最新版本

// ==UserScript==
// @name         Cookie 管理器 (V9.3 最终完美版)
// @namespace    https://github.com/gemini-script-creator
// @version      9.3.1
// @description  最终版!修复页脚UI布局,优化按钮文本,锁定所有功能,为您呈现最完美的最终形态。
// @author       Gemini & Pro User
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- ⭐ 用户自定义配置 ---
    const CONFIG = {
        iconSize: 38,
        iconImageUrl: '', //这里是cookie修改器图标(图床完整URL)
        initialPosition: { bottom: '80px', right: '15px' },
        itemsPerPage: 3
    };
    // --- ⭐ 配置结束 ---


    // --- 样式定义 ---
    function getStyles() {
        return `
            :host { all: initial; }
            #cookie-manager-btn-wrapper { position: fixed; width: ${CONFIG.iconSize}px; height: ${CONFIG.iconSize}px; z-index: 2147483647; cursor: grab; user-select: none; }
            #cookie-manager-btn-wrapper:active { cursor: grabbing; }
            #cookie-manager-btn-inner { width: 100%; height: 100%; border-radius: 50%; background-color: #007bff; color: white; font-size: ${Math.floor(CONFIG.iconSize * 0.5)}px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); display: flex; justify-content: center; align-items: center; transition: transform 0.2s; }
            #cookie-manager-btn-wrapper:active #cookie-manager-btn-inner { transform: scale(0.9); }
            #cookie-manager-btn-inner img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
            #cookie-manager-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 450px; background-color: #f9f9f9; border: 1px solid #ccc; border-radius: 8px; box-shadow: 0 6px 12px rgba(0,0,0,0.3); z-index: 2147483647; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-size: 14px; max-height: 80vh; display: none; flex-direction: column; }
            #cookie-manager-header { padding: 12px 15px; background-color: #007bff; color: white; font-weight: bold; border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; }
            #cookie-manager-header .close-btn { cursor: pointer; font-size: 24px; font-weight: bold; }
            #cookie-manager-top-actions { padding: 12px 15px; border-bottom: 1px solid #eee; background-color: #f9f9f9; flex-shrink: 0; }
            #cookie-list-container { padding: 5px 15px; flex-grow: 1; }
            .scroll-mode #cookie-list-container { overflow-y: auto; }
            .cookie-item { display: flex; flex-direction: column; border-bottom: 1px solid #eee; padding: 10px 0; }
            .cookie-item:last-child { border-bottom: none; }
            .cookie-key { font-weight: bold; color: #333; word-break: break-all; }
            .cookie-value { color: #555; margin: 4px 0; word-break: break-all; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; }
            .cookie-props { font-size: 11px; color: #888; margin-top: 4px; background-color: #eee; padding: 3px 6px; border-radius: 3px; }
            .prop-label { font-weight: bold; }
            .cookie-actions { display: flex; gap: 8px; margin-top: 8px; }
            .cookie-actions button { border: none; border-radius: 4px; padding: 5px 10px; font-size: 12px; font-weight: bold; color: white; cursor: pointer; transition: filter 0.2s, transform 0.1s; }
            .cookie-actions button:hover { filter: brightness(1.1); }
            .cookie-actions button:active { transform: scale(0.95); filter: brightness(0.9); }
            .cookie-actions .modify-btn { background-color: #007bff; }
            .cookie-actions .copy-btn { background-color: #6c757d; }
            .cookie-actions .delete-btn { background-color: #dc3545; }
            #cookie-manager-footer { padding: 10px 15px; border-top: 1px solid #eee; flex-shrink: 0; display: flex; justify-content: space-between; align-items: center; }
            .pagination-center { display: flex; justify-content: center; align-items: center; gap: 8px; flex-grow: 1; overflow: hidden; }
            .pagination-center button, .pagination-center span { flex-shrink: 0; white-space: nowrap; padding: 5px 10px; cursor: pointer; border: 1px solid #ccc; background-color: #f0f0f0; color: #333; border-radius: 4px; font-size: 12px;}
            .footer-nav, .footer-actions { display: flex; align-items: center; gap: 8px; }
            .pagination-controls { padding: 5px 10px; cursor: pointer; border: 1px solid #ccc; background-color: #fff; color: #333; border-radius: 4px; flex-shrink: 0; }
            .pagination-controls:disabled { cursor: not-allowed; opacity: 0.5; }
            .page-info { font-size: 13px; color: #555; border: none !important; background: none !important; }
            #edit-modal-overlay, #batch-add-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); z-index: 2147483647; display: none; justify-content: center; align-items: center; }
            #edit-modal-content, #batch-add-modal-content { background: #fff; padding: 20px; border-radius: 8px; width: 90%; max-width: 500px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); display:flex; flex-direction:column; max-height: 90vh; }
            #edit-modal-content h3, #batch-add-modal-content h3 { margin-top: 0; margin-bottom: 20px; text-align: center; flex-shrink: 0; }
            .form-group { margin-bottom: 15px; }
            .form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
            .form-group input, .form-group textarea { width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ccc; box-sizing: border-box; font-family: inherit; }
            .form-group input[readonly], .form-group textarea[readonly] { background-color: #eee; cursor: not-allowed; }
            .form-group textarea { min-height: 80px; resize: vertical; }
            #modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; flex-shrink: 0; }
            #modal-actions button { padding: 8px 15px; border-radius: 4px; border: none; cursor: pointer; font-weight: bold; }
            #modal-save-btn, #batch-add-save-btn { background-color: #007bff; color: white; }
            #modal-cancel-btn, #batch-add-cancel-btn { background-color: #ccc; }
            .search-wrapper { display: flex; margin-top: 10px; }
            .search-wrapper input { flex: 1; padding: 8px; font-size: 14px; border: 1px solid #ccc; box-sizing: border-box; }
            .search-wrapper input:focus { outline: 1px solid #007bff; z-index: 1; }
            #search-by-key-input { border-top-left-radius: 4px; border-bottom-left-radius: 4px; }
            #search-by-value-input { border-left: none; border-top-right-radius: 4px; border-bottom-right-radius: 4px; }
            .actions-wrapper { display: flex; flex-direction: column; gap: 10px; }
            .actions-row { display: flex; gap: 10px; }
            .actions-row button { flex: 1; padding: 8px; font-size: 14px; border: none; cursor: pointer; border-radius: 4px; }
            #batch-add-btn { background-color: #28a745; color: white; }
            #single-add-btn { background-color: #17a2b8; color: white; }
            #copy-all-btn { background-color: #6c757d; color: white; }
            .batch-add-props { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 15px; flex-shrink: 0; }
            #batch-input-area { overflow-y: auto; padding-right: 10px; border-top: 1px solid #eee; border-bottom: 1px solid #eee; padding-top: 10px; margin-top: 15px;}
            .batch-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
            .batch-row input { flex: 1; }
            .batch-row .delete-row-btn { background: #dc3545; color: white; border: none; border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-weight: bold; flex-shrink: 0; line-height: 24px; text-align:center; }
            #add-row-btn { margin-top: 10px; width: 100%; padding: 8px; background: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; flex-shrink: 0; }
            .cookie-item-header { display: flex; align-items: center; gap: 8px; }
            .selection-checkbox { display: none; width: 16px; height: 16px; border: 1px solid #888; border-radius: 3px; cursor: pointer; flex-shrink: 0; }
            .selection-checkbox.checked { background-color: #007bff; color: white; text-align: center; line-height: 16px; font-weight: bold; }
            .selection-mode .selection-checkbox { display: inline-block; }
            #auto-match-area { border: 1px dashed #007bff; padding: 10px; border-radius: 4px; text-align: center; color: #555; cursor: pointer; margin-top: 15px; }
            #delete-all-btn { background-color: #dc3545; color: white; }
        `;
    }

    // --- ⭐ 双核引擎 ---
    const hasCookieStore = !!window.cookieStore;
    const cookieManager = {
        async get() { if (hasCookieStore) { const cookies = await cookieStore.getAll(); return cookies.map(c => ({ key: c.name, value: c.value, domain: c.domain, path: c.path })); } else { if (!document.cookie) return []; return document.cookie.split(';').map(cookie => { const eqIndex = cookie.indexOf('='); if (eqIndex < 0) return null; return { key: cookie.substring(0, eqIndex).trim(), value: cookie.substring(eqIndex + 1).trim(), domain: 'N/A', path: 'N/A' }; }).filter(Boolean); } },
        async set(key, value, { domain, path, days = 365 } = {}) { try { if (hasCookieStore) { await cookieStore.set({ name: key, value, domain, path, expires: Date.now() + (days * 24 * 60 * 60 * 1000) }); } else { let expires = ""; if (days) { const date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); expires = "; expires=" + date.toUTCString(); } const domainPart = domain ? `; domain=${domain}` : ''; const pathPart = path ? `; path=${path}` : ''; document.cookie = `${key}=${value || ""}${expires}${pathPart}${domainPart}`; } return true; } catch (e) { console.error("Cookie Manager Error:", e); if (e.message.includes('domain-match')) { alert(`设置失败!\n\n错误:您提供的Domain (${domain}) 与当前网站域不匹配。\n\n规则:脚本只能为当前网站 (${window.location.hostname}) 及其父域设置Cookie。`); } else { alert(`设置Cookie失败: ${e.message}`); } return false; } },
        async delete(cookie) { try { if (hasCookieStore) { await cookieStore.delete({ name: cookie.key, domain: cookie.domain, path: cookie.path }); } else { const domain = window.location.hostname; const path = window.location.pathname; document.cookie = `${cookie.key}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=${domain}`; document.cookie = `${cookie.key}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}; domain=${domain}`; document.cookie = `${cookie.key}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/;`; } return true; } catch (e) { console.error("Cookie Manager Error:", e); return false; } }
    };

    // --- 核心功能 (无变化) ---
    async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); alert('已复制到剪贴板!'); } catch (err) { alert('复制失败!'); } }

    // --- UI 创建和管理 ---
    let shadowRoot = null; let panel = null; let allCookies = []; let filteredCookies = []; let currentPage = 1; let totalPages = 1; let isInSelectionMode = false;
    let selectionPool = new Map();
    let viewMode = GM_getValue('cookieManagerViewMode', 'pagination');
    function getCookieId(cookie) { return `${cookie.key}||${cookie.domain}||${cookie.path}`; }

    function getPanel() { if (!shadowRoot) return null; if (!panel) { panel = shadowRoot.getElementById('cookie-manager-panel'); } return panel; }
    function showEditModal({ mode = 'add', cookie = {} } = {}) { const modalOverlay = shadowRoot.getElementById('edit-modal-overlay'); const modalTitle = shadowRoot.getElementById('modal-title'); const keyInput = shadowRoot.getElementById('cookie-key-input'); const valueInput = shadowRoot.getElementById('cookie-value-input'); const domainInput = shadowRoot.getElementById('cookie-domain-input'); const pathInput = shadowRoot.getElementById('cookie-path-input'); const expiresInput = shadowRoot.getElementById('cookie-expires-input'); const saveBtn = shadowRoot.getElementById('modal-save-btn'); const cancelBtn = shadowRoot.getElementById('modal-cancel-btn'); keyInput.readOnly = false; expiresInput.readOnly = false; saveBtn.style.backgroundColor = ''; if (mode === 'add') { modalTitle.textContent = '新增 Cookie'; saveBtn.textContent = '保存'; keyInput.value = ''; valueInput.value = ''; domainInput.value = hasCookieStore ? window.location.hostname : ''; pathInput.value = '/'; expiresInput.value = '365'; } else { modalTitle.textContent = '修改 Cookie'; saveBtn.textContent = '保存'; keyInput.value = decodeURIComponent(cookie.key); keyInput.readOnly = true; valueInput.value = decodeURIComponent(cookie.value); domainInput.value = cookie.domain; pathInput.value = cookie.path; expiresInput.value = '365'; } modalOverlay.style.display = 'flex'; const newSaveBtn = saveBtn.cloneNode(true); saveBtn.parentNode.replaceChild(newSaveBtn, saveBtn); newSaveBtn.addEventListener('click', async () => { const newKey = keyInput.value.trim(); const newValue = valueInput.value; const newDomain = domainInput.value.trim(); const newPath = pathInput.value.trim() || '/'; const newExpires = parseInt(expiresInput.value.trim(), 10); if (!newKey) { alert('Cookie 名称 (key) 不能为空!'); return; } const options = { days: isNaN(newExpires) ? 365 : newExpires, path: newPath, domain: newDomain }; let proceed = true; if (mode === 'add' && hasCookieStore) { const existingCookie = await cookieStore.get({ name: newKey, domain: newDomain, path: newPath }); if (existingCookie) { proceed = confirm(`警告:一个具有相同名称、域和路径的Cookie已存在。\n您确定要覆盖它吗?`); } } if (!proceed) { return; } let success = false; if (mode === 'modify') { await cookieManager.delete(cookie); success = await cookieManager.set(newKey, newValue, options); } else { success = await cookieManager.set(newKey, newValue, options); } if (success) { alert(`${mode === 'add' ? '新增' : '修改'}成功!`); modalOverlay.style.display = 'none'; await renderPanelContent(); } }); cancelBtn.onclick = () => { modalOverlay.style.display = 'none'; }; }
    function addNewBatchRow(key = '', value = '') { const inputArea = shadowRoot.getElementById('batch-input-area'); const row = document.createElement('div'); row.className = 'batch-row'; row.innerHTML = ` <input type="text" class="batch-key" placeholder="Key" value="${key}"> <input type="text" class="batch-value" placeholder="Value"> <button class="delete-row-btn">&times;</button> `; row.querySelector('.delete-row-btn').onclick = () => row.remove(); inputArea.appendChild(row); }
    function parseAndPopulate(text) { const inputArea = shadowRoot.getElementById('batch-input-area'); inputArea.innerHTML = ''; const pairs = text.trim().split(';'); pairs.forEach(pair => { pair = pair.trim(); if (!pair) return; const eqIndex = pair.indexOf('='); if (eqIndex > 0) { const key = pair.substring(0, eqIndex).trim(); const value = pair.substring(eqIndex + 1).trim(); if (key) { addNewBatchRow(key, value); } } else { const parts = pair.split(/[\s:]+/); if (parts.length >= 2) { const key = parts.shift().trim(); const value = parts.join(' ').trim(); addNewBatchRow(key, value); } } }); if (inputArea.children.length === 0) { addNewBatchRow(); } }
    function showBatchAddModal() { const modalOverlay = shadowRoot.getElementById('batch-add-modal-overlay'); const saveBtn = shadowRoot.getElementById('batch-add-save-btn'); const cancelBtn = shadowRoot.getElementById('batch-add-cancel-btn'); const addRowBtn = shadowRoot.getElementById('add-row-btn'); const inputArea = shadowRoot.getElementById('batch-input-area'); const domainInput = shadowRoot.getElementById('batch-domain-input'); const pathInput = shadowRoot.getElementById('batch-path-input'); const autoMatchArea = shadowRoot.getElementById('auto-match-area'); domainInput.value = hasCookieStore ? window.location.hostname : ''; pathInput.value = '/'; inputArea.innerHTML = ''; addNewBatchRow(); modalOverlay.style.display = 'flex'; addRowBtn.onclick = addNewBatchRow; autoMatchArea.addEventListener('paste', (e) => { e.preventDefault(); const pastedText = (e.clipboardData || window.clipboardData).getData('text'); parseAndPopulate(pastedText); }); const newSaveBtn = saveBtn.cloneNode(true); saveBtn.parentNode.replaceChild(newSaveBtn, saveBtn); newSaveBtn.addEventListener('click', async () => { const domain = domainInput.value.trim(); const path = pathInput.value.trim() || '/'; const rows = inputArea.querySelectorAll('.batch-row'); const cookiesToAdd = []; rows.forEach(row => { const key = row.querySelector('.batch-key').value.trim(); const value = row.querySelector('.batch-value').value.trim(); if (key) { cookiesToAdd.push({ key, value }); } }); if (cookiesToAdd.length === 0) { alert('请输入至少一个有效的Cookie!'); return; } const promises = cookiesToAdd.map(cookie => cookieManager.set(cookie.key, cookie.value, { domain, path })); const results = await Promise.all(promises); const successCount = results.filter(Boolean).length; alert(`批量操作完成!\n成功设置了 ${successCount} / ${cookiesToAdd.length} 个Cookie。`); if (successCount > 0) { modalOverlay.style.display = 'none'; await renderPanelContent(); } }); cancelBtn.onclick = () => { modalOverlay.style.display = 'none'; }; }

    async function renderPanelContent() {
        isInSelectionMode = false;
        selectionPool.clear();
        const currentPanel = getPanel(); if (!currentPanel) return;
        currentPanel.className = '';

        currentPanel.innerHTML = `
            <div id="cookie-manager-header"><span>Cookie 管理器 ${!hasCookieStore ? '(兼容模式)' : ''}</span><span class="close-btn">&times;</span></div>
            <div id="cookie-manager-top-actions">
                <div class="actions-wrapper">
                    <div class="actions-row">
                        <button id="batch-add-btn">批量添加Cookie</button>
                        <button id="single-add-btn">单个添加Cookie</button>
                    </div>
                    <div class="actions-row">
                        <button id="copy-all-btn">一键复制所有Cookie</button>
                    </div>
                </div>
                <div class="search-wrapper">
                    <input type="search" id="search-by-key-input" placeholder="按 key 搜索...">
                    <input type="search" id="search-by-value-input" placeholder="按 value 搜索...">
                </div>
            </div>
            <div id="cookie-list-container"></div>
            <div id="cookie-manager-footer"></div>
        `;
        allCookies = await cookieManager.get();
        filteredCookies = allCookies;
        updateListView();
        addStaticPanelEventListeners();
    }
    
    function updateListView() {
        const currentPanel = getPanel();
        currentPanel.classList.remove('pagination-mode', 'scroll-mode');
        currentPanel.classList.add(`${viewMode}-mode`);

        if (viewMode === 'pagination') {
            totalPages = Math.ceil(filteredCookies.length / CONFIG.itemsPerPage);
            currentPage = 1;
        } else {
            totalPages = 1;
            currentPage = 1;
        }
        
        const listContainer = shadowRoot.querySelector('#cookie-list-container');
        const footer = shadowRoot.querySelector('#cookie-manager-footer');
        listContainer.innerHTML = ''; footer.innerHTML = '';
        
        if (filteredCookies.length === 0) {
            listContainer.innerHTML = '<p style="text-align:center;color:#888;padding:20px 0;">没有找到匹配的 Cookie。</p>';
        } else {
            renderPaginationControls();
            showPage(currentPage);
        }
    }
    
    function renderPaginationControls() {
        const footer = shadowRoot.querySelector('#cookie-manager-footer'); if (!footer) return;
        const pageInfoText = (totalPages > 1) ? `第 ${currentPage} / ${totalPages} 页` : '';

        let centerControlsHTML = '';
        if (isInSelectionMode) {
            const selectionCount = selectionPool.size;
            centerControlsHTML = `
                <button id="copy-selected-btn" title="复制选中的Cookie">复制已选(${selectionCount})</button>
                <button id="delete-selected-btn" title="删除选中的Cookie" style="background-color:#dc3545; color:white;">删除已选(${selectionCount})</button>
            `;
        } else {
            // 【⭐ 修改】按钮文本更新
            centerControlsHTML = `<button id="delete-all-btn" title="删除当前网站的所有Cookie">一键删除Cookie</button>`;
        }
        
        footer.innerHTML = `
            <div class="footer-actions">
                <button class="pagination-controls prev-btn" title="上一页">&laquo;</button>
            </div>
            <div class="pagination-center">
                <button id="selection-mode-btn">批量选择</button>
                ${centerControlsHTML}
                <button id="view-mode-toggle" title="切换视图模式">${viewMode === 'pagination' ? '滚动模式' : '分页模式'}</button>
                <span class="page-info"></span>
            </div>
            <div class="footer-actions">
                <button class="pagination-controls next-btn" title="下一页">&raquo;</button>
            </div>
        `;

        const selectionModeBtn = footer.querySelector('#selection-mode-btn');
        if (isInSelectionMode) { selectionModeBtn.textContent = '取消选择'; selectionModeBtn.style.backgroundColor = '#ffc107'; }

        footer.querySelector('.prev-btn').addEventListener('click', () => { if (currentPage > 1) showPage(currentPage - 1); });
        footer.querySelector('.next-btn').addEventListener('click', () => { if (currentPage < totalPages) showPage(currentPage + 1); });
        selectionModeBtn.addEventListener('click', () => { isInSelectionMode = !isInSelectionMode; if (!isInSelectionMode) { selectionPool.clear(); } getPanel().classList.toggle('selection-mode'); renderPaginationControls(); showPage(currentPage); });
        
        footer.querySelector('#view-mode-toggle').addEventListener('click', () => { viewMode = (viewMode === 'pagination') ? 'scroll' : 'pagination'; GM_setValue('cookieManagerViewMode', viewMode); updateListView(); });

        if (isInSelectionMode) {
            footer.querySelector('#copy-selected-btn').addEventListener('click', async () => { const selectedCookies = Array.from(selectionPool.values()); if (selectedCookies.length === 0) { alert('请至少选择一个Cookie!'); return; } const cookieString = selectedCookies.map(c => `${c.key}=${c.value}`).join('; '); await copyToClipboard(cookieString); await renderPanelContent(); });
            footer.querySelector('#delete-selected-btn').addEventListener('click', async () => { const selectedCookies = Array.from(selectionPool.values()); if (selectedCookies.length === 0) { alert('请至少选择一个Cookie!'); return; } if (confirm(`您确定要删除选中的 ${selectedCookies.length} 个Cookie吗?`)) { await Promise.all(selectedCookies.map(cookieManager.delete)); alert('删除成功!'); await renderPanelContent(); } });
        } else {
            footer.querySelector('#delete-all-btn').addEventListener('click', async () => { if (allCookies.length === 0) { alert('当前网站没有任何Cookie可删除。'); return; } if (confirm(`警告:您确定要删除当前网站的全部 ${allCookies.length} 个Cookie吗?\n此操作不可逆!`)) { await Promise.all(allCookies.map(cookieManager.delete)); alert('全部删除成功!'); await renderPanelContent(); } });
        }
        
        updatePaginationState();
    }
    
    function showPage(page) {
        currentPage = page; const container = shadowRoot.querySelector('#cookie-list-container'); container.innerHTML = '';
        const pageCookies = (viewMode === 'pagination') ? filteredCookies.slice((page - 1) * CONFIG.itemsPerPage, page * CONFIG.itemsPerPage) : filteredCookies;

        pageCookies.forEach(cookie => {
            const item = document.createElement('div'); item.className = 'cookie-item';
            const cookieId = getCookieId(cookie);
            const isChecked = selectionPool.has(cookieId);
            item.innerHTML = ` <div class="cookie-item-header"> <span class="selection-checkbox ${isChecked ? 'checked' : ''}" data-cookie-id='${cookieId}'>${isChecked ? '✓' : ''}</span> <span class="cookie-key">${decodeURIComponent(cookie.key)}</span> </div> <span class="cookie-value">${decodeURIComponent(cookie.value)}</span> ${hasCookieStore ? `<div class="cookie-props"> <span class="prop-label">Domain:</span> ${cookie.domain} | <span class="prop-label">Path:</span> ${cookie.path} </div>` : ''} <div class="cookie-actions"> <button class="modify-btn" data-cookie='${JSON.stringify(cookie)}'>修改</button> <button class="copy-btn" data-value="${encodeURIComponent(cookie.value)}">复制值</button> <button class="delete-btn" data-cookie='${JSON.stringify(cookie)}'>删除</button> </div>`;
            container.appendChild(item);
        });
        addDynamicCookieEventListeners(); updatePaginationState();
    }
    
    function updatePaginationState() { const pageInfo = shadowRoot.querySelector('.page-info'); const prevBtn = shadowRoot.querySelector('.prev-btn'); const nextBtn = shadowRoot.querySelector('.next-btn'); if (pageInfo) pageInfo.style.display = viewMode === 'pagination' ? 'inline' : 'none'; if (prevBtn) { prevBtn.style.visibility = (viewMode === 'pagination' && totalPages > 1) ? 'visible' : 'hidden'; prevBtn.disabled = (currentPage === 1); } if (nextBtn) { nextBtn.style.visibility = (viewMode === 'pagination' && totalPages > 1) ? 'visible' : 'hidden'; nextBtn.disabled = (currentPage >= totalPages); } }
    
    function addStaticPanelEventListeners() { const currentPanel = getPanel(); currentPanel.querySelector('.close-btn').addEventListener('click', () => currentPanel.style.display = 'none'); currentPanel.querySelector('#single-add-btn').addEventListener('click', () => { showEditModal({ mode: 'add' }); }); currentPanel.querySelector('#batch-add-btn').addEventListener('click', () => { showBatchAddModal(); }); currentPanel.querySelector('#copy-all-btn').addEventListener('click', async () => { if (allCookies.length === 0) { alert('当前网站没有任何Cookie可复制。'); return; } const cookieString = allCookies.map(c => `${c.key}=${c.value}`).join('; '); await copyToClipboard(cookieString); alert(`已复制全部 ${allCookies.length} 个Cookie到剪贴板!`); }); const keySearchInput = shadowRoot.getElementById('search-by-key-input'); const valueSearchInput = shadowRoot.getElementById('search-by-value-input'); function performSearch() { const keyTerm = keySearchInput.value.trim().toLowerCase(); const valueTerm = valueSearchInput.value.trim().toLowerCase(); filteredCookies = allCookies.filter(cookie => { const keyMatch = decodeURIComponent(cookie.key).toLowerCase().includes(keyTerm); const valueMatch = decodeURIComponent(cookie.value).toLowerCase().includes(valueTerm); return keyMatch && valueMatch; }); updateListView(); } keySearchInput.addEventListener('input', performSearch); valueSearchInput.addEventListener('input', performSearch); }
    function addDynamicCookieEventListeners() { shadowRoot.querySelectorAll('.modify-btn').forEach(btn => btn.addEventListener('click', (e) => { const cookie = JSON.parse(e.target.dataset.cookie); showEditModal({ mode: 'modify', cookie }); })); shadowRoot.querySelectorAll('.copy-btn').forEach(btn => btn.addEventListener('click', (e) => copyToClipboard(decodeURIComponent(e.target.dataset.value)))); shadowRoot.querySelectorAll('.delete-btn').forEach(btn => btn.addEventListener('click', async (e) => { const cookie = JSON.parse(e.target.dataset.cookie); if (confirm(`确定要删除 Cookie "${decodeURIComponent(cookie.key)}" 吗?`)) { await cookieManager.delete(cookie); alert('删除成功!'); await renderPanelContent(); } })); shadowRoot.querySelectorAll('.selection-checkbox').forEach(box => box.addEventListener('click', () => { const cookieId = box.dataset.cookieId; const isChecked = box.classList.toggle('checked'); box.textContent = isChecked ? '✓' : ''; if (isChecked) { const cookie = allCookies.find(c => getCookieId(c) === cookieId); if (cookie) selectionPool.set(cookieId, cookie); } else { selectionPool.delete(cookieId); } renderPaginationControls(); updatePaginationState(); })); }

    // --- ⭐ 守护神机制 ---
    function init() {
        if (document.readyState !== 'complete') {
            window.addEventListener('load', init, { once: true });
            return;
        }
        createUI();
        setInterval(() => {
            if (!document.getElementById('cookie-manager-host')) {
                createUI();
            }
        }, 2000);
    }

    function createUI() {
        if (document.getElementById('cookie-manager-host')) { return; }
        if (!document.body) { setTimeout(createUI, 100); return; }
        
        const hostElement = document.createElement('div'); hostElement.id = 'cookie-manager-host'; document.body.appendChild(hostElement);
        shadowRoot = hostElement.attachShadow({ mode: 'open' });
        const styleEl = document.createElement('style'); styleEl.textContent = getStyles(); shadowRoot.appendChild(styleEl);
        const panelEl = document.createElement('div'); panelEl.id = 'cookie-manager-panel'; shadowRoot.appendChild(panelEl);
        const editModalEl = document.createElement('div'); editModalEl.id = 'edit-modal-overlay'; editModalEl.innerHTML = ` <div id="edit-modal-content"> <h3 id="modal-title">编辑 Cookie</h3> <div class="form-group"> <label for="cookie-key-input">名称 (Key)</label> <input type="text" id="cookie-key-input"> </div> <div class="form-group"> <label for="cookie-value-input">值 (Value)</label> <textarea id="cookie-value-input" class="editable"></textarea> </div> <div class="form-group"> <label for="cookie-domain-input">域 (Domain)</label> <input type="text" id="cookie-domain-input" placeholder="留空则为当前域名"> </div> <div class="form-group"> <label for="cookie-path-input">路径 (Path)</label> <input type="text" id="cookie-path-input" placeholder="默认为 /"> </div> <div class="form-group"> <label for="cookie-expires-input">有效期 (天)</label> <input type="number" id="cookie-expires-input" value="365"> </div> <div id="modal-actions"> <button id="modal-cancel-btn">取消</button> <button id="modal-save-btn">保存</button> </div> </div> `; shadowRoot.appendChild(editModalEl);
        const batchAddModalEl = document.createElement('div'); batchAddModalEl.id = 'batch-add-modal-overlay'; batchAddModalEl.innerHTML = ` <div id="batch-add-modal-content"> <h3>批量添加 Cookie</h3> <div class="batch-add-props"> <div class="form-group"> <label for="batch-domain-input">应用于 Domain</label> <input type="text" id="batch-domain-input" placeholder="留空则为当前域名"> </div> <div class="form-group"> <label for="batch-path-input">应用于 Path</label> <input type="text" id="batch-path-input" placeholder="默认为 /"> </div> </div> <div class="form-group"> <label>粘贴自动匹配 Key 和 Value</label> <textarea id="auto-match-area" placeholder="在此粘贴,可自动识别并填充下方列表。\n支持格式:\nkey1=value1\nkey2:value2\nkey3=value3; key4=value4" style="height: 60px;"></textarea> </div> <div id="batch-input-area"></div> <button id="add-row-btn">+ 添加一行</button> <div id="modal-actions"> <button id="batch-add-cancel-btn">取消</button> <button id="batch-add-save-btn">全部添加</button> </div> </div>`; shadowRoot.appendChild(batchAddModalEl);
        const btnWrapper = document.createElement('div'); btnWrapper.id = 'cookie-manager-btn-wrapper'; const btnInner = document.createElement('div'); btnInner.id = 'cookie-manager-btn-inner';
        if (CONFIG.iconImageUrl) { const img = document.createElement('img'); img.src = CONFIG.iconImageUrl; img.onerror = function() { console.error("Cookie Manager: 自定义图标加载失败! URL:", CONFIG.iconImageUrl); if (img.parentNode) { img.parentNode.removeChild(img); } btnInner.textContent = 'C'; btnInner.style.backgroundColor = ''; }; btnInner.appendChild(img); btnInner.style.backgroundColor = 'transparent'; } else { btnInner.textContent = 'C'; }
        btnWrapper.appendChild(btnInner); shadowRoot.appendChild(btnWrapper);
        const savedPosition = GM_getValue('iconPosition', CONFIG.initialPosition); btnWrapper.style.top = savedPosition.top || 'auto'; btnWrapper.style.left = savedPosition.left || 'auto'; btnWrapper.style.bottom = savedPosition.bottom || 'auto'; btnWrapper.style.right = savedPosition.right || 'auto';
        let isDragging = false, wasDragged = false, dragStartX, dragStartY, initialLeft, initialTop;
        const onDragStart = (e) => { isDragging = true; wasDragged = false; const touch = e.type.startsWith('touch') ? e.targetTouches[0] : e; dragStartX = touch.clientX; dragStartY = touch.clientY; const rect = btnWrapper.getBoundingClientRect(); initialLeft = rect.left; initialTop = rect.top; btnWrapper.style.transition = 'none'; };
        const onDragMove = (e) => { if (!isDragging) return; e.preventDefault(); wasDragged = true; const touch = e.type.startsWith('touch') ? e.targetTouches[0] : e; let newX = initialLeft + (touch.clientX - dragStartX); let newY = initialTop + (touch.clientY - dragStartY); newX = Math.max(0, Math.min(newX, window.innerWidth - btnWrapper.offsetWidth)); newY = Math.max(0, Math.min(newY, window.innerHeight - btnWrapper.offsetHeight)); btnWrapper.style.left = `${newX}px`; btnWrapper.style.top = `${newY}px`; btnWrapper.style.right = 'auto'; btnWrapper.style.bottom = 'auto'; };
        const onDragEnd = () => { if (!isDragging) return; isDragging = false; const finalRect = btnWrapper.getBoundingClientRect(); const newPosition = { top: `${finalRect.top}px`, left: `${finalRect.left}px`, bottom: 'auto', right: 'auto' }; GM_setValue('iconPosition', newPosition); setTimeout(() => { wasDragged = false; }, 50); };
        btnWrapper.addEventListener('touchstart', onDragStart, { passive: false }); btnWrapper.addEventListener('touchmove', onDragMove, { passive: false }); btnWrapper.addEventListener('touchend', onDragEnd); btnWrapper.addEventListener('touchcancel', onDragEnd);
        btnWrapper.addEventListener('mousedown', onDragStart); document.addEventListener('mousemove', onDragMove); document.addEventListener('mouseup', onDragEnd);
        btnWrapper.addEventListener('click', async (e) => { if (wasDragged) { e.stopPropagation(); return; } const currentPanel = getPanel(); if (currentPanel && currentPanel.style.display === 'flex') { currentPanel.style.display = 'none'; } else { await renderPanelContent(); currentPanel.style.display = 'flex'; } });
    }

    init();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址