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