源码分享 - Cloudflare Worker 建立 Claude 镜像号池

wsa01378 2024-11-19 17:09 1


搭建方式:CF Worker和KV储存


KV储存: 创建命名空间 - CLAUDE_KV

然后点进去worker-设置-绑定如下图


CF Worker 代码,把claude的sk-key改到范例里面就能用了



// Configuration
const CACHE_DURATION_MINUTES = 50;//对话缓存时间 min
const CHECK_PERIOD_MINUTES = 240; //繁忙复位时间 min
const RETENTION_PERIOD_MINUTES = 240;
const BASE_URL = 'https://claude.asia';
const AUTH_ENDPOINT = '/manage-api/auth/oauth_token';
const CACHE_KEY = 'login_urls_cache';

// Convert minutes to milliseconds
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;

// Status constants
const STATUS = {
BUSY: { color: 'busy', status: '繁忙' },
FREE: { color: 'free', status: '空闲' }
};

//放SESSION_KEYS的地方,带引号,一行一个
const SESSION_KEYS = [
'sk-ant-sid01-hh-mtHqsoN1K0yVF-9s7DGrtZRcKedOJJOuwKczckLzVLN_J-XgNA9FKmoHV4_QinzgiVNZjtUe7BQfxA',
'sk-ant-sid01-6QAgd-Dt0nfMeKAXK2x5jpJbM7QbQPkMToHOv3C-OjviencKp27Gm0faNtm6cpuQ7hrpDVQ4dXtwKAAA',
//'sk-ant-sid01-6QAgd-Dt0nfMeAXK2x5jtpJbM7QbQPkMToHOv3C-OjviencKp27Gm0faNtmjoTR6cpuQ7hrpDVQ4AA',
//'sk-ant-sid01-6QAgd-Dt0nfMeAXK2x5jtpJbM7QbQPkMToHOv3C-OjviencKp27Gm0faNtmjoTR6cpuQ7hrpDVQ4AA',

];

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);
}
}

async function generateHTML(loginUrls, accountStatuses) {
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>
<script>
function updateStatus(accountNumber, event) {
event.preventDefault();
const link = event.currentTarget;
const url = link.href;

fetch('?clicked=' + accountNumber)
.then(() => {
window.open(url, '_blank');
setTimeout(() => location.reload(), 500);
})
.catch(console.error);
}
</script>
</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 class="accounts">
${loginUrls.map((url, i) => `
<a href="${url}"
class="account ${accountStatuses[i].color}"
onclick="updateStatus(${i + 1}, event)">
账号${i + 1}
<span>(${accountStatuses[i].status})</span>
</a>
`).join('')}
</div>
</main>

<footer>
<div class="copyright">© Claude.asia</div>
</footer>
</div>
</body>
</html>`;
}

export default {
async fetch(request, env) {
const url = new URL(request.url);

// Handle click updates
if (url.searchParams.has('clicked')) {
const clickedAccount = parseInt(url.searchParams.get('clicked'));
if (clickedAccount > 0 && clickedAccount <= SESSION_KEYS.length) {
await updateClickTime(clickedAccount, env);
}
return new Response('OK');
}

// Get login URLs from cache or API
const cacheKey = 'login_urls_cache';
let loginUrls;

try {
const cachedData = await env.CLAUDE_KV.get(cacheKey, 'json');
if (cachedData && (Date.now() - cachedData.timestamp) < (CACHE_DURATION * 1000)) {
loginUrls = cachedData.urls;
} else {
// Dynamically generate login URLs based on SESSION_KEYS length
loginUrls = await Promise.all(SESSION_KEYS.map(key => getLoginUrl(key)));
await env.CLAUDE_KV.put(cacheKey, JSON.stringify({
timestamp: Date.now(),
urls: loginUrls
}));
}
} catch (error) {
console.error("Cache Error:", error);
loginUrls = await Promise.all(SESSION_KEYS.map(key => getLoginUrl(key)));
}

// Get account statuses dynamically based on SESSION_KEYS length
const accountStatuses = await Promise.all(
SESSION_KEYS.map((_, i) => getAccountStatus(i + 1, env))
);

// Generate and return HTML
const html = await generateHTML(loginUrls, accountStatuses);
return new Response(html, {
headers: {
'Content-Type': 'text/html;charset=UTF-8'
}
});
}
};

提取sk keys方式↓:


源码魔改自下面项目


Claude镜像号池地址:https://share.claude.asia
现在有44个免费账号,我自己共享了30个,坛友贡献了14个
有一些朋友问源码,下面分享下,是用php写的。
在网站根目录新建php文件和session_keys.txt文件:
php文件代码如下:
<?php
// 从文件中读取 session keys
$session_keys = file('sessio…


注册claude方法
gmail邮箱,美国isp节点。始王提到icloud隐私邮箱也ok。
接码用https://sms-activate.guru/cn 支付宝充20¥接15条短信+
国家选ISRAEL 小众号段
一次可以开几个号,接码有延迟1分钟+,收不到码点交叉可以自动返款。
当天没访问过claude的节点,1天注册1个号可能稳一点。
试过华为云奈飞解锁的SG,注册会秒封,可能ip不够干净..
最新回复 (17)
  • 𝓫𝓪𝓷𝓵𝓪𝓷 11-19 17:10
    1

    太强了,佬^-^

  • yangtb2024 11-19 17:10
    2

    没有号啊

  • 𝓫𝓪𝓷𝓵𝓪𝓷 11-19 17:10
    3

    sms不是一个号只能用一次吗

  • 大帅哥 11-19 17:12
    4

    感谢大佬分享!

  • 黑猫警长 11-19 17:14
    5

    挺好,就是号没的太快了,堆不上去

  • mistpeak 11-19 17:16
    6

    大佬邮箱呢,上回我8个gmail邮箱爆了7个,但是我自用的最早的2年号倒是依旧坚挺,Claude2开始前就注册了

  • wsa01378 楼主 11-19 17:23
    7

    这两天用6个isp 节点,gmail自用老号,注册都成功。

    edge隐私模式换着节点一个个注册

  • CowBoy 11-19 17:25
    8

    看来claude2api不远了,佬 棒棒的

  • wsa01378 楼主 11-19 18:04
    9

    所以要多个gmail多次接码

  • 𝓫𝓪𝓷𝓵𝓪𝓷 11-19 18:11
    10

    我一个手机号给一个邮箱弄了就不行了

  • 赛博人 11-28 10:03
    11

    太强了佬,如果能加个登录验证就好了,防止扫到

  • 理塘王 11-30 17:58
    12

    我测试手机点账号打不开新页面,pc是可以的,不知道什么情况

  • wsa01378 楼主 11-30 18:02
    13

    safari手机拦截了弹出的新标签页

    试了下chrome手机版可以修复,弹出时打钩一律显示就好了

  • 理塘王 11-30 19:58
    14

    搞定了,搜了下ios不允许window.open方法,我加了个判断ua的执行不同跳转就好了

  • wsa01378 楼主 01-27 19:41
    15

    更新代码


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

  • dw 02-28 14:48
    16

    热佬,如果自己想给cloudflare访问加个密码该设置哪个参数呢?

  • hyint8 09-16 02:01
    17

    很喜欢,保存一份

* 帖子来源Linux.do
返回