// ==UserScript== // @name Claude SessionKey Manager // @version 3.1 // @description 轻松管理和切换Claude的SessionKey(可拖拽浮动按钮,简洁弹出式菜单)。此脚本基于 https://greasyfork.org/scripts/501296,原作者: https://greasyfork.org/zh-CN/users/1337296 // @match https://claude.ai/* // @match https://demo.fuclaude.com/* // @grant none // @license GNU GPLv3 // @author f14xuanlv // @namespace https://greasyfork.org/users/1454591 // @downloadURL https://update.greasyfork.icu/scripts/531980/Claude%20SessionKey%20Manager.user.js // @updateURL https://update.greasyfork.icu/scripts/531980/Claude%20SessionKey%20Manager.meta.js // ==/UserScript== (function() { 'use strict'; // =============== 配置项 =============== const tokens = [ {name: 'default_empty', key: 'sk-ant-sid01-xxx'}, {name: 'default_empty1', key: 'sk-ant-sid01-xxx'}, // 此处添加更多的SessionKey ]; // =============== 样式设置 =============== const styles = document.createElement('style'); styles.textContent = ` .skm-main-button { position: fixed; z-index: 10000; display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 50%; background: #C96442; color: white; font-size: 16px; font-weight: bold; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); cursor: pointer; user-select: none; touch-action: none; border: none; } .skm-main-button:hover { background: #E67816; } .skm-dragging { opacity: 0.8; } .skm-popup { position: fixed; z-index: 9999; width: 280px; background-color: #FFF8F0 !important; border-radius: 8px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); overflow: hidden; transition: opacity 0.2s ease; opacity: 0; pointer-events: none; } .skm-popup.active { opacity: 1; pointer-events: all; } .skm-popup-header { background: #C96442; color: white !important; padding: 12px 16px; font-weight: bold; font-size: 15px; display: flex; justify-content: space-between; align-items: center; } .skm-close-btn { background: none; border: none; color: white !important; font-size: 18px; cursor: pointer; display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 50%; } .skm-close-btn:hover { background-color: rgba(255, 255, 255, 0.2); } .skm-popup-content { padding: 16px; background-color: #FFF8F0 !important; } .skm-token-list { margin-bottom: 16px; max-height: 200px; overflow-y: auto; } .skm-token-item { padding: 10px; margin-bottom: 8px; border-radius: 6px; border: 1px solid #E8DFD5; background-color: white !important; display: flex; justify-content: space-between; align-items: center; cursor: pointer; transition: all 0.2s; color: #333 !important; } .skm-token-item:hover { background-color: #FFF5EA !important; border-color: #C96442; color: #333 !important; } .skm-token-item.active { background-color: #FFF0DD !important; border-color: #C96442; color: #333 !important; } .skm-token-name { flex-grow: 1; font-size: 14px; padding-right: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #333 !important; } .skm-use-btn { background-color: #C96442; color: white !important; border: none; border-radius: 4px; padding: 4px 8px; font-size: 12px; cursor: pointer; min-width: 42px; text-align: center; } .skm-use-btn:hover { background-color: #E67816; } .skm-footer { display: flex; justify-content: flex-end; gap: 8px; padding-top: 8px; border-top: 1px solid #E8DFD5; } .skm-version { font-size: 12px; color: #999 !important; margin-right: auto; align-self: center; } .skm-scrollbar { scrollbar-width: thin; scrollbar-color: #C96442 #FFF8F0; } .skm-scrollbar::-webkit-scrollbar { width: 6px; } .skm-scrollbar::-webkit-scrollbar-track { background: #FFF8F0; } .skm-scrollbar::-webkit-scrollbar-thumb { background-color: #C96442; border-radius: 6px; } .skm-hint { margin-bottom: 12px; padding: 8px; background-color: #FFF0DD !important; border-radius: 4px; border-left: 3px solid #C96442; } .skm-hint-text { font-size: 12px; color: #996633 !important; } /* Force correct colors in all themes */ @media (prefers-color-scheme: light), (prefers-color-scheme: dark) { .skm-popup, .skm-popup-content { background-color: #FFF8F0 !important; } .skm-token-item { background-color: white !important; color: #333 !important; } .skm-token-name { color: #333 !important; } .skm-hint-text { color: #996633 !important; } } `; document.head.appendChild(styles); // =============== 创建主按钮 =============== const mainButton = document.createElement('button'); mainButton.className = 'skm-main-button'; mainButton.innerHTML = 'SK'; mainButton.title = 'Claude SessionKey Manager'; document.body.appendChild(mainButton); // =============== 创建弹出菜单 =============== const popup = document.createElement('div'); popup.className = 'skm-popup'; const popupContent = `
SessionKey Manager
提示:鼠标长按 SK 图标可以拖动位置
${tokens.map((token, index) => `
${token.name}
`).join('')}
`; popup.innerHTML = popupContent; document.body.appendChild(popup); // =============== 功能实现 =============== // 加载保存的位置 function loadSavedPosition() { const savedPosition = localStorage.getItem('skmButtonPosition'); if (savedPosition) { try { const { top, right } = JSON.parse(savedPosition); mainButton.style.top = `${top}px`; mainButton.style.right = `${right}px`; } catch (error) { console.error('Failed to parse saved position', error); setDefaultPosition(); } } else { setDefaultPosition(); } } // 设置默认位置 function setDefaultPosition() { mainButton.style.top = '70px'; mainButton.style.right = '20px'; } // 保存位置 function savePosition() { const position = { top: parseInt(mainButton.style.top), right: parseInt(mainButton.style.right) }; localStorage.setItem('skmButtonPosition', JSON.stringify(position)); } // 拖拽功能实现 let isDragging = false; let dragStartX, dragStartY; let initialRight, initialTop; function startDrag(e) { isDragging = true; mainButton.classList.add('skm-dragging'); const rect = mainButton.getBoundingClientRect(); initialRight = window.innerWidth - rect.right; initialTop = rect.top; if (e.type === 'touchstart') { dragStartX = e.touches[0].clientX; dragStartY = e.touches[0].clientY; } else { dragStartX = e.clientX; dragStartY = e.clientY; } } function handleMouseDown(e) { // 阻止默认行为以防止选中文本等 e.preventDefault(); // 直接开始拖拽,不加延迟 startDrag(e); // 如果菜单是打开的,关闭它 popup.classList.remove('active'); } function handleMouseMove(e) { if (!isDragging) return; e.preventDefault(); let currentX, currentY; if (e.type === 'touchmove') { currentX = e.touches[0].clientX; currentY = e.touches[0].clientY; } else { currentX = e.clientX; currentY = e.clientY; } const deltaX = currentX - dragStartX; const deltaY = currentY - dragStartY; const newRight = Math.max(10, initialRight - deltaX); const newTop = Math.max(10, initialTop + deltaY); // 限制不超出屏幕 const maxRight = window.innerWidth - mainButton.offsetWidth - 10; const maxTop = window.innerHeight - mainButton.offsetHeight - 10; mainButton.style.right = `${Math.min(newRight, maxRight)}px`; mainButton.style.top = `${Math.min(newTop, maxTop)}px`; } function handleMouseUp(e) { if (!isDragging) return; e.preventDefault(); isDragging = false; mainButton.classList.remove('skm-dragging'); savePosition(); // 防止点击事件同时触发 e.stopPropagation(); setTimeout(() => { isDragging = false; }, 0); } // 加载之前选择的token function loadSelectedToken() { const storedToken = localStorage.getItem('skmSelectedToken'); if (storedToken) { const tokenItems = document.querySelectorAll('.skm-token-item'); tokenItems.forEach(item => { const index = parseInt(item.dataset.index); if (tokens[index] && tokens[index].key === storedToken) { item.classList.add('active'); } }); } } // 处理token选择 function handleTokenSelection(token) { if (token === '') { console.log('Empty token selected. No action taken.'); } else { autoLogin(token); } } // 自动登录 function autoLogin(token) { const currentURL = window.location.href; let loginUrl; if (currentURL.startsWith('https://demo.fuclaude.com/')) { loginUrl = `https://demo.fuclaude.com/login_token?session_key=${token}`; } else { loginUrl = `https://demo.fuclaude.com/login_token?session_key=${token}`; } window.location.href = loginUrl; } // 显示/隐藏弹出菜单 function togglePopup(e) { // 如果正在拖拽,不触发菜单 if (isDragging) return; // 阻止事件冒泡 e.stopPropagation(); const rect = mainButton.getBoundingClientRect(); const isActive = popup.classList.contains('active'); if (!isActive) { // 计算弹出位置,确保在屏幕内 const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; let left = rect.left; if (left + popup.offsetWidth > windowWidth - 10) { left = windowWidth - popup.offsetWidth - 10; } let top = rect.bottom + 10; // 如果下方空间不足,则显示在按钮上方 if (top + popup.offsetHeight > windowHeight - 10) { top = rect.top - popup.offsetHeight - 10; } popup.style.left = `${Math.max(10, left)}px`; popup.style.top = `${Math.max(10, top)}px`; popup.classList.add('active'); loadSelectedToken(); // 点击外部关闭菜单 setTimeout(() => { document.addEventListener('click', closePopupOnOutsideClick); }, 10); } else { popup.classList.remove('active'); document.removeEventListener('click', closePopupOnOutsideClick); } } // 点击外部关闭菜单 function closePopupOnOutsideClick(e) { if (!popup.contains(e.target) && e.target !== mainButton) { popup.classList.remove('active'); document.removeEventListener('click', closePopupOnOutsideClick); } } // =============== 事件绑定 =============== // 主按钮点击事件 mainButton.addEventListener('click', togglePopup); // 拖拽相关事件 - 鼠标 mainButton.addEventListener('mousedown', handleMouseDown); document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); // 拖拽相关事件 - 触摸 mainButton.addEventListener('touchstart', handleMouseDown, { passive: false }); document.addEventListener('touchmove', handleMouseMove, { passive: false }); document.addEventListener('touchend', handleMouseUp); // ESC键关闭菜单 document.addEventListener('keydown', e => { if (e.key === 'Escape') { popup.classList.remove('active'); } }); // 关闭按钮事件 const closeBtn = popup.querySelector('.skm-close-btn'); closeBtn.addEventListener('click', e => { e.stopPropagation(); popup.classList.remove('active'); document.removeEventListener('click', closePopupOnOutsideClick); }); // Token选择事件 const tokenItems = popup.querySelectorAll('.skm-token-item'); tokenItems.forEach(item => { const useBtn = item.querySelector('.skm-use-btn'); // 使用按钮点击事件 useBtn.addEventListener('click', e => { e.stopPropagation(); const index = parseInt(item.dataset.index); const selectedToken = tokens[index].key; // 更新选中状态 tokenItems.forEach(i => i.classList.remove('active')); item.classList.add('active'); // 保存选择并登录 localStorage.setItem('skmSelectedToken', selectedToken); handleTokenSelection(selectedToken); }); // 点击整个item也可以选择 item.addEventListener('click', () => { const index = parseInt(item.dataset.index); const selectedToken = tokens[index].key; // 更新选中状态 tokenItems.forEach(i => i.classList.remove('active')); item.classList.add('active'); // 保存选择 localStorage.setItem('skmSelectedToken', selectedToken); }); }); // 初始化位置 loadSavedPosition(); })();