在 [更新了链接]切换Claude Sessionkey的油猴脚本(适用fuclaude),优化版 的基础上继续优化
- 将管理和添加的逻辑简化合并
- 将默认token设置改为数组
- 动态更新token列表
- 选中自动切换token
- 支持一键导入和导出
感谢 cursor 的(免费)赞助
感谢 xiaohan17 等佬友的群策群力
感谢 baicha 赞助的一个账号让楼主迈入claude小康
油猴脚本
// ==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();
})();