feat(sign_in): 新增独立每日签到插件,与抽卡签到共用同一份签到数据
- 群聊命令「签到」(别名: 每日签到/打卡),白名单群聊或管理员可用 - 复用 xapi /bot/gacha/sign-in + /bot/points/add,与抽卡/WPF 共用 bot_gacha_daily_sign_in 表 - 积分随机 1-100,流水沿用 gacha_sign/抽卡签到;先到先得,重复签到提示已签到 - 新增 10 个单元测试,ruff 通过;更新 PLUGINS.md / README.md
This commit is contained in:
66
danding_bot/plugins/danding_sign_in/service.py
Normal file
66
danding_bot/plugins/danding_sign_in/service.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""独立签到插件 - 签到编排。
|
||||
|
||||
先记录签到再发放积分(与抽卡签到 `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}
|
||||
Reference in New Issue
Block a user