From 69cef746eee9082f141768081d31470e0b61d093 Mon Sep 17 00:00:00 2001 From: LanQian <5499636+LanQian528@users.noreply.github.com> Date: Mon, 27 May 2024 15:48:07 +0800 Subject: [PATCH] v1.1.12 add auth_key, fix bugs --- .github/workflows/build_docker.yml | 2 +- README.md | 35 ++++++++++----------- chat2api.py | 3 +- chatgpt/ChatService.py | 49 ++++++++++++++++++------------ chatgpt/chatFormat.py | 4 ++- chatgpt/chatLimit.py | 4 ++- chatgpt/refreshToken.py | 4 +-- chatgpt/reverseProxy.py | 7 +++-- chatgpt/wssClient.py | 2 +- templates/tokens.html | 33 +++++++++++--------- utils/config.py | 4 ++- 11 files changed, 86 insertions(+), 61 deletions(-) diff --git a/.github/workflows/build_docker.yml b/.github/workflows/build_docker.yml index db3dbb7..813f9a9 100644 --- a/.github/workflows/build_docker.yml +++ b/.github/workflows/build_docker.yml @@ -37,7 +37,7 @@ jobs: images: lanqian528/chat2api tags: | type=raw,value=latest,enable={{is_default_branch}} - type=raw,value=v1.1.11 + type=raw,value=v1.1.12 - name: Build and push uses: docker/build-push-action@v5 diff --git a/README.md b/README.md index c35168c..9e18ce3 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ 3. 接口返回的状态码和响应体 ## 功能 -### 最新版 v1.1.11 + +### 最新版 v1.1.12 > 已完成 > - [x] 流式、非流式传输 @@ -55,22 +56,22 @@ 每个环境变量都有默认值,如果不懂环境变量的含义,请不要设置,更不要传空值,字符串无需引号。 -| 分类 | 变量名 | 示例值 | 描述 | -|------|-------------------|-------------------------------------|--------------------------------------------------------------| -| 安全相关 | API_PREFIX | your_prefix | API 前缀密码,不设置容易被人访问,设置后需请求 `/your_prefix/v1/chat/completions` | -| | AUTHORIZATION | sk-xxxxxxxx, sk-yyyyyyyy | 为使用多账号轮询 Tokens 设置的授权,英文逗号分隔 | -| 请求相关 | CHATGPT_BASE_URL | https://chatgpt.com | ChatGPT 网关地址,设置后会改变请求的网站,多个网关用逗号分隔 | -| | PROXY_URL | your_first_proxy, your_second_proxy | 代理 URL,多个代理用逗号分隔 | -| | ARKOSE_TOKEN_URL | https://arkose.example.com/token | 获取 Arkose token 的地址 | -| 功能相关 | HISTORY_DISABLED | true | 是否不保存聊天记录并返回 conversation_id | -| | POW_DIFFICULTY | 00003a | 要解决的工作量证明难度,不懂别设置 | -| | RETRY_TIMES | 3 | 出错重试次数,使用 AUTHORIZATION 会自动轮询下一个账号 | -| | ENABLE_GATEWAY | true | 是否启用网关模式(WEBUI) | -| | CONVERSATION_ONLY | false | 是否直接使用对话接口,如果你用的网关支持自动解决pow和arkose才启用 | -| | ENABLE_LIMIT | true | 开启后不尝试突破官方次数限制,尽可能防止封号 | -| | UPLOAD_BY_URL | false | 开启后按照 `URL+空格+正文` 进行对话,自动解析 URL 内容并上传,多个 URL 用空格分隔 | -| | CHECK_MODEL | false | 检查账号是否支持传入模型,开启后可以稍微避免4o返回3.5内容,但是会增加请求时延,且并不能解决降智问题 | - +| 分类 | 变量名 | 示例值 | 默认值 | 描述 | +|------|-------------------|-------------------------------------------------------------|-----------------------|--------------------------------------------------------------| +| 安全相关 | API_PREFIX | `your_prefix` | `None` | API 前缀密码,不设置容易被人访问,设置后需请求 `/your_prefix/v1/chat/completions` | +| | AUTHORIZATION | `your_first_authorization`,
`your_second_authorization` | `[]` | 你自己为使用多账号轮询 Tokens 设置的授权,英文逗号分隔 | +| | AUTH_KEY | `your_auth_key` | `None` | 私人网关需要加`auth_key`请求头才设置该项 | +| 请求相关 | CHATGPT_BASE_URL | `https://chatgpt.com` | `https://chatgpt.com` | ChatGPT 网关地址,设置后会改变请求的网站,多个网关用逗号分隔 | +| | PROXY_URL | `http://ip:port`,
`http://username:password@ip:port` | `[]` | 代理 URL,多个代理用逗号分隔 | +| | ARKOSE_TOKEN_URL | `https://example.com/token` | `[]` | 获取 Arkose token 的地址 | +| 功能相关 | HISTORY_DISABLED | `true` | `true` | 是否不保存聊天记录并返回 conversation_id | +| | POW_DIFFICULTY | `00003a` | `00003a` | 要解决的工作量证明难度,不懂别设置 | +| | RETRY_TIMES | `3` | `3` | 出错重试次数,使用 AUTHORIZATION 会自动轮询下一个账号 | +| | ENABLE_GATEWAY | `true` | `true` | 是否启用网关模式(WEBUI) | +| | CONVERSATION_ONLY | `false` | `false` | 是否直接使用对话接口,如果你用的网关支持自动解决pow和arkose才启用 | +| | ENABLE_LIMIT | `true` | `true` | 开启后不尝试突破官方次数限制,尽可能防止封号 | +| | UPLOAD_BY_URL | `false` | `false` | 开启后按照 `URL+空格+正文` 进行对话,自动解析 URL 内容并上传,多个 URL 用空格分隔 | +| | CHECK_MODEL | `false` | `false` | 检查账号是否支持传入模型,开启后可以稍微避免4o返回3.5内容,但是会增加请求时延,且并不能解决降智问题 | ## 部署 diff --git a/chat2api.py b/chat2api.py index 4ce2355..927b6b9 100644 --- a/chat2api.py +++ b/chat2api.py @@ -87,7 +87,8 @@ async def send_conversation(request: Request, req_token: str = Depends(oauth2_sc @app.get(f"/{api_prefix}/tokens" if api_prefix else "/tokens", response_class=HTMLResponse) async def upload_html(request: Request): tokens_count = len(token_list) - return templates.TemplateResponse("tokens.html", {"request": request, "api_prefix": api_prefix, "tokens_count": tokens_count}) + 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") diff --git a/chatgpt/ChatService.py b/chatgpt/ChatService.py index 8c85865..5adc3e1 100644 --- a/chatgpt/ChatService.py +++ b/chatgpt/ChatService.py @@ -17,7 +17,7 @@ from utils.Client import Client from utils.Logger import logger from utils.authorization import verify_token from utils.config import proxy_url_list, chatgpt_base_url_list, arkose_token_url_list, history_disabled, pow_difficulty, \ - conversation_only, enable_limit, limit_status_code, upload_by_url, check_model + conversation_only, enable_limit, limit_status_code, upload_by_url, check_model, auth_key class ChatService: @@ -61,9 +61,20 @@ class ChatService: self.history_disabled = data.get('history_disabled', history_disabled) self.data = data + self.origin_model = self.data.get("model", "gpt-3.5-turbo-0125") self.resp_model = model_proxy.get(self.origin_model, self.origin_model) - self.req_model = None + if "gpt-4o" in self.origin_model: + self.req_model = "gpt-4o" + elif "gpt-4-mobile" in self.origin_model: + self.req_model = "gpt-4-mobile" + elif "gpt-4-gizmo" in self.origin_model: + self.req_model = "gpt-4o" + elif "gpt-4" in self.origin_model: + self.req_model = "gpt-4" + else: + self.req_model = "text-davinci-002-render-sha" + self.api_messages = self.data.get("messages", []) self.prompt_tokens = 0 self.max_tokens = self.data.get("max_tokens", 2147483647) @@ -99,8 +110,12 @@ class ChatService: else: self.base_url = self.host_url + "/backend-anon" + if auth_key: + self.base_headers['authkey'] = auth_key + await get_dpl(self) - self.s.session.cookies.set("__Secure-next-auth.callback-url", "https%3A%2F%2Fchatgpt.com;", domain=self.host_url.split("://")[1], secure=True) + self.s.session.cookies.set("__Secure-next-auth.callback-url", "https%3A%2F%2Fchatgpt.com;", + domain=self.host_url.split("://")[1], secure=True) async def get_wss_url(self): url = f'{self.base_url}/register-websocket' @@ -129,17 +144,6 @@ class ChatService: if r.status_code == 200: resp = r.json() - if "gpt-4o" in self.origin_model: - self.req_model = "gpt-4o" - elif "gpt-4-mobile" in self.origin_model: - self.req_model = "gpt-4-mobile" - elif "gpt-4-gizmo" in self.origin_model: - self.req_model = "gpt-4o" - elif "gpt-4" in self.origin_model: - self.req_model = "gpt-4" - else: - self.req_model = "text-davinci-002-render-sha" - if check_model: r = await self.s.get(f'{self.base_url}/models', headers=headers, timeout=5) if r.status_code == 200: @@ -174,9 +178,11 @@ class ChatService: if proofofwork_required: proofofwork_diff = proofofwork.get("difficulty") if proofofwork_diff <= pow_difficulty: - raise HTTPException(status_code=403, detail=f"Proof of work difficulty too high: {proofofwork_diff}") + raise HTTPException(status_code=403, + detail=f"Proof of work difficulty too high: {proofofwork_diff}") proofofwork_seed = proofofwork.get("seed") - self.proof_token, solved = await run_in_threadpool(get_answer_token, proofofwork_seed, proofofwork_diff, config) + self.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") @@ -285,7 +291,8 @@ class ChatService: raise HTTPException(status_code=e.status_code, detail=str(e)) url = f'{self.base_url}/conversation' stream = self.data.get("stream", False) - r = await self.s.post_stream(url, headers=self.chat_headers, json=self.chat_request, timeout=10, stream=True) + r = await self.s.post_stream(url, headers=self.chat_headers, json=self.chat_request, timeout=10, + stream=True) if r.status_code != 200: rtext = await r.atext() if "application/json" == r.headers.get("Content-Type", ""): @@ -304,7 +311,9 @@ class ChatService: if "text/event-stream" in content_type and stream: return stream_response(self, r.aiter_lines(), self.resp_model, self.max_tokens) elif "text/event-stream" in content_type and not stream: - return await format_not_stream_response(stream_response(self, r.aiter_lines(), self.resp_model, self.max_tokens), self.prompt_tokens, self.max_tokens, self.resp_model) + return await format_not_stream_response( + stream_response(self, r.aiter_lines(), self.resp_model, self.max_tokens), self.prompt_tokens, + self.max_tokens, self.resp_model) elif "application/json" in content_type: rtext = await r.atext() resp = json.loads(rtext) @@ -319,7 +328,9 @@ class ChatService: if stream and isinstance(wss_r, types.AsyncGeneratorType): return stream_response(self, wss_r, self.resp_model, self.max_tokens) else: - return await format_not_stream_response(stream_response(self, wss_r, self.resp_model, self.max_tokens), self.prompt_tokens, self.max_tokens, self.resp_model) + return await format_not_stream_response( + stream_response(self, wss_r, self.resp_model, self.max_tokens), self.prompt_tokens, + self.max_tokens, self.resp_model) finally: if not isinstance(wss_r, types.AsyncGeneratorType): await self.ws.close() diff --git a/chatgpt/chatFormat.py b/chatgpt/chatFormat.py index 5db68dc..9d95da6 100644 --- a/chatgpt/chatFormat.py +++ b/chatgpt/chatFormat.py @@ -248,7 +248,9 @@ async def stream_response(service, response, model, max_tokens): def get_url_from_content(content): if isinstance(content, str) and content.startswith('http'): try: - url = re.match(r'(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))', content.split(' ')[0])[0] + url = re.match( + r'(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))', + content.split(' ')[0])[0] content = content.replace(url, '').strip() return url, content except Exception: diff --git a/chatgpt/chatLimit.py b/chatgpt/chatLimit.py index c444f6f..f446036 100644 --- a/chatgpt/chatLimit.py +++ b/chatgpt/chatLimit.py @@ -1,6 +1,7 @@ import threading import time from datetime import datetime + from utils.Logger import logger lock = threading.Lock() @@ -16,7 +17,8 @@ def check_isLimit(detail, access_token): def initial_access_list(key, clear_time): with lock: limit_access_token[key] = clear_time - logger.info(f"{key[:40]}: Reached 429 limit, will be cleared at {datetime.fromtimestamp(clear_time).replace(second=0, microsecond=0)}") + logger.info( + f"{key[:40]}: Reached 429 limit, will be cleared at {datetime.fromtimestamp(clear_time).replace(second=0, microsecond=0)}") def remove_refresh_list(key): diff --git a/chatgpt/refreshToken.py b/chatgpt/refreshToken.py index 0485e70..945ccd5 100644 --- a/chatgpt/refreshToken.py +++ b/chatgpt/refreshToken.py @@ -9,7 +9,6 @@ from utils.Client import Client from utils.Logger import logger from utils.config import proxy_url_list - DATA_FOLDER = "data" REFRESH_MAP_FILE = os.path.join(DATA_FOLDER, "refresh_map.json") @@ -29,7 +28,8 @@ def save_refresh_map(refresh_map): async def rt2ac(refresh_token): - if refresh_token in refresh_map and int(time.time()) - refresh_map.get(refresh_token, {}).get("timestamp", 0) < 2 * 24 * 60 * 60: + if refresh_token in refresh_map and int(time.time()) - refresh_map.get(refresh_token, {}).get("timestamp", + 0) < 2 * 24 * 60 * 60: access_token = refresh_map[refresh_token]["token"] logger.info(f"refresh_token -> access_token from cache") return access_token diff --git a/chatgpt/reverseProxy.py b/chatgpt/reverseProxy.py index 9de9faa..27c40b5 100644 --- a/chatgpt/reverseProxy.py +++ b/chatgpt/reverseProxy.py @@ -113,10 +113,11 @@ async def chatgpt_reverse_proxy(request: Request, path: str): if "oai-dm=1" not in r.headers.get("Location"): return Response(status_code=307, headers={ "Location": r.headers.get("Location").replace("chat.openai.com", origin_host) - .replace("chatgpt.com", origin_host) - .replace("https", petrol) + "?oai-dm=1"}, background=background) + .replace("chatgpt.com", origin_host) + .replace("https", petrol) + "?oai-dm=1"}, background=background) else: - return Response(status_code=307, headers={"Location": r.headers.get("Location")}, background=background) + return Response(status_code=307, headers={"Location": r.headers.get("Location")}, + background=background) elif r.status_code == 302: return Response(status_code=302, headers={"Location": r.headers.get("Location").replace("chatgpt.com", origin_host) diff --git a/chatgpt/wssClient.py b/chatgpt/wssClient.py index 3cf75b6..8d71eb8 100644 --- a/chatgpt/wssClient.py +++ b/chatgpt/wssClient.py @@ -20,4 +20,4 @@ async def ac2wss(access_token): async def set_wss(access_token, wss_url): wss_map[access_token] = {"timestamp": int(time.time()), "wss_url": wss_url} - return True \ No newline at end of file + return True diff --git a/templates/tokens.html b/templates/tokens.html index d4cb3c2..3e04416 100644 --- a/templates/tokens.html +++ b/templates/tokens.html @@ -2,7 +2,7 @@ - + Tokens 管理 -
-

Tokens 管理

-

当前可用 Tokens 数量:{{ tokens_count }}

-
- -

注:使用docker时如果挂载了data文件夹则重启后不需要再次上传

- -
-
- -
-

点击清空,将会清空所有已保存的 Tokens

-
+
+

Tokens 管理

+

当前可用 Tokens 数量:{{ tokens_count }}

+
+ +

注:使用docker时如果挂载了data文件夹则重启后不需要再次上传

+ +
+
+ +
+

点击清空,将会清空所有已保存的 Tokens

+
\ No newline at end of file diff --git a/utils/config.py b/utils/config.py index bd9e4a5..f7c7055 100644 --- a/utils/config.py +++ b/utils/config.py @@ -21,6 +21,7 @@ def is_true(x): api_prefix = os.getenv('API_PREFIX', None) authorization = os.getenv('AUTHORIZATION', '').replace(' ', '') chatgpt_base_url = os.getenv('CHATGPT_BASE_URL', 'https://chatgpt.com').replace(' ', '') +auth_key = os.getenv('AUTH_KEY', None) arkose_token_url = os.getenv('ARKOSE_TOKEN_URL', '').replace(' ', '') proxy_url = os.getenv('PROXY_URL', '').replace(' ', '') history_disabled = is_true(os.getenv('HISTORY_DISABLED', True)) @@ -39,12 +40,13 @@ arkose_token_url_list = arkose_token_url.split(',') if arkose_token_url else [] proxy_url_list = proxy_url.split(',') if proxy_url else [] logger.info("-" * 60) -logger.info("Chat2Api v1.1.11 | https://github.com/lanqian528/chat2api") +logger.info("Chat2Api v1.1.12 | https://github.com/lanqian528/chat2api") logger.info("-" * 60) logger.info("Environment variables:") logger.info("API_PREFIX: " + str(api_prefix)) logger.info("AUTHORIZATION: " + str(authorization_list)) logger.info("CHATGPT_BASE_URL: " + str(chatgpt_base_url_list)) +logger.info("AUTH_KEY: " + str(auth_key)) logger.info("ARKOSE_TOKEN_URL: " + str(arkose_token_url_list)) logger.info("PROXY_URL: " + str(proxy_url_list)) logger.info("HISTORY_DISABLED: " + str(history_disabled))