diff --git a/README.md b/README.md index 68f5db1..55a89c9 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ 🔍 回复格式与真实 API 完全一致,适配几乎所有客户端 +👮 配套用户管理端[Chat-Share](https://github.com/h88782481/Chat-Share)使用前需提前配置好环境变量(ENABLE_GATEWAY设置为True,AUTO_SEED设置为False) + + ## 交流群 [https://t.me/chat2api](https://t.me/chat2api) diff --git a/chatgpt/ChatService.py b/chatgpt/ChatService.py index 9716be9..89580af 100644 --- a/chatgpt/ChatService.py +++ b/chatgpt/ChatService.py @@ -8,9 +8,10 @@ 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_fp +from chatgpt.authorization import get_req_token, verify_token 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.fp import get_fp from chatgpt.proofofWork import get_config, get_dpl, get_answer_token, get_requirements_token from utils.Client import Client @@ -52,10 +53,10 @@ 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.fp = get_fp(self.req_token).copy() + self.proxy_url = self.fp.pop("proxy_url", None) + self.impersonate = self.fp.pop("impersonate", "safari15_3") 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}") @@ -86,7 +87,6 @@ class ChatService: self.s = Client(proxy=self.proxy_url, impersonate=self.impersonate) - self.oai_device_id = str(uuid.uuid4()) self.persona = None self.ark0se_token = None self.proof_token = None @@ -100,19 +100,16 @@ class ChatService: 'accept-encoding': 'gzip, deflate, br, zstd', 'accept-language': 'en-US,en;q=0.9', 'content-type': 'application/json', - 'oai-device-id': self.oai_device_id, 'oai-language': oai_language, 'origin': self.host_url, 'priority': 'u=1, i', 'referer': f'{self.host_url}/', - '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', - 'user-agent': self.user_agent + 'sec-fetch-site': 'same-origin' } + self.base_headers.update(self.fp) + if self.access_token: self.base_url = self.host_url + "/backend-api" self.base_headers['authorization'] = f'Bearer {self.access_token}' @@ -225,7 +222,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.fp.get("impersonate", "safari15_3")) + ark0se_client = Client(impersonate=self.impersonate) try: r2 = await ark0se_client.post( url=self.ark0se_token_url, json={"blob": ark0se_dx, "method": ark0se_method}, timeout=15 diff --git a/chatgpt/authorization.py b/chatgpt/authorization.py index f0ce2a3..e2aac4d 100644 --- a/chatgpt/authorization.py +++ b/chatgpt/authorization.py @@ -1,11 +1,7 @@ import asyncio import json import random -import uuid -import ua_generator -from ua_generator.options import Options -from ua_generator.data.version import VersionRange from fastapi import HTTPException import utils.configs as configs @@ -47,51 +43,6 @@ def get_req_token(req_token, seed=None): return globals.seed_map[seed]["token"] -def get_fp(req_token): - fp = globals.fp_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"] is None 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.fp_map[req_token] = fp - with open(globals.FP_FILE, "w", encoding="utf-8") as f: - json.dump(globals.fp_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.fp_map[req_token] = fp - with open(globals.FP_FILE, "w", encoding="utf-8") as f: - json.dump(globals.fp_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.fp_map[req_token] = fp - with open(globals.FP_FILE, "w", encoding="utf-8") as f: - json.dump(globals.fp_map, f, indent=4) - fp = {k.lower(): v for k, v in fp.items()} - return fp - 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, - "oai-device-id": str(uuid.uuid4()) - } - if not req_token: - return fp - else: - globals.fp_map[req_token] = fp - with open(globals.FP_FILE, "w", encoding="utf-8") as f: - json.dump(globals.fp_map, f, indent=4) - return fp - - async def verify_token(req_token): if not req_token: if configs.authorization_list: diff --git a/chatgpt/fp.py b/chatgpt/fp.py new file mode 100644 index 0000000..ae1003a --- /dev/null +++ b/chatgpt/fp.py @@ -0,0 +1,61 @@ +import json +import random +import uuid + +import ua_generator +from ua_generator.data.version import VersionRange +from ua_generator.options import Options + +import utils.globals as globals +from utils import configs + + +def get_fp(req_token): + fp = globals.fp_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"] is None or 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.fp_map[req_token] = fp + with open(globals.FP_FILE, "w", encoding="utf-8") as f: + json.dump(globals.fp_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.fp_map[req_token] = fp + with open(globals.FP_FILE, "w", encoding="utf-8") as f: + json.dump(globals.fp_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.fp_map[req_token] = fp + with open(globals.FP_FILE, "w", encoding="utf-8") as f: + json.dump(globals.fp_map, f, indent=4) + fp = {k.lower(): v for k, v in fp.items()} + return fp + else: + options = Options(version_ranges={ + 'chrome': VersionRange(min_version=124), + 'edge': VersionRange(min_version=124), + }) + ua = ua_generator.generate( + device=configs.device_tuple if configs.device_tuple else ('desktop'), + browser=configs.browser_tuple if configs.browser_tuple else ('chrome', 'edge', 'firefox', 'safari'), + platform=configs.platform_tuple if configs.platform_tuple else ('windows', 'macos'), + options=options + ) + fp = { + "user-agent": ua.text if not configs.user_agents_list else random.choice(configs.user_agents_list), + "impersonate": random.choice(globals.impersonate_list), + "proxy_url": random.choice(configs.proxy_url_list) if configs.proxy_url_list else None, + "oai-device-id": str(uuid.uuid4()) + } + if ua.device == "desktop" and ua.browser in ("chrome", "edge"): + fp["sec-ch-ua-platform"] = ua.ch.platform + fp["sec-ch-ua"] = ua.ch.brands + fp["sec-ch-ua-mobile"] = ua.ch.mobile + + if not req_token: + return fp + else: + globals.fp_map[req_token] = fp + with open(globals.FP_FILE, "w", encoding="utf-8") as f: + json.dump(globals.fp_map, f, indent=4) + return fp diff --git a/gateway/backend.py b/gateway/backend.py index a4718ad..b5cb031 100644 --- a/gateway/backend.py +++ b/gateway/backend.py @@ -11,7 +11,8 @@ from starlette.concurrency import run_in_threadpool import utils.globals as globals from app import app -from chatgpt.authorization import verify_token, get_fp +from chatgpt.authorization import verify_token +from chatgpt.fp import get_fp from chatgpt.proofofWork import get_answer_token, get_config, get_requirements_token from gateway.chatgpt import chatgpt_html from gateway.reverseProxy import chatgpt_reverse_proxy, content_generator, get_real_req_token, headers_reject_list @@ -252,11 +253,10 @@ if no_sentinel: 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) + fp = get_fp(req_token).copy() proxy_url = fp.pop("proxy_url", None) impersonate = fp.pop("impersonate", "safari15_3") - 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") + 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") host_url = random.choice(chatgpt_base_url_list) if chatgpt_base_url_list else "https://chatgpt.com" proof_token = None @@ -264,14 +264,10 @@ if no_sentinel: headers = { key: value for key, value in request.headers.items() - if (key.lower() not in ["host", "origin", "referer", "priority", - "oai-device-id"] and key.lower() not in headers_reject_list) + if (key.lower() not in ["host", "origin", "referer", "priority", "sec-ch-ua-platform", "sec-ch-ua", "sec-ch-ua-mobile", "oai-device-id"] and key.lower() not in headers_reject_list) } headers.update(fp) - headers.update({ - "authorization": f"Bearer {access_token}", - "oai-device-id": fp.get("oai-device-id", str(uuid.uuid4())) - }) + headers.update({"authorization": f"Bearer {access_token}"}) client = Client(proxy=proxy_url, impersonate=impersonate) diff --git a/gateway/reverseProxy.py b/gateway/reverseProxy.py index a79052a..e409b3e 100644 --- a/gateway/reverseProxy.py +++ b/gateway/reverseProxy.py @@ -9,7 +9,8 @@ from fastapi.responses import StreamingResponse, Response from starlette.background import BackgroundTask import utils.globals as globals -from chatgpt.authorization import verify_token, get_req_token, get_fp +from chatgpt.authorization import verify_token, get_req_token +from chatgpt.fp import get_fp from utils.Client import Client from utils.Logger import logger from utils.configs import chatgpt_base_url_list @@ -173,7 +174,7 @@ async def chatgpt_reverse_proxy(request: Request, path: str): token = request.cookies.get("token", "") req_token = await get_real_req_token(token) - fp = get_fp(req_token) + fp = get_fp(req_token).copy() proxy_url = fp.pop("proxy_url", None) impersonate = fp.pop("impersonate", "safari15_3") user_agent = fp.get("user-agent") diff --git a/gateway/share.py b/gateway/share.py index a54a83a..328fc8e 100644 --- a/gateway/share.py +++ b/gateway/share.py @@ -9,7 +9,8 @@ from fastapi.security import HTTPAuthorizationCredentials import utils.globals as globals from app import app, security_scheme -from chatgpt.authorization import get_fp, verify_token +from chatgpt.authorization import verify_token +from chatgpt.fp import get_fp from gateway.reverseProxy import get_real_req_token from utils.Client import Client from utils.Logger import logger @@ -127,13 +128,13 @@ async def chatgpt_account_check(access_token): 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) + fp = get_fp(req_token).copy() proxy_url = fp.pop("proxy_url", None) impersonate = fp.pop("impersonate", "safari15_3") headers = base_headers.copy() - headers.update({"authorization": f"Bearer {access_token}"}) headers.update(fp) + headers.update({"authorization": f"Bearer {access_token}"}) client = Client(proxy=proxy_url, impersonate=impersonate) r = await client.get(f"{host_url}/backend-api/models?history_and_training_disabled=false", headers=headers, diff --git a/utils/configs.py b/utils/configs.py index 3ee8141..ddb0b9d 100644 --- a/utils/configs.py +++ b/utils/configs.py @@ -32,6 +32,9 @@ 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', '[]') +device_tuple_str = os.getenv('DEVICE_TUPLE', '()') +browser_tuple_str = os.getenv('BROWSER_TUPLE', '()') +platform_tuple_str = os.getenv('PLATFORM_TUPLE', '()') cf_file_url = os.getenv('CF_FILE_URL', None) turnstile_solver_url = os.getenv('TURNSTILE_SOLVER_URL', None) @@ -53,6 +56,9 @@ ark0se_token_url_list = ark0se_token_url.split(',') if ark0se_token_url else [] proxy_url_list = proxy_url.split(',') if proxy_url else [] impersonate_list = ast.literal_eval(impersonate_list_str) user_agents_list = ast.literal_eval(user_agents_list_str) +device_tuple = ast.literal_eval(device_tuple_str) +browser_tuple = ast.literal_eval(browser_tuple_str) +platform_tuple = ast.literal_eval(platform_tuple_str) enable_gateway = is_true(os.getenv('ENABLE_GATEWAY', False)) auto_seed = is_true(os.getenv('AUTO_SEED', True)) diff --git a/version.txt b/version.txt index 0a182f2..7faacca 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.7.2 \ No newline at end of file +v1.7.3-beta1 \ No newline at end of file