Upload 25 files
Browse files- Dockerfile +22 -22
- chat2api.py +106 -6
- chatgpt/ChatService.py +89 -58
- chatgpt/authorization.py +41 -10
- chatgpt/globals.py +55 -4
- chatgpt/refreshToken.py +8 -5
- chatgpt/reverseProxy.py +43 -52
- requirements.txt +2 -1
- templates/chatgpt.html +0 -0
- utils/Client.py +10 -13
- utils/config.py +4 -2
Dockerfile
CHANGED
|
@@ -1,23 +1,23 @@
|
|
| 1 |
-
# 选择精简版的Python镜像
|
| 2 |
-
FROM python:3.11-slim
|
| 3 |
-
|
| 4 |
-
# 设置工作目录
|
| 5 |
-
WORKDIR /app
|
| 6 |
-
|
| 7 |
-
# 复制应用代码到工作目录
|
| 8 |
-
COPY . /app
|
| 9 |
-
|
| 10 |
-
# 设置环境变量
|
| 11 |
-
ENV DEMO_SECRET=123
|
| 12 |
-
|
| 13 |
-
# 安装项目依赖
|
| 14 |
-
RUN pip install --no-cache-dir -r requirements.txt
|
| 15 |
-
|
| 16 |
-
# 创建应用程序所需的目录,并赋予必要权限
|
| 17 |
-
RUN mkdir -p /app/data && chmod -R 777 /app/data
|
| 18 |
-
|
| 19 |
-
# 暴露应用所使用的端口
|
| 20 |
-
EXPOSE 5005
|
| 21 |
-
|
| 22 |
-
# 启动应用
|
| 23 |
CMD ["python", "app.py"]
|
|
|
|
| 1 |
+
# 选择精简版的Python镜像
|
| 2 |
+
FROM python:3.11-slim
|
| 3 |
+
|
| 4 |
+
# 设置工作目录
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# 复制应用代码到工作目录
|
| 8 |
+
COPY . /app
|
| 9 |
+
|
| 10 |
+
# 设置环境变量
|
| 11 |
+
ENV DEMO_SECRET=123
|
| 12 |
+
|
| 13 |
+
# 安装项目依赖
|
| 14 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 15 |
+
|
| 16 |
+
# 创建应用程序所需的目录,并赋予必要权限
|
| 17 |
+
RUN mkdir -p /app/data && chmod -R 777 /app/data
|
| 18 |
+
|
| 19 |
+
# 暴露应用所使用的端口
|
| 20 |
+
EXPOSE 5005
|
| 21 |
+
|
| 22 |
+
# 启动应用
|
| 23 |
CMD ["python", "app.py"]
|
chat2api.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import asyncio
|
|
|
|
| 2 |
import types
|
| 3 |
import warnings
|
| 4 |
|
|
@@ -10,13 +11,14 @@ from fastapi.responses import StreamingResponse, JSONResponse
|
|
| 10 |
from fastapi.security import OAuth2PasswordBearer
|
| 11 |
from fastapi.templating import Jinja2Templates
|
| 12 |
from starlette.background import BackgroundTask
|
|
|
|
| 13 |
|
| 14 |
from chatgpt.ChatService import ChatService
|
| 15 |
-
from chatgpt.authorization import refresh_all_tokens
|
| 16 |
import chatgpt.globals as globals
|
| 17 |
from chatgpt.reverseProxy import chatgpt_reverse_proxy
|
| 18 |
from utils.Logger import logger
|
| 19 |
-
from utils.config import api_prefix, scheduled_refresh
|
| 20 |
from utils.retry import async_retry
|
| 21 |
|
| 22 |
warnings.filterwarnings("ignore")
|
|
@@ -38,7 +40,8 @@ app.add_middleware(
|
|
| 38 |
@app.on_event("startup")
|
| 39 |
async def app_start():
|
| 40 |
if scheduled_refresh:
|
| 41 |
-
scheduler.add_job(id='refresh', func=refresh_all_tokens, trigger='cron', hour=3, minute=0, day='*/4',
|
|
|
|
| 42 |
scheduler.start()
|
| 43 |
asyncio.get_event_loop().call_later(0, lambda: asyncio.create_task(refresh_all_tokens(force_refresh=False)))
|
| 44 |
|
|
@@ -128,6 +131,103 @@ async def error_tokens():
|
|
| 128 |
return {"status": "success", "error_tokens": error_tokens_list}
|
| 129 |
|
| 130 |
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import asyncio
|
| 2 |
+
import time
|
| 3 |
import types
|
| 4 |
import warnings
|
| 5 |
|
|
|
|
| 11 |
from fastapi.security import OAuth2PasswordBearer
|
| 12 |
from fastapi.templating import Jinja2Templates
|
| 13 |
from starlette.background import BackgroundTask
|
| 14 |
+
from starlette.responses import RedirectResponse, Response
|
| 15 |
|
| 16 |
from chatgpt.ChatService import ChatService
|
| 17 |
+
from chatgpt.authorization import refresh_all_tokens, verify_token, get_req_token
|
| 18 |
import chatgpt.globals as globals
|
| 19 |
from chatgpt.reverseProxy import chatgpt_reverse_proxy
|
| 20 |
from utils.Logger import logger
|
| 21 |
+
from utils.config import api_prefix, scheduled_refresh, enable_gateway
|
| 22 |
from utils.retry import async_retry
|
| 23 |
|
| 24 |
warnings.filterwarnings("ignore")
|
|
|
|
| 40 |
@app.on_event("startup")
|
| 41 |
async def app_start():
|
| 42 |
if scheduled_refresh:
|
| 43 |
+
scheduler.add_job(id='refresh', func=refresh_all_tokens, trigger='cron', hour=3, minute=0, day='*/4',
|
| 44 |
+
kwargs={'force_refresh': True})
|
| 45 |
scheduler.start()
|
| 46 |
asyncio.get_event_loop().call_later(0, lambda: asyncio.create_task(refresh_all_tokens(force_refresh=False)))
|
| 47 |
|
|
|
|
| 131 |
return {"status": "success", "error_tokens": error_tokens_list}
|
| 132 |
|
| 133 |
|
| 134 |
+
if enable_gateway:
|
| 135 |
+
@app.get("/", response_class=HTMLResponse)
|
| 136 |
+
async def chatgpt(request: Request):
|
| 137 |
+
seed_token = request.query_params.get("seed")
|
| 138 |
+
if not seed_token:
|
| 139 |
+
seed_token = request.cookies.get("seed_token")
|
| 140 |
+
if not seed_token:
|
| 141 |
+
seed_token = str(int(time.time()))
|
| 142 |
+
req_token = get_req_token(seed_token)
|
| 143 |
+
seed_token = await verify_token(req_token)
|
| 144 |
+
|
| 145 |
+
response = templates.TemplateResponse("chatgpt.html", {"request": request, "access_token": seed_token})
|
| 146 |
+
response.set_cookie("seed_token", value=seed_token)
|
| 147 |
+
return response
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
@app.get("/backend-api/gizmos/bootstrap")
|
| 151 |
+
async def get_gizmos_bootstrap():
|
| 152 |
+
return {"gizmos": []}
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
# @app.get("/backend-api/conversations")
|
| 156 |
+
# async def get_conversations():
|
| 157 |
+
# return {"items": [], "total": 0, "limit": 28, "offset": 0, "has_missing_conversations": False}
|
| 158 |
+
|
| 159 |
+
# @app.patch("/backend-api/conversations")
|
| 160 |
+
# async def get_conversations():
|
| 161 |
+
# return {"success": True, "message": None}
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
@app.get("/backend-api/me")
|
| 165 |
+
async def get_me():
|
| 166 |
+
created = int(time.time())
|
| 167 |
+
return {
|
| 168 |
+
"object": "user",
|
| 169 |
+
"id": "org-chatgpt",
|
| 170 |
+
"email": "chatgpt@openai.com",
|
| 171 |
+
"name": "ChatGPT",
|
| 172 |
+
"picture": "https://cdn.auth0.com/avatars/ai.png",
|
| 173 |
+
"created": created,
|
| 174 |
+
"phone_number": None,
|
| 175 |
+
"mfa_flag_enabled": False,
|
| 176 |
+
"amr": [],
|
| 177 |
+
"groups": [],
|
| 178 |
+
"orgs": {
|
| 179 |
+
"object": "list",
|
| 180 |
+
"data": [
|
| 181 |
+
{
|
| 182 |
+
"object": "organization",
|
| 183 |
+
"id": "org-chatgpt",
|
| 184 |
+
"created": 1715641300,
|
| 185 |
+
"title": "Personal",
|
| 186 |
+
"name": "user-chatgpt",
|
| 187 |
+
"description": "Personal org for chatgpt@openai.com",
|
| 188 |
+
"personal": True,
|
| 189 |
+
"settings": {},
|
| 190 |
+
"parent_org_id": None,
|
| 191 |
+
"is_default": False,
|
| 192 |
+
"role": "owner",
|
| 193 |
+
"is_scale_tier_authorized_purchaser": None,
|
| 194 |
+
"is_scim_managed": False,
|
| 195 |
+
"projects": {
|
| 196 |
+
"object": "list",
|
| 197 |
+
"data": []
|
| 198 |
+
},
|
| 199 |
+
"groups": [],
|
| 200 |
+
"geography": None
|
| 201 |
+
}
|
| 202 |
+
]
|
| 203 |
+
},
|
| 204 |
+
"has_payg_project_spend_limit": None
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
banned_paths = ["backend-api/accounts/logout_all", "backend-api/accounts/deactivate",
|
| 209 |
+
"backend-api/user_system_messages"]
|
| 210 |
+
redirect_paths = ["auth/logout"]
|
| 211 |
+
chatgpt_paths = ["c/"]
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"])
|
| 215 |
+
async def reverse_proxy(request: Request, path: str):
|
| 216 |
+
for chatgpt_path in chatgpt_paths:
|
| 217 |
+
if chatgpt_path in path:
|
| 218 |
+
return await chatgpt(request)
|
| 219 |
+
|
| 220 |
+
for banned_path in banned_paths:
|
| 221 |
+
if banned_path in path:
|
| 222 |
+
return Response(status_code=404)
|
| 223 |
+
|
| 224 |
+
for redirect_path in redirect_paths:
|
| 225 |
+
if redirect_path in path:
|
| 226 |
+
redirect_url = str(request.base_url)
|
| 227 |
+
return RedirectResponse(url=f"{redirect_url}?seed={int(time.time())}", status_code=302)
|
| 228 |
+
|
| 229 |
+
return await chatgpt_reverse_proxy(request, path)
|
| 230 |
+
else:
|
| 231 |
+
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"])
|
| 232 |
+
async def reverse_proxy():
|
| 233 |
+
raise HTTPException(status_code=404, detail="Gateway is disabled")
|
chatgpt/ChatService.py
CHANGED
|
@@ -8,27 +8,46 @@ from starlette.concurrency import run_in_threadpool
|
|
| 8 |
|
| 9 |
from api.files import get_image_size, get_file_extension, determine_file_use_case
|
| 10 |
from api.models import model_proxy
|
| 11 |
-
from chatgpt.authorization import get_req_token, verify_token
|
| 12 |
from chatgpt.chatFormat import api_messages_to_chat, stream_response, format_not_stream_response, head_process_response
|
| 13 |
from chatgpt.chatLimit import check_is_limit, handle_request_limit
|
| 14 |
from chatgpt.proofofWork import get_config, get_dpl, get_answer_token, get_requirements_token
|
| 15 |
|
| 16 |
from utils.Client import Client
|
| 17 |
from utils.Logger import logger
|
| 18 |
-
from utils.config import
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
|
| 22 |
class ChatService:
|
| 23 |
def __init__(self, origin_token=None):
|
| 24 |
-
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"
|
| 25 |
self.req_token = get_req_token(origin_token)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
self.chat_token = "gAAAAAB"
|
| 27 |
self.s = None
|
| 28 |
self.ws = None
|
| 29 |
|
| 30 |
async def set_dynamic_data(self, data):
|
| 31 |
if self.req_token:
|
|
|
|
|
|
|
| 32 |
logger.info(f"Request token: {self.req_token}")
|
| 33 |
req_len = len(self.req_token.split(","))
|
| 34 |
if req_len == 1:
|
|
@@ -64,7 +83,7 @@ class ChatService:
|
|
| 64 |
self.host_url = random.choice(chatgpt_base_url_list) if chatgpt_base_url_list else "https://chatgpt.com"
|
| 65 |
self.ark0se_token_url = random.choice(ark0se_token_url_list) if ark0se_token_url_list else None
|
| 66 |
|
| 67 |
-
self.s = Client(proxy=self.proxy_url)
|
| 68 |
|
| 69 |
self.oai_device_id = str(uuid.uuid4())
|
| 70 |
self.persona = None
|
|
@@ -85,13 +104,13 @@ class ChatService:
|
|
| 85 |
'Origin': self.host_url,
|
| 86 |
'Priority': 'u=1, i',
|
| 87 |
'Referer': f'{self.host_url}/',
|
| 88 |
-
'Sec-Ch-Ua': '"Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"',
|
| 89 |
-
'Sec-Ch-Ua-Mobile':
|
| 90 |
-
'Sec-Ch-Ua-Platform': '"Windows"',
|
| 91 |
'Sec-Fetch-Dest': 'empty',
|
| 92 |
'Sec-Fetch-Mode': 'cors',
|
| 93 |
'Sec-Fetch-Site': 'same-origin',
|
| 94 |
-
'User-Agent': self.user_agent
|
| 95 |
}
|
| 96 |
if self.access_token:
|
| 97 |
self.base_url = self.host_url + "/backend-api"
|
|
@@ -155,12 +174,15 @@ class ChatService:
|
|
| 155 |
models = r.json().get('models')
|
| 156 |
if not any(self.req_model in model.get("slug", "") for model in models):
|
| 157 |
logger.error(f"Model {self.req_model} not support.")
|
| 158 |
-
raise HTTPException(
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
| 164 |
else:
|
| 165 |
raise HTTPException(status_code=404, detail="Failed to get models")
|
| 166 |
else:
|
|
@@ -168,12 +190,15 @@ class ChatService:
|
|
| 168 |
if self.persona != "chatgpt-paid":
|
| 169 |
if self.req_model == "gpt-4":
|
| 170 |
logger.error(f"Model {self.resp_model} not support for {self.persona}")
|
| 171 |
-
raise HTTPException(
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
|
|
|
|
|
|
|
|
|
| 177 |
|
| 178 |
turnstile = resp.get('turnstile', {})
|
| 179 |
turnstile_required = turnstile.get('required')
|
|
@@ -181,7 +206,9 @@ class ChatService:
|
|
| 181 |
turnstile_dx = turnstile.get("dx")
|
| 182 |
try:
|
| 183 |
if turnstile_solver_url:
|
| 184 |
-
res = await self.s.post(
|
|
|
|
|
|
|
| 185 |
self.turnstile_token = res.json().get("t")
|
| 186 |
except Exception as e:
|
| 187 |
logger.info(f"Turnstile ignored: {e}")
|
|
@@ -197,12 +224,10 @@ class ChatService:
|
|
| 197 |
if not self.ark0se_token_url:
|
| 198 |
raise HTTPException(status_code=403, detail="Ark0se service required")
|
| 199 |
ark0se_dx = ark0se.get("dx")
|
| 200 |
-
ark0se_client = Client()
|
| 201 |
try:
|
| 202 |
r2 = await ark0se_client.post(
|
| 203 |
-
url=self.ark0se_token_url,
|
| 204 |
-
json={"blob": ark0se_dx, "method": ark0se_method},
|
| 205 |
-
timeout=15
|
| 206 |
)
|
| 207 |
r2esp = r2.json()
|
| 208 |
logger.info(f"ark0se_token: {r2esp}")
|
|
@@ -220,11 +245,11 @@ class ChatService:
|
|
| 220 |
if proofofwork_required:
|
| 221 |
proofofwork_diff = proofofwork.get("difficulty")
|
| 222 |
if proofofwork_diff <= pow_difficulty:
|
| 223 |
-
raise HTTPException(status_code=403,
|
| 224 |
-
detail=f"Proof of work difficulty too high: {proofofwork_diff}")
|
| 225 |
proofofwork_seed = proofofwork.get("seed")
|
| 226 |
-
self.proof_token, solved = await run_in_threadpool(
|
| 227 |
-
|
|
|
|
| 228 |
if not solved:
|
| 229 |
raise HTTPException(status_code=403, detail="Failed to solve proof of work")
|
| 230 |
|
|
@@ -254,11 +279,13 @@ class ChatService:
|
|
| 254 |
logger.error(f"Failed to format messages: {str(e)}")
|
| 255 |
raise HTTPException(status_code=400, detail="Failed to format messages.")
|
| 256 |
self.chat_headers = self.base_headers.copy()
|
| 257 |
-
self.chat_headers.update(
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
|
|
|
|
|
|
| 262 |
if self.ark0se_token:
|
| 263 |
self.chat_headers['Openai-Sentinel-Ark' + 'ose-Token'] = self.ark0se_token
|
| 264 |
|
|
@@ -294,7 +321,7 @@ class ChatService:
|
|
| 294 |
"suggestions": [],
|
| 295 |
"timezone_offset_min": -480,
|
| 296 |
"variant_purpose": "comparison_implicit",
|
| 297 |
-
"websocket_request_id": f"{uuid.uuid4()}"
|
| 298 |
}
|
| 299 |
if self.conversation_id:
|
| 300 |
self.chat_request['conversation_id'] = self.conversation_id
|
|
@@ -304,8 +331,7 @@ class ChatService:
|
|
| 304 |
try:
|
| 305 |
url = f'{self.base_url}/conversation'
|
| 306 |
stream = self.data.get("stream", False)
|
| 307 |
-
r = await self.s.post_stream(url, headers=self.chat_headers, json=self.chat_request, timeout=10,
|
| 308 |
-
stream=True)
|
| 309 |
if r.status_code != 200:
|
| 310 |
rtext = await r.atext()
|
| 311 |
if "application/json" == r.headers.get("Content-Type", ""):
|
|
@@ -327,13 +353,19 @@ class ChatService:
|
|
| 327 |
if "text/event-stream" in content_type:
|
| 328 |
res, start = await head_process_response(r.aiter_lines())
|
| 329 |
if not start:
|
| 330 |
-
raise HTTPException(
|
|
|
|
|
|
|
|
|
|
| 331 |
if stream:
|
| 332 |
return stream_response(self, res, self.resp_model, self.max_tokens)
|
| 333 |
else:
|
| 334 |
return await format_not_stream_response(
|
| 335 |
-
stream_response(self, res, self.resp_model, self.max_tokens),
|
| 336 |
-
self.
|
|
|
|
|
|
|
|
|
|
| 337 |
elif "application/json" in content_type:
|
| 338 |
rtext = await r.atext()
|
| 339 |
resp = json.loads(rtext)
|
|
@@ -376,12 +408,12 @@ class ChatService:
|
|
| 376 |
url = f'{self.base_url}/files'
|
| 377 |
headers = self.base_headers.copy()
|
| 378 |
try:
|
| 379 |
-
r = await self.s.post(
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
"timezone_offset_min": -480,
|
| 383 |
-
|
| 384 |
-
|
| 385 |
if r.status_code == 200:
|
| 386 |
res = r.json()
|
| 387 |
file_id = res.get('file_id')
|
|
@@ -395,12 +427,14 @@ class ChatService:
|
|
| 395 |
|
| 396 |
async def upload(self, upload_url, file_content, mime_type):
|
| 397 |
headers = self.base_headers.copy()
|
| 398 |
-
headers.update(
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
|
|
|
|
|
|
| 404 |
headers.pop('Authorization', None)
|
| 405 |
try:
|
| 406 |
r = await self.s.put(upload_url, headers=headers, data=file_content)
|
|
@@ -438,7 +472,7 @@ class ChatService:
|
|
| 438 |
"mime_type": mime_type,
|
| 439 |
"width": width,
|
| 440 |
"height": height,
|
| 441 |
-
"use_case": use_case
|
| 442 |
}
|
| 443 |
logger.info(f"File_meta: {file_meta}")
|
| 444 |
return file_meta
|
|
@@ -468,10 +502,7 @@ class ChatService:
|
|
| 468 |
async def get_response_file_url(self, conversation_id, message_id, sandbox_path):
|
| 469 |
try:
|
| 470 |
url = f"{self.base_url}/conversation/{conversation_id}/interpreter/download"
|
| 471 |
-
params = {
|
| 472 |
-
"message_id": message_id,
|
| 473 |
-
"sandbox_path": sandbox_path
|
| 474 |
-
}
|
| 475 |
headers = self.base_headers.copy()
|
| 476 |
r = await self.s.get(url, headers=headers, params=params, timeout=10)
|
| 477 |
if r.status_code == 200:
|
|
|
|
| 8 |
|
| 9 |
from api.files import get_image_size, get_file_extension, determine_file_use_case
|
| 10 |
from api.models import model_proxy
|
| 11 |
+
from chatgpt.authorization import get_req_token, verify_token, get_ua
|
| 12 |
from chatgpt.chatFormat import api_messages_to_chat, stream_response, format_not_stream_response, head_process_response
|
| 13 |
from chatgpt.chatLimit import check_is_limit, handle_request_limit
|
| 14 |
from chatgpt.proofofWork import get_config, get_dpl, get_answer_token, get_requirements_token
|
| 15 |
|
| 16 |
from utils.Client import Client
|
| 17 |
from utils.Logger import logger
|
| 18 |
+
from utils.config import (
|
| 19 |
+
proxy_url_list,
|
| 20 |
+
chatgpt_base_url_list,
|
| 21 |
+
ark0se_token_url_list,
|
| 22 |
+
history_disabled,
|
| 23 |
+
pow_difficulty,
|
| 24 |
+
conversation_only,
|
| 25 |
+
enable_limit,
|
| 26 |
+
upload_by_url,
|
| 27 |
+
check_model,
|
| 28 |
+
auth_key,
|
| 29 |
+
user_agents_list,
|
| 30 |
+
turnstile_solver_url,
|
| 31 |
+
)
|
| 32 |
|
| 33 |
|
| 34 |
class ChatService:
|
| 35 |
def __init__(self, origin_token=None):
|
| 36 |
+
# 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"
|
| 37 |
self.req_token = get_req_token(origin_token)
|
| 38 |
+
self.ua = get_ua(self.req_token)
|
| 39 |
+
self.user_agent = self.ua.get(
|
| 40 |
+
"User-Agent",
|
| 41 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
|
| 42 |
+
)
|
| 43 |
self.chat_token = "gAAAAAB"
|
| 44 |
self.s = None
|
| 45 |
self.ws = None
|
| 46 |
|
| 47 |
async def set_dynamic_data(self, data):
|
| 48 |
if self.req_token:
|
| 49 |
+
logger.info(f"Request impersonate: {self.ua.get('impersonate')}")
|
| 50 |
+
logger.info(f"Request ua:{self.user_agent}")
|
| 51 |
logger.info(f"Request token: {self.req_token}")
|
| 52 |
req_len = len(self.req_token.split(","))
|
| 53 |
if req_len == 1:
|
|
|
|
| 83 |
self.host_url = random.choice(chatgpt_base_url_list) if chatgpt_base_url_list else "https://chatgpt.com"
|
| 84 |
self.ark0se_token_url = random.choice(ark0se_token_url_list) if ark0se_token_url_list else None
|
| 85 |
|
| 86 |
+
self.s = Client(proxy=self.proxy_url, impersonate=self.ua.get("impersonate", "safari15_3"))
|
| 87 |
|
| 88 |
self.oai_device_id = str(uuid.uuid4())
|
| 89 |
self.persona = None
|
|
|
|
| 104 |
'Origin': self.host_url,
|
| 105 |
'Priority': 'u=1, i',
|
| 106 |
'Referer': f'{self.host_url}/',
|
| 107 |
+
'Sec-Ch-Ua': self.ua.get("Sec-Ch-Ua", '"Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"'),
|
| 108 |
+
'Sec-Ch-Ua-Mobile': self.ua.get("Sec-Ch-Ua-Mobile", "?0"),
|
| 109 |
+
'Sec-Ch-Ua-Platform': self.ua.get("Sec-Ch-Ua-Platform", '"Windows"'),
|
| 110 |
'Sec-Fetch-Dest': 'empty',
|
| 111 |
'Sec-Fetch-Mode': 'cors',
|
| 112 |
'Sec-Fetch-Site': 'same-origin',
|
| 113 |
+
'User-Agent': self.user_agent,
|
| 114 |
}
|
| 115 |
if self.access_token:
|
| 116 |
self.base_url = self.host_url + "/backend-api"
|
|
|
|
| 174 |
models = r.json().get('models')
|
| 175 |
if not any(self.req_model in model.get("slug", "") for model in models):
|
| 176 |
logger.error(f"Model {self.req_model} not support.")
|
| 177 |
+
raise HTTPException(
|
| 178 |
+
status_code=404,
|
| 179 |
+
detail={
|
| 180 |
+
"message": f"The model `{self.origin_model}` does not exist or you do not have access to it.",
|
| 181 |
+
"type": "invalid_request_error",
|
| 182 |
+
"param": None,
|
| 183 |
+
"code": "model_not_found",
|
| 184 |
+
},
|
| 185 |
+
)
|
| 186 |
else:
|
| 187 |
raise HTTPException(status_code=404, detail="Failed to get models")
|
| 188 |
else:
|
|
|
|
| 190 |
if self.persona != "chatgpt-paid":
|
| 191 |
if self.req_model == "gpt-4":
|
| 192 |
logger.error(f"Model {self.resp_model} not support for {self.persona}")
|
| 193 |
+
raise HTTPException(
|
| 194 |
+
status_code=404,
|
| 195 |
+
detail={
|
| 196 |
+
"message": f"The model `{self.origin_model}` does not exist or you do not have access to it.",
|
| 197 |
+
"type": "invalid_request_error",
|
| 198 |
+
"param": None,
|
| 199 |
+
"code": "model_not_found",
|
| 200 |
+
},
|
| 201 |
+
)
|
| 202 |
|
| 203 |
turnstile = resp.get('turnstile', {})
|
| 204 |
turnstile_required = turnstile.get('required')
|
|
|
|
| 206 |
turnstile_dx = turnstile.get("dx")
|
| 207 |
try:
|
| 208 |
if turnstile_solver_url:
|
| 209 |
+
res = await self.s.post(
|
| 210 |
+
turnstile_solver_url, json={"url": "https://chatgpt.com", "p": p, "dx": turnstile_dx}
|
| 211 |
+
)
|
| 212 |
self.turnstile_token = res.json().get("t")
|
| 213 |
except Exception as e:
|
| 214 |
logger.info(f"Turnstile ignored: {e}")
|
|
|
|
| 224 |
if not self.ark0se_token_url:
|
| 225 |
raise HTTPException(status_code=403, detail="Ark0se service required")
|
| 226 |
ark0se_dx = ark0se.get("dx")
|
| 227 |
+
ark0se_client = Client(impersonate=self.ua.get("impersonate", "safari15_3"))
|
| 228 |
try:
|
| 229 |
r2 = await ark0se_client.post(
|
| 230 |
+
url=self.ark0se_token_url, json={"blob": ark0se_dx, "method": ark0se_method}, timeout=15
|
|
|
|
|
|
|
| 231 |
)
|
| 232 |
r2esp = r2.json()
|
| 233 |
logger.info(f"ark0se_token: {r2esp}")
|
|
|
|
| 245 |
if proofofwork_required:
|
| 246 |
proofofwork_diff = proofofwork.get("difficulty")
|
| 247 |
if proofofwork_diff <= pow_difficulty:
|
| 248 |
+
raise HTTPException(status_code=403, detail=f"Proof of work difficulty too high: {proofofwork_diff}")
|
|
|
|
| 249 |
proofofwork_seed = proofofwork.get("seed")
|
| 250 |
+
self.proof_token, solved = await run_in_threadpool(
|
| 251 |
+
get_answer_token, proofofwork_seed, proofofwork_diff, config
|
| 252 |
+
)
|
| 253 |
if not solved:
|
| 254 |
raise HTTPException(status_code=403, detail="Failed to solve proof of work")
|
| 255 |
|
|
|
|
| 279 |
logger.error(f"Failed to format messages: {str(e)}")
|
| 280 |
raise HTTPException(status_code=400, detail="Failed to format messages.")
|
| 281 |
self.chat_headers = self.base_headers.copy()
|
| 282 |
+
self.chat_headers.update(
|
| 283 |
+
{
|
| 284 |
+
'Accept': 'text/event-stream',
|
| 285 |
+
'Openai-Sentinel-Chat-Requirements-Token': self.chat_token,
|
| 286 |
+
'Openai-Sentinel-Proof-Token': self.proof_token,
|
| 287 |
+
}
|
| 288 |
+
)
|
| 289 |
if self.ark0se_token:
|
| 290 |
self.chat_headers['Openai-Sentinel-Ark' + 'ose-Token'] = self.ark0se_token
|
| 291 |
|
|
|
|
| 321 |
"suggestions": [],
|
| 322 |
"timezone_offset_min": -480,
|
| 323 |
"variant_purpose": "comparison_implicit",
|
| 324 |
+
"websocket_request_id": f"{uuid.uuid4()}",
|
| 325 |
}
|
| 326 |
if self.conversation_id:
|
| 327 |
self.chat_request['conversation_id'] = self.conversation_id
|
|
|
|
| 331 |
try:
|
| 332 |
url = f'{self.base_url}/conversation'
|
| 333 |
stream = self.data.get("stream", False)
|
| 334 |
+
r = await self.s.post_stream(url, headers=self.chat_headers, json=self.chat_request, timeout=10, stream=True)
|
|
|
|
| 335 |
if r.status_code != 200:
|
| 336 |
rtext = await r.atext()
|
| 337 |
if "application/json" == r.headers.get("Content-Type", ""):
|
|
|
|
| 353 |
if "text/event-stream" in content_type:
|
| 354 |
res, start = await head_process_response(r.aiter_lines())
|
| 355 |
if not start:
|
| 356 |
+
raise HTTPException(
|
| 357 |
+
status_code=403,
|
| 358 |
+
detail="Our systems have detected unusual activity coming from your system. Please try again later.",
|
| 359 |
+
)
|
| 360 |
if stream:
|
| 361 |
return stream_response(self, res, self.resp_model, self.max_tokens)
|
| 362 |
else:
|
| 363 |
return await format_not_stream_response(
|
| 364 |
+
stream_response(self, res, self.resp_model, self.max_tokens),
|
| 365 |
+
self.prompt_tokens,
|
| 366 |
+
self.max_tokens,
|
| 367 |
+
self.resp_model,
|
| 368 |
+
)
|
| 369 |
elif "application/json" in content_type:
|
| 370 |
rtext = await r.atext()
|
| 371 |
resp = json.loads(rtext)
|
|
|
|
| 408 |
url = f'{self.base_url}/files'
|
| 409 |
headers = self.base_headers.copy()
|
| 410 |
try:
|
| 411 |
+
r = await self.s.post(
|
| 412 |
+
url,
|
| 413 |
+
headers=headers,
|
| 414 |
+
json={"file_name": file_name, "file_size": file_size, "timezone_offset_min": -480, "use_case": use_case},
|
| 415 |
+
timeout=5,
|
| 416 |
+
)
|
| 417 |
if r.status_code == 200:
|
| 418 |
res = r.json()
|
| 419 |
file_id = res.get('file_id')
|
|
|
|
| 427 |
|
| 428 |
async def upload(self, upload_url, file_content, mime_type):
|
| 429 |
headers = self.base_headers.copy()
|
| 430 |
+
headers.update(
|
| 431 |
+
{
|
| 432 |
+
'Accept': 'application/json, text/plain, */*',
|
| 433 |
+
'Content-Type': mime_type,
|
| 434 |
+
'X-Ms-Blob-Type': 'BlockBlob',
|
| 435 |
+
'X-Ms-Version': '2020-04-08',
|
| 436 |
+
}
|
| 437 |
+
)
|
| 438 |
headers.pop('Authorization', None)
|
| 439 |
try:
|
| 440 |
r = await self.s.put(upload_url, headers=headers, data=file_content)
|
|
|
|
| 472 |
"mime_type": mime_type,
|
| 473 |
"width": width,
|
| 474 |
"height": height,
|
| 475 |
+
"use_case": use_case,
|
| 476 |
}
|
| 477 |
logger.info(f"File_meta: {file_meta}")
|
| 478 |
return file_meta
|
|
|
|
| 502 |
async def get_response_file_url(self, conversation_id, message_id, sandbox_path):
|
| 503 |
try:
|
| 504 |
url = f"{self.base_url}/conversation/{conversation_id}/interpreter/download"
|
| 505 |
+
params = {"message_id": message_id, "sandbox_path": sandbox_path}
|
|
|
|
|
|
|
|
|
|
| 506 |
headers = self.base_headers.copy()
|
| 507 |
r = await self.s.get(url, headers=headers, params=params, timeout=10)
|
| 508 |
if r.status_code == 200:
|
chatgpt/authorization.py
CHANGED
|
@@ -1,28 +1,59 @@
|
|
| 1 |
import asyncio
|
|
|
|
|
|
|
| 2 |
|
|
|
|
| 3 |
from fastapi import HTTPException
|
| 4 |
|
|
|
|
| 5 |
from chatgpt.refreshToken import rt2ac
|
| 6 |
from utils.Logger import logger
|
| 7 |
-
from utils.config import authorization_list
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
-
def get_req_token(req_token):
|
| 12 |
if req_token in authorization_list:
|
| 13 |
-
if len(
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
| 17 |
globals.count += 1
|
| 18 |
-
globals.count %=
|
| 19 |
-
|
| 20 |
else:
|
| 21 |
return None
|
| 22 |
else:
|
| 23 |
return req_token
|
| 24 |
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
async def verify_token(req_token):
|
| 27 |
if not req_token:
|
| 28 |
if authorization_list:
|
|
@@ -45,7 +76,7 @@ async def verify_token(req_token):
|
|
| 45 |
|
| 46 |
|
| 47 |
async def refresh_all_tokens(force_refresh=False):
|
| 48 |
-
for token in globals.token_list:
|
| 49 |
if len(token) == 45:
|
| 50 |
try:
|
| 51 |
await asyncio.sleep(2)
|
|
|
|
| 1 |
import asyncio
|
| 2 |
+
import os
|
| 3 |
+
import random
|
| 4 |
|
| 5 |
+
import ua_generator
|
| 6 |
from fastapi import HTTPException
|
| 7 |
|
| 8 |
+
import chatgpt.globals as globals
|
| 9 |
from chatgpt.refreshToken import rt2ac
|
| 10 |
from utils.Logger import logger
|
| 11 |
+
from utils.config import authorization_list, random_token
|
| 12 |
+
|
| 13 |
+
os.environ['PYTHONHASHSEED'] = '0'
|
| 14 |
+
random.seed(0)
|
| 15 |
+
|
| 16 |
|
| 17 |
+
def get_req_token(req_token, seed=None):
|
| 18 |
+
available_token_list = list(set(globals.token_list) - set(globals.error_token_list))
|
| 19 |
+
length = len(available_token_list)
|
| 20 |
+
if seed and length > 0:
|
| 21 |
+
req_token = globals.token_list[hash(seed) % length]
|
| 22 |
+
while req_token in globals.error_token_list:
|
| 23 |
+
req_token = random.choice(globals.token_list)
|
| 24 |
+
return req_token
|
| 25 |
|
|
|
|
| 26 |
if req_token in authorization_list:
|
| 27 |
+
if len(available_token_list) > 0:
|
| 28 |
+
if random_token:
|
| 29 |
+
req_token = random.choice(available_token_list)
|
| 30 |
+
return req_token
|
| 31 |
+
else:
|
| 32 |
globals.count += 1
|
| 33 |
+
globals.count %= length
|
| 34 |
+
return available_token_list[globals.count]
|
| 35 |
else:
|
| 36 |
return None
|
| 37 |
else:
|
| 38 |
return req_token
|
| 39 |
|
| 40 |
|
| 41 |
+
def get_ua(req_token):
|
| 42 |
+
user_agent = globals.user_agent_map.get(req_token, "")
|
| 43 |
+
# token为空,免登录用户,则随机生成ua
|
| 44 |
+
if not user_agent:
|
| 45 |
+
ua = ua_generator.generate(device='desktop', browser=('chrome', 'edge'), platform=('windows', 'macos'))
|
| 46 |
+
return {
|
| 47 |
+
"User-Agent": ua.text,
|
| 48 |
+
"Sec-Ch-Ua-Platform": ua.platform,
|
| 49 |
+
"Sec-Ch-Ua": ua.ch.brands,
|
| 50 |
+
"Sec-Ch-Ua-Mobile": ua.ch.mobile,
|
| 51 |
+
"impersonate": random.choice(globals.impersonate_list),
|
| 52 |
+
}
|
| 53 |
+
else:
|
| 54 |
+
return user_agent
|
| 55 |
+
|
| 56 |
+
|
| 57 |
async def verify_token(req_token):
|
| 58 |
if not req_token:
|
| 59 |
if authorization_list:
|
|
|
|
| 76 |
|
| 77 |
|
| 78 |
async def refresh_all_tokens(force_refresh=False):
|
| 79 |
+
for token in list(set(globals.token_list) - set(globals.error_token_list)):
|
| 80 |
if len(token) == 45:
|
| 81 |
try:
|
| 82 |
await asyncio.sleep(2)
|
chatgpt/globals.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
| 1 |
import json
|
| 2 |
import os
|
| 3 |
|
|
|
|
|
|
|
|
|
|
| 4 |
from utils.Logger import logger
|
| 5 |
|
| 6 |
DATA_FOLDER = "data"
|
|
@@ -8,13 +11,28 @@ TOKENS_FILE = os.path.join(DATA_FOLDER, "token.txt")
|
|
| 8 |
REFRESH_MAP_FILE = os.path.join(DATA_FOLDER, "refresh_map.json")
|
| 9 |
ERROR_TOKENS_FILE = os.path.join(DATA_FOLDER, "error_token.txt")
|
| 10 |
WSS_MAP_FILE = os.path.join(DATA_FOLDER, "wss_map.json")
|
|
|
|
| 11 |
|
| 12 |
count = 0
|
| 13 |
token_list = []
|
| 14 |
error_token_list = []
|
| 15 |
refresh_map = {}
|
| 16 |
wss_map = {}
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
if not os.path.exists(DATA_FOLDER):
|
| 20 |
os.makedirs(DATA_FOLDER)
|
|
@@ -30,8 +48,8 @@ if os.path.exists(WSS_MAP_FILE):
|
|
| 30 |
wss_map = json.load(file)
|
| 31 |
else:
|
| 32 |
wss_map = {}
|
| 33 |
-
|
| 34 |
-
|
| 35 |
if os.path.exists(TOKENS_FILE):
|
| 36 |
with open(TOKENS_FILE, "r", encoding="utf-8") as f:
|
| 37 |
for line in f:
|
|
@@ -41,6 +59,7 @@ else:
|
|
| 41 |
with open(TOKENS_FILE, "w", encoding="utf-8") as f:
|
| 42 |
pass
|
| 43 |
|
|
|
|
| 44 |
if os.path.exists(ERROR_TOKENS_FILE):
|
| 45 |
with open(ERROR_TOKENS_FILE, "r", encoding="utf-8") as f:
|
| 46 |
for line in f:
|
|
@@ -50,5 +69,37 @@ else:
|
|
| 50 |
with open(ERROR_TOKENS_FILE, "w", encoding="utf-8") as f:
|
| 51 |
pass
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
if token_list:
|
| 54 |
-
logger.info(f"Token list count: {len(token_list)}, Error token list count: {len(error_token_list)}")
|
|
|
|
| 1 |
import json
|
| 2 |
import os
|
| 3 |
|
| 4 |
+
import ua_generator
|
| 5 |
+
import random
|
| 6 |
+
|
| 7 |
from utils.Logger import logger
|
| 8 |
|
| 9 |
DATA_FOLDER = "data"
|
|
|
|
| 11 |
REFRESH_MAP_FILE = os.path.join(DATA_FOLDER, "refresh_map.json")
|
| 12 |
ERROR_TOKENS_FILE = os.path.join(DATA_FOLDER, "error_token.txt")
|
| 13 |
WSS_MAP_FILE = os.path.join(DATA_FOLDER, "wss_map.json")
|
| 14 |
+
USER_AGENTS_FILE = os.path.join(DATA_FOLDER, "user_agents.json")
|
| 15 |
|
| 16 |
count = 0
|
| 17 |
token_list = []
|
| 18 |
error_token_list = []
|
| 19 |
refresh_map = {}
|
| 20 |
wss_map = {}
|
| 21 |
+
user_agent_map = {}
|
| 22 |
+
impersonate_list = [
|
| 23 |
+
"chrome99",
|
| 24 |
+
"chrome100",
|
| 25 |
+
"chrome101",
|
| 26 |
+
"chrome104",
|
| 27 |
+
"chrome107",
|
| 28 |
+
"chrome110",
|
| 29 |
+
"chrome116",
|
| 30 |
+
"chrome119",
|
| 31 |
+
"chrome120",
|
| 32 |
+
"chrome123",
|
| 33 |
+
"edge99",
|
| 34 |
+
"edge101",
|
| 35 |
+
]
|
| 36 |
|
| 37 |
if not os.path.exists(DATA_FOLDER):
|
| 38 |
os.makedirs(DATA_FOLDER)
|
|
|
|
| 48 |
wss_map = json.load(file)
|
| 49 |
else:
|
| 50 |
wss_map = {}
|
| 51 |
+
|
| 52 |
+
|
| 53 |
if os.path.exists(TOKENS_FILE):
|
| 54 |
with open(TOKENS_FILE, "r", encoding="utf-8") as f:
|
| 55 |
for line in f:
|
|
|
|
| 59 |
with open(TOKENS_FILE, "w", encoding="utf-8") as f:
|
| 60 |
pass
|
| 61 |
|
| 62 |
+
|
| 63 |
if os.path.exists(ERROR_TOKENS_FILE):
|
| 64 |
with open(ERROR_TOKENS_FILE, "r", encoding="utf-8") as f:
|
| 65 |
for line in f:
|
|
|
|
| 69 |
with open(ERROR_TOKENS_FILE, "w", encoding="utf-8") as f:
|
| 70 |
pass
|
| 71 |
|
| 72 |
+
if os.path.exists(USER_AGENTS_FILE):
|
| 73 |
+
with open(USER_AGENTS_FILE, "r", encoding="utf-8") as f:
|
| 74 |
+
user_agent_map = json.load(f)
|
| 75 |
+
# token数量变化时,更新ua
|
| 76 |
+
if len(user_agent_map.keys()) != len(token_list):
|
| 77 |
+
new_tokens = list(set(token_list) - user_agent_map.keys())
|
| 78 |
+
for token in new_tokens:
|
| 79 |
+
ua = ua_generator.generate(device='desktop', browser=('chrome', 'edge'), platform=('windows', 'macos'))
|
| 80 |
+
ua_dict = {
|
| 81 |
+
"User-Agent": ua.text,
|
| 82 |
+
"Sec-Ch-Ua-Platform": ua.platform,
|
| 83 |
+
"Sec-Ch-Ua": ua.ch.brands,
|
| 84 |
+
"Sec-Ch-Ua-Mobile": ua.ch.mobile,
|
| 85 |
+
"impersonate": random.choice(impersonate_list),
|
| 86 |
+
}
|
| 87 |
+
user_agent_map[token] = ua_dict
|
| 88 |
+
with open(USER_AGENTS_FILE, "w", encoding="utf-8") as f:
|
| 89 |
+
f.write(json.dumps(user_agent_map, indent=4))
|
| 90 |
+
else:
|
| 91 |
+
for token in token_list:
|
| 92 |
+
ua = ua_generator.generate(device='desktop', browser=('chrome', 'edge'), platform=('windows', 'macos'))
|
| 93 |
+
ua_dict = {
|
| 94 |
+
"User-Agent": ua.text,
|
| 95 |
+
"Sec-Ch-Ua-Platform": ua.platform,
|
| 96 |
+
"Sec-Ch-Ua": ua.ch.brands,
|
| 97 |
+
"Sec-Ch-Ua-Mobile": ua.ch.mobile,
|
| 98 |
+
"impersonate": random.choice(impersonate_list),
|
| 99 |
+
}
|
| 100 |
+
user_agent_map[token] = ua_dict
|
| 101 |
+
with open(USER_AGENTS_FILE, "w", encoding="utf-8") as f:
|
| 102 |
+
f.write(json.dumps(user_agent_map, indent=4))
|
| 103 |
+
|
| 104 |
if token_list:
|
| 105 |
+
logger.info(f"Token list count: {len(token_list)}, Error token list count: {len(error_token_list)}")
|
chatgpt/refreshToken.py
CHANGED
|
@@ -45,11 +45,14 @@ async def chat_refresh(refresh_token):
|
|
| 45 |
access_token = r.json()['access_token']
|
| 46 |
return access_token
|
| 47 |
else:
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
| 53 |
except Exception as e:
|
| 54 |
logger.error(f"Failed to refresh access_token `{refresh_token}`: {str(e)}")
|
| 55 |
raise HTTPException(status_code=500, detail=f"Failed to refresh access_token.")
|
|
|
|
| 45 |
access_token = r.json()['access_token']
|
| 46 |
return access_token
|
| 47 |
else:
|
| 48 |
+
if "invalid_grant" in r.text or "access_denied" in r.text:
|
| 49 |
+
if refresh_token not in globals.error_token_list:
|
| 50 |
+
globals.error_token_list.append(refresh_token)
|
| 51 |
+
with open(globals.ERROR_TOKENS_FILE, "a", encoding="utf-8") as f:
|
| 52 |
+
f.write(refresh_token + "\n")
|
| 53 |
+
raise Exception(r.text)
|
| 54 |
+
else:
|
| 55 |
+
raise Exception(r.text[:300])
|
| 56 |
except Exception as e:
|
| 57 |
logger.error(f"Failed to refresh access_token `{refresh_token}`: {str(e)}")
|
| 58 |
raise HTTPException(status_code=500, detail=f"Failed to refresh access_token.")
|
chatgpt/reverseProxy.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
|
|
| 1 |
import random
|
| 2 |
|
| 3 |
from fastapi import Request, HTTPException
|
| 4 |
from fastapi.responses import StreamingResponse, Response
|
| 5 |
from starlette.background import BackgroundTask
|
| 6 |
|
|
|
|
| 7 |
from utils.Client import Client
|
| 8 |
from utils.config import chatgpt_base_url_list, proxy_url_list, enable_gateway
|
| 9 |
|
|
@@ -60,105 +62,94 @@ headers_reject_list = [
|
|
| 60 |
|
| 61 |
|
| 62 |
async def chatgpt_reverse_proxy(request: Request, path: str):
|
| 63 |
-
if not enable_gateway:
|
| 64 |
-
raise HTTPException(status_code=404, detail="Gateway is disabled")
|
| 65 |
try:
|
| 66 |
origin_host = request.url.netloc
|
| 67 |
-
if
|
| 68 |
-
petrol = "http"
|
| 69 |
-
else:
|
| 70 |
petrol = "https"
|
| 71 |
-
if path.startswith("v1/"):
|
| 72 |
-
base_url = "https://ab.chatgpt.com"
|
| 73 |
else:
|
| 74 |
-
|
| 75 |
-
if "
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
params = dict(request.query_params)
|
|
|
|
|
|
|
| 79 |
headers = {
|
| 80 |
key: value for key, value in request.headers.items()
|
| 81 |
-
if (key.lower() not in ["host", "origin", "referer"
|
| 82 |
-
"authorization"] and key.lower() not in headers_reject_list)
|
| 83 |
}
|
| 84 |
-
request_cookies = dict(request.cookies)
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
headers.update({
|
| 87 |
-
"accept-
|
| 88 |
"host": base_url.replace("https://", "").replace("http://", ""),
|
| 89 |
"origin": base_url,
|
| 90 |
"referer": f"{base_url}/",
|
| 91 |
-
"sec-ch-ua": '"Chromium";v="128", "Not;A=Brand";v="24", "Microsoft Edge";v="128"',
|
| 92 |
-
"sec-ch-ua-mobile": "?0",
|
| 93 |
-
"sec-ch-ua-platform": "\"Windows\"",
|
| 94 |
"sec-fetch-dest": "empty",
|
| 95 |
"sec-fetch-mode": "cors",
|
| 96 |
"sec-fetch-site": "same-origin",
|
| 97 |
-
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0"
|
| 98 |
})
|
| 99 |
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
else:
|
| 106 |
-
|
|
|
|
|
|
|
| 107 |
|
| 108 |
client = Client(proxy=random.choice(proxy_url_list) if proxy_url_list else None)
|
| 109 |
try:
|
| 110 |
background = BackgroundTask(client.close)
|
| 111 |
r = await client.request(request.method, f"{base_url}/{path}", params=params, headers=headers,
|
| 112 |
cookies=request_cookies, data=data, stream=True, allow_redirects=False)
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
elif r.status_code == 307:
|
| 116 |
-
if "oai-dm=1" not in r.headers.get("Location"):
|
| 117 |
-
return Response(status_code=307, headers={
|
| 118 |
-
"Location": r.headers.get("Location").replace("chat.openai.com", origin_host)
|
| 119 |
-
.replace("chatgpt.com", origin_host)
|
| 120 |
-
.replace("https", petrol)}, background=background)
|
| 121 |
-
else:
|
| 122 |
-
return Response(status_code=307, headers={"Location": r.headers.get("Location")},
|
| 123 |
-
background=background)
|
| 124 |
-
elif r.status_code == 302:
|
| 125 |
return Response(status_code=302,
|
| 126 |
headers={"Location": r.headers.get("Location").replace("chatgpt.com", origin_host)
|
| 127 |
-
.replace("chat.openai.com", origin_host)
|
| 128 |
-
.replace("ab.chatgpt.com", origin_host)
|
| 129 |
.replace("cdn.oaistatic.com", origin_host)
|
| 130 |
.replace("https", petrol)}, background=background)
|
| 131 |
elif 'stream' in r.headers.get("content-type", ""):
|
| 132 |
return StreamingResponse(r.aiter_content(), media_type=r.headers.get("content-type", ""),
|
| 133 |
background=background)
|
| 134 |
else:
|
| 135 |
-
if "/conversation" in path or "/register-websocket" in path:
|
| 136 |
response = Response(content=(await r.atext()), media_type=r.headers.get("content-type"),
|
| 137 |
status_code=r.status_code, background=background)
|
| 138 |
else:
|
| 139 |
content = ((await r.atext()).replace("chatgpt.com", origin_host)
|
| 140 |
-
.replace("chat.openai.com", origin_host)
|
| 141 |
-
.replace("ab.chatgpt.com", origin_host)
|
| 142 |
.replace("cdn.oaistatic.com", origin_host)
|
|
|
|
| 143 |
.replace("https", petrol))
|
| 144 |
rheaders = dict(r.headers)
|
| 145 |
-
cache_control = rheaders.get("cache-control", "")
|
| 146 |
content_type = rheaders.get("content-type", "")
|
|
|
|
|
|
|
| 147 |
rheaders = {
|
| 148 |
"cache-control": cache_control,
|
| 149 |
-
"content-type": content_type
|
|
|
|
| 150 |
}
|
| 151 |
response = Response(content=content, headers=rheaders,
|
| 152 |
status_code=r.status_code, background=background)
|
| 153 |
-
for cookie_name in r.cookies:
|
| 154 |
-
if cookie_name in request_cookies:
|
| 155 |
-
continue
|
| 156 |
-
for cookie_domain in [".chatgpt.com"]:
|
| 157 |
-
cookie_value = r.cookies.get(name=cookie_name, domain=cookie_domain)
|
| 158 |
-
if cookie_name.startswith("__"):
|
| 159 |
-
response.set_cookie(key=cookie_name, value=cookie_value, secure=True, httponly=True)
|
| 160 |
-
else:
|
| 161 |
-
response.set_cookie(key=cookie_name, value=cookie_value)
|
| 162 |
return response
|
| 163 |
except Exception:
|
| 164 |
await client.close()
|
|
|
|
| 1 |
+
import json
|
| 2 |
import random
|
| 3 |
|
| 4 |
from fastapi import Request, HTTPException
|
| 5 |
from fastapi.responses import StreamingResponse, Response
|
| 6 |
from starlette.background import BackgroundTask
|
| 7 |
|
| 8 |
+
from chatgpt.authorization import verify_token, get_req_token
|
| 9 |
from utils.Client import Client
|
| 10 |
from utils.config import chatgpt_base_url_list, proxy_url_list, enable_gateway
|
| 11 |
|
|
|
|
| 62 |
|
| 63 |
|
| 64 |
async def chatgpt_reverse_proxy(request: Request, path: str):
|
|
|
|
|
|
|
| 65 |
try:
|
| 66 |
origin_host = request.url.netloc
|
| 67 |
+
if request.url.is_secure:
|
|
|
|
|
|
|
| 68 |
petrol = "https"
|
|
|
|
|
|
|
| 69 |
else:
|
| 70 |
+
petrol = "http"
|
| 71 |
+
if "x-forwarded-proto" in request.headers:
|
| 72 |
+
petrol = request.headers["x-forwarded-proto"]
|
| 73 |
+
if "cf-visitor" in request.headers:
|
| 74 |
+
cf_visitor = json.loads(request.headers["cf-visitor"])
|
| 75 |
+
petrol = cf_visitor.get("scheme", petrol)
|
| 76 |
|
| 77 |
params = dict(request.query_params)
|
| 78 |
+
request_cookies = dict(request.cookies)
|
| 79 |
+
|
| 80 |
headers = {
|
| 81 |
key: value for key, value in request.headers.items()
|
| 82 |
+
if (key.lower() not in ["host", "origin", "referer"] and key.lower() not in headers_reject_list)
|
|
|
|
| 83 |
}
|
|
|
|
| 84 |
|
| 85 |
+
base_url = random.choice(chatgpt_base_url_list) if chatgpt_base_url_list else "https://chatgpt.com"
|
| 86 |
+
if "assets/" in path:
|
| 87 |
+
base_url = "https://cdn.oaistatic.com"
|
| 88 |
+
if "file-" in path and "backend-api" not in path:
|
| 89 |
+
base_url = "https://files.oaiusercontent.com"
|
| 90 |
+
|
| 91 |
+
# req_token = request.cookies.get("req_token")
|
| 92 |
+
# ua = get_ua(req_token)
|
| 93 |
+
# headers.update(ua)
|
| 94 |
headers.update({
|
| 95 |
+
"accept-language": "en-US,en;q=0.9",
|
| 96 |
"host": base_url.replace("https://", "").replace("http://", ""),
|
| 97 |
"origin": base_url,
|
| 98 |
"referer": f"{base_url}/",
|
|
|
|
|
|
|
|
|
|
| 99 |
"sec-fetch-dest": "empty",
|
| 100 |
"sec-fetch-mode": "cors",
|
| 101 |
"sec-fetch-site": "same-origin",
|
|
|
|
| 102 |
})
|
| 103 |
|
| 104 |
+
seed_token = headers.get("authorization", "").replace("Bearer ", "")
|
| 105 |
+
if seed_token:
|
| 106 |
+
req_token = get_req_token(seed_token)
|
| 107 |
+
access_token = await verify_token(req_token)
|
| 108 |
+
if access_token.startswith("eyJhbGciOi"):
|
| 109 |
+
headers.update({"authorization": access_token})
|
| 110 |
+
else:
|
| 111 |
+
req_token = get_req_token(None, seed_token)
|
| 112 |
+
access_token = await verify_token(req_token)
|
| 113 |
+
headers.update({"authorization": access_token})
|
| 114 |
else:
|
| 115 |
+
headers.pop("authorization", None)
|
| 116 |
+
|
| 117 |
+
data = await request.body()
|
| 118 |
|
| 119 |
client = Client(proxy=random.choice(proxy_url_list) if proxy_url_list else None)
|
| 120 |
try:
|
| 121 |
background = BackgroundTask(client.close)
|
| 122 |
r = await client.request(request.method, f"{base_url}/{path}", params=params, headers=headers,
|
| 123 |
cookies=request_cookies, data=data, stream=True, allow_redirects=False)
|
| 124 |
+
|
| 125 |
+
if r.status_code == 302:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
return Response(status_code=302,
|
| 127 |
headers={"Location": r.headers.get("Location").replace("chatgpt.com", origin_host)
|
|
|
|
|
|
|
| 128 |
.replace("cdn.oaistatic.com", origin_host)
|
| 129 |
.replace("https", petrol)}, background=background)
|
| 130 |
elif 'stream' in r.headers.get("content-type", ""):
|
| 131 |
return StreamingResponse(r.aiter_content(), media_type=r.headers.get("content-type", ""),
|
| 132 |
background=background)
|
| 133 |
else:
|
| 134 |
+
if "/backend-api/conversation" in path or "/register-websocket" in path:
|
| 135 |
response = Response(content=(await r.atext()), media_type=r.headers.get("content-type"),
|
| 136 |
status_code=r.status_code, background=background)
|
| 137 |
else:
|
| 138 |
content = ((await r.atext()).replace("chatgpt.com", origin_host)
|
|
|
|
|
|
|
| 139 |
.replace("cdn.oaistatic.com", origin_host)
|
| 140 |
+
# .replace("files.oaiusercontent.com", origin_host)
|
| 141 |
.replace("https", petrol))
|
| 142 |
rheaders = dict(r.headers)
|
|
|
|
| 143 |
content_type = rheaders.get("content-type", "")
|
| 144 |
+
cache_control = rheaders.get("cache-control", "")
|
| 145 |
+
expires = rheaders.get("expires", "")
|
| 146 |
rheaders = {
|
| 147 |
"cache-control": cache_control,
|
| 148 |
+
"content-type": content_type,
|
| 149 |
+
"expires": expires
|
| 150 |
}
|
| 151 |
response = Response(content=content, headers=rheaders,
|
| 152 |
status_code=r.status_code, background=background)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
return response
|
| 154 |
except Exception:
|
| 155 |
await client.close()
|
requirements.txt
CHANGED
|
@@ -8,4 +8,5 @@ websockets
|
|
| 8 |
pillow
|
| 9 |
pybase64
|
| 10 |
jinja2
|
| 11 |
-
APScheduler
|
|
|
|
|
|
| 8 |
pillow
|
| 9 |
pybase64
|
| 10 |
jinja2
|
| 11 |
+
APScheduler
|
| 12 |
+
ua-generator
|
templates/chatgpt.html
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
utils/Client.py
CHANGED
|
@@ -4,44 +4,41 @@ from curl_cffi.requests import AsyncSession
|
|
| 4 |
|
| 5 |
|
| 6 |
class Client:
|
| 7 |
-
def __init__(self, proxy=None, timeout=15, verify=True):
|
| 8 |
-
self.proxies = {
|
| 9 |
-
"http": proxy,
|
| 10 |
-
"https": proxy,
|
| 11 |
-
}
|
| 12 |
self.timeout = timeout
|
| 13 |
self.verify = verify
|
| 14 |
|
| 15 |
-
self.impersonate =
|
| 16 |
# impersonate=self.impersonate
|
| 17 |
|
| 18 |
# self.ja3 = ""
|
| 19 |
# self.akamai = ""
|
| 20 |
# ja3=self.ja3, akamai=self.akamai
|
| 21 |
-
self.session = AsyncSession(proxies=self.proxies, timeout=self.timeout, verify=self.verify)
|
| 22 |
-
self.session2 = AsyncSession(proxies=self.proxies, timeout=self.timeout, verify=self.verify)
|
| 23 |
|
| 24 |
async def post(self, *args, **kwargs):
|
| 25 |
-
r = await self.session.post(*args,
|
| 26 |
return r
|
| 27 |
|
| 28 |
async def post_stream(self, *args, headers=None, cookies=None, **kwargs):
|
| 29 |
if self.session:
|
| 30 |
headers = headers or self.session.headers
|
| 31 |
cookies = cookies or self.session.cookies
|
| 32 |
-
r = await self.session2.post(*args, headers=headers, cookies=cookies,
|
| 33 |
return r
|
| 34 |
|
| 35 |
async def get(self, *args, **kwargs):
|
| 36 |
-
r = await self.session.get(*args,
|
| 37 |
return r
|
| 38 |
|
| 39 |
async def request(self, *args, **kwargs):
|
| 40 |
-
r = await self.session.request(*args,
|
| 41 |
return r
|
| 42 |
|
| 43 |
async def put(self, *args, **kwargs):
|
| 44 |
-
r = await self.session.put(*args,
|
| 45 |
return r
|
| 46 |
|
| 47 |
async def close(self):
|
|
|
|
| 4 |
|
| 5 |
|
| 6 |
class Client:
|
| 7 |
+
def __init__(self, proxy=None, timeout=15, verify=True, impersonate='safari15_3'):
|
| 8 |
+
self.proxies = {"http": proxy, "https": proxy}
|
|
|
|
|
|
|
|
|
|
| 9 |
self.timeout = timeout
|
| 10 |
self.verify = verify
|
| 11 |
|
| 12 |
+
self.impersonate = impersonate
|
| 13 |
# impersonate=self.impersonate
|
| 14 |
|
| 15 |
# self.ja3 = ""
|
| 16 |
# self.akamai = ""
|
| 17 |
# ja3=self.ja3, akamai=self.akamai
|
| 18 |
+
self.session = AsyncSession(proxies=self.proxies, timeout=self.timeout, impersonate=self.impersonate, verify=self.verify)
|
| 19 |
+
self.session2 = AsyncSession(proxies=self.proxies, timeout=self.timeout, impersonate=self.impersonate, verify=self.verify)
|
| 20 |
|
| 21 |
async def post(self, *args, **kwargs):
|
| 22 |
+
r = await self.session.post(*args, **kwargs)
|
| 23 |
return r
|
| 24 |
|
| 25 |
async def post_stream(self, *args, headers=None, cookies=None, **kwargs):
|
| 26 |
if self.session:
|
| 27 |
headers = headers or self.session.headers
|
| 28 |
cookies = cookies or self.session.cookies
|
| 29 |
+
r = await self.session2.post(*args, headers=headers, cookies=cookies, **kwargs)
|
| 30 |
return r
|
| 31 |
|
| 32 |
async def get(self, *args, **kwargs):
|
| 33 |
+
r = await self.session.get(*args, **kwargs)
|
| 34 |
return r
|
| 35 |
|
| 36 |
async def request(self, *args, **kwargs):
|
| 37 |
+
r = await self.session.request(*args, **kwargs)
|
| 38 |
return r
|
| 39 |
|
| 40 |
async def put(self, *args, **kwargs):
|
| 41 |
+
r = await self.session.put(*args, **kwargs)
|
| 42 |
return r
|
| 43 |
|
| 44 |
async def close(self):
|
utils/config.py
CHANGED
|
@@ -24,6 +24,7 @@ authorization = os.getenv('AUTHORIZATION', '').replace(' ', '')
|
|
| 24 |
chatgpt_base_url = os.getenv('CHATGPT_BASE_URL', 'https://chatgpt.com').replace(' ', '')
|
| 25 |
auth_key = os.getenv('AUTH_KEY', None)
|
| 26 |
user_agents = os.getenv('USER_AGENTS', '[]')
|
|
|
|
| 27 |
|
| 28 |
ark0se_token_url = os.getenv('ARK' + 'OSE_TOKEN_URL', '').replace(' ', '')
|
| 29 |
if not ark0se_token_url:
|
|
@@ -37,7 +38,7 @@ turnstile_solver_url = os.getenv('TURNSTILE_SOLVER_URL', None)
|
|
| 37 |
history_disabled = is_true(os.getenv('HISTORY_DISABLED', True))
|
| 38 |
pow_difficulty = os.getenv('POW_DIFFICULTY', '000032')
|
| 39 |
retry_times = int(os.getenv('RETRY_TIMES', 3))
|
| 40 |
-
enable_gateway = is_true(os.getenv('ENABLE_GATEWAY',
|
| 41 |
conversation_only = is_true(os.getenv('CONVERSATION_ONLY', False))
|
| 42 |
enable_limit = is_true(os.getenv('ENABLE_LIMIT', True))
|
| 43 |
upload_by_url = is_true(os.getenv('UPLOAD_BY_URL', False))
|
|
@@ -51,7 +52,7 @@ proxy_url_list = proxy_url.split(',') if proxy_url else []
|
|
| 51 |
user_agents_list = ast.literal_eval(user_agents)
|
| 52 |
|
| 53 |
logger.info("-" * 60)
|
| 54 |
-
logger.info("Chat2Api 1.
|
| 55 |
logger.info("-" * 60)
|
| 56 |
logger.info("Environment variables:")
|
| 57 |
logger.info("API_PREFIX: " + str(api_prefix))
|
|
@@ -71,4 +72,5 @@ logger.info("UPLOAD_BY_URL: " + str(upload_by_url))
|
|
| 71 |
logger.info("CHECK_MODEL: " + str(check_model))
|
| 72 |
logger.info("SCHEDULED_REFRESH: " + str(scheduled_refresh))
|
| 73 |
logger.info("USER_AGENTS: " + str(user_agents_list))
|
|
|
|
| 74 |
logger.info("-" * 60)
|
|
|
|
| 24 |
chatgpt_base_url = os.getenv('CHATGPT_BASE_URL', 'https://chatgpt.com').replace(' ', '')
|
| 25 |
auth_key = os.getenv('AUTH_KEY', None)
|
| 26 |
user_agents = os.getenv('USER_AGENTS', '[]')
|
| 27 |
+
random_token = is_true(os.getenv('RANDOM_TOKEN', True))
|
| 28 |
|
| 29 |
ark0se_token_url = os.getenv('ARK' + 'OSE_TOKEN_URL', '').replace(' ', '')
|
| 30 |
if not ark0se_token_url:
|
|
|
|
| 38 |
history_disabled = is_true(os.getenv('HISTORY_DISABLED', True))
|
| 39 |
pow_difficulty = os.getenv('POW_DIFFICULTY', '000032')
|
| 40 |
retry_times = int(os.getenv('RETRY_TIMES', 3))
|
| 41 |
+
enable_gateway = is_true(os.getenv('ENABLE_GATEWAY', False))
|
| 42 |
conversation_only = is_true(os.getenv('CONVERSATION_ONLY', False))
|
| 43 |
enable_limit = is_true(os.getenv('ENABLE_LIMIT', True))
|
| 44 |
upload_by_url = is_true(os.getenv('UPLOAD_BY_URL', False))
|
|
|
|
| 52 |
user_agents_list = ast.literal_eval(user_agents)
|
| 53 |
|
| 54 |
logger.info("-" * 60)
|
| 55 |
+
logger.info("Chat2Api 1.5.6 | https://github.com/lanqian528/chat2api")
|
| 56 |
logger.info("-" * 60)
|
| 57 |
logger.info("Environment variables:")
|
| 58 |
logger.info("API_PREFIX: " + str(api_prefix))
|
|
|
|
| 72 |
logger.info("CHECK_MODEL: " + str(check_model))
|
| 73 |
logger.info("SCHEDULED_REFRESH: " + str(scheduled_refresh))
|
| 74 |
logger.info("USER_AGENTS: " + str(user_agents_list))
|
| 75 |
+
logger.info("RANDOM_TOKEN: " + str(random_token))
|
| 76 |
logger.info("-" * 60)
|