备份: 完整开发状态(含反混淆脚本和临时文件)

This commit is contained in:
ccdojox-crypto
2025-12-17 17:18:02 +08:00
parent 9e2333c90c
commit 7e9ea173a7
2872 changed files with 326818 additions and 249 deletions

View File

@@ -3,20 +3,87 @@
"""
from fastapi import APIRouter, Depends, Request
from sqlalchemy.orm import Session
from typing import Optional, List
from pydantic import BaseModel
from app.database import get_db
from app.services import AccountService, KeyService, LogService, GlobalSettingsService
from app.schemas import VerifyKeyRequest, VerifyKeyResponse, SwitchAccountRequest, SwitchAccountResponse
from app.models import AccountStatus, MembershipType
router = APIRouter(prefix="/api", tags=["Client API"])
@router.post("/verify-key", response_model=VerifyKeyResponse)
# ========== 账号数据响应模型 ==========
class AccountData(BaseModel):
"""账号数据 - 写入本地Cursor的数据"""
accessToken: str
refreshToken: Optional[str] = None
workosSessionToken: Optional[str] = None
email: str
membership_type: str
usage_type: Optional[str] = "default"
# 机器ID相关 (可选)
serviceMachineId: Optional[str] = None
machineId: Optional[str] = None
macMachineId: Optional[str] = None
devDeviceId: Optional[str] = None
sqmId: Optional[str] = None
machineIdFile: Optional[str] = None
# 使用统计
requestCount: Optional[int] = 0
usageAmount: Optional[float] = 0.0
# 额度信息
quota: Optional[int] = None
quotaUsed: Optional[int] = None
quotaRemaining: Optional[int] = None
expireDate: Optional[str] = None
class ApiResponse(BaseModel):
"""通用API响应"""
success: bool
message: Optional[str] = None
error: Optional[str] = None
data: Optional[AccountData] = None
# ========== 验证和切换 API ==========
def build_account_data(account, key) -> AccountData:
"""构建账号数据对象"""
expire_date = key.expire_at.strftime("%Y-%m-%d %H:%M:%S") if key.expire_at else None
return AccountData(
accessToken=account.access_token,
refreshToken=account.refresh_token,
workosSessionToken=account.workos_session_token,
email=account.email,
membership_type=account.membership_type.value,
quota=key.quota,
quotaUsed=key.quota_used,
quotaRemaining=key.quota - key.quota_used,
expireDate=expire_date
)
@router.post("/verify")
async def verify(request: VerifyKeyRequest, req: Request, db: Session = Depends(get_db)):
"""验证激活码 (前端调用的路径)"""
return await verify_key_impl(request, req, db)
@router.post("/verify-key")
async def verify_key(request: VerifyKeyRequest, req: Request, db: Session = Depends(get_db)):
"""验证激活码"""
"""验证激活码 (兼容路径)"""
return await verify_key_impl(request, req, db)
async def verify_key_impl(request: VerifyKeyRequest, req: Request, db: Session):
"""验证激活码实现"""
key = KeyService.get_by_key(db, request.key)
if not key:
return VerifyKeyResponse(success=False, error="激活码不存在")
return {"success": False, "valid": False, "error": "激活码不存在"}
# 首次激活:设置激活时间和过期时间
KeyService.activate(db, key)
@@ -26,75 +93,81 @@ async def verify_key(request: VerifyKeyRequest, req: Request, db: Session = Depe
device_ok, device_msg = KeyService.check_device(db, key, request.device_id)
if not device_ok:
LogService.log(db, key.id, "verify", ip_address=req.client.host, success=False, message=device_msg)
return VerifyKeyResponse(success=False, error=device_msg)
return {"success": False, "valid": False, "error": device_msg}
# 检查激活码是否有效
is_valid, message = KeyService.is_valid(key, db)
if not is_valid:
LogService.log(db, key.id, "verify", ip_address=req.client.host, success=False, message=message)
return VerifyKeyResponse(success=False, error=message)
return {"success": False, "valid": False, "error": message}
# 获取当前绑定的账号,或分配新账号
account = None
if key.current_account_id:
account = AccountService.get_by_id(db, key.current_account_id)
if not account or account.status != "active":
# 只有账号不存在或被禁用/过期才分配新的IN_USE 状态的账号继续使用)
if not account or account.status in (AccountStatus.DISABLED, AccountStatus.EXPIRED):
# 分配新账号
account = AccountService.get_available(db, key.membership_type)
if not account:
LogService.log(db, key.id, "verify", ip_address=req.client.host, success=False, message="无可用账号")
return VerifyKeyResponse(success=False, error="暂无可用账号,请稍后重试")
return {"success": False, "valid": False, "error": "暂无可用账号,请稍后重试"}
KeyService.bind_account(db, key, account)
AccountService.mark_used(db, account, key.id)
LogService.log(db, key.id, "verify", account.id, ip_address=req.client.host, success=True)
# 返回格式匹配原版插件期望
expire_date = key.expire_at.strftime("%Y-%m-%d %H:%M:%S") if key.expire_at else None
quota_cost = KeyService.get_quota_cost(db, key.membership_type)
return VerifyKeyResponse(
success=True,
email=account.email,
accessToken=account.access_token,
refreshToken=account.refresh_token,
WorkosCursorSessionToken=account.workos_session_token,
membership_type=account.membership_type.value,
quota=key.quota,
quotaUsed=key.quota_used,
quotaRemaining=key.quota - key.quota_used,
quotaCost=quota_cost,
expireDate=expire_date
)
return {
"success": True,
"valid": True,
"expire_date": expire_date,
"switch_remaining": key.quota - key.quota_used,
"switch_limit": key.quota,
"data": build_account_data(account, key)
}
@router.post("/switch-account", response_model=SwitchAccountResponse)
@router.post("/switch")
async def switch(request: SwitchAccountRequest, req: Request, db: Session = Depends(get_db)):
"""切换账号 (前端调用的路径)"""
return await switch_account_impl(request, req, db)
@router.post("/switch-account")
async def switch_account(request: SwitchAccountRequest, req: Request, db: Session = Depends(get_db)):
"""切换账号"""
"""切换账号 (兼容路径)"""
return await switch_account_impl(request, req, db)
async def switch_account_impl(request: SwitchAccountRequest, req: Request, db: Session):
"""切换账号实现"""
key = KeyService.get_by_key(db, request.key)
if not key:
return SwitchAccountResponse(success=False, error="激活码不存在")
return ApiResponse(success=False, error="激活码不存在")
# 检查设备限制
if request.device_id:
device_ok, device_msg = KeyService.check_device(db, key, request.device_id)
if not device_ok:
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message=device_msg)
return SwitchAccountResponse(success=False, error=device_msg)
return ApiResponse(success=False, error=device_msg)
# 检查激活码是否有效
is_valid, message = KeyService.is_valid(key, db)
if not is_valid:
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message=message)
return SwitchAccountResponse(success=False, error=message)
return ApiResponse(success=False, error=message)
# 检查换号频率限制
can_switch, switch_msg = KeyService.can_switch(db, key)
if not can_switch:
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message=switch_msg)
return SwitchAccountResponse(success=False, error=switch_msg)
return ApiResponse(success=False, error=switch_msg)
# 释放当前账号
if key.current_account_id:
@@ -106,7 +179,7 @@ async def switch_account(request: SwitchAccountRequest, req: Request, db: Sessio
account = AccountService.get_available(db, key.membership_type)
if not account:
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message="无可用账号")
return SwitchAccountResponse(success=False, error="暂无可用账号,请稍后重试")
return ApiResponse(success=False, error="暂无可用账号,请稍后重试")
# 绑定新账号并扣除额度
KeyService.bind_account(db, key, account)
@@ -115,34 +188,581 @@ async def switch_account(request: SwitchAccountRequest, req: Request, db: Sessio
LogService.log(db, key.id, "switch", account.id, ip_address=req.client.host, success=True)
return SwitchAccountResponse(
return ApiResponse(
success=True,
message="切换成功",
email=account.email,
accessToken=account.access_token,
refreshToken=account.refresh_token,
WorkosCursorSessionToken=account.workos_session_token,
membership_type=account.membership_type.value,
quotaUsed=key.quota_used,
quotaRemaining=key.quota - key.quota_used
data=build_account_data(account, key)
)
# ========== 版本 API ==========
@router.get("/version")
async def get_version():
"""获取版本信息"""
return {
"success": True,
"version": "1.0.0",
"update_url": None,
"message": None
"latestVersion": "1.0.0",
"updateUrl": None,
"message": None,
"forceUpdate": False
}
# ========== 代理配置 API ==========
# 内存存储代理配置 (生产环境应存数据库)
proxy_config = {
"is_enabled": False,
"proxy_url": ""
}
@router.get("/proxy-config")
async def get_proxy_config():
"""获取代理配置"""
return {
"success": True,
"data": proxy_config
}
@router.post("/proxy-config")
async def update_proxy_config(config: dict):
"""更新代理配置"""
global proxy_config
if "is_enabled" in config:
proxy_config["is_enabled"] = config["is_enabled"]
if "proxy_url" in config:
proxy_config["proxy_url"] = config["proxy_url"]
return {
"success": True,
"message": "代理配置已更新",
"data": proxy_config
}
# ========== 无感换号 API ==========
# 内存存储无感配置 (生产环境应存数据库)
seamless_config = {
"enabled": False,
"mode": "auto",
"switchThreshold": 10,
"accountPool": [],
"currentIndex": 0
}
@router.get("/seamless/status")
async def seamless_status():
"""无感换号状态(简化实现)"""
async def seamless_status(db: Session = Depends(get_db)):
"""获取无感换号状态"""
return {
"success": True,
"enabled": seamless_config["enabled"],
"message": "无感换号功能已就绪" if seamless_config["enabled"] else "无感换号功能未启用"
}
@router.get("/seamless/user-status")
async def seamless_user_status(key: str = None, userKey: str = None, db: Session = Depends(get_db)):
"""获取用户切换状态"""
# 兼容两种参数名
actual_key = key or userKey
if not actual_key:
return {"success": False, "valid": False, "error": "缺少激活码参数"}
activation_key = KeyService.get_by_key(db, actual_key)
if not activation_key:
return {"success": False, "valid": False, "error": "激活码不存在"}
# 检查是否有效
is_valid, message = KeyService.is_valid(activation_key, db)
if not is_valid:
return {"success": False, "valid": False, "error": message}
# 获取当前绑定的账号
locked_account = None
if activation_key.current_account_id:
account = AccountService.get_by_id(db, activation_key.current_account_id)
if account and account.status not in (AccountStatus.DISABLED, AccountStatus.EXPIRED):
locked_account = {
"email": account.email,
"membership_type": account.membership_type.value
}
# Pro密钥使用 quota (积分制)
# Auto密钥使用换号次数限制
is_pro = activation_key.membership_type == MembershipType.PRO
switch_remaining = activation_key.quota - activation_key.quota_used if is_pro else 999 # Auto不限积分
return {
"success": True,
"valid": True,
"switchRemaining": switch_remaining,
"canSwitch": switch_remaining > 0,
"lockedAccount": locked_account,
"membershipType": activation_key.membership_type.value,
"data": {
"canSwitch": True,
"quotaRemaining": activation_key.quota - activation_key.quota_used,
"switchCount": activation_key.switch_count
}
}
@router.get("/seamless/config")
async def get_seamless_config():
"""获取无感配置"""
return {
"success": True,
"data": seamless_config
}
@router.post("/seamless/config")
async def update_seamless_config(config: dict):
"""更新无感配置"""
global seamless_config
for key in ["enabled", "mode", "switchThreshold", "accountPool", "currentIndex"]:
if key in config:
seamless_config[key] = config[key]
return {
"success": True,
"message": "配置已更新",
"data": seamless_config
}
@router.post("/seamless/inject")
async def inject_seamless(data: dict, req: Request, db: Session = Depends(get_db)):
"""注入无感模式 - 返回账号数据"""
user_key = data.get("user_key")
if not user_key:
return {"success": False, "error": "缺少user_key参数"}
key = KeyService.get_by_key(db, user_key)
if not key:
return {"success": False, "error": "激活码不存在"}
# 检查有效性
is_valid, message = KeyService.is_valid(key, db)
if not is_valid:
return {"success": False, "error": message}
# 获取账号
account = None
if key.current_account_id:
account = AccountService.get_by_id(db, key.current_account_id)
# 只有账号不存在或被禁用/过期才分配新的
if not account or account.status in (AccountStatus.DISABLED, AccountStatus.EXPIRED):
account = AccountService.get_available(db, key.membership_type)
if not account:
return {"success": False, "error": "暂无可用账号"}
KeyService.bind_account(db, key, account)
AccountService.mark_used(db, account, key.id)
LogService.log(db, key.id, "seamless_inject", account.id, ip_address=req.client.host, success=True)
return {
"success": True,
"message": "无感模式已注入",
"data": build_account_data(account, key).model_dump()
}
@router.post("/seamless/restore")
async def restore_seamless():
"""恢复无感模式设置"""
global seamless_config
seamless_config["enabled"] = False
return {
"success": True,
"message": "已恢复默认设置"
}
@router.get("/seamless/accounts")
async def get_seamless_accounts(db: Session = Depends(get_db)):
"""获取账号池列表"""
# 返回可用账号列表 (脱敏)
accounts = AccountService.get_all(db, limit=100)
account_list = []
for acc in accounts:
if acc.status == AccountStatus.ACTIVE:
account_list.append({
"id": acc.id,
"email": acc.email[:3] + "***" + acc.email[acc.email.index("@"):],
"membership_type": acc.membership_type.value,
"status": acc.status.value
})
return {
"success": True,
"data": {
"accounts": account_list,
"total": len(account_list)
}
}
@router.post("/seamless/accounts")
async def sync_seamless_accounts(data: dict):
"""同步账号池"""
# 这个接口在我们的架构中不需要实际功能
# 账号由管理后台统一管理
return {
"success": True,
"message": "账号由管理后台统一管理"
}
@router.get("/seamless/token")
async def get_seamless_token(key: str, db: Session = Depends(get_db)):
"""获取无感Token"""
activation_key = KeyService.get_by_key(db, key)
if not activation_key:
return {"success": False, "error": "激活码不存在"}
account = None
if activation_key.current_account_id:
account = AccountService.get_by_id(db, activation_key.current_account_id)
if not account:
return {"success": False, "error": "未绑定账号"}
return {
"success": True,
"data": build_account_data(account, activation_key).model_dump()
}
@router.get("/seamless/get-token")
async def get_seamless_token_v2(userKey: str = None, key: str = None, req: Request = None, db: Session = Depends(get_db)):
"""获取无感Token (注入代码调用的路径,兼容 userKey 和 key 两种参数名)
返回格式需要直接包含 accessToken、email 等字段,供注入代码使用
"""
actual_key = userKey or key
if not actual_key:
return {"success": False, "error": "缺少激活码参数"}
activation_key = KeyService.get_by_key(db, actual_key)
if not activation_key:
return {"success": False, "error": "激活码不存在"}
# 检查有效性
is_valid, message = KeyService.is_valid(activation_key, db)
if not is_valid:
return {"success": False, "error": message}
# 获取或分配账号
account = None
is_new = False
if activation_key.current_account_id:
account = AccountService.get_by_id(db, activation_key.current_account_id)
# 只有账号不存在或被禁用/过期才分配新的
if not account or account.status in (AccountStatus.DISABLED, AccountStatus.EXPIRED):
# Auto密钥检查是否允许获取新账号频率限制
if activation_key.membership_type == MembershipType.FREE:
can_switch, switch_msg = KeyService.can_switch(db, activation_key)
if not can_switch:
return {"success": False, "error": switch_msg}
# 分配新账号
account = AccountService.get_available(db, activation_key.membership_type)
if not account:
return {"success": False, "error": "暂无可用账号"}
KeyService.bind_account(db, activation_key, account)
AccountService.mark_used(db, account, activation_key.id)
# 记录换号(用于频率限制)
KeyService.use_switch(db, activation_key)
is_new = True
# 记录日志
if req:
LogService.log(db, activation_key.id, "seamless_get_token", account.id, ip_address=req.client.host, success=True)
# 返回格式需要直接包含字段,供注入代码使用
# 注入代码检查: if(d && d.accessToken) { ... }
return {
"success": True,
"accessToken": account.access_token,
"refreshToken": account.refresh_token or "",
"email": account.email,
"membership_type": account.membership_type.value,
"switchVersion": activation_key.switch_count, # 用于检测是否需要更新
"switchRemaining": activation_key.quota - activation_key.quota_used,
"is_new": is_new,
"machineIds": None # 机器ID由客户端本地管理
}
@router.post("/seamless/switch")
async def switch_seamless_token(data: dict, req: Request, db: Session = Depends(get_db)):
"""切换无感Token"""
user_key = data.get("userKey")
if not user_key:
return {"switched": False, "message": "缺少userKey参数"}
# 复用切换逻辑
request = SwitchAccountRequest(key=user_key)
return await switch_token_impl(request, req, db)
@router.post("/seamless/switch-token")
async def switch_seamless_token_v2(data: dict, req: Request, db: Session = Depends(get_db)):
"""切换无感Token (client.js 调用的路径)"""
user_key = data.get("userKey")
if not user_key:
return {"switched": False, "message": "缺少userKey参数"}
request = SwitchAccountRequest(key=user_key)
return await switch_token_impl(request, req, db)
async def switch_token_impl(request: SwitchAccountRequest, req: Request, db: Session):
"""切换Token实现 - 返回插件期望的格式"""
key = KeyService.get_by_key(db, request.key)
if not key:
return {"switched": False, "message": "激活码不存在"}
# 检查设备限制
if request.device_id:
device_ok, device_msg = KeyService.check_device(db, key, request.device_id)
if not device_ok:
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message=device_msg)
return {"switched": False, "message": device_msg}
# 检查激活码是否有效
is_valid, message = KeyService.is_valid(key, db)
if not is_valid:
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message=message)
return {"switched": False, "message": message}
# 检查换号频率限制
can_switch, switch_msg = KeyService.can_switch(db, key)
if not can_switch:
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message=switch_msg)
return {"switched": False, "message": switch_msg}
# 释放当前账号
if key.current_account_id:
old_account = AccountService.get_by_id(db, key.current_account_id)
if old_account:
AccountService.release(db, old_account)
# 获取新账号
account = AccountService.get_available(db, key.membership_type)
if not account:
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message="无可用账号")
return {"switched": False, "message": "暂无可用账号,请稍后重试"}
# 绑定新账号并扣除额度
KeyService.bind_account(db, key, account)
KeyService.use_switch(db, key)
AccountService.mark_used(db, account, key.id)
LogService.log(db, key.id, "switch", account.id, ip_address=req.client.host, success=True)
return {
"switched": True,
"switchRemaining": key.quota - key.quota_used,
"email": account.email,
"data": build_account_data(account, key)
}
# ========== 代理配置 API ==========
@router.get("/proxy-config")
async def get_proxy_config(db: Session = Depends(get_db)):
"""获取代理配置"""
return {
"success": True,
"enabled": False,
"message": "无感换号功能暂未开放"
"host": "",
"port": 0,
"username": "",
"password": ""
}
@router.put("/proxy-config")
async def update_proxy_config(config: dict, db: Session = Depends(get_db)):
"""更新代理配置 (客户端本地使用,后端仅返回成功)"""
return {"success": True, "message": "代理配置已保存"}
# ========== 无感换号配置 API ==========
@router.get("/seamless/status")
async def get_seamless_status(db: Session = Depends(get_db)):
"""获取无感换号全局状态"""
settings = GlobalSettingsService.get_all(db)
account_stats = AccountService.count(db)
return {
"success": True,
"enabled": True,
"available_accounts": account_stats.get("active", 0),
"total_accounts": account_stats.get("total", 0),
"switch_interval_minutes": settings.auto_switch_interval_minutes,
"max_switches_per_day": settings.auto_max_switches_per_day
}
@router.get("/seamless/config")
async def get_seamless_config(db: Session = Depends(get_db)):
"""获取无感换号配置"""
settings = GlobalSettingsService.get_all(db)
return {
"success": True,
"auto_switch": True,
"switch_interval_minutes": settings.auto_switch_interval_minutes,
"max_switches_per_day": settings.auto_max_switches_per_day,
"pro_quota_cost": settings.pro_quota_cost
}
@router.post("/seamless/config")
async def update_seamless_config(config: dict, db: Session = Depends(get_db)):
"""更新无感换号配置"""
# 从请求中提取配置
updates = {}
if "switch_interval_minutes" in config:
updates["auto_switch_interval_minutes"] = config["switch_interval_minutes"]
if "max_switches_per_day" in config:
updates["auto_max_switches_per_day"] = config["max_switches_per_day"]
if "pro_quota_cost" in config:
updates["pro_quota_cost"] = config["pro_quota_cost"]
if updates:
GlobalSettingsService.update_all(db, **updates)
return {"success": True, "message": "配置已更新"}
@router.post("/seamless/inject")
async def seamless_inject(data: dict, db: Session = Depends(get_db)):
"""注入无感换号代码 (客户端本地操作,后端仅记录)"""
user_key = data.get("userKey")
if user_key:
key = KeyService.get_by_key(db, user_key)
if key:
LogService.log(db, key.id, "seamless_inject", success=True, message="启用无感换号")
return {"success": True, "message": "无感换号已启用"}
@router.post("/seamless/restore")
async def seamless_restore(data: dict = None, db: Session = Depends(get_db)):
"""恢复原始代码 (客户端本地操作,后端仅记录)"""
if data and data.get("userKey"):
key = KeyService.get_by_key(db, data["userKey"])
if key:
LogService.log(db, key.id, "seamless_restore", success=True, message="禁用无感换号")
return {"success": True, "message": "已恢复原始状态"}
@router.get("/seamless/accounts")
async def get_seamless_accounts(userKey: str = None, db: Session = Depends(get_db)):
"""获取可用账号列表 (供管理使用)"""
if not userKey:
return {"success": False, "error": "缺少 userKey 参数"}
key = KeyService.get_by_key(db, userKey)
if not key:
return {"success": False, "error": "激活码不存在"}
# 获取该激活码类型的可用账号数量
accounts = AccountService.get_all(db, limit=100)
available_count = sum(1 for a in accounts if a.status == AccountStatus.ACTIVE and a.membership_type == key.membership_type)
return {
"success": True,
"available_count": available_count,
"membership_type": key.membership_type.value
}
@router.post("/seamless/sync-accounts")
async def sync_seamless_accounts(data: dict, db: Session = Depends(get_db)):
"""同步账号 (客户端上报账号信息)"""
# 客户端可能上报当前使用的账号状态
return {"success": True, "message": "同步成功"}
# ========== 版本检查 API ==========
@router.get("/version")
async def get_version():
"""获取最新版本信息"""
return {
"success": True,
"current_version": "0.4.5",
"latest_version": "0.4.5",
"has_update": False,
"download_url": "",
"changelog": ""
}
# ========== 公告 API ==========
@router.get("/announcement")
async def get_announcement(db: Session = Depends(get_db)):
"""获取公告信息"""
return {
"success": True,
"has_announcement": True,
"data": {
"is_active": True,
"title": "欢迎使用蜂鸟Pro",
"content": "感谢使用蜂鸟Pro\n\n如有问题请联系客服。",
"type": "info",
"created_at": "2024-12-17 00:00:00"
}
}
@router.get("/announcements/latest")
async def get_announcements_latest(db: Session = Depends(get_db)):
"""获取最新公告 (前端调用的路径)"""
return {
"success": True,
"data": {
"is_active": True,
"title": "欢迎使用蜂鸟Pro",
"content": "感谢使用蜂鸟Pro\n\n如有问题请联系客服。",
"type": "info",
"created_at": "2024-12-17 00:00:00"
}
}
# ========== 用量查询 API ==========
@router.get("/usage")
async def get_usage(userKey: str = None, db: Session = Depends(get_db)):
"""获取用量信息"""
if not userKey:
return {"success": False, "error": "缺少 userKey 参数"}
key = KeyService.get_by_key(db, userKey)
if not key:
return {"success": False, "error": "激活码不存在"}
account = None
if key.current_account_id:
account = AccountService.get_by_id(db, key.current_account_id)
return {
"success": True,
"membership_type": key.membership_type.value,
"quota": key.quota,
"quota_used": key.quota_used,
"quota_remaining": key.quota - key.quota_used,
"switch_count": key.switch_count,
"expire_at": key.expire_at.strftime("%Y-%m-%d %H:%M:%S") if key.expire_at else None,
"current_email": account.email if account else None
}