- 群聊命令「签到」(别名: 每日签到/打卡),白名单群聊或管理员可用 - 复用 xapi /bot/gacha/sign-in + /bot/points/add,与抽卡/WPF 共用 bot_gacha_daily_sign_in 表 - 积分随机 1-100,流水沿用 gacha_sign/抽卡签到;先到先得,重复签到提示已签到 - 新增 10 个单元测试,ruff 通过;更新 PLUGINS.md / README.md
67 lines
2.5 KiB
Python
67 lines
2.5 KiB
Python
"""独立签到插件 - 签到编排。
|
||
|
||
先记录签到再发放积分(与抽卡签到 `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}
|