【我也来优雅优雅】快速搭建 GPT 多账号个人分享站点

【我也来优雅优雅】快速搭建 GPT-4 个人分享站点 继续


功能特点

  • :white_check_mark: 支持多账号
  • :lock: 支持白名单
  • :shield: 支持 Turnstile 验证

KV 存储的变量

变量名 说明
YOUR_DOMAIN 反代域名,可直接使用 *.new.oaifree.com (*为任意值)
SITE_PASSWORD 站点口令
ALLOWED_USERS 用户白名单,如 user1,user2
TURNSTILE_SITE_KEY Turnstile 密钥
********@***** 格式:
key: [email protected]
value: {"refresh_token":"aaa","access_token":"bbb"}
worker.js
addEventListener("fetch", event => {
    event.respondWith(handleRequest(event.request));
});

// 主请求处理函数
async function handleRequest(request) {
    if (request.method === "POST") {
        const url = new URL(request.url);
        if (url.pathname === "/accounts") {
            return handleAccountSelection(request);
        } else {
            return handleFormSubmission(request);
        }
    } else {
        return displayLoginPage();
    }
}

// 处理用户提交的表单
async function handleFormSubmission(request) {
    const formData = await request.formData();
    try {
        const unique_name = await validateUser(formData);
        return displayAccountPage(unique_name);
    } catch (error) {
        return displayErrorPage(error.message);
    }
}

// 处理账户选择
async function handleAccountSelection(request) {
    const formData = await request.formData();
    const unique_name = formData.get("unique_name");
    const account_key = formData.get("account_key");

    try {
        const accountData = JSON.parse(await KV.get(account_key));
        const access_token = accountData.access_token || await getValidToken(account_key);
        const shareToken = await generateShareToken(unique_name, access_token);
        const YOUR_DOMAIN = (await KV.get("YOUR_DOMAIN")) || new URL(request.url).host;
        const oauthLink = await getOAuthLink(shareToken, YOUR_DOMAIN);
        return Response.redirect(oauthLink, 302);
    } catch (error) {
        return displayErrorPage(error.message);
    }
}

// 验证用户身份
async function validateUser(formData) {
    const SITE_PASSWORD = (await KV.get("SITE_PASSWORD")) || "";
    const ALLOWED_USERS = (await KV.get("ALLOWED_USERS")) || "";
    const site_password = formData.get("site_password") || "";
    const unique_name = formData.get("unique_name") || "";

    if (site_password !== SITE_PASSWORD) {
        throw new Error("访问密码错误");
    }

    const allowedUsersArray = ALLOWED_USERS.split(",");
    if (!allowedUsersArray.includes(unique_name)) {
        throw new Error("用户不在白名单中");
    }

    return unique_name;
}

// 获取所有账户的键
async function getAccounts() {
    const allKeys = await KV.list(); // 返回所有键的数组
    return allKeys.keys.filter(key => key.name.includes("@")).map(key => key.name);
}

// 获取有效令牌
async function getValidToken(account_key) {
    let accountData = JSON.parse(await KV.get(account_key));
    let token = accountData.access_token;
    if (!token || isTokenExpired(token)) {
        token = await refreshToken(account_key);
    }
    return token;
}

// 刷新 Token
async function refreshToken(account_key) {
    const url = "https://token.oaifree.com/api/auth/refresh";
    let accountData = JSON.parse(await KV.get(account_key));
    const refreshToken = accountData.refresh_token;
    const response = await fetch(url, {
        method: "POST",
        headers: {
            "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
        },
        body: `refresh_token=${refreshToken}`,
    });
    if (!response.ok) throw new Error("Error fetching access token");
    const data = await response.json();
    accountData.access_token = data.access_token;
    await KV.put(account_key, JSON.stringify(accountData));
    return data.access_token;
}

// 生成共享令牌
async function generateShareToken(unique_name, access_token) {
    const url = "https://chat.oaifree.com/token/register";
    const body = new URLSearchParams({
        unique_name,
        access_token,
        site_limit: "",
        expires_in: "0",
        gpt35_limit: "-1",
        gpt4_limit: "-1",
        show_conversations: "true",
        temporary_chat: "false",
        reset_limit: "false",
    });

    const response = await fetch(url, {
        method: "POST",
        headers: {
            "Content-Type": "application/x-www-form-urlencoded",
        },
        body: body.toString(),
    });

    const data = await response.json();
    return data.token_key || "未找到 Share_token";
}

// 获取 OAuth 链接
async function getOAuthLink(shareToken, proxiedDomain) {
    const url = `https://${proxiedDomain}/api/auth/oauth_token`;
    const response = await fetch(url, {
        method: "POST",
        headers: {
            Origin: `https://${proxiedDomain}`,
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ share_token: shareToken }),
    });
    const data = await response.json();
    return data.login_url;
}

// 解析 JWT Token
function isTokenExpired(token) {
    const payload = parseJwt(token);
    return payload.exp < Math.floor(Date.now() / 1000);
}

function parseJwt(token) {
    const base64Url = token.split(".")[1];
    const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
    const jsonPayload = decodeURIComponent(
        atob(base64)
            .split("")
            .map(c => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2))
            .join("")
    );
    return JSON.parse(jsonPayload);
}

// 显示登录页面
async function displayLoginPage() {
    const TURNSTILE_SITE_KEY = await KV.get("TURNSTILE_SITE_KEY");
    const formHtml = `
        <!DOCTYPE html>
        <html lang="zh-CN">
        <head>
            <meta charset="UTF-8">
            <title>欢迎使用oaifree</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
            <link rel="icon" type="image/png" href="https://img.pub/p/8efdf03e4c4a5f057ac6.jpg">
            <style>
                body { background-image: url('https://image.daosuijun.top/file/0070dc8827c70b14ba67e.png'); background-size: cover; background-position: center; background-attachment: fixed; }
                input::placeholder { color: transparent; }
                input:not(:placeholder-shown) + label, input:focus + label { top: -20px; padding: 0 5px; left: 12px; z-index: 10; background: rgba(255, 255, 255, 0); }
                .container { background: rgba(255, 255, 255, 0.85); border-radius: 15px; padding: 30px; max-width: 400px; margin: auto; text-align: center; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); }
                .logo { margin: 0 auto 20px; width: 120px; height: auto; }
                .title { font-size: 24px; font-weight: bold; color: #333; margin-bottom: 15px; }
                .form { margin-top: 20px; }
                .input-field { width: 100%; height: 45px; padding: 10px; border-radius: 5px; border: 1px solid #ccc; margin-bottom: 20px; }
                .button { background-color: #48bb78; color: white; padding: 12px 20px; border-radius: 5px; text-decoration: none; display: inline-block; transition: background-color 0.3s; width: 100%; }
                .button:hover { background-color: #38a169; }
            </style>
        </head>
        <body class="flex justify-center items-center min-h-screen m-0 bg-black bg-opacity-60">
            <div class="container">
                <img src="https://img.pub/p/dd92c1e0a8b081befd3d.jpg" alt="LINUX DO" class="logo">
                <h1 class="title">欢迎使用</h1>
                <form method="POST" class="form">
                    <div class="relative mb-4">
                        <input type="text" id="unique_name" name="unique_name" placeholder=" " required class="input-field">
                        <label for="unique_name" class="absolute left-5 top-1 text-green-500 text-sm transition-all duration-300">用户名</label>
                    </div>
                    <div class="relative mb-4">
                        <input type="password" id="site_password" name="site_password" placeholder=" " class="input-field">
                        <label for="site_password" class="absolute left-5 top-1 text-green-500 text-sm transition-all duration-300">口令</label>
                    </div>
                    <div class="cf-turnstile my-4 flex justify-center" data-sitekey="${TURNSTILE_SITE_KEY}" data-callback="onTurnstileCallback"></div>
                    <input type="hidden" id="cf-turnstile-response" name="cf-turnstile-response" required>
                    <button type="submit" class="w-full h-12 bg-green-500 hover:bg-green-600 text-white font-bold rounded-lg transition-colors duration-300">点击登录</button>
                </form>
            </div>
        </body>
        <script>
            function onTurnstileCallback(token) {
                document.getElementById('cf-turnstile-response').value = token;
            }

            document.querySelector('form').addEventListener('submit', function(event) {
                if (!document.getElementById('cf-turnstile-response').value) {
                    alert('请完成验证。');
                    event.preventDefault();
                }
            });
        </script>
        <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
        </html>
    `;
    return new Response(formHtml, {
        headers: {
            "Content-Type": "text/html; charset=utf-8",
        },
    });
}

// 显示账户选择页面
async function displayAccountPage(unique_name) {
    const keys = await getAccounts();
    const accountOptions = keys.map(key => `<option value="${key}">${key}</option>`).join("");
    const formHtml = `
        <!DOCTYPE html>
        <html lang="zh-CN">
        <head>
            <meta charset="UTF-8">
            <title>选择账户</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
            <link rel="icon" type="image/png" href="https://img.pub/p/8efdf03e4c4a5f057ac6.jpg">
            <style>
                body { background-image: url('https://image.daosuijun.top/file/0070dc8827c70b14ba67e.png'); background-size: cover; background-position: center; background-attachment: fixed; }
                .container { background: rgba(255, 255, 255, 0.85); border-radius: 15px; padding: 30px; max-width: 400px; margin: auto; text-align: center; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); }
                .logo { margin: 0 auto 20px; width: 120px; height: auto; }
                .title { font-size: 24px; font-weight: bold; color: #333; margin-bottom: 15px; }
                .form { margin-top: 20px; }
                .select { width: 100%; height: 45px; padding: 10px; border-radius: 5px; border: 1px solid #ccc; margin-bottom: 20px; }
                .button { background-color: #48bb78; color: white; padding: 12px 20px; border-radius: 5px; text-decoration: none; display: inline-block; transition: background-color 0.3s; width: 100%; }
                .button:hover { background-color: #38a169; }
            </style>
        </head>
        <body class="flex justify-center items-center min-h-screen m-0 bg-black bg-opacity-60">
            <div class="container">
                <img src="https://img.pub/p/dd92c1e0a8b081befd3d.jpg" alt="LINUX DO" class="logo">
                <h1 class="title">选择账户</h1>
                <form method="POST" action="/accounts" class="form">
                    <input type="hidden" name="unique_name" value="${unique_name}">
                    <select name="account_key" class="select">
                        ${accountOptions}
                    </select>
                    <button type="submit" class="w-full h-12 bg-green-500 hover:bg-green-600 text-white font-bold rounded-lg transition-colors duration-300">开始使用</button>
                </form>
            </div>
        </body>
        </html>
    `;
    return new Response(formHtml, {
        headers: {
            "Content-Type": "text/html; charset=utf-8",
        },
    });
}

// 显示错误页面
function displayErrorPage(errorMessage) {
    const htmlContent = `
        <!DOCTYPE html>
        <html lang="zh-CN">
        <head>
            <meta charset="UTF-8">
            <title>登录错误</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
            <link rel="icon" type="image/png" href="https://img.pub/p/8efdf03e4c4a5f057ac6.jpg">
            <style>
                body { background-image: url('https://image.daosuijun.top/file/0070dc8827c70b14ba67e.png'); background-size: cover; background-position: center; background-attachment: fixed; }
                .container { background: rgba(255, 255, 255, 0.85); border-radius: 15px; padding: 30px; max-width: 400px; margin: auto; text-align: center; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); }
                .logo { margin: 0 auto 20px; width: 120px; height: auto; }
                .title { font-size: 24px; font-weight: bold; color: #333; margin-bottom: 15px; }
                .message { font-size: 18px; color: #e53e3e; margin-bottom: 25px; }
                .button { background-color: #48bb78; color: white; padding: 12px 20px; border-radius: 5px; text-decoration: none; display: inline-block; transition: background-color 0.3s; }
                .button:hover { background-color: #38a169; }
            </style>
        </head>
        <body class="flex justify-center items-center min-h-screen m-0 bg-black bg-opacity-60">
            <div class="container">
                <img src="https://img.pub/p/dd92c1e0a8b081befd3d.jpg" alt="LINUX DO" class="logo">
                <h1 class="title">登录错误</h1>
                <p class="message">${errorMessage}</p>
            </div>
        </body>
        </html>
    `;
    return new Response(htmlContent, {
        status: 401,
        headers: { "Content-Type": "text/html; charset=utf-8" },
    });
}
101 个赞

感谢分享!

1 个赞

这LOGO它就不对啊 :smile:

4 个赞

忘记改了 换!

1 个赞

哈哈哈哈 老板好

1 个赞

牛蛙牛蛙

@neo 已更换新版 logo :rofl: 你别说还挺好看

高端大气上档次 :tieba_091:

4 个赞

厉害,下次试一试

优雅永不过时

6 个赞

666

1 个赞

太优雅了

4 个赞

好优雅!

优雅简洁

1 个赞

能不能加个隐藏gpt账号邮箱的功能?想分享给别人用

你用 @测试账号一 这种名字替换邮箱就好了

非常感谢!

优雅,永不过时

mark

佬强