Table of Contents
接上文 【新手教程】oaifree 喂饭级 Step-by-step 一步步打开潘多拉 ,受到伟大的坛友们的启发,于是我又糊了一个。
究极优雅 之 pandora-4o
:
-
称之为
pandora-4o
: 还原 pandoranext 通过API_PREFIX
代理 Chat2API; -
还原官网登陆界面,输入邮箱,一击直达!
-
登陆可选项:是否开启临时聊天;(当然也可以支持更多选项)
巨人的启发:
开始配置:
以下下为参考环境变量配置:
// prettier-ignore
interface Env {
API_KEY: string; // Chat2API 的 API_KEY, example: sk-any-fucking-secure-key
API_PREFIX: string; // 不与默认路径冲突的任意字符串, example: ababbaba
ACCESS_TOKEN: string; // 多个 AC 用逗号分隔
CHATGPT_ACCOUNT_ID?: string; // 可选,Team 账号设置, example: 9b2c9c9c-9c9c-9c9c-9c9c-9c9c9c9c9c9c
BARK_SERVER?: string; // 可选,bark 用于报错通知服务,主要是没有 Refresh token,祝我早日 3 级再来更新
BARK_DEVICE_KEY?: string;
OAIFREE_KV?: KVNamespace; // 可选,log 打个日志记录成员登陆信息
FORCE_TEMPORARY_CHAT?: string; // 可选,是否强制临时聊天,默认 'false'
FORBIDDEN_PATHS?: string; // 可选,屏蔽一些接口,逗号分隔,比如 '/admin,...'
ALLOW_DELETE?: string; // 可选,是否允许删除操作,默认 'true'
}
安全考虑你可以:
- 配置 Cloudflare zero trust 访问控制;
- 配置自定义用户白名单,修改
isValidSecret
方法;
使用:
ChatGPT
- 直接访问 https://<你的域名>/
- 输入邮箱(目前是验证邮箱格式,当然其他任意字符串格式理论都是可以支持的)
API
Worker 代码
/**
* API:
* - chat2api: https://<your host>/<API_PREFIX>/v1/...
* - backend-api: https://<your host>/<API_PREFIX>/backend-api/...
* - public-api: https://<your host>/<API_PREFIX>/public-api/...
* - token: https://<your host>/<API_PREFIX>/token
* Mirror ChatGPT:
* - Mirror openai login page: https://<your host>/auth/login_auth0
* - new oaifree: https://<your host>/
* */
// prettier-ignore
interface Env {
API_KEY: string; // Chat2API 的 API_KEY, example: sk-any-fucking-secure-key
API_PREFIX: string; // 不与默认路径冲突的任意字符串, example: ababbaba
ACCESS_TOKEN: string; // 多个 AC 用逗号分隔
CHATGPT_ACCOUNT_ID?: string; // 可选,Team 账号设置, example: 9b2c9c9c-9c9c-9c9c-9c9c-9c9c9c9c9c9c
BARK_SERVER?: string; // 可选,bark 用于报错通知服务,主要是 Refresh token,祝我早日 3 级再来更新
BARK_DEVICE_KEY?: string;
OAIFREE_KV?: KVNamespace; // 可选,log 打个日志记录成员登陆信息
FORCE_TEMPORARY_CHAT?: string; // 可选,是否强制临时聊天,默认 'false'
FORBIDDEN_PATHS?: string; // 可选,屏蔽一些接口,逗号分隔,比如 '/admin,...'
ALLOW_DELETE?: string; // 可选,是否允许删除操作,默认 'true'
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
console.log(`Received request for URL: ${request.url}`);
const clonedRequest = request.clone();
if (env.FORBIDDEN_PATHS) {
const forbiddenPaths = env.FORBIDDEN_PATHS.split(',');
if (forbiddenPaths.some(path => url.pathname.includes(path))) {
console.error(`Access to forbidden path: ${url.pathname}`);
return new Response(JSON.stringify({ error: 'Not allowed!' }), { status: 403 });
}
}
if (env.ALLOW_DELETE === 'false' && request.method === 'DELETE') {
return new Response(JSON.stringify({ error: 'Not allowed!' }), { status: 403 });
}
const tokenPaths = ['/token', '/static', '/cdn-cgi'];
if (tokenPaths.some(path => url.pathname.includes(path))) {
url.host = 'chat.oaifree.com';
return fetch(new Request(url, request));
}
if (!url.pathname.startsWith(`/${env.API_PREFIX}`)) {
return handleChatRequest(clonedRequest, url, env);
}
if (!isValidApiKey(clonedRequest, env)) {
console.error('Invalid API Key');
return new Response('Invalid API Key', { status: 401 });
}
const processedRequest = await handleAPIRequestPath(clonedRequest, url, env);
return handleAPIRequest(processedRequest, url, env);
},
};
async function handleChatRequest(request: Request, url: URL, env: Env): Promise<Response> {
if (url.pathname === '/auth/login_auth0' || url.pathname === '/auth/login') {
return await handleLoginToken(request, env);
}
url.host = 'new.oaifree.com';
return fetch(new Request(url, request));
}
async function loggingLogin(env: Env, uniqueName: string) {
const dateString = new Date().toISOString();
console.log(`${uniqueName} logged in at ${dateString}`);
if (env.OAIFREE_KV) {
await env.OAIFREE_KV.put(`${uniqueName}`, `Logged in at ${dateString}`);
}
}
async function handleLoginToken(request: Request, env: Env): Promise<Response> {
if (request.method === 'POST') {
const formData = await request.formData();
const uniqueName = formData.get('email')?.toString().trim();
const tempChat = formData.get('tempchat')?.trim() === 'on';
if (!uniqueName || !isValidSecret(uniqueName)) {
return new Response('Invalid or unique name', { status: 400 });
}
loggingLogin(env, uniqueName);
try {
const shareToken = await getShareToken(request, env, uniqueName, tempChat);
const oAuthLink = `${await getOAuthLink(request, shareToken)}&_=${Date.now()}`;
return Response.redirect(oAuthLink, 302);
} catch (error) {
const errorText = `500 ${error}`;
return new Response(errorText, { status: 500 });
}
} else {
const formHtml = formHTML(env);
return new Response(formHtml, {
headers: { 'Content-Type': 'text/html; charset=utf-8' },
});
}
}
function formHTML(env: Env): string {
return `<!doctype html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Login - ChatGPT</title>
<style>
@charset "UTF-8";
.oai-header img {
display: flex;
height: 32px;
width: 32px;
background-color: #fff;
padding: 32px 0 0;
fill: #202123;
}
a {
font-weight: 400;
text-decoration: inherit;
color: #10a37f;
}
.main-container {
flex: 1 0 auto;
min-height: 0;
display: grid;
box-sizing: border-box;
grid-template-rows: [left-start center-start right-start] 1fr [left-end center-end right-end];
grid-template-columns: [left-start center-start] 1fr [left-end right-start] 1fr [center-end right-end];
align-items: center;
justify-content: center;
justify-items: center;
grid-column-gap: 160px;
column-gap: 160px;
padding: 80px;
width: 100%;
}
.login-container {
background-color: #fff;
padding: 0 40px 40px;
border-radius: 3px;
box-shadow: none;
width: 320px;
box-sizing: content-box;
flex-shrink: 0;
}
.title-wrapper {
padding: 40px 40px 24px;
box-sizing: content-box;
}
.title {
font-size: 32px;
font: 'Söhne';
margin: 24px 0 0;
color: #2d333a;
width: 320px;
text-align: center;
}
.input-wrapper {
position: relative;
margin-bottom: 25px;
width: 320px;
box-sizing: content-box;
}
.email-input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-color: #fff;
border: 1px solid #c2c8d0;
border-radius: 6px;
box-sizing: border-box;
color: #2d333a;
font-family: inherit;
font-size: 16px;
height: 52px;
line-height: 1.1;
outline: none;
padding-block: 1px;
padding-inline: 2px;
padding: 0 16px;
transition:
box-shadow 0.2s ease-in-out,
border-color 0.2s ease-in-out;
width: 100%;
text-rendering: auto;
letter-spacing: normal;
word-spacing: normal;
text-transform: none;
text-indent: 0px;
text-shadow: none;
display: inline-block;
text-align: start;
margin: 0;
}
.email-input:focus,
.email-input:valid {
border: 1px solid #10a37f;
outline: none;
}
.email-input:focus-within {
box-shadow: 1px #10a37f;
}
.email-input:focus + .email-label,
.email-input:valid + .email-label {
font-size: 14px;
top: 0;
left: 10px;
color: #10a37f;
background-color: #fff;
}
.email-label {
position: absolute;
top: 26px;
left: 16px;
background-color: #fff;
color: #6f7780;
font-size: 16px;
font-weight: 400;
margin-bottom: 8px;
max-width: 90%;
overflow: hidden;
pointer-events: none;
padding: 1px 6px;
text-overflow: ellipsis;
transform: translateY(-50%);
transform-origin: 0;
transition:
transform 0.15s ease-in-out,
top 0.15s ease-in-out,
padding 0.15s ease-in-out;
white-space: nowrap;
z-index: 1;
}
.continue-btn {
position: relative;
height: 52px;
width: 320px;
background-color: #10a37f;
color: #fff;
margin: 24px 0 0;
align-items: center;
justify-content: center;
display: flex;
border-radius: 6px;
padding: 4px 16px;
font: inherit;
border-width: 0px;
cursor: pointer;
}
.continue-btn:hover {
box-shadow: inset 0 0 0 150px #0000001a;
}
:root {
font-family:
Söhne,
-apple-system,
BlinkMacSystemFont,
Helvetica,
sans-serif;
line-height: 1.5;
font-weight: 400;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.page-wrapper {
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 100%;
}
.oai-header {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
background-color: #fff;
}
.oai-header img {
display: flex;
height: 64px;
width: 64px;
background-color: #fff;
padding: 64px 0 0;
fill: #202123;
}
body {
background-color: #fff;
display: block;
margin: 0;
}
.content-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: auto;
white-space: normal;
border-radius: 5px;
position: relative;
grid-area: center;
box-shadow: none;
vertical-align: baseline;
box-sizing: content-box;
}
.checkbox-wrapper {
margin: 20px 0;
display: flex;
align-items: center;
}
.checkbox-label {
margin-left: 8px;
font-weight: 600;
color: #6f7780;
font-size: 14px;
}
</style>
</head>
<body>
<div id="root">
<div class="page-wrapper">
<header class="oai-header"><img src="https://img2.imgtp.com/2024/04/20/z9twuEmo.svg" alt="OpenAI's Logo" /></header>
<main class="main-container">
<section class="content-wrapper">
<div class="title-wrapper"><h1 class="title">Welcome back</h1></div>
<div class="login-container">
<form method="POST">
<div class="input-wrapper">
<input
class="email-input"
inputmode="email"
type="email"
id="email-input"
name="email"
autocomplete="username"
autocapitalize="none"
spellcheck="false"
required
placeholder=" "
/><label class="email-label" for="email-input">Email address</label>
</div>
<div class="checkbox-wrapper">
<input type="checkbox" id="temp-chat" name="tempchat" />
<label class="checkbox-label" for="temp-chat">Temporary Chat</label>
</div>
<button class="continue-btn">Continue</button>
</form>
</div>
</section>
</main>
</div>
</div>
<script>
const forceTemporaryChat = JSON.parse('${env.FORCE_TEMPORARY_CHAT}');
window.addEventListener('DOMContentLoaded', () => {
const checkbox = document.getElementById('temp-chat');
if (forceTemporaryChat) {
checkbox.checked = true;
checkbox.disabled = true;
}
});
</script>
</body>
</html>
`;
}
function isValidSecret(email: string): boolean {
// TODO: email white list
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailPattern.test(email);
}
async function handleAPIRequestPath(request: Request, url: URL, env: Env): Promise<Request> {
const prefix = `/${env.API_PREFIX}`;
if (url.pathname.startsWith(prefix)) {
url.pathname = url.pathname.substring(prefix.length);
const apiPath = `/v1/`;
const backendApiPath = `/backend-api/`;
const publicApiPath = `/public-api/`;
if (url.pathname.startsWith(apiPath)) {
url.host = 'api.oaifree.com';
return modifyRequestForApi(request);
} else if (url.pathname.startsWith(backendApiPath) || url.pathname.startsWith(publicApiPath)) {
url.host = 'chat.oaifree.com';
url.pathname = `/dad04481-fa3f-494e-b90c-b822128073e5${url.pathname}`;
} else {
url.host = 'chat.oaifree.com';
}
}
return request;
}
function isValidApiKey(request: Request, env: Env): boolean {
const incomingApiKey = request.headers.get('Authorization')?.replace('Bearer ', '');
return incomingApiKey === env.API_KEY;
}
async function modifyRequestForApi(request: Request): Promise<Request> {
if (!request.bodyUsed) {
const clonedRequest = request.clone();
const reqBody = await clonedRequest.json();
if (reqBody && reqBody.model && reqBody.model.startsWith('gpt-4-gizmo-')) {
reqBody.model = reqBody.model.replace('gpt-4-gizmo-', '');
return new Request(request.url, {
method: request.method,
headers: request.headers,
body: JSON.stringify(reqBody),
});
}
}
return request;
}
async function handleAPIRequest(processedRequest: Request, url: URL, env: Env): Promise<Response> {
const tokens = env.ACCESS_TOKEN.split(',');
for (let retryCount = 0; retryCount < tokens.length; retryCount++) {
const headers = new Headers(processedRequest.headers);
headers.set('Authorization', formatAuthorizationHeader(tokens[retryCount], env));
// Ensure we're only cloning once per retry attempt
const retryRequest = processedRequest.clone();
const modifiedRequest = new Request(url.toString(), {
method: processedRequest.method,
headers,
body: retryRequest.body,
});
console.log(`Attempting fetch with token index ${retryCount}`);
const response = await fetch(modifiedRequest);
if (response.ok) {
console.log(`Request successful with token index ${retryCount}`);
return response;
}
console.warn(`Request failed with token index ${retryCount}, status: ${response.status}`);
if (env.BARK_SERVER && env.BARK_DEVICE_KEY) {
await sendBarkNotification(`Request failed with token index ${retryCount}`, `Status: ${response.status}`, 'default', env);
}
}
console.error('All tokens failed after retries');
return new Response('All tokens failed', { status: 401 });
}
function formatAuthorizationHeader(token: string, env: Env): string {
let authorizationKey = `Bearer ${token}`;
if (env.CHATGPT_ACCOUNT_ID) {
authorizationKey += `,${env.CHATGPT_ACCOUNT_ID}`;
}
return authorizationKey;
}
async function getShareToken(request: Request, env: Env, uniqueName: string, tempChat: boolean) {
const currentHost = new URL(request.url).origin;
const url = 'https://chat.oaifree.com/token/register';
// Ensure FORCE_TEMPORARY_CHAT environment variable is interpreted correctly.
const forceTempChat = env.FORCE_TEMPORARY_CHAT === 'true' || tempChat;
console.log(`Force temporary chat: ${forceTempChat}`);
const accessToken = env.ACCESS_TOKEN.split(',')[0];
const body = new URLSearchParams({
unique_name: uniqueName,
access_token: accessToken,
site_limit: currentHost,
expires_in: '0',
gpt35_limit: '-1',
gpt4_limit: '-1',
show_conversations: 'false',
show_userinfo: 'false',
temporary_chat: forceTempChat.toString(),
reset_limit: 'false',
}).toString();
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: body,
});
if (response.ok) {
const res = await response.json();
console.log(`Share token response: ${JSON.stringify(res)}`);
return res.token_key;
} else {
// Log detailed error information
console.error(`Failed to retrieve share token: ${response.statusText}`);
console.error(`Response status: ${response.status}`);
console.error(`Failed URL: ${url}`);
console.error(`Request Body: ${body}`);
throw new Error(`Failed to retrieve share token: ${response.text()}`);
}
}
async function getOAuthLink(request: Request, shareToken: string) {
const currentHost = new URL(request.url).origin;
// const url = `${currentHost}/api/auth/oauth_token`;
const url = `https://new.oaifree.com/api/auth/oauth_token`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Origin': currentHost,
'Content-Type': 'application/json',
},
body: JSON.stringify({
share_token: shareToken,
}),
});
if (response.ok) {
const data = await response.json();
console.log(`OAuth link response: ${JSON.stringify(data)}`);
return data.login_url;
} else {
console.error(`Failed to get OAuth link: ${response.statusText}`);
throw new Error('Failed to get OAuth link: ${response.text()}');
}
}
async function sendBarkNotification(title: string, body: string, group: string, env: Env): Promise<void> {
const barkUrl = `${env.BARK_SERVER}/push`;
const data = {
title,
body,
group,
device_key: env.BARK_DEVICE_KEY,
};
const response = await fetch(barkUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (response.ok) {
console.log('Bark notification sent successfully');
} else {
const respText = await response.text();
console.error('Failed to send Bark notification:', response.status, response.statusText, respText);
}
}