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

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

太强了,佬:+1:

没有号啊tieba_087

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

感谢大佬分享!

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

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

这两天用6个isp 节点,gmail自用老号,注册都成功。
edge隐私模式换着节点一个个注册

看来claude2api不远了,佬 棒棒的

所以要多个gmail多次接码

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

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

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

safari手机拦截了弹出的新标签页
试了下chrome手机版可以修复,弹出时打钩一律显示就好了

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