切换Claude Sessionkey的油猴脚本(来自xiaohan17大佬)再度优化

感谢[[email protected] ~]#
佬引用的帖子集百家之长的claude session管理工具
这个项目我试了下功能上确实是应有尽有并且UI非常美观:rofl:,我之前没刷到过,大家有需求的都去看看那个项目吧:rofl:,我第一次发帖希望大家海涵,帖子好像没法删得联系管理员,所以暂时先保留着了:bouquet:

:tada: USER: 脚本升级啦! :tada:
xiaohan17 大佬的
[更新了链接] 切换Claude Sessionkey的油猴脚本(适用fuclaude), 优化版
基础上,我进一步迭代了脚本功能,带来了以下超实用更新!:sparkles:

:rocket: 新增功能

  1. :wrench:管理界面升级
  • 支持在管理界面直接查看并复制“token key”,操作更方便!
  1. :floppy_disk:存储方式优化
  • 将 token 从浏览器存储转为存储到油猴中,
  • 已储存的 key 可以随脚本一起导出,备份无忧!

:clipboard: 使用方法

  • 直接将代码粘贴到油猴脚本编辑器中,即可开用!简单粗暴!:smiling_face_with_sunglasses:
// ==UserScript==// ==UserScript==
// @name         ClaudeToken切换(十七优化版,存储到油猴,可查看Token)
// @namespace    https://www.violet17.com
// @version      1.7.6
// @description  动态切换claude的token(数据改为GM存储,管理界面可查看完整Token, 修复IP显示问题和改善token展示)
// @author
// @match        https://claude.ai/*
// @match        https://demo.fuclaude.com/*
// @match        https://claude.asia/*
// @grant        GM_xmlhttpRequest
// @grant        GM.setValue
// @grant        GM.getValue
// @connect      ipapi.co
// @license      GNU GPLv3
// ==/UserScript==

(async function() {
    'use strict';

    // 配置
    const config = {
        storageKey: 'claudeTokens',
        ipApiUrl: 'https://ipapi.co/json/', // 使用JSON接口获取更多信息
        defaultToken: {
            name: 'xiaohan17',
            key: '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'
        }
    };

    // 样式
    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: 300px;
            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: 300px;
            overflow-y: auto;
            margin-bottom: 15px;
        }
        .claude-token-item {
            margin-bottom: 10px;
            padding: 10px;
            border: 1px solid ${isDarkMode ? '#3f3f3c' : '#eee'};
            border-radius: 4px;
            background-color: ${isDarkMode ? '#3a3935' : '#fafafa'};
        }
        .claude-token-item-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 5px;
        }
        .claude-token-name {
            font-weight: bold;
            font-size: 14px;
            color: ${isDarkMode ? '#f5f4ef' : '#333'};
        }
        .claude-token-key {
            font-family: monospace;
            font-size: 12px;
            color: ${isDarkMode ? '#ddd' : '#555'};
            word-break: break-all;
        }
        .copy-button {
            margin-left: 5px;
            font-size: 12px;
            padding: 2px 6px;
            border: none;
            border-radius: 3px;
            background-color: ${isDarkMode ? '#2f2f2c' : '#e0e0e0'};
            color: ${isDarkMode ? '#f5f4ef' : '#333'};
            cursor: pointer;
        }
    `;

    // 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//www.w3.org/2000/svg%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/%3E%3C/svg%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 = {
        async init() {
            this.isDarkMode = document.documentElement.getAttribute('data-mode') === 'dark';
            this.injectStyles();

            // 从GM存储中获取Tokens(如果没有则使用默认token)
            const saved = await GM.getValue(config.storageKey, '[]');
            const parsed = JSON.parse(saved);
            this.tokens = parsed.length > 0 ? parsed : [config.defaultToken];

            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, this.manageButton].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';
        },

        async saveTokens() {
            // 存入Tampermonkey存储
            await GM.setValue(config.storageKey, JSON.stringify(this.tokens));
        },

        createUI() {
            this.tokenSelect = UI.createTokenSelect(this.isDarkMode);
            this.toggleButton = UI.createButton('...', {
                position: 'fixed',
                top: '10px',
                right: '145px',
                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.switchButton = 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.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.manageButton = 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.switchButton);
            buttonContainer.appendChild(this.addButton);
            buttonContainer.appendChild(this.manageButton);
            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';
            });
            this.switchButton.addEventListener('click', () => this.switchToken());
            this.addButton.addEventListener('click', () => this.showAddTokenModal());
            this.manageButton.addEventListener('click', () => this.showManageTokensModal());
        },

        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 = '';
            this.tokens.forEach((token, index) => {
                const option = document.createElement('option');
                option.value = index;
                option.text = token.name;
                this.tokenSelect.appendChild(option);
            });
        },

        // 修正处:切换时将所选 token 移动到数组首位,这样显示的名称即为当前激活的 token
        switchToken() {
            const index = parseInt(this.tokenSelect.value, 10);
            const selectedToken = this.tokens[index];
            if (index !== 0) {
                this.tokens.splice(index, 1);
                this.tokens.unshift(selectedToken);
                this.saveTokens();
                this.updateTokenSelect();
            }
            this.applyToken(selectedToken.key);
        },

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

            if (currentURL.startsWith('https://claude.ai/')) {
                // 修改cookie后刷新
                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');
            const nameInput = document.createElement('input');
            nameInput.placeholder = 'Token名称';
            const keyInput = document.createElement('input');
            keyInput.placeholder = 'Token密钥';
            const addButton = document.createElement('button');
            addButton.textContent = '添加';
            const cancelButton = document.createElement('button');
            cancelButton.textContent = '取消';
            cancelButton.className = 'cancel';

            content.appendChild(nameInput);
            content.appendChild(keyInput);
            content.appendChild(addButton);
            content.appendChild(cancelButton);

            const { close } = UI.createModal('添加新Token', content);

            addButton.addEventListener('click', async () => {
                if (nameInput.value && keyInput.value) {
                    this.tokens.push({ name: nameInput.value, key: keyInput.value });
                    await this.saveTokens();
                    this.updateTokenSelect();
                    close();
                }
            });

            cancelButton.addEventListener('click', close);
        },

        showManageTokensModal() {
            const content = document.createElement('div');
            const tokenList = document.createElement('div');
            tokenList.className = 'claude-token-list';

            this.tokens.forEach((token, index) => {
                const tokenItem = document.createElement('div');
                tokenItem.className = 'claude-token-item';

                const headerDiv = document.createElement('div');
                headerDiv.className = 'claude-token-item-header';

                const nameDiv = document.createElement('div');
                nameDiv.className = 'claude-token-name';
                nameDiv.textContent = token.name;

                const deleteButton = document.createElement('button');
                deleteButton.textContent = '删除';
                deleteButton.className = 'cancel';
                deleteButton.addEventListener('click', async () => {
                    this.tokens.splice(index, 1);
                    await this.saveTokens();
                    this.updateTokenSelect();
                    tokenItem.remove();
                });

                headerDiv.appendChild(nameDiv);
                headerDiv.appendChild(deleteButton);

                const keyDiv = document.createElement('div');
                keyDiv.className = 'claude-token-key';
                keyDiv.textContent = token.key;

                const copyButton = document.createElement('button');
                copyButton.textContent = '复制';
                copyButton.className = 'copy-button';
                copyButton.addEventListener('click', () => {
                    navigator.clipboard.writeText(token.key);
                    copyButton.textContent = '已复制';
                    setTimeout(() => {
                        copyButton.textContent = '复制';
                    }, 2000);
                });

                const keyContainer = document.createElement('div');
                keyContainer.style.display = 'flex';
                keyContainer.style.alignItems = 'center';
                keyContainer.appendChild(keyDiv);
                keyContainer.appendChild(copyButton);

                tokenItem.appendChild(headerDiv);
                tokenItem.appendChild(keyContainer);

                tokenList.appendChild(tokenItem);
            });

            content.appendChild(tokenList);

            const closeButton = document.createElement('button');
            closeButton.textContent = '关闭';
            content.appendChild(closeButton);

            const { close } = UI.createModal('管理Tokens', content);
            closeButton.addEventListener('click', close);
        },

        fetchIPCountryCode() {
            GM_xmlhttpRequest({
                method: "GET",
                url: config.ipApiUrl,
                onload: (response) => {
                    if (response.status === 200) {
                        try {
                            const data = JSON.parse(response.responseText);
                            this.toggleButton.innerText = data.country_code || 'NA';
                        } catch (e) {
                            this.toggleButton.innerText = 'ERR';
                        }
                    } else {
                        this.toggleButton.innerText = 'ERR';
                    }
                },
                onerror: () => {
                    this.toggleButton.innerText = 'ERR';
                }
            });
        }
    };

    // 初始化应用
    await App.init();
})();

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      2025-03-13
// @description  try to take over the world!
// @author       You
// @match        http://*/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Your code here...
})();

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      2025-03-13
// @description  try to take over the world!
// @author       You
// @match        https://duckduckgo.com/?q=DuckDuckGo+AI+Chat&ia=chat&duckai=1
// @icon         https://www.google.com/s2/favicons?sz=64&domain=duckduckgo.com
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Your code here...
})();

// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      2025-03-16
// @description  try to take over the world!
// @author       You
// @match        http://*/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Your code here...
})();

管理界面效果图(有点丑 :joy:):

:folded_hands: 致谢

感谢以下大佬们的努力,为 Claude 多账户管理带来了便利:

41 个赞

进来学习

6 个赞

是刷论坛的时候经常看到的大佬!(膜拜) :grinning_face: :heart_on_fire: :waving_hand:

6 个赞

加点效果图呗 看看 :tieba_087:

6 个赞

我一个号都没有(原来一个被封了),你们都开始要切多个号用了 :face_with_peeking_eye:

2 个赞

好,让O3 mini-high整的,界面有点丑 :joy:

2 个赞

感觉ui不如这个

3 个赞

这个管理脚本从七月份始皇的fuclaude出来后就有了,优化版油猴脚本,用于管理 claude 的 sessionKey
[更新了链接]切换Claude Sessionkey的油猴脚本(适用fuclaude),优化版
我和claude合作的切换sessionKey的油猴脚本,同时参考了几位大佬的帖子,适用于fuclaude!
现在claude账户封控感觉没这么严了,佬友可以多注册几个账户玩玩 :grinning_face:

谢谢佬友,我发之前没见过这个帖子哈哈:joy:,我去学习学习 :grinning_face:

谢谢佬友哇,我真是论坛刷少了,早知道有这个我就不整了:rofl:,我感觉我这个帖子已经没有存在的必要了:rofl:

兄弟,兄弟,不至于,不至于 :laughing:

:rofl:谢谢佬友了,那个确实不错喔,不过我第一次发帖,没找到怎么关闭帖子,好像得申请:rofl:

以前是接码注册过两个也被封了两个,现在的注册姿势是什么,还是要接码吗?

1 个赞

我也不会 :rofl:

1 个赞

应该还是吧,我现在也没注册了:rofl:不太清楚了,当时注册了二十几个号轮着用的:rofl:,可以给佬友推荐一个接码网站,这是我当时用的
https://sms-activate.org/getNumber

我就一菜鸟,不敢当不敢当

ok,我之前也是用的这个网站接码的,等我周末搞一搞。

哪有,樱佬的:airplane:场分享贴对我帮助很大:bouquet:

:bouquet:我印象里当时用阿根廷区的号成功率要高一些并且价格相对便宜,不知道现在怎么样了:rofl:

谢谢佬友分享