ClaudeToken 继续切换

[更新了链接]切换Claude Sessionkey的油猴脚本(适用fuclaude),优化版 的基础上继续优化

  • 将管理和添加的逻辑简化合并
  • 将默认token设置改为数组
  • 动态更新token列表
  • 选中自动切换token
  • 支持一键导入和导出

感谢 cursor 的(免费)赞助 :yum:
感谢 xiaohan17 等佬友的群策群力 :smiling_face_with_three_hearts:
感谢 baicha 赞助的一个账号让楼主迈入claude小康 :heart_eyes:

油猴脚本
// ==UserScript==
// @name         Claude Token Switcher
// @version      2.0.0
// @description  动态切换claude的token
// @author       Reno, xiaohan17, ethan-j
// @match        https://claude.ai/*
// @match        https://demo.fuclaude.com/*
// @grant        GM_xmlhttpRequest
// @connect      ipapi.co
// @license      GNU GPLv3
// ==/UserScript==

(function() {
    'use strict';

    // 配置
    const config = {
        storageKey: 'claudeTokens',
        lastUsedTokenKey: 'lastUsedTokenName',
        ipApiUrl: 'https://ipapi.co/country_code',
        defaultToken: [
            {
                name: '[email protected]',
                key: 'sk-ant-sid01-111111'
            }
        ]
    };

    // 样式
    const getStyles = (isDarkMode) => `
        .claude-modal {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: ${isDarkMode ? 'rgba(0, 0, 0, 0.7)' : 'rgba(0, 0, 0, 0.5)'};
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 10000;
        }
        .claude-modal-content {
            background-color: ${isDarkMode ? '#2c2b28' : '#fff'};
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px ${isDarkMode ? 'rgba(0, 0, 0, 0.3)' : 'rgba(0, 0, 0, 0.1)'};
            width: 600px;
            max-width: 90%;
        }
        .claude-modal h2 {
            margin-top: 0;
            margin-bottom: 15px;
            color: ${isDarkMode ? '#f5f4ef' : '#333'};
            font-size: 18px;
        }
        .claude-modal input {
            width: 100%;
            padding: 8px;
            margin-bottom: 10px;
            border: 1px solid ${isDarkMode ? '#3f3f3c' : '#ddd'};
            border-radius: 4px;
            font-size: 14px;
            background-color: ${isDarkMode ? '#2f2f2c' : '#fff'};
            color: ${isDarkMode ? '#f5f4ef' : '#333'};
        }
        .claude-modal button {
            padding: 8px 12px;
            margin-right: 10px;
            border: none;
            border-radius: 4px;
            background-color: ${isDarkMode ? '#3f3f3c' : '#4CAF50'};
            color: ${isDarkMode ? '#f5f4ef' : 'white'};
            cursor: pointer;
            transition: background-color 0.3s;
        }
        .claude-modal button:hover {
            background-color: ${isDarkMode ? '#4a4a47' : '#45a049'};
        }
        .claude-modal button.cancel {
            background-color: ${isDarkMode ? '#3a3935' : '#f44336'};
        }
        .claude-modal button.cancel:hover {
            background-color: ${isDarkMode ? '#454540' : '#da190b'};
        }
        .claude-token-list {
            max-height: 400px;
            overflow-y: auto;
            margin-bottom: 15px;
        }
        .claude-token-item {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            padding: 12px 0;
            border-bottom: 1px solid ${isDarkMode ? '#3f3f3c' : '#eee'};
        }
        .claude-token-item:last-child {
            border-bottom: none;
        }
        .claude-token-key {
            word-break: break-all;
            max-width: 450px;
            font-size: 12px;
            line-height: 1.4;
            cursor: pointer;
            user-select: none;
        }
        .delete-icon {
            cursor: pointer;
            width: 20px;
            height: 20px;
            padding: 2px;
            border-radius: 4px;
            transition: background-color 0.2s;
        }
        .delete-icon:hover {
            background-color: ${isDarkMode ? '#454540' : '#ffebee'};
        }
        .delete-icon path {
            fill: ${isDarkMode ? '#f5f4ef' : '#e53935'};
        }
        .claude-token-manager {
            background-color: ${isDarkMode ? '#2c2b28' : '#fcfaf5'};
            color: ${isDarkMode ? '#f5f4ef' : '#333'};
        }
        .claude-token-manager select,
        .claude-token-manager button {
            background-color: ${isDarkMode ? '#2f2f2c' : '#f5f1e9'};
            color: ${isDarkMode ? '#f5f4ef' : '#333'};
            border-color: ${isDarkMode ? '#3f3f3c' : '#ccc'};
        }
        .claude-token-manager button:hover {
            background-color: ${isDarkMode ? '#3a3935' : '#e5e1d9'};
        }
        .key-input-container {
            position: relative;
            display: flex;
            align-items: center;
            margin-bottom: 8px;
        }
        .toggle-visibility {
            position: absolute;
            right: 8px;
            cursor: pointer;
            padding: 4px;
            background: none;
            border: none;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .toggle-visibility:hover {
            background-color: ${isDarkMode ? '#454540' : '#f5f5f5'};
            border-radius: 4px;
        }
        .toggle-visibility svg {
            width: 20px;
            height: 20px;
        }
        .toggle-visibility path {
            fill: ${isDarkMode ? '#f5f4ef' : '#666'};
        }
    `;

    // UI 组件
    const UI = {
        createElem(tag, styles) {
            const elem = document.createElement(tag);
            Object.assign(elem.style, styles);
            return elem;
        },

        createButton(text, styles) {
            const button = this.createElem('button', styles);
            button.innerText = text;
            return button;
        },

        createTokenSelect(isDarkMode) {
            return this.createElem('select', {
                marginRight: '4px',
                fontSize: '12px',
                width: '150px',
                backgroundColor: isDarkMode ? '#2f2f2c' : '#f5f1e9',
                color: isDarkMode ? '#f5f4ef' : '#333',
                height: '24px',
                padding: '0 4px',
                lineHeight: '24px',
                border: `1px solid ${isDarkMode ? '#3f3f3c' : '#ccc'}`,
                borderRadius: '3px',
                appearance: 'none',
                WebkitAppearance: 'none',
                MozAppearance: 'none',
                backgroundImage: 'url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23007CB2%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E")',
                backgroundRepeat: 'no-repeat',
                backgroundPosition: 'right .5em top 50%',
                backgroundSize: '.65em auto',
            });
        },

        createModal(title, content) {
            const modal = document.createElement('div');
            modal.className = 'claude-modal';

            const modalContent = document.createElement('div');
            modalContent.className = 'claude-modal-content';

            const titleElem = document.createElement('h2');
            titleElem.textContent = title;
            modalContent.appendChild(titleElem);

            modalContent.appendChild(content);
            modal.appendChild(modalContent);

            document.body.appendChild(modal);

            return {
                modal,
                close: () => document.body.removeChild(modal)
            };
        }
    };

    // 主要功能
    const App = {
        init() {
            this.isDarkMode = document.documentElement.getAttribute('data-mode') === 'dark';
            this.injectStyles();
            this.tokens = this.loadTokens();
            this.createUI();
            this.setupEventListeners();
            this.updateTokenSelect();
            this.fetchIPCountryCode();
            this.observeThemeChanges();
        },

        injectStyles() {
            this.styleElem = document.createElement('style');
            this.styleElem.textContent = getStyles(this.isDarkMode);
            document.head.appendChild(this.styleElem);
        },

        updateStyles() {
            this.styleElem.textContent = getStyles(this.isDarkMode);
            this.updateUIColors();
        },

        updateUIColors() {
            Object.assign(this.container.style, {
                backgroundColor: this.isDarkMode ? '#2c2b28' : '#fcfaf5',
                boxShadow: `0 1px 3px ${this.isDarkMode ? 'rgba(0,0,0,0.3)' : 'rgba(0,0,0,0.2)'}`,
            });

            Object.assign(this.tokenSelect.style, {
                backgroundColor: this.isDarkMode ? '#2f2f2c' : '#f5f1e9',
                color: this.isDarkMode ? '#f5f4ef' : '#333',
                border: `1px solid ${this.isDarkMode ? '#3f3f3c' : '#ccc'}`,
            });

            [this.switchButton, this.addButton].forEach(button => {
                Object.assign(button.style, {
                    backgroundColor: this.isDarkMode ? '#2f2f2c' : '#f5f1e9',
                    color: this.isDarkMode ? '#f5f4ef' : '#333',
                    border: `1px solid ${this.isDarkMode ? '#3f3f3c' : '#ccc'}`,
                });
            });

            this.toggleButton.style.color = this.isDarkMode ? '#f5f4ef' : '#29261b';
        },

        loadTokens() {
            try {
                const savedTokens = JSON.parse(localStorage.getItem(config.storageKey) || '[]');
                const tokens = savedTokens.length > 0 ? savedTokens : config.defaultToken;

                // 获取上次使用的token名称
                const lastUsedTokenName = localStorage.getItem(config.lastUsedTokenKey);

                // 如果有最后使用的token名称,将其移到首位
                if (lastUsedTokenName) {
                    const lastUsedIndex = tokens.findIndex(token => token.name === lastUsedTokenName);
                    if (lastUsedIndex !== -1) {
                        const lastUsedToken = tokens[lastUsedIndex];
                        tokens.splice(lastUsedIndex, 1);
                        tokens.unshift(lastUsedToken);
                    }
                }

                return tokens;
            } catch (error) {
                console.error('加载tokens时出错:', error);
                return config.defaultToken;
            }
        },

        saveTokens() {
            localStorage.setItem(config.storageKey, JSON.stringify(this.tokens));
        },

        createUI() {
            this.tokenSelect = UI.createTokenSelect(this.isDarkMode);
            this.toggleButton = UI.createButton('...', {
                position: 'fixed',
                top: '10px',
                right: '95px',
                zIndex: '9998',
                width: '36px',
                height: '36px',
                backgroundColor: 'transparent',
                border: 'none',
                borderRadius: '0.375rem',
                fontSize: '12px',
                cursor: 'pointer',
                display: 'flex',
                justifyContent: 'center',
                alignItems: 'center',
                transition: 'background-color 0.3s ease, color 0.3s ease',
                color: this.isDarkMode ? '#f5f4ef' : '#29261b',
            });
            this.addButton = UI.createButton('管理', {
                fontSize: '12px',
                height: '24px',
                padding: '0 8px',
                lineHeight: '22px',
                border: `1px solid ${this.isDarkMode ? '#3f3f3c' : '#ccc'}`,
                borderRadius: '3px',
                backgroundColor: this.isDarkMode ? '#2f2f2c' : '#f5f1e9',
                color: this.isDarkMode ? '#f5f4ef' : '#333',
                cursor: 'pointer',
            });

            this.container = UI.createElem('div', {
                position: 'fixed',
                top: '50px',
                right: '77px',
                zIndex: '9999',
                backgroundColor: this.isDarkMode ? '#2c2b28' : '#fcfaf5',
                padding: '8px',
                borderRadius: '3px',
                boxShadow: `0 1px 3px ${this.isDarkMode ? 'rgba(0,0,0,0.3)' : 'rgba(0,0,0,0.2)'}`,
                display: 'none',
                fontSize: '12px',
                width: 'auto',
            });

            const buttonContainer = UI.createElem('div', {
                display: 'flex',
                flexDirection: 'row',
                alignItems: 'center',
                gap: '4px',
            });

            buttonContainer.appendChild(this.tokenSelect);
            buttonContainer.appendChild(this.addButton);
            this.container.appendChild(buttonContainer);

            document.body.appendChild(this.container);
            document.body.appendChild(this.toggleButton);
        },

        setupEventListeners() {
            this.toggleButton.addEventListener('click', () => this.toggleContainer());
            this.toggleButton.addEventListener('mouseover', () => {
                this.toggleButton.style.backgroundColor = this.isDarkMode ? '#1a1915' : '#ded8c4';
                this.toggleButton.style.color = this.isDarkMode ? '#f5f4ef' : '#29261b';
            });
            this.toggleButton.addEventListener('mouseout', () => {
                this.toggleButton.style.backgroundColor = 'transparent';
                this.toggleButton.style.color = this.isDarkMode ? '#f5f4ef' : '#29261b';
            });

            // 添加select的change事件监听
            this.tokenSelect.addEventListener('change', () => {
                const selectedIndex = this.tokenSelect.value;
                const selectedToken = this.tokens[selectedIndex];
                const lastUsedTokenName = localStorage.getItem(config.lastUsedTokenKey);

                // 如果选择的token与上次使用的不同,则自动切换
                if (selectedToken.name !== lastUsedTokenName) {
                    this.switchToken();
                }
            });

            this.addButton.addEventListener('click', () => this.showAddTokenModal());
        },

        observeThemeChanges() {
            const observer = new MutationObserver((mutations) => {
                mutations.forEach((mutation) => {
                    if (mutation.type === 'attributes' && mutation.attributeName === 'data-mode') {
                        this.isDarkMode = document.documentElement.getAttribute('data-mode') === 'dark';
                        this.updateStyles();
                    }
                });
            });

            observer.observe(document.documentElement, {
                attributes: true,
                attributeFilter: ['data-mode']
            });
        },

        toggleContainer() {
            this.container.style.display = this.container.style.display === 'none' ? 'block' : 'none';
        },

        updateTokenSelect() {
            this.tokenSelect.innerHTML = '';
            if (!Array.isArray(this.tokens)) {
                console.error('tokens不是数组:', this.tokens);
                this.tokens = [this.tokens];
            }

            this.tokens.forEach((token, index) => {
                if (token && token.name) {
                    const option = document.createElement('option');
                    option.value = index;
                    option.text = token.name;
                    this.tokenSelect.appendChild(option);
                }
            });

            // 始终选中第一个选项(最后使用的token)
            this.tokenSelect.selectedIndex = 0;
        },

        switchToken() {
            const selectedIndex = this.tokenSelect.value;
            const selectedToken = this.tokens[selectedIndex];

            // 保存选中的token名称
            localStorage.setItem(config.lastUsedTokenKey, selectedToken.name);

            // 将选中的token移到数组首位
            this.tokens.splice(selectedIndex, 1);
            this.tokens.unshift(selectedToken);

            // 保存新的tokens顺序
            this.saveTokens();

            // 更新下拉列表
            this.updateTokenSelect();

            // 应用token
            this.applyToken(selectedToken.key);
        },

        applyToken(token) {
            const currentURL = window.location.href;

            if (currentURL.startsWith('https://claude.ai/')) {
                document.cookie = `sessionKey=${token}; path=/; domain=.claude.ai`;
                window.location.reload();
            } else {
                let loginUrl;
                if (currentURL.startsWith('https://demo.fuclaude.com/')) {
                    loginUrl = `https://demo.fuclaude.com/login_token?session_key=${token}`;
                } else if (currentURL.startsWith('https://claude.asia/')) {
                    loginUrl = `https://claude.asia/login_token?session_key=${token}`;
                }

                if (loginUrl) {
                    window.location.href = loginUrl;
                }
            }
        },

        showAddTokenModal() {
            const content = document.createElement('div');
            let closeModal;  // 声明closeModal变量

            // 创建token列表区域
            const tokenList = document.createElement('div');
            tokenList.className = 'claude-token-list';

            // 创建token表单
            const createTokenForm = (token = null) => {
                const tokenItem = document.createElement('div');
                tokenItem.className = 'claude-token-item';

                const tokenForm = document.createElement('div');
                tokenForm.style.flex = '1';

                // 名称输入框
                const nameInput = document.createElement('input');
                nameInput.placeholder = 'Token名称';
                nameInput.value = token ? token.name : '';
                nameInput.style.marginBottom = '8px';

                // Key输入框
                const keyInput = document.createElement('input');
                keyInput.placeholder = 'Token密钥';
                keyInput.value = token ? token.key : '';
                keyInput.style.fontFamily = 'monospace';
                keyInput.type = 'password';  // 默认隐藏密钥

                // 组装表单
                tokenForm.appendChild(nameInput);
                tokenForm.appendChild(keyInput);
                tokenItem.appendChild(tokenForm);

                return {
                    item: tokenItem,
                    nameInput,
                    keyInput
                };
            };

            // 收集所有输入框的引用
            const formRefs = [];

            // 创建添加新表单的按钮
            const addFormButton = document.createElement('button');
            addFormButton.textContent = '+ 添加新Token';
            addFormButton.style.width = '100%';
            addFormButton.style.marginBottom = '20px';
            addFormButton.style.padding = '10px';
            addFormButton.style.backgroundColor = this.isDarkMode ? '#2f2f2c' : '#f5f5f5';
            addFormButton.style.color = this.isDarkMode ? '#f5f4ef' : '#666';
            addFormButton.style.border = `1px dashed ${this.isDarkMode ? '#3f3f3c' : '#ddd'}`;
            addFormButton.style.borderRadius = '4px';
            addFormButton.style.cursor = 'pointer';
            addFormButton.style.transition = 'all 0.3s ease';

            // 添加hover效果
            addFormButton.addEventListener('mouseover', () => {
                addFormButton.style.backgroundColor = this.isDarkMode ? '#3a3935' : '#eee';
                addFormButton.style.borderColor = this.isDarkMode ? '#4a4a47' : '#ccc';
            });
            addFormButton.addEventListener('mouseout', () => {
                addFormButton.style.backgroundColor = this.isDarkMode ? '#2f2f2c' : '#f5f5f5';
                addFormButton.style.borderColor = this.isDarkMode ? '#3f3f3c' : '#ddd';
            });

            // 添加新表单的函数
            const addNewForm = () => {
                const newForm = createTokenForm();
                formRefs.push(newForm);

                // 检查是否有未使用的默认配置
                if (config.defaultToken && config.defaultToken.length > 0) {
                    const usedTokens = formRefs.map(form => form.nameInput.value);
                    const unusedTokens = config.defaultToken.filter(defaultToken =>
                        !usedTokens.includes(defaultToken.name)
                    );

                    if (unusedTokens.length > 0) {
                        newForm.nameInput.value = unusedTokens[0].name;
                        newForm.keyInput.value = unusedTokens[0].key;
                    }
                }

                tokenList.appendChild(newForm.item);

                // 添加后自动滚动到底部
                setTimeout(() => {
                    tokenList.scrollTo({
                        top: tokenList.scrollHeight,
                        behavior: 'smooth'
                    });
                    // 自动聚焦到新表单的名称输入框
                    newForm.nameInput.focus();
                }, 100);
            };

            // 初始化表单 - 只添加已有的tokens
            if (this.tokens.length > 0) {
                this.tokens.forEach(token => {
                    const form = createTokenForm(token);
                    formRefs.push(form);
                    tokenList.appendChild(form.item);
                });
            }

            addFormButton.addEventListener('click', addNewForm);

            // 在 buttonContainer 之前添加导入导出按钮容器
            const importExportContainer = document.createElement('div');
            importExportContainer.style.display = 'flex';
            importExportContainer.style.gap = '10px';
            importExportContainer.style.marginTop = '20px';
            importExportContainer.style.marginBottom = '10px';

            // 导出按钮
            const exportButton = document.createElement('button');
            exportButton.textContent = '导出Tokens';
            exportButton.style.flex = '1';
            exportButton.style.padding = '8px';
            exportButton.style.backgroundColor = this.isDarkMode ? '#2f2f2c' : '#f5f5f5';
            exportButton.style.color = this.isDarkMode ? '#f5f4ef' : '#666';
            exportButton.style.border = `1px solid ${this.isDarkMode ? '#3f3f3c' : '#ddd'}`;
            exportButton.style.borderRadius = '4px';
            exportButton.style.cursor = 'pointer';

            // 导入按钮
            const importButton = document.createElement('button');
            importButton.textContent = '导入Tokens';
            importButton.style.flex = '1';
            importButton.style.padding = '8px';
            importButton.style.backgroundColor = this.isDarkMode ? '#2f2f2c' : '#f5f5f5';
            importButton.style.color = this.isDarkMode ? '#f5f4ef' : '#666';
            importButton.style.border = `1px solid ${this.isDarkMode ? '#3f3f3c' : '#ddd'}`;
            importButton.style.borderRadius = '4px';
            importButton.style.cursor = 'pointer';

            // 隐藏的文件输入框
            const fileInput = document.createElement('input');
            fileInput.type = 'file';
            fileInput.accept = '.txt';
            fileInput.style.display = 'none';

            // 导出功能
            exportButton.addEventListener('click', () => {
                const tokensData = JSON.stringify(this.tokens, null, 2);
                const blob = new Blob([tokensData], { type: 'text/plain' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = 'claude_tokens.txt';
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
            });

            // 导入功能
            importButton.addEventListener('click', () => {
                fileInput.click();
            });

            fileInput.addEventListener('change', (event) => {
                const file = event.target.files[0];
                if (file) {
                    const reader = new FileReader();
                    reader.onload = (e) => {
                        try {
                            const importedTokens = JSON.parse(e.target.result);
                            if (Array.isArray(importedTokens) && importedTokens.length > 0) {
                                // 清空现有表单
                                tokenList.innerHTML = '';
                                formRefs.length = 0;

                                // 添加导入的tokens
                                importedTokens.forEach(token => {
                                    const form = createTokenForm(token);
                                    formRefs.push(form);
                                    tokenList.appendChild(form.item);
                                });

                                alert('Tokens导入成功!');
                            } else {
                                alert('导入的文件格式不正确!');
                            }
                        } catch (error) {
                            alert('导入失败:文件格式错误!');
                            console.error('导入错误:', error);
                        }
                    };
                    reader.readAsText(file);
                }
            });

            importExportContainer.appendChild(exportButton);
            importExportContainer.appendChild(importButton);
            content.appendChild(importExportContainer);
            content.appendChild(fileInput);

            // 组装界面
            content.appendChild(tokenList);
            content.appendChild(addFormButton);

            // 底部按钮容器
            const buttonContainer = document.createElement('div');
            buttonContainer.style.display = 'flex';
            buttonContainer.style.gap = '10px';
            buttonContainer.style.marginTop = '20px';

            // 取消按钮
            const cancelButton = document.createElement('button');
            cancelButton.textContent = '取消';
            cancelButton.style.flex = '1';
            cancelButton.style.padding = '10px';
            cancelButton.style.backgroundColor = this.isDarkMode ? '#2f2f2c' : '#f5f5f5';
            cancelButton.style.color = this.isDarkMode ? '#f5f4ef' : '#666';
            cancelButton.style.border = `1px solid ${this.isDarkMode ? '#3f3f3c' : '#ddd'}`;
            cancelButton.style.borderRadius = '4px';
            cancelButton.style.cursor = 'pointer';
            cancelButton.style.transition = 'all 0.3s ease';

            cancelButton.addEventListener('mouseover', () => {
                cancelButton.style.backgroundColor = this.isDarkMode ? '#3a3935' : '#eee';
            });
            cancelButton.addEventListener('mouseout', () => {
                cancelButton.style.backgroundColor = this.isDarkMode ? '#2f2f2c' : '#f5f5f5';
            });

            // 保存按钮
            const saveButton = document.createElement('button');
            saveButton.textContent = '保存';
            saveButton.style.flex = '1';
            saveButton.style.padding = '10px';
            saveButton.style.backgroundColor = this.isDarkMode ? '#4CAF50' : '#4CAF50';
            saveButton.style.color = 'white';
            saveButton.style.border = 'none';
            saveButton.style.borderRadius = '4px';
            saveButton.style.cursor = 'pointer';
            saveButton.style.transition = 'all 0.3s ease';

            saveButton.addEventListener('mouseover', () => {
                saveButton.style.backgroundColor = this.isDarkMode ? '#45a049' : '#45a049';
            });
            saveButton.addEventListener('mouseout', () => {
                saveButton.style.backgroundColor = this.isDarkMode ? '#4CAF50' : '#4CAF50';
            });

            // 先创建modal并获取close函数
            const { modal, close } = UI.createModal('Token管理', content);
            closeModal = close;  // 保存close函数引用

            // 设置按钮事件
            cancelButton.addEventListener('click', closeModal);
            saveButton.addEventListener('click', () => {
                // 收集所有有效的token
                const newTokens = formRefs
                    .filter(form => {
                        const hasName = form.nameInput.value.trim() !== '';
                        const hasKey = form.keyInput.value.trim() !== '';
                        return hasName && hasKey;
                    })
                    .map(form => ({
                        name: form.nameInput.value.trim(),
                        key: form.keyInput.value.trim()
                    }));

                if (newTokens.length === 0) {
                    alert('请至少添加一个有效的Token');
                    return;
                }

                // 更新tokens
                this.tokens = newTokens;
                this.saveTokens();
                this.updateTokenSelect();
                closeModal();
            });

            buttonContainer.appendChild(cancelButton);
            buttonContainer.appendChild(saveButton);
            content.appendChild(buttonContainer);
        },

        fetchIPCountryCode() {
            GM_xmlhttpRequest({
                method: "GET",
                url: config.ipApiUrl,
                onload: (response) => {
                    if (response.status === 200) {
                        this.toggleButton.innerText = response.responseText.trim();
                    } else {
                        this.toggleButton.innerText = 'ERR';
                    }
                },
                onerror: () => {
                    this.toggleButton.innerText = 'ERR';
                }
            });
        }
    };

    // 初始化应用
    App.init();
})();
39 个赞

大佬太强了tieba_087

5 个赞

R佬高产!!!

4 个赞

感谢分享大佬厉害啊

2 个赞

R佬高产!!!

3 个赞

再加个token测活功能就完美了

2 个赞

太强了reno佬 :tieba_087:

1 个赞

真的是太强了,佬

1 个赞

感谢佬友!佬友牛的!

1 个赞

感谢reno佬分享

1 个赞

感谢reno佬

1 个赞

感谢佬友!佬友牛

1 个赞

感谢大佬分享

1 个赞

前排试用 :bili_117:

1 个赞

这位佬的也不错,带一键测活,就是对接的官方的接口,不太方便 :tieba_087:

1 个赞

不错,赞一个!

1 个赞

来了来了!

1 个赞

来晚了 吃不上了

1 个赞

不错,赞一个

1 个赞

太强咯大佬

2 个赞