Commit ·
ea88387
1
Parent(s): 77eaf1d
fix: token导入
Browse files- src/api/admin.py +139 -0
src/api/admin.py
CHANGED
|
@@ -90,6 +90,23 @@ class ST2ATRequest(BaseModel):
|
|
| 90 |
st: str
|
| 91 |
|
| 92 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
# ========== Auth Middleware ==========
|
| 94 |
|
| 95 |
async def verify_admin_token(authorization: str = Header(None)):
|
|
@@ -378,6 +395,98 @@ async def st_to_at(
|
|
| 378 |
raise HTTPException(status_code=400, detail=str(e))
|
| 379 |
|
| 380 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 381 |
# ========== Config Management ==========
|
| 382 |
|
| 383 |
@router.get("/api/config/proxy")
|
|
@@ -722,3 +831,33 @@ async def update_cache_base_url(
|
|
| 722 |
await db.reload_config_to_memory()
|
| 723 |
|
| 724 |
return {"success": True, "message": "缓存Base URL更新成功"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
st: str
|
| 91 |
|
| 92 |
|
| 93 |
+
class ImportTokenItem(BaseModel):
|
| 94 |
+
"""导入Token项"""
|
| 95 |
+
email: Optional[str] = None
|
| 96 |
+
access_token: Optional[str] = None
|
| 97 |
+
session_token: Optional[str] = None
|
| 98 |
+
is_active: bool = True
|
| 99 |
+
image_enabled: bool = True
|
| 100 |
+
video_enabled: bool = True
|
| 101 |
+
image_concurrency: int = -1
|
| 102 |
+
video_concurrency: int = -1
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
class ImportTokensRequest(BaseModel):
|
| 106 |
+
"""导入Token请求"""
|
| 107 |
+
tokens: List[ImportTokenItem]
|
| 108 |
+
|
| 109 |
+
|
| 110 |
# ========== Auth Middleware ==========
|
| 111 |
|
| 112 |
async def verify_admin_token(authorization: str = Header(None)):
|
|
|
|
| 395 |
raise HTTPException(status_code=400, detail=str(e))
|
| 396 |
|
| 397 |
|
| 398 |
+
@router.post("/api/tokens/import")
|
| 399 |
+
async def import_tokens(
|
| 400 |
+
request: ImportTokensRequest,
|
| 401 |
+
token: str = Depends(verify_admin_token)
|
| 402 |
+
):
|
| 403 |
+
"""批量导入Token"""
|
| 404 |
+
from datetime import datetime, timezone
|
| 405 |
+
|
| 406 |
+
added = 0
|
| 407 |
+
updated = 0
|
| 408 |
+
errors = []
|
| 409 |
+
|
| 410 |
+
for idx, item in enumerate(request.tokens):
|
| 411 |
+
try:
|
| 412 |
+
st = item.session_token
|
| 413 |
+
|
| 414 |
+
if not st:
|
| 415 |
+
errors.append(f"第{idx+1}项: 缺少 session_token")
|
| 416 |
+
continue
|
| 417 |
+
|
| 418 |
+
# 使用 ST 转 AT 获取用户信息
|
| 419 |
+
try:
|
| 420 |
+
result = await token_manager.flow_client.st_to_at(st)
|
| 421 |
+
at = result["access_token"]
|
| 422 |
+
email = result.get("user", {}).get("email")
|
| 423 |
+
expires = result.get("expires")
|
| 424 |
+
|
| 425 |
+
if not email:
|
| 426 |
+
errors.append(f"第{idx+1}项: 无法获取邮箱信息")
|
| 427 |
+
continue
|
| 428 |
+
|
| 429 |
+
# 解析过期时间
|
| 430 |
+
at_expires = None
|
| 431 |
+
is_expired = False
|
| 432 |
+
if expires:
|
| 433 |
+
try:
|
| 434 |
+
at_expires = datetime.fromisoformat(expires.replace('Z', '+00:00'))
|
| 435 |
+
# 判断是否过期
|
| 436 |
+
now = datetime.now(timezone.utc)
|
| 437 |
+
is_expired = at_expires <= now
|
| 438 |
+
except:
|
| 439 |
+
pass
|
| 440 |
+
|
| 441 |
+
# 使用邮箱检查是否已存在
|
| 442 |
+
existing_tokens = await token_manager.get_all_tokens()
|
| 443 |
+
existing = next((t for t in existing_tokens if t.email == email), None)
|
| 444 |
+
|
| 445 |
+
if existing:
|
| 446 |
+
# 更新现有Token
|
| 447 |
+
await token_manager.update_token(
|
| 448 |
+
token_id=existing.id,
|
| 449 |
+
st=st,
|
| 450 |
+
at=at,
|
| 451 |
+
at_expires=at_expires,
|
| 452 |
+
image_enabled=item.image_enabled,
|
| 453 |
+
video_enabled=item.video_enabled,
|
| 454 |
+
image_concurrency=item.image_concurrency,
|
| 455 |
+
video_concurrency=item.video_concurrency
|
| 456 |
+
)
|
| 457 |
+
# 如果过期则禁用
|
| 458 |
+
if is_expired:
|
| 459 |
+
await token_manager.disable_token(existing.id)
|
| 460 |
+
updated += 1
|
| 461 |
+
else:
|
| 462 |
+
# 添加新Token
|
| 463 |
+
new_token = await token_manager.add_token(
|
| 464 |
+
st=st,
|
| 465 |
+
image_enabled=item.image_enabled,
|
| 466 |
+
video_enabled=item.video_enabled,
|
| 467 |
+
image_concurrency=item.image_concurrency,
|
| 468 |
+
video_concurrency=item.video_concurrency
|
| 469 |
+
)
|
| 470 |
+
# 如果过期则禁用
|
| 471 |
+
if is_expired:
|
| 472 |
+
await token_manager.disable_token(new_token.id)
|
| 473 |
+
added += 1
|
| 474 |
+
|
| 475 |
+
except Exception as e:
|
| 476 |
+
errors.append(f"第{idx+1}项: {str(e)}")
|
| 477 |
+
|
| 478 |
+
except Exception as e:
|
| 479 |
+
errors.append(f"第{idx+1}项: {str(e)}")
|
| 480 |
+
|
| 481 |
+
return {
|
| 482 |
+
"success": True,
|
| 483 |
+
"added": added,
|
| 484 |
+
"updated": updated,
|
| 485 |
+
"errors": errors if errors else None,
|
| 486 |
+
"message": f"导入完成: 新增 {added} 个, 更新 {updated} 个" + (f", {len(errors)} 个失败" if errors else "")
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
|
| 490 |
# ========== Config Management ==========
|
| 491 |
|
| 492 |
@router.get("/api/config/proxy")
|
|
|
|
| 831 |
await db.reload_config_to_memory()
|
| 832 |
|
| 833 |
return {"success": True, "message": "缓存Base URL更新成功"}
|
| 834 |
+
|
| 835 |
+
|
| 836 |
+
@router.post("/api/captcha/config")
|
| 837 |
+
async def update_captcha_config(
|
| 838 |
+
request: dict,
|
| 839 |
+
token: str = Depends(verify_admin_token)
|
| 840 |
+
):
|
| 841 |
+
"""Update captcha configuration"""
|
| 842 |
+
yescaptcha_api_key = request.get("yescaptcha_api_key")
|
| 843 |
+
yescaptcha_base_url = request.get("yescaptcha_base_url")
|
| 844 |
+
|
| 845 |
+
await db.update_captcha_config(
|
| 846 |
+
yescaptcha_api_key=yescaptcha_api_key,
|
| 847 |
+
yescaptcha_base_url=yescaptcha_base_url
|
| 848 |
+
)
|
| 849 |
+
|
| 850 |
+
# 🔥 Hot reload: sync database config to memory
|
| 851 |
+
await db.reload_config_to_memory()
|
| 852 |
+
|
| 853 |
+
return {"success": True, "message": "验证码配置更新成功"}
|
| 854 |
+
|
| 855 |
+
|
| 856 |
+
@router.get("/api/captcha/config")
|
| 857 |
+
async def get_captcha_config(token: str = Depends(verify_admin_token)):
|
| 858 |
+
"""Get captcha configuration"""
|
| 859 |
+
captcha_config = await db.get_captcha_config()
|
| 860 |
+
return {
|
| 861 |
+
"yescaptcha_api_key": captcha_config.yescaptcha_api_key,
|
| 862 |
+
"yescaptcha_base_url": captcha_config.yescaptcha_base_url
|
| 863 |
+
}
|