feat: 请求日志页面记录处理中任务状态、记录角色卡生成任务

新增生成面板
token导出新增代理、备注等字段
token导入支持使用自定义代理获取信息
完善错误提示

close #41
This commit is contained in:
TheSmallHanCat
2025-12-31 11:27:37 +08:00
parent 2eb0ceb325
commit b6d272fcfb
11 changed files with 9094 additions and 91 deletions

View File

@@ -97,6 +97,8 @@ class ImportTokenItem(BaseModel):
access_token: str # Access Token (AT)
session_token: Optional[str] = None # Session Token (ST)
refresh_token: Optional[str] = None # Refresh Token (RT)
proxy_url: Optional[str] = None # Proxy URL (optional, for compatibility)
remark: Optional[str] = None # Remark (optional, for compatibility)
is_active: bool = True # Active status
image_enabled: bool = True # Enable image generation
video_enabled: bool = True # Enable video generation
@@ -345,11 +347,13 @@ async def delete_token(token_id: int, token: str = Depends(verify_admin_token)):
@router.post("/api/tokens/import")
async def import_tokens(request: ImportTokensRequest, token: str = Depends(verify_admin_token)):
"""Import tokens in append mode (update if exists, add if not)"""
try:
added_count = 0
updated_count = 0
added_count = 0
updated_count = 0
failed_count = 0
results = []
for import_item in request.tokens:
for import_item in request.tokens:
try:
# Check if token with this email already exists
existing_token = await db.get_token_by_email(import_item.email)
@@ -360,6 +364,8 @@ async def import_tokens(request: ImportTokensRequest, token: str = Depends(verif
token=import_item.access_token,
st=import_item.session_token,
rt=import_item.refresh_token,
proxy_url=import_item.proxy_url,
remark=import_item.remark,
image_enabled=import_item.image_enabled,
video_enabled=import_item.video_enabled,
image_concurrency=import_item.image_concurrency,
@@ -375,12 +381,19 @@ async def import_tokens(request: ImportTokensRequest, token: str = Depends(verif
video_concurrency=import_item.video_concurrency
)
updated_count += 1
results.append({
"email": import_item.email,
"status": "updated",
"success": True
})
else:
# Add new token
new_token = await token_manager.add_token(
token_value=import_item.access_token,
st=import_item.session_token,
rt=import_item.refresh_token,
proxy_url=import_item.proxy_url,
remark=import_item.remark,
update_if_exists=False,
image_enabled=import_item.image_enabled,
video_enabled=import_item.video_enabled,
@@ -398,15 +411,28 @@ async def import_tokens(request: ImportTokensRequest, token: str = Depends(verif
video_concurrency=import_item.video_concurrency
)
added_count += 1
results.append({
"email": import_item.email,
"status": "added",
"success": True
})
except Exception as e:
failed_count += 1
results.append({
"email": import_item.email,
"status": "failed",
"success": False,
"error": str(e)
})
return {
"success": True,
"message": f"Import completed: {added_count} added, {updated_count} updated",
"added": added_count,
"updated": updated_count
}
except Exception as e:
raise HTTPException(status_code=400, detail=f"Import failed: {str(e)}")
return {
"success": True,
"message": f"Import completed: {added_count} added, {updated_count} updated, {failed_count} failed",
"added": added_count,
"updated": updated_count,
"failed": failed_count,
"results": results
}
@router.put("/api/tokens/{token_id}")
async def update_token(
@@ -654,12 +680,12 @@ async def activate_sora2(
if result.get("success"):
# Get new invite code after activation
sora2_info = await token_manager.get_sora2_invite_code(token_obj.token)
sora2_info = await token_manager.get_sora2_invite_code(token_obj.token, token_id)
# Get remaining count
sora2_remaining_count = 0
try:
remaining_info = await token_manager.get_sora2_remaining_count(token_obj.token)
remaining_info = await token_manager.get_sora2_remaining_count(token_obj.token, token_id)
if remaining_info.get("success"):
sora2_remaining_count = remaining_info.get("remaining_count", 0)
except Exception as e:
@@ -697,20 +723,34 @@ async def activate_sora2(
# Logs endpoints
@router.get("/api/logs")
async def get_logs(limit: int = 100, token: str = Depends(verify_admin_token)):
"""Get recent logs with token email"""
"""Get recent logs with token email and task progress"""
logs = await db.get_recent_logs(limit)
return [{
"id": log.get("id"),
"token_id": log.get("token_id"),
"token_email": log.get("token_email"),
"token_username": log.get("token_username"),
"operation": log.get("operation"),
"status_code": log.get("status_code"),
"duration": log.get("duration"),
"created_at": log.get("created_at"),
"request_body": log.get("request_body"),
"response_body": log.get("response_body")
} for log in logs]
result = []
for log in logs:
log_data = {
"id": log.get("id"),
"token_id": log.get("token_id"),
"token_email": log.get("token_email"),
"token_username": log.get("token_username"),
"operation": log.get("operation"),
"status_code": log.get("status_code"),
"duration": log.get("duration"),
"created_at": log.get("created_at"),
"request_body": log.get("request_body"),
"response_body": log.get("response_body"),
"task_id": log.get("task_id")
}
# If task_id exists and status is in-progress, get task progress
if log.get("task_id") and log.get("status_code") == -1:
task = await db.get_task(log.get("task_id"))
if task:
log_data["progress"] = task.progress
log_data["task_status"] = task.status
result.append(log_data)
return result
@router.delete("/api/logs")
async def clear_logs(token: str = Depends(verify_admin_token)):

View File

@@ -181,15 +181,27 @@ async def create_chat_completion(
):
yield chunk
except Exception as e:
# Try to parse structured error (JSON format)
error_data = None
try:
error_data = json_module.loads(str(e))
except:
pass
# Return OpenAI-compatible error format
error_response = {
"error": {
"message": str(e),
"type": "server_error",
"param": None,
"code": None
if error_data and isinstance(error_data, dict) and "error" in error_data:
# Structured error (e.g., unsupported_country_code)
error_response = error_data
else:
# Generic error
error_response = {
"error": {
"message": str(e),
"type": "server_error",
"param": None,
"code": None
}
}
}
error_chunk = f'data: {json_module.dumps(error_response)}\n\n'
yield error_chunk
yield 'data: [DONE]\n\n'

View File

@@ -254,6 +254,21 @@ class Database:
except Exception as e:
print(f" ✗ Failed to add column '{col_name}': {e}")
# Check and add missing columns to request_logs table
if await self._table_exists(db, "request_logs"):
columns_to_add = [
("task_id", "TEXT"),
("updated_at", "TIMESTAMP"),
]
for col_name, col_type in columns_to_add:
if not await self._column_exists(db, "request_logs", col_name):
try:
await db.execute(f"ALTER TABLE request_logs ADD COLUMN {col_name} {col_type}")
print(f" ✓ Added column '{col_name}' to request_logs table")
except Exception as e:
print(f" ✗ Failed to add column '{col_name}': {e}")
# Ensure all config tables have their default rows
# Pass config_dict if available to initialize from setting.toml
await self._ensure_config_rows(db, config_dict)
@@ -340,12 +355,14 @@ class Database:
CREATE TABLE IF NOT EXISTS request_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
token_id INTEGER,
task_id TEXT,
operation TEXT NOT NULL,
request_body TEXT,
response_body TEXT,
status_code INTEGER NOT NULL,
duration FLOAT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP,
FOREIGN KEY (token_id) REFERENCES tokens(id)
)
""")
@@ -848,15 +865,40 @@ class Database:
return None
# Request log operations
async def log_request(self, log: RequestLog):
"""Log a request"""
async def log_request(self, log: RequestLog) -> int:
"""Log a request and return log ID"""
async with aiosqlite.connect(self.db_path) as db:
await db.execute("""
INSERT INTO request_logs (token_id, operation, request_body, response_body, status_code, duration)
VALUES (?, ?, ?, ?, ?, ?)
""", (log.token_id, log.operation, log.request_body, log.response_body,
cursor = await db.execute("""
INSERT INTO request_logs (token_id, task_id, operation, request_body, response_body, status_code, duration)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (log.token_id, log.task_id, log.operation, log.request_body, log.response_body,
log.status_code, log.duration))
await db.commit()
return cursor.lastrowid
async def update_request_log(self, log_id: int, response_body: Optional[str] = None,
status_code: Optional[int] = None, duration: Optional[float] = None):
"""Update request log with completion data"""
async with aiosqlite.connect(self.db_path) as db:
updates = []
params = []
if response_body is not None:
updates.append("response_body = ?")
params.append(response_body)
if status_code is not None:
updates.append("status_code = ?")
params.append(status_code)
if duration is not None:
updates.append("duration = ?")
params.append(duration)
if updates:
updates.append("updated_at = CURRENT_TIMESTAMP")
params.append(log_id)
query = f"UPDATE request_logs SET {', '.join(updates)} WHERE id = ?"
await db.execute(query, params)
await db.commit()
async def get_recent_logs(self, limit: int = 100) -> List[dict]:
"""Get recent logs with token email"""
@@ -866,13 +908,15 @@ class Database:
SELECT
rl.id,
rl.token_id,
rl.task_id,
rl.operation,
rl.request_body,
rl.response_body,
rl.status_code,
rl.duration,
rl.created_at,
t.email as token_email
t.email as token_email,
t.username as token_username
FROM request_logs rl
LEFT JOIN tokens t ON rl.token_id = t.id
ORDER BY rl.created_at DESC

View File

@@ -71,12 +71,14 @@ class RequestLog(BaseModel):
"""Request log model"""
id: Optional[int] = None
token_id: Optional[int] = None
task_id: Optional[str] = None # Link to task for progress tracking
operation: str
request_body: Optional[str] = None
response_body: Optional[str] = None
status_code: int
duration: float
status_code: int # -1 for in-progress
duration: float # -1.0 for in-progress
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
class AdminConfig(BaseModel):
"""Admin configuration"""

View File

@@ -332,6 +332,8 @@ class GenerationHandler:
stream: Whether to stream response
"""
start_time = time.time()
log_id = None # Initialize log_id to avoid reference before assignment
token_obj = None # Initialize token_obj to avoid reference before assignment
# Validate model
if model not in MODEL_CONFIG:
@@ -515,7 +517,18 @@ class GenerationHandler:
progress=0.0
)
await self.db.create_task(task)
# Create initial log entry (status_code=-1, duration=-1.0 means in-progress)
log_id = await self._log_request(
token_obj.id,
f"generate_{model_config['type']}",
{"model": model, "prompt": prompt, "has_image": image is not None},
{}, # Empty response initially
-1, # -1 means in-progress
-1.0, # -1.0 means in-progress
task_id=task_id
)
# Record usage
await self.token_manager.record_usage(token_obj.id, is_video=is_video)
@@ -557,14 +570,14 @@ class GenerationHandler:
except:
response_data["result_urls"] = task_info.result_urls
await self._log_request(
token_obj.id,
f"generate_{model_config['type']}",
{"model": model, "prompt": prompt, "has_image": image is not None},
response_data,
200,
duration
)
# Update log entry with completion data
if log_id:
await self.db.update_request_log(
log_id,
response_body=json.dumps(response_data),
status_code=200,
duration=duration
)
except Exception as e:
# Release lock for image generation on error
@@ -584,16 +597,33 @@ class GenerationHandler:
is_overload = "heavy_load" in error_str or "under heavy load" in error_str
await self.token_manager.record_error(token_obj.id, is_overload=is_overload)
# Log failed request
# Parse error message to check if it's a structured error (JSON)
error_response = None
try:
import json
error_response = json.loads(str(e))
except:
pass
# Update log entry with error data
duration = time.time() - start_time
await self._log_request(
token_obj.id if token_obj else None,
f"generate_{model_config['type'] if model_config else 'unknown'}",
{"model": model, "prompt": prompt, "has_image": image is not None},
{"error": str(e)},
500,
duration
)
if log_id:
if error_response:
# Structured error (e.g., unsupported_country_code)
await self.db.update_request_log(
log_id,
response_body=json.dumps(error_response),
status_code=400,
duration=duration
)
else:
# Generic error
await self.db.update_request_log(
log_id,
response_body=json.dumps({"error": str(e)}),
status_code=500,
duration=duration
)
raise e
async def _poll_task_result(self, task_id: str, token: str, is_video: bool,
@@ -1136,21 +1166,23 @@ class GenerationHandler:
async def _log_request(self, token_id: Optional[int], operation: str,
request_data: Dict[str, Any], response_data: Dict[str, Any],
status_code: int, duration: float):
"""Log request to database"""
status_code: int, duration: float, task_id: Optional[str] = None) -> Optional[int]:
"""Log request to database and return log ID"""
try:
log = RequestLog(
token_id=token_id,
task_id=task_id,
operation=operation,
request_body=json.dumps(request_data),
response_body=json.dumps(response_data),
status_code=status_code,
duration=duration
)
await self.db.log_request(log)
return await self.db.log_request(log)
except Exception as e:
# Don't fail the request if logging fails
print(f"Failed to log request: {e}")
return None
# ==================== Character Creation and Remix Handlers ====================
@@ -1171,6 +1203,7 @@ class GenerationHandler:
if not token_obj:
raise Exception("No available tokens for character creation")
start_time = time.time()
try:
yield self._format_stream_chunk(
reasoning_content="**Character Creation Begins**\n\nInitializing character creation...\n",
@@ -1255,6 +1288,26 @@ class GenerationHandler:
await self.sora_client.set_character_public(cameo_id, token_obj.token)
debug_logger.log_info(f"Character set as public")
# Log successful character creation
duration = time.time() - start_time
await self._log_request(
token_id=token_obj.id,
operation="character_only",
request_data={
"type": "character_creation",
"has_video": True
},
response_data={
"success": True,
"username": username,
"display_name": display_name,
"character_id": character_id,
"cameo_id": cameo_id
},
status_code=200,
duration=duration
)
# Step 7: Return success message
yield self._format_stream_chunk(
content=f"角色创建成功,角色名@{username}",
@@ -1263,6 +1316,23 @@ class GenerationHandler:
yield "data: [DONE]\n\n"
except Exception as e:
# Log failed character creation
duration = time.time() - start_time
await self._log_request(
token_id=token_obj.id if token_obj else None,
operation="character_only",
request_data={
"type": "character_creation",
"has_video": True
},
response_data={
"success": False,
"error": str(e)
},
status_code=500,
duration=duration
)
debug_logger.log_error(
error_message=f"Character creation failed: {str(e)}",
status_code=500,
@@ -1289,6 +1359,10 @@ class GenerationHandler:
raise Exception("No available tokens for video generation")
character_id = None
start_time = time.time()
username = None
display_name = None
cameo_id = None
try:
yield self._format_stream_chunk(
reasoning_content="**Character Creation and Video Generation Begins**\n\nInitializing...\n",
@@ -1366,6 +1440,28 @@ class GenerationHandler:
)
debug_logger.log_info(f"Character finalized, character_id: {character_id}")
# Log successful character creation (before video generation)
character_creation_duration = time.time() - start_time
await self._log_request(
token_id=token_obj.id,
operation="character_with_video",
request_data={
"type": "character_creation_with_video",
"has_video": True,
"prompt": prompt
},
response_data={
"success": True,
"username": username,
"display_name": display_name,
"character_id": character_id,
"cameo_id": cameo_id,
"stage": "character_created"
},
status_code=200,
duration=character_creation_duration
)
# Step 6: Generate video with character
yield self._format_stream_chunk(
reasoning_content="**Video Generation Process Begins**\n\nGenerating video with character...\n"
@@ -1414,6 +1510,28 @@ class GenerationHandler:
await self.token_manager.record_success(token_obj.id, is_video=True)
except Exception as e:
# Log failed character creation
duration = time.time() - start_time
await self._log_request(
token_id=token_obj.id if token_obj else None,
operation="character_with_video",
request_data={
"type": "character_creation_with_video",
"has_video": True,
"prompt": prompt
},
response_data={
"success": False,
"username": username,
"display_name": display_name,
"character_id": character_id,
"cameo_id": cameo_id,
"error": str(e)
},
status_code=500,
duration=duration
)
# Record error (check if it's an overload error)
if token_obj:
error_str = str(e).lower()
@@ -1553,6 +1671,16 @@ class GenerationHandler:
debug_logger.log_info(f"Cameo status: {current_status} (message: {status_message}) (attempt {attempt + 1}/{max_attempts})")
# Check if processing failed
if current_status == "failed":
error_message = status_message or "Character creation failed"
debug_logger.log_error(
error_message=f"Cameo processing failed: {error_message}",
status_code=500,
response_text=error_message
)
raise Exception(f"角色创建失败: {error_message}")
# Check if processing is complete
# Primary condition: status_message == "Completed" means processing is done
if status_message == "Completed":
@@ -1568,6 +1696,11 @@ class GenerationHandler:
consecutive_errors += 1
error_msg = str(e)
# Check if it's a character creation failure (not a network error)
# These should be raised immediately, not retried
if "角色创建失败" in error_msg:
raise
# Log error with context
debug_logger.log_error(
error_message=f"Failed to get cameo status (attempt {attempt + 1}/{max_attempts}, consecutive errors: {consecutive_errors}): {error_msg}",

View File

@@ -9,16 +9,21 @@ class ProxyManager:
def __init__(self, db: Database):
self.db = db
async def get_proxy_url(self, token_id: Optional[int] = None) -> Optional[str]:
async def get_proxy_url(self, token_id: Optional[int] = None, proxy_url: Optional[str] = None) -> Optional[str]:
"""Get proxy URL for a token, with fallback to global proxy
Args:
token_id: Token ID (optional). If provided, returns token-specific proxy if set,
otherwise falls back to global proxy.
proxy_url: Direct proxy URL (optional). If provided, returns this proxy URL directly.
Returns:
Proxy URL string or None
"""
# If proxy_url is directly provided, use it
if proxy_url:
return proxy_url
# If token_id is provided, try to get token-specific proxy first
if token_id is not None:
token = await self.db.get_token(token_id)

View File

@@ -180,6 +180,29 @@ class SoraClient:
# Check status
if response.status_code not in [200, 201]:
# Parse error response
error_data = None
try:
error_data = response.json()
except:
pass
# Check for unsupported_country_code error
if error_data and isinstance(error_data, dict):
error_info = error_data.get("error", {})
if error_info.get("code") == "unsupported_country_code":
# Create structured error with full error data
import json
error_msg = json.dumps(error_data)
debug_logger.log_error(
error_message=f"Unsupported country: {error_msg}",
status_code=response.status_code,
response_text=error_msg
)
# Raise exception with structured error data
raise Exception(error_msg)
# Generic error handling
error_msg = f"API request failed: {response.status_code} - {response.text}"
debug_logger.log_error(
error_message=error_msg,

View File

@@ -59,9 +59,9 @@ class TokenManager:
# 转换为小写
return format_choice.lower()
async def get_user_info(self, access_token: str) -> dict:
async def get_user_info(self, access_token: str, token_id: Optional[int] = None, proxy_url: Optional[str] = None) -> dict:
"""Get user info from Sora API"""
proxy_url = await self.proxy_manager.get_proxy_url()
proxy_url = await self.proxy_manager.get_proxy_url(token_id, proxy_url)
async with AsyncSession() as session:
headers = {
@@ -90,7 +90,7 @@ class TokenManager:
return response.json()
async def get_subscription_info(self, token: str) -> Dict[str, Any]:
async def get_subscription_info(self, token: str, token_id: Optional[int] = None, proxy_url: Optional[str] = None) -> Dict[str, Any]:
"""Get subscription information from Sora API
Returns:
@@ -101,7 +101,7 @@ class TokenManager:
}
"""
print(f"🔍 开始获取订阅信息...")
proxy_url = await self.proxy_manager.get_proxy_url()
proxy_url = await self.proxy_manager.get_proxy_url(token_id, proxy_url)
headers = {
"Authorization": f"Bearer {token}"
@@ -163,9 +163,9 @@ class TokenManager:
raise Exception(f"Failed to get subscription info: {response.status_code}")
async def get_sora2_invite_code(self, access_token: str) -> dict:
async def get_sora2_invite_code(self, access_token: str, token_id: Optional[int] = None, proxy_url: Optional[str] = None) -> dict:
"""Get Sora2 invite code"""
proxy_url = await self.proxy_manager.get_proxy_url()
proxy_url = await self.proxy_manager.get_proxy_url(token_id, proxy_url)
print(f"🔍 开始获取Sora2邀请码...")
@@ -263,7 +263,7 @@ class TokenManager:
"invite_code": None
}
async def get_sora2_remaining_count(self, access_token: str) -> dict:
async def get_sora2_remaining_count(self, access_token: str, token_id: Optional[int] = None, proxy_url: Optional[str] = None) -> dict:
"""Get Sora2 remaining video count
Returns:
@@ -273,7 +273,7 @@ class TokenManager:
"access_resets_in_seconds": 46833
}
"""
proxy_url = await self.proxy_manager.get_proxy_url()
proxy_url = await self.proxy_manager.get_proxy_url(token_id, proxy_url)
print(f"🔍 开始获取Sora2剩余次数...")
@@ -692,7 +692,7 @@ class TokenManager:
# Get user info from Sora API
try:
user_info = await self.get_user_info(token_value)
user_info = await self.get_user_info(token_value, proxy_url=proxy_url)
email = user_info.get("email", jwt_email or "")
name = user_info.get("name") or ""
except Exception as e:
@@ -705,7 +705,7 @@ class TokenManager:
plan_title = None
subscription_end = None
try:
sub_info = await self.get_subscription_info(token_value)
sub_info = await self.get_subscription_info(token_value, proxy_url=proxy_url)
plan_type = sub_info.get("plan_type")
plan_title = sub_info.get("plan_title")
# Parse subscription end time
@@ -727,7 +727,7 @@ class TokenManager:
sora2_total_count = 0
sora2_remaining_count = 0
try:
sora2_info = await self.get_sora2_invite_code(token_value)
sora2_info = await self.get_sora2_invite_code(token_value, proxy_url=proxy_url)
sora2_supported = sora2_info.get("supported", False)
sora2_invite_code = sora2_info.get("invite_code")
sora2_redeemed_count = sora2_info.get("redeemed_count", 0)
@@ -736,7 +736,7 @@ class TokenManager:
# If Sora2 is supported, get remaining count
if sora2_supported:
try:
remaining_info = await self.get_sora2_remaining_count(token_value)
remaining_info = await self.get_sora2_remaining_count(token_value, proxy_url=proxy_url)
if remaining_info.get("success"):
sora2_remaining_count = remaining_info.get("remaining_count", 0)
print(f"✅ Sora2剩余次数: {sora2_remaining_count}")
@@ -753,7 +753,7 @@ class TokenManager:
# Check and set username if needed
try:
# Get fresh user info to check username
user_info = await self.get_user_info(token_value)
user_info = await self.get_user_info(token_value, proxy_url=proxy_url)
username = user_info.get("username")
# If username is null, need to set one
@@ -931,10 +931,10 @@ class TokenManager:
try:
# Try to get user info from Sora API
user_info = await self.get_user_info(token_data.token)
user_info = await self.get_user_info(token_data.token, token_id)
# Refresh Sora2 invite code and counts
sora2_info = await self.get_sora2_invite_code(token_data.token)
sora2_info = await self.get_sora2_invite_code(token_data.token, token_id)
sora2_supported = sora2_info.get("supported", False)
sora2_invite_code = sora2_info.get("invite_code")
sora2_redeemed_count = sora2_info.get("redeemed_count", 0)
@@ -944,7 +944,7 @@ class TokenManager:
# If Sora2 is supported, get remaining count
if sora2_supported:
try:
remaining_info = await self.get_sora2_remaining_count(token_data.token)
remaining_info = await self.get_sora2_remaining_count(token_data.token, token_id)
if remaining_info.get("success"):
sora2_remaining_count = remaining_info.get("remaining_count", 0)
except Exception as e:
@@ -1012,19 +1012,22 @@ class TokenManager:
try:
token_data = await self.db.get_token(token_id)
if token_data and token_data.sora2_supported:
remaining_info = await self.get_sora2_remaining_count(token_data.token)
remaining_info = await self.get_sora2_remaining_count(token_data.token, token_id)
if remaining_info.get("success"):
remaining_count = remaining_info.get("remaining_count", 0)
await self.db.update_token_sora2_remaining(token_id, remaining_count)
print(f"✅ 更新Token {token_id} 的Sora2剩余次数: {remaining_count}")
# If remaining count is 0, set cooldown
if remaining_count == 0:
# If remaining count is 1 or less, disable token and set cooldown
if remaining_count <= 1:
reset_seconds = remaining_info.get("access_resets_in_seconds", 0)
if reset_seconds > 0:
cooldown_until = datetime.now() + timedelta(seconds=reset_seconds)
await self.db.update_token_sora2_cooldown(token_id, cooldown_until)
print(f"⏱️ Token {token_id} 剩余次数为0,设置冷却时间至: {cooldown_until}")
print(f"⏱️ Token {token_id} 剩余次数为{remaining_count},设置冷却时间至: {cooldown_until}")
# Disable token
await self.disable_token(token_id)
print(f"🚫 Token {token_id} 剩余次数为{remaining_count},已自动禁用")
except Exception as e:
print(f"Failed to update Sora2 remaining count: {e}")
@@ -1040,7 +1043,7 @@ class TokenManager:
print(f"🔄 Token {token_id} Sora2冷却已过期正在刷新剩余次数...")
try:
remaining_info = await self.get_sora2_remaining_count(token_data.token)
remaining_info = await self.get_sora2_remaining_count(token_data.token, token_id)
if remaining_info.get("success"):
remaining_count = remaining_info.get("remaining_count", 0)
await self.db.update_token_sora2_remaining(token_id, remaining_count)