手把手教小白创建直达潘多拉ChatGPT镜像站(Share token使用超级优雅了,多个RT账号组建RTS账号池轮循生成AT、页面设置授权密码)

一、基本配置修改
1、DNS添加A记录demo、oai、token指向自己服务器IP。

2、创建demo镜像站(打码部分为demo.xxxx.com/*,xxxx.com为自己的域名)


image

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    url.host = 'demo.oaifree.com';
    return fetch(new Request(url, request))
  }
}

3、创建oai镜像站(打码部分为oai.xxxx.com/*,xxxx.com为自己的域名)


image

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    url.host = 'new.oaifree.com';
    return fetch(new Request(url, request))
  }
}

4、创建share token站(打码部分为token.xxxx.com/xxxx.com为自己的域名)



以下代码中的
xxxx.com*要换成自己的域名

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) {
  if (token === '0' || token === '') return true;  // 如果token是'0'或空,视为过期
  const payload = parseJwt(token);
  const currentTime = Math.floor(Date.now() / 1000); // 获取当前时间戳(秒)
  return payload.exp < currentTime; // 检查 token 是否过期
}

async function handleRequest(request) {
  const rts = JSON.parse(await oai_global_variables.get('rts') || '[]'); // 如果未定义,则默认为一个空数组
  const url = new URL(request.url);
  const params = new URLSearchParams(url.search);
  const password = params.get('password');
  // @ts-ignore
  const PASSWORD = await oai_global_variables.get('PASSWORD'); // 从变量存储中获取密码
  let rtIndex = parseInt(await oai_global_variables.get('rt_index') || '0');
  // 更新 rt_index 的代码放置位置
  const currentTime = Math.floor(Date.now() / 1000);
  let lastUpdated = parseInt(await oai_global_variables.get('last_updated') || '0');
  const HoursInSeconds = 4 * 3600;

  if (currentTime - lastUpdated > HoursInSeconds) {
    rtIndex = (rtIndex + 1) % rts.length;
    await oai_global_variables.put('rt_index', rtIndex.toString());
    lastUpdated = currentTime;
    await oai_global_variables.put('last_updated', lastUpdated.toString());
  }
  
  if (password !== PASSWORD) {
    // 返回密码输入表单而不是简单的401状态,以便用户有机会重新输入密码
    return new Response(`
    <!DOCTYPE html>
    <html>
    <head>
        <title>请输入授权密码</title>
        <style>
            body {
                font-family: Arial, sans-serif;
                background-color: #f4f4f4;
                padding: 20px;
                text-align: center; // 确保内容居中显示
            }
            form, .response-container {
                background: black;
                padding: 20px;
                border-radius: 8px;
                box-shadow: 0 2px 5px rgba(0,0,0,0.1);
                max-width: 600px;
                margin: 20px auto;
            }
            h1 {
                display: block; // 设置为块级元素,自动填充容器宽度
                margin-bottom: 10px;
                font-size: 28px;
                color: black; // 设置文字颜色
                background-color: transparent; // 移除背景色,使用透明
                padding: 10px; // 内边距为统一的10px
                border-radius: 5px;
                font-weight: 1200;
                text-align: center; // 文本居中显示
                width: 100%; // 宽度与按钮相同
                font-family: 'Arial Black', Gadget, sans-serif; 
            }
            input[type="password"], button {
                width: 100%; // 输入框和按钮宽度设置为100%
                padding: 15px;
                height: 25px;  // 直接设置高度
                margin-top: 5px;
                margin-bottom: 20px;
                border-radius: 5px;
                border: 1px solid #ccc;
            }
            button {
                background-color: #007BFF;
                color: white;
                border: none;
                border-radius: 5px;
                cursor: pointer;
                font-size: 16px;
                font-weight: 600;
            }
        </style>
    </head>
    <body>
        <h1>请在下框输入授权密码</h1>
        <form action="" method="GET">
            <input type="password" name="password" placeholder="Enter Password" required>
            <button type="submit">确认</button>
        </form>
    </body>
    </html>
    `, { status: 401, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
  }

  // @ts-ignore
  const token = await oai_global_variables.get('at'); // 这里填入你的 JWT
  if (token === '0' || token === '' || isTokenExpired(token)) {
    // 执行获取新 Token 或刷新 Token 的逻辑
    const url = 'https://token.oaifree.com/api/auth/refresh';
    // @ts-ignore
    const refreshToken = rts[rtIndex];  // 实际情况下你可能会从某处获取这个值
    // 发送 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();  // 假设服务器返回的是 JSON 格式数据
	const newAccessToken = data.access_token; // 直接从 JSON 中获取 access_token
	await oai_global_variables.put('at', newAccessToken);
	rtIndex = (rtIndex + 1) % rts.length; // 更新索引,循环使用 refresh tokens
	// @ts-ignore
	await oai_global_variables.put('rt_index', rtIndex.toString());
       lastUpdated = currentTime;
       await oai_global_variables.put('last_updated', lastUpdated.toString());
    } else {
	console.error('Refresh token failed:', refreshToken);
	// 标记当前 token 为失效
	rts.splice(rtIndex, 1); // 从数组中移除失效的 token
	if (rts.length === 0) {
	   return new Response('All refresh tokens are invalid, please re-authenticate.', { status: 401 });
	}
	// 尝试使用下一个 available token
	rtIndex = rtIndex % rts.length; // 重设 rtIndex
	await oai_global_variables.put('rt_index', rtIndex.toString());
	await oai_global_variables.put('last_updated', currentTime.toString());
	await oai_global_variables.put('rts', JSON.stringify(rts)); // 更新存储的 rts
    }
} 


  // 如果 Token 未过期,继续执行原来的逻辑
  if (request.method === "POST") {
    const formData = await request.formData();
    // @ts-ignore
    const at = await oai_global_variables.get('at');
    const access_token = formData.get('access_token') || at; //删除行尾at变量以停止使用内置的access_token(两个竖线也需要删除)
    const unique_name = formData.get('unique_name');
    const site_limit = formData.get('site_limit') || '';
    const expires_in = formData.get('expires_in') || '';
    const gpt35_limit = formData.get('gpt35_limit') || '';
    const gpt4_limit = formData.get('gpt4_limit') || '';
    const show_conversations = formData.get('show_conversations') || 'false';
    const show_userinfo = formData.get('show_userinfo') || 'false';
    const reset_limit = formData.get('reset_limit') || 'false';

    const url = 'https://chat.oaifree.com/token/register';
    const body = new URLSearchParams({
      unique_name,
      access_token, // 使用来自表单或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
    });
  if (!apiResponse.ok) {
    return new Response('Error registering access token', { status: apiResponse.status });
}
    const responseText = await apiResponse.text();
    const tokenKeyMatch = /"token_key":"([^"]+)"/.exec(responseText);
    const tokenKey = tokenKeyMatch ? tokenKeyMatch[1] : '未找到 Share_token';
    // @ts-ignore

    const PASSWORD = await oai_global_variables.get('PASSWORD');

    const resultHtml = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <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;
      }
      label, h1, p {
        display: block;
        margin-bottom: 10px;
        font-size: 16px;
      }
      input[type="text"], input[type="checkbox"], 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: auto; /* 自动调整高度以适应内容 */
        min-height: 150px; /* 最小高度 */
        overflow: auto; /* 内容超出时显示滚动条 */
        width: calc(100% - 22px); /* 计算宽度以适应父容器 */
        padding: 10px;
        margin-top: 5px;
        margin-bottom: 20px;
        border-radius: 5px;
        border: 1px solid #ccc;
      }
      button {
        background-color: #007BFF;
        color: white;
        border: none;
        border-radius: 5px;
        cursor: pointer;
        font-size: 16px;
        font-weight:600;
        padding: 10px 20px;
        margin-bottom: 10px;
        width:auto; /* 删除宽度强制百分之百的设置 */
      }
      button a{
        background-color: #000000;
        color: white;
        padding: 10px 20px;
        border: none;
        border-radius: 5px;
        cursor: pointer;
        font-size: 16px;
        font-weight:600;
        margin-bottom: 10px;
        display: block;  /* 使<a>标签以块级元素展示,填满button */
        text-align: center;  /* 文本居中显示 */
        width: 100%;  /* 充满整个按钮 */
        text-decoration: none;  /* 去除下划线 */
      }
      button:hover {
        background-color: #0056b3;
      }
      button a {
        color: inherit;
        text-decoration: none;
        background: none;
        padding: 0;
        display: inline;
      }
      @media (max-width: 768px) {
        body, form, .response-container {
          padding: 10px;
        textarea {
         width: calc(100% - 20px); /* 在小屏设备上适当调整宽度 */
        }
      }
    </style>
    </head>
    <body>
    <div class="response-container">
      <h1>Share Token</h1>
      <textarea id="tokenKey" readonly>${tokenKey}</textarea>
      <button onclick="copyTokenKey()">一键复制</button>
      <br></br>
      <button onclick="goToOtherPage1()">去聊天(旧页面)</button>
      <br></br>
      <button onclick="goToOtherPage2()">去聊天(新页面)</button>
      <script>
        function copyTokenKey() {
          const copyText = document.getElementById("tokenKey");
          navigator.clipboard.writeText(copyText.value)
            .then(() => alert("已复制: " + copyText.value))  // 成功则弹出已复制提示
            .catch(err => alert('复制失败', err));  // 失败则弹出失败提示
        }
        function goToOtherPage1() {
          const tokenKey = document.getElementById("tokenKey").value;
          window.open('https://demo.xxxx.com/auth/login_share?token=' + encodeURIComponent(tokenKey), '_blank');
        }
        function goToOtherPage2() {
          const tokenKey = document.getElementById("tokenKey").value;
          window.open('https://oai.xxxx.com/auth/login_share?token=' + encodeURIComponent(tokenKey), '_blank');
        }
      </script>
      <a href="/?password=${PASSWORD}">返回</a> <!-- 修正闭合引号 -->
      <h1>响应原文</h1>
      <textarea id="response" readonly>${responseText}</textarea>
    </div>
    </body>
    </html>
    `;
    return new Response(resultHtml, {
      headers: {
        'Content-Type': 'text/html; charset=utf-8'
      },
    });
  } else {
    // 如果不是POST请求,返回一个表单页面
    const formHtml = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <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; 
      }
      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: #007BFF;
        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: #0056b3;
      }
      @media (max-width: 768px) {
        body, form, .response-container {
          padding: 10px;
        textarea {
         width: calc(100% - 20px); /* 在小屏设备上适当调整宽度 */
        }
      }
      .checkbox-group {
        display: flex;
        justify-content: space-between;
      }
      .checkbox-group input[type="checkbox"] {
        margin-right: 15px;
      }
      .checkbox-group label {
        margin-right: 10px;
      }
    </style>
    </head>
    <body>
    <h1>欢迎使用ChatGPT</h1>
    <form method="POST">
      <label for="unique_name">输入独一无二的名字,以区分身份、会话隔离、聊天记录保存:</label>
      <input type="text" id="unique_name" name="unique_name" placeholder="只能由数字和字母构成" required value="">

      <details>
        <summary>更多选项(不懂则无需更改)</summary>
        <br></br>
        <div class="form-container">
        <label for="access_token">Access Token:</label>
            <input type="text" id="access_token" name="access_token" placeholder="你的 access token" value="">

            <label for="site_limit">限制使用网站:</label>
            <input type="text" id="site_limit" name="site_limit" placeholder="可选,空则不限" value="">

            <label for="expires_in">过期秒数:</label>
            <input type="text" id="expires_in" name="expires_in" placeholder="为0取Access Token过期时间,负数吊销分享令牌" value="0">

            <label for="gpt35_limit">GPT-3.5次数:</label>
            <input type="text" id="gpt35_limit" name="gpt35_limit" placeholder="为0无法使用,负数不限制" value="-1">

            <label for="gpt4_limit">GPT-4次数:</label>
            <input type="text" id="gpt4_limit" name="gpt4_limit" placeholder="为0无法使用,负数不限制" value="-1">

            <div class="checkbox-group">
              <div>
                <input type="checkbox" id="show_conversations" name="show_conversations" value="true">
                <label for="show_conversations">会话无需隔离</label>
              </div>

              <div>
                <input type="checkbox" id="show_userinfo" name="show_userinfo" value="true">
                <label for="show_userinfo">展示账号信息</label>
              </div>

              <div>
                <input type="checkbox" id="reset_limit" name="reset_limit" value="true">
                <label for="reset_limit">重置已用次数</label>
              </div>
            </div>
        </div>
      </details>
    <br></br>
      <button type="submit">获取您的访问门票 share token</button>
    </form>
    </body>
    </html>
    `;
    return new Response(formHtml, {
      headers: {
        'Content-Type': 'text/html; charset=utf-8'
      },
    });
  }
}

二、创建workers命令空间KV及workers 路由
1、添加名称为:oai_global_variables,点击查看添加条目:



image

注:PASSWORD代表网页授权登陆密码,at代表access_token初始值设置为0,rt_index为数组索引初始值为0,rts分别为三级账号在始皇站获取的refresh_token数组,按格式[“refresh_token1”,“refresh_token2”,“refresh_token3”]。
2、通过网站域名创建workers路由


image

三、实际操作
1、打开https://token.xxxx.com,输入KV中设置的PASSWORD密码并回车


2、页面跳转如下:
image
3、不用复制了,直接按下图点击进入


注:前端界面操作的时候at并非一成不变,而是获取的时候如果超过4小时会自动刷新at,防止前边的at次数超限,可以在代码中结合修改。

开始愉快的ChatGPT聊天吧! :face_with_raised_eyebrow: :grin:

45 个赞

我能知道这是什么,小白就不一定能看明白了。明白了也不一定知道咋使用 :rofl:

6 个赞

来学习

4 个赞

所以这是啥呀

3 个赞

cf worker

3 个赞

不明觉厉

3 个赞

看着就牛逼

4 个赞

三级账号在始皇站获取的refresh_token 必须三级才能。。。

3 个赞

你这个真的能跑?
我看代码里用了一些你没在KV中定义的要从kv中取到参数。
你这能跑,我倒立洗头好吧,要发贴别误导人 :upside_down_face:

另外这样的话,聊天记录存储就废了。

4 个赞

看我帖子就知道了。

3 个赞

我自己跑成功了已经用了很久才发出来的……

3 个赞

代码里边域名要换成自己的

3 个赞

我是直接根据自己的代码边操作边截图,所有的功能都能运行

3 个赞

别的就不说了,就这两行你解释一下

5 个赞

这个是创建的多个refresh_token的列表,在前边代码里边有定义,就是3个rt组建的列表,跟KV里边的参数无关

3 个赞

你知道这啥意思嘛

4 个赞

此处应该有大善人

3 个赞

我在kv里边定义了3个rt进行循环,rts只是一个备用数组

3 个赞

感谢

1 个赞

再看下,已经修正代码

1 个赞