Commit ·
49d8e1d
1
Parent(s): 7fc098b
feat: 支持更多打码平台
Browse files- src/api/admin.py +18 -0
- src/core/config.py +66 -0
- src/core/database.py +51 -8
- src/core/models.py +7 -1
- src/main.py +6 -0
- src/services/flow_client.py +72 -41
- static/manage.html +50 -3
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 |
-
|
| 1215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,
|
|
|
|
| 1225 |
else:
|
| 1226 |
new_method = captcha_method if captcha_method is not None else "yescaptcha"
|
| 1227 |
-
|
| 1228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,
|
| 1234 |
-
|
| 1235 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 710 |
-
|
| 711 |
-
if not client_key:
|
| 712 |
-
debug_logger.log_info("[reCAPTCHA] API key not configured, skipping")
|
| 713 |
-
return None
|
| 714 |
|
| 715 |
-
|
| 716 |
-
|
|
|
|
|
|
|
|
|
|
| 717 |
base_url = config.yescaptcha_base_url
|
| 718 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 719 |
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 731 |
}
|
|
|
|
| 732 |
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
|
| 737 |
-
|
| 738 |
|
| 739 |
-
|
| 740 |
-
|
|
|
|
|
|
|
| 741 |
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
|
| 751 |
-
|
| 752 |
|
|
|
|
|
|
|
| 753 |
solution = result_json.get('solution', {})
|
| 754 |
response = solution.get('gRecaptchaResponse')
|
| 755 |
-
|
| 756 |
if response:
|
|
|
|
| 757 |
return response
|
| 758 |
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
return None
|
| 762 |
|
| 763 |
-
|
| 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,
|
| 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)}},
|