Merge pull request #180 from lanqian528/dev

Dev to Main
This commit is contained in:
LanQian 2024-11-04 00:47:15 +08:00 committed by GitHub
commit dcec8be538
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 5980 additions and 5103 deletions

View File

@ -1,14 +1,4 @@
API_PREFIX=your_prefix
CHATGPT_BASE_URL=https://chatgpt.com
HISTORY_DISABLED=true
PROXY_URL=your_first_proxy, your_second_proxy
EXPORT_PROXY_URL=your_export_proxy
ARK0SE_TOKEN_URL=https://ark0se.example.com/token
POW_DIFFICULTY=000032
RETRY_TIMES=3
ENABLE_GATEWAY=true
CONVERSATION_ONLY=false
ENABLE_LIMIT=true
UPLOAD_BY_URL=false
SCHEDULED_REFRESH=false
USER_AGENTS=["ua1", "ua2"]
SCHEDULED_REFRESH=false

View File

@ -140,6 +140,7 @@ curl --location 'http://127.0.0.1:5005/v1/chat/completions' \
| | SCHEDULED_REFRESH | `false` | `false` | 是否定时刷新 `AccessToken` 开启后每次启动程序将会全部非强制刷新一次每4天晚上3点全部强制刷新一次。 |
| | RANDOM_TOKEN | `true` | `true` | 是否随机选取后台 `Token` ,开启后随机后台账号,关闭后为顺序轮询 |
| 网关功能 | ENABLE_GATEWAY | `false` | `false` | 是否启用网关模式,开启后可以使用镜像站,但也将会不设防 |
| | AUTO_SEED | `false` | `true` | 是否启用随机账号模式,默认启用,输入`seed`后随机匹配后台`Token`。关闭之后需要手动对接接口,来进行`Token`管控。 |
## 部署

122
api/chat2api.py Normal file
View File

@ -0,0 +1,122 @@
import asyncio
import types
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from fastapi import Request, Depends, HTTPException, Form
from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
from starlette.background import BackgroundTask
import utils.globals as globals
from app import templates, oauth2_scheme, app
from chatgpt.ChatService import ChatService
from chatgpt.authorization import refresh_all_tokens
from utils.Logger import logger
from utils.configs import api_prefix, scheduled_refresh
from utils.retry import async_retry
scheduler = AsyncIOScheduler()
@app.on_event("startup")
async def app_start():
if scheduled_refresh:
scheduler.add_job(id='refresh', func=refresh_all_tokens, trigger='cron', hour=3, minute=0, day='*/2',
kwargs={'force_refresh': True})
scheduler.start()
asyncio.get_event_loop().call_later(0, lambda: asyncio.create_task(refresh_all_tokens(force_refresh=False)))
async def to_send_conversation(request_data, req_token):
chat_service = ChatService(req_token)
try:
await chat_service.set_dynamic_data(request_data)
await chat_service.get_chat_requirements()
return chat_service
except HTTPException as e:
await chat_service.close_client()
raise HTTPException(status_code=e.status_code, detail=e.detail)
except Exception as e:
await chat_service.close_client()
logger.error(f"Server error, {str(e)}")
raise HTTPException(status_code=500, detail="Server error")
async def process(request_data, req_token):
chat_service = await to_send_conversation(request_data, req_token)
await chat_service.prepare_send_conversation()
res = await chat_service.send_conversation()
return chat_service, res
@app.post(f"/{api_prefix}/v1/chat/completions" if api_prefix else "/v1/chat/completions")
async def send_conversation(request: Request, req_token: str = Depends(oauth2_scheme)):
try:
request_data = await request.json()
except Exception:
raise HTTPException(status_code=400, detail={"error": "Invalid JSON body"})
chat_service, res = await async_retry(process, request_data, req_token)
try:
if isinstance(res, types.AsyncGeneratorType):
background = BackgroundTask(chat_service.close_client)
return StreamingResponse(res, media_type="text/event-stream", background=background)
else:
background = BackgroundTask(chat_service.close_client)
return JSONResponse(res, media_type="application/json", background=background)
except HTTPException as e:
await chat_service.close_client()
if e.status_code == 500:
logger.error(f"Server error, {str(e)}")
raise HTTPException(status_code=500, detail="Server error")
raise HTTPException(status_code=e.status_code, detail=e.detail)
except Exception as e:
await chat_service.close_client()
logger.error(f"Server error, {str(e)}")
raise HTTPException(status_code=500, detail="Server error")
@app.get(f"/{api_prefix}/tokens" if api_prefix else "/tokens", response_class=HTMLResponse)
async def upload_html(request: Request):
tokens_count = len(set(globals.token_list) - set(globals.error_token_list))
return templates.TemplateResponse("tokens.html",
{"request": request, "api_prefix": api_prefix, "tokens_count": tokens_count})
@app.post(f"/{api_prefix}/tokens/upload" if api_prefix else "/tokens/upload")
async def upload_post(text: str = Form(...)):
lines = text.split("\n")
for line in lines:
if line.strip() and not line.startswith("#"):
globals.token_list.append(line.strip())
with open("../data/token.txt", "a", encoding="utf-8") as f:
f.write(line.strip() + "\n")
logger.info(f"Token count: {len(globals.token_list)}, Error token count: {len(globals.error_token_list)}")
tokens_count = len(set(globals.token_list) - set(globals.error_token_list))
return {"status": "success", "tokens_count": tokens_count}
@app.post(f"/{api_prefix}/tokens/clear" if api_prefix else "/tokens/clear")
async def upload_post():
globals.token_list.clear()
globals.error_token_list.clear()
with open("../data/token.txt", "w", encoding="utf-8") as f:
pass
logger.info(f"Token count: {len(globals.token_list)}, Error token count: {len(globals.error_token_list)}")
tokens_count = len(set(globals.token_list) - set(globals.error_token_list))
return {"status": "success", "tokens_count": tokens_count}
@app.post(f"/{api_prefix}/tokens/error" if api_prefix else "/tokens/error")
async def error_tokens():
error_tokens_list = list(set(globals.error_token_list))
return {"status": "success", "error_tokens": error_tokens_list}
@app.get(f"/{api_prefix}/tokens/add/{{token}}" if api_prefix else "/tokens/add/{token}")
async def add_token(token: str):
if token.strip() and not token.startswith("#"):
globals.token_list.append(token.strip())
with open("../data/token.txt", "a", encoding="utf-8") as f:
f.write(token.strip() + "\n")
logger.info(f"Token count: {len(globals.token_list)}, Error token count: {len(globals.error_token_list)}")
tokens_count = len(set(globals.token_list) - set(globals.error_token_list))
return {"status": "success", "tokens_count": tokens_count}

View File

@ -4,7 +4,7 @@ import pybase64
from PIL import Image
from utils.Client import Client
from utils.config import export_proxy_url, cf_file_url
from utils.configs import export_proxy_url, cf_file_url
async def get_file_content(url):

31
app.py
View File

@ -1,4 +1,13 @@
import warnings
import uvicorn
from fastapi import FastAPI
from fastapi.security import OAuth2PasswordBearer
from fastapi.middleware.cors import CORSMiddleware
from fastapi.templating import Jinja2Templates
warnings.filterwarnings("ignore")
log_config = uvicorn.config.LOGGING_CONFIG
default_format = "%(asctime)s | %(levelname)s | %(message)s"
@ -6,5 +15,23 @@ access_format = r'%(asctime)s | %(levelname)s | %(client_addr)s: %(request_line)
log_config["formatters"]["default"]["fmt"] = default_format
log_config["formatters"]["access"]["fmt"] = access_format
uvicorn.run("chat2api:app", host="0.0.0.0", port=5005)
# uvicorn.run("chat2api:app", host="0.0.0.0", port=5005, ssl_keyfile="key.pem", ssl_certfile="cert.pem")
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
templates = Jinja2Templates(directory="templates")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)
import gateway.backend
import gateway.share
import api.chat2api
if __name__ == "__main__":
uvicorn.run("app:app", host="0.0.0.0", port=5005)
# uvicorn.run("app:app", host="0.0.0.0", port=5005, ssl_keyfile="key.pem", ssl_certfile="cert.pem")

View File

@ -1,254 +0,0 @@
import asyncio
import time
import types
import warnings
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from fastapi import FastAPI, Request, Depends, HTTPException, Form
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse
from fastapi.responses import StreamingResponse, JSONResponse
from fastapi.security import OAuth2PasswordBearer
from fastapi.templating import Jinja2Templates
from starlette.background import BackgroundTask
from starlette.responses import RedirectResponse, Response
from chatgpt.ChatService import ChatService
from chatgpt.authorization import refresh_all_tokens, verify_token, get_req_token
import chatgpt.globals as globals
from chatgpt.reverseProxy import chatgpt_reverse_proxy
from utils.Logger import logger
from utils.config import api_prefix, scheduled_refresh, enable_gateway
from utils.retry import async_retry
warnings.filterwarnings("ignore")
app = FastAPI()
scheduler = AsyncIOScheduler()
templates = Jinja2Templates(directory="templates")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.on_event("startup")
async def app_start():
if scheduled_refresh:
scheduler.add_job(id='refresh', func=refresh_all_tokens, trigger='cron', hour=3, minute=0, day='*/4',
kwargs={'force_refresh': True})
scheduler.start()
asyncio.get_event_loop().call_later(0, lambda: asyncio.create_task(refresh_all_tokens(force_refresh=False)))
async def to_send_conversation(request_data, req_token):
chat_service = ChatService(req_token)
try:
await chat_service.set_dynamic_data(request_data)
await chat_service.get_chat_requirements()
return chat_service
except HTTPException as e:
await chat_service.close_client()
raise HTTPException(status_code=e.status_code, detail=e.detail)
except Exception as e:
await chat_service.close_client()
logger.error(f"Server error, {str(e)}")
raise HTTPException(status_code=500, detail="Server error")
async def process(request_data, req_token):
chat_service = await to_send_conversation(request_data, req_token)
await chat_service.prepare_send_conversation()
res = await chat_service.send_conversation()
return chat_service, res
@app.post(f"/{api_prefix}/v1/chat/completions" if api_prefix else "/v1/chat/completions")
async def send_conversation(request: Request, req_token: str = Depends(oauth2_scheme)):
try:
request_data = await request.json()
except Exception:
raise HTTPException(status_code=400, detail={"error": "Invalid JSON body"})
chat_service, res = await async_retry(process, request_data, req_token)
try:
if isinstance(res, types.AsyncGeneratorType):
background = BackgroundTask(chat_service.close_client)
return StreamingResponse(res, media_type="text/event-stream", background=background)
else:
background = BackgroundTask(chat_service.close_client)
return JSONResponse(res, media_type="application/json", background=background)
except HTTPException as e:
await chat_service.close_client()
if e.status_code == 500:
logger.error(f"Server error, {str(e)}")
raise HTTPException(status_code=500, detail="Server error")
raise HTTPException(status_code=e.status_code, detail=e.detail)
except Exception as e:
await chat_service.close_client()
logger.error(f"Server error, {str(e)}")
raise HTTPException(status_code=500, detail="Server error")
@app.get(f"/{api_prefix}/tokens" if api_prefix else "/tokens", response_class=HTMLResponse)
async def upload_html(request: Request):
tokens_count = len(set(globals.token_list) - set(globals.error_token_list))
return templates.TemplateResponse("tokens.html",
{"request": request, "api_prefix": api_prefix, "tokens_count": tokens_count})
@app.post(f"/{api_prefix}/tokens/upload" if api_prefix else "/tokens/upload")
async def upload_post(text: str = Form(...)):
lines = text.split("\n")
for line in lines:
if line.strip() and not line.startswith("#"):
globals.token_list.append(line.strip())
with open("data/token.txt", "a", encoding="utf-8") as f:
f.write(line.strip() + "\n")
logger.info(f"Token count: {len(globals.token_list)}, Error token count: {len(globals.error_token_list)}")
tokens_count = len(set(globals.token_list) - set(globals.error_token_list))
return {"status": "success", "tokens_count": tokens_count}
@app.post(f"/{api_prefix}/tokens/clear" if api_prefix else "/tokens/clear")
async def upload_post():
globals.token_list.clear()
globals.error_token_list.clear()
with open("data/token.txt", "w", encoding="utf-8") as f:
pass
logger.info(f"Token count: {len(globals.token_list)}, Error token count: {len(globals.error_token_list)}")
tokens_count = len(set(globals.token_list) - set(globals.error_token_list))
return {"status": "success", "tokens_count": tokens_count}
@app.post(f"/{api_prefix}/tokens/error" if api_prefix else "/tokens/error")
async def error_tokens():
error_tokens_list = list(set(globals.error_token_list))
return {"status": "success", "error_tokens": error_tokens_list}
@app.get(f"/{api_prefix}/tokens/add/{{token}}" if api_prefix else "/tokens/add/{token}")
async def add_token(token: str):
if token.strip() and not token.startswith("#"):
globals.token_list.append(token.strip())
with open("data/token.txt", "a", encoding="utf-8") as f:
f.write(token.strip() + "\n")
logger.info(f"Token count: {len(globals.token_list)}, Error token count: {len(globals.error_token_list)}")
tokens_count = len(set(globals.token_list) - set(globals.error_token_list))
return {"status": "success", "tokens_count": tokens_count}
if enable_gateway:
@app.get("/", response_class=HTMLResponse)
async def chatgpt_html(request: Request):
token = request.query_params.get("token")
if not token:
token = request.cookies.get("token")
if not token:
return await login_html(request)
response = templates.TemplateResponse("chatgpt.html", {"request": request, "token": token})
response.set_cookie("token", value=token)
return response
@app.get("/login", response_class=HTMLResponse)
async def login_html(request: Request):
response = templates.TemplateResponse("login.html", {"request": request})
return response
@app.get("/backend-api/gizmos/bootstrap")
async def get_gizmos_bootstrap():
return {"gizmos": []}
# @app.get("/backend-api/conversations")
# async def get_conversations():
# return {"items": [], "total": 0, "limit": 28, "offset": 0, "has_missing_conversations": False}
# @app.patch("/backend-api/conversations")
# async def get_conversations():
# return {"success": True, "message": None}
@app.get("/backend-api/me")
async def get_me(request: Request):
me = {
"object": "user",
"id": "org-chatgpt",
"email": "chatgpt@openai.com",
"name": "ChatGPT",
"picture": "https://cdn.auth0.com/avatars/ai.png",
"created": int(time.time()),
"phone_number": None,
"mfa_flag_enabled": False,
"amr": [],
"groups": [],
"orgs": {
"object": "list",
"data": [
{
"object": "organization",
"id": "org-chatgpt",
"created": 1715641300,
"title": "Personal",
"name": "user-chatgpt",
"description": "Personal org for chatgpt@openai.com",
"personal": True,
"settings": {},
"parent_org_id": None,
"is_default": False,
"role": "owner",
"is_scale_tier_authorized_purchaser": None,
"is_scim_managed": False,
"projects": {
"object": "list",
"data": []
},
"groups": [],
"geography": None
}
]
},
"has_payg_project_spend_limit": None
}
return me
banned_paths = [
"backend-api/accounts/logout_all",
"backend-api/accounts/deactivate",
"backend-api/user_system_messages",
"backend-api/memories",
"backend-api/settings/clear_account_user_memory"
]
redirect_paths = ["auth/logout"]
chatgpt_paths = ["c/"]
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"])
async def reverse_proxy(request: Request, path: str):
for chatgpt_path in chatgpt_paths:
if chatgpt_path in path:
return await chatgpt_html(request)
for banned_path in banned_paths:
if banned_path in path:
return Response(status_code=404)
for redirect_path in redirect_paths:
if redirect_path in path:
redirect_url = str(request.base_url)
response = RedirectResponse(url=f"{redirect_url}", status_code=302)
response.delete_cookie("token")
return response
return await chatgpt_reverse_proxy(request, path)
else:
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"])
async def reverse_proxy():
raise HTTPException(status_code=404, detail="Gateway is disabled")

View File

@ -8,14 +8,14 @@ from starlette.concurrency import run_in_threadpool
from api.files import get_image_size, get_file_extension, determine_file_use_case
from api.models import model_proxy
from chatgpt.authorization import get_req_token, verify_token, get_ua
from chatgpt.authorization import get_req_token, verify_token, get_fp
from chatgpt.chatFormat import api_messages_to_chat, stream_response, format_not_stream_response, head_process_response
from chatgpt.chatLimit import check_is_limit, handle_request_limit
from chatgpt.proofofWork import get_config, get_dpl, get_answer_token, get_requirements_token
from utils.Client import Client
from utils.Logger import logger
from utils.config import (
from utils.configs import (
proxy_url_list,
chatgpt_base_url_list,
ark0se_token_url_list,
@ -26,7 +26,6 @@ from utils.config import (
upload_by_url,
check_model,
auth_key,
user_agents_list,
turnstile_solver_url,
)
@ -35,20 +34,12 @@ class ChatService:
def __init__(self, origin_token=None):
# self.user_agent = random.choice(user_agents_list) if user_agents_list else "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"
self.req_token = get_req_token(origin_token)
self.ua = get_ua(self.req_token)
self.user_agent = self.ua.get(
"user-agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
)
self.chat_token = "gAAAAAB"
self.s = None
self.ws = None
async def set_dynamic_data(self, data):
if self.req_token:
logger.info(f"Request impersonate: {self.ua.get('impersonate')}")
logger.info(f"Request ua:{self.user_agent}")
logger.info(f"Request token: {self.req_token}")
req_len = len(self.req_token.split(","))
if req_len == 1:
self.access_token = await verify_token(self.req_token)
@ -61,6 +52,15 @@ class ChatService:
self.access_token = None
self.account_id = None
self.fp = get_fp(self.req_token)
self.proxy_url = self.fp.get("proxy_url")
self.user_agent = self.fp.get("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0")
self.impersonate = self.fp.get("impersonate", "safari15_3")
logger.info(f"Request token: {self.req_token}")
logger.info(f"Request proxy: {self.proxy_url}")
logger.info(f"Request UA: {self.user_agent}")
logger.info(f"Request impersonate: {self.impersonate}")
self.data = data
await self.set_model()
if enable_limit and self.req_token:
@ -79,11 +79,12 @@ class ChatService:
if not isinstance(self.max_tokens, int):
self.max_tokens = 2147483647
self.proxy_url = random.choice(proxy_url_list) if proxy_url_list else None
# self.proxy_url = random.choice(proxy_url_list) if proxy_url_list else None
self.host_url = random.choice(chatgpt_base_url_list) if chatgpt_base_url_list else "https://chatgpt.com"
self.ark0se_token_url = random.choice(ark0se_token_url_list) if ark0se_token_url_list else None
self.s = Client(proxy=self.proxy_url, impersonate=self.ua.get("impersonate", "safari15_3"))
self.s = Client(proxy=self.proxy_url, impersonate=self.impersonate)
self.oai_device_id = str(uuid.uuid4())
self.persona = None
@ -104,9 +105,9 @@ class ChatService:
'origin': self.host_url,
'priority': 'u=1, i',
'referer': f'{self.host_url}/',
'sec-ch-ua': self.ua.get("sec-ch-ua", '"Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"'),
'sec-ch-ua-mobile': self.ua.get("sec-ch-ua-mobile", "?0"),
'sec-ch-ua-platform': self.ua.get("sec-ch-ua-platform", '"Windows"'),
'sec-ch-ua': self.fp.get("sec-ch-ua", '"Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"'),
'sec-ch-ua-mobile': self.fp.get("sec-ch-ua-mobile", "?0"),
'sec-ch-ua-platform': self.fp.get("sec-ch-ua-platform", '"Windows"'),
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
@ -114,9 +115,9 @@ class ChatService:
}
if self.access_token:
self.base_url = self.host_url + "/backend-api"
self.base_headers['Authorization'] = f'Bearer {self.access_token}'
self.base_headers['authorization'] = f'Bearer {self.access_token}'
if self.account_id:
self.base_headers['Chatgpt-Account-Id'] = self.account_id
self.base_headers['chatgpt-account-id'] = self.account_id
else:
self.base_url = self.host_url + "/backend-anon"
@ -224,7 +225,7 @@ class ChatService:
if not self.ark0se_token_url:
raise HTTPException(status_code=403, detail="Ark0se service required")
ark0se_dx = ark0se.get("dx")
ark0se_client = Client(impersonate=self.ua.get("impersonate", "safari15_3"))
ark0se_client = Client(impersonate=self.fp.get("impersonate", "safari15_3"))
try:
r2 = await ark0se_client.post(
url=self.ark0se_token_url, json={"blob": ark0se_dx, "method": ark0se_method}, timeout=15
@ -382,7 +383,7 @@ class ChatService:
url = f"{self.base_url}/files/{file_id}/download"
headers = self.base_headers.copy()
try:
r = await self.s.get(url, headers=headers, timeout=5)
r = await self.s.get(url, headers=headers, timeout=10)
if r.status_code == 200:
download_url = r.json().get('download_url')
return download_url
@ -396,7 +397,7 @@ class ChatService:
url = f"{self.base_url}/files/{file_id}/uploaded"
headers = self.base_headers.copy()
try:
r = await self.s.post(url, headers=headers, json={}, timeout=30)
r = await self.s.post(url, headers=headers, json={}, timeout=10)
if r.status_code == 200:
download_url = r.json().get('download_url')
return download_url
@ -413,7 +414,7 @@ class ChatService:
r = await self.s.post(
url,
headers=headers,
json={"file_name": file_name, "file_size": file_size, "timezone_offset_min": -480, "use_case": use_case},
json={"file_name": file_name, "file_size": file_size, "reset_rate_limits": False, "timezone_offset_min": -480, "use_case": use_case},
timeout=5,
)
if r.status_code == 200:
@ -438,7 +439,9 @@ class ChatService:
'x-ms-version': '2020-04-08',
}
)
headers.pop('Authorization', None)
headers.pop('authorization', None)
headers.pop('oai-device-id', None)
headers.pop('oai-language', None)
try:
r = await self.s.put(upload_url, headers=headers, data=file_content, timeout=60)
if r.status_code == 201:

View File

@ -1,77 +1,97 @@
import asyncio
import json
import os
import random
import ua_generator
from ua_generator.options import Options
from ua_generator.data.version import VersionRange
from fastapi import HTTPException
import chatgpt.globals as globals
import utils.configs as configs
import utils.globals as globals
from chatgpt.refreshToken import rt2ac
from utils.Logger import logger
from utils.config import authorization_list, random_token
os.environ['PYTHONHASHSEED'] = '0'
random.seed(0)
def get_req_token(req_token, seed=None):
available_token_list = list(set(globals.token_list) - set(globals.error_token_list))
length = len(available_token_list)
if seed and length > 0:
req_token = globals.token_list[hash(seed) % length]
while req_token in globals.error_token_list:
req_token = random.choice(globals.token_list)
return req_token
if req_token in authorization_list:
if len(available_token_list) > 0:
if random_token:
req_token = random.choice(available_token_list)
return req_token
if configs.auto_seed or not seed:
available_token_list = list(set(globals.token_list) - set(globals.error_token_list))
length = len(available_token_list)
if seed and length > 0:
if seed not in globals.seed_map.keys():
globals.seed_map[seed] = {"token": random.choice(available_token_list), "conversations": []}
with open(globals.SEED_MAP_FILE, "w") as f:
json.dump(globals.seed_map, f, indent=4)
else:
globals.count += 1
globals.count %= length
return available_token_list[globals.count]
req_token = globals.seed_map[seed]["token"]
return req_token
if req_token in configs.authorization_list:
if len(available_token_list) > 0:
if configs.random_token:
req_token = random.choice(available_token_list)
return req_token
else:
globals.count += 1
globals.count %= length
return available_token_list[globals.count]
else:
return None
else:
return None
return req_token
else:
return req_token
if seed not in globals.seed_map.keys():
raise HTTPException(status_code=401, detail={"error": "Invalid Seed"})
return globals.seed_map[seed]["token"]
def get_ua(req_token):
user_agent = globals.user_agent_map.get(req_token, {})
user_agent = {k.lower(): v for k, v in user_agent.items()}
if not user_agent:
if not req_token:
ua = ua_generator.generate(device='desktop', browser=('chrome', 'edge'), platform=('windows', 'macos'))
return {
"user-agent": ua.text,
"sec-ch-ua-platform": ua.platform,
"sec-ch-ua": ua.ch.brands,
"sec-ch-ua-mobile": ua.ch.mobile,
"impersonate": random.choice(globals.impersonate_list),
}
else:
ua = ua_generator.generate(device='desktop', browser=('chrome', 'edge'), platform=('windows', 'macos'))
user_agent = {
"user-agent": ua.text,
"sec-ch-ua-platform": ua.platform,
"sec-ch-ua": ua.ch.brands,
"sec-ch-ua-mobile": ua.ch.mobile,
"impersonate": random.choice(globals.impersonate_list),
}
globals.user_agent_map[req_token] = user_agent
with open(globals.USER_AGENTS_FILE, "w", encoding="utf-8") as f:
f.write(json.dumps(globals.user_agent_map, indent=4))
return user_agent
else:
def get_fp(req_token):
fp = globals.user_agent_map.get(req_token, {})
if fp and fp.get("user-agent") and fp.get("impersonate"):
if "proxy_url" in fp.keys() and fp["proxy_url"] and fp["proxy_url"] not in configs.proxy_url_list:
fp["proxy_url"] = random.choice(configs.proxy_url_list) if configs.proxy_url_list else None
globals.user_agent_map[req_token] = fp
with open(globals.FP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.user_agent_map, f, indent=4)
if globals.impersonate_list and "impersonate" in fp.keys() and fp["impersonate"] not in globals.impersonate_list:
fp["impersonate"] = random.choice(globals.impersonate_list)
globals.user_agent_map[req_token] = fp
with open(globals.FP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.user_agent_map, f, indent=4)
if configs.user_agents_list and "user-agent" in fp.keys() and fp["user-agent"] not in configs.user_agents_list:
fp["user-agent"] = random.choice(configs.user_agents_list)
globals.user_agent_map[req_token] = fp
with open(globals.FP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.user_agent_map, f, indent=4)
user_agent = {k.lower(): v for k, v in fp.items()}
return user_agent
else:
options = Options(version_ranges={
'chrome': VersionRange(min_version=124),
'edge': VersionRange(min_version=124),
})
ua = ua_generator.generate(device='desktop', browser=('chrome', 'edge', 'firefox', 'safari'),
platform=('windows', 'macos'), options=options)
fp = {
"user-agent": ua.text if not configs.user_agents_list else random.choice(configs.user_agents_list),
"sec-ch-ua-platform": ua.ch.platform,
"sec-ch-ua": ua.ch.brands,
"sec-ch-ua-mobile": ua.ch.mobile,
"impersonate": random.choice(globals.impersonate_list),
"proxy_url": random.choice(configs.proxy_url_list) if configs.proxy_url_list else None,
}
if not req_token:
return fp
else:
globals.user_agent_map[req_token] = fp
with open(globals.FP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.user_agent_map, f, indent=4)
return fp
async def verify_token(req_token):
if not req_token:
if authorization_list:
if configs.authorization_list:
logger.error("Unauthorized with empty token.")
raise HTTPException(status_code=401)
else:
@ -94,7 +114,7 @@ async def refresh_all_tokens(force_refresh=False):
for token in list(set(globals.token_list) - set(globals.error_token_list)):
if len(token) == 45:
try:
await asyncio.sleep(2)
await asyncio.sleep(0.5)
await rt2ac(token, force_refresh=force_refresh)
except HTTPException:
pass

View File

@ -1,108 +0,0 @@
import json
import os
import ua_generator
import random
from utils.Logger import logger
DATA_FOLDER = "data"
TOKENS_FILE = os.path.join(DATA_FOLDER, "token.txt")
REFRESH_MAP_FILE = os.path.join(DATA_FOLDER, "refresh_map.json")
ERROR_TOKENS_FILE = os.path.join(DATA_FOLDER, "error_token.txt")
WSS_MAP_FILE = os.path.join(DATA_FOLDER, "wss_map.json")
USER_AGENTS_FILE = os.path.join(DATA_FOLDER, "user_agents.json")
count = 0
token_list = []
error_token_list = []
refresh_map = {}
wss_map = {}
user_agent_map = {}
impersonate_list = [
"chrome99",
"chrome100",
"chrome101",
"chrome104",
"chrome107",
"chrome110",
"chrome116",
"chrome119",
"chrome120",
"chrome123",
"edge99",
"edge101",
]
if not os.path.exists(DATA_FOLDER):
os.makedirs(DATA_FOLDER)
if os.path.exists(REFRESH_MAP_FILE):
with open(REFRESH_MAP_FILE, "r") as file:
refresh_map = json.load(file)
else:
refresh_map = {}
if os.path.exists(WSS_MAP_FILE):
with open(WSS_MAP_FILE, "r") as file:
wss_map = json.load(file)
else:
wss_map = {}
if os.path.exists(TOKENS_FILE):
with open(TOKENS_FILE, "r", encoding="utf-8") as f:
for line in f:
if line.strip() and not line.startswith("#"):
token_list.append(line.strip())
else:
with open(TOKENS_FILE, "w", encoding="utf-8") as f:
pass
if os.path.exists(ERROR_TOKENS_FILE):
with open(ERROR_TOKENS_FILE, "r", encoding="utf-8") as f:
for line in f:
if line.strip() and not line.startswith("#"):
error_token_list.append(line.strip())
else:
with open(ERROR_TOKENS_FILE, "w", encoding="utf-8") as f:
pass
if os.path.exists(USER_AGENTS_FILE):
with open(USER_AGENTS_FILE, "r", encoding="utf-8") as f:
try:
user_agent_map = json.load(f)
except json.JSONDecodeError:
user_agent_map = {}
# token数量变化时更新ua
if len(user_agent_map.keys()) != len(token_list):
new_tokens = list(set(token_list) - user_agent_map.keys())
for token in new_tokens:
ua = ua_generator.generate(device='desktop', browser=('chrome', 'edge'), platform=('windows', 'macos'))
ua_dict = {
"user-agent": ua.text,
"sec-ch-ua-platform": ua.platform,
"sec-ch-ua": ua.ch.brands,
"sec-ch-ua-mobile": ua.ch.mobile,
"impersonate": random.choice(impersonate_list),
}
user_agent_map[token] = ua_dict
with open(USER_AGENTS_FILE, "w", encoding="utf-8") as f:
f.write(json.dumps(user_agent_map, indent=4))
else:
for token in token_list:
ua = ua_generator.generate(device='desktop', browser=('chrome', 'edge'), platform=('windows', 'macos'))
ua_dict = {
"user-agent": ua.text,
"sec-ch-ua-platform": ua.platform,
"sec-ch-ua": ua.ch.brands,
"sec-ch-ua-mobile": ua.ch.mobile,
"impersonate": random.choice(impersonate_list),
}
user_agent_map[token] = ua_dict
with open(USER_AGENTS_FILE, "w", encoding="utf-8") as f:
f.write(json.dumps(user_agent_map, indent=4))
if token_list:
logger.info(f"Token list count: {len(token_list)}, Error token list count: {len(error_token_list)}")

View File

@ -10,7 +10,7 @@ from html.parser import HTMLParser
import pybase64
from utils.Logger import logger
from utils.config import conversation_only
from utils.configs import conversation_only
cores = [16, 24, 32]
screens = [3000, 4000, 6000]

View File

@ -6,25 +6,21 @@ from fastapi import HTTPException
from utils.Client import Client
from utils.Logger import logger
from utils.config import proxy_url_list
import chatgpt.globals as globals
def save_refresh_map(refresh_map):
with open(globals.REFRESH_MAP_FILE, "w") as file:
json.dump(refresh_map, file)
from utils.configs import proxy_url_list
import utils.globals as globals
async def rt2ac(refresh_token, force_refresh=False):
if not force_refresh and (refresh_token in globals.refresh_map and int(time.time()) - globals.refresh_map.get(refresh_token, {}).get("timestamp", 0) < 5 * 24 * 60 * 60):
access_token = globals.refresh_map[refresh_token]["token"]
logger.info(f"refresh_token -> access_token from cache")
# logger.info(f"refresh_token -> access_token from cache")
return access_token
else:
try:
access_token = await chat_refresh(refresh_token)
globals.refresh_map[refresh_token] = {"token": access_token, "timestamp": int(time.time())}
save_refresh_map(globals.refresh_map)
with open(globals.REFRESH_MAP_FILE, "w") as f:
json.dump(globals.refresh_map, f, indent=4)
logger.info(f"refresh_token -> access_token with openai: {access_token}")
return access_token
except HTTPException as e:

View File

@ -2,12 +2,12 @@ import json
import time
from utils.Logger import logger
import chatgpt.globals as globals
import utils.globals as globals
def save_wss_map(wss_map):
with open(globals.WSS_MAP_FILE, "w") as file:
json.dump(wss_map, file)
with open(globals.WSS_MAP_FILE, "w") as f:
json.dump(wss_map, f, indent=4)
async def token2wss(token):

332
gateway/backend.py Normal file
View File

@ -0,0 +1,332 @@
import json
import re
import time
import uuid
from fastapi import Request, HTTPException
from fastapi.responses import HTMLResponse, RedirectResponse, Response
import utils.globals as globals
from app import app, templates
from gateway.reverseProxy import chatgpt_reverse_proxy
from utils.configs import authorization_list
from utils.configs import enable_gateway
with open("templates/remix_context.json", "r", encoding="utf-8") as f:
remix_context = json.load(f)
def set_value_for_key(data, target_key, new_value):
if isinstance(data, dict):
for key, value in data.items():
if key == target_key:
data[key] = new_value
else:
set_value_for_key(value, target_key, new_value)
elif isinstance(data, list):
for item in data:
set_value_for_key(item, target_key, new_value)
if enable_gateway:
@app.get("/", response_class=HTMLResponse)
async def chatgpt_html(request: Request):
token = request.query_params.get("token")
if not token:
token = request.cookies.get("token")
if not token:
return await login_html(request)
user_remix_context = remix_context.copy()
set_value_for_key(user_remix_context, "user", {"id": "user-chatgpt"})
set_value_for_key(user_remix_context, "accessToken", token)
response = templates.TemplateResponse("chatgpt.html", {"request": request, "remix_context": user_remix_context})
response.set_cookie("token", value=token, expires="Thu, 01 Jan 2099 00:00:00 GMT")
return response
# @app.get("/backend-api/accounts/check/v4-2023-04-27")
# async def check_account(request: Request):
# token = request.headers.get("Authorization").replace("Bearer ", "")
# check_account_response = await chatgpt_reverse_proxy(request, "backend-api/accounts/check/v4-2023-04-27")
# if len(token) == 45 or token.startswith("eyJhbGciOi"):
# return check_account_response
# else:
# check_account_str = check_account_response.body.decode('utf-8')
# check_account_info = json.loads(check_account_str)
# for key in check_account_info.get("accounts", {}).keys():
# account_id = check_account_info["accounts"][key]["account"]["account_id"]
# globals.seed_map[token]["user_id"] = check_account_info["accounts"][key]["account"]["account_user_id"].split("__")[0]
# check_account_info["accounts"][key]["account"]["account_user_id"] = f"user-chatgpt__{account_id}"
# with open(globals.SEED_MAP_FILE, "w", encoding="utf-8") as f:
# json.dump(globals.seed_map, f, indent=4)
# return check_account_info
def verify_authorization(request: Request):
auth_header = request.headers.get("Authorization").replace("Bearer ", "")
if not auth_header:
raise HTTPException(status_code=401, detail="Authorization header is missing")
if auth_header not in authorization_list:
raise HTTPException(status_code=401, detail="Invalid authorization")
@app.get("/seedtoken")
async def get_seedtoken(request: Request):
verify_authorization(request)
try:
params = request.query_params
seed = params.get("seed")
if seed:
if seed not in globals.seed_map:
raise HTTPException(status_code=404, detail=f"Seed '{seed}' not found")
return {
"status": "success",
"data": {
"seed": seed,
"token": globals.seed_map[seed]["token"]
}
}
token_map = {
seed: data["token"]
for seed, data in globals.seed_map.items()
}
return {"status": "success", "data": token_map}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
@app.post("/seedtoken")
async def set_seedtoken(request: Request):
verify_authorization(request)
data = await request.json()
seed = data.get("seed")
token = data.get("token")
if seed not in globals.seed_map:
globals.seed_map[seed] = {
"token": token,
"conversations": []
}
else:
globals.seed_map[seed]["token"] = token
with open(globals.SEED_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.seed_map, f, indent=4)
return {"status": "success", "message": "Token updated successfully"}
@app.delete("/seedtoken")
async def delete_seedtoken(request: Request):
verify_authorization(request)
try:
data = await request.json()
seed = data.get("seed")
if seed == "clear":
globals.seed_map.clear()
with open(globals.SEED_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.seed_map, f, indent=4)
return {"status": "success", "message": "All seeds deleted successfully"}
if not seed:
raise HTTPException(status_code=400, detail="Missing required field: seed")
if seed not in globals.seed_map:
raise HTTPException(status_code=404, detail=f"Seed '{seed}' not found")
del globals.seed_map[seed]
with open(globals.SEED_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.seed_map, f, indent=4)
return {
"status": "success",
"message": f"Seed '{seed}' deleted successfully"
}
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="Invalid JSON data")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
@app.get("/login", response_class=HTMLResponse)
async def login_html(request: Request):
response = templates.TemplateResponse("login.html", {"request": request})
return response
@app.get("/gpts")
async def get_gpts():
return {"kind": "store"}
@app.get("/backend-api/gizmos/bootstrap")
async def get_gizmos_bootstrap():
return {"gizmos": []}
@app.get("/backend-api/conversations")
async def get_conversations(request: Request):
limit = int(request.query_params.get("limit", 28))
offset = int(request.query_params.get("offset", 0))
token = request.headers.get("Authorization", "").replace("Bearer ", "")
if len(token) == 45 or token.startswith("eyJhbGciOi"):
return await chatgpt_reverse_proxy(request, "backend-api/conversations")
else:
items = []
for conversation_id in globals.seed_map.get(token, {}).get("conversations", []):
conversation = globals.conversation_map.get(conversation_id, None)
if conversation:
items.append(conversation)
items = items[int(offset):int(offset) + int(limit)]
conversations = {
"items": items,
"total": len(items),
"limit": limit,
"offset": offset,
"has_missing_conversations": False
}
return conversations
@app.get("/backend-api/conversation/{conversation_id}")
async def update_conversation(request: Request, conversation_id: str):
token = request.headers.get("Authorization", "").replace("Bearer ", "")
conversation_details_response = await chatgpt_reverse_proxy(request,
f"backend-api/conversation/{conversation_id}")
if len(token) == 45 or token.startswith("eyJhbGciOi"):
return conversation_details_response
else:
conversation_details_str = conversation_details_response.body.decode('utf-8')
conversation_details = json.loads(conversation_details_str)
if conversation_id in globals.seed_map[token][
"conversations"] and conversation_id in globals.conversation_map:
globals.conversation_map[conversation_id]["title"] = conversation_details.get("title", None)
globals.conversation_map[conversation_id]["is_archived"] = conversation_details.get("is_archived",
False)
with open(globals.CONVERSATION_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.conversation_map, f, indent=4)
return conversation_details_response
@app.patch("/backend-api/conversation/{conversation_id}")
async def patch_conversation(request: Request, conversation_id: str):
token = request.headers.get("Authorization", "").replace("Bearer ", "")
patch_response = (await chatgpt_reverse_proxy(request, f"backend-api/conversation/{conversation_id}"))
if len(token) == 45 or token.startswith("eyJhbGciOi"):
return patch_response
else:
data = await request.json()
if conversation_id in globals.seed_map[token][
"conversations"] and conversation_id in globals.conversation_map:
if not data.get("is_visible", True):
globals.conversation_map.pop(conversation_id)
globals.seed_map[token]["conversations"].remove(conversation_id)
with open(globals.SEED_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.seed_map, f, indent=4)
else:
globals.conversation_map[conversation_id].update(data)
with open(globals.CONVERSATION_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.conversation_map, f, indent=4)
return patch_response
@app.get("/backend-api/me")
async def get_me(request: Request):
me = {
"object": "user",
"id": "org-chatgpt",
"email": "chatgpt@openai.com",
"name": "ChatGPT",
"picture": "https://cdn.auth0.com/avatars/ai.png",
"created": int(time.time()),
"phone_number": None,
"mfa_flag_enabled": False,
"amr": [],
"groups": [],
"orgs": {
"object": "list",
"data": [
{
"object": "organization",
"id": "org-chatgpt",
"created": 1715641300,
"title": "Personal",
"name": "user-chatgpt",
"description": "Personal org for chatgpt@openai.com",
"personal": True,
"settings": {
"threads_ui_visibility": "NONE",
"usage_dashboard_visibility": "ANY_ROLE",
"disable_user_api_keys": False
},
"parent_org_id": None,
"is_default": True,
"role": "owner",
"is_scale_tier_authorized_purchaser": None,
"is_scim_managed": False,
"projects": {
"object": "list",
"data": []
},
"groups": [],
"geography": None
}
]
},
"has_payg_project_spend_limit": True
}
return me
banned_paths = [
"backend-api/accounts/logout_all",
"backend-api/accounts/deactivate",
"backend-api/user_system_messages",
"backend-api/memories",
"backend-api/settings/clear_account_user_memory",
"backend-api/conversations/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
"backend-api/accounts/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/invites",
"admin",
]
redirect_paths = ["auth/logout"]
chatgpt_paths = ["c/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"]
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"])
async def reverse_proxy(request: Request, path: str):
if re.match("/v1/rgstr", path):
return Response(status_code=202, content=b'{"success":true}')
if re.match("ces/v1", path):
return {"success": True}
if re.match("backend-api/edge", path):
return Response(status_code=204)
for chatgpt_path in chatgpt_paths:
if re.match(chatgpt_path, path):
return await chatgpt_html(request)
for banned_path in banned_paths:
if re.match(banned_path, path):
raise HTTPException(status_code=403, detail="Forbidden")
for redirect_path in redirect_paths:
if re.match(redirect_path, path):
redirect_url = str(request.base_url)
response = RedirectResponse(url=f"{redirect_url}login", status_code=302)
return response
return await chatgpt_reverse_proxy(request, path)
else:
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"])
async def reverse_proxy():
raise HTTPException(status_code=404, detail="Gateway is disabled")

View File

@ -1,13 +1,24 @@
import json
import random
import time
from fastapi import Request, HTTPException
from fastapi.responses import StreamingResponse, Response
from starlette.background import BackgroundTask
from chatgpt.authorization import verify_token, get_req_token, get_ua
from chatgpt.authorization import verify_token, get_req_token, get_fp
import utils.globals as globals
from utils.Client import Client
from utils.config import chatgpt_base_url_list, proxy_url_list, enable_gateway
from utils.Logger import logger
from utils.configs import chatgpt_base_url_list, proxy_url_list
from datetime import datetime, timezone
def generate_current_time():
current_time = datetime.now(timezone.utc)
formatted_time = current_time.isoformat(timespec='microseconds').replace('+00:00', 'Z')
return formatted_time
headers_reject_list = [
"x-real-ip",
@ -70,6 +81,60 @@ async def get_real_req_token(token):
return req_token
async def content_generator(r, token):
conversation_id = None
title = None
async for chunk in r.aiter_content():
try:
if (len(token) != 45 and not token.startswith("eyJhbGciOi")) and (not conversation_id or not title):
chat_chunk = chunk.decode('utf-8')
if "title" in chat_chunk:
pass
if chat_chunk.startswith("data: {"):
if "\n\nevent: delta" in chat_chunk:
index = chat_chunk.find("\n\nevent: delta")
chunk_data = chat_chunk[6:index]
elif "\n\ndata: {" in chat_chunk:
index = chat_chunk.find("\n\ndata: {")
chunk_data = chat_chunk[6:index]
else:
chunk_data = chat_chunk[6:]
chunk_data = chunk_data.strip()
if conversation_id is None:
conversation_id = json.loads(chunk_data).get("conversation_id")
if conversation_id in globals.conversation_map:
title = globals.conversation_map[conversation_id].get("title")
if title is None:
title = json.loads(chunk_data).get("title")
if conversation_id and title:
conversation_detail = {
"id": conversation_id,
"title": title,
"update_time": generate_current_time(),
"workspace_id": None,
}
if conversation_id not in globals.conversation_map:
globals.conversation_map[conversation_id] = conversation_detail
else:
globals.conversation_map[conversation_id]["update_time"] = generate_current_time()
if conversation_id not in globals.seed_map[token]["conversations"]:
globals.seed_map[token]["conversations"].insert(0, conversation_id)
else:
globals.seed_map[token]["conversations"].remove(conversation_id)
globals.seed_map[token]["conversations"].insert(0, conversation_id)
with open(globals.CONVERSATION_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.conversation_map, f, indent=4)
with open(globals.SEED_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.seed_map, f, indent=4)
logger.info(f"Conversation ID: {conversation_id}, Title: {title}")
except Exception as e:
# logger.error(e)
# logger.error(chunk.decode('utf-8'))
pass
yield chunk
async def chatgpt_reverse_proxy(request: Request, path: str):
try:
origin_host = request.url.netloc
@ -96,11 +161,16 @@ async def chatgpt_reverse_proxy(request: Request, path: str):
base_url = "https://cdn.oaistatic.com"
if "file-" in path and "backend-api" not in path:
base_url = "https://files.oaiusercontent.com"
if "v1/" in path:
base_url = "https://ab.chatgpt.com"
token = request.cookies.get("token")
token = request.cookies.get("token", "")
req_token = await get_real_req_token(token)
ua = get_ua(req_token)
headers.update(ua)
fp = get_fp(req_token)
proxy_url = fp.get("proxy_url")
user_agent = fp.get("user-agent")
impersonate = fp.get("impersonate", "safari15_3")
headers.update(fp)
headers.update({
"accept-language": "en-US,en;q=0.9",
@ -108,6 +178,13 @@ async def chatgpt_reverse_proxy(request: Request, path: str):
"origin": base_url,
"referer": f"{base_url}/"
})
if "ab.chatgpt.com" in base_url:
headers.update({
"statsig-sdk-type": "js-client",
"statsig-api-key": "client-tnE5GCU2F2cTxRiMbvTczMDT1jpwIigZHsZSdqiy4u",
"statsig-sdk-version": "5.1.0",
"statsig-client-time": int(time.time() * 1000)
})
token = headers.get("authorization", "").replace("Bearer ", "")
if token:
@ -117,28 +194,39 @@ async def chatgpt_reverse_proxy(request: Request, path: str):
data = await request.body()
client = Client(proxy=random.choice(proxy_url_list) if proxy_url_list else None)
client = Client(proxy=proxy_url, impersonate=impersonate)
try:
background = BackgroundTask(client.close)
r = await client.request(request.method, f"{base_url}/{path}", params=params, headers=headers,
cookies=request_cookies, data=data, stream=True, allow_redirects=False)
if r.status_code == 307:
if "bing" in path:
return Response(status_code=307,
headers={"Location": r.headers.get("Location").replace("chatgpt.com", origin_host)
.replace("https", petrol)}, background=background)
if r.status_code == 302:
return Response(status_code=302,
headers={"Location": r.headers.get("Location").replace("chatgpt.com", origin_host)
.replace("cdn.oaistatic.com", origin_host)
.replace("https", petrol)}, background=background)
elif 'stream' in r.headers.get("content-type", ""):
return StreamingResponse(r.aiter_content(), media_type=r.headers.get("content-type", ""),
logger.info(f"Request token: {req_token}")
logger.info(f"Request proxy: {proxy_url}")
logger.info(f"Request UA: {user_agent}")
logger.info(f"Request impersonate: {impersonate}")
return StreamingResponse(content_generator(r, token), media_type=r.headers.get("content-type", ""),
background=background)
else:
if "/backend-api/conversation" in path or "/register-websocket" in path:
response = Response(content=(await r.atext()), media_type=r.headers.get("content-type"),
status_code=r.status_code, background=background)
else:
content = ((await r.atext()).replace("chatgpt.com", origin_host)
content = await r.atext()
content = (content
.replace("ab.chatgpt.com", origin_host)
.replace("cdn.oaistatic.com", origin_host)
# .replace("files.oaiusercontent.com", origin_host)
.replace("chatgpt.com", origin_host)
.replace("https", petrol))
rheaders = dict(r.headers)
content_type = rheaders.get("content-type", "")

247
gateway/share.py Normal file
View File

@ -0,0 +1,247 @@
import json
import random
import uuid
from fastapi import Request, HTTPException
from fastapi.responses import Response
from starlette.background import BackgroundTask
from starlette.concurrency import run_in_threadpool
from starlette.responses import StreamingResponse
from app import app
from chatgpt.authorization import get_fp, verify_token
from chatgpt.proofofWork import get_config, get_requirements_token, get_answer_token
from gateway.reverseProxy import get_real_req_token, content_generator
from utils.Client import Client
from utils.Logger import logger
from utils.configs import proxy_url_list, chatgpt_base_url_list, turnstile_solver_url, x_sign, no_sentinel
base_headers = {
'accept': '*/*',
'accept-encoding': 'gzip, deflate, br, zstd',
'accept-language': 'en-US,en;q=0.9',
'content-type': 'application/json',
'oai-language': 'en-US',
'priority': 'u=1, i',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
}
async def chatgpt_account_check(access_token):
auth_info = {}
client = Client(proxy=random.choice(proxy_url_list) if proxy_url_list else None)
try:
proxy_url = random.choice(proxy_url_list) if proxy_url_list else None
host_url = random.choice(chatgpt_base_url_list) if chatgpt_base_url_list else "https://chatgpt.com"
req_token = await get_real_req_token(access_token)
access_token = await verify_token(req_token)
fp = get_fp(req_token)
headers = base_headers.copy()
headers.update({"authorization": f"Bearer {access_token}"})
headers.update(fp)
client = Client(proxy=proxy_url, impersonate=fp.get("impersonate", "safari15_3"))
r = await client.get(f"{host_url}/backend-api/models?history_and_training_disabled=false", headers=headers,
timeout=10)
if r.status_code != 200:
raise HTTPException(status_code=r.status_code, detail=r.text)
models = r.json()
r = await client.get(f"{host_url}/backend-api/accounts/check/v4-2023-04-27", headers=headers, timeout=10)
if r.status_code != 200:
raise HTTPException(status_code=r.status_code, detail=r.text)
accounts_info = r.json()
auth_info.update({"models": models["models"]})
auth_info.update({"accounts_info": accounts_info})
account_ordering = accounts_info.get("account_ordering", [])
is_deactivated = None
plan_type = None
team_ids = []
for account in account_ordering:
this_is_deactivated = accounts_info['accounts'].get(account, {}).get("account", {}).get("is_deactivated",
False)
this_plan_type = accounts_info['accounts'].get(account, {}).get("account", {}).get("plan_type", "free")
if this_is_deactivated and is_deactivated is None:
is_deactivated = True
else:
is_deactivated = False
if "team" in this_plan_type:
plan_type = this_plan_type
team_ids.append(account)
elif plan_type is None:
plan_type = this_plan_type
auth_info.update({"accountCheckInfo": {
"is_deactivated": is_deactivated,
"plan_type": plan_type,
"team_ids": team_ids
}})
return auth_info
except Exception as e:
logger.error(f"chatgpt_account_check: {e}")
return {}
finally:
await client.close()
async def chatgpt_refresh(refresh_token):
client = Client(proxy=random.choice(proxy_url_list) if proxy_url_list else None)
try:
data = {
"client_id": "pdlLIX2Y72MIl2rhLhTE9VV9bN905kBh",
"grant_type": "refresh_token",
"redirect_uri": "com.openai.chat://auth0.openai.com/ios/com.openai.chat/callback",
"refresh_token": refresh_token
}
r = await client.post("https://auth0.openai.com/oauth/token", json=data, timeout=10)
if r.status_code != 200:
raise HTTPException(status_code=r.status_code, detail=r.text)
res = r.json()
auth_info = {}
auth_info.update(res)
auth_info.update({"refresh_token": refresh_token})
auth_info.update({"accessToken": res.get("access_token", "")})
return auth_info
except Exception as e:
logger.error(f"chatgpt_refresh: {e}")
return {}
finally:
await client.close()
@app.post("/auth/refresh")
async def refresh(request: Request):
auth_info = {}
form_data = await request.form()
auth_info.update(form_data)
access_token = auth_info.get("access_token", auth_info.get("accessToken", ""))
refresh_token = auth_info.get("refresh_token", "")
if not refresh_token and not access_token:
raise HTTPException(status_code=401, detail="refresh_token or access_token is required")
if access_token:
account_check_info = await chatgpt_account_check(access_token)
if account_check_info:
auth_info.update(account_check_info)
auth_info.update({"accessToken": access_token})
return Response(content=json.dumps(auth_info), media_type="application/json")
if refresh_token:
chatgpt_refresh_info = await chatgpt_refresh(refresh_token)
if chatgpt_refresh_info:
auth_info.update(chatgpt_refresh_info)
access_token = auth_info.get("accessToken", "")
account_check_info = await chatgpt_account_check(access_token)
if account_check_info:
auth_info.update(account_check_info)
auth_info.update({"accessToken": access_token})
return Response(content=json.dumps(auth_info), media_type="application/json")
raise HTTPException(status_code=401, detail="Unauthorized")
if no_sentinel:
@app.post("/backend-api/sentinel/chat-requirements")
async def sentinel_chat_conversations():
return {
"arkose": {
"dx": None,
"required": False
},
"persona": "chatgpt-paid",
"proofofwork": {
"difficulty": None,
"required": False,
"seed": None
},
"token": str(uuid.uuid4()),
"turnstile": {
"dx": None,
"required": False
}
}
@app.post("/backend-api/conversation")
async def chat_conversations(request: Request):
token = request.headers.get("Authorization", "").replace("Bearer ", "")
req_token = await get_real_req_token(token)
access_token = await verify_token(req_token)
fp = get_fp(req_token)
proxy_url = fp.get("proxy_url", None)
user_agent = fp.get("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0")
impersonate = fp.get("impersonate", "safari15_3")
host_url = random.choice(chatgpt_base_url_list) if chatgpt_base_url_list else "https://chatgpt.com"
proof_token = None
turnstile_token = None
headers = base_headers.copy()
headers.update(fp)
headers.update({"authorization": f"Bearer {access_token}"})
client = Client(proxy=proxy_url, impersonate=impersonate)
config = get_config(user_agent)
p = get_requirements_token(config)
data = {'p': p}
r = await client.post(f'{host_url}/backend-api/sentinel/chat-requirements', headers=headers, json=data,
timeout=10)
resp = r.json()
turnstile = resp.get('turnstile', {})
turnstile_required = turnstile.get('required')
if turnstile_required:
turnstile_dx = turnstile.get("dx")
try:
if turnstile_solver_url:
res = await client.post(turnstile_solver_url,
json={"url": "https://chatgpt.com", "p": p, "dx": turnstile_dx})
turnstile_token = res.json().get("t")
except Exception as e:
logger.info(f"Turnstile ignored: {e}")
proofofwork = resp.get('proofofwork', {})
proofofwork_required = proofofwork.get('required')
if proofofwork_required:
proofofwork_diff = proofofwork.get("difficulty")
proofofwork_seed = proofofwork.get("seed")
proof_token, solved = await run_in_threadpool(
get_answer_token, proofofwork_seed, proofofwork_diff, config
)
if not solved:
raise HTTPException(status_code=403, detail="Failed to solve proof of work")
chat_token = resp.get('token')
headers.update({
"openai-sentinel-chat-requirements-token": chat_token,
"openai-sentinel-proof-token": proof_token,
"openai-sentinel-turnstile-token": turnstile_token,
})
params = dict(request.query_params)
data = await request.body()
request_cookies = dict(request.cookies)
background = BackgroundTask(client.close)
r = await client.post_stream(f"{host_url}/backend-api/conversation", params=params, headers=headers,
cookies=request_cookies, data=data, stream=True, allow_redirects=False)
rheaders = r.headers
if x_sign:
rheaders.update({"x-sign": x_sign})
if 'stream' in rheaders.get("content-type", ""):
logger.info(f"Request token: {req_token}")
logger.info(f"Request proxy: {proxy_url}")
logger.info(f"Request UA: {user_agent}")
logger.info(f"Request impersonate: {impersonate}")
return StreamingResponse(content_generator(r, token), headers=rheaders,
media_type=rheaders.get("content-type"), background=background)
else:
return Response(content=(await r.atext()), headers=rheaders, media_type=rheaders.get("content-type"),
status_code=r.status_code, background=background)

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,10 @@
<div id="popup" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div class="bg-white p-8 rounded-xl shadow-lg max-w-lg w-full h-auto">
<h3 class="text-xl font-bold text-gray-800 mb-4 text-center">请输入您的 Token</h3>
<!-- <h3 class="text-xl font-bold text-gray-800 mb-4 text-center">请输入您的 Token</h3>-->
<p class="font-semibold text-gray-800 text-center mb-1">直接点击
<span class="font-bold text-indigo-600">开始</span>
进入最近用过的账号</p>
<label for="popup-input"></label>
<textarea
id="popup-input"
@ -71,13 +74,8 @@
function submitToken() {
var inputValue = document.getElementById('popup-input').value;
if (inputValue) {
window.location.href = '/?token=' + inputValue;
closePopup();
} else {
window.location.href = '/?token=' + Math.random().toString(36);
closePopup();
}
window.location.href = '/?token=' + inputValue;
closePopup();
}
</script>
</body>

4852
templates/remix_context.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -20,17 +20,19 @@ def is_true(x):
api_prefix = os.getenv('API_PREFIX', None)
auto_seed = os.getenv('AUTO_SEED', True)
authorization = os.getenv('AUTHORIZATION', '').replace(' ', '')
chatgpt_base_url = os.getenv('CHATGPT_BASE_URL', 'https://chatgpt.com').replace(' ', '')
auth_key = os.getenv('AUTH_KEY', None)
user_agents = os.getenv('USER_AGENTS', '[]')
random_token = is_true(os.getenv('RANDOM_TOKEN', True))
x_sign = os.getenv('X_SIGN', None)
ark0se_token_url = os.getenv('ARK' + 'OSE_TOKEN_URL', '').replace(' ', '')
if not ark0se_token_url:
ark0se_token_url = os.getenv('ARK0SE_TOKEN_URL', None)
proxy_url = os.getenv('PROXY_URL', '').replace(' ', '')
export_proxy_url = os.getenv('EXPORT_PROXY_URL', None)
impersonate_list_str = os.getenv('IMPERSONATE', '[]')
user_agents_list_str = os.getenv('USER_AGENTS', '[]')
cf_file_url = os.getenv('CF_FILE_URL', None)
turnstile_solver_url = os.getenv('TURNSTILE_SOLVER_URL', None)
@ -38,18 +40,22 @@ turnstile_solver_url = os.getenv('TURNSTILE_SOLVER_URL', None)
history_disabled = is_true(os.getenv('HISTORY_DISABLED', True))
pow_difficulty = os.getenv('POW_DIFFICULTY', '000032')
retry_times = int(os.getenv('RETRY_TIMES', 3))
enable_gateway = is_true(os.getenv('ENABLE_GATEWAY', False))
conversation_only = is_true(os.getenv('CONVERSATION_ONLY', False))
enable_limit = is_true(os.getenv('ENABLE_LIMIT', True))
upload_by_url = is_true(os.getenv('UPLOAD_BY_URL', False))
check_model = is_true(os.getenv('CHECK_MODEL', False))
scheduled_refresh = is_true(os.getenv('SCHEDULED_REFRESH', False))
random_token = is_true(os.getenv('RANDOM_TOKEN', True))
authorization_list = authorization.split(',') if authorization else []
chatgpt_base_url_list = chatgpt_base_url.split(',') if chatgpt_base_url else []
ark0se_token_url_list = ark0se_token_url.split(',') if ark0se_token_url else []
proxy_url_list = proxy_url.split(',') if proxy_url else []
user_agents_list = ast.literal_eval(user_agents)
impersonate_list = ast.literal_eval(impersonate_list_str)
user_agents_list = ast.literal_eval(user_agents_list_str)
enable_gateway = is_true(os.getenv('ENABLE_GATEWAY', False))
no_sentinel = is_true(os.getenv('NO_SENTINEL', False))
with open('version.txt') as f:
version = f.read().strip()
@ -66,6 +72,8 @@ logger.info("------------------------- Request --------------------------")
logger.info("CHATGPT_BASE_URL: " + str(chatgpt_base_url_list))
logger.info("PROXY_URL: " + str(proxy_url_list))
logger.info("EXPORT_PROXY_URL: " + str(export_proxy_url))
logger.info("IMPERSONATE: " + str(impersonate_list))
logger.info("USER_AGENTS: " + str(user_agents_list))
logger.info("---------------------- Functionality -----------------------")
logger.info("HISTORY_DISABLED: " + str(history_disabled))
logger.info("POW_DIFFICULTY: " + str(pow_difficulty))
@ -78,5 +86,5 @@ logger.info("SCHEDULED_REFRESH: " + str(scheduled_refresh))
logger.info("RANDOM_TOKEN: " + str(random_token))
logger.info("------------------------- Gateway --------------------------")
logger.info("ENABLE_GATEWAY: " + str(enable_gateway))
logger.info("AUTO_SEED: " + str(auto_seed))
logger.info("-" * 60)

107
utils/globals.py Normal file
View File

@ -0,0 +1,107 @@
import json
import os
import utils.configs as configs
from utils.Logger import logger
DATA_FOLDER = "data"
TOKENS_FILE = os.path.join(DATA_FOLDER, "token.txt")
REFRESH_MAP_FILE = os.path.join(DATA_FOLDER, "refresh_map.json")
ERROR_TOKENS_FILE = os.path.join(DATA_FOLDER, "error_token.txt")
WSS_MAP_FILE = os.path.join(DATA_FOLDER, "wss_map.json")
FP_FILE = os.path.join(DATA_FOLDER, "fp_map.json")
SEED_MAP_FILE = os.path.join(DATA_FOLDER, "seed_map.json")
CONVERSATION_MAP_FILE = os.path.join(DATA_FOLDER, "conversation_map.json")
count = 0
token_list = []
error_token_list = []
refresh_map = {}
wss_map = {}
user_agent_map = {}
seed_map = {}
conversation_map = {}
impersonate_list = [
"chrome99",
"chrome100",
"chrome101",
"chrome104",
"chrome107",
"chrome110",
"chrome116",
"chrome119",
"chrome120",
"chrome123",
"edge99",
"edge101",
] if not configs.impersonate_list else configs.impersonate_list
if not os.path.exists(DATA_FOLDER):
os.makedirs(DATA_FOLDER)
if os.path.exists(REFRESH_MAP_FILE):
with open(REFRESH_MAP_FILE, "r") as f:
try:
refresh_map = json.load(f)
except:
refresh_map = {}
else:
refresh_map = {}
if os.path.exists(WSS_MAP_FILE):
with open(WSS_MAP_FILE, "r") as f:
try:
wss_map = json.load(f)
except:
wss_map = {}
else:
wss_map = {}
if os.path.exists(FP_FILE):
with open(FP_FILE, "r", encoding="utf-8") as f:
try:
user_agent_map = json.load(f)
except:
user_agent_map = {}
else:
user_agent_map = {}
if os.path.exists(SEED_MAP_FILE):
with open(SEED_MAP_FILE, "r") as f:
try:
seed_map = json.load(f)
except:
seed_map = {}
else:
seed_map = {}
if os.path.exists(CONVERSATION_MAP_FILE):
with open(CONVERSATION_MAP_FILE, "r") as f:
try:
conversation_map = json.load(f)
except:
conversation_map = {}
else:
conversation_map = {}
if os.path.exists(TOKENS_FILE):
with open(TOKENS_FILE, "r", encoding="utf-8") as f:
for line in f:
if line.strip() and not line.startswith("#"):
token_list.append(line.strip())
else:
with open(TOKENS_FILE, "w", encoding="utf-8") as f:
pass
if os.path.exists(ERROR_TOKENS_FILE):
with open(ERROR_TOKENS_FILE, "r", encoding="utf-8") as f:
for line in f:
if line.strip() and not line.startswith("#"):
error_token_list.append(line.strip())
else:
with open(ERROR_TOKENS_FILE, "w", encoding="utf-8") as f:
pass
if token_list:
logger.info(f"Token list count: {len(token_list)}, Error token list count: {len(error_token_list)}")
logger.info("-" * 60)

View File

@ -1,7 +1,7 @@
from fastapi import HTTPException
from utils.Logger import logger
from utils.config import retry_times
from utils.configs import retry_times
async def async_retry(func, *args, max_retries=retry_times, **kwargs):

View File

@ -1 +1 @@
1.5.11
1.6.8-beta1