Claude SessionKey Manager

轻松管理和切换Claude的SessionKey(可拖拽浮动按钮,简洁弹出式菜单)

当前为 2025-04-06 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Claude SessionKey Manager
// @version      3.0
// @description  轻松管理和切换Claude的SessionKey(可拖拽浮动按钮,简洁弹出式菜单)
// @match        https://claude.ai/*
// @match        https://demo.fuclaude.com/*
// @grant        none
// @license      GNU GPLv3
// @author       f14xuanlv
// @namespace    https://greasyfork.org/users/1454591
// @description  这是基于 https://greasyfork.org/scripts/501296 的修改版本
// @description  原作者: https://greasyfork.org/zh-CN/users/1337296
// ==/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;
            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;
            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;
            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;
        }

        .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;
            display: flex;
            justify-content: space-between;
            align-items: center;
            cursor: pointer;
            transition: all 0.2s;
        }

        .skm-token-item:hover {
            background-color: #FFF5EA;
            border-color: #C96442;
        }

        .skm-token-item.active {
            background-color: #FFF0DD;
            border-color: #C96442;
        }

        .skm-token-name {
            flex-grow: 1;
            font-size: 14px;
            padding-right: 8px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        .skm-use-btn {
            background-color: #C96442;
            color: white;
            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;
            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;
            border-radius: 4px;
            border-left: 3px solid #C96442;
        }
        
        .skm-hint-text {
            font-size: 12px;
            color: #996633;
        }
    `;
    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 = `
        <div class="skm-popup-header">
            <span>SessionKey Manager</span>
            <button class="skm-close-btn">&times;</button>
        </div>
        <div class="skm-popup-content">
            <div class="skm-hint">
                <div class="skm-hint-text">提示:鼠标长按 SK 图标可以拖动位置</div>
            </div>
            <div class="skm-token-list skm-scrollbar">
                ${tokens.map((token, index) => `
                    <div class="skm-token-item" data-index="${index}">
                        <div class="skm-token-name">${token.name}</div>
                        <button class="skm-use-btn">使用</button>
                    </div>
                `).join('')}
            </div>
            <div class="skm-footer">
                <div class="skm-version">v3.0</div>
            </div>
        </div>
    `;
    
    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();
})();