feat: 抽卡签到功能 - 首次抽卡/三连自动签到获随机积分

- data_manager: 新增 daily_sign_in 表、has_signed_in_today、record_sign_in 方法
- utils: 新增 get_luck_description、format_sign_in_message 函数
- __init__: 新增 try_handle_daily_sign_in 签到入口
- handle_gacha/handle_triple_gacha 成功路径 finish()→send()+签到+return
- 签到失败不影响抽卡主流程,UNIQUE约束防并发重复
This commit is contained in:
2026-04-05 22:07:50 +08:00
parent 08ba1399ef
commit 7f022b92e0
4 changed files with 212 additions and 7 deletions

View File

@@ -1,4 +1,6 @@
import os
import logging
import random
from nonebot import on_command, on_startswith
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageEvent, Message
from nonebot.adapters.onebot.v11.message import MessageSegment
@@ -8,9 +10,10 @@ from pathlib import Path
from .config import Config
from .gacha import GachaSystem
from .utils import format_user_mention, get_image_path
from .utils import format_sign_in_message, format_user_mention, get_image_path
from .api_utils import process_ssr_sp_reward, process_achievement_reward
from . import web_api
from danding_bot.plugins.danding_points import points_api
# 创建Config实例
config = Config()
@@ -27,6 +30,11 @@ INTRO_COMMANDS = config.INTRO_COMMANDS
DAILY_LIMIT = config.DAILY_LIMIT
gacha_system = GachaSystem()
logger = logging.getLogger(__name__)
SIGN_IN_MIN_POINTS = 1
SIGN_IN_MAX_POINTS = 100
SIGN_IN_SOURCE = "gacha_sign"
SIGN_IN_REASON = "抽卡签到"
# 检查是否允许使用功能的规则
def check_permission() -> Rule:
@@ -43,6 +51,32 @@ def check_permission() -> Rule:
return Rule(_checker)
async def try_handle_daily_sign_in(matcher, user_id: str, user_name: str) -> None:
"""处理抽卡成功后的每日签到,不影响主流程"""
try:
if gacha_system.data_manager.has_signed_in_today(user_id):
return
points = random.randint(SIGN_IN_MIN_POINTS, SIGN_IN_MAX_POINTS)
success, new_balance = await points_api.add_points(
user_id,
points,
SIGN_IN_SOURCE,
SIGN_IN_REASON,
)
if not success:
logger.error("抽卡签到积分发放失败 user_id=%s points=%s", user_id, points)
return
if not gacha_system.data_manager.record_sign_in(user_id, points):
logger.warning("抽卡签到落库冲突,积分已发放但签到记录重复 user_id=%s", user_id)
return
await matcher.send(format_sign_in_message(user_id, user_name, points, new_balance))
except Exception:
logger.exception("处理抽卡签到失败 user_id=%s", user_id)
# 注册抽卡命令,添加权限检查规则
gacha_matcher = on_command("抽卡", aliases=set(GACHA_COMMANDS), priority=10, rule=check_permission())
@@ -188,7 +222,9 @@ async def handle_gacha(bot: Bot, event: MessageEvent, state: T_State):
else:
msg.append(f"\n\n抽中SSR或SP时可获得蛋定助手天卡一张哦~~")
await gacha_matcher.finish(msg)
await gacha_matcher.send(msg)
await try_handle_daily_sign_in(gacha_matcher, user_id, user_name)
return
async def notify_admin(bot: Bot, message: str):
"""通知管理员"""
@@ -395,7 +431,9 @@ async def handle_triple_gacha(bot: Bot, event: MessageEvent, state: T_State):
admin_msg += f" 需要手动发放 {ssr_count} 张奖励!"
await notify_admin(bot, admin_msg)
await triple_gacha_matcher.finish(msg)
await triple_gacha_matcher.send(msg)
await try_handle_daily_sign_in(triple_gacha_matcher, user_id, user_name)
return
@achievement_matcher.handle()
async def handle_achievement(bot: Bot, event: MessageEvent, state: T_State):
@@ -759,4 +797,4 @@ from . import web_api
try:
web_api.register_web_routes()
except Exception as e:
print(f"❌ 注册 onmyoji_gacha Web 路由失败: {e}")
print(f"❌ 注册 onmyoji_gacha Web 路由失败: {e}")

View File

@@ -97,8 +97,23 @@ class DataManager:
total_consecutive_days INTEGER DEFAULT 0
)
""")
self._init_sign_in_table(cursor)
conn.commit()
def _init_sign_in_table(self, cursor: sqlite3.Cursor) -> None:
"""创建每日签到表"""
cursor.execute("""
CREATE TABLE IF NOT EXISTS daily_sign_in (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
sign_date TEXT NOT NULL,
points_awarded INTEGER NOT NULL,
created_at TEXT NOT NULL,
UNIQUE(user_id, sign_date)
)
""")
def update_achievement_progress(self, user_id: str, rarity: str) -> List[str]:
"""更新用户成就进度,返回新解锁的成就列表"""
@@ -321,6 +336,33 @@ class DataManager:
def get_today_date(self) -> str:
"""获取当前日期字符串"""
return datetime.datetime.now().strftime("%Y-%m-%d")
def has_signed_in_today(self, user_id: str) -> bool:
"""检查用户今天是否已签到"""
today = self.get_today_date()
with sqlite3.connect(config.DB_FILE) as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT 1 FROM daily_sign_in WHERE user_id = ? AND sign_date = ? LIMIT 1",
(user_id, today),
)
return cursor.fetchone() is not None
def record_sign_in(self, user_id: str, points_awarded: int) -> bool:
"""记录每日签到重复签到返回False"""
today = self.get_today_date()
created_at = datetime.datetime.now().isoformat()
try:
with sqlite3.connect(config.DB_FILE) as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO daily_sign_in (user_id, sign_date, points_awarded, created_at)
VALUES (?, ?, ?, ?)
""", (user_id, today, points_awarded, created_at))
conn.commit()
return True
except sqlite3.IntegrityError:
return False
def get_current_time(self) -> str:
"""获取当前时间字符串"""
@@ -549,4 +591,4 @@ class DataManager:
# 更新成就进度
unlocked_achievements = self.update_achievement_progress(user_id, rarity)
return unlocked_achievements
return unlocked_achievements

View File

@@ -1,5 +1,5 @@
import os
from typing import Union, Optional
from typing import Optional
from pathlib import Path
def get_image_path(file_path: str) -> str:
@@ -9,4 +9,34 @@ def get_image_path(file_path: str) -> str:
def format_user_mention(user_id: str, user_name: Optional[str] = None) -> str:
"""格式化用户@信息"""
display_name = user_name if user_name else f"用户{user_id}"
return f"@{display_name}"
return f"@{display_name}"
def get_luck_description(points: int) -> tuple[str, str]:
"""根据积分返回运气描述与emoji"""
if points <= 10:
return "非酋", "😭"
if points <= 30:
return "一般", "😐"
if points <= 60:
return "小欧", "😊"
if points <= 90:
return "大欧", "🎉"
return "欧皇", "👑"
def format_sign_in_message(
user_id: str,
user_name: str,
points: int,
balance: int,
) -> str:
"""格式化签到成功消息"""
luck_text, luck_emoji = get_luck_description(points)
mention = format_user_mention(user_id, user_name)
return (
f"{mention} 📅 每日签到成功!\n"
f"🎁 获得积分:{points}\n"
f"{luck_emoji} 今日运气:{luck_text}\n"
f"💰 当前积分:{balance}"
)