更加美观的cf worker fuclaude号池

去年11月,我给这位大佬的fuclaude号池做了一些修改,现在我又进行了一次改进,比上次的版本更加美观,并且加上了页面样式切换按钮

演示效果如下:
1
在cloudflare worker中,新建项目,把下面的代码全部复制粘贴,修改key和密码之后,选择部署 即可使用

const CONFIG = {
  ORIGINAL_WEBSITE: "https://demo.fuclaude.com",
  SESSION_KEYS: [       "sk-ant-sid01-1", "sk-ant-sid01-2","sk-ant-sid01-3"
               ],
  KEY_NAMES: ["1", "2", "3"],
  SITE_PASSWORD: "site_passwd",
  GUEST_PASSWORD: "guest_passwd"
};

export default {
  async fetch(request, env, ctx) {
    return await handleRequest(request);
  }
};

async function handleRequest(request) {
  const url = new URL(request.url);
  if (url.pathname === '/login') return handleRootPath(request, url, true);
  if (url.pathname === '/') return handleRootPath(request, url);
  return proxyRequest(request);
}

async function handleRootPath(request, url, forceLogin = false) {
  const cookie = request.headers.get('Cookie') || '';
  if (!forceLogin && cookie.includes('_Secure-next-auth.session-data')) {
    return Response.redirect(`${url.origin}/new`, 302);
  }
  if (request.method === 'POST') return handleLogin(request, url);
  return new Response(formHtml, { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}

async function handleLogin(request, url) {
  try {
    const formData = await request.formData();
    const loginType = formData.get('login_type');
    const selectedKeyIndex = formData.get('session_key_index');
    let body = { 'session_key': CONFIG.SESSION_KEYS[selectedKeyIndex] };
    if (loginType === 'site') {
      const sitePassword = formData.get('site_password');
      if (sitePassword !== CONFIG.SITE_PASSWORD) {
        return new Response('站点密码错误', { status: 403, headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
      }
    } else if (loginType === 'guest') {
      const username = formData.get('username');
      const guestPassword = formData.get('guest_password');
      if (!username || username.trim() === '') {
        return new Response('访客登录必须提供用户名', { status: 400, headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
      }
      if (guestPassword !== CONFIG.GUEST_PASSWORD) {
        return new Response('访客密码错误', { status: 403, headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
      }
      body.unique_name = username;
    } else {
      return new Response('无效的登录类型', { status: 400, headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
    }
    const authUrl = `${CONFIG.ORIGINAL_WEBSITE}/manage-api/auth/oauth_token`;
    const apiResponse = await fetch(authUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body)
    });
    if (!apiResponse.ok) throw new Error(`API request failed with status ${apiResponse.status}`);
    const respJson = await apiResponse.json();
    const login_url = respJson.login_url || '/';
    return Response.redirect(`https://${url.host}${login_url}`, 302);
  } catch (error) {
    console.error('Login error:', error);
    return new Response('登录过程中发生错误', { status: 500, headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
  }
}

async function proxyRequest(request) {
  const url = new URL(request.url);
  const newUrl = `${CONFIG.ORIGINAL_WEBSITE}${url.pathname}${url.search}`;
  const modifiedRequest = new Request(newUrl, request);
  const response = await fetch(modifiedRequest);
  const contentType = response.headers.get('content-type');
  if (contentType && contentType.includes('text/html') && url.pathname !== '/login_oauth') {
    let html = await response.text();
    const regex = /<div[^>]*>(?=[\s\S]*?<h3[\s\S]*?<\/h3>)(?=[\s\S]*?<p[\s\S]*?<\/p>)(?=[\s\S]*?<div[\s\S]*?<\/div>)[\s\S]*?<\/div>/gi;
    html = html.replace(regex, '');
    return new Response(html, { headers: response.headers });
  }
  return response;
}

const formHtml = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Claude镜像站</title>
  <link rel="icon" href="https://www.anthropic.com/favicon.ico" type="image/x-icon">
  <style>
    :root {
      --bg-dark: rgb(33, 33, 33);
      --container-bg-dark: rgb(45, 45, 45);
      --text-dark: #e0e0e0;
      --bg-light: #edeadf;
      --container-bg-light: #f3f1ea;
      --text-light: #333333;
      --button-bg: #ab5235;
      --button-hover: #c25f3d;
      --input-focus: #d87347;
    }

    /* 主题相关样式 */
    [data-theme="dark"] {
      --bg: var(--bg-dark);
      --container-bg: var(--container-bg-dark);
      --text: var(--text-dark);
    }

    [data-theme="light"] {
      --bg: var(--bg-light);
      --container-bg: var(--container-bg-light);
      --text: var(--text-light);
    }

    body, html {
      height: 100dvh;
      margin: 0;
      overflow: hidden;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
      background: var(--bg);
      color: var(--text);
      line-height: 1.6;
    }

    .container {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100dvh;
      padding: 20px;
      box-sizing: border-box;
      backdrop-filter: blur(10px);
      background-image: linear-gradient(120deg, rgba(0,0,0,0.05) 0%, rgba(0,0,0,0.02) 100%);
    }

    .form-container {
      padding: 30px;
      border-radius: 20px;
      width: 100%;
      max-width: 400px;
      background: var(--container-bg);
      transition: all 0.3s ease;
      position: relative;
      backdrop-filter: blur(8px);
      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
    }

    h1 {
      text-align: center;
      margin-bottom: 25px;
      font-size: 28px;
      font-weight: 700;
      letter-spacing: -0.5px;
      background: linear-gradient(45deg, var(--button-bg), var(--button-hover));
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      padding-bottom: 5px;
    }

    .form-group {
      margin-bottom: 20px;
      position: relative;
    }

    label {
      display: block;
      margin-bottom: 8px;
      font-weight: 600;
      font-size: 0.95rem;
      color: var(--text);
      opacity: 0.85;
    }

    input[type="text"], input[type="password"] {
      width: 100%;
      padding: 12px 16px;
      box-sizing: border-box;
      font-size: 16px;
      border-radius: 12px;
      transition: all 0.3s ease;
      background-color: var(--bg);
      border: 1px solid rgba(0, 0, 0, 0.1);
      color: var(--text);
    }

    input[type="text"]:focus, input[type="password"]:focus {
      outline: none;
      border-color: var(--input-focus);
      box-shadow: 0 0 0 3px rgba(171, 82, 53, 0.15);
    }

    button {
      width: 100%;
      padding: 12px 16px;
      background-color: var(--button-bg);
      color: white;
      border: none;
      border-radius: 12px;
      font-size: 16px;
      font-weight: 600;
      cursor: pointer;
      margin-bottom: 12px;
      transition: all 0.3s ease;
      box-shadow: 0 4px 12px rgba(171, 82, 53, 0.2);
    }

    button:hover {
      background-color: var(--button-hover);
      transform: translateY(-1px);
    }

    .key-buttons {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 16px;
      margin-bottom: 12px;
    }

    .key-button {
      flex: 1;
      padding: 14px;
      color: white;
      background-color: var(--button-bg);
      border: none;
      border-radius: 12px;
      cursor: pointer;
      font-size: 15px;
      font-weight: 600;
      text-align: center;
      margin-bottom: 0;
      transition: all 0.3s ease;
      box-shadow: 0 4px 12px rgba(171, 82, 53, 0.2);
      position: relative;
      overflow: hidden;
    }

    .key-button::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: linear-gradient(45deg, transparent, rgba(255,255,255,0.1), transparent);
      transform: translateX(-100%);
      transition: 0.5s;
    }

    .key-button:hover::before {
      transform: translateX(100%);
    }

    .switch-btn {
      background-color: transparent;
      color: var(--button-bg);
      border: 2px solid var(--button-bg);
      font-weight: 600;
      box-shadow: none;
      margin-top: 6px;
    }

    .switch-btn:hover {
      background-color: rgba(171, 82, 53, 0.08);
    }

    .hidden {
      display: none;
    }

    /* 主题切换按钮样式 */
    .theme-toggle {
      position: fixed;
      top: 20px;
      right: 20px;
      width: 40px;
      height: 40px;
      border-radius: 50%;
      background: transparent;
      border: none;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all 0.3s ease;
      z-index: 1000;
      padding: 0;
    }

    .theme-toggle:hover {
      transform: scale(1.2);
    }

    /* 亮色模式下悬浮效果 */
    [data-theme="light"] .theme-toggle:hover {
      background: #202020;
    }

    .theme-toggle svg {
      width: 20px;
      height: 20px;
      transition: all 0.3s ease;
    }

    /* 暗色模式下月亮图标为浅色 */
    [data-theme="dark"] .theme-toggle .moon-icon {
      fill: #f2efe8;
    }

    /* 亮色模式下太阳图标为橙色 */
    [data-theme="light"] .theme-toggle .sun-icon {
      fill: #c25f3d;
    }

    /* 亮色模式下悬浮时太阳图标放大并保持颜色 */
    [data-theme="light"] .theme-toggle:hover .sun-icon {
      fill: #c25f3d;
      transform: scale(1.1);
    }

    .theme-toggle .sun-icon {
      display: none;
    }

    .theme-toggle .moon-icon {
      display: block;
    }

    [data-theme="light"] .theme-toggle .sun-icon {
      display: block;
    }

    [data-theme="light"] .theme-toggle .moon-icon {
      display: none;
    }

    @media (max-width: 480px) {
      .form-container {
        padding: 25px 20px;
      }
      
      h1 {
        font-size: 24px;
      }

      .key-buttons {
        gap: 12px;
      }

      .key-button {
        padding: 12px;
        font-size: 14px;
      }
    }

    @keyframes fadeIn {
      from { opacity: 0; transform: translateY(10px); }
      to { opacity: 1; transform: translateY(0); }
    }

    .form-container {
      animation: fadeIn 0.5s ease-out;
    }
  </style>
</head>
<body>
  <button class="theme-toggle" onclick="toggleTheme()" aria-label="切换主题">
    <svg class="sun-icon" viewBox="0 0 24 24">
      <path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
    </svg>
    <svg class="moon-icon" viewBox="0 0 24 24">
      <path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-3.03 0-5.5-2.47-5.5-5.5 0-1.82.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
    </svg>
  </button>
  <div class="container">
    <div class="form-container" id="keySelection">
      <h1>Claude镜像站</h1>
      <div class="key-buttons" id="keyButtons"></div>
    </div>
    <div class="form-container hidden" id="guestLogin">
      <h1>访客登录</h1>
      <form method="POST">
        <input type="hidden" name="login_type" value="guest">
        <input type="hidden" id="guest_selected_key_index" name="session_key_index" value="">
        <div class="form-group">
          <label for="username">用户名:</label>
          <input type="text" id="username" name="username" placeholder="用于隔离会话,填一个别人猜不到的即可" required>
        </div>
        <div class="form-group">
          <label for="guest_password">访客密码:</label>
          <input type="password" id="guest_password" name="guest_password" placeholder="输入访客密码" required>
        </div>
        <button type="submit">登录</button>
      </form>
      <button class="switch-btn" onclick="toggleForm()">切换到站点密码登录</button>
      <button class="switch-btn" onclick="backToKeySelection()">返回选择通道</button>
    </div>
    <div class="form-container hidden" id="siteLogin">
      <h1>站点密码登录</h1>
      <form method="POST">
        <input type="hidden" name="login_type" value="site">
        <input type="hidden" id="site_selected_key_index" name="session_key_index" value="">
        <div class="form-group">
          <label for="site_password">站点密码:</label>
          <input type="password" id="site_password" name="site_password" placeholder="输入站点密码" required>
        </div>
        <button type="submit">登录</button>
      </form>
      <button class="switch-btn" onclick="toggleForm()">切换到访客登录</button>
      <button class="switch-btn" onclick="backToKeySelection()">返回选择通道</button>
    </div>
  </div>
  <script>
    const keyCount = ${CONFIG.SESSION_KEYS.length};
    const keyNames = ${JSON.stringify(CONFIG.KEY_NAMES)};
    function createKeyButtons() {
      const container = document.getElementById('keyButtons');
      for (let i = 0; i < keyCount; i++) {
        const button = document.createElement('button');
        button.type = 'button';
        button.className = 'key-button';
        button.textContent = keyNames[i] || \`通道 \${i + 1}\`;
        button.onclick = () => selectKey(i);
        container.appendChild(button);
      }
    }
    function selectKey(index) {
      document.getElementById('guest_selected_key_index').value = index;
      document.getElementById('site_selected_key_index').value = index;
      document.getElementById('keySelection').classList.add('hidden');
      document.getElementById('guestLogin').classList.remove('hidden');
    }
    function backToKeySelection() {
      document.getElementById('keySelection').classList.remove('hidden');
      document.getElementById('guestLogin').classList.add('hidden');
      document.getElementById('siteLogin').classList.add('hidden');
    }
    function toggleForm() {
      const guestLogin = document.getElementById('guestLogin');
      const siteLogin = document.getElementById('siteLogin');
      if (guestLogin.classList.contains('hidden')) {
        guestLogin.classList.remove('hidden');
        siteLogin.classList.add('hidden');
      } else {
        guestLogin.classList.add('hidden');
        siteLogin.classList.remove('hidden');
      }
    }
    createKeyButtons();
    function toggleTheme() {
      const html = document.documentElement;
      const currentTheme = html.getAttribute('data-theme') || 'light';
      const newTheme = currentTheme === 'light' ? 'dark' : 'light';
      
      html.setAttribute('data-theme', newTheme);
      localStorage.setItem('theme', newTheme);
    }
    function initTheme() {
      const savedTheme = localStorage.getItem('theme');
      const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
      const theme = savedTheme || (prefersDark ? 'dark' : 'light');
      
      document.documentElement.setAttribute('data-theme', theme);
    }
    document.addEventListener('DOMContentLoaded', initTheme);
  </script>
</body>
</html>`;
18 个赞

太强了,正好有需要

1 个赞

视频看起来背景颜色不一致 有色差,是录屏软件和编码的问题,实际的颜色是一样的

1 个赞

佬,能不能隔离会话,想分享给朋友使用

1 个赞

强大啊!

1 个赞

可以的,GUEST_PASSWORD填写的是访客密码,默认就是访客登录,不同用户名看不到其他记录


站点密码登录可以看到所有记录

1 个赞

大佬太强了

1 个赞

大佬太强了 :tieba_087:

1 个赞

我部署这个代码运行后显示 "登录过程中发生错误"是什么问题呀
单独使用 key 登录没问题

2 个赞

学习一下

1 个赞

大佬,能否搞一个一键换号按钮,访客登录进去了,但是claude3.5次数满了,想要换号。也方便我自己轮询


这里可以直接退出的

1 个赞

忘了加图标了,马上补上
PixPin_2025-01-01_14-42-22

1 个赞

此话题已在最后回复的 30 天后被自动关闭。不再允许新回复。