一、基本配置修改
1、DNS添加A记录demo、oai、token指向自己服务器IP。
2、创建demo镜像站(打码部分为demo.xxxx.com/*,xxxx.com为自己的域名)
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为自己的域名)
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,点击查看添加条目:
注:PASSWORD代表网页授权登陆密码,at代表access_token初始值设置为0,rt_index为数组索引初始值为0,rts分别为三级账号在始皇站获取的refresh_token数组,按格式[“refresh_token1”,“refresh_token2”,“refresh_token3”]。
2、通过网站域名创建workers路由
三、实际操作
1、打开https://token.xxxx.com,输入KV中设置的PASSWORD密码并回车
2、页面跳转如下:
3、不用复制了,直接按下图点击进入
注:前端界面操作的时候at并非一成不变,而是获取的时候如果超过4小时会自动刷新at,防止前边的at次数超限,可以在代码中结合修改。
开始愉快的ChatGPT聊天吧!