dfa32412 commited on
Commit
e3d7dfc
·
verified ·
1 Parent(s): 0e7fef8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1248 -112
app.py CHANGED
@@ -1,135 +1,1271 @@
1
- from playwright.sync_api import sync_playwright
 
 
 
 
 
 
 
 
 
2
 
3
- # 用于存储捕获到的请求头信息
4
- all_request_headers_info = []
 
 
 
5
 
 
 
6
 
7
- def handle_request(request):
8
- """
9
- 捕获每个请求的URL, 方法和头部信息
10
- """
11
- # print(f"Intercepted request to: {request.url}") # 调试时可以取消注释
12
- all_request_headers_info.append({
13
- "url": request.url,
14
- "method": request.method,
15
- "headers": request.headers # request.headers 是一个字典
16
- })
17
 
 
 
 
 
 
 
 
18
 
19
- def main():
20
- with sync_playwright() as p:
21
- # 启动浏览器,可以是 chromium, firefox, or webkit
22
- # headless=False 可以看到浏览器操作,True则为无头模式
23
- browser = p.chromium.launch(headless=False,
24
- args=[
25
- '--no-sandbox',
26
- '--disable-setuid-sandbox',
27
- '--disable-dev-shm-usage' # 有时也需要这个,但 --shm-size 更好
28
- ])
29
-
30
- # 创建一个新的浏览器上下文
31
- # 可以在这里设置 user_agent, viewport, etc.
32
- context = browser.new_context(
33
- user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0",
34
  )
35
 
36
- # 在上下文中创建一个新页面
37
- page = context.new_page()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
- # 注册请求拦截器,这必须在导航之前完成
40
- # 'request' 事件会在每个HTTP请求发起时触发
41
- page.on("request", handle_request)
42
 
43
- print(f"Navigating to https://grok.com/ ...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  try:
45
- # 访问目标网站,设置一个合理的超时时间(例如60秒)
46
- page.goto("https://grok.com/", timeout=60000)
47
- print("Page loaded. Waiting for 10 seconds for dynamic content or further requests...")
48
-
49
- # 检查是否仍然被 Cloudflare 阻止 (例如,查找特定的标题或元素)
50
- title = page.title()
51
- print(f"Page title: {title}")
52
-
53
- if "请稍候…" in page.content() or "Just a moment..." in page.content() or "Cloudflare" in title or "Checking your browser" in title:
54
- print("Still on a Cloudflare challenge page. Waiting longer or trying interaction...")
55
- # 你可能需要在这里添加更长的等待或模拟用户交互
56
- # 例如,等待特定的元素出现,表明挑战已通过
57
- try:
58
- page.wait_for_selector("body:not(:has-text('请稍候…'))", timeout=60000)
59
- print("Cloudflare challenge likely passed.")
60
- title = page.title()
61
- print(f"New page title: {title}")
62
- page.screenshot(path="cf_passed.png")
63
- except Exception as e:
64
- print(f"Failed to pass Cloudflare challenge after extended wait: {e}")
65
- page.screenshot(path="cf_failed.png")
66
- else:
67
- print("Successfully navigated to the page.")
68
- page.screenshot(path="cf_success.png")
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
- page.wait_for_timeout(10000)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
 
 
 
 
 
 
 
 
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  try:
75
- textarea_locator = page.get_by_label("向Grok提任何问题")
76
- textarea_locator.fill("你好")
77
- print("Successfully entered '你好' into the textarea.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  except Exception as e:
79
- print(f"Could not find or fill the textarea with aria-label '向Grok提任何问题'. Error: {e}")
80
- browser.close()
81
- return
 
 
 
 
 
 
 
82
 
83
- # 2. 查找 aria-label 为“提交”的 button 并点击
84
- # 使用 get_by_role('button', name='...') 是 Playwright 推荐的方式来查找具有特定可访问名称的按钮
 
 
 
 
 
 
85
  try:
86
- submit_button_locator = page.get_by_role("button", name="提交")
87
- submit_button_locator.click()
88
- print("Successfully clicked the '提交' button.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  except Exception as e:
90
- print(f"Could not find or click the button with aria-label '提交'. Error: {e}")
91
- browser.close()
92
- return
93
-
94
- # 等待10秒
95
- # Playwright 的 page.wait_for_timeout() 是首选,因为它与Playwright的事件循环集成
96
- # page.wait_for_timeout(10000)
97
- # 或者使用 time.sleep(10) 也可以,但在Playwright脚本中前者更佳
98
-
99
- print("\n--- Cookies ---")
100
- # 获取当前上下文中的所有cookies
101
- cookies = context.cookies()
102
- if cookies:
103
- for cookie in cookies:
104
- print(
105
- f"Name: {cookie['name']}, Value: {cookie['value']}, Domain: {cookie['domain']}, Path: {cookie['path']}")
106
- else:
107
- print("No cookies found.")
108
-
109
- print("\n--- Request Headers (collected during the session) ---")
110
- if all_request_headers_info:
111
- # 打印捕获到的每个请求的头部信息
112
- # 注意:这里会包含所有资源的请求(HTML, CSS, JS, XHR, 图片等)
113
- for i, req_info in enumerate(all_request_headers_info):
114
- if req_info['url'] == 'https://grok.com/rest/app-chat/conversations/new':
115
- datas = {
116
- 'x-xai-request-id': req_info['headers']['x-xai-request-id'],
117
- 'x-statsig-id':req_info['headers']['x-statsig-id'],
118
- 'user-agent': req_info['headers']['user-agent'],
119
- }
120
- print(datas)
121
- return datas
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  else:
123
- print("No requests were intercepted (this is unlikely if the page loaded).")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
- except Exception as e:
126
- print(f"An error occurred: {e}")
127
- finally:
128
- # 确保浏览器关闭
129
- print("\nClosing browser...")
130
- browser.close()
131
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
 
 
 
133
 
134
- if __name__ == "__main__":
135
- main()
 
 
 
 
1
+ import os
2
+ import json
3
+ import uuid
4
+ import time
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
+ import get_xid
17
 
18
+ current_dir = os.path.dirname(os.path.abspath(__file__))
19
+ env_path = os.path.join(current_dir, '.env')
20
 
21
+ load_dotenv(env_path)
22
+
23
+ class Logger:
24
+ def __init__(self, level="INFO", colorize=True, format=None):
25
+ logger.remove()
 
 
 
 
 
26
 
27
+ if format is None:
28
+ format = (
29
+ "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
30
+ "<level>{level: <8}</level> | "
31
+ "<cyan>{extra[filename]}</cyan>:<cyan>{extra[function]}</cyan>:<cyan>{extra[lineno]}</cyan> | "
32
+ "<level>{message}</level>"
33
+ )
34
 
35
+ logger.add(
36
+ sys.stderr,
37
+ level=level,
38
+ format=format,
39
+ colorize=colorize,
40
+ backtrace=True,
41
+ diagnose=True
 
 
 
 
 
 
 
 
42
  )
43
 
44
+ self.logger = logger
45
+
46
+ def _get_caller_info(self):
47
+ frame = inspect.currentframe()
48
+ try:
49
+ caller_frame = frame.f_back.f_back
50
+ full_path = caller_frame.f_code.co_filename
51
+ function = caller_frame.f_code.co_name
52
+ lineno = caller_frame.f_lineno
53
+
54
+ filename = os.path.basename(full_path)
55
+
56
+ return {
57
+ 'filename': filename,
58
+ 'function': function,
59
+ 'lineno': lineno
60
+ }
61
+ finally:
62
+ del frame
63
+
64
+ def info(self, message, source="API"):
65
+ caller_info = self._get_caller_info()
66
+ self.logger.bind(**caller_info).info(f"[{source}] {message}")
67
+
68
+ def error(self, message, source="API"):
69
+ caller_info = self._get_caller_info()
70
+
71
+ if isinstance(message, Exception):
72
+ self.logger.bind(**caller_info).exception(f"[{source}] {str(message)}")
73
+ else:
74
+ self.logger.bind(**caller_info).error(f"[{source}] {message}")
75
+
76
+ def warning(self, message, source="API"):
77
+ caller_info = self._get_caller_info()
78
+ self.logger.bind(**caller_info).warning(f"[{source}] {message}")
79
+
80
+ def debug(self, message, source="API"):
81
+ caller_info = self._get_caller_info()
82
+ self.logger.bind(**caller_info).debug(f"[{source}] {message}")
83
+
84
+ async def request_logger(self, request):
85
+ caller_info = self._get_caller_info()
86
+ self.logger.bind(**caller_info).info(f"请求: {request.method} {request.path}", "Request")
87
 
88
+ logger = Logger(level="INFO")
 
 
89
 
90
+
91
+ CONFIG = {
92
+ "XIDS": None,
93
+ "MODELS": {
94
+ 'grok-2': 'grok-latest',
95
+ 'grok-2-imageGen': 'grok-latest',
96
+ 'grok-2-search': 'grok-latest',
97
+ "grok-3": "grok-3",
98
+ "grok-3-search": "grok-3",
99
+ "grok-3-imageGen": "grok-3",
100
+ "grok-3-deepsearch": "grok-3",
101
+ "grok-3-reasoning": "grok-3"
102
+ },
103
+ "API": {
104
+ "IS_TEMP_CONVERSATION": os.getenv("IS_TEMP_CONVERSATION", "true").lower() == "true",
105
+ "IS_CUSTOM_SSO": os.getenv("IS_CUSTOM_SSO", "false").lower() == "true",
106
+ "BASE_URL": "https://grok.com",
107
+ "API_KEY": os.getenv("API_KEY", "sk-123456"),
108
+ "SIGNATURE_COOKIE": None,
109
+ "PICGO_KEY": os.getenv("PICGO_KEY") or None,
110
+ "TUMY_KEY": os.getenv("TUMY_KEY") or None,
111
+ "RETRY_TIME": 1000,
112
+ "PROXY": os.getenv("PROXY") or None
113
+ },
114
+ "ADMIN": {
115
+ "MANAGER_SWITCH": os.getenv("MANAGER_SWITCH") or None,
116
+ "PASSWORD": os.getenv("ADMINPASSWORD") or None
117
+ },
118
+ "SERVER": {
119
+ "COOKIE": None,
120
+ "CF_CLEARANCE":os.getenv("CF_CLEARANCE") or None,
121
+ "PORT": int(os.getenv("PORT", 5200))
122
+ },
123
+ "RETRY": {
124
+ "RETRYSWITCH": False,
125
+ "MAX_ATTEMPTS": 2
126
+ },
127
+ "SHOW_THINKING": os.getenv("SHOW_THINKING") == "true",
128
+ "IS_THINKING": False,
129
+ "IS_IMG_GEN": False,
130
+ "IS_IMG_GEN2": False,
131
+ "ISSHOW_SEARCH_RESULTS": os.getenv("ISSHOW_SEARCH_RESULTS", "true").lower() == "true"
132
+ }
133
+
134
+
135
+ DEFAULT_HEADERS = {
136
+ 'Accept': '*/*',
137
+ 'Accept-Language': 'zh-CN,zh;q=0.9',
138
+ 'Accept-Encoding': 'gzip, deflate, br, zstd',
139
+ 'Content-Type': 'text/plain;charset=UTF-8',
140
+ 'Connection': 'keep-alive',
141
+ 'Origin': 'https://grok.com',
142
+ 'Priority': 'u=1, i',
143
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0',
144
+ 'Sec-Ch-Ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
145
+ 'Sec-Ch-Ua-Mobile': '?0',
146
+ 'Sec-Ch-Ua-Platform': '"macOS"',
147
+ 'Sec-Fetch-Dest': 'empty',
148
+ 'Sec-Fetch-Mode': 'cors',
149
+ 'Sec-Fetch-Site': 'same-origin',
150
+ 'x-statsig-id': 'cHV/H8IhE6huG6FFpOG9UBkA9sguxSrYHE0EPfQgZ8M4q7fBPU58POnc9X4Av7nZK36qlXOOMaM1/0jWl1SMLjOejLsqcw',
151
+ 'x-xai-request-id': '6b3e84d8-fc58-4a6f-b698-81791acfebda',
152
+ 'Baggage': 'sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c'
153
+ }
154
+
155
+ class AuthTokenManager:
156
+ def __init__(self):
157
+ self.token_model_map = {}
158
+ self.expired_tokens = set()
159
+ self.token_status_map = {}
160
+
161
+ self.model_config = {
162
+ "grok-2": {
163
+ "RequestFrequency": 30,
164
+ "ExpirationTime": 1 * 60 * 60 * 1000 # 1小时
165
+ },
166
+ "grok-3": {
167
+ "RequestFrequency": 20,
168
+ "ExpirationTime": 2 * 60 * 60 * 1000 # 2小时
169
+ },
170
+ "grok-3-deepsearch": {
171
+ "RequestFrequency": 10,
172
+ "ExpirationTime": 24 * 60 * 60 * 1000 # 24小时
173
+ },
174
+ "grok-3-reasoning": {
175
+ "RequestFrequency": 10,
176
+ "ExpirationTime": 24 * 60 * 60 * 1000 # 24小时
177
+ }
178
+ }
179
+ self.token_reset_switch = False
180
+ self.token_reset_timer = None
181
+
182
+ def add_token(self, token):
183
+ sso = token.split("sso=")[1].split(";")[0]
184
+ for model in self.model_config.keys():
185
+ if model not in self.token_model_map:
186
+ self.token_model_map[model] = []
187
+ if sso not in self.token_status_map:
188
+ self.token_status_map[sso] = {}
189
+
190
+ existing_token_entry = next((entry for entry in self.token_model_map[model] if entry["token"] == token), None)
191
+
192
+ if not existing_token_entry:
193
+ self.token_model_map[model].append({
194
+ "token": token,
195
+ "RequestCount": 0,
196
+ "AddedTime": int(time.time() * 1000),
197
+ "StartCallTime": None
198
+ })
199
+
200
+ if model not in self.token_status_map[sso]:
201
+ self.token_status_map[sso][model] = {
202
+ "isValid": True,
203
+ "invalidatedTime": None,
204
+ "totalRequestCount": 0
205
+ }
206
+
207
+ def set_token(self, token):
208
+ models = list(self.model_config.keys())
209
+ self.token_model_map = {model: [{
210
+ "token": token,
211
+ "RequestCount": 0,
212
+ "AddedTime": int(time.time() * 1000),
213
+ "StartCallTime": None
214
+ }] for model in models}
215
+
216
+ sso = token.split("sso=")[1].split(";")[0]
217
+ self.token_status_map[sso] = {model: {
218
+ "isValid": True,
219
+ "invalidatedTime": None,
220
+ "totalRequestCount": 0
221
+ } for model in models}
222
+
223
+ def delete_token(self, token):
224
  try:
225
+ sso = token.split("sso=")[1].split(";")[0]
226
+ for model in self.token_model_map:
227
+ self.token_model_map[model] = [entry for entry in self.token_model_map[model] if entry["token"] != token]
228
+
229
+ if sso in self.token_status_map:
230
+ del self.token_status_map[sso]
231
+
232
+ logger.info(f"令牌已成功移除: {token}", "TokenManager")
233
+ return True
234
+ except Exception as error:
235
+ logger.error(f"令牌删除失败: {str(error)}")
236
+ return False
237
+ def reduce_token_request_count(self, model_id, count):
238
+ try:
239
+ normalized_model = self.normalize_model_name(model_id)
240
+
241
+ if normalized_model not in self.token_model_map:
242
+ logger.error(f"模型 {normalized_model} 不存在", "TokenManager")
243
+ return False
 
 
 
 
 
244
 
245
+ if not self.token_model_map[normalized_model]:
246
+ logger.error(f"模型 {normalized_model} 没有可用的token", "TokenManager")
247
+ return False
248
+
249
+ token_entry = self.token_model_map[normalized_model][0]
250
+
251
+ # 确保RequestCount不会小于0
252
+ new_count = max(0, token_entry["RequestCount"] - count)
253
+ reduction = token_entry["RequestCount"] - new_count
254
+
255
+ token_entry["RequestCount"] = new_count
256
+
257
+ # 更新token状态
258
+ if token_entry["token"]:
259
+ sso = token_entry["token"].split("sso=")[1].split(";")[0]
260
+ if sso in self.token_status_map and normalized_model in self.token_status_map[sso]:
261
+ self.token_status_map[sso][normalized_model]["totalRequestCount"] = max(
262
+ 0,
263
+ self.token_status_map[sso][normalized_model]["totalRequestCount"] - reduction
264
+ )
265
+ return True
266
+
267
+ except Exception as error:
268
+ logger.error(f"重置校对token请求次数时发生错误: {str(error)}", "TokenManager")
269
+ return False
270
+ def get_next_token_for_model(self, model_id, is_return=False):
271
+ normalized_model = self.normalize_model_name(model_id)
272
+
273
+ if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:
274
+ return None
275
+
276
+ token_entry = self.token_model_map[normalized_model][0]
277
+ if is_return:
278
+ return token_entry["token"]
279
+
280
+ if token_entry:
281
+ if token_entry["StartCallTime"] is None:
282
+ token_entry["StartCallTime"] = int(time.time() * 1000)
283
+
284
+ if not self.token_reset_switch:
285
+ self.start_token_reset_process()
286
+ self.token_reset_switch = True
287
+
288
+ token_entry["RequestCount"] += 1
289
+
290
+ if token_entry["RequestCount"] > self.model_config[normalized_model]["RequestFrequency"]:
291
+ self.remove_token_from_model(normalized_model, token_entry["token"])
292
+ next_token_entry = self.token_model_map[normalized_model][0] if self.token_model_map[normalized_model] else None
293
+ return next_token_entry["token"] if next_token_entry else None
294
+
295
+ sso = token_entry["token"].split("sso=")[1].split(";")[0]
296
+ if sso in self.token_status_map and normalized_model in self.token_status_map[sso]:
297
+ if token_entry["RequestCount"] == self.model_config[normalized_model]["RequestFrequency"]:
298
+ self.token_status_map[sso][normalized_model]["isValid"] = False
299
+ self.token_status_map[sso][normalized_model]["invalidatedTime"] = int(time.time() * 1000)
300
+ self.token_status_map[sso][normalized_model]["totalRequestCount"] += 1
301
+
302
+ return token_entry["token"]
303
+
304
+ return None
305
+
306
+ def remove_token_from_model(self, model_id, token):
307
+ normalized_model = self.normalize_model_name(model_id)
308
+
309
+ if normalized_model not in self.token_model_map:
310
+ logger.error(f"模型 {normalized_model} 不存在", "TokenManager")
311
+ return False
312
+
313
+ model_tokens = self.token_model_map[normalized_model]
314
+ token_index = next((i for i, entry in enumerate(model_tokens) if entry["token"] == token), -1)
315
+
316
+ if token_index != -1:
317
+ removed_token_entry = model_tokens.pop(token_index)
318
+ self.expired_tokens.add((
319
+ removed_token_entry["token"],
320
+ normalized_model,
321
+ int(time.time() * 1000)
322
+ ))
323
+
324
+ if not self.token_reset_switch:
325
+ self.start_token_reset_process()
326
+ self.token_reset_switch = True
327
+
328
+ logger.info(f"模型{model_id}的令牌已失效,已成功移除令牌: {token}", "TokenManager")
329
+ return True
330
+
331
+ logger.error(f"在模型 {normalized_model} 中未找到 token: {token}", "TokenManager")
332
+ return False
333
+
334
+ def get_expired_tokens(self):
335
+ return list(self.expired_tokens)
336
+
337
+ def normalize_model_name(self, model):
338
+ if model.startswith('grok-') and 'deepsearch' not in model and 'reasoning' not in model:
339
+ return '-'.join(model.split('-')[:2])
340
+ return model
341
+
342
+ def get_token_count_for_model(self, model_id):
343
+ normalized_model = self.normalize_model_name(model_id)
344
+ return len(self.token_model_map.get(normalized_model, []))
345
+
346
+ def get_remaining_token_request_capacity(self):
347
+ remaining_capacity_map = {}
348
+
349
+ for model in self.model_config.keys():
350
+ model_tokens = self.token_model_map.get(model, [])
351
+ model_request_frequency = self.model_config[model]["RequestFrequency"]
352
+
353
+ total_used_requests = sum(token_entry.get("RequestCount", 0) for token_entry in model_tokens)
354
+
355
+ remaining_capacity = (len(model_tokens) * model_request_frequency) - total_used_requests
356
+ remaining_capacity_map[model] = max(0, remaining_capacity)
357
+
358
+ return remaining_capacity_map
359
+
360
+ def get_token_array_for_model(self, model_id):
361
+ normalized_model = self.normalize_model_name(model_id)
362
+ return self.token_model_map.get(normalized_model, [])
363
+
364
+ def start_token_reset_process(self):
365
+ def reset_expired_tokens():
366
+ now = int(time.time() * 1000)
367
+
368
+ tokens_to_remove = set()
369
+ for token_info in self.expired_tokens:
370
+ token, model, expired_time = token_info
371
+ expiration_time = self.model_config[model]["ExpirationTime"]
372
+
373
+ if now - expired_time >= expiration_time:
374
+ if not any(entry["token"] == token for entry in self.token_model_map.get(model, [])):
375
+ if model not in self.token_model_map:
376
+ self.token_model_map[model] = []
377
+
378
+ self.token_model_map[model].append({
379
+ "token": token,
380
+ "RequestCount": 0,
381
+ "AddedTime": now,
382
+ "StartCallTime": None
383
+ })
384
+
385
+ sso = token.split("sso=")[1].split(";")[0]
386
+ if sso in self.token_status_map and model in self.token_status_map[sso]:
387
+ self.token_status_map[sso][model]["isValid"] = True
388
+ self.token_status_map[sso][model]["invalidatedTime"] = None
389
+ self.token_status_map[sso][model]["totalRequestCount"] = 0
390
+
391
+ tokens_to_remove.add(token_info)
392
+
393
+ self.expired_tokens -= tokens_to_remove
394
+
395
+ for model in self.model_config.keys():
396
+ if model not in self.token_model_map:
397
+ continue
398
+
399
+ for token_entry in self.token_model_map[model]:
400
+ if not token_entry.get("StartCallTime"):
401
+ continue
402
+
403
+ expiration_time = self.model_config[model]["ExpirationTime"]
404
+ if now - token_entry["StartCallTime"] >= expiration_time:
405
+ sso = token_entry["token"].split("sso=")[1].split(";")[0]
406
+ if sso in self.token_status_map and model in self.token_status_map[sso]:
407
+ self.token_status_map[sso][model]["isValid"] = True
408
+ self.token_status_map[sso][model]["invalidatedTime"] = None
409
+ self.token_status_map[sso][model]["totalRequestCount"] = 0
410
+
411
+ token_entry["RequestCount"] = 0
412
+ token_entry["StartCallTime"] = None
413
+
414
+ import threading
415
+ # 启动一个线程执行定时任务,每小时执行一次
416
+ def run_timer():
417
+ while True:
418
+ reset_expired_tokens()
419
+ time.sleep(3600)
420
+
421
+ timer_thread = threading.Thread(target=run_timer)
422
+ timer_thread.daemon = True
423
+ timer_thread.start()
424
+
425
+ def get_all_tokens(self):
426
+ all_tokens = set()
427
+ for model_tokens in self.token_model_map.values():
428
+ for entry in model_tokens:
429
+ all_tokens.add(entry["token"])
430
+ return list(all_tokens)
431
+ def get_current_token(self, model_id):
432
+ normalized_model = self.normalize_model_name(model_id)
433
+
434
+ if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:
435
+ return None
436
+
437
+ token_entry = self.token_model_map[normalized_model][0]
438
+ return token_entry["token"]
439
+
440
+ def get_token_status_map(self):
441
+ return self.token_status_map
442
+
443
+ class Utils:
444
+ @staticmethod
445
+ def organize_search_results(search_results):
446
+ if not search_results or 'results' not in search_results:
447
+ return ''
448
+
449
+ results = search_results['results']
450
+ formatted_results = []
451
+
452
+ for index, result in enumerate(results):
453
+ title = result.get('title', '未知标题')
454
+ url = result.get('url', '#')
455
+ preview = result.get('preview', '无预览内容')
456
+
457
+ formatted_result = f"\r\n<details><summary>资料[{index}]: {title}</summary>\r\n{preview}\r\n\n[Link]({url})\r\n</details>"
458
+ formatted_results.append(formatted_result)
459
+
460
+ return '\n\n'.join(formatted_results)
461
+
462
+ @staticmethod
463
+ def create_auth_headers(model, is_return=False):
464
+ return token_manager.get_next_token_for_model(model, is_return)
465
+
466
+ @staticmethod
467
+ def get_proxy_options():
468
+ proxy = CONFIG["API"]["PROXY"]
469
+ proxy_options = {}
470
+
471
+ if proxy:
472
+ logger.info(f"使用代理: {proxy}", "Server")
473
+
474
+ if proxy.startswith("socks5://"):
475
+ proxy_options["proxy"] = proxy
476
 
477
+ if '@' in proxy:
478
+ auth_part = proxy.split('@')[0].split('://')[1]
479
+ if ':' in auth_part:
480
+ username, password = auth_part.split(':')
481
+ proxy_options["proxy_auth"] = (username, password)
482
+ else:
483
+ proxy_options["proxies"] = {"https": proxy, "http": proxy}
484
+ return proxy_options
485
+
486
+ class GrokApiClient:
487
+ def __init__(self, model_id):
488
+ if model_id not in CONFIG["MODELS"]:
489
+ raise ValueError(f"不支持的模型: {model_id}")
490
+ self.model_id = CONFIG["MODELS"][model_id]
491
+
492
+ def process_message_content(self, content):
493
+ if isinstance(content, str):
494
+ return content
495
+ return None
496
+
497
+ def get_image_type(self, base64_string):
498
+ mime_type = 'image/jpeg'
499
+ if 'data:image' in base64_string:
500
+ import re
501
+ matches = re.search(r'data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,', base64_string)
502
+ if matches:
503
+ mime_type = matches.group(1)
504
+
505
+ extension = mime_type.split('/')[1]
506
+ file_name = f"image.{extension}"
507
+
508
+ return {
509
+ "mimeType": mime_type,
510
+ "fileName": file_name
511
+ }
512
+ def upload_base64_file(self, message, model):
513
+ try:
514
+ message_base64 = base64.b64encode(message.encode('utf-8')).decode('utf-8')
515
+ upload_data = {
516
+ "fileName": "message.txt",
517
+ "fileMimeType": "text/plain",
518
+ "content": message_base64
519
+ }
520
+
521
+ logger.info("发送文字文件请求", "Server")
522
+ cookie = f"{Utils.create_auth_headers(model, True)};{CONFIG['SERVER']['CF_CLEARANCE']}"
523
+ proxy_options = Utils.get_proxy_options()
524
+ response = curl_requests.post(
525
+ "https://grok.com/rest/app-chat/upload-file",
526
+ headers={
527
+ **DEFAULT_HEADERS,
528
+ "Cookie":cookie
529
+ },
530
+ json=upload_data,
531
+ impersonate="chrome133a",
532
+ verify=False,
533
+ **proxy_options
534
+ )
535
+
536
+ if response.status_code != 200:
537
+ logger.error(f"上传文件失败,状态码:{response.status_code}", "Server")
538
+ raise Exception(f"上传文件失败,状态码:{response.status_code}")
539
+
540
+ result = response.json()
541
+ logger.info(f"上传文件成功: {result}", "Server")
542
+ return result.get("fileMetadataId", "")
543
+
544
+ except Exception as error:
545
+ logger.error(str(error), "Server")
546
+ raise Exception(f"上传文件失败,状态码:{response.status_code}")
547
+ def upload_base64_image(self, base64_data, url):
548
+ try:
549
+ if 'data:image' in base64_data:
550
+ image_buffer = base64_data.split(',')[1]
551
+ else:
552
+ image_buffer = base64_data
553
+
554
+ image_info = self.get_image_type(base64_data)
555
+ mime_type = image_info["mimeType"]
556
+ file_name = image_info["fileName"]
557
 
558
+ upload_data = {
559
+ "rpc": "uploadFile",
560
+ "req": {
561
+ "fileName": file_name,
562
+ "fileMimeType": mime_type,
563
+ "content": image_buffer
564
+ }
565
+ }
566
 
567
+ logger.info("发送图片请求", "Server")
568
+
569
+ proxy_options = Utils.get_proxy_options()
570
+ response = curl_requests.post(
571
+ url,
572
+ headers={
573
+ **DEFAULT_HEADERS,
574
+ "Cookie":CONFIG["SERVER"]['COOKIE']
575
+ },
576
+ json=upload_data,
577
+ impersonate="chrome133a",
578
+ verify=False,
579
+ **proxy_options
580
+ )
581
+
582
+ if response.status_code != 200:
583
+ logger.error(f"上传图片失败,状态码:{response.status_code}", "Server")
584
+ return ''
585
+
586
+ result = response.json()
587
+ logger.info(f"上传图片成功: {result}", "Server")
588
+ return result.get("fileMetadataId", "")
589
+
590
+ except Exception as error:
591
+ logger.error(str(error), "Server")
592
+ return ''
593
+
594
+ def prepare_chat_request(self, request):
595
+ if ((request["model"] == 'grok-2-imageGen' or request["model"] == 'grok-3-imageGen') and
596
+ not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"] and
597
+ request.get("stream", False)):
598
+ raise ValueError("该模型流式输出需要配置PICGO或者TUMY图床密钥!")
599
+
600
+ todo_messages = request["messages"]
601
+ if request["model"] in ['grok-2-imageGen', 'grok-3-imageGen', 'grok-3-deepsearch']:
602
+ last_message = todo_messages[-1]
603
+ if last_message["role"] != 'user':
604
+ raise ValueError('此模型最后一条消息必须是用户消息!')
605
+ todo_messages = [last_message]
606
+ file_attachments = []
607
+ messages = ''
608
+ last_role = None
609
+ last_content = ''
610
+ message_length = 0
611
+ convert_to_file = False
612
+ last_message_content = ''
613
+ search = request["model"] in ['grok-2-search', 'grok-3-search']
614
+
615
+ # 移除<think>标签及其内容和base64图片
616
+ def remove_think_tags(text):
617
+ import re
618
+ text = re.sub(r'<think>[\s\S]*?<\/think>', '', text).strip()
619
+ text = re.sub(r'!\[image\]\(data:.*?base64,.*?\)', '[图片]', text)
620
+ return text
621
+
622
+ def process_content(content):
623
+ if isinstance(content, list):
624
+ text_content = ''
625
+ for item in content:
626
+ if item["type"] == 'image_url':
627
+ text_content += ("[图片]" if not text_content else '\n[图片]')
628
+ elif item["type"] == 'text':
629
+ text_content += (remove_think_tags(item["text"]) if not text_content else '\n' + remove_think_tags(item["text"]))
630
+ return text_content
631
+ elif isinstance(content, dict) and content is not None:
632
+ if content["type"] == 'image_url':
633
+ return "[图片]"
634
+ elif content["type"] == 'text':
635
+ return remove_think_tags(content["text"])
636
+ return remove_think_tags(self.process_message_content(content))
637
+
638
+ for current in todo_messages:
639
+ role = 'assistant' if current["role"] == 'assistant' else 'user'
640
+ is_last_message = current == todo_messages[-1]
641
+
642
+ if is_last_message and "content" in current:
643
+ if isinstance(current["content"], list):
644
+ for item in current["content"]:
645
+ if item["type"] == 'image_url':
646
+ processed_image = self.upload_base64_image(
647
+ item["image_url"]["url"],
648
+ f"{CONFIG['API']['BASE_URL']}/api/rpc"
649
+ )
650
+ if processed_image:
651
+ file_attachments.append(processed_image)
652
+ elif isinstance(current["content"], dict) and current["content"].get("type") == 'image_url':
653
+ processed_image = self.upload_base64_image(
654
+ current["content"]["image_url"]["url"],
655
+ f"{CONFIG['API']['BASE_URL']}/api/rpc"
656
+ )
657
+ if processed_image:
658
+ file_attachments.append(processed_image)
659
+
660
+
661
+ text_content = process_content(current.get("content", ""))
662
+ if is_last_message and convert_to_file:
663
+ last_message_content = f"{role.upper()}: {text_content or '[图片]'}\n"
664
+ continue
665
+ if text_content or (is_last_message and file_attachments):
666
+ if role == last_role and text_content:
667
+ last_content += '\n' + text_content
668
+ messages = messages[:messages.rindex(f"{role.upper()}: ")] + f"{role.upper()}: {last_content}\n"
669
+ else:
670
+ messages += f"{role.upper()}: {text_content or '[图片]'}\n"
671
+ last_content = text_content
672
+ last_role = role
673
+ message_length += len(messages)
674
+ if message_length >= 40000:
675
+ convert_to_file = True
676
+
677
+ if convert_to_file:
678
+ file_id = self.upload_base64_file(messages, request["model"])
679
+ if file_id:
680
+ file_attachments.insert(0, file_id)
681
+ messages = last_message_content.strip()
682
+ if messages.strip() == '':
683
+ if convert_to_file:
684
+ messages = '基于txt文件内容进行回复:'
685
+ else:
686
+ raise ValueError('消息内容为空!')
687
+ return {
688
+ "temporary": CONFIG["API"].get("IS_TEMP_CONVERSATION", False),
689
+ "modelName": self.model_id,
690
+ "message": messages.strip(),
691
+ "fileAttachments": file_attachments[:4],
692
+ "imageAttachments": [],
693
+ "disableSearch": False,
694
+ "enableImageGeneration": True,
695
+ "returnImageBytes": False,
696
+ "returnRawGrokInXaiRequest": False,
697
+ "enableImageStreaming": False,
698
+ "imageGenerationCount": 1,
699
+ "forceConcise": False,
700
+ "toolOverrides": {
701
+ "imageGen": request["model"] in ['grok-2-imageGen', 'grok-3-imageGen'],
702
+ "webSearch": search,
703
+ "xSearch": search,
704
+ "xMediaSearch": search,
705
+ "trendsSearch": search,
706
+ "xPostAnalyze": search
707
+ },
708
+ "enableSideBySide": True,
709
+ "isPreset": False,
710
+ "sendFinalMetadata": True,
711
+ "customInstructions": "",
712
+ "deepsearchPreset": "default" if request["model"] == 'grok-3-deepsearch' else "",
713
+ "isReasoning": request["model"] == 'grok-3-reasoning'
714
+ }
715
+
716
+ class MessageProcessor:
717
+ @staticmethod
718
+ def create_chat_response(message, model, is_stream=False):
719
+ base_response = {
720
+ "id": f"chatcmpl-{uuid.uuid4()}",
721
+ "created": int(time.time()),
722
+ "model": model
723
+ }
724
+
725
+ if is_stream:
726
+ return {
727
+ **base_response,
728
+ "object": "chat.completion.chunk",
729
+ "choices": [{
730
+ "index": 0,
731
+ "delta": {
732
+ "content": message
733
+ }
734
+ }]
735
+ }
736
+
737
+ return {
738
+ **base_response,
739
+ "object": "chat.completion",
740
+ "choices": [{
741
+ "index": 0,
742
+ "message": {
743
+ "role": "assistant",
744
+ "content": message
745
+ },
746
+ "finish_reason": "stop"
747
+ }],
748
+ "usage": None
749
+ }
750
+
751
+ def process_model_response(response, model):
752
+ result = {"token": None, "imageUrl": None}
753
+
754
+ if CONFIG["IS_IMG_GEN"]:
755
+ if response.get("cachedImageGenerationResponse") and not CONFIG["IS_IMG_GEN2"]:
756
+ result["imageUrl"] = response["cachedImageGenerationResponse"]["imageUrl"]
757
+ return result
758
+
759
+ if model == 'grok-2':
760
+ result["token"] = response.get("token")
761
+ elif model in ['grok-2-search', 'grok-3-search']:
762
+ if response.get("webSearchResults") and CONFIG["ISSHOW_SEARCH_RESULTS"]:
763
+ result["token"] = f"\r\n<think>{Utils.organize_search_results(response['webSearchResults'])}</think>\r\n"
764
+ else:
765
+ result["token"] = response.get("token")
766
+ elif model == 'grok-3':
767
+ result["token"] = response.get("token")
768
+ elif model == 'grok-3-deepsearch':
769
+ if response.get("messageStepId") and not CONFIG["SHOW_THINKING"]:
770
+ return result
771
+ if response.get("messageStepId") and not CONFIG["IS_THINKING"]:
772
+ result["token"] = "<think>" + response.get("token", "")
773
+ CONFIG["IS_THINKING"] = True
774
+ elif not response.get("messageStepId") and CONFIG["IS_THINKING"] and response.get("messageTag") == "final":
775
+ result["token"] = "</think>" + response.get("token", "")
776
+ CONFIG["IS_THINKING"] = False
777
+ elif (response.get("messageStepId") and CONFIG["IS_THINKING"] and response.get("messageTag") == "assistant") or response.get("messageTag") == "final":
778
+ result["token"] = response.get("token")
779
+ elif model == 'grok-3-reasoning':
780
+ if response.get("isThinking") and not CONFIG["SHOW_THINKING"]:
781
+ return result
782
+
783
+ if response.get("isThinking") and not CONFIG["IS_THINKING"]:
784
+ result["token"] = "<think>" + response.get("token", "")
785
+ CONFIG["IS_THINKING"] = True
786
+ elif not response.get("isThinking") and CONFIG["IS_THINKING"]:
787
+ result["token"] = "</think>" + response.get("token", "")
788
+ CONFIG["IS_THINKING"] = False
789
+ else:
790
+ result["token"] = response.get("token")
791
+
792
+ return result
793
+
794
+ def handle_image_response(image_url):
795
+ max_retries = 2
796
+ retry_count = 0
797
+ image_base64_response = None
798
+
799
+ while retry_count < max_retries:
800
+ try:
801
+ proxy_options = Utils.get_proxy_options()
802
+ image_base64_response = curl_requests.get(
803
+ f"https://assets.grok.com/{image_url}",
804
+ headers={
805
+ **DEFAULT_HEADERS,
806
+ "Cookie":CONFIG["SERVER"]['COOKIE']
807
+ },
808
+ impersonate="chrome133a",
809
+ **proxy_options
810
+ )
811
+
812
+ if image_base64_response.status_code == 200:
813
+ break
814
+
815
+ retry_count += 1
816
+ if retry_count == max_retries:
817
+ raise Exception(f"上游服务请求失败! status: {image_base64_response.status_code}")
818
+
819
+ time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)
820
+
821
+ except Exception as error:
822
+ logger.error(str(error), "Server")
823
+ retry_count += 1
824
+ if retry_count == max_retries:
825
+ raise
826
+
827
+ time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)
828
+
829
+ image_buffer = image_base64_response.content
830
+
831
+ if not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"]:
832
+ base64_image = base64.b64encode(image_buffer).decode('utf-8')
833
+ image_content_type = image_base64_response.headers.get('content-type', 'image/jpeg')
834
+ return f"![image](data:{image_content_type};base64,{base64_image})"
835
+
836
+ logger.info("开始上传图床", "Server")
837
+
838
+ if CONFIG["API"]["PICGO_KEY"]:
839
+ files = {'source': ('image.jpg', image_buffer, 'image/jpeg')}
840
+ headers = {
841
+ "X-API-Key": CONFIG["API"]["PICGO_KEY"]
842
+ }
843
+
844
+ response_url = requests.post(
845
+ "https://www.picgo.net/api/1/upload",
846
+ files=files,
847
+ headers=headers
848
+ )
849
+
850
+ if response_url.status_code != 200:
851
+ return "生图失败,请查看PICGO图床密钥是否设置正确"
852
+ else:
853
+ logger.info("生图成功", "Server")
854
+ result = response_url.json()
855
+ return f"![image]({result['image']['url']})"
856
+
857
+
858
+ elif CONFIG["API"]["TUMY_KEY"]:
859
+ files = {'file': ('image.jpg', image_buffer, 'image/jpeg')}
860
+ headers = {
861
+ "Accept": "application/json",
862
+ 'Authorization': f"Bearer {CONFIG['API']['TUMY_KEY']}"
863
+ }
864
+
865
+ response_url = requests.post(
866
+ "https://tu.my/api/v1/upload",
867
+ files=files,
868
+ headers=headers
869
+ )
870
+
871
+ if response_url.status_code != 200:
872
+ return "生图失败,请查看TUMY图床密钥是否设置正确"
873
+ else:
874
+ try:
875
+ result = response_url.json()
876
+ logger.info("生图成功", "Server")
877
+ return f"![image]({result['data']['links']['url']})"
878
+ except Exception as error:
879
+ logger.error(str(error), "Server")
880
+ return "生图失败,请查看TUMY图床密钥是否设置正确"
881
+
882
+ def handle_non_stream_response(response, model):
883
+ try:
884
+ logger.info("开始处理非流式响应", "Server")
885
+
886
+ stream = response.iter_lines()
887
+ full_response = ""
888
+
889
+ CONFIG["IS_THINKING"] = False
890
+ CONFIG["IS_IMG_GEN"] = False
891
+ CONFIG["IS_IMG_GEN2"] = False
892
+
893
+ for chunk in stream:
894
+ if not chunk:
895
+ continue
896
  try:
897
+ line_json = json.loads(chunk.decode("utf-8").strip())
898
+ if line_json.get("error"):
899
+ logger.error(json.dumps(line_json, indent=2), "Server")
900
+ return json.dumps({"error": "RateLimitError"}) + "\n\n"
901
+
902
+ response_data = line_json.get("result", {}).get("response")
903
+ if not response_data:
904
+ continue
905
+
906
+ if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):
907
+ CONFIG["IS_IMG_GEN"] = True
908
+
909
+ result = process_model_response(response_data, model)
910
+
911
+ if result["token"]:
912
+ full_response += result["token"]
913
+
914
+ if result["imageUrl"]:
915
+ CONFIG["IS_IMG_GEN2"] = True
916
+ return handle_image_response(result["imageUrl"])
917
+
918
+ except json.JSONDecodeError:
919
+ continue
920
  except Exception as e:
921
+ logger.error(f"处理流式响应行时出错: {str(e)}", "Server")
922
+ continue
923
+
924
+ return full_response
925
+ except Exception as error:
926
+ logger.error(str(error), "Server")
927
+ raise
928
+ def handle_stream_response(response, model):
929
+ def generate():
930
+ logger.info("开始处理流式响应", "Server")
931
 
932
+ stream = response.iter_lines()
933
+ CONFIG["IS_THINKING"] = False
934
+ CONFIG["IS_IMG_GEN"] = False
935
+ CONFIG["IS_IMG_GEN2"] = False
936
+
937
+ for chunk in stream:
938
+ if not chunk:
939
+ continue
940
  try:
941
+ line_json = json.loads(chunk.decode("utf-8").strip())
942
+ if line_json.get("error"):
943
+ logger.error(json.dumps(line_json, indent=2), "Server")
944
+ yield json.dumps({"error": "RateLimitError"}) + "\n\n"
945
+ return
946
+
947
+ response_data = line_json.get("result", {}).get("response")
948
+ if not response_data:
949
+ continue
950
+
951
+ if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):
952
+ CONFIG["IS_IMG_GEN"] = True
953
+
954
+ result = process_model_response(response_data, model)
955
+
956
+ if result["token"]:
957
+ yield f"data: {json.dumps(MessageProcessor.create_chat_response(result['token'], model, True))}\n\n"
958
+
959
+ if result["imageUrl"]:
960
+ CONFIG["IS_IMG_GEN2"] = True
961
+ image_data = handle_image_response(result["imageUrl"])
962
+ yield f"data: {json.dumps(MessageProcessor.create_chat_response(image_data, model, True))}\n\n"
963
+
964
+ except json.JSONDecodeError:
965
+ continue
966
  except Exception as e:
967
+ logger.error(f"处理流式响应行时出错: {str(e)}", "Server")
968
+ continue
969
+
970
+ yield "data: [DONE]\n\n"
971
+ return generate()
972
+
973
+ def initialization():
974
+ sso_array = os.getenv("SSO", "").split(',')
975
+ logger.info("开始加载令牌", "Server")
976
+ for sso in sso_array:
977
+ if sso:
978
+ token_manager.add_token(f"sso-rw={sso};sso={sso}")
979
+
980
+ logger.info(f"成功加载令牌: {json.dumps(token_manager.get_all_tokens(), indent=2)}", "Server")
981
+ logger.info(f"令牌加载完成,共加载: {len(token_manager.get_all_tokens())}个令牌", "Server")
982
+
983
+ if CONFIG["API"]["PROXY"]:
984
+ logger.info(f"代理已设置: {CONFIG['API']['PROXY']}", "Server")
985
+
986
+ logger.info("获取xids")
987
+
988
+ xids = get_xid.main()
989
+ if xids is not None:
990
+ CONFIG['XIDS'] = xids
991
+
992
+ logger.info("初始化完成", "Server")
993
+
994
+
995
+ app = Flask(__name__)
996
+ app.wsgi_app = ProxyFix(app.wsgi_app)
997
+ app.secret_key = os.getenv('FLASK_SECRET_KEY') or secrets.token_hex(16)
998
+ app.json.sort_keys = False
999
+
1000
+ @app.route('/manager/login', methods=['GET', 'POST'])
1001
+ def manager_login():
1002
+ if CONFIG["ADMIN"]["MANAGER_SWITCH"]:
1003
+ if request.method == 'POST':
1004
+ password = request.form.get('password')
1005
+ if password == CONFIG["ADMIN"]["PASSWORD"]:
1006
+ session['is_logged_in'] = True
1007
+ return redirect('/manager')
1008
+ return render_template('login.html', error=True)
1009
+ return render_template('login.html', error=False)
1010
+ else:
1011
+ return redirect('/')
1012
+
1013
+ def check_auth():
1014
+ return session.get('is_logged_in', False)
1015
+
1016
+ @app.route('/manager')
1017
+ def manager():
1018
+ if not check_auth():
1019
+ return redirect('/manager/login')
1020
+ return render_template('manager.html')
1021
+
1022
+ @app.route('/manager/api/get')
1023
+ def get_manager_tokens():
1024
+ if not check_auth():
1025
+ return jsonify({"error": "Unauthorized"}), 401
1026
+ return jsonify(token_manager.get_token_status_map())
1027
+
1028
+ @app.route('/manager/api/add', methods=['POST'])
1029
+ def add_manager_token():
1030
+ if not check_auth():
1031
+ return jsonify({"error": "Unauthorized"}), 401
1032
+ try:
1033
+ sso = request.json.get('sso')
1034
+ if not sso:
1035
+ return jsonify({"error": "SSO token is required"}), 400
1036
+ token_manager.add_token(f"sso-rw={sso};sso={sso}")
1037
+ return jsonify({"success": True})
1038
+ except Exception as e:
1039
+ return jsonify({"error": str(e)}), 500
1040
+
1041
+ @app.route('/manager/api/delete', methods=['POST'])
1042
+ def delete_manager_token():
1043
+ if not check_auth():
1044
+ return jsonify({"error": "Unauthorized"}), 401
1045
+ try:
1046
+ sso = request.json.get('sso')
1047
+ if not sso:
1048
+ return jsonify({"error": "SSO token is required"}), 400
1049
+ token_manager.delete_token(f"sso-rw={sso};sso={sso}")
1050
+ return jsonify({"success": True})
1051
+ except Exception as e:
1052
+ return jsonify({"error": str(e)}), 500
1053
+
1054
+ @app.route('/manager/api/cf_clearance', methods=['POST'])
1055
+ def setCf_Manager_clearance():
1056
+ if not check_auth():
1057
+ return jsonify({"error": "Unauthorized"}), 401
1058
+ try:
1059
+ cf_clearance = request.json.get('cf_clearance')
1060
+ if not cf_clearance:
1061
+ return jsonify({"error": "cf_clearance is required"}), 400
1062
+ CONFIG["SERVER"]['CF_CLEARANCE'] = cf_clearance
1063
+ return jsonify({"success": True})
1064
+ except Exception as e:
1065
+ return jsonify({"error": str(e)}), 500
1066
+
1067
+
1068
+ @app.route('/get/tokens', methods=['GET'])
1069
+ def get_tokens():
1070
+ auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
1071
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1072
+ return jsonify({"error": '自定义的SSO令牌模式无法获取轮询sso令牌状态'}), 403
1073
+ elif auth_token != CONFIG["API"]["API_KEY"]:
1074
+ return jsonify({"error": 'Unauthorized'}), 401
1075
+ return jsonify(token_manager.get_token_status_map())
1076
+
1077
+ @app.route('/add/token', methods=['POST'])
1078
+ def add_token():
1079
+ auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
1080
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1081
+ return jsonify({"error": '自定义的SSO令牌模式无法添加sso令牌'}), 403
1082
+ elif auth_token != CONFIG["API"]["API_KEY"]:
1083
+ return jsonify({"error": 'Unauthorized'}), 401
1084
+
1085
+ try:
1086
+ sso = request.json.get('sso')
1087
+ token_manager.add_token(f"sso-rw={sso};sso={sso}")
1088
+ return jsonify(token_manager.get_token_status_map().get(sso, {})), 200
1089
+ except Exception as error:
1090
+ logger.error(str(error), "Server")
1091
+ return jsonify({"error": '添加sso令牌失败'}), 500
1092
+
1093
+ @app.route('/set/cf_clearance', methods=['POST'])
1094
+ def setCf_clearance():
1095
+ auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
1096
+ if auth_token != CONFIG["API"]["API_KEY"]:
1097
+ return jsonify({"error": 'Unauthorized'}), 401
1098
+ try:
1099
+ cf_clearance = request.json.get('cf_clearance')
1100
+ CONFIG["SERVER"]['CF_CLEARANCE'] = cf_clearance
1101
+ return jsonify({"message": '设置cf_clearance成功'}), 200
1102
+ except Exception as error:
1103
+ logger.error(str(error), "Server")
1104
+ return jsonify({"error": '设置cf_clearance失败'}), 500
1105
+
1106
+ @app.route('/delete/token', methods=['POST'])
1107
+ def delete_token():
1108
+ auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
1109
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1110
+ return jsonify({"error": '自定义的SSO令牌模式无法删除sso令牌'}), 403
1111
+ elif auth_token != CONFIG["API"]["API_KEY"]:
1112
+ return jsonify({"error": 'Unauthorized'}), 401
1113
+
1114
+ try:
1115
+ sso = request.json.get('sso')
1116
+ token_manager.delete_token(f"sso-rw={sso};sso={sso}")
1117
+ return jsonify({"message": '删除sso令牌成功'}), 200
1118
+ except Exception as error:
1119
+ logger.error(str(error), "Server")
1120
+ return jsonify({"error": '删除sso令牌失败'}), 500
1121
+
1122
+ @app.route('/v1/models', methods=['GET'])
1123
+ def get_models():
1124
+ return jsonify({
1125
+ "object": "list",
1126
+ "data": [
1127
+ {
1128
+ "id": model,
1129
+ "object": "model",
1130
+ "created": int(time.time()),
1131
+ "owned_by": "grok"
1132
+ }
1133
+ for model in CONFIG["MODELS"].keys()
1134
+ ]
1135
+ })
1136
+
1137
+ @app.route('/v1/chat/completions', methods=['POST'])
1138
+ def chat_completions():
1139
+ response_status_code = 500
1140
+ try:
1141
+ auth_token = request.headers.get('Authorization',
1142
+ '').replace('Bearer ', '')
1143
+ if auth_token:
1144
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1145
+ result = f"sso={auth_token};sso-rw={auth_token}"
1146
+ token_manager.set_token(result)
1147
+ elif auth_token != CONFIG["API"]["API_KEY"]:
1148
+ return jsonify({"error": 'Unauthorized'}), 401
1149
+ else:
1150
+ return jsonify({"error": 'API_KEY缺失'}), 401
1151
+
1152
+ data = request.json
1153
+ model = data.get("model")
1154
+ stream = data.get("stream", False)
1155
+
1156
+ retry_count = 0
1157
+ grok_client = GrokApiClient(model)
1158
+ request_payload = grok_client.prepare_chat_request(data)
1159
+
1160
+
1161
+ while retry_count < CONFIG["RETRY"]["MAX_ATTEMPTS"]:
1162
+ retry_count += 1
1163
+ CONFIG["API"]["SIGNATURE_COOKIE"] = Utils.create_auth_headers(model)
1164
+
1165
+ if not CONFIG["API"]["SIGNATURE_COOKIE"]:
1166
+ raise ValueError('该模型无可用令牌')
1167
+
1168
+ logger.info(
1169
+ f"当前令牌: {json.dumps(CONFIG['API']['SIGNATURE_COOKIE'], indent=2)}","Server")
1170
+ logger.info(
1171
+ f"当前可用模型的全部可用数量: {json.dumps(token_manager.get_remaining_token_request_capacity(), indent=2)}","Server")
1172
+
1173
+ if CONFIG['SERVER']['CF_CLEARANCE']:
1174
+ CONFIG["SERVER"]['COOKIE'] = f"{CONFIG['API']['SIGNATURE_COOKIE']};{CONFIG['SERVER']['CF_CLEARANCE']}"
1175
  else:
1176
+ CONFIG["SERVER"]['COOKIE'] = CONFIG['API']['SIGNATURE_COOKIE']
1177
+ logger.info("有请求到来","Server")
1178
+ try:
1179
+ proxy_options = Utils.get_proxy_options()
1180
+ response = curl_requests.post(
1181
+ f"{CONFIG['API']['BASE_URL']}/rest/app-chat/conversations/new",
1182
+ headers={
1183
+ **DEFAULT_HEADERS,
1184
+ "Cookie":CONFIG["SERVER"]['COOKIE']
1185
+ },
1186
+ json=request_payload,
1187
+ impersonate="chrome133a",
1188
+ verify=False,
1189
+ stream=True,
1190
+ **proxy_options)
1191
+ logger.info(CONFIG["SERVER"]['COOKIE'],"Server")
1192
+ if response.status_code == 200:
1193
+ response_status_code = 200
1194
+ logger.info("请求成功", "Server")
1195
+ logger.info(f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}","Server")
1196
 
1197
+ try:
1198
+ if stream:
1199
+ return Response(stream_with_context(
1200
+ handle_stream_response(response, model)),content_type='text/event-stream')
1201
+ else:
1202
+ content = handle_non_stream_response(response, model)
1203
+ return jsonify(
1204
+ MessageProcessor.create_chat_response(content, model))
1205
+
1206
+ except Exception as error:
1207
+ logger.error(str(error), "Server")
1208
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1209
+ raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
1210
+ token_manager.remove_token_from_model(model, CONFIG["API"]["SIGNATURE_COOKIE"])
1211
+ if token_manager.get_token_count_for_model(model) == 0:
1212
+ raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
1213
+ elif response.status_code == 403:
1214
+ response_status_code = 403
1215
+ token_manager.reduce_token_request_count(model,1)#重置去除当前因为错误未成功请求的次数,确保不会因为错误未成功请求的次数导致次数上限
1216
+ if token_manager.get_token_count_for_model(model) == 0:
1217
+ raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
1218
+ raise ValueError(f"IP暂时被封无法破盾,请稍后重试或者更换ip")
1219
+ elif response.status_code == 429:
1220
+ response_status_code = 429
1221
+ token_manager.reduce_token_request_count(model,1)
1222
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1223
+ raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
1224
+
1225
+ token_manager.remove_token_from_model(
1226
+ model, CONFIG["API"]["SIGNATURE_COOKIE"])
1227
+ if token_manager.get_token_count_for_model(model) == 0:
1228
+ raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
1229
+
1230
+ else:
1231
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1232
+ raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
1233
+
1234
+ logger.error(f"令牌异常错误状态!status: {response.status_code}","Server")
1235
+ token_manager.remove_token_from_model(model, CONFIG["API"]["SIGNATURE_COOKIE"])
1236
+ logger.info(
1237
+ f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}",
1238
+ "Server")
1239
+
1240
+ except Exception as e:
1241
+ logger.error(f"请求处理异常: {str(e)}", "Server")
1242
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1243
+ raise
1244
+ continue
1245
+ if response_status_code == 403:
1246
+ raise ValueError('IP暂时被封无法破盾,请稍后重试或者更换ip')
1247
+ elif response_status_code == 500:
1248
+ raise ValueError('当前模型所有令牌暂无可用,请稍后重试')
1249
+
1250
+ except Exception as error:
1251
+ logger.error(str(error), "ChatAPI")
1252
+ return jsonify(
1253
+ {"error": {
1254
+ "message": str(error),
1255
+ "type": "server_error"
1256
+ }}), response_status_code
1257
+
1258
+ @app.route('/', defaults={'path': ''})
1259
+ @app.route('/<path:path>')
1260
+ def catch_all(path):
1261
+ return 'api运行正常', 200
1262
 
1263
+ if __name__ == '__main__':
1264
+ token_manager = AuthTokenManager()
1265
+ initialization()
1266
 
1267
+ app.run(
1268
+ host='0.0.0.0',
1269
+ port=CONFIG["SERVER"]["PORT"],
1270
+ debug=False
1271
+ )