81 lines
2.4 KiB
Python
81 lines
2.4 KiB
Python
"""账号绑定服务 — TG ↔ Harvest"""
|
||
import secrets
|
||
import logging
|
||
|
||
import httpx
|
||
|
||
import config
|
||
from db.models import UserBinding
|
||
|
||
logger = logging.getLogger("spam_guard")
|
||
|
||
# 临时绑定 token 存储(token -> user_id,5分钟过期)
|
||
_pending_bindings: dict[str, dict] = {}
|
||
|
||
|
||
def generate_bind_token(tg_user_id: int, tg_username: str) -> tuple[str, str]:
|
||
"""生成绑定链接,返回 (token, url)"""
|
||
token = secrets.token_urlsafe(16)
|
||
_pending_bindings[token] = {
|
||
"tg_user_id": tg_user_id,
|
||
"tg_username": tg_username,
|
||
}
|
||
url = f"{config.HARVEST_SERVER_URL}/bind?token={token}&tg_user_id={tg_user_id}"
|
||
return token, url
|
||
|
||
|
||
async def verify_binding(token: str) -> dict:
|
||
"""验证绑定(harvest-server 回调时调用)"""
|
||
info = _pending_bindings.pop(token, None)
|
||
if not info:
|
||
return {"ok": False, "reason": "invalid_or_expired_token"}
|
||
|
||
tg_user_id = info["tg_user_id"]
|
||
tg_username = info["tg_username"]
|
||
|
||
# 尝试调用 harvest-server verify
|
||
try:
|
||
async with httpx.AsyncClient(timeout=10) as client:
|
||
resp = await client.post(
|
||
f"{config.HARVEST_SERVER_URL}/api/verify",
|
||
json={"token": token, "tg_user_id": tg_user_id},
|
||
)
|
||
if resp.status_code == 200:
|
||
data = resp.json()
|
||
harvest_uid = data.get("uid", "")
|
||
harvest_token = data.get("token", "")
|
||
else:
|
||
harvest_uid = str(tg_user_id)
|
||
harvest_token = token
|
||
except Exception:
|
||
harvest_uid = str(tg_user_id)
|
||
harvest_token = token
|
||
|
||
# 写入绑定
|
||
binding, _ = await UserBinding.get_or_create(
|
||
tg_user_id=tg_user_id,
|
||
defaults={
|
||
"tg_username": tg_username,
|
||
"harvest_uid": harvest_uid,
|
||
"harvest_token": harvest_token,
|
||
},
|
||
)
|
||
logger.info(f"🔗 账号绑定成功: TG={tg_user_id} Harvest={harvest_uid}")
|
||
return {"ok": True, "harvest_uid": harvest_uid}
|
||
|
||
|
||
async def unbind(tg_user_id: int) -> bool:
|
||
"""解绑"""
|
||
deleted = await UserBinding.filter(tg_user_id=tg_user_id).delete()
|
||
if deleted:
|
||
logger.info(f"🔗 账号解绑: TG={tg_user_id}")
|
||
return deleted > 0
|
||
|
||
|
||
async def get_binding(tg_user_id: int) -> dict | None:
|
||
"""查询绑定"""
|
||
b = await UserBinding.filter(tg_user_id=tg_user_id).first()
|
||
if b:
|
||
return {"harvest_uid": b.harvest_uid, "bound_at": b.bound_at}
|
||
return None
|