Upload 2 files
Browse files- Dockerfile +1 -1
- app.py +261 -65
Dockerfile
CHANGED
|
@@ -2,7 +2,7 @@ FROM python:3.10-slim
|
|
| 2 |
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
-
RUN pip install --no-cache-dir flask requests curl_cffi werkzeug loguru
|
| 6 |
|
| 7 |
COPY . .
|
| 8 |
|
|
|
|
| 2 |
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
+
RUN pip install --no-cache-dir flask requests curl_cffi werkzeug loguru dotenv
|
| 6 |
|
| 7 |
COPY . .
|
| 8 |
|
app.py
CHANGED
|
@@ -5,13 +5,19 @@ import time
|
|
| 5 |
import base64
|
| 6 |
import sys
|
| 7 |
import inspect
|
|
|
|
| 8 |
from loguru import logger
|
|
|
|
| 9 |
|
| 10 |
import requests
|
| 11 |
-
from flask import Flask, request, Response, jsonify, stream_with_context
|
| 12 |
from curl_cffi import requests as curl_requests
|
| 13 |
from werkzeug.middleware.proxy_fix import ProxyFix
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
class Logger:
|
| 17 |
def __init__(self, level="INFO", colorize=True, format=None):
|
|
@@ -93,27 +99,34 @@ CONFIG = {
|
|
| 93 |
"grok-3-reasoning": "grok-3"
|
| 94 |
},
|
| 95 |
"API": {
|
| 96 |
-
"IS_TEMP_CONVERSATION": os.
|
| 97 |
-
"IS_CUSTOM_SSO": os.
|
| 98 |
-
"BASE_URL": "https://
|
| 99 |
-
"API_KEY": os.
|
| 100 |
"SIGNATURE_COOKIE": None,
|
| 101 |
-
"PICGO_KEY": os.
|
| 102 |
-
"TUMY_KEY": os.
|
| 103 |
"RETRY_TIME": 1000,
|
| 104 |
-
"PROXY": os.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
},
|
| 106 |
"SERVER": {
|
| 107 |
-
"
|
|
|
|
|
|
|
| 108 |
},
|
| 109 |
"RETRY": {
|
|
|
|
| 110 |
"MAX_ATTEMPTS": 2
|
| 111 |
},
|
| 112 |
-
"SHOW_THINKING": os.
|
| 113 |
"IS_THINKING": False,
|
| 114 |
"IS_IMG_GEN": False,
|
| 115 |
"IS_IMG_GEN2": False,
|
| 116 |
-
"ISSHOW_SEARCH_RESULTS": os.
|
| 117 |
}
|
| 118 |
|
| 119 |
|
|
@@ -217,14 +230,48 @@ class AuthTokenManager:
|
|
| 217 |
except Exception as error:
|
| 218 |
logger.error(f"令牌删除失败: {str(error)}")
|
| 219 |
return False
|
| 220 |
-
|
| 221 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
normalized_model = self.normalize_model_name(model_id)
|
| 223 |
|
| 224 |
if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:
|
| 225 |
return None
|
| 226 |
|
| 227 |
token_entry = self.token_model_map[normalized_model][0]
|
|
|
|
|
|
|
| 228 |
|
| 229 |
if token_entry:
|
| 230 |
if token_entry["StartCallTime"] is None:
|
|
@@ -377,6 +424,14 @@ class AuthTokenManager:
|
|
| 377 |
for entry in model_tokens:
|
| 378 |
all_tokens.add(entry["token"])
|
| 379 |
return list(all_tokens)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
|
| 381 |
def get_token_status_map(self):
|
| 382 |
return self.token_status_map
|
|
@@ -401,8 +456,8 @@ class Utils:
|
|
| 401 |
return '\n\n'.join(formatted_results)
|
| 402 |
|
| 403 |
@staticmethod
|
| 404 |
-
def create_auth_headers(model):
|
| 405 |
-
return token_manager.get_next_token_for_model(model)
|
| 406 |
|
| 407 |
@staticmethod
|
| 408 |
def get_proxy_options():
|
|
@@ -411,12 +466,17 @@ class Utils:
|
|
| 411 |
|
| 412 |
if proxy:
|
| 413 |
logger.info(f"使用代理: {proxy}", "Server")
|
| 414 |
-
|
| 415 |
-
|
| 416 |
if proxy.startswith("socks5://"):
|
| 417 |
-
proxy_options["
|
| 418 |
-
|
| 419 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
return proxy_options
|
| 421 |
|
| 422 |
class GrokApiClient:
|
|
@@ -445,7 +505,41 @@ class GrokApiClient:
|
|
| 445 |
"mimeType": mime_type,
|
| 446 |
"fileName": file_name
|
| 447 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
|
|
|
|
|
|
|
|
|
|
| 449 |
def upload_base64_image(self, base64_data, url):
|
| 450 |
try:
|
| 451 |
if 'data:image' in base64_data:
|
|
@@ -473,10 +567,11 @@ class GrokApiClient:
|
|
| 473 |
url,
|
| 474 |
headers={
|
| 475 |
**DEFAULT_HEADERS,
|
| 476 |
-
"Cookie":
|
| 477 |
},
|
| 478 |
json=upload_data,
|
| 479 |
impersonate="chrome133a",
|
|
|
|
| 480 |
**proxy_options
|
| 481 |
)
|
| 482 |
|
|
@@ -504,11 +599,13 @@ class GrokApiClient:
|
|
| 504 |
if last_message["role"] != 'user':
|
| 505 |
raise ValueError('此模型最后一条消息必须是用户消息!')
|
| 506 |
todo_messages = [last_message]
|
| 507 |
-
|
| 508 |
file_attachments = []
|
| 509 |
messages = ''
|
| 510 |
last_role = None
|
| 511 |
last_content = ''
|
|
|
|
|
|
|
|
|
|
| 512 |
search = request["model"] in ['grok-2-search', 'grok-3-search']
|
| 513 |
|
| 514 |
# 移除<think>标签及其内容和base64图片
|
|
@@ -558,7 +655,9 @@ class GrokApiClient:
|
|
| 558 |
|
| 559 |
|
| 560 |
text_content = process_content(current.get("content", ""))
|
| 561 |
-
|
|
|
|
|
|
|
| 562 |
if text_content or (is_last_message and file_attachments):
|
| 563 |
if role == last_role and text_content:
|
| 564 |
last_content += '\n' + text_content
|
|
@@ -567,9 +666,22 @@ class GrokApiClient:
|
|
| 567 |
messages += f"{role.upper()}: {text_content or '[图片]'}\n"
|
| 568 |
last_content = text_content
|
| 569 |
last_role = role
|
| 570 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 571 |
return {
|
| 572 |
-
"temporary": CONFIG["API"]
|
| 573 |
"modelName": self.model_id,
|
| 574 |
"message": messages.strip(),
|
| 575 |
"fileAttachments": file_attachments[:4],
|
|
@@ -684,12 +796,12 @@ def handle_image_response(image_url):
|
|
| 684 |
try:
|
| 685 |
proxy_options = Utils.get_proxy_options()
|
| 686 |
image_base64_response = curl_requests.get(
|
| 687 |
-
f"https://
|
| 688 |
headers={
|
| 689 |
**DEFAULT_HEADERS,
|
| 690 |
-
"Cookie":
|
| 691 |
},
|
| 692 |
-
impersonate="
|
| 693 |
**proxy_options
|
| 694 |
)
|
| 695 |
|
|
@@ -855,7 +967,7 @@ def handle_stream_response(response, model):
|
|
| 855 |
return generate()
|
| 856 |
|
| 857 |
def initialization():
|
| 858 |
-
sso_array = os.
|
| 859 |
logger.info("开始加载令牌", "Server")
|
| 860 |
for sso in sso_array:
|
| 861 |
if sso:
|
|
@@ -872,11 +984,76 @@ logger.info("初始化完成", "Server")
|
|
| 872 |
|
| 873 |
app = Flask(__name__)
|
| 874 |
app.wsgi_app = ProxyFix(app.wsgi_app)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 875 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 876 |
|
| 877 |
-
@app.before_request
|
| 878 |
-
def log_request_info():
|
| 879 |
-
logger.info(f"{request.method} {request.path}", "Request")
|
| 880 |
|
| 881 |
@app.route('/get/tokens', methods=['GET'])
|
| 882 |
def get_tokens():
|
|
@@ -885,7 +1062,6 @@ def get_tokens():
|
|
| 885 |
return jsonify({"error": '自定义的SSO令牌模式无法获取轮询sso令牌状态'}), 403
|
| 886 |
elif auth_token != CONFIG["API"]["API_KEY"]:
|
| 887 |
return jsonify({"error": 'Unauthorized'}), 401
|
| 888 |
-
|
| 889 |
return jsonify(token_manager.get_token_status_map())
|
| 890 |
|
| 891 |
@app.route('/add/token', methods=['POST'])
|
|
@@ -903,7 +1079,20 @@ def add_token():
|
|
| 903 |
except Exception as error:
|
| 904 |
logger.error(str(error), "Server")
|
| 905 |
return jsonify({"error": '添加sso令牌失败'}), 500
|
| 906 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 907 |
@app.route('/delete/token', methods=['POST'])
|
| 908 |
def delete_token():
|
| 909 |
auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
|
|
@@ -937,6 +1126,7 @@ def get_models():
|
|
| 937 |
|
| 938 |
@app.route('/v1/chat/completions', methods=['POST'])
|
| 939 |
def chat_completions():
|
|
|
|
| 940 |
try:
|
| 941 |
auth_token = request.headers.get('Authorization',
|
| 942 |
'').replace('Bearer ', '')
|
|
@@ -956,63 +1146,69 @@ def chat_completions():
|
|
| 956 |
retry_count = 0
|
| 957 |
grok_client = GrokApiClient(model)
|
| 958 |
request_payload = grok_client.prepare_chat_request(data)
|
|
|
|
| 959 |
|
| 960 |
while retry_count < CONFIG["RETRY"]["MAX_ATTEMPTS"]:
|
| 961 |
retry_count += 1
|
| 962 |
-
CONFIG["API"]["SIGNATURE_COOKIE"] = Utils.create_auth_headers(
|
| 963 |
-
model)
|
| 964 |
|
| 965 |
if not CONFIG["API"]["SIGNATURE_COOKIE"]:
|
| 966 |
raise ValueError('该模型无可用令牌')
|
| 967 |
|
| 968 |
logger.info(
|
| 969 |
-
f"当前令牌: {json.dumps(CONFIG['API']['SIGNATURE_COOKIE'], indent=2)}",
|
| 970 |
-
"Server")
|
| 971 |
logger.info(
|
| 972 |
-
f"当前可用模型的全部可用数量: {json.dumps(token_manager.get_remaining_token_request_capacity(), indent=2)}",
|
| 973 |
-
|
| 974 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 975 |
try:
|
| 976 |
proxy_options = Utils.get_proxy_options()
|
| 977 |
response = curl_requests.post(
|
| 978 |
f"{CONFIG['API']['BASE_URL']}/rest/app-chat/conversations/new",
|
| 979 |
headers={
|
| 980 |
-
**DEFAULT_HEADERS,
|
| 981 |
-
CONFIG["
|
| 982 |
},
|
| 983 |
-
|
| 984 |
impersonate="chrome133a",
|
|
|
|
| 985 |
stream=True,
|
| 986 |
**proxy_options)
|
|
|
|
| 987 |
if response.status_code == 200:
|
|
|
|
| 988 |
logger.info("请求成功", "Server")
|
| 989 |
-
logger.info(
|
| 990 |
-
f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}",
|
| 991 |
-
"Server")
|
| 992 |
|
| 993 |
try:
|
| 994 |
if stream:
|
| 995 |
return Response(stream_with_context(
|
| 996 |
-
handle_stream_response(response, model)),
|
| 997 |
-
content_type='text/event-stream')
|
| 998 |
else:
|
| 999 |
-
content = handle_non_stream_response(
|
| 1000 |
-
response, model)
|
| 1001 |
return jsonify(
|
| 1002 |
-
MessageProcessor.create_chat_response(
|
| 1003 |
-
content, model))
|
| 1004 |
|
| 1005 |
except Exception as error:
|
| 1006 |
logger.error(str(error), "Server")
|
| 1007 |
if CONFIG["API"]["IS_CUSTOM_SSO"]:
|
| 1008 |
raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
|
| 1009 |
-
|
| 1010 |
-
token_manager.remove_token_from_model(
|
| 1011 |
-
model, CONFIG["API"]["SIGNATURE_COOKIE"])
|
| 1012 |
if token_manager.get_token_count_for_model(model) == 0:
|
| 1013 |
raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
|
| 1014 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1015 |
elif response.status_code == 429:
|
|
|
|
|
|
|
| 1016 |
if CONFIG["API"]["IS_CUSTOM_SSO"]:
|
| 1017 |
raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
|
| 1018 |
|
|
@@ -1025,10 +1221,8 @@ def chat_completions():
|
|
| 1025 |
if CONFIG["API"]["IS_CUSTOM_SSO"]:
|
| 1026 |
raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
|
| 1027 |
|
| 1028 |
-
logger.error(f"令牌异常错误状态!status: {response.status_code}",
|
| 1029 |
-
|
| 1030 |
-
token_manager.remove_token_from_model(
|
| 1031 |
-
model, CONFIG["API"]["SIGNATURE_COOKIE"])
|
| 1032 |
logger.info(
|
| 1033 |
f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}",
|
| 1034 |
"Server")
|
|
@@ -1038,8 +1232,10 @@ def chat_completions():
|
|
| 1038 |
if CONFIG["API"]["IS_CUSTOM_SSO"]:
|
| 1039 |
raise
|
| 1040 |
continue
|
| 1041 |
-
|
| 1042 |
-
|
|
|
|
|
|
|
| 1043 |
|
| 1044 |
except Exception as error:
|
| 1045 |
logger.error(str(error), "ChatAPI")
|
|
@@ -1047,7 +1243,7 @@ def chat_completions():
|
|
| 1047 |
{"error": {
|
| 1048 |
"message": str(error),
|
| 1049 |
"type": "server_error"
|
| 1050 |
-
}}),
|
| 1051 |
|
| 1052 |
@app.route('/', defaults={'path': ''})
|
| 1053 |
@app.route('/<path:path>')
|
|
@@ -1062,4 +1258,4 @@ if __name__ == '__main__':
|
|
| 1062 |
host='0.0.0.0',
|
| 1063 |
port=CONFIG["SERVER"]["PORT"],
|
| 1064 |
debug=False
|
| 1065 |
-
)
|
|
|
|
| 5 |
import base64
|
| 6 |
import sys
|
| 7 |
import inspect
|
| 8 |
+
import secrets
|
| 9 |
from loguru import logger
|
| 10 |
+
from dotenv import load_dotenv
|
| 11 |
|
| 12 |
import requests
|
| 13 |
+
from flask import Flask, request, Response, jsonify, stream_with_context, render_template, redirect, session
|
| 14 |
from curl_cffi import requests as curl_requests
|
| 15 |
from werkzeug.middleware.proxy_fix import ProxyFix
|
| 16 |
|
| 17 |
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
| 18 |
+
env_path = os.path.join(current_dir, '.env')
|
| 19 |
+
|
| 20 |
+
load_dotenv(env_path)
|
| 21 |
|
| 22 |
class Logger:
|
| 23 |
def __init__(self, level="INFO", colorize=True, format=None):
|
|
|
|
| 99 |
"grok-3-reasoning": "grok-3"
|
| 100 |
},
|
| 101 |
"API": {
|
| 102 |
+
"IS_TEMP_CONVERSATION": os.getenv("IS_TEMP_CONVERSATION", "true").lower() == "true",
|
| 103 |
+
"IS_CUSTOM_SSO": os.getenv("IS_CUSTOM_SSO", "false").lower() == "true",
|
| 104 |
+
"BASE_URL": "https://grok.com",
|
| 105 |
+
"API_KEY": os.getenv("API_KEY", "sk-123456"),
|
| 106 |
"SIGNATURE_COOKIE": None,
|
| 107 |
+
"PICGO_KEY": os.getenv("PICGO_KEY") or None,
|
| 108 |
+
"TUMY_KEY": os.getenv("TUMY_KEY") or None,
|
| 109 |
"RETRY_TIME": 1000,
|
| 110 |
+
"PROXY": os.getenv("PROXY") or None
|
| 111 |
+
},
|
| 112 |
+
"ADMIN": {
|
| 113 |
+
"MANAGER_SWITCH": os.getenv("MANAGER_SWITCH") or None,
|
| 114 |
+
"PASSWORD": os.getenv("ADMINPASSWORD") or None
|
| 115 |
},
|
| 116 |
"SERVER": {
|
| 117 |
+
"COOKIE": None,
|
| 118 |
+
"CF_CLEARANCE":os.getenv("CF_CLEARANCE") or None,
|
| 119 |
+
"PORT": int(os.getenv("PORT", 5200))
|
| 120 |
},
|
| 121 |
"RETRY": {
|
| 122 |
+
"RETRYSWITCH": False,
|
| 123 |
"MAX_ATTEMPTS": 2
|
| 124 |
},
|
| 125 |
+
"SHOW_THINKING": os.getenv("SHOW_THINKING") == "true",
|
| 126 |
"IS_THINKING": False,
|
| 127 |
"IS_IMG_GEN": False,
|
| 128 |
"IS_IMG_GEN2": False,
|
| 129 |
+
"ISSHOW_SEARCH_RESULTS": os.getenv("ISSHOW_SEARCH_RESULTS", "true").lower() == "true"
|
| 130 |
}
|
| 131 |
|
| 132 |
|
|
|
|
| 230 |
except Exception as error:
|
| 231 |
logger.error(f"令牌删除失败: {str(error)}")
|
| 232 |
return False
|
| 233 |
+
def reduce_token_request_count(self, model_id, count):
|
| 234 |
+
try:
|
| 235 |
+
normalized_model = self.normalize_model_name(model_id)
|
| 236 |
+
|
| 237 |
+
if normalized_model not in self.token_model_map:
|
| 238 |
+
logger.error(f"模型 {normalized_model} 不存在", "TokenManager")
|
| 239 |
+
return False
|
| 240 |
+
|
| 241 |
+
if not self.token_model_map[normalized_model]:
|
| 242 |
+
logger.error(f"模型 {normalized_model} 没有可用的token", "TokenManager")
|
| 243 |
+
return False
|
| 244 |
+
|
| 245 |
+
token_entry = self.token_model_map[normalized_model][0]
|
| 246 |
+
|
| 247 |
+
# 确保RequestCount不会小于0
|
| 248 |
+
new_count = max(0, token_entry["RequestCount"] - count)
|
| 249 |
+
reduction = token_entry["RequestCount"] - new_count
|
| 250 |
+
|
| 251 |
+
token_entry["RequestCount"] = new_count
|
| 252 |
+
|
| 253 |
+
# 更新token状态
|
| 254 |
+
if token_entry["token"]:
|
| 255 |
+
sso = token_entry["token"].split("sso=")[1].split(";")[0]
|
| 256 |
+
if sso in self.token_status_map and normalized_model in self.token_status_map[sso]:
|
| 257 |
+
self.token_status_map[sso][normalized_model]["totalRequestCount"] = max(
|
| 258 |
+
0,
|
| 259 |
+
self.token_status_map[sso][normalized_model]["totalRequestCount"] - reduction
|
| 260 |
+
)
|
| 261 |
+
return True
|
| 262 |
+
|
| 263 |
+
except Exception as error:
|
| 264 |
+
logger.error(f"重置校对token请求次数时发生错误: {str(error)}", "TokenManager")
|
| 265 |
+
return False
|
| 266 |
+
def get_next_token_for_model(self, model_id, is_return=False):
|
| 267 |
normalized_model = self.normalize_model_name(model_id)
|
| 268 |
|
| 269 |
if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:
|
| 270 |
return None
|
| 271 |
|
| 272 |
token_entry = self.token_model_map[normalized_model][0]
|
| 273 |
+
if is_return:
|
| 274 |
+
return token_entry["token"]
|
| 275 |
|
| 276 |
if token_entry:
|
| 277 |
if token_entry["StartCallTime"] is None:
|
|
|
|
| 424 |
for entry in model_tokens:
|
| 425 |
all_tokens.add(entry["token"])
|
| 426 |
return list(all_tokens)
|
| 427 |
+
def get_current_token(self, model_id):
|
| 428 |
+
normalized_model = self.normalize_model_name(model_id)
|
| 429 |
+
|
| 430 |
+
if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:
|
| 431 |
+
return None
|
| 432 |
+
|
| 433 |
+
token_entry = self.token_model_map[normalized_model][0]
|
| 434 |
+
return token_entry["token"]
|
| 435 |
|
| 436 |
def get_token_status_map(self):
|
| 437 |
return self.token_status_map
|
|
|
|
| 456 |
return '\n\n'.join(formatted_results)
|
| 457 |
|
| 458 |
@staticmethod
|
| 459 |
+
def create_auth_headers(model, is_return=False):
|
| 460 |
+
return token_manager.get_next_token_for_model(model, is_return)
|
| 461 |
|
| 462 |
@staticmethod
|
| 463 |
def get_proxy_options():
|
|
|
|
| 466 |
|
| 467 |
if proxy:
|
| 468 |
logger.info(f"使用代理: {proxy}", "Server")
|
| 469 |
+
|
|
|
|
| 470 |
if proxy.startswith("socks5://"):
|
| 471 |
+
proxy_options["proxy"] = proxy
|
| 472 |
+
|
| 473 |
+
if '@' in proxy:
|
| 474 |
+
auth_part = proxy.split('@')[0].split('://')[1]
|
| 475 |
+
if ':' in auth_part:
|
| 476 |
+
username, password = auth_part.split(':')
|
| 477 |
+
proxy_options["proxy_auth"] = (username, password)
|
| 478 |
+
else:
|
| 479 |
+
proxy_options["proxies"] = {"https": proxy, "http": proxy}
|
| 480 |
return proxy_options
|
| 481 |
|
| 482 |
class GrokApiClient:
|
|
|
|
| 505 |
"mimeType": mime_type,
|
| 506 |
"fileName": file_name
|
| 507 |
}
|
| 508 |
+
def upload_base64_file(self, message, model):
|
| 509 |
+
try:
|
| 510 |
+
message_base64 = base64.b64encode(message.encode('utf-8')).decode('utf-8')
|
| 511 |
+
upload_data = {
|
| 512 |
+
"fileName": "message.txt",
|
| 513 |
+
"fileMimeType": "text/plain",
|
| 514 |
+
"content": message_base64
|
| 515 |
+
}
|
| 516 |
+
|
| 517 |
+
logger.info("发送文字文件请求", "Server")
|
| 518 |
+
cookie = f"{Utils.create_auth_headers(model, True)};{CONFIG['SERVER']['CF_CLEARANCE']}"
|
| 519 |
+
proxy_options = Utils.get_proxy_options()
|
| 520 |
+
response = curl_requests.post(
|
| 521 |
+
"https://grok.com/rest/app-chat/upload-file",
|
| 522 |
+
headers={
|
| 523 |
+
**DEFAULT_HEADERS,
|
| 524 |
+
"Cookie":cookie
|
| 525 |
+
},
|
| 526 |
+
json=upload_data,
|
| 527 |
+
impersonate="chrome133a",
|
| 528 |
+
verify=False,
|
| 529 |
+
**proxy_options
|
| 530 |
+
)
|
| 531 |
+
|
| 532 |
+
if response.status_code != 200:
|
| 533 |
+
logger.error(f"上传文件失败,状态码:{response.status_code}", "Server")
|
| 534 |
+
raise Exception(f"上传文件失败,状态码:{response.status_code}")
|
| 535 |
+
|
| 536 |
+
result = response.json()
|
| 537 |
+
logger.info(f"上传文件成功: {result}", "Server")
|
| 538 |
+
return result.get("fileMetadataId", "")
|
| 539 |
|
| 540 |
+
except Exception as error:
|
| 541 |
+
logger.error(str(error), "Server")
|
| 542 |
+
raise Exception(f"上传文件失败,状态码:{response.status_code}")
|
| 543 |
def upload_base64_image(self, base64_data, url):
|
| 544 |
try:
|
| 545 |
if 'data:image' in base64_data:
|
|
|
|
| 567 |
url,
|
| 568 |
headers={
|
| 569 |
**DEFAULT_HEADERS,
|
| 570 |
+
"Cookie":CONFIG["SERVER"]['COOKIE']
|
| 571 |
},
|
| 572 |
json=upload_data,
|
| 573 |
impersonate="chrome133a",
|
| 574 |
+
verify=False,
|
| 575 |
**proxy_options
|
| 576 |
)
|
| 577 |
|
|
|
|
| 599 |
if last_message["role"] != 'user':
|
| 600 |
raise ValueError('此模型最后一条消息必须是用户消息!')
|
| 601 |
todo_messages = [last_message]
|
|
|
|
| 602 |
file_attachments = []
|
| 603 |
messages = ''
|
| 604 |
last_role = None
|
| 605 |
last_content = ''
|
| 606 |
+
message_length = 0
|
| 607 |
+
convert_to_file = False
|
| 608 |
+
last_message_content = ''
|
| 609 |
search = request["model"] in ['grok-2-search', 'grok-3-search']
|
| 610 |
|
| 611 |
# 移除<think>标签及其内容和base64图片
|
|
|
|
| 655 |
|
| 656 |
|
| 657 |
text_content = process_content(current.get("content", ""))
|
| 658 |
+
if is_last_message and convert_to_file:
|
| 659 |
+
last_message_content = f"{role.upper()}: {text_content or '[图片]'}\n"
|
| 660 |
+
continue
|
| 661 |
if text_content or (is_last_message and file_attachments):
|
| 662 |
if role == last_role and text_content:
|
| 663 |
last_content += '\n' + text_content
|
|
|
|
| 666 |
messages += f"{role.upper()}: {text_content or '[图片]'}\n"
|
| 667 |
last_content = text_content
|
| 668 |
last_role = role
|
| 669 |
+
message_length += len(messages)
|
| 670 |
+
if message_length >= 40000:
|
| 671 |
+
convert_to_file = True
|
| 672 |
+
|
| 673 |
+
if convert_to_file:
|
| 674 |
+
file_id = self.upload_base64_file(messages, request["model"])
|
| 675 |
+
if file_id:
|
| 676 |
+
file_attachments.insert(0, file_id)
|
| 677 |
+
messages = last_message_content.strip()
|
| 678 |
+
if messages.strip() == '':
|
| 679 |
+
if convert_to_file:
|
| 680 |
+
messages = '基于txt文件内容进行回复:'
|
| 681 |
+
else:
|
| 682 |
+
raise ValueError('消息内容为空!')
|
| 683 |
return {
|
| 684 |
+
"temporary": CONFIG["API"].get("IS_TEMP_CONVERSATION", False),
|
| 685 |
"modelName": self.model_id,
|
| 686 |
"message": messages.strip(),
|
| 687 |
"fileAttachments": file_attachments[:4],
|
|
|
|
| 796 |
try:
|
| 797 |
proxy_options = Utils.get_proxy_options()
|
| 798 |
image_base64_response = curl_requests.get(
|
| 799 |
+
f"https://assets.grok.com/{image_url}",
|
| 800 |
headers={
|
| 801 |
**DEFAULT_HEADERS,
|
| 802 |
+
"Cookie":CONFIG["SERVER"]['COOKIE']
|
| 803 |
},
|
| 804 |
+
impersonate="chrome133a",
|
| 805 |
**proxy_options
|
| 806 |
)
|
| 807 |
|
|
|
|
| 967 |
return generate()
|
| 968 |
|
| 969 |
def initialization():
|
| 970 |
+
sso_array = os.getenv("SSO", "").split(',')
|
| 971 |
logger.info("开始加载令牌", "Server")
|
| 972 |
for sso in sso_array:
|
| 973 |
if sso:
|
|
|
|
| 984 |
|
| 985 |
app = Flask(__name__)
|
| 986 |
app.wsgi_app = ProxyFix(app.wsgi_app)
|
| 987 |
+
app.secret_key = os.getenv('FLASK_SECRET_KEY') or secrets.token_hex(16)
|
| 988 |
+
app.json.sort_keys = False
|
| 989 |
+
|
| 990 |
+
@app.route('/manager/login', methods=['GET', 'POST'])
|
| 991 |
+
def manager_login():
|
| 992 |
+
if CONFIG["ADMIN"]["MANAGER_SWITCH"]:
|
| 993 |
+
if request.method == 'POST':
|
| 994 |
+
password = request.form.get('password')
|
| 995 |
+
if password == CONFIG["ADMIN"]["PASSWORD"]:
|
| 996 |
+
session['is_logged_in'] = True
|
| 997 |
+
return redirect('/manager')
|
| 998 |
+
return render_template('login.html', error=True)
|
| 999 |
+
return render_template('login.html', error=False)
|
| 1000 |
+
else:
|
| 1001 |
+
return redirect('/')
|
| 1002 |
+
|
| 1003 |
+
def check_auth():
|
| 1004 |
+
return session.get('is_logged_in', False)
|
| 1005 |
+
|
| 1006 |
+
@app.route('/manager')
|
| 1007 |
+
def manager():
|
| 1008 |
+
if not check_auth():
|
| 1009 |
+
return redirect('/manager/login')
|
| 1010 |
+
return render_template('manager.html')
|
| 1011 |
+
|
| 1012 |
+
@app.route('/manager/api/get')
|
| 1013 |
+
def get_manager_tokens():
|
| 1014 |
+
if not check_auth():
|
| 1015 |
+
return jsonify({"error": "Unauthorized"}), 401
|
| 1016 |
+
return jsonify(token_manager.get_token_status_map())
|
| 1017 |
|
| 1018 |
+
@app.route('/manager/api/add', methods=['POST'])
|
| 1019 |
+
def add_manager_token():
|
| 1020 |
+
if not check_auth():
|
| 1021 |
+
return jsonify({"error": "Unauthorized"}), 401
|
| 1022 |
+
try:
|
| 1023 |
+
sso = request.json.get('sso')
|
| 1024 |
+
if not sso:
|
| 1025 |
+
return jsonify({"error": "SSO token is required"}), 400
|
| 1026 |
+
token_manager.add_token(f"sso-rw={sso};sso={sso}")
|
| 1027 |
+
return jsonify({"success": True})
|
| 1028 |
+
except Exception as e:
|
| 1029 |
+
return jsonify({"error": str(e)}), 500
|
| 1030 |
+
|
| 1031 |
+
@app.route('/manager/api/delete', methods=['POST'])
|
| 1032 |
+
def delete_manager_token():
|
| 1033 |
+
if not check_auth():
|
| 1034 |
+
return jsonify({"error": "Unauthorized"}), 401
|
| 1035 |
+
try:
|
| 1036 |
+
sso = request.json.get('sso')
|
| 1037 |
+
if not sso:
|
| 1038 |
+
return jsonify({"error": "SSO token is required"}), 400
|
| 1039 |
+
token_manager.delete_token(f"sso-rw={sso};sso={sso}")
|
| 1040 |
+
return jsonify({"success": True})
|
| 1041 |
+
except Exception as e:
|
| 1042 |
+
return jsonify({"error": str(e)}), 500
|
| 1043 |
+
|
| 1044 |
+
@app.route('/manager/api/cf_clearance', methods=['POST'])
|
| 1045 |
+
def setCf_Manager_clearance():
|
| 1046 |
+
if not check_auth():
|
| 1047 |
+
return jsonify({"error": "Unauthorized"}), 401
|
| 1048 |
+
try:
|
| 1049 |
+
cf_clearance = request.json.get('cf_clearance')
|
| 1050 |
+
if not cf_clearance:
|
| 1051 |
+
return jsonify({"error": "cf_clearance is required"}), 400
|
| 1052 |
+
CONFIG["SERVER"]['CF_CLEARANCE'] = cf_clearance
|
| 1053 |
+
return jsonify({"success": True})
|
| 1054 |
+
except Exception as e:
|
| 1055 |
+
return jsonify({"error": str(e)}), 500
|
| 1056 |
|
|
|
|
|
|
|
|
|
|
| 1057 |
|
| 1058 |
@app.route('/get/tokens', methods=['GET'])
|
| 1059 |
def get_tokens():
|
|
|
|
| 1062 |
return jsonify({"error": '自定义的SSO令牌模式无法获取轮询sso令牌状态'}), 403
|
| 1063 |
elif auth_token != CONFIG["API"]["API_KEY"]:
|
| 1064 |
return jsonify({"error": 'Unauthorized'}), 401
|
|
|
|
| 1065 |
return jsonify(token_manager.get_token_status_map())
|
| 1066 |
|
| 1067 |
@app.route('/add/token', methods=['POST'])
|
|
|
|
| 1079 |
except Exception as error:
|
| 1080 |
logger.error(str(error), "Server")
|
| 1081 |
return jsonify({"error": '添加sso令牌失败'}), 500
|
| 1082 |
+
|
| 1083 |
+
@app.route('/set/cf_clearance', methods=['POST'])
|
| 1084 |
+
def setCf_clearance():
|
| 1085 |
+
auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
|
| 1086 |
+
if auth_token != CONFIG["API"]["API_KEY"]:
|
| 1087 |
+
return jsonify({"error": 'Unauthorized'}), 401
|
| 1088 |
+
try:
|
| 1089 |
+
cf_clearance = request.json.get('cf_clearance')
|
| 1090 |
+
CONFIG["SERVER"]['CF_CLEARANCE'] = cf_clearance
|
| 1091 |
+
return jsonify({"message": '设置cf_clearance成功'}), 200
|
| 1092 |
+
except Exception as error:
|
| 1093 |
+
logger.error(str(error), "Server")
|
| 1094 |
+
return jsonify({"error": '设置cf_clearance失败'}), 500
|
| 1095 |
+
|
| 1096 |
@app.route('/delete/token', methods=['POST'])
|
| 1097 |
def delete_token():
|
| 1098 |
auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
|
|
|
|
| 1126 |
|
| 1127 |
@app.route('/v1/chat/completions', methods=['POST'])
|
| 1128 |
def chat_completions():
|
| 1129 |
+
response_status_code = 500
|
| 1130 |
try:
|
| 1131 |
auth_token = request.headers.get('Authorization',
|
| 1132 |
'').replace('Bearer ', '')
|
|
|
|
| 1146 |
retry_count = 0
|
| 1147 |
grok_client = GrokApiClient(model)
|
| 1148 |
request_payload = grok_client.prepare_chat_request(data)
|
| 1149 |
+
|
| 1150 |
|
| 1151 |
while retry_count < CONFIG["RETRY"]["MAX_ATTEMPTS"]:
|
| 1152 |
retry_count += 1
|
| 1153 |
+
CONFIG["API"]["SIGNATURE_COOKIE"] = Utils.create_auth_headers(model)
|
|
|
|
| 1154 |
|
| 1155 |
if not CONFIG["API"]["SIGNATURE_COOKIE"]:
|
| 1156 |
raise ValueError('该模型无可用令牌')
|
| 1157 |
|
| 1158 |
logger.info(
|
| 1159 |
+
f"当前令牌: {json.dumps(CONFIG['API']['SIGNATURE_COOKIE'], indent=2)}","Server")
|
|
|
|
| 1160 |
logger.info(
|
| 1161 |
+
f"当前可用模型的全部可用数量: {json.dumps(token_manager.get_remaining_token_request_capacity(), indent=2)}","Server")
|
| 1162 |
+
|
| 1163 |
+
if CONFIG['SERVER']['CF_CLEARANCE']:
|
| 1164 |
+
CONFIG["SERVER"]['COOKIE'] = f"{CONFIG['API']['SIGNATURE_COOKIE']};{CONFIG['SERVER']['CF_CLEARANCE']}"
|
| 1165 |
+
else:
|
| 1166 |
+
CONFIG["SERVER"]['COOKIE'] = CONFIG['API']['SIGNATURE_COOKIE']
|
| 1167 |
+
logger.info(json.dumps(request_payload,indent=2),"Server")
|
| 1168 |
try:
|
| 1169 |
proxy_options = Utils.get_proxy_options()
|
| 1170 |
response = curl_requests.post(
|
| 1171 |
f"{CONFIG['API']['BASE_URL']}/rest/app-chat/conversations/new",
|
| 1172 |
headers={
|
| 1173 |
+
**DEFAULT_HEADERS,
|
| 1174 |
+
"Cookie":CONFIG["SERVER"]['COOKIE']
|
| 1175 |
},
|
| 1176 |
+
json=request_payload,
|
| 1177 |
impersonate="chrome133a",
|
| 1178 |
+
verify=False,
|
| 1179 |
stream=True,
|
| 1180 |
**proxy_options)
|
| 1181 |
+
logger.info(CONFIG["SERVER"]['COOKIE'],"Server")
|
| 1182 |
if response.status_code == 200:
|
| 1183 |
+
response_status_code = 200
|
| 1184 |
logger.info("请求成功", "Server")
|
| 1185 |
+
logger.info(f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}","Server")
|
|
|
|
|
|
|
| 1186 |
|
| 1187 |
try:
|
| 1188 |
if stream:
|
| 1189 |
return Response(stream_with_context(
|
| 1190 |
+
handle_stream_response(response, model)),content_type='text/event-stream')
|
|
|
|
| 1191 |
else:
|
| 1192 |
+
content = handle_non_stream_response(response, model)
|
|
|
|
| 1193 |
return jsonify(
|
| 1194 |
+
MessageProcessor.create_chat_response(content, model))
|
|
|
|
| 1195 |
|
| 1196 |
except Exception as error:
|
| 1197 |
logger.error(str(error), "Server")
|
| 1198 |
if CONFIG["API"]["IS_CUSTOM_SSO"]:
|
| 1199 |
raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
|
| 1200 |
+
token_manager.remove_token_from_model(model, CONFIG["API"]["SIGNATURE_COOKIE"])
|
|
|
|
|
|
|
| 1201 |
if token_manager.get_token_count_for_model(model) == 0:
|
| 1202 |
raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
|
| 1203 |
+
elif response.status_code == 403:
|
| 1204 |
+
response_status_code = 403
|
| 1205 |
+
token_manager.reduce_token_request_count(model,1)#重置去除当前因为错误未成功请求的次数,确保不会因为错误未成功请求的次数导致次数上限
|
| 1206 |
+
if token_manager.get_token_count_for_model(model) == 0:
|
| 1207 |
+
raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
|
| 1208 |
+
raise ValueError(f"IP暂时被封无法破盾,请稍后重试或者更换ip")
|
| 1209 |
elif response.status_code == 429:
|
| 1210 |
+
response_status_code = 429
|
| 1211 |
+
token_manager.reduce_token_request_count(model,1)
|
| 1212 |
if CONFIG["API"]["IS_CUSTOM_SSO"]:
|
| 1213 |
raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
|
| 1214 |
|
|
|
|
| 1221 |
if CONFIG["API"]["IS_CUSTOM_SSO"]:
|
| 1222 |
raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
|
| 1223 |
|
| 1224 |
+
logger.error(f"令牌异常错误状态!status: {response.status_code}","Server")
|
| 1225 |
+
token_manager.remove_token_from_model(model, CONFIG["API"]["SIGNATURE_COOKIE"])
|
|
|
|
|
|
|
| 1226 |
logger.info(
|
| 1227 |
f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}",
|
| 1228 |
"Server")
|
|
|
|
| 1232 |
if CONFIG["API"]["IS_CUSTOM_SSO"]:
|
| 1233 |
raise
|
| 1234 |
continue
|
| 1235 |
+
if response_status_code == 403:
|
| 1236 |
+
raise ValueError('IP暂时被封无法破盾,请稍后重试或者更换ip')
|
| 1237 |
+
elif response_status_code == 500:
|
| 1238 |
+
raise ValueError('当前模型所有令牌暂无可用,请稍后重试')
|
| 1239 |
|
| 1240 |
except Exception as error:
|
| 1241 |
logger.error(str(error), "ChatAPI")
|
|
|
|
| 1243 |
{"error": {
|
| 1244 |
"message": str(error),
|
| 1245 |
"type": "server_error"
|
| 1246 |
+
}}), response_status_code
|
| 1247 |
|
| 1248 |
@app.route('/', defaults={'path': ''})
|
| 1249 |
@app.route('/<path:path>')
|
|
|
|
| 1258 |
host='0.0.0.0',
|
| 1259 |
port=CONFIG["SERVER"]["PORT"],
|
| 1260 |
debug=False
|
| 1261 |
+
)
|