// ==UserScript==
// @name Claude SessionKey Manager
// @version 3.1
// @description 轻松管理和切换Claude的SessionKey(可拖拽浮动按钮,简洁弹出式菜单)。此脚本基于 https://gf.qytechs.cn/scripts/501296,原作者: https://gf.qytechs.cn/zh-CN/users/1337296
// @match https://claude.ai/*
// @match https://demo.fuclaude.com/*
// @grant none
// @license GNU GPLv3
// @author f14xuanlv
// @namespace https://gf.qytechs.cn/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 !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: 200px;
overflow-y: auto;
}
.skm-token-item {
padding: 10px;
margin-bottom: 8px;
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">×</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.1</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();
})();