Claude SessionKey Manager

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

目前為 2025-04-06 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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
// 这是基于 https://greasyfork.org/scripts/501296 的修改版本
// 原作者: https://greasyfork.org/zh-CN/users/1337296
// 修改者: f14xuanlv
// @namespace https://greasyfork.org/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;
            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();
})();