"""独立签到插件 - 签到编排。 先记录签到再发放积分(与抽卡签到 `onmyoji_gacha/sign_in.py` 同模式), 避免重复签到时重复发积分:xapi `POST /bot/gacha/sign-in` 负责判断当天 是否已签到(与抽卡/WPF 共用 `bot_gacha_daily_sign_in` 表去重),只有它 确认首次签到后,才调用 `/bot/points/add` 发积分。 """ from __future__ import annotations import logging import random from typing import Any, Dict, Optional, Protocol logger = logging.getLogger(__name__) class SignInRecorder(Protocol): async def record_sign_in(self, user_id: str, points_awarded: int) -> Optional[Dict[str, Any]]: """记录每日签到,返回 xapi data(含 signed_already);失败返回 None。""" class PointsAwarder(Protocol): async def add_points(self, user_id: str, amount: int, source: str, reason: str) -> tuple[bool, int]: """发放积分,返回(是否成功, 新余额)。""" async def perform_daily_sign_in( *, sign_in_api: SignInRecorder, points_api: PointsAwarder, user_id: str, points_min: int, points_max: int, source: str, reason: str, ) -> Dict[str, Any]: """执行每日签到。 返回 dict: - {"success": True, "already_signed": False, "points": int, "balance": int} 首次签到成功 - {"success": True, "already_signed": True, "points": int, "sign_date": str} 当天已签到 - {"success": False, "message": str} 失败 """ low, high = sorted((points_min, points_max)) points = random.randint(low, high) sign_result = await sign_in_api.record_sign_in(user_id, points) if sign_result is None: return {"success": False, "message": "签到服务暂不可用,请稍后再试"} if sign_result.get("signed_already"): # 今天已签到(抽卡/WPF/命令任一入口先到先得),不重复发积分 return { "success": True, "already_signed": True, "points": int(sign_result.get("points_awarded", 0) or 0), "sign_date": str(sign_result.get("sign_date", "")), } ok, balance = await points_api.add_points(user_id, points, source, reason) if not ok: logger.error("签到记录成功但积分发放失败 user_id=%s points=%s", user_id, points) return {"success": False, "message": "积分发放失败,请稍后再试"} return {"success": True, "already_signed": False, "points": points, "balance": balance}