claude sessionkey checker (cf worker)
LINUX DO - 最新话题 (卡尔 · 马克思)没啥大用,但是估计今天很多人有点用。
cloudflare worker创建一个就行了。
const CLAUDE_ORIGIN = "「这里填写fuclaude网站,需要填写不带cloudflare盾的,不行就自建一个」";
const URL_LOGIN = `${CLAUDE_ORIGIN}/login_token?state=`;
const URL_API = `${CLAUDE_ORIGIN}/api/bootstrap`;
const CONSTANTS = {
CLAUDE_ORIGIN,
URL_LOGIN,
URL_API,
HEADERS: {
DNT: "1",
Referer: CLAUDE_ORIGIN + "/",
"Upgrade-Insecure-Requests": "1",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0",
"sec-ch-ua": '"Microsoft Edge";v="141", "Not?A_Brand";v="8", "Chromium";v="141"',
"sec-ch-ua-platform": '"Windows"',
},
FORM_HEADERS: { "Content-Type": "application/x-www-form-urlencoded" },
MAX_RETRIES: 3,
MAX_CONCURRENCY: 3,
TIMEOUT_MS: 15000,
MASK_KEEP: 15,
};
export default {
async fetch(request) {
const { pathname } = new URL(request.url);
if (request.method === "POST" && pathname === "/check") {
const body = await request.json();
const keys = Array.isArray(body?.keys) ? body.keys : [];
const { tasks, order } = dedupeWithOrder(keys);
const maxConcurrency = clamp(Number(body?.maxConcurrency) || CONSTANTS.MAX_CONCURRENCY, 1, 10);
const maxRetries = clamp(Number(body?.maxRetries) || CONSTANTS.MAX_RETRIES, 1, 5);
if (!tasks.length) return json({ ok: true, summary: { total: 0, alive: 0, dead: 0, error: 0 }, results: [] });
const results = await runWithConcurrency(tasks, maxConcurrency, (t) => checkSession(t, maxRetries));
const sorted = results.sort((a, b) => a[0] - b[0]);
const alive = sorted.filter((x) => x[2] === "alive").length;
const dead = sorted.filter((x) => x[2] === "dead").length;
const error = sorted.filter((x) => x[2] === "error").length;
return json({
ok: true,
order,
summary: { total: sorted.length, alive, dead, error },
results: sorted.map(([i, key, status, info]) => ({ index: i, key, status, info })),
});
}
return html(indexHtml());
},
};
function json(data, init = {}) {
return new Response(JSON.stringify(data), { headers: { "content-type": "application/json; charset=utf-8" }, ...init });
}
function html(content, init = {}) {
return new Response(content, { headers: { "content-type": "text/html; charset=utf-8" }, ...init });
}
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
function clamp(n, a, b) {
return Math.max(a, Math.min(b, n));
}
async function fetchWithTimeout(url, init = {}) {
const controller = new AbortController();
const t = setTimeout(() => controller.abort("timeout"), CONSTANTS.TIMEOUT_MS);
try {
return await fetch(url, { ...init, signal: controller.signal });
} finally {
clearTimeout(t);
}
}
function collectCookies(headers) {
const items = typeof headers.getSetCookie === "function" ? headers.getSetCookie() : [headers.get("set-cookie")].filter(Boolean);
const pairs = items.map((c) => c.split(";")[0]).filter(Boolean);
return pairs.join("; ");
}
function dedupeWithOrder(lines) {
const map = new Map();
let i = 1;
for (const raw of lines) {
const key = String(raw || "").trim();
if (!key) continue;
if (!map.has(key)) map.set(key, i);
i++;
}
const tasks = [...map.entries()].map(([key, index]) => [index, key]);
const order = tasks.map(([index, key]) => ({ index, key }));
return { tasks, order };
}
async function checkSession(tuple, maxRetries) {
const [originalIndex, sessionKey] = tuple;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const login = await fetchWithTimeout(CONSTANTS.URL_LOGIN, {
method: "POST",
headers: { ...CONSTANTS.HEADERS, ...CONSTANTS.FORM_HEADERS },
body: new URLSearchParams({ action: "token", session_key: sessionKey }).toString(),
});
const cookie = collectCookies(login.headers);
const api = await fetchWithTimeout(CONSTANTS.URL_API, { headers: { ...CONSTANTS.HEADERS, Cookie: cookie } });
if (!api.ok) return [originalIndex, sessionKey, "error", `HTTP ${api.status}`];
let data;
try {
data = await api.json();
} catch {
if (attempt < maxRetries - 1) {
await sleep(1000);
continue;
} else {
return [originalIndex, sessionKey, "error", `JSON解析失败 (已重试 ${maxRetries} 次)`];
}
}
const account = data?.account || {};
if (account?.email_address) return [originalIndex, sessionKey, "alive", account.email_address];
return [originalIndex, sessionKey, "dead", null];
} catch (e) {
const msg = (e && (e.reason || e.message)) || String(e);
return [originalIndex, sessionKey, "error", msg];
}
}
return [originalIndex, sessionKey, "error", "未知错误"];
}
async function runWithConcurrency(items, limit, worker) {
const results = new Array(items.length);
let cursor = 0;
const runners = Array(Math.min(limit, items.length))
.fill(0)
.map(async () => {
while (true) {
const idx = cursor++;
if (idx >= items.length) break;
results[idx] = await worker(items[idx], idx);
}
});
await Promise.all(runners);
return results;
}
function indexHtml() {
return `<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
<title>Session Key 批量检测</title>
<style>
:root{
--bg:#f8f9fb;--card:#ffffff;--ink:#1f2937;--muted:#6b7280;--line:#e5e7eb;--brand:#111827;--ok:#16a34a;--bad:#ef4444;--warn:#d97706;
--radius:16px;--shadow:0 10px 30px rgba(0,0,0,.06);
}
*{box-sizing:border-box}html,body{height:100%}
body{margin:0;background:radial-gradient(1200px 600px at 80% -10%, #eef2ff 0, transparent 60%), var(--bg);color:var(--ink);font:500 14px/1.6 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","PingFang SC","Noto Sans CJK SC","Noto Sans SC","Microsoft YaHei","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";}
.wrap{max-width:980px;margin:0 auto;padding:28px 16px 64px}
header{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px}
.title{display:flex;gap:10px;align-items:center;font-weight:800;letter-spacing:.2px}
.dot{width:10px;height:10px;border-radius:50%;background:var(--brand);box-shadow:0 0 0 4px rgba(17,24,39,.08)}
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow)}
.stack{display:grid;grid-template-columns:1fr;gap:16px}
.area{padding:18px}
.side{padding:18px}
textarea{width:100%;min-height:240px;border:1px dashed var(--line);background:#fafafa;border-radius:12px;padding:14px;outline:none;resize:vertical;font:500 13px/1.5 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; white-space:pre; overflow:auto}
textarea::-webkit-scrollbar{height:10px}
.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.hint{color:var(--muted)}
.sep{height:1px;background:var(--line);margin:14px 0}
.btn{appearance:none;border:1px solid var(--line);background:var(--ink);color:#fff;border-radius:12px;padding:10px 14px;font-weight:700;cursor:pointer;transition:transform .04s ease, opacity .2s}
.btn:disabled{opacity:.6;cursor:not-allowed}
.btn.secondary{background:#fff;color:var(--ink)}
.btn.ghost{background:transparent;border-color:var(--line);color:var(--ink)}
.btn:active{transform:translateY(1px)}
.pill{font-size:12px;color:var(--muted);padding:4px 8px;border-radius:999px;border:1px solid var(--line);background:#fff}
.summary{display:flex;gap:8px;flex-wrap:wrap}
.tag{padding:6px 10px;border-radius:999px;border:1px solid var(--line);background:#fff;font-weight:700}
.tag.ok{color:var(--ok);border-color:#dcfce7;background:#f0fdf4}
.tag.bad{color:var(--bad);border-color:#fee2e2;background:#fef2f2}
.tag.warn{color:var(--warn);border-color:#fde68a;background:#fffbeb}
table{width:max-content;border-collapse:separate;border-spacing:0;border-radius:12px;border:1px solid var(--line)}
thead th{font-size:12px;letter-spacing:.3px;text-transform:uppercase;color:var(--muted);text-align:left;background:#fafafa}
th,td{padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:middle; white-space:nowrap}
tbody tr:last-child td{border-bottom:none}
.status{font-weight:800;display:inline-flex;align-items:center;gap:6px}
.s-ok{color:var(--ok)}.s-bad{color:var(--bad)}.s-warn{color:var(--warn)}
.muted{color:var(--muted)}
.kbd{font:600 12px/1 ui-monospace,monospace;background:#f3f4f6;border:1px solid var(--line);padding:2px 6px;border-radius:6px}
.footer{color:var(--muted);font-size:12px;margin-top:18px;display:flex;justify-content:space-between;gap:8px;flex-wrap:wrap}
.switch{display:inline-flex;gap:8px;align-items:center}
.switch input{appearance:none;width:36px;height:22px;border-radius:999px;background:#e5e7eb;position:relative;transition:background .2s}
.switch input:checked{background:#111827}
.switch input::after{content:"";position:absolute;top:3px;left:3px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s}
.switch input:checked::after{transform:translateX(14px)}
.scroll-x{overflow-x:auto; padding-bottom:8px}
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="title"><span class="dot"></span><span>Session Key 批量检测</span></div>
<div class="row">
<label class="switch"><input id="mask" type="checkbox" checked><span class="hint">掩码显示</span></label>
<button id="exportCsv" class="btn secondary" disabled>导出 CSV</button>
<button id="exportJson" class="btn ghost" disabled>导出 JSON</button>
</div>
</header>
<div class="stack">
<div class="card area">
<div class="row" >
<div class="hint">一行一个 session_key,自动去重与清洗(不自动换行)</div>
<div class="pill" id="counter">0 行</div>
</div>
<div class="row" >
<textarea id="input" placeholder="在此粘贴多行 session_key,一行一个" wrap="off"></textarea>
</div>
<div class="row" >
<div class="row">
<span class="pill">并发 <strong id="con-display">3</strong></span>
<input id="con" type="range" min="1" max="10" value="3" >
<span class="pill">重试 <strong id="rt-display">3</strong></span>
<input id="rt" type="range" min="1" max="5" value="3" >
</div>
<div class="row">
<button id="sample" class="btn ghost">填入样例</button>
<button id="clear" class="btn secondary">清空</button>
<button id="run" class="btn">开始检测</button>
</div>
</div>
</div>
<div class="card side">
<div class="row" >
<div class="hint" id="status">就绪</div>
<div class="summary" id="summary"></div>
</div>
<div class="sep"></div>
<div id="tableWrap" class="scroll-x"></div>
</div>
</div>
<div class="footer">
<div>支持 <span class="kbd">⌘/Ctrl</span> + <span class="kbd">Enter</span> 直接开始检测</div>
<div id="time" class="muted"></div>
</div>
</div>
<script>
const UI = {
input: document.getElementById('input'),
run: document.getElementById('run'),
clear: document.getElementById('clear'),
sample: document.getElementById('sample'),
counter: document.getElementById('counter'),
status: document.getElementById('status'),
summary: document.getElementById('summary'),
table: document.getElementById('tableWrap'),
mask: document.getElementById('mask'),
exportCsv: document.getElementById('exportCsv'),
exportJson: document.getElementById('exportJson'),
con: document.getElementById('con'),
conDisplay: document.getElementById('con-display'),
rt: document.getElementById('rt'),
rtDisplay: document.getElementById('rt-display'),
time: document.getElementById('time'),
};
const STATE = { results: [], order: [], mask: true, meta: { total:0, alive:0, dead:0, error:0 } };
const SAMPLE = [
"sk-ant-sid01-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"sk-ant-sid01-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"sk-ant-sid01-cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"sk-ant-sid01-dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
].join("\\n");
function dedupeLines(text){
const lines = text.split(/\\r?\\n/).map(s => s.trim()).filter(Boolean);
const map = new Map();
lines.forEach((k, i) => { if(!map.has(k)) map.set(k, i+1); });
return { list: [...map.keys()], count: map.size };
}
function maskKey(k){
const keep = ${CONSTANTS.MASK_KEEP};
return k.length > keep ? k.slice(0, keep) + "…" : k;
}
function setBusy(b){
UI.run.disabled = b; UI.clear.disabled = b; UI.sample.disabled = b; UI.con.disabled = b; UI.rt.disabled = b;
UI.exportCsv.disabled = b || STATE.results.length === 0;
UI.exportJson.disabled = b || STATE.results.length === 0;
UI.status.textContent = b ? "检测中…" : "就绪";
}
function renderSummary(){
const { total, alive, dead, error } = STATE.meta;
UI.summary.innerHTML = [
\`<span class="tag">总数:\${total}</span>\`,
\`<span class="tag ok">存活:\${alive}</span>\`,
\`<span class="tag bad">失效:\${dead}</span>\`,
\`<span class="tag warn">错误:\${error}</span>\`,
].join("");
}
function renderTable(){
if(!STATE.results.length){ UI.table.innerHTML = '<div class="muted">暂无数据</div>'; return; }
const rows = STATE.results.map(r => {
const s = r.status;
const cls = s === 'alive' ? 's-ok' : s === 'dead' ? 's-bad' : 's-warn';
const icon = s === 'alive' ? '✓' : s === 'dead' ? '✗' : '!';
const key = STATE.mask ? maskKey(r.key) : r.key;
const info = r.info ? String(r.info).replace(/[&<>]/g, s => ({'&':'&','<':'<','>':'>'}[s])) : '—';
return \`<tr>
<td class="muted" >\${String(r.index).padStart(3,' ')}</td>
<td >\${key}</td>
<td ><span class="status \${cls}"><span>\${icon}</span><span>\${s}</span></span></td>
<td class="muted">\${info}</td>
</tr>\`;
}).join("");
UI.table.innerHTML = \`
<div class="scroll-x">
<table>
<thead><tr><th >#</th><th>Session Key</th><th >状态</th><th>附加信息</th></tr></thead>
<tbody>\${rows}</tbody>
</table>
</div>\`;
}
async function run(){
const { list, count } = dedupeLines(UI.input.value);
UI.counter.textContent = \`\${count} 行\`;
if(!count){ UI.status.textContent = "未找到有效的 session_key"; return; }
setBusy(true);
const payload = { keys: list, maxConcurrency: Number(UI.con.value), maxRetries: Number(UI.rt.value) };
try{
const res = await fetch("/check", { method:"POST", headers:{ "content-type":"application/json" }, body: JSON.stringify(payload) });
const data = await res.json();
if(!data.ok) throw new Error("请求失败");
STATE.results = data.results;
STATE.order = data.order;
STATE.meta = data.summary;
renderSummary(); renderTable();
UI.exportCsv.disabled = STATE.results.length === 0;
UI.exportJson.disabled = STATE.results.length === 0;
UI.status.textContent = "完成";
UI.time.textContent = new Date().toLocaleString();
}catch(e){
UI.status.textContent = "出错:" + (e.message || String(e));
}finally{
setBusy(false);
}
}
function exportCSV(){
if(!STATE.results.length) return;
const head = ["index","key","status","info"];
const rows = STATE.results.map(r => [r.index, r.key, r.status, r.info ?? ""]);
const csv = [head, ...rows].map(arr => arr.map(v => '"' + String(v).replace(/"/g,'""') + '"').join(",")).join("\\n");
const blob = new Blob([new TextEncoder().encode(csv)], { type:"text/csv;charset=utf-8" });
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "session_check_"+Date.now()+".csv";
a.click();
URL.revokeObjectURL(a.href);
}
function exportJSON(){
if(!STATE.results.length) return;
const blob = new Blob([JSON.stringify({ summary: STATE.meta, results: STATE.results }, null, 2)], { type:"application/json" });
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "session_check_"+Date.now()+".json";
a.click();
URL.revokeObjectURL(a.href);
}
UI.input.addEventListener('input', () => {
const { count } = dedupeLines(UI.input.value);
UI.counter.textContent = \`\${count} 行\`;
});
UI.run.addEventListener('click', run);
UI.clear.addEventListener('click', () => { UI.input.value = ""; UI.counter.textContent = "0 行"; UI.status.textContent = "就绪"; STATE.results = []; STATE.meta = { total:0, alive:0, dead:0, error:0 }; UI.summary.innerHTML = ""; UI.table.innerHTML = ""; });
UI.sample.addEventListener('click', () => { UI.input.value = SAMPLE; const { count } = dedupeLines(UI.input.value); UI.counter.textContent = \`\${count} 行\`; });
UI.mask.addEventListener('change', (e) => { STATE.mask = !!e.target.checked; renderTable(); });
UI.exportCsv.addEventListener('click', exportCSV);
UI.exportJson.addEventListener('click', exportJSON);
UI.con.addEventListener('input', (e) => { UI.conDisplay.textContent = e.target.value; });
UI.rt.addEventListener('input', (e) => { UI.rtDisplay.textContent = e.target.value; });
document.addEventListener('keydown', (e) => {
if((e.ctrlKey || e.metaKey) && e.key === 'Enter'){ run(); }
});
</script>
</body>
</html>`;
}
3 posts - 3 participants
Generated by RSStT. The copyright belongs to the original author.