// ==UserScript== // @name 小号切换器 // @namespace http://tampermonkey.net/ // @version 4.0.0 // @description 多账号切换 | localStorage + Cookie (GM_cookie) | 直接存储对象,分散存储便于查看 // @author xcv2 // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_addStyle // @grant GM_notification // @grant GM_cookie // @run-at document-end // ==/UserScript== (function() { 'use strict'; GM_addStyle(` /* ---------- 主弹窗样式 ---------- */ #as-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:999999;display:none;justify-content:center;align-items:center;font-family:system-ui,sans-serif}#as-overlay.active{display:flex}#as-modal{background:#fff;border-radius:16px;width:540px;max-width:94vw;max-height:88vh;box-shadow:0 20px 60px rgba(0,0,0,0.3);display:flex;flex-direction:column;overflow:hidden;animation:asFadeIn .2s ease}@keyframes asFadeIn{from{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}#as-header{padding:18px 24px;border-bottom:1px solid #e8ecf0;display:flex;justify-content:space-between;align-items:center;flex-shrink:0;background:#fafbfc}#as-header h2{margin:0;font-size:18px;font-weight:600;color:#1a1a2e;display:flex;align-items:center;gap:8px}#as-header h2 small{font-size:13px;font-weight:400;color:#888;margin-left:6px}#as-close-btn{background:none;border:none;font-size:28px;cursor:pointer;color:#999;padding:0 4px;line-height:1;transition:color .2s}#as-close-btn:hover{color:#333}#as-body{padding:16px 24px 20px;overflow-y:auto;flex:1}#as-status{background:#f6f8fa;border-radius:10px;padding:10px 16px;margin-bottom:16px;font-size:14px;color:#333;display:flex;align-items:center;gap:8px;flex-wrap:wrap}#as-status .label{color:#888;font-weight:400}#as-status .name{font-weight:600;color:#1a1a2e}#as-status .type-badge{background:#e8ecf0;padding:2px 10px;border-radius:12px;font-size:12px;color:#555}#as-status .empty-hint{color:#aaa;font-style:italic}#as-account-list{list-style:none;padding:0;margin:0 0 16px 0;max-height:280px;overflow-y:auto;border:1px solid #e8ecf0;border-radius:10px}#as-account-list li{display:flex;justify-content:space-between;align-items:center;padding:10px 16px;border-bottom:1px solid #f0f2f5}#as-account-list li:last-child{border-bottom:none}#as-account-list li:hover{background:#f8f9fb}#as-account-list li.active{background:#eef4ff}#as-account-list .info{display:flex;align-items:center;gap:10px;flex:1;min-width:0}#as-account-list .name{font-weight:500;color:#1a1a2e;font-size:14px}#as-account-list .type-badge{font-size:11px;padding:2px 10px;border-radius:12px;background:#e8ecf0;color:#555;flex-shrink:0}#as-account-list .type-badge.localstorage{background:#dbeafe;color:#1e40af}#as-account-list .type-badge.cookie{background:#fce4ec;color:#b71c1c}#as-account-list .type-badge.both{background:#e8f5e9;color:#1b5e20}#as-account-list .active-mark{color:#2563eb;font-size:14px;margin-left:4px;flex-shrink:0}#as-account-list .actions{display:flex;gap:4px;flex-shrink:0}#as-account-list .actions button{background:none;border:none;padding:4px 10px;border-radius:6px;font-size:13px;cursor:pointer;transition:background .15s;color:#555}#as-account-list .actions .switch-btn{color:#2563eb;background:#eef4ff}#as-account-list .actions .switch-btn:hover{background:#dbeafe}#as-account-list .actions .delete-btn{color:#dc2626}#as-account-list .actions .delete-btn:hover{background:#fee2e2}#as-account-list .actions .rename-btn{color:#f59e0b}#as-account-list .actions .rename-btn:hover{background:#fef3c7}#as-account-list .empty-msg{padding:30px 16px;text-align:center;color:#aaa;font-size:14px}#as-add-section{border-top:1px solid #e8ecf0;padding-top:16px;margin-top:4px}#as-add-section .row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}#as-add-section .row>*{flex:1 1 auto}#as-add-section input[type=text]{flex:2 1 140px;padding:8px 12px;border:1px solid #d1d5db;border-radius:8px;font-size:14px;outline:none;transition:border-color .2s;min-width:80px}#as-add-section input[type=text]:focus{border-color:#2563eb;box-shadow:0 0 0 3px rgba(37,99,235,.1)}#as-add-section select{padding:8px 12px;border:1px solid #d1d5db;border-radius:8px;font-size:14px;background:#fff;outline:none;cursor:pointer;flex:1 1 100px}#as-add-section select:focus{border-color:#2563eb}#as-add-btn{padding:8px 20px;background:#2563eb;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s;flex:0 0 auto}#as-add-btn:hover{background:#1d4ed8}#as-add-btn:active{transform:scale(.97)}#as-footer{padding:12px 24px 18px;border-top:1px solid #e8ecf0;display:flex;justify-content:flex-end;gap:10px;flex-shrink:0;background:#fafbfc}#as-footer button{padding:8px 18px;border:none;border-radius:8px;font-size:14px;cursor:pointer;transition:background .2s}#as-footer .view-btn{background:#e8ecf0;color:#333;margin-right:auto}#as-footer .view-btn:hover{background:#d1d5db}#as-refresh-btn{background:#e8ecf0;color:#333}#as-refresh-btn:hover{background:#d1d5db}#as-close-footer-btn{background:#2563eb;color:#fff}#as-close-footer-btn:hover{background:#1d4ed8}@media(max-width:480px){#as-modal{width:96vw;max-height:90vh}#as-header{padding:14px 16px}#as-body{padding:12px 16px 16px}#as-add-section .row{flex-direction:column;align-items:stretch}#as-add-section input[type=text],#as-add-section select,#as-add-btn{flex:1 1 auto}#as-footer{flex-direction:column}#as-footer button{width:100%}#as-footer .view-btn{margin-right:0}} /* ---------- 数据查看器 ---------- */ #as-data-viewer-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);z-index:1000000;display:none;justify-content:center;align-items:center;font-family:system-ui,sans-serif}#as-data-viewer-overlay.active{display:flex}#as-data-viewer{background:#fff;border-radius:16px;width:700px;max-width:92vw;max-height:85vh;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,0.4);overflow:hidden}#as-data-viewer-header{padding:16px 20px;border-bottom:1px solid #e8ecf0;display:flex;justify-content:space-between;align-items:center;flex-shrink:0;background:#fafbfc}#as-data-viewer-header h3{margin:0;font-size:16px;font-weight:600;color:#1a1a2e}#as-data-viewer-close{background:none;border:none;font-size:24px;cursor:pointer;color:#999;padding:0 4px;line-height:1}#as-data-viewer-close:hover{color:#333}#as-data-viewer-body{padding:16px 20px 20px;overflow-y:auto;flex:1}#as-data-viewer-body pre{background:#f6f8fa;padding:16px;border-radius:10px;font-size:13px;font-family:Menlo,Consolas,monospace;white-space:pre-wrap;word-wrap:break-word;margin:0;max-height:60vh;overflow-y:auto;border:1px solid #e8ecf0}#as-data-viewer-footer{padding:12px 20px 18px;border-top:1px solid #e8ecf0;display:flex;justify-content:flex-end;gap:10px;flex-shrink:0;background:#fafbfc}#as-data-viewer-footer button{padding:6px 16px;border:none;border-radius:8px;font-size:13px;cursor:pointer;transition:background .2s}#as-data-copy-btn{background:#2563eb;color:#fff}#as-data-copy-btn:hover{background:#1d4ed8}#as-data-close-btn{background:#e8ecf0;color:#333}#as-data-close-btn:hover{background:#d1d5db} /* ---------- 自定义确认弹窗 ---------- */ #as-confirm-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:1000001;display:none;justify-content:center;align-items:center;font-family:system-ui,sans-serif}#as-confirm-overlay.active{display:flex}#as-confirm-modal{background:#fff;border-radius:16px;width:420px;max-width:90vw;box-shadow:0 20px 60px rgba(0,0,0,0.3);display:flex;flex-direction:column;overflow:hidden;animation:asFadeIn .2s ease}#as-confirm-header{padding:16px 24px;border-bottom:1px solid #e8ecf0;background:#fafbfc}#as-confirm-header h3{margin:0;font-size:17px;font-weight:600;color:#1a1a2e}#as-confirm-body{padding:20px 24px;font-size:15px;color:#333;line-height:1.6;white-space:pre-wrap}#as-confirm-footer{padding:12px 24px 20px;border-top:1px solid #e8ecf0;display:flex;justify-content:flex-end;gap:10px;background:#fafbfc}#as-confirm-footer button{padding:8px 20px;border:none;border-radius:8px;font-size:14px;cursor:pointer;transition:background .2s}#as-confirm-cancel{background:#e8ecf0;color:#333}#as-confirm-cancel:hover{background:#d1d5db}#as-confirm-ok{background:#2563eb;color:#fff}#as-confirm-ok:hover{background:#1d4ed8}#as-confirm-ok.danger{background:#dc2626}#as-confirm-ok.danger:hover{background:#b91c1c} `); const META_KEY = 'as_meta'; function getSiteKey() { return location.hostname; } function getMeta() { const raw = GM_getValue(META_KEY); return raw && typeof raw === 'object' ? raw : {}; } function saveMeta(meta) { GM_setValue(META_KEY, meta); } function getSiteMeta() { const meta = getMeta(); const site = getSiteKey(); if (!meta[site] || typeof meta[site] !== 'object') { meta[site] = { accounts: [], current: null }; } return meta[site]; } function saveSiteMeta(siteMeta) { const meta = getMeta(); meta[getSiteKey()] = siteMeta; saveMeta(meta); } function getAccountKey(name) { return `as_data_${getSiteKey()}_${name}`; } function loadAccountData(name) { const data = GM_getValue(getAccountKey(name)); return data && typeof data === 'object' ? data : null; } function saveAccountData(name, data) { GM_setValue(getAccountKey(name), data); } function deleteAccountData(name) { GM_deleteValue(getAccountKey(name)); } function getAccountNames() { return getSiteMeta().accounts || []; } function getCurrentAccountName() { return getSiteMeta().current || null; } function setCurrentAccountName(name) { const meta = getSiteMeta(); meta.current = name; saveSiteMeta(meta); } function addAccount(name, data) { const meta = getSiteMeta(); if (!meta.accounts.includes(name)) meta.accounts.push(name); saveSiteMeta(meta); saveAccountData(name, data); } function updateAccount(name, data) { saveAccountData(name, data); } function removeAccount(name) { const meta = getSiteMeta(); const idx = meta.accounts.indexOf(name); if (idx === -1) return false; meta.accounts.splice(idx, 1); if (meta.current === name) meta.current = null; saveSiteMeta(meta); deleteAccountData(name); return true; } function getAllAccounts() { const names = getAccountNames(); const result = {}; for (const n of names) { const d = loadAccountData(n); if (d) result[n] = d; } return result; } function generateDefaultName(existing) { let i = 1; while (existing.includes(`账号${i}`)) i++; return `账号${i}`; } function captureLocalStorage() { const data = {}; for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); if (k !== null) data[k] = localStorage.getItem(k); } return data; } function restoreLocalStorage(data) { localStorage.clear(); for (const [k, v] of Object.entries(data)) localStorage.setItem(k, v); } function captureCookies() { return new Promise((resolve, reject) => { GM_cookie.list({ url: location.href }, (cookies, err) => { if (err) reject(err); else resolve(cookies || []); }); }); } function restoreCookies(cookies) { return new Promise((resolve, reject) => { if (!cookies || !cookies.length) { resolve(); return; } GM_cookie.list({ url: location.href }, (existing, err) => { if (err) { reject(err); return; } const delProm = (existing || []).map(c => new Promise(r => { GM_cookie.set({ url: location.href, name: c.name, value: '', path: c.path || '/', domain: c.domain || location.hostname, secure: c.secure || false, httpOnly: c.httpOnly || false, expirationDate: 0 }, () => r()); })); Promise.all(delProm).then(() => { const setProm = cookies.map(c => new Promise(r => { const d = { url: location.href, name: c.name, value: c.value, path: c.path || '/', domain: c.domain || location.hostname, secure: c.secure || false, httpOnly: c.httpOnly || false }; if (c.expirationDate) d.expirationDate = c.expirationDate; GM_cookie.set(d, () => r()); })); Promise.all(setProm).then(resolve).catch(reject); }).catch(reject); }); }); } async function capturePageData(type) { const result = {}; if (type === 'localstorage' || type === 'both') result.localStorage = captureLocalStorage(); if (type === 'cookie' || type === 'both') result.cookies = await captureCookies(); return result; } async function restorePageData(type, data) { if (type === 'localstorage' || type === 'both' && data.localStorage) restoreLocalStorage(data.localStorage); if (type === 'cookie' || type === 'both' && data.cookies) await restoreCookies(data.cookies); } async function clearPageData() { localStorage.clear(); return new Promise(resolve => { GM_cookie.list({ url: location.href }, (cookies, err) => { if (err) { resolve(); return; } const delProm = (cookies || []).map(c => new Promise(r => { GM_cookie.set({ url: location.href, name: c.name, value: '', path: c.path || '/', domain: c.domain || location.hostname, secure: c.secure || false, httpOnly: c.httpOnly || false, expirationDate: 0 }, () => r()); })); Promise.all(delProm).then(() => resolve()); }); }); } function renameAccount(oldName, newName) { if (oldName === newName) { GM_notification({ text: '新名称与旧名称相同,无需修改', timeout: 1500 }); return false; } const accounts = getAllAccounts(); if (!accounts[oldName]) { alert(`账号 "${oldName}" 不存在`); return false; } const names = getAccountNames(); if (names.includes(newName)) { alert(`账号 "${newName}" 已存在`); return false; } const data = loadAccountData(oldName); if (!data) { alert(`无法读取账号 "${oldName}" 的数据`); return false; } saveAccountData(newName, data); deleteAccountData(oldName); const meta = getSiteMeta(); const idx = meta.accounts.indexOf(oldName); if (idx !== -1) meta.accounts[idx] = newName; if (meta.current === oldName) meta.current = newName; saveSiteMeta(meta); GM_notification({ text: `已重命名为 "${newName}"`, timeout: 2000 }); return true; } // ========== 自定义确认弹窗 ========== let confirmResolve = null; function showConfirm({ title = '确认', message = '', confirmText = '确定', cancelText = '取消', danger = false } = {}) { return new Promise((resolve) => { // 创建或获取确认弹窗DOM let overlay = document.getElementById('as-confirm-overlay'); if (!overlay) { overlay = document.createElement('div'); overlay.id = 'as-confirm-overlay'; overlay.innerHTML = `