Claude SessionKey Manager

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

您需要先安装一个扩展,例如 篡改猴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.2
// @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
// ==/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: 350px;
        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: 400px;
        overflow-y: auto;
    }

    .skm-token-item {
        padding: 6px 10px;
        margin-bottom: 6px;
        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.2</div>
            </div>
        </div>
    `;

    popup.innerHTML = popupContent;
    document.body.appendChild(popup);

    // =============== 功能实现 ===============

    // 自动登录函数
    function autoLogin(token) {
        const currentURL = new URL(window.location.href);
        const host = currentURL.host;
        const protocol = currentURL.protocol;

        // 根据当前网站构建登录URL
        const loginUrl = `${protocol}//${host}/login_token?session_key=${token}`;

        console.log('Redirecting to:', loginUrl);
        window.location.href = loginUrl;
    }

    // 加载保存的位置
    function loadSavedPosition() {
        const savedPosition = localStorage.getItem('skmButtonPosition');
        if (savedPosition) {
            try {
                const position = JSON.parse(savedPosition);
                // 兼容旧版本的right定位
                if (position.right !== undefined) {
                    mainButton.style.top = `${position.top}px`;
                    mainButton.style.right = `${position.right}px`;
                } else {
                    mainButton.style.left = `${position.left}px`;
                    mainButton.style.top = `${position.top}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 rect = mainButton.getBoundingClientRect();
        const position = {
            left: rect.left,
            top: rect.top
        };
        localStorage.setItem('skmButtonPosition', JSON.stringify(position));
    }

    // 拖拽功能实现
    let isDragging = false;
    let hasMoved = false;
    let startX, startY;
    let startLeft, startTop;

    function handleMouseDown(e) {
        e.preventDefault();

        const rect = mainButton.getBoundingClientRect();
        startX = e.clientX;
        startY = e.clientY;
        startLeft = rect.left;
        startTop = rect.top;

        hasMoved = false;
        isDragging = false;

        // 如果菜单是打开的,关闭它
        popup.classList.remove('active');
    }

    function handleMouseMove(e) {
        if (startX === undefined) return;

        const deltaX = e.clientX - startX;
        const deltaY = e.clientY - startY;
        const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);

        // 移动超过3像素就开始拖拽
        if (distance > 3) {
            if (!isDragging) {
                isDragging = true;
                mainButton.classList.add('skm-dragging');
            }

            hasMoved = true;

            // 计算新位置
            const newLeft = startLeft + deltaX;
            const newTop = startTop + deltaY;

            // 限制在窗口内
            const maxLeft = window.innerWidth - mainButton.offsetWidth - 10;
            const maxTop = window.innerHeight - mainButton.offsetHeight - 10;

            mainButton.style.left = Math.max(10, Math.min(newLeft, maxLeft)) + 'px';
            mainButton.style.top = Math.max(10, Math.min(newTop, maxTop)) + 'px';
            mainButton.style.right = 'auto'; // 清除right定位
        }
    }

    function handleMouseUp(e) {
        if (startX === undefined) return;

        if (isDragging) {
            mainButton.classList.remove('skm-dragging');
            savePosition();
        }

        // 重置状态
        isDragging = false;
        startX = undefined;
        startY = undefined;

        // 短暂延迟重置移动状态,避免立即触发点击
        setTimeout(() => {
            hasMoved = false;
        }, 10);
    }

    // 加载之前选择的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 togglePopup(e) {
        // 如果刚完成拖拽,不触发菜单
        if (hasMoved) {
            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);

    // 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();
})();