(0521更新-去掉小锁)chatGpt plus拼车神器UI版本 plus ----优雅再接力

本文基于

在@ tttt 的UI版本基础上做了一些自认为是优化的的优化
感谢两位大佬以及大佬文中提到的所有大佬的前期工作。让我得以方便的使用

我在@ tttt 的UI版本的基础上做了以下工作:

1.增加用户名用户白名单 ,参考@ Maple 本文的第二篇引用中的白名单内容

2.增加用户自定义密码的变量,用户名为变量key,用户名的密码为 变量value,使一起拼车的用户方便记住自己的密码(白名单用户第一次登录判定为密码正确,并存到变量表中后续验证取变量表中的密码进行验证)无需管理员手动加变量。

3.原UI版使用 https://yourdomain/auth/login_share?token=fk-xxx 的方式会暴露share_token,有一定风险。我对其进行了转OAuth的方式登陆,提高安全性,参考@ Maple 本文的第二篇引用中的转OAuth的方式登陆相关内容

4.对错误界面做了一定优化。

5.登录界面做了一些优化

6.增加display_lock_flag变量用于控制是否显示小锁:lock:标志,不填为不显示,填写true则显示小锁,不显示锁时加载稍微会更慢一点,会并行把其他页的响应拿到合并并过滤

workers代码:

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

function parseJwt(token) {
    const base64Url = token.split('.')[1]; // 获取载荷部分
    const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); // 将 Base64Url 转为 Base64
    const jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));

    return JSON.parse(jsonPayload); // 返回载荷解析后的 JSON 对象
}

function isTokenExpired(token) {
    const payload = parseJwt(token);
    const currentTime = Math.floor(Date.now() / 1000); // 获取当前时间戳(秒)
    return payload.exp < currentTime; // 检查 token 是否过期
}


async function handleRequest(request) {
    const requestURL = new URL(request.url);
    // ------------ 反代到 new.oaifree.com
    const path = requestURL.pathname;
    //拦截请求
    if (path !== '/auth/login_auth0' && path !== '/auth/login') {
        requestURL.host = 'new.oaifree.com';
        if (path !== '/backend-api/conversations') {
            return await fetch(new Request(requestURL, request));
        }
        const displayLock = await oai_global_variables.get('display_lock_flag');
        if(displayLock === 'true'){
            return await fetch(new Request(requestURL, request));
        }
        return  await handleLockFlag(requestURL, request);
    }

    // ------------ 进行拿share_token去登录
    const token = await oai_global_variables.get('at'); // 这里填入你的 JWT
    if (isTokenExpired(token)) {
        // 如果 Token 过期,执行获取新 Token 的逻辑
        const url = 'https://token.oaifree.com/api/auth/refresh';
        // @ts-ignore
        const refreshToken = await oai_global_variables.get('rt');  // 实际情况下你可能会从某处获取这个值
        // 发送 POST 请求
        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) {
            const data = await response.json();
            const token = data.access_token;
            // @ts-ignore
            await oai_global_variables.put('at', token);
        } else {
            return getErrorResponse(response.status)
        }
    }
    // 如果 Token 未过期,继续执行原来的逻辑
    if (request.method === "POST") {
        const formData = await request.formData();
        // @ts-ignore
        const access_token = await oai_global_variables.get('at')
        const unique_name = formData.get('unique_name');
        const site_limit = '';
        const expires_in = '0';
        const gpt35_limit = '-1';
        const gpt4_limit = '-1';
        const show_conversations = 'false';
        const show_userinfo = 'false';// 是否显示用户信息
        const reset_limit = 'false';
        // @ts-ignore
        // 验证用户名是否存在
        const users = await oai_global_variables.get("users");

        if (!users.split(",").includes(unique_name)) {
            return getErrorResponse(401);
        }
        const site_password = formData.get('site_password') || '';
        const userPassword = await oai_global_variables.get(unique_name);
        const userPasswordIsNull = userPassword === undefined || userPassword === null || userPassword === ''
        if (userPasswordIsNull) {
            // 首次登录,认定密码正确,并将密码存储
            await oai_global_variables.put(unique_name, site_password);
        }

        // 变量中有密码且不对则拒绝访问
        if (!userPasswordIsNull && site_password !== userPassword) {
            return getErrorResponse(401);
        }
        const url = 'https://chat.oaifree.com/token/register';
        const body = new URLSearchParams({
            unique_name,
            access_token, // 使用来自表单v或KV变量的 access_token
            site_limit,
            expires_in,
            gpt35_limit,
            gpt4_limit,
            show_conversations,
            show_userinfo,
            reset_limit
        }).toString();

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

        const respJson = await apiResponse.json();
        const tokenKey = 'token_key' in respJson ? respJson.token_key : '未找到 Share_token';

        // @ts-ignore
        const DOMAIN = await oai_global_variables.get('DOMAIN') || requestURL.host;
        // 重定向到登录 URL
        return Response.redirect(await getOAuthLink(tokenKey, DOMAIN), 301);
    }
    return getLoginPage();
}

// 获取错误页面的函数
function getErrorResponse(statusCode) {
    const errorMessageMap = {
        401: '访问被拒绝,请检查您的凭据。',
        404: '请求的资源未找到。',
        500: '服务器内部错误,请稍后再试。'
    };

    const message = errorMessageMap[statusCode] || '发生未知错误。';
    const html = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>错误</title>
<link rel="icon" type="image/x-icon" href="https://cdn2.oaifree.com/_next/static/media/favicon-32x32.630a2b99.png">
<style>
body {
  font-family: Arial, sans-serif;
  background-color: #f4f4f4;
  padding: 20px;
  text-align: center;
}
.error-container {
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
  max-width: 600px;
  margin: 50px auto;
}
button {
  background-color: #0f9977;
  color: #ffffff;
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 16px;
  font-weight: 600;
  width: 295px !important;
  height: 52px;
}
h1 {
  color: #333;
}
p {
  color: #666;
}
</style>
</head>
<body>
<div class="error-container">
<h1>ERROR</h1>
<p>${message}</p>
<button onclick="history.back()">Back</button>
</div>
</body>
</html>
`;
    return new Response(html, {
        headers: {
            'Content-Type': 'text/html; charset=utf-8'
        },
        status: statusCode
    });
}

//获取登录页面的函数
function getLoginPage() {
    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>登陆</title>
<link rel="icon" type="image/x-icon" href="https://cdn2.oaifree.com/_next/static/media/favicon-32x32.630a2b99.png">
<style>
  body {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 90vh;
      overflow: hidden;
      font-family: Arial, sans-serif;
      background-color: #f4f4f4;
  }
  h1 {
      text-align: center;
  }
  input {
      appearance: none;
      -webkit-appearance: none;
      -moz-appearance: none;
      outline: none;
  }
  .input-wrapper {
      position: relative;
      margin-bottom: 20px;
  }
  .input-wrapper label {
      position: absolute;
      left: 10px;
      top: 14px;
      transition: all 0.3s ease;
      color: #ccc;
  }
  .input-wrapper input {
      width: 274px;
      height: 52px;
      padding: 0 10px;
      border-radius: 5px;
      border: 1px solid #ccc;
      display: block;
      font-size: 16px;
  }
  .input-wrapper input:not(:placeholder-shown) + label,
  .input-wrapper input:focus + label {
      top: -12px; /* Adjusted to not cover the line */
      left: 20px; /* Moved right a bit */
      font-size: 12px;
      color: #0f9977;
  }
  .password-toggle {
      position: absolute;
      right: 10px;
      top: 15px;
      cursor: pointer;
  }
  button {
      background-color: #0f9977;
      color: #ffffff;
      padding: 10px 20px;
      border: none;
      border-radius: 5px;
      cursor: pointer;
      font-size: 16px;
      font-weight: 600;
      width: 295px !important;
      height: 52px;
  }
  @media (max-width: 768px) {
      body, form, .response-container {
          padding: 20px;
      }
  }
</style>
</head>
<body>
<div>
  <h1>Welcome Back</h1>
  <form method="POST">
      <div class="input-wrapper">
          <input type="text" id="unique_name" name="unique_name" placeholder=" " required value="">
          <label for="unique_name">Username</label>
      </div>
      <div class="input-wrapper">
          <input type="password" id="site_password" name="site_password" placeholder=" ">
          <label for="site_password">Password</label>
          <span class="password-toggle" onclick="togglePassword()">
              <svg width="24" height="24" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <path d="M12 5C7 5 2.73 8.11 1 12.5C2.73 16.89 7 20 12 20C17 20 21.27 16.89 23 12.5C21.27 8.11 17 5 12 5ZM12 17C9.24 17 7 14.76 7 12C7 9.24 9.24 7 12 7C14.76 7 17 9.24 17 12C17 14.76 14.76 17 12 17ZM12 9C10.34 9 9 10.34 9 12C9 13.66 10.34 15 12 15C13.66 15 15 13.66 15 12C15 10.34 13.66 9 12 9Z" fill="#CCC"/>
              </svg>
          </span>
      </div>
      <button type="submit">Continue</button>
  </form>
</div>
<script>
  function togglePassword() {
      var passwordInput = document.getElementById('site_password');
      var passwordIcon = document.querySelector('.password-toggle svg path');
      if (passwordInput.type === 'password') {
          passwordInput.type = 'text';
          passwordIcon.setAttribute('fill', '#0f9977'); // 更改图标颜色以反映状态更改
      } else {
          passwordInput.type = 'password';
          passwordIcon.setAttribute('fill', '#CCC'); // 更改图标颜色以恢复原状
      }
  }
</script>
</body>
</html>
`;

    return new Response(formHtml, {
        headers: {
            "Content-Type": "text/html; charset=utf-8",
        },
    });
}

// 获取 OAuth 登录链接的函数
async function getOAuthLink(tokenKey, DOMAIN) {
    const url = `https://new.oaifree.com/api/auth/oauth_token`;
    const response = await fetch(url, {
        method: 'POST',
        headers: {
            'Origin': `https://${DOMAIN}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            share_token: tokenKey
        })
    })
    const data = await response.json();
    return data.login_url;
}

// 处理小锁去除逻辑
async function handleLockFlag(requestURL, request) {
    const response = await fetch(new Request(requestURL, request));
    let data = await response.json();
    //获取总数
    const total = data.total;

    //构建所有页的请求
    const requests = [];
    if(total<=28){
    // 总数小于一页,过滤锁定的数据直接返回
    data.items = data.items.filter(item => item.title !== "🔒");
    return new Response(JSON.stringify(data), {
        status: response.status,
        headers: response.headers
    });
    }
    //计算多少页
    const page = Math.ceil(total / 28);
    for (let i = 1; i < page; i++) {
        const tempRequestURL = new URL(requestURL);
        tempRequestURL.searchParams.set('offset', String(i * 28));
        tempRequestURL.searchParams.set('limit', String(28));
        tempRequestURL.searchParams.set('order', 'updated');
        requests.push(fetch(new Request(tempRequestURL, request)));
    }
    //并行执行所有请求
    const responses = await Promise.all(requests);
    //处理所有响应
    for (const res of responses) {
        const tempData = await res.json();
        data.items = data.items.concat(tempData.items);
    }
    // 过滤锁定的数据
    data.items = data.items.filter(item => item.title !== "🔒");
    data.total = data.items.length;
    data.offset = 0;
    data.limit = data.items.length;
    return new Response(JSON.stringify(data), {
        status: response.status,
        headers: response.headers
    });
}
44 个赞

玩的花啊 :smiley:

4 个赞

看上去很厉害的样子

1 个赞

接力好啊

1 个赞

优雅永不过时

1 个赞

都说优雅 然而 屎黄说 花

1 个赞

一个 worker 都给搞定了 6

1 个赞

优雅…太优雅了

1 个赞

报错
{“error”:“Error”,“message”:“Token and remote not found”}
而且,看代码好像密码是看变量unique_name。而不是作者提到的key和value?

1 个赞

kv里添加用户名,密码,对应key和value

2 个赞

看到了,多谢。看岔了。unique_name是变量名不是字符串。明天再试试

1 个赞

大佬,用户名和密码咋设置啊

不用自己设置了,看我最新worker 代码 以及第二条内容描述

不用自己设置用户名密码kv了,设置白名单就行。看我新的代码以及第二点的内容描述

1 个赞

感谢收藏了

哈哈哈

哈哈哈哈,各位大佬弄的东西挺好玩

挺花~

这个是我的kv设置,我用users中的一个登录提示:访问被拒绝,请检查您的凭据。

你的YOUR_DOMAIN改了吗,我代码里面用的DOMAIN