Claude SessionKey Manager

轻松管理和切换Claude的SessionKey(可拖拽浮动按钮,简洁弹出式菜单)。此脚本基于 https://gf.qytechs.cn/scripts/501296,原作者: https://gf.qytechs.cn/zh-CN/users/1337296

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

QingJ © 2025

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