// ==UserScript== // @name 网页数据清除与管理 // @namespace http://tampermonkey.net/ // @version 2.1 // @description 菜单仅含“⚙️ 菜单设置”,所有功能集中在对话框内 // @author Chaos // @match *://*/* // @icon data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23444' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 6 L5 19 A1 1 0 0 0 6 20 L18 20 A1 1 0 0 0 19 19 L19 6 Z'%3E%3C/path%3E%3Cpath d='M4 6 L20 6 L19 4 L5 4 Z'%3E%3C/path%3E%3Cpath d='M9 10 L15 16 M15 10 L9 16'%3E%3C/path%3E%3C/svg%3E // @grant GM_registerMenuCommand // @grant GM_setClipboard // @grant GM_unregisterMenuCommand // @grant GM_getValue // @grant GM_setValue // @run-at document-end // @license MIT // ==/UserScript== (function() { 'use strict'; // 所有可用功能列表(保留用于菜单设置,但主菜单仅显示“菜单设置”) const ALL_FEATURES = { clearAllDetailed: { nameKey: 'clearAllDetailed', defaultInMain: false }, clearCookies: { nameKey: 'clearCookies', defaultInMain: false }, clearSessionStorage: { nameKey: 'clearSessionStorage', defaultInMain: false }, clearLocalStorage: { nameKey: 'clearLocalStorage', defaultInMain: false }, clearIndexedDB: { nameKey: 'clearIndexedDB', defaultInMain: false }, clearCacheStorage: { nameKey: 'clearCacheStorage', defaultInMain: false }, clearServiceWorkers: { nameKey: 'clearServiceWorkers', defaultInMain: false }, clearFrameStorage: { nameKey: 'clearFrameStorage', defaultInMain: false }, copyCookies: { nameKey: 'copyCookies', defaultInMain: false }, clearGuide: { nameKey: 'clearGuide', defaultInMain: false }, switchLang: { nameKey: 'switchLang', defaultInMain: false }, menuSettings: { nameKey: 'menuSettings', defaultInMain: false } }; // 多语言包(仅保留核心翻译,其他语言可在需要时添加) const translations = { 'zh-CN': { // 功能名称 clearAllDetailed: '清除 %s 所有数据(详细)', clearCookies: '清除 %s Cookie(包括子域名)', clearLocalStorage: '清除 %s 本地存储', clearSessionStorage: '清除 %s 会话存储', clearIndexedDB: '清除 %s IndexedDB数据库', clearCacheStorage: '清除 %s 缓存存储', clearServiceWorkers: '注销 %s Service Worker', clearFrameStorage: '清除 %s 同域框架存储', copyCookies: '复制当前Cookie', clearGuide: '数据清理指南', switchLang: '切换语言', menuSettings: '⚙️ 菜单设置', // 对话框标签 menuSettingsTitle: '网页数据清除工具', selectMainMenu: '选择显示在主菜单的功能(已弃用,所有功能在此界面)', saveSettings: '保存设置', settingsSaved: '菜单设置已保存!请刷新页面生效', // 工具按钮文字 toolClearAll: '清理当前网站所有数据', toolClearCookies: '清理当前网站Cookie', toolClearLocalStorage: '清理本地存储', toolClearSessionStorage: '清理会话存储', toolClearIndexedDB: '清理IndexedDB', toolClearCacheStorage: '清理缓存存储', toolClearServiceWorkers: '注销Service Worker', toolClearFrameStorage: '清理同域框架存储', toolCopyCookies: '复制当前Cookie', toolGuide: '查看清理指南', // 其他 selectLang: '选择语言', langZhCN: '简体中文', langEn: 'English', cancel: '取消', // 清理结果提示 allClearedDetailed: '已清除 %s 的所有数据:\n%s\n\n注意:部分受保护数据(如HttpOnly Cookie)可能无法清除。如需彻底清理,建议使用浏览器的"清除网站数据"功能。', cookieCleared: '已清除 %s 的Cookie:\n- 总清除:%d 个\n- 剩余:%d 个(可能受保护)', localStorageCleared: '已清除 %s 的本地存储:\n- 共清除 %d 项数据', sessionStorageCleared: '已清除 %s 的会话存储:\n- 共清除 %d 项数据', indexedDBCleared: '已清除 %s 的IndexedDB数据库:\n- 共清除 %d 个数据库', cacheStorageCleared: '已清除 %s 的缓存存储:\n- 共清除 %d 个缓存', serviceWorkersCleared: '已注销 %s 的Service Worker:\n- 共注销 %d 个', frameStorageCleared: '已清除 %s 的同域框架存储:\n- 共清理 %d 个框架\n- 清除数据项:%d 个', noCookies: '未检测到 %s 的Cookie可清除', noLocalStorage: '未检测到 %s 的本地存储数据', noSessionStorage: '未检测到 %s 的会话存储数据', noIndexedDB: '未检测到 %s 的IndexedDB数据库', noCacheStorage: '未检测到 %s 的缓存存储', noServiceWorkers: '未检测到 %s 的Service Worker', noFrames: '未检测到 %s 的同域框架', nothingCleared: '未清除到 %s 的任何数据', cookiesCopied: '已复制Cookie到剪贴板', noCookiesToCopy: '无Cookie可复制', guide: `网页数据清理完全指南: 1. 客户端脚本可清理的数据类型: - Cookie(非HttpOnly类型) - 本地存储(LocalStorage) - 会话存储(SessionStorage) - IndexedDB数据库 - 缓存存储(Cache Storage) - Service Worker - 同域框架内存储 2. 需要手动清理的数据类型: - HttpOnly Cookie(受浏览器保护) - 跨域关联数据(如第三方登录信息) - 浏览器级缓存(如DNS缓存) 3. 手动清理步骤: - 打开浏览器设置 → 隐私与安全 - 找到"清除浏览数据"或类似选项 - 勾选"Cookie和网站数据"及"缓存的图片和文件" - 选择合适的时间范围(建议"所有时间") - 点击"清除数据"完成操作 4. 强制刷新页面(清理后推荐): - 长按刷新按钮 → 选择"强制刷新" - 或使用快捷键:Ctrl+Shift+R (Windows) / Cmd+Shift+R (Mac)`, crossDomainNote: '注意:只能清除当前可访问域名的Cookie。其他存储数据需打开对应网站后使用本工具。' }, 'en': { clearAllDetailed: 'Clear %s all data (detailed)', clearCookies: 'Clear %s Cookies (including subdomains)', clearLocalStorage: 'Clear %s Local Storage', clearSessionStorage: 'Clear %s Session Storage', clearIndexedDB: 'Clear %s IndexedDB Databases', clearCacheStorage: 'Clear %s Cache Storage', clearServiceWorkers: 'Unregister %s Service Workers', clearFrameStorage: 'Clear %s Same-Domain Frame Storage', copyCookies: 'Copy Current Cookies', clearGuide: 'Data Clearing Guide', switchLang: 'Switch Language', menuSettings: '⚙️ Menu Settings', menuSettingsTitle: 'Web Data Cleaner', selectMainMenu: 'Select features to show in main menu (deprecated, all features here)', saveSettings: 'Save Settings', settingsSaved: 'Menu settings saved! Refresh to apply.', toolClearAll: 'Clear All Data for Current Site', toolClearCookies: 'Clear Cookies for Current Site', toolClearLocalStorage: 'Clear Local Storage', toolClearSessionStorage: 'Clear Session Storage', toolClearIndexedDB: 'Clear IndexedDB', toolClearCacheStorage: 'Clear Cache Storage', toolClearServiceWorkers: 'Unregister Service Worker', toolClearFrameStorage: 'Clear Same-Domain Frame Storage', toolCopyCookies: 'Copy Current Cookies', toolGuide: 'View Clearing Guide', selectLang: 'Select Language', langZhCN: 'Simplified Chinese', langEn: 'English', cancel: 'Cancel', allClearedDetailed: 'All data cleared for %s:\n%s\n\nNote: Some protected data (like HttpOnly Cookies) may not be cleared. Use browser\'s "Clear site data" for full cleaning.', cookieCleared: 'Cookies cleared for %s:\n- Total cleared: %d\n- Remaining: %d (may be protected)', localStorageCleared: 'Local Storage cleared for %s:\n- Total items cleared: %d', sessionStorageCleared: 'Session Storage cleared for %s:\n- Total items cleared: %d', indexedDBCleared: 'IndexedDB databases cleared for %s:\n- Total databases cleared: %d', cacheStorageCleared: 'Cache Storage cleared for %s:\n- Total caches cleared: %d', serviceWorkersCleared: 'Service Workers unregistered for %s:\n- Total unregistered: %d', frameStorageCleared: 'Same-domain frame storage cleared for %s:\n- Frames processed: %d\n- Items cleared: %d', noCookies: 'No cookies detected for %s', noLocalStorage: 'No Local Storage data detected for %s', noSessionStorage: 'No Session Storage data detected for %s', noIndexedDB: 'No IndexedDB databases detected for %s', noCacheStorage: 'No Cache Storage detected for %s', noServiceWorkers: 'No Service Workers detected for %s', noFrames: 'No same-domain frames detected for %s', nothingCleared: 'No data cleared for %s', cookiesCopied: 'Cookies copied to clipboard', noCookiesToCopy: 'No cookies to copy', guide: `Complete Web Data Clearing Guide: 1. Data types that can be cleared by client scripts: - Cookies (non-HttpOnly types) - Local Storage - Session Storage - IndexedDB databases - Cache Storage - Service Workers - Storage within same-domain frames 2. Data types requiring manual clearing: - HttpOnly Cookies (protected by browser) - Cross-domain related data (e.g., third-party login info) - Browser-level cache (e.g., DNS cache) 3. Manual clearing steps: - Open browser settings → Privacy & Security - Find "Clear browsing data" or similar option - Check "Cookies and site data" and "Cached images and files" - Select appropriate time range (recommend "All time") - Click "Clear data" to complete 4. Force refresh page (recommended after clearing): - Long press refresh button → Select "Force refresh" - Or use shortcut: Ctrl+Shift+R (Windows) / Cmd+Shift+R (Mac)`, crossDomainNote: 'Note: Only cookies accessible from current domain can be cleared. Other storage requires opening the target site.' } }; // 核心状态管理 let state = { domain: window.location.hostname, subdomains: getAllPossibleSubdomains(window.location.hostname), currentLang: getInitialLanguage(), menuSettings: getMenuSettings() }; function getMenuSettings() { const saved = GM_getValue('menu_settings', null); if (saved) return saved; const defaultSettings = {}; Object.keys(ALL_FEATURES).forEach(key => { defaultSettings[key] = ALL_FEATURES[key].defaultInMain; }); return defaultSettings; } function saveMenuSettings(settings) { GM_setValue('menu_settings', settings); state.menuSettings = settings; } function getInitialLanguage() { const savedLang = GM_getValue('preferred_language', null); if (savedLang && translations[savedLang]) return savedLang; const browserLang = navigator.language || navigator.userLanguage; if (browserLang.startsWith('zh')) return 'zh-CN'; return 'en'; } function t(key, ...args) { const translation = translations[state.currentLang]?.[key] || translations['en'][key]; return translation ? sprintf(translation, ...args) : key; } function sprintf(str, ...args) { return str.replace(/%s|%d/g, () => args.length ? args.shift() : ''); } function getAllPossibleSubdomains(hostname) { const parts = hostname.split('.'); const subdomains = new Set(); subdomains.add(hostname); subdomains.add(hostname.replace(/^www\./i, '')); subdomains.add('.' + hostname); for (let i = 0; i < parts.length; i++) { for (let j = i; j < parts.length; j++) { const sub = parts.slice(i, j + 1).join('.'); if (sub) { subdomains.add(sub); subdomains.add('.' + sub); } } } return Array.from(subdomains); } // 清理功能实现(同原脚本,略作整理) const DetailedCleaner = { clearCookies(domain = state.domain, subdomains = state.subdomains) { const initialCookies = document.cookie ? document.cookie.split('; ').filter(c => c).length : 0; if (initialCookies === 0) return { cleared: 0, remaining: 0 }; const paths = ['/', '/login', '/account', '/auth', '/user', '/api', '/service', '/admin']; document.cookie.split('; ').forEach(cookie => { const name = cookie.split('=')[0]; paths.forEach(path => { subdomains.forEach(subDomain => { document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}; domain=${subDomain};`; document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path};`; document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=${subDomain};`; }); }); }); const remainingCookies = document.cookie ? document.cookie.split('; ').filter(c => c).length : 0; return { cleared: initialCookies - remainingCookies, remaining: remainingCookies }; }, clearLocalStorage() { const count = localStorage.length; if (count) localStorage.clear(); return count; }, clearSessionStorage() { const count = sessionStorage.length; if (count) sessionStorage.clear(); return count; }, clearIndexedDB() { return new Promise(resolve => { if (!window.indexedDB) resolve(0); else indexedDB.databases().then(dbs => { let cleared = 0; const total = dbs.length; if (!total) resolve(0); dbs.forEach((db, i) => { if (db.name) { const req = indexedDB.deleteDatabase(db.name); req.onsuccess = () => { cleared++; if (i === total - 1) resolve(cleared); }; req.onerror = () => { if (i === total - 1) resolve(cleared); }; } else if (i === total - 1) resolve(cleared); }); }).catch(() => resolve(0)); }); }, clearCacheStorage() { return new Promise(resolve => { if (!('caches' in window)) resolve(0); else caches.keys().then(names => { let cleared = 0; const total = names.length; if (!total) resolve(0); names.forEach((name, i) => { if (name.includes(state.domain) || name.includes(state.domain.replace(/^www\./, ''))) { caches.delete(name).then(success => { if (success) cleared++; if (i === total - 1) resolve(cleared); }); } else if (i === total - 1) resolve(cleared); }); }).catch(() => resolve(0)); }); }, clearServiceWorkers() { return new Promise(resolve => { if (!('serviceWorker' in navigator)) resolve(0); else navigator.serviceWorker.getRegistrations().then(regs => { let cleared = 0; const total = regs.length; if (!total) resolve(0); regs.forEach((reg, i) => { if (reg.scope.includes(state.domain)) { reg.unregister().then(success => { if (success) cleared++; if (i === total - 1) resolve(cleared); }); } else if (i === total - 1) resolve(cleared); }); }).catch(() => resolve(0)); }); }, clearFrameStorage() { let frameCount = 0, itemCount = 0; const frames = document.querySelectorAll('iframe, frame, embed, object'); frames.forEach(frame => { try { if (frame.contentWindow && frame.contentWindow.location.hostname && (state.subdomains.includes(frame.contentWindow.location.hostname) || frame.contentWindow.location.hostname.includes(state.domain))) { frameCount++; const local = frame.contentWindow.localStorage.length; if (local) { frame.contentWindow.localStorage.clear(); itemCount += local; } const session = frame.contentWindow.sessionStorage.length; if (session) { frame.contentWindow.sessionStorage.clear(); itemCount += session; } } } catch(e) {} }); return { frameCount, itemCount }; } }; // 结果处理(直接调用清理函数并弹窗) const ResultHandler = { async handleClearAll() { const cookie = DetailedCleaner.clearCookies(); const local = DetailedCleaner.clearLocalStorage(); const session = DetailedCleaner.clearSessionStorage(); const indexedDB = await DetailedCleaner.clearIndexedDB(); const cache = await DetailedCleaner.clearCacheStorage(); const sw = await DetailedCleaner.clearServiceWorkers(); const frame = DetailedCleaner.clearFrameStorage(); const results = []; if (cookie.cleared) results.push(`- Cookie:${cookie.cleared} 个(剩余 ${cookie.remaining} 个)`); if (local) results.push(`- 本地存储:${local} 项`); if (session) results.push(`- 会话存储:${session} 项`); if (indexedDB) results.push(`- IndexedDB:${indexedDB} 个数据库`); if (cache) results.push(`- 缓存存储:${cache} 个`); if (sw) results.push(`- Service Worker:${sw} 个`); if (frame.itemCount) results.push(`- 同域框架:${frame.itemCount} 项(${frame.frameCount} 个框架)`); if (results.length) alert(t('allClearedDetailed', state.domain, results.join('\n'))); else alert(t('nothingCleared', state.domain)); if (results.length) location.reload(); }, handleCookies() { const result = DetailedCleaner.clearCookies(); if (result.cleared) alert(t('cookieCleared', state.domain, result.cleared, result.remaining)); else alert(t('noCookies', state.domain)); if (result.cleared) location.reload(); }, handleLocalStorage() { const count = DetailedCleaner.clearLocalStorage(); if (count) alert(t('localStorageCleared', state.domain, count)); else alert(t('noLocalStorage', state.domain)); if (count) location.reload(); }, handleSessionStorage() { const count = DetailedCleaner.clearSessionStorage(); if (count) alert(t('sessionStorageCleared', state.domain, count)); else alert(t('noSessionStorage', state.domain)); if (count) location.reload(); }, async handleIndexedDB() { const count = await DetailedCleaner.clearIndexedDB(); if (count) alert(t('indexedDBCleared', state.domain, count)); else alert(t('noIndexedDB', state.domain)); if (count) location.reload(); }, async handleCacheStorage() { const count = await DetailedCleaner.clearCacheStorage(); if (count) alert(t('cacheStorageCleared', state.domain, count)); else alert(t('noCacheStorage', state.domain)); if (count) location.reload(); }, async handleServiceWorkers() { const count = await DetailedCleaner.clearServiceWorkers(); if (count) alert(t('serviceWorkersCleared', state.domain, count)); else alert(t('noServiceWorkers', state.domain)); if (count) location.reload(); }, handleFrameStorage() { const result = DetailedCleaner.clearFrameStorage(); if (result.itemCount) alert(t('frameStorageCleared', state.domain, result.frameCount, result.itemCount)); else alert(t('noFrames', state.domain)); if (result.itemCount) location.reload(); }, copyCookies() { const cookies = document.cookie; if (cookies) { GM_setClipboard(cookies); alert(t('cookiesCopied')); } else alert(t('noCookiesToCopy')); }, showGuide() { alert(t('guide')); } }; // 创建主对话框(包含所有功能) function createMainDialog() { const existing = document.querySelector('#data-cleaner-dialog'); if (existing) existing.remove(); const overlay = document.createElement('div'); overlay.id = 'data-cleaner-overlay'; overlay.style.cssText = `position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:999998;`; const dialog = document.createElement('div'); dialog.id = 'data-cleaner-dialog'; dialog.style.cssText = `position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);padding:20px;background:white;border-radius:8px;box-shadow:0 2px 15px rgba(0,0,0,0.3);z-index:999999;min-width:320px;max-width:500px;max-height:80vh;overflow-y:auto;font-family:sans-serif;`; // 标题 const title = document.createElement('h3'); title.textContent = t('menuSettingsTitle'); title.style.margin = '0 0 15px 0'; title.style.textAlign = 'center'; dialog.appendChild(title); // 工具按钮区 const toolSection = document.createElement('div'); toolSection.style.marginBottom = '20px'; const toolTitle = document.createElement('h4'); toolTitle.textContent = '🔧 数据清理工具'; toolTitle.style.margin = '0 0 10px 0'; toolSection.appendChild(toolTitle); const buttonGrid = document.createElement('div'); buttonGrid.style.display = 'grid'; buttonGrid.style.gridTemplateColumns = 'repeat(2, 1fr)'; buttonGrid.style.gap = '8px'; const tools = [ { text: 'toolClearAll', handler: () => ResultHandler.handleClearAll() }, { text: 'toolClearCookies', handler: () => ResultHandler.handleCookies() }, { text: 'toolClearLocalStorage', handler: () => ResultHandler.handleLocalStorage() }, { text: 'toolClearSessionStorage', handler: () => ResultHandler.handleSessionStorage() }, { text: 'toolClearIndexedDB', handler: () => ResultHandler.handleIndexedDB() }, { text: 'toolClearCacheStorage', handler: () => ResultHandler.handleCacheStorage() }, { text: 'toolClearServiceWorkers', handler: () => ResultHandler.handleServiceWorkers() }, { text: 'toolClearFrameStorage', handler: () => ResultHandler.handleFrameStorage() }, { text: 'toolCopyCookies', handler: () => ResultHandler.copyCookies() }, { text: 'toolGuide', handler: () => ResultHandler.showGuide() } ]; tools.forEach(tool => { const btn = document.createElement('button'); btn.textContent = t(tool.text); btn.style.cssText = `padding:8px;cursor:pointer;border:1px solid #ccc;border-radius:4px;background:#f5f5f5;transition:0.2s;`; btn.onmouseover = () => btn.style.background = '#e9e9e9'; btn.onmouseout = () => btn.style.background = '#f5f5f5'; btn.onclick = () => { tool.handler(); dialog.remove(); overlay.remove(); }; buttonGrid.appendChild(btn); }); toolSection.appendChild(buttonGrid); dialog.appendChild(toolSection); // 语言切换 const langSection = document.createElement('div'); langSection.style.marginBottom = '20px'; const langTitle = document.createElement('h4'); langTitle.textContent = t('switchLang'); langSection.appendChild(langTitle); const langButtons = document.createElement('div'); langButtons.style.display = 'flex'; langButtons.style.gap = '8px'; const langs = [ { code: 'zh-CN', label: 'langZhCN' }, { code: 'en', label: 'langEn' } ]; langs.forEach(lang => { const btn = document.createElement('button'); btn.textContent = t(lang.label); btn.style.cssText = `padding:6px 12px;border:1px solid #ccc;border-radius:4px;cursor:pointer;background:${state.currentLang === lang.code ? '#e0e0e0' : 'white'}`; btn.onclick = () => { if (lang.code !== state.currentLang) { state.currentLang = lang.code; GM_setValue('preferred_language', lang.code); dialog.remove(); overlay.remove(); createMainDialog(); // 重新打开以刷新语言 } else { dialog.remove(); overlay.remove(); } }; langButtons.appendChild(btn); }); langSection.appendChild(langButtons); dialog.appendChild(langSection); // 关闭按钮 const closeBtn = document.createElement('button'); closeBtn.textContent = t('cancel'); closeBtn.style.cssText = 'padding:8px 16px;background:#6c757d;color:white;border:none;border-radius:4px;cursor:pointer;display:block;margin:0 auto;'; closeBtn.onclick = () => { dialog.remove(); overlay.remove(); }; dialog.appendChild(closeBtn); document.body.appendChild(overlay); document.body.appendChild(dialog); } // 注册主菜单(仅一个菜单项,显示为“⚙️ 菜单设置”) let menuId = null; function registerMainMenu() { if (menuId) GM_unregisterMenuCommand(menuId); // 直接使用带图标的菜单文本,不再使用翻译 menuId = GM_registerMenuCommand('⚙️ 菜单设置', createMainDialog); } // 语言变化时重新注册菜单(保持菜单文字同步,但这里菜单文字固定为带emoji,无需更新) // 保留原函数但无实际作用,可删,保留不影响 function switchLanguageAndReload(lang) { if (translations[lang] && lang !== state.currentLang) { state.currentLang = lang; GM_setValue('preferred_language', lang); registerMainMenu(); } } // 初始注册 registerMainMenu(); })();