更新代码
// =======================================
// = Cloudflare Worker =
// =======================================
// ====== 配置信息 ======
const CACHE_DURATION_MINUTES = 50; // 对话缓存时间
const CHECK_PERIOD_MINUTES = 240; // 繁忙复位时间
const RETENTION_PERIOD_MINUTES = 240;
const BASE_URL = 'https://demo.fuclaude.com';
const AUTH_ENDPOINT = '/manage-api/auth/oauth_token';
const CACHE_KEY = 'login_urls_cache';
// 将分钟转换为毫秒
const CACHE_DURATION_MS = CACHE_DURATION_MINUTES * 60 * 1000;
const CHECK_PERIOD_MS = CHECK_PERIOD_MINUTES * 60 * 1000;
const RETENTION_PERIOD_MS = RETENTION_PERIOD_MINUTES * 60 * 1000;
// 状态常量
const STATUS = {
BUSY: { color: 'busy', status: '繁忙' },
FREE: { color: 'free', status: '空闲' }
};
// Session Keys
const SESSION_KEYS = [
'sk-ant-sid01-hh-mtHqsoN1K0yVF-98s7DGrtuwKczckLzH0VLN_J-XgNA9FKmoHV4_QinzgiVNZjtUe7BQfxd2A-KZGcUAAA',
'sk-ant-sid01-B-f0dml-IvgTdXQ5FlDgfQiCILgeJyaeVuTDWFckAm11WIl8KCWIrBHQ8QfafBQvi5IWQ-x7-VmgAA',
];
// ====== 获取登录地址 ======
async function getLoginUrl(sessionKey) {
try {
const response = await fetch(`${BASE_URL}${AUTH_ENDPOINT}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
session_key: sessionKey,
unique_name: Date.now().toString()
}),
});
const data = await response.json();
if (data.login_url) {
return data.login_url.startsWith('http')
? data.login_url
: `${BASE_URL}${data.login_url}`;
}
} catch (error) {
console.error("Login URL Error:", error);
}
return '#';
}
// ====== 获取账号状态 ======
async function getAccountStatus(accountNumber, env) {
const key = `clicks_${accountNumber}`;
const currentTime = Date.now();
try {
const clicks = await env.CLAUDE_KV.get(key, 'json') || [];
const recentClicks = clicks.filter(time => (currentTime - time) < CHECK_PERIOD_MS);
return recentClicks.length >= 1 ? STATUS.BUSY : STATUS.FREE;
} catch (error) {
console.error("Status Error:", error);
return STATUS.FREE;
}
}
// ====== 更新点击时间(上报用户点击) ======
async function updateClickTime(accountNumber, env) {
const key = `clicks_${accountNumber}`;
const currentTime = Date.now();
try {
const clicks = await env.CLAUDE_KV.get(key, 'json') || [];
const recentClicks = clicks.filter(time => (currentTime - time) < RETENTION_PERIOD_MS);
recentClicks.push(currentTime);
await env.CLAUDE_KV.put(key, JSON.stringify(recentClicks));
} catch (error) {
console.error("Update Click Error:", error);
}
}
// ====== 返回「静态」页面框架 ======
function getSkeletonHTML() {
return `<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Claude 镜像号池</title>
<style>
body, html {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
margin: 0; padding: 0; min-height: 100vh;
background: linear-gradient(135deg, #ffffff, #f0f0f0);
}
.container {
max-width: 1200px; margin: 0 auto; padding: 20px;
}
header {
display: flex; justify-content: space-between; align-items: center;
padding: 20px 0;
}
h1, h2 {
color: #2c3e50; margin: 0;
}
.accounts {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 15px;
margin: 30px 0;
}
.account {
display: flex; flex-direction: column; justify-content: center; align-items: center;
min-height: 80px; text-decoration: none; padding: 12px 8px; border-radius: 12px;
font-size: 0.95rem; transition: all 0.3s ease; border: 1px solid rgba(0,0,0,0.1);
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.account.free {
background-color: #81c784; color: #1b5e20;
}
.account.busy {
background-color: #ef9a9a; color: #c62828;
}
.account:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.account span {
margin-top: 5px;
font-size: 0.85rem;
opacity: 0.9;
}
footer {
margin-top: 40px; padding-top: 20px; border-top: 1px solid #eee; text-align: center; color: #666;
}
@media (max-width: 768px) {
.accounts {
grid-template-columns: repeat(3, 1fr); gap: 10px;
}
.account {
min-height: 70px; padding: 10px 5px;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<img src="https://share.claude.asia/img/logo.png" height="30" alt="logo" />
</header>
<main>
<h2 style="text-align:center;">免费账号</h2>
<!-- 账号列表容器 -->
<div id="accountList" class="accounts">
<!-- 这里先留空,JS 代码会把数据填充进来 -->
</div>
</main>
<footer>
<div class="copyright">© Claude.asia</div>
</footer>
</div>
<!-- 通过 JS 异步获取数据,再渲染到页面 -->
<script>
// 渲染函数
function renderAccounts(data) {
const accountList = document.getElementById('accountList');
if (!accountList) return;
// 根据获取到的 data,动态生成 HTML
// data.loginUrls 与 data.statuses 长度一致
let html = '';
data.loginUrls.forEach((url, i) => {
const colorClass = data.statuses[i].color;
const statusName = data.statuses[i].status;
html += \`
<a href="\${url}"
class="account \${colorClass}"
onclick="handleClick(\${i+1}, event, '\${url}')">
账号\${i+1}
<span>(\${statusName})</span>
</a>
\`;
});
accountList.innerHTML = html;
}
// 点击事件上报
function handleClick(accountNumber, event, url) {
event.preventDefault();
// 先通知服务器
fetch('/click', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ accountNumber })
}).then(() => {
// 再打开新页面
window.open(url, '_blank');
// 短暂延迟后刷新当前页面,或者重新拉取数据
setTimeout(() => location.reload(), 500);
}).catch(console.error);
}
// 异步获取 /data
async function loadData() {
try {
const res = await fetch('/data');
const data = await res.json();
// data = { loginUrls: [...], statuses: [...] }
renderAccounts(data);
} catch (error) {
console.error('Load data error:', error);
}
}
// 页面加载后拉取数据
window.addEventListener('DOMContentLoaded', loadData);
</script>
</body>
</html>
`;
}
// ====== 获取实际数据并返回 JSON ======
async function getData(env) {
// 先尝试从 KV 中读取缓存
const cachedData = await env.CLAUDE_KV.get(CACHE_KEY, 'json');
const now = Date.now();
let loginUrls;
// 看看缓存是否过期
if (cachedData && (now - cachedData.timestamp) < CACHE_DURATION_MS) {
loginUrls = cachedData.urls;
} else {
// 没缓存或过期,重新生成并写入 KV
loginUrls = await Promise.all(SESSION_KEYS.map(key => getLoginUrl(key)));
await env.CLAUDE_KV.put(CACHE_KEY, JSON.stringify({
timestamp: now,
urls: loginUrls
}));
}
// 获取账号状态
const statuses = await Promise.all(
SESSION_KEYS.map((_, i) => getAccountStatus(i + 1, env))
);
return { loginUrls, statuses };
}
// ====== 主入口 ======
export default {
async fetch(request, env) {
const url = new URL(request.url);
// 1) 静态框架路由:GET /
if (url.pathname === '/' && request.method === 'GET') {
return new Response(getSkeletonHTML(), {
headers: { 'Content-Type': 'text/html;charset=UTF-8' }
});
}
// 2) 动态数据路由:GET /data
if (url.pathname === '/data' && request.method === 'GET') {
const data = await getData(env);
return new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' }
});
}
// 3) 点击上报路由:POST /click
if (url.pathname === '/click' && request.method === 'POST') {
try {
const { accountNumber } = await request.json();
if (accountNumber > 0 && accountNumber <= SESSION_KEYS.length) {
await updateClickTime(accountNumber, env);
}
return new Response(JSON.stringify({ success: true }));
} catch (err) {
console.error('Click update error', err);
return new Response(JSON.stringify({ success: false }), { status: 500 });
}
}
// 其余未匹配路径返回 404
return new Response('Not found', { status: 404 });
}
};