【极致反代 oaifree】之【糊】之【拼车】之 【究极优雅】 之 pandora-4o

Table of Contents

  1. 究极优雅pandora-4o
    1. 巨人的启发:
    2. 开始配置:
      1. :warning: 安全考虑你可以:
    3. 使用:
      1. ChatGPT
      2. Chat2API
    4. Worker 代码

接上文 【新手教程】oaifree 喂饭级 Step-by-step 一步步打开潘多拉 ,受到伟大的坛友们的启发,于是我又糊了一个。

究极优雅pandora-4o

  1. 称之为 pandora-4o : 还原 pandoranext 通过 API_PREFIX 代理 Chat2API;

  2. 还原官网登陆界面,输入邮箱,一击直达!

  3. 登陆可选项:是否开启临时聊天;(当然也可以支持更多选项)

巨人的启发:

  1. https://linux.do/t/topic/47799
  2. https://linux.do/t/topic/63209
  3. https://linux.do/t/topic/59043

开始配置:

以下下为参考环境变量配置:

// 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'
}

安全考虑你可以:

  1. 配置 Cloudflare zero trust 访问控制;
  2. 配置自定义用户白名单,修改 isValidSecret 方法;

使用:

ChatGPT

  • 直接访问 https://<你的域名>/
  • 输入邮箱(目前是验证邮箱格式,当然其他任意字符串格式理论都是可以支持的)

API

image

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);
	}
}

17 Likes

test
test
大草

image

就test?

2 Likes

?????啥玩意

2 Likes

这种可以举报吗。

2 Likes

入院入院

1 Like

连demo都没?

应该注明编辑中的

1 Like

好好好,不是test了 :laughing:

你们两个在搞什么

别随意编辑别人的帖子

4 Likes

image

一开始只有test

1 Like

image

这个不能输入

记住了

下班!

抱歉啊,我以为是被墙了水帖 :laughing:

1 Like

好好好 集大成者 确实称得上究极了

美中不足的就是还没适配rt

但还是很棒啊 想偷点创意升级一下我的那个了 :face_with_peeking_eye:

而且这样的话就不需要创建kv来存变量了,虽然从编程的角度看没那么优雅,但从部署使用的角度来看确实更简单了

1 Like

我也是。

Added chatgpt, openai

好哥哥,能不能糊个docker一键起飞

拒绝加班

1 Like

恳求坛神赐予我一个 3 级

1 Like