【优雅系列】Claude私人拼车神器——多账号+聊天隔离+站密码

前言

距离一时火热的优雅系列拼车工具发布已经远超半年了,上个月 oaifree 被噶,之前的 chatgpt 优雅系列变成历史。

最近用了一段时间 oaipro,感觉和小伙伴拼车还是之前的方案优雅。

于是,想要重振优雅系列的荣光~

Credits

感谢这些基于 cf worker 的相关工作,有所参考:

先看效果

Worker

KV

  • kv_shared_fuclaude
    • SITE_PASSWORD
    • account_nickname_to_session_keys,格式是 json, key 为账户昵称,value 为 session key。
    {
      "账户昵称1(随便起什么)": "sk-ant-sid01--xxxxxxxxxx...",
      "账户昵称2": "sk-ant-sid01--xxxxxxxxxx..."
    }
    

Binding

代码

var KV = kv_shared_fuclaude;
const HOST = 'demo.fuclaude.com';
const BASE_URL = `https://${HOST}`;
const AUTH_ENDPOINT = '/manage-api/auth/oauth_token';

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function generateLoginHtml(accounts) {
  return `<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI服务</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        background-color: #f4f4f4;
        padding: 20px;
      }
      form, .response-container {
        background: white;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        max-width: 600px;
        margin: 20px auto;
      }
      h1 {
        text-align: center; 
      }
      h2 {
        text-align: center; 
      }
      p {
        display: block;
        margin-bottom: 10px;
        font-size: 16px;
      }
      input[type="text"], textarea {
        width: calc(100% - 22px);
        padding: 10px;
        margin-top: 5px;
        margin-bottom: 20px;
        border-radius: 5px;
        border: 1px solid #ccc;
      }
      textarea {
        font-family: 'Courier New', monospace;
        background-color: #f8f8f8;
        height: 150px; /* Adjust height based on your needs */
      }
      button {
        background-color: #000000;
        color: white;
        padding: 10px 20px;
        border: none;
        border-radius: 5px;
        cursor: pointer;
        font-size: 16px;
        font-weight:600;
        width:100% !important
      }
      button:hover {
        background-color: #1e293b;
      }
      @media (max-width: 768px) {
        body, form, .response-container {
          padding: 10px;
        }
      }
      .checkbox-group {
        display: flex;
        justify-content: space-between;
      }
      .checkbox-group input[type="checkbox"] {
        margin-right: 5px;
      }
      .checkbox-group label {
        margin-right: 10px;
      }
      select {
        width: calc(100% - 22px);
        padding: 10px;
        margin-top: 5px;
        margin-bottom: 20px;
        border-radius: 5px;
        border: 1px solid #ccc;
        font-size: 16px;
        background-color: white;
      }
      select:focus {
        outline: none;
        border-color: #000;
      }
    </style>
</head>

<body>
<h1>标题</h1>
<form method="POST">
  <label for="account-select">请选择一个号:</label>
  <select id="account-select" name="account">
    <option value="" disabled selected>请选择一个账号</option>
    ${Object.keys(accounts).map((nickname) => `
      <option value="${nickname}">${nickname}</option>
    `).join('')}
  </select>
  <br/>

  <label for="site_password">请输入本网站使用密码:</label>
  <input type="text" id="site_password" name="site_password" placeholder="The website password">

  <label for="unique_name">请输入独一无二的名字,以区分你的身份,用于会话隔离:</label>
  <input type="text" id="unique_name" name="unique_name" placeholder="Your unique name" required value="">
  <br/>

  <button type="submit">访问使用</button>
</form>

</html>`;
}

async function handleRequest(request) {
  const requestURL = new URL(request.url);

  // ------------ 反代到 fuclaude
  const path = requestURL.pathname;
  if (path !== '/login') {
    requestURL.host = HOST;
    return fetch(new Request(requestURL, request))
  }

  const accountsJsonStr = await KV.get('account_nickname_to_session_keys');
  const accounts = JSON.parse(accountsJsonStr);
  // 返回账号列表
  if (request.method === "GET") {
    const html = await generateLoginHtml(accounts);
    return new Response(html, {
      headers: {
        'Content-Type': 'text/html; charset=utf-8'
      },
    });
  }

  // 选中账号进行登录
  if (request.method === "POST") {
    const formData = await request.formData();
    const account = formData.get("account");
    const sitePassword = formData.get('site_password') || '';
    const uniqueName = formData.get('unique_name');

    // @ts-ignore
    const SITE_PASSWORD = await KV.get('SITE_PASSWORD') || '';
    if (sitePassword !== SITE_PASSWORD) {
      return new Response('站密码错误', { status: 401 }); //如果你不需要密码访问,注释此行代码
    }

    const sessionKey = accounts[account];
    const apiResp = await fetch(`${BASE_URL}${AUTH_ENDPOINT}`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            session_key: sessionKey,
            unique_name: uniqueName
        }),
    });
    const respData = await apiResp.json();
    if (respData.login_url) {
      return Response.redirect(`https://${requestURL.host}${respData.login_url}`, 301)
    }
    return new Response("未知错误~", {
      headers: {
        'Content-Type': 'text/html; charset=utf-8'
      },
    });
  }

}

其他

  • 喜欢的点个赞再走 :kissing_heart:
  • 如果有更好的想法改动,之后可以引这篇帖子,其他佬友能在该帖的相关帖子上看到
54 个赞

很强的,回头我直接试试

2 个赞

只能填一个KEY吗

支持多个账号

这个好啊,大佬牛的,刚好缺这玩意

1 个赞

优雅,太优雅了!

2 个赞

和我的docker版差不多呀 :tieba_003:

1 个赞

先mark,感谢大佬分享

1 个赞

用上了,谢谢佬

1 个赞

不错,如果能显示账号是否用完每日额度就更好了

1 个赞

这对我的需求不是很强烈,所以我只简单做了上述功能。

欢迎后续佬友做了发新帖,然后回来说一下 :kissing_heart:

整个多账户,合租的人一人一个账号,这样可以更舒服。

1 个赞

哈哈哈,也不错呢

我没有这个需求,大佬有的话可以自己动手 把我这当抛砖引玉了,:laughing:

发新帖了我帮忙顶 :pinched_fingers:

1 个赞

谢谢大佬 :tieba_003:

1 个赞

佬友太强啦 :bili_019:

1 个赞

这个雀食不错呀,先收藏了。

1 个赞

只剩下稳定的车了~

不懂就问,因为自己在甲骨文机器部署并反代了,如何用那个网址呀?
直接改const HOST = ‘demo.fuclaude.com
这里就可以了吗

对,有域名的话直接改。

用 ip 地址的话,还得改下面的 https → http

先赞后看,感谢分享