TheSmallHanCat commited on
Commit
49d8e1d
·
1 Parent(s): 7fc098b

feat: 支持更多打码平台

Browse files
src/api/admin.py CHANGED
@@ -853,6 +853,12 @@ async def update_captcha_config(
853
  captcha_method = request.get("captcha_method")
854
  yescaptcha_api_key = request.get("yescaptcha_api_key")
855
  yescaptcha_base_url = request.get("yescaptcha_base_url")
 
 
 
 
 
 
856
  browser_proxy_enabled = request.get("browser_proxy_enabled", False)
857
  browser_proxy_url = request.get("browser_proxy_url", "")
858
 
@@ -866,6 +872,12 @@ async def update_captcha_config(
866
  captcha_method=captcha_method,
867
  yescaptcha_api_key=yescaptcha_api_key,
868
  yescaptcha_base_url=yescaptcha_base_url,
 
 
 
 
 
 
869
  browser_proxy_enabled=browser_proxy_enabled,
870
  browser_proxy_url=browser_proxy_url if browser_proxy_enabled else None
871
  )
@@ -884,6 +896,12 @@ async def get_captcha_config(token: str = Depends(verify_admin_token)):
884
  "captcha_method": captcha_config.captcha_method,
885
  "yescaptcha_api_key": captcha_config.yescaptcha_api_key,
886
  "yescaptcha_base_url": captcha_config.yescaptcha_base_url,
 
 
 
 
 
 
887
  "browser_proxy_enabled": captcha_config.browser_proxy_enabled,
888
  "browser_proxy_url": captcha_config.browser_proxy_url or ""
889
  }
 
853
  captcha_method = request.get("captcha_method")
854
  yescaptcha_api_key = request.get("yescaptcha_api_key")
855
  yescaptcha_base_url = request.get("yescaptcha_base_url")
856
+ capmonster_api_key = request.get("capmonster_api_key")
857
+ capmonster_base_url = request.get("capmonster_base_url")
858
+ ezcaptcha_api_key = request.get("ezcaptcha_api_key")
859
+ ezcaptcha_base_url = request.get("ezcaptcha_base_url")
860
+ capsolver_api_key = request.get("capsolver_api_key")
861
+ capsolver_base_url = request.get("capsolver_base_url")
862
  browser_proxy_enabled = request.get("browser_proxy_enabled", False)
863
  browser_proxy_url = request.get("browser_proxy_url", "")
864
 
 
872
  captcha_method=captcha_method,
873
  yescaptcha_api_key=yescaptcha_api_key,
874
  yescaptcha_base_url=yescaptcha_base_url,
875
+ capmonster_api_key=capmonster_api_key,
876
+ capmonster_base_url=capmonster_base_url,
877
+ ezcaptcha_api_key=ezcaptcha_api_key,
878
+ ezcaptcha_base_url=ezcaptcha_base_url,
879
+ capsolver_api_key=capsolver_api_key,
880
+ capsolver_base_url=capsolver_base_url,
881
  browser_proxy_enabled=browser_proxy_enabled,
882
  browser_proxy_url=browser_proxy_url if browser_proxy_enabled else None
883
  )
 
896
  "captcha_method": captcha_config.captcha_method,
897
  "yescaptcha_api_key": captcha_config.yescaptcha_api_key,
898
  "yescaptcha_base_url": captcha_config.yescaptcha_base_url,
899
+ "capmonster_api_key": captcha_config.capmonster_api_key,
900
+ "capmonster_base_url": captcha_config.capmonster_base_url,
901
+ "ezcaptcha_api_key": captcha_config.ezcaptcha_api_key,
902
+ "ezcaptcha_base_url": captcha_config.ezcaptcha_base_url,
903
+ "capsolver_api_key": captcha_config.capsolver_api_key,
904
+ "capsolver_base_url": captcha_config.capsolver_base_url,
905
  "browser_proxy_enabled": captcha_config.browser_proxy_enabled,
906
  "browser_proxy_url": captcha_config.browser_proxy_url or ""
907
  }
src/core/config.py CHANGED
@@ -213,6 +213,72 @@ class Config:
213
  self._config["captcha"] = {}
214
  self._config["captcha"]["yescaptcha_base_url"] = base_url
215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
 
217
  # Global config instance
218
  config = Config()
 
213
  self._config["captcha"] = {}
214
  self._config["captcha"]["yescaptcha_base_url"] = base_url
215
 
216
+ @property
217
+ def capmonster_api_key(self) -> str:
218
+ """Get CapMonster API key"""
219
+ return self._config.get("captcha", {}).get("capmonster_api_key", "")
220
+
221
+ def set_capmonster_api_key(self, api_key: str):
222
+ """Set CapMonster API key"""
223
+ if "captcha" not in self._config:
224
+ self._config["captcha"] = {}
225
+ self._config["captcha"]["capmonster_api_key"] = api_key
226
+
227
+ @property
228
+ def capmonster_base_url(self) -> str:
229
+ """Get CapMonster base URL"""
230
+ return self._config.get("captcha", {}).get("capmonster_base_url", "https://api.capmonster.cloud")
231
+
232
+ def set_capmonster_base_url(self, base_url: str):
233
+ """Set CapMonster base URL"""
234
+ if "captcha" not in self._config:
235
+ self._config["captcha"] = {}
236
+ self._config["captcha"]["capmonster_base_url"] = base_url
237
+
238
+ @property
239
+ def ezcaptcha_api_key(self) -> str:
240
+ """Get EzCaptcha API key"""
241
+ return self._config.get("captcha", {}).get("ezcaptcha_api_key", "")
242
+
243
+ def set_ezcaptcha_api_key(self, api_key: str):
244
+ """Set EzCaptcha API key"""
245
+ if "captcha" not in self._config:
246
+ self._config["captcha"] = {}
247
+ self._config["captcha"]["ezcaptcha_api_key"] = api_key
248
+
249
+ @property
250
+ def ezcaptcha_base_url(self) -> str:
251
+ """Get EzCaptcha base URL"""
252
+ return self._config.get("captcha", {}).get("ezcaptcha_base_url", "https://api.ez-captcha.com")
253
+
254
+ def set_ezcaptcha_base_url(self, base_url: str):
255
+ """Set EzCaptcha base URL"""
256
+ if "captcha" not in self._config:
257
+ self._config["captcha"] = {}
258
+ self._config["captcha"]["ezcaptcha_base_url"] = base_url
259
+
260
+ @property
261
+ def capsolver_api_key(self) -> str:
262
+ """Get CapSolver API key"""
263
+ return self._config.get("captcha", {}).get("capsolver_api_key", "")
264
+
265
+ def set_capsolver_api_key(self, api_key: str):
266
+ """Set CapSolver API key"""
267
+ if "captcha" not in self._config:
268
+ self._config["captcha"] = {}
269
+ self._config["captcha"]["capsolver_api_key"] = api_key
270
+
271
+ @property
272
+ def capsolver_base_url(self) -> str:
273
+ """Get CapSolver base URL"""
274
+ return self._config.get("captcha", {}).get("capsolver_base_url", "https://api.capsolver.com")
275
+
276
+ def set_capsolver_base_url(self, base_url: str):
277
+ """Set CapSolver base URL"""
278
+ if "captcha" not in self._config:
279
+ self._config["captcha"] = {}
280
+ self._config["captcha"]["capsolver_base_url"] = base_url
281
+
282
 
283
  # Global config instance
284
  config = Config()
src/core/database.py CHANGED
@@ -216,6 +216,12 @@ class Database:
216
  captcha_method TEXT DEFAULT 'browser',
217
  yescaptcha_api_key TEXT DEFAULT '',
218
  yescaptcha_base_url TEXT DEFAULT 'https://api.yescaptcha.com',
 
 
 
 
 
 
219
  website_key TEXT DEFAULT '6LdsFiUsAAAAAIjVDZcuLhaHiDn5nnHVXVRQGeMV',
220
  page_action TEXT DEFAULT 'FLOW_GENERATION',
221
  browser_proxy_enabled BOOLEAN DEFAULT 0,
@@ -277,6 +283,12 @@ class Database:
277
  captcha_columns_to_add = [
278
  ("browser_proxy_enabled", "BOOLEAN DEFAULT 0"),
279
  ("browser_proxy_url", "TEXT"),
 
 
 
 
 
 
280
  ]
281
 
282
  for col_name, col_type in captcha_columns_to_add:
@@ -489,6 +501,12 @@ class Database:
489
  captcha_method TEXT DEFAULT 'browser',
490
  yescaptcha_api_key TEXT DEFAULT '',
491
  yescaptcha_base_url TEXT DEFAULT 'https://api.yescaptcha.com',
 
 
 
 
 
 
492
  website_key TEXT DEFAULT '6LdsFiUsAAAAAIjVDZcuLhaHiDn5nnHVXVRQGeMV',
493
  page_action TEXT DEFAULT 'FLOW_GENERATION',
494
  browser_proxy_enabled BOOLEAN DEFAULT 0,
@@ -1199,6 +1217,12 @@ class Database:
1199
  captcha_method: str = None,
1200
  yescaptcha_api_key: str = None,
1201
  yescaptcha_base_url: str = None,
 
 
 
 
 
 
1202
  browser_proxy_enabled: bool = None,
1203
  browser_proxy_url: str = None
1204
  ):
@@ -1211,28 +1235,47 @@ class Database:
1211
  if row:
1212
  current = dict(row)
1213
  new_method = captcha_method if captcha_method is not None else current.get("captcha_method", "yescaptcha")
1214
- new_api_key = yescaptcha_api_key if yescaptcha_api_key is not None else current.get("yescaptcha_api_key", "")
1215
- new_base_url = yescaptcha_base_url if yescaptcha_base_url is not None else current.get("yescaptcha_base_url", "https://api.yescaptcha.com")
 
 
 
 
 
 
1216
  new_proxy_enabled = browser_proxy_enabled if browser_proxy_enabled is not None else current.get("browser_proxy_enabled", False)
1217
  new_proxy_url = browser_proxy_url if browser_proxy_url is not None else current.get("browser_proxy_url")
1218
 
1219
  await db.execute("""
1220
  UPDATE captcha_config
1221
  SET captcha_method = ?, yescaptcha_api_key = ?, yescaptcha_base_url = ?,
 
 
 
1222
  browser_proxy_enabled = ?, browser_proxy_url = ?, updated_at = CURRENT_TIMESTAMP
1223
  WHERE id = 1
1224
- """, (new_method, new_api_key, new_base_url, new_proxy_enabled, new_proxy_url))
 
1225
  else:
1226
  new_method = captcha_method if captcha_method is not None else "yescaptcha"
1227
- new_api_key = yescaptcha_api_key if yescaptcha_api_key is not None else ""
1228
- new_base_url = yescaptcha_base_url if yescaptcha_base_url is not None else "https://api.yescaptcha.com"
 
 
 
 
 
 
1229
  new_proxy_enabled = browser_proxy_enabled if browser_proxy_enabled is not None else False
1230
  new_proxy_url = browser_proxy_url
1231
 
1232
  await db.execute("""
1233
- INSERT INTO captcha_config (id, captcha_method, yescaptcha_api_key, yescaptcha_base_url, browser_proxy_enabled, browser_proxy_url)
1234
- VALUES (1, ?, ?, ?, ?, ?)
1235
- """, (new_method, new_api_key, new_base_url, new_proxy_enabled, new_proxy_url))
 
 
 
1236
 
1237
  await db.commit()
1238
 
 
216
  captcha_method TEXT DEFAULT 'browser',
217
  yescaptcha_api_key TEXT DEFAULT '',
218
  yescaptcha_base_url TEXT DEFAULT 'https://api.yescaptcha.com',
219
+ capmonster_api_key TEXT DEFAULT '',
220
+ capmonster_base_url TEXT DEFAULT 'https://api.capmonster.cloud',
221
+ ezcaptcha_api_key TEXT DEFAULT '',
222
+ ezcaptcha_base_url TEXT DEFAULT 'https://api.ez-captcha.com',
223
+ capsolver_api_key TEXT DEFAULT '',
224
+ capsolver_base_url TEXT DEFAULT 'https://api.capsolver.com',
225
  website_key TEXT DEFAULT '6LdsFiUsAAAAAIjVDZcuLhaHiDn5nnHVXVRQGeMV',
226
  page_action TEXT DEFAULT 'FLOW_GENERATION',
227
  browser_proxy_enabled BOOLEAN DEFAULT 0,
 
283
  captcha_columns_to_add = [
284
  ("browser_proxy_enabled", "BOOLEAN DEFAULT 0"),
285
  ("browser_proxy_url", "TEXT"),
286
+ ("capmonster_api_key", "TEXT DEFAULT ''"),
287
+ ("capmonster_base_url", "TEXT DEFAULT 'https://api.capmonster.cloud'"),
288
+ ("ezcaptcha_api_key", "TEXT DEFAULT ''"),
289
+ ("ezcaptcha_base_url", "TEXT DEFAULT 'https://api.ez-captcha.com'"),
290
+ ("capsolver_api_key", "TEXT DEFAULT ''"),
291
+ ("capsolver_base_url", "TEXT DEFAULT 'https://api.capsolver.com'"),
292
  ]
293
 
294
  for col_name, col_type in captcha_columns_to_add:
 
501
  captcha_method TEXT DEFAULT 'browser',
502
  yescaptcha_api_key TEXT DEFAULT '',
503
  yescaptcha_base_url TEXT DEFAULT 'https://api.yescaptcha.com',
504
+ capmonster_api_key TEXT DEFAULT '',
505
+ capmonster_base_url TEXT DEFAULT 'https://api.capmonster.cloud',
506
+ ezcaptcha_api_key TEXT DEFAULT '',
507
+ ezcaptcha_base_url TEXT DEFAULT 'https://api.ez-captcha.com',
508
+ capsolver_api_key TEXT DEFAULT '',
509
+ capsolver_base_url TEXT DEFAULT 'https://api.capsolver.com',
510
  website_key TEXT DEFAULT '6LdsFiUsAAAAAIjVDZcuLhaHiDn5nnHVXVRQGeMV',
511
  page_action TEXT DEFAULT 'FLOW_GENERATION',
512
  browser_proxy_enabled BOOLEAN DEFAULT 0,
 
1217
  captcha_method: str = None,
1218
  yescaptcha_api_key: str = None,
1219
  yescaptcha_base_url: str = None,
1220
+ capmonster_api_key: str = None,
1221
+ capmonster_base_url: str = None,
1222
+ ezcaptcha_api_key: str = None,
1223
+ ezcaptcha_base_url: str = None,
1224
+ capsolver_api_key: str = None,
1225
+ capsolver_base_url: str = None,
1226
  browser_proxy_enabled: bool = None,
1227
  browser_proxy_url: str = None
1228
  ):
 
1235
  if row:
1236
  current = dict(row)
1237
  new_method = captcha_method if captcha_method is not None else current.get("captcha_method", "yescaptcha")
1238
+ new_yes_key = yescaptcha_api_key if yescaptcha_api_key is not None else current.get("yescaptcha_api_key", "")
1239
+ new_yes_url = yescaptcha_base_url if yescaptcha_base_url is not None else current.get("yescaptcha_base_url", "https://api.yescaptcha.com")
1240
+ new_cap_key = capmonster_api_key if capmonster_api_key is not None else current.get("capmonster_api_key", "")
1241
+ new_cap_url = capmonster_base_url if capmonster_base_url is not None else current.get("capmonster_base_url", "https://api.capmonster.cloud")
1242
+ new_ez_key = ezcaptcha_api_key if ezcaptcha_api_key is not None else current.get("ezcaptcha_api_key", "")
1243
+ new_ez_url = ezcaptcha_base_url if ezcaptcha_base_url is not None else current.get("ezcaptcha_base_url", "https://api.ez-captcha.com")
1244
+ new_cs_key = capsolver_api_key if capsolver_api_key is not None else current.get("capsolver_api_key", "")
1245
+ new_cs_url = capsolver_base_url if capsolver_base_url is not None else current.get("capsolver_base_url", "https://api.capsolver.com")
1246
  new_proxy_enabled = browser_proxy_enabled if browser_proxy_enabled is not None else current.get("browser_proxy_enabled", False)
1247
  new_proxy_url = browser_proxy_url if browser_proxy_url is not None else current.get("browser_proxy_url")
1248
 
1249
  await db.execute("""
1250
  UPDATE captcha_config
1251
  SET captcha_method = ?, yescaptcha_api_key = ?, yescaptcha_base_url = ?,
1252
+ capmonster_api_key = ?, capmonster_base_url = ?,
1253
+ ezcaptcha_api_key = ?, ezcaptcha_base_url = ?,
1254
+ capsolver_api_key = ?, capsolver_base_url = ?,
1255
  browser_proxy_enabled = ?, browser_proxy_url = ?, updated_at = CURRENT_TIMESTAMP
1256
  WHERE id = 1
1257
+ """, (new_method, new_yes_key, new_yes_url, new_cap_key, new_cap_url,
1258
+ new_ez_key, new_ez_url, new_cs_key, new_cs_url, new_proxy_enabled, new_proxy_url))
1259
  else:
1260
  new_method = captcha_method if captcha_method is not None else "yescaptcha"
1261
+ new_yes_key = yescaptcha_api_key if yescaptcha_api_key is not None else ""
1262
+ new_yes_url = yescaptcha_base_url if yescaptcha_base_url is not None else "https://api.yescaptcha.com"
1263
+ new_cap_key = capmonster_api_key if capmonster_api_key is not None else ""
1264
+ new_cap_url = capmonster_base_url if capmonster_base_url is not None else "https://api.capmonster.cloud"
1265
+ new_ez_key = ezcaptcha_api_key if ezcaptcha_api_key is not None else ""
1266
+ new_ez_url = ezcaptcha_base_url if ezcaptcha_base_url is not None else "https://api.ez-captcha.com"
1267
+ new_cs_key = capsolver_api_key if capsolver_api_key is not None else ""
1268
+ new_cs_url = capsolver_base_url if capsolver_base_url is not None else "https://api.capsolver.com"
1269
  new_proxy_enabled = browser_proxy_enabled if browser_proxy_enabled is not None else False
1270
  new_proxy_url = browser_proxy_url
1271
 
1272
  await db.execute("""
1273
+ INSERT INTO captcha_config (id, captcha_method, yescaptcha_api_key, yescaptcha_base_url,
1274
+ capmonster_api_key, capmonster_base_url, ezcaptcha_api_key, ezcaptcha_base_url,
1275
+ capsolver_api_key, capsolver_base_url, browser_proxy_enabled, browser_proxy_url)
1276
+ VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1277
+ """, (new_method, new_yes_key, new_yes_url, new_cap_key, new_cap_url,
1278
+ new_ez_key, new_ez_url, new_cs_key, new_cs_url, new_proxy_enabled, new_proxy_url))
1279
 
1280
  await db.commit()
1281
 
src/core/models.py CHANGED
@@ -147,9 +147,15 @@ class DebugConfig(BaseModel):
147
  class CaptchaConfig(BaseModel):
148
  """Captcha configuration"""
149
  id: int = 1
150
- captcha_method: str = "browser" # yescaptcha 或 browser
151
  yescaptcha_api_key: str = ""
152
  yescaptcha_base_url: str = "https://api.yescaptcha.com"
 
 
 
 
 
 
153
  website_key: str = "6LdsFiUsAAAAAIjVDZcuLhaHiDn5nnHVXVRQGeMV"
154
  page_action: str = "FLOW_GENERATION"
155
  browser_proxy_enabled: bool = False # 浏览器打码是否启用代理
 
147
  class CaptchaConfig(BaseModel):
148
  """Captcha configuration"""
149
  id: int = 1
150
+ captcha_method: str = "browser" # yescaptcha, capmonster, ezcaptcha, capsolver 或 browser
151
  yescaptcha_api_key: str = ""
152
  yescaptcha_base_url: str = "https://api.yescaptcha.com"
153
+ capmonster_api_key: str = ""
154
+ capmonster_base_url: str = "https://api.capmonster.cloud"
155
+ ezcaptcha_api_key: str = ""
156
+ ezcaptcha_base_url: str = "https://api.ez-captcha.com"
157
+ capsolver_api_key: str = ""
158
+ capsolver_base_url: str = "https://api.capsolver.com"
159
  website_key: str = "6LdsFiUsAAAAAIjVDZcuLhaHiDn5nnHVXVRQGeMV"
160
  page_action: str = "FLOW_GENERATION"
161
  browser_proxy_enabled: bool = False # 浏览器打码是否启用代理
src/main.py CHANGED
@@ -71,6 +71,12 @@ async def lifespan(app: FastAPI):
71
  config.set_captcha_method(captcha_config.captcha_method)
72
  config.set_yescaptcha_api_key(captcha_config.yescaptcha_api_key)
73
  config.set_yescaptcha_base_url(captcha_config.yescaptcha_base_url)
 
 
 
 
 
 
74
 
75
  # Initialize browser captcha service if needed
76
  browser_service = None
 
71
  config.set_captcha_method(captcha_config.captcha_method)
72
  config.set_yescaptcha_api_key(captcha_config.yescaptcha_api_key)
73
  config.set_yescaptcha_base_url(captcha_config.yescaptcha_base_url)
74
+ config.set_capmonster_api_key(captcha_config.capmonster_api_key)
75
+ config.set_capmonster_base_url(captcha_config.capmonster_base_url)
76
+ config.set_ezcaptcha_api_key(captcha_config.ezcaptcha_api_key)
77
+ config.set_ezcaptcha_base_url(captcha_config.ezcaptcha_base_url)
78
+ config.set_capsolver_api_key(captcha_config.capsolver_api_key)
79
+ config.set_capsolver_base_url(captcha_config.capsolver_base_url)
80
 
81
  # Initialize browser captcha service if needed
82
  browser_service = None
src/services/flow_client.py CHANGED
@@ -684,7 +684,7 @@ class FlowClient:
684
  return str(uuid.uuid4())
685
 
686
  async def _get_recaptcha_token(self, project_id: str) -> Optional[str]:
687
- """获取reCAPTCHA token - 支持种方式"""
688
  captcha_method = config.captcha_method
689
 
690
  # 恒定浏览器打码
@@ -705,61 +705,92 @@ class FlowClient:
705
  except Exception as e:
706
  debug_logger.log_error(f"[reCAPTCHA Browser] error: {str(e)}")
707
  return None
 
 
 
708
  else:
709
- # YesCaptcha打码
710
- client_key = config.yescaptcha_api_key
711
- if not client_key:
712
- debug_logger.log_info("[reCAPTCHA] API key not configured, skipping")
713
- return None
714
 
715
- website_key = "6LdsFiUsAAAAAIjVDZcuLhaHiDn5nnHVXVRQGeMV"
716
- website_url = f"https://labs.google/fx/tools/flow/project/{project_id}"
 
 
 
717
  base_url = config.yescaptcha_base_url
718
- page_action = "FLOW_GENERATION"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
719
 
720
- try:
721
- async with AsyncSession() as session:
722
- create_url = f"{base_url}/createTask"
723
- create_data = {
724
- "clientKey": client_key,
725
- "task": {
726
- "websiteURL": website_url,
727
- "websiteKey": website_key,
728
- "type": "RecaptchaV3TaskProxylessM1",
729
- "pageAction": page_action
730
- }
 
 
 
 
 
 
 
731
  }
 
732
 
733
- result = await session.post(create_url, json=create_data, impersonate="chrome110")
734
- result_json = result.json()
735
- task_id = result_json.get('taskId')
736
 
737
- debug_logger.log_info(f"[reCAPTCHA] created task_id: {task_id}")
738
 
739
- if not task_id:
740
- return None
 
 
741
 
742
- get_url = f"{base_url}/getTaskResult"
743
- for i in range(40):
744
- get_data = {
745
- "clientKey": client_key,
746
- "taskId": task_id
747
- }
748
- result = await session.post(get_url, json=get_data, impersonate="chrome110")
749
- result_json = result.json()
750
 
751
- debug_logger.log_info(f"[reCAPTCHA] polling #{i+1}: {result_json}")
752
 
 
 
753
  solution = result_json.get('solution', {})
754
  response = solution.get('gRecaptchaResponse')
755
-
756
  if response:
 
757
  return response
758
 
759
- time.sleep(3)
760
-
761
- return None
762
 
763
- except Exception as e:
764
- debug_logger.log_error(f"[reCAPTCHA] error: {str(e)}")
765
  return None
 
 
 
 
 
684
  return str(uuid.uuid4())
685
 
686
  async def _get_recaptcha_token(self, project_id: str) -> Optional[str]:
687
+ """获取reCAPTCHA token - 支持打码方式"""
688
  captcha_method = config.captcha_method
689
 
690
  # 恒定浏览器打码
 
705
  except Exception as e:
706
  debug_logger.log_error(f"[reCAPTCHA Browser] error: {str(e)}")
707
  return None
708
+ # API打码服务
709
+ elif captcha_method in ["yescaptcha", "capmonster", "ezcaptcha", "capsolver"]:
710
+ return await self._get_api_captcha_token(captcha_method, project_id)
711
  else:
712
+ debug_logger.log_error(f"[reCAPTCHA] Unknown captcha method: {captcha_method}")
713
+ return None
 
 
 
714
 
715
+ async def _get_api_captcha_token(self, method: str, project_id: str) -> Optional[str]:
716
+ """通用API打码服务"""
717
+ # 获取配置
718
+ if method == "yescaptcha":
719
+ client_key = config.yescaptcha_api_key
720
  base_url = config.yescaptcha_base_url
721
+ task_type = "RecaptchaV3TaskProxylessM1"
722
+ elif method == "capmonster":
723
+ client_key = config.capmonster_api_key
724
+ base_url = config.capmonster_base_url
725
+ task_type = "RecaptchaV3EnterpriseTask"
726
+ elif method == "ezcaptcha":
727
+ client_key = config.ezcaptcha_api_key
728
+ base_url = config.ezcaptcha_base_url
729
+ task_type = "ReCaptchaV3EnterpriseTaskProxyless"
730
+ elif method == "capsolver":
731
+ client_key = config.capsolver_api_key
732
+ base_url = config.capsolver_base_url
733
+ task_type = "ReCaptchaV3EnterpriseTaskProxyLess"
734
+ else:
735
+ debug_logger.log_error(f"[reCAPTCHA] Unknown API method: {method}")
736
+ return None
737
 
738
+ if not client_key:
739
+ debug_logger.log_info(f"[reCAPTCHA] {method} API key not configured, skipping")
740
+ return None
741
+
742
+ website_key = "6LdsFiUsAAAAAIjVDZcuLhaHiDn5nnHVXVRQGeMV"
743
+ website_url = f"https://labs.google/fx/tools/flow/project/{project_id}"
744
+ page_action = "FLOW_GENERATION"
745
+
746
+ try:
747
+ async with AsyncSession() as session:
748
+ create_url = f"{base_url}/createTask"
749
+ create_data = {
750
+ "clientKey": client_key,
751
+ "task": {
752
+ "websiteURL": website_url,
753
+ "websiteKey": website_key,
754
+ "type": task_type,
755
+ "pageAction": page_action
756
  }
757
+ }
758
 
759
+ result = await session.post(create_url, json=create_data, impersonate="chrome110")
760
+ result_json = result.json()
761
+ task_id = result_json.get('taskId')
762
 
763
+ debug_logger.log_info(f"[reCAPTCHA {method}] created task_id: {task_id}")
764
 
765
+ if not task_id:
766
+ error_desc = result_json.get('errorDescription', 'Unknown error')
767
+ debug_logger.log_error(f"[reCAPTCHA {method}] Failed to create task: {error_desc}")
768
+ return None
769
 
770
+ get_url = f"{base_url}/getTaskResult"
771
+ for i in range(40):
772
+ get_data = {
773
+ "clientKey": client_key,
774
+ "taskId": task_id
775
+ }
776
+ result = await session.post(get_url, json=get_data, impersonate="chrome110")
777
+ result_json = result.json()
778
 
779
+ debug_logger.log_info(f"[reCAPTCHA {method}] polling #{i+1}: {result_json}")
780
 
781
+ status = result_json.get('status')
782
+ if status == 'ready':
783
  solution = result_json.get('solution', {})
784
  response = solution.get('gRecaptchaResponse')
 
785
  if response:
786
+ debug_logger.log_info(f"[reCAPTCHA {method}] Token获取成功")
787
  return response
788
 
789
+ time.sleep(3)
 
 
790
 
791
+ debug_logger.log_error(f"[reCAPTCHA {method}] Timeout waiting for token")
 
792
  return None
793
+
794
+ except Exception as e:
795
+ debug_logger.log_error(f"[reCAPTCHA {method}] error: {str(e)}")
796
+ return None
static/manage.html CHANGED
@@ -269,6 +269,9 @@
269
  <label class="text-sm font-medium mb-2 block">打码方式</label>
270
  <select id="cfgCaptchaMethod" class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm" onchange="toggleCaptchaOptions()">
271
  <option value="yescaptcha">YesCaptcha打码</option>
 
 
 
272
  <option value="browser">无头浏览器打码</option>
273
  <option value="personal">内置浏览器打码</option>
274
  </select>
@@ -289,6 +292,48 @@
289
  </div>
290
  </div>
291
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  <!-- 浏览器打码配置选项 -->
293
  <div id="browserCaptchaOptions" class="hidden space-y-4">
294
  <div class="rounded-md bg-blue-50 dark:bg-blue-900/20 p-3 border border-blue-200 dark:border-blue-800">
@@ -335,6 +380,7 @@
335
  <label class="text-sm font-semibold mb-2 block">连接Token</label>
336
  <div class="flex gap-2">
337
  <input id="cfgPluginConnectionToken" type="text" class="flex h-9 flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm" placeholder="留空自动生成">
 
338
  <button onclick="copyConnectionToken()" class="inline-flex items-center justify-center rounded-md bg-secondary text-secondary-foreground hover:bg-secondary/80 h-9 px-4">复制</button>
339
  </div>
340
  <p class="text-xs text-muted-foreground mt-1">用于验证Chrome扩展插件的身份,留空将自动生成随机token</p>
@@ -698,14 +744,15 @@
698
  loadGenerationTimeout=async()=>{try{console.log('开始加载生成超时配置...');const r=await apiRequest('/api/generation/timeout');if(!r){console.error('API请求失败');return}const d=await r.json();console.log('生成超时配置数据:',d);if(d.success&&d.config){const imageTimeout=d.config.image_timeout||300;const videoTimeout=d.config.video_timeout||1500;console.log('设置图片超时:',imageTimeout);console.log('设置视频超时:',videoTimeout);$('cfgImageTimeout').value=imageTimeout;$('cfgVideoTimeout').value=videoTimeout;console.log('生成超时配置加载成功')}else{console.error('生成超时配置数据格式错误:',d)}}catch(e){console.error('加载生成超时配置失败:',e);showToast('加载生成超时配置失败: '+e.message,'error')}},
699
  saveCacheConfig=async()=>{const enabled=$('cfgCacheEnabled').checked,timeout=parseInt($('cfgCacheTimeout').value)||7200,baseUrl=$('cfgCacheBaseUrl').value.trim();console.log('保存缓存配置:',{enabled,timeout,baseUrl});if(timeout<60||timeout>86400)return showToast('缓存超时时间必须在 60-86400 秒之间','error');if(baseUrl&&!baseUrl.startsWith('http://')&&!baseUrl.startsWith('https://'))return showToast('域名必须以 http:// 或 https:// 开头','error');try{console.log('保存缓存启用状态...');const r0=await apiRequest('/api/cache/enabled',{method:'POST',body:JSON.stringify({enabled:enabled})});if(!r0){console.error('保存缓存启用状态请求失败');return}const d0=await r0.json();console.log('缓存启用状态保存结果:',d0);if(!d0.success){console.error('保存缓存启用状态失败:',d0);return showToast('保存缓存启用状态失败','error')}console.log('保存超时时间...');const r1=await apiRequest('/api/cache/config',{method:'POST',body:JSON.stringify({timeout:timeout})});if(!r1){console.error('保存超时时间请求失败');return}const d1=await r1.json();console.log('超时时间保存结果:',d1);if(!d1.success){console.error('保存超时时间失败:',d1);return showToast('保存超时时间失败','error')}console.log('保存域名...');const r2=await apiRequest('/api/cache/base-url',{method:'POST',body:JSON.stringify({base_url:baseUrl})});if(!r2){console.error('保存域名请求失败');return}const d2=await r2.json();console.log('域名保存结果:',d2);if(d2.success){showToast('缓存配置保存成功','success');console.log('等待配置文件写入完成...');await new Promise(r=>setTimeout(r,200));console.log('重新加载配置...');await loadCacheConfig()}else{console.error('保存域名失败:',d2);showToast('保存域名失败','error')}}catch(e){console.error('保存失败:',e);showToast('保存失败: '+e.message,'error')}},
700
  saveGenerationTimeout=async()=>{const imageTimeout=parseInt($('cfgImageTimeout').value)||300,videoTimeout=parseInt($('cfgVideoTimeout').value)||1500;console.log('保存生成超时配置:',{imageTimeout,videoTimeout});if(imageTimeout<60||imageTimeout>3600)return showToast('图片超时时间必须在 60-3600 秒之间','error');if(videoTimeout<60||videoTimeout>7200)return showToast('视频超时时间必须在 60-7200 秒之间','error');try{const r=await apiRequest('/api/generation/timeout',{method:'POST',body:JSON.stringify({image_timeout:imageTimeout,video_timeout:videoTimeout})});if(!r){console.error('保存请求失败');return}const d=await r.json();console.log('保存结果:',d);if(d.success){showToast('生成超时配置保存成功','success');await new Promise(r=>setTimeout(r,200));await loadGenerationTimeout()}else{console.error('保存失败:',d);showToast('保存失败','error')}}catch(e){console.error('保存失败:',e);showToast('保存失败: '+e.message,'error')}},
701
- toggleCaptchaOptions=()=>{const method=$('cfgCaptchaMethod').value;$('yescaptchaOptions').style.display=method==='yescaptcha'?'block':'none';$('browserCaptchaOptions').classList.toggle('hidden',method!=='browser')},
702
  toggleBrowserProxyInput=()=>{const enabled=$('cfgBrowserProxyEnabled').checked;$('browserProxyUrlInput').classList.toggle('hidden',!enabled)},
703
- loadCaptchaConfig=async()=>{try{console.log('开始加载验证码配置...');const r=await apiRequest('/api/captcha/config');if(!r){console.error('API请求失败');return}const d=await r.json();console.log('验证码配置数据:',d);$('cfgCaptchaMethod').value=d.captcha_method||'yescaptcha';$('cfgYescaptchaApiKey').value=d.yescaptcha_api_key||'';$('cfgYescaptchaBaseUrl').value=d.yescaptcha_base_url||'https://api.yescaptcha.com';$('cfgBrowserProxyEnabled').checked=d.browser_proxy_enabled||false;$('cfgBrowserProxyUrl').value=d.browser_proxy_url||'';toggleCaptchaOptions();toggleBrowserProxyInput();console.log('验证码配置加载成功')}catch(e){console.error('加载验证码配置失败:',e);showToast('加载验证码配置失败: '+e.message,'error')}},
704
- saveCaptchaConfig=async()=>{const method=$('cfgCaptchaMethod').value,apiKey=$('cfgYescaptchaApiKey').value.trim(),baseUrl=$('cfgYescaptchaBaseUrl').value.trim(),browserProxyEnabled=$('cfgBrowserProxyEnabled').checked,browserProxyUrl=$('cfgBrowserProxyUrl').value.trim();console.log('保存验证码配置:',{method,apiKey,baseUrl,browserProxyEnabled,browserProxyUrl});try{const r=await apiRequest('/api/captcha/config',{method:'POST',body:JSON.stringify({captcha_method:method,yescaptcha_api_key:apiKey,yescaptcha_base_url:baseUrl,browser_proxy_enabled:browserProxyEnabled,browser_proxy_url:browserProxyUrl})});if(!r){console.error('保存请求失败');return}const d=await r.json();console.log('保存结果:',d);if(d.success){showToast('验证码配置保存成功','success');await new Promise(r=>setTimeout(r,200));await loadCaptchaConfig()}else{console.error('保存失败:',d);showToast(d.message||'保存失败','error')}}catch(e){console.error('保存失败:',e);showToast('保存失败: '+e.message,'error')}},
705
  loadPluginConfig=async()=>{try{const r=await apiRequest('/api/plugin/config');if(!r)return;const d=await r.json();if(d.success&&d.config){$('cfgPluginConnectionUrl').value=d.config.connection_url||'';$('cfgPluginConnectionToken').value=d.config.connection_token||'';$('cfgAutoEnableOnUpdate').checked=d.config.auto_enable_on_update||false}}catch(e){console.error('加���插件配置失败:',e);showToast('加载插件配置失败: '+e.message,'error')}},
706
  savePluginConfig=async()=>{const token=$('cfgPluginConnectionToken').value.trim();const autoEnable=$('cfgAutoEnableOnUpdate').checked;try{const r=await apiRequest('/api/plugin/config',{method:'POST',body:JSON.stringify({connection_token:token,auto_enable_on_update:autoEnable})});if(!r)return;const d=await r.json();if(d.success){showToast('插件配置保存成功','success');await loadPluginConfig()}else{showToast(d.message||'保存失败','error')}}catch(e){showToast('保存失败: '+e.message,'error')}},
707
  copyConnectionUrl=()=>{const url=$('cfgPluginConnectionUrl').value;if(!url){showToast('连接接口为空','error');return}navigator.clipboard.writeText(url).then(()=>showToast('连接接口已复制','success')).catch(()=>showToast('复制失败','error'))},
708
  copyConnectionToken=()=>{const token=$('cfgPluginConnectionToken').value;if(!token){showToast('连接Token为空','error');return}navigator.clipboard.writeText(token).then(()=>showToast('连接Token已复制','success')).catch(()=>showToast('复制失败','error'))},
 
709
  toggleATAutoRefresh=async()=>{try{const enabled=$('atAutoRefreshToggle').checked;const r=await apiRequest('/api/token-refresh/enabled',{method:'POST',body:JSON.stringify({enabled:enabled})});if(!r){$('atAutoRefreshToggle').checked=!enabled;return}const d=await r.json();if(d.success){showToast(enabled?'AT自动刷新已启用':'AT自动刷新已禁用','success')}else{showToast('操作失败: '+(d.detail||'未知错误'),'error');$('atAutoRefreshToggle').checked=!enabled}}catch(e){showToast('操作失败: '+e.message,'error');$('atAutoRefreshToggle').checked=!enabled}},
710
  loadATAutoRefreshConfig=async()=>{try{const r=await apiRequest('/api/token-refresh/config');if(!r)return;const d=await r.json();if(d.success&&d.config){$('atAutoRefreshToggle').checked=d.config.at_auto_refresh_enabled||false}else{console.error('AT自动刷新配置数据格式错误:',d)}}catch(e){console.error('加载AT自动刷新配置失败:',e)}},
711
  loadLogs=async()=>{try{const r=await apiRequest('/api/logs?limit=100');if(!r)return;const logs=await r.json();window.allLogs=logs;const tb=$('logsTableBody');tb.innerHTML=logs.map(l=>`<tr><td class="py-2.5 px-3">${l.operation}</td><td class="py-2.5 px-3"><span class="text-xs ${l.token_email?'text-blue-600':'text-muted-foreground'}">${l.token_email||'未知'}</span></td><td class="py-2.5 px-3"><span class="inline-flex items-center rounded px-2 py-0.5 text-xs ${l.status_code===200?'bg-green-50 text-green-700':'bg-red-50 text-red-700'}">${l.status_code}</span></td><td class="py-2.5 px-3">${l.duration.toFixed(2)}</td><td class="py-2.5 px-3 text-xs text-muted-foreground">${l.created_at?new Date(l.created_at).toLocaleString('zh-CN'):'-'}</td><td class="py-2.5 px-3"><button onclick="showLogDetail(${l.id})" class="inline-flex items-center justify-center rounded-md hover:bg-blue-50 hover:text-blue-700 h-7 px-2 text-xs">查看</button></td></tr>`).join('')}catch(e){console.error('加载日志失败:',e)}},
 
269
  <label class="text-sm font-medium mb-2 block">打码方式</label>
270
  <select id="cfgCaptchaMethod" class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm" onchange="toggleCaptchaOptions()">
271
  <option value="yescaptcha">YesCaptcha打码</option>
272
+ <option value="capmonster">CapMonster打码</option>
273
+ <option value="ezcaptcha">EzCaptcha打码</option>
274
+ <option value="capsolver">CapSolver打码</option>
275
  <option value="browser">无头浏览器打码</option>
276
  <option value="personal">内置浏览器打码</option>
277
  </select>
 
292
  </div>
293
  </div>
294
 
295
+ <!-- CapMonster配置选项 -->
296
+ <div id="capmonsterOptions" class="hidden space-y-4">
297
+ <div>
298
+ <label class="text-sm font-medium mb-2 block">CapMonster API密钥</label>
299
+ <input id="cfgCapmonsterApiKey" type="text" class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm" placeholder="请输入CapMonster API密钥">
300
+ <p class="text-xs text-muted-foreground mt-1">用于自动获取reCAPTCHA验证码</p>
301
+ </div>
302
+ <div>
303
+ <label class="text-sm font-medium mb-2 block">CapMonster API地址</label>
304
+ <input id="cfgCapmonsterBaseUrl" type="text" class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm" placeholder="https://api.capmonster.cloud">
305
+ <p class="text-xs text-muted-foreground mt-1">CapMonster服务地址,默认:https://api.capmonster.cloud</p>
306
+ </div>
307
+ </div>
308
+
309
+ <!-- EzCaptcha配置选项 -->
310
+ <div id="ezcaptchaOptions" class="hidden space-y-4">
311
+ <div>
312
+ <label class="text-sm font-medium mb-2 block">EzCaptcha API密钥</label>
313
+ <input id="cfgEzcaptchaApiKey" type="text" class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm" placeholder="请输入EzCaptcha API密钥">
314
+ <p class="text-xs text-muted-foreground mt-1">用于自动获取reCAPTCHA验证码</p>
315
+ </div>
316
+ <div>
317
+ <label class="text-sm font-medium mb-2 block">EzCaptcha API地址</label>
318
+ <input id="cfgEzcaptchaBaseUrl" type="text" class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm" placeholder="https://api.ez-captcha.com">
319
+ <p class="text-xs text-muted-foreground mt-1">EzCaptcha服务地址,默认:https://api.ez-captcha.com</p>
320
+ </div>
321
+ </div>
322
+
323
+ <!-- CapSolver配置选项 -->
324
+ <div id="capsolverOptions" class="hidden space-y-4">
325
+ <div>
326
+ <label class="text-sm font-medium mb-2 block">CapSolver API密钥</label>
327
+ <input id="cfgCapsolverApiKey" type="text" class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm" placeholder="请输入CapSolver API密钥">
328
+ <p class="text-xs text-muted-foreground mt-1">用于自动获取reCAPTCHA验证码</p>
329
+ </div>
330
+ <div>
331
+ <label class="text-sm font-medium mb-2 block">CapSolver API地址</label>
332
+ <input id="cfgCapsolverBaseUrl" type="text" class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm" placeholder="https://api.capsolver.com">
333
+ <p class="text-xs text-muted-foreground mt-1">CapSolver服务地址,默认:https://api.capsolver.com</p>
334
+ </div>
335
+ </div>
336
+
337
  <!-- 浏览器打码配置选项 -->
338
  <div id="browserCaptchaOptions" class="hidden space-y-4">
339
  <div class="rounded-md bg-blue-50 dark:bg-blue-900/20 p-3 border border-blue-200 dark:border-blue-800">
 
380
  <label class="text-sm font-semibold mb-2 block">连接Token</label>
381
  <div class="flex gap-2">
382
  <input id="cfgPluginConnectionToken" type="text" class="flex h-9 flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm" placeholder="留空自动生成">
383
+ <button onclick="generateRandomToken()" class="inline-flex items-center justify-center rounded-md bg-primary text-primary-foreground hover:bg-primary/90 h-9 px-4">随机</button>
384
  <button onclick="copyConnectionToken()" class="inline-flex items-center justify-center rounded-md bg-secondary text-secondary-foreground hover:bg-secondary/80 h-9 px-4">复制</button>
385
  </div>
386
  <p class="text-xs text-muted-foreground mt-1">用于验证Chrome扩展插件的身份,留空将自动生成随机token</p>
 
744
  loadGenerationTimeout=async()=>{try{console.log('开始加载生成超时配置...');const r=await apiRequest('/api/generation/timeout');if(!r){console.error('API请求失败');return}const d=await r.json();console.log('生成超时配置数据:',d);if(d.success&&d.config){const imageTimeout=d.config.image_timeout||300;const videoTimeout=d.config.video_timeout||1500;console.log('设置图片超时:',imageTimeout);console.log('设置视频超时:',videoTimeout);$('cfgImageTimeout').value=imageTimeout;$('cfgVideoTimeout').value=videoTimeout;console.log('生成超时配置加载成功')}else{console.error('生成超时配置数据格式错误:',d)}}catch(e){console.error('加载生成超时配置失败:',e);showToast('加载生成超时配置失败: '+e.message,'error')}},
745
  saveCacheConfig=async()=>{const enabled=$('cfgCacheEnabled').checked,timeout=parseInt($('cfgCacheTimeout').value)||7200,baseUrl=$('cfgCacheBaseUrl').value.trim();console.log('保存缓存配置:',{enabled,timeout,baseUrl});if(timeout<60||timeout>86400)return showToast('缓存超时时间必须在 60-86400 秒之间','error');if(baseUrl&&!baseUrl.startsWith('http://')&&!baseUrl.startsWith('https://'))return showToast('域名必须以 http:// 或 https:// 开头','error');try{console.log('保存缓存启用状态...');const r0=await apiRequest('/api/cache/enabled',{method:'POST',body:JSON.stringify({enabled:enabled})});if(!r0){console.error('保存缓存启用状态请求失败');return}const d0=await r0.json();console.log('缓存启用状态保存结果:',d0);if(!d0.success){console.error('保存缓存启用状态失败:',d0);return showToast('保存缓存启用状态失败','error')}console.log('保存超时时间...');const r1=await apiRequest('/api/cache/config',{method:'POST',body:JSON.stringify({timeout:timeout})});if(!r1){console.error('保存超时时间请求失败');return}const d1=await r1.json();console.log('超时时间保存结果:',d1);if(!d1.success){console.error('保存超时时间失败:',d1);return showToast('保存超时时间失败','error')}console.log('保存域名...');const r2=await apiRequest('/api/cache/base-url',{method:'POST',body:JSON.stringify({base_url:baseUrl})});if(!r2){console.error('保存域名请求失败');return}const d2=await r2.json();console.log('域名保存结果:',d2);if(d2.success){showToast('缓存配置保存成功','success');console.log('等待配置文件写入完成...');await new Promise(r=>setTimeout(r,200));console.log('重新加载配置...');await loadCacheConfig()}else{console.error('保存域名失败:',d2);showToast('保存域名失败','error')}}catch(e){console.error('保存失败:',e);showToast('保存失败: '+e.message,'error')}},
746
  saveGenerationTimeout=async()=>{const imageTimeout=parseInt($('cfgImageTimeout').value)||300,videoTimeout=parseInt($('cfgVideoTimeout').value)||1500;console.log('保存生成超时配置:',{imageTimeout,videoTimeout});if(imageTimeout<60||imageTimeout>3600)return showToast('图片超时时间必须在 60-3600 秒之间','error');if(videoTimeout<60||videoTimeout>7200)return showToast('视频超时时间必须在 60-7200 秒之间','error');try{const r=await apiRequest('/api/generation/timeout',{method:'POST',body:JSON.stringify({image_timeout:imageTimeout,video_timeout:videoTimeout})});if(!r){console.error('保存请求失败');return}const d=await r.json();console.log('保存结果:',d);if(d.success){showToast('生成超时配置保存成功','success');await new Promise(r=>setTimeout(r,200));await loadGenerationTimeout()}else{console.error('保存失败:',d);showToast('保存失败','error')}}catch(e){console.error('保存失败:',e);showToast('保存失败: '+e.message,'error')}},
747
+ toggleCaptchaOptions=()=>{const method=$('cfgCaptchaMethod').value;$('yescaptchaOptions').style.display=method==='yescaptcha'?'block':'none';$('capmonsterOptions').classList.toggle('hidden',method!=='capmonster');$('ezcaptchaOptions').classList.toggle('hidden',method!=='ezcaptcha');$('capsolverOptions').classList.toggle('hidden',method!=='capsolver');$('browserCaptchaOptions').classList.toggle('hidden',method!=='browser')},
748
  toggleBrowserProxyInput=()=>{const enabled=$('cfgBrowserProxyEnabled').checked;$('browserProxyUrlInput').classList.toggle('hidden',!enabled)},
749
+ loadCaptchaConfig=async()=>{try{console.log('开始加载验证码配置...');const r=await apiRequest('/api/captcha/config');if(!r){console.error('API请求失败');return}const d=await r.json();console.log('验证码配置数据:',d);$('cfgCaptchaMethod').value=d.captcha_method||'yescaptcha';$('cfgYescaptchaApiKey').value=d.yescaptcha_api_key||'';$('cfgYescaptchaBaseUrl').value=d.yescaptcha_base_url||'https://api.yescaptcha.com';$('cfgCapmonsterApiKey').value=d.capmonster_api_key||'';$('cfgCapmonsterBaseUrl').value=d.capmonster_base_url||'https://api.capmonster.cloud';$('cfgEzcaptchaApiKey').value=d.ezcaptcha_api_key||'';$('cfgEzcaptchaBaseUrl').value=d.ezcaptcha_base_url||'https://api.ez-captcha.com';$('cfgCapsolverApiKey').value=d.capsolver_api_key||'';$('cfgCapsolverBaseUrl').value=d.capsolver_base_url||'https://api.capsolver.com';$('cfgBrowserProxyEnabled').checked=d.browser_proxy_enabled||false;$('cfgBrowserProxyUrl').value=d.browser_proxy_url||'';toggleCaptchaOptions();toggleBrowserProxyInput();console.log('验证码配置加载成功')}catch(e){console.error('加载验证码配置失败:',e);showToast('加载验证码配置失败: '+e.message,'error')}},
750
+ saveCaptchaConfig=async()=>{const method=$('cfgCaptchaMethod').value,yesApiKey=$('cfgYescaptchaApiKey').value.trim(),yesBaseUrl=$('cfgYescaptchaBaseUrl').value.trim(),capApiKey=$('cfgCapmonsterApiKey').value.trim(),capBaseUrl=$('cfgCapmonsterBaseUrl').value.trim(),ezApiKey=$('cfgEzcaptchaApiKey').value.trim(),ezBaseUrl=$('cfgEzcaptchaBaseUrl').value.trim(),solverApiKey=$('cfgCapsolverApiKey').value.trim(),solverBaseUrl=$('cfgCapsolverBaseUrl').value.trim(),browserProxyEnabled=$('cfgBrowserProxyEnabled').checked,browserProxyUrl=$('cfgBrowserProxyUrl').value.trim();console.log('保存验证码配置:',{method,yesApiKey,yesBaseUrl,capApiKey,capBaseUrl,ezApiKey,ezBaseUrl,solverApiKey,solverBaseUrl,browserProxyEnabled,browserProxyUrl});try{const r=await apiRequest('/api/captcha/config',{method:'POST',body:JSON.stringify({captcha_method:method,yescaptcha_api_key:yesApiKey,yescaptcha_base_url:yesBaseUrl,capmonster_api_key:capApiKey,capmonster_base_url:capBaseUrl,ezcaptcha_api_key:ezApiKey,ezcaptcha_base_url:ezBaseUrl,capsolver_api_key:solverApiKey,capsolver_base_url:solverBaseUrl,browser_proxy_enabled:browserProxyEnabled,browser_proxy_url:browserProxyUrl})});if(!r){console.error('保存请求失败');return}const d=await r.json();console.log('保存结果:',d);if(d.success){showToast('验证码配置保存成功','success');await new Promise(r=>setTimeout(r,200));await loadCaptchaConfig()}else{console.error('保存失败:',d);showToast(d.message||'保存失败','error')}}catch(e){console.error('保存失败:',e);showToast('保存失败: '+e.message,'error')}},
751
  loadPluginConfig=async()=>{try{const r=await apiRequest('/api/plugin/config');if(!r)return;const d=await r.json();if(d.success&&d.config){$('cfgPluginConnectionUrl').value=d.config.connection_url||'';$('cfgPluginConnectionToken').value=d.config.connection_token||'';$('cfgAutoEnableOnUpdate').checked=d.config.auto_enable_on_update||false}}catch(e){console.error('加���插件配置失败:',e);showToast('加载插件配置失败: '+e.message,'error')}},
752
  savePluginConfig=async()=>{const token=$('cfgPluginConnectionToken').value.trim();const autoEnable=$('cfgAutoEnableOnUpdate').checked;try{const r=await apiRequest('/api/plugin/config',{method:'POST',body:JSON.stringify({connection_token:token,auto_enable_on_update:autoEnable})});if(!r)return;const d=await r.json();if(d.success){showToast('插件配置保存成功','success');await loadPluginConfig()}else{showToast(d.message||'保存失败','error')}}catch(e){showToast('保存失败: '+e.message,'error')}},
753
  copyConnectionUrl=()=>{const url=$('cfgPluginConnectionUrl').value;if(!url){showToast('连接接口为空','error');return}navigator.clipboard.writeText(url).then(()=>showToast('连接接口已复制','success')).catch(()=>showToast('复制失败','error'))},
754
  copyConnectionToken=()=>{const token=$('cfgPluginConnectionToken').value;if(!token){showToast('连接Token为空','error');return}navigator.clipboard.writeText(token).then(()=>showToast('连接Token已复制','success')).catch(()=>showToast('复制失败','error'))},
755
+ generateRandomToken=()=>{const chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';let token='';for(let i=0;i<32;i++){token+=chars.charAt(Math.floor(Math.random()*chars.length))}$('cfgPluginConnectionToken').value=token;showToast('随机Token已生成','success')},
756
  toggleATAutoRefresh=async()=>{try{const enabled=$('atAutoRefreshToggle').checked;const r=await apiRequest('/api/token-refresh/enabled',{method:'POST',body:JSON.stringify({enabled:enabled})});if(!r){$('atAutoRefreshToggle').checked=!enabled;return}const d=await r.json();if(d.success){showToast(enabled?'AT自动刷新已启用':'AT自动刷新已禁用','success')}else{showToast('操作失败: '+(d.detail||'未知错误'),'error');$('atAutoRefreshToggle').checked=!enabled}}catch(e){showToast('操作失败: '+e.message,'error');$('atAutoRefreshToggle').checked=!enabled}},
757
  loadATAutoRefreshConfig=async()=>{try{const r=await apiRequest('/api/token-refresh/config');if(!r)return;const d=await r.json();if(d.success&&d.config){$('atAutoRefreshToggle').checked=d.config.at_auto_refresh_enabled||false}else{console.error('AT自动刷新配置数据格式错误:',d)}}catch(e){console.error('加载AT自动刷新配置失败:',e)}},
758
  loadLogs=async()=>{try{const r=await apiRequest('/api/logs?limit=100');if(!r)return;const logs=await r.json();window.allLogs=logs;const tb=$('logsTableBody');tb.innerHTML=logs.map(l=>`<tr><td class="py-2.5 px-3">${l.operation}</td><td class="py-2.5 px-3"><span class="text-xs ${l.token_email?'text-blue-600':'text-muted-foreground'}">${l.token_email||'未知'}</span></td><td class="py-2.5 px-3"><span class="inline-flex items-center rounded px-2 py-0.5 text-xs ${l.status_code===200?'bg-green-50 text-green-700':'bg-red-50 text-red-700'}">${l.status_code}</span></td><td class="py-2.5 px-3">${l.duration.toFixed(2)}</td><td class="py-2.5 px-3 text-xs text-muted-foreground">${l.created_at?new Date(l.created_at).toLocaleString('zh-CN'):'-'}</td><td class="py-2.5 px-3"><button onclick="showLogDetail(${l.id})" class="inline-flex items-center justify-center rounded-md hover:bg-blue-50 hover:text-blue-700 h-7 px-2 text-xs">查看</button></td></tr>`).join('')}catch(e){console.error('加载日志失败:',e)}},