merge: resolve onmyoji_gacha conflicts
This commit is contained in:
305
PLUGINS.md
305
PLUGINS.md
@@ -1,146 +1,159 @@
|
||||
# 蛋定助手插件文档
|
||||
|
||||
## 项目概述
|
||||
|
||||
蛋定助手是一个基于 NoneBot2 框架开发的 QQ 机器人,提供多种功能插件,包括 AI 聊天、游戏辅助、积分系统、社群管理等。
|
||||
|
||||
## 插件总览
|
||||
|
||||
| 插件名称 | 描述 | 触发方式 | 权限要求 |
|
||||
|---------|------|---------|---------|
|
||||
| [chatai](#1-chatai---ai-聊天) | AI 聊天(DeepSeek),支持图文回复 | `*` 开头消息 | 所有用户 |
|
||||
| [auto_recall](#2-auto_recall---自动撤回) | 自动撤回机器人发送的消息 | 自动执行 | 系统自动 |
|
||||
| [auto_friend_accept](#3-auto_friend_accept---自动接受好友) | 自动同意好友请求并发送欢迎语 | 自动执行 | 系统自动 |
|
||||
| [welcome_plugin](#4-welcome_plugin---入群欢迎) | 新成员入群欢迎并发送帮助菜单 | 自动执行 | 特定群 (621016172) |
|
||||
| [danding_qqpush](#5-danding_qqpush---消息推送) | 通过 HTTP API 向指定群推送图文通知 | HTTP POST | 接口 Token 验证 |
|
||||
| [danding_points](#6-danding_points---积分系统核心) | 积分系统数据库与 API 核心 | API 调用 | 系统内部 |
|
||||
| [danding_points_query](#7-danding_points_query---积分查询) | 查询余额、排行及交易记录 | 命令触发 | 所有用户 |
|
||||
| [group_horse_racing](#8-group_horse_racing---群赛马游戏) | 多人赛马游戏,支持下注与积分奖惩 | 命令触发 | 允许的群聊 |
|
||||
| [onmyoji_gacha](#9-onmyoji_gacha---阴阳师抽卡模拟) | 阴阳师主题抽卡,包含成就与卡密奖励 | 命令触发 | 允许的群聊/用户 |
|
||||
| [damo_balance](#10-damo_balance---大漠账户查询) | 查询大漠平台账户余额 | 命令触发 | 特定用户 |
|
||||
| [danding_api](#11-danding_api---管理-api) | 管理员操作:卡密管理、加时、在线查询 | 命令触发 | 超级用户 |
|
||||
| [danding_help](#12-danding_help---帮助菜单) | 提供教程、下载及功能指引 | 命令触发 | 特定群 (621016172) |
|
||||
| [command_list](#13-command_list---指令列表) | 获取系统支持的所有指令列表 | 命令触发 | 所有用户 |
|
||||
|
||||
---
|
||||
|
||||
## 详细插件文档
|
||||
|
||||
### 1. chatai - AI 聊天
|
||||
|
||||
基于 DeepSeek AI 的聊天功能,支持将回复转换为图片形式,并在一定时间后自动撤回。
|
||||
|
||||
- **使用方法**: 发送以 `*` 开头的消息。
|
||||
- **配置项**: `DEEPSEEK_TOKEN` (必填)。
|
||||
- **特性**: AI 回复会自动转为图片显示,默认 120 秒后撤回。
|
||||
|
||||
### 2. auto_recall - 自动撤回
|
||||
|
||||
监控机器人发出的消息,并在指定时间后自动撤回,保持聊天环境整洁。
|
||||
|
||||
- **配置项**:
|
||||
- `RECALL_DELAY`: 普通消息撤回延迟(默认 110s)。
|
||||
- `QQPUSH_RECALL_DELAY`: 推送消息撤回延迟(默认 3600s)。
|
||||
|
||||
### 3. auto_friend_accept - 自动接受好友
|
||||
|
||||
自动处理好友请求,提升用户接入效率。
|
||||
|
||||
- **配置项**:
|
||||
- `auto_accept_enabled`: 是否开启自动接受。
|
||||
- `auto_reply_message`: 接受后的欢迎语。
|
||||
|
||||
### 4. welcome_plugin - 入群欢迎
|
||||
|
||||
针对特定群聊的新成员欢迎功能。
|
||||
|
||||
- **触发场景**: 新成员加入群 `621016172`。
|
||||
- **功能**: 随机发送欢迎语,并附带帮助菜单图片。
|
||||
|
||||
### 5. danding_qqpush - 消息推送
|
||||
|
||||
提供外部系统向 QQ 推送通知的 HTTP 接口。
|
||||
|
||||
- **接口**: `POST /danding/qqpush/{token}`
|
||||
- **功能**: 自动将长文本转换为图片,支持 `@用户` 和换行符 `#`。
|
||||
- **配置**: `DANDING_QQPUSH_TOKEN`。
|
||||
|
||||
### 6. danding_points - 积分系统核心
|
||||
|
||||
为其他插件提供积分存储与结算的基础设施。
|
||||
|
||||
- **功能**: 数据库管理(SQLite)、余额增减、排行榜计算、交易日志记录。
|
||||
- **数据库路径**: `data/danding_points/points.db`
|
||||
|
||||
### 7. danding_points_query - 积分查询
|
||||
|
||||
用户通过命令与积分系统交互。
|
||||
|
||||
- **主要命令**:
|
||||
- `我的积分`: 查看个人余额。
|
||||
- `积分查询 @用户`: 查看他人余额。
|
||||
- `积分排行`: 查看前 10 名。
|
||||
- `积分历史查询`: 查看最近 5 条变动记录。
|
||||
- `积分帮助`: 获取指令指引。
|
||||
|
||||
### 8. group_horse_racing - 群赛马游戏
|
||||
|
||||
集成积分系统的多人互动游戏。
|
||||
|
||||
- **主要命令**:
|
||||
- `/赛马报名 [马名]`: 参加比赛。
|
||||
- `/赛马下注 <序号> <金额>`: 对马匹下注。
|
||||
- `/赛马开赛`: 开始比赛(至少 2 人)。
|
||||
- **积分逻辑**: 参赛奖励 50 分,冠军奖励 200 分,支持下注赔率结算。
|
||||
|
||||
### 9. onmyoji_gacha - 阴阳师抽卡模拟
|
||||
|
||||
高度还原的抽卡模拟,包含成就系统。
|
||||
|
||||
- **主要命令**:
|
||||
- `抽卡`: 执行单抽。
|
||||
- `三连抽`: 执行三连抽。
|
||||
- `我的抽卡`: 查看个人统计。
|
||||
- `查询成就`: 查看已解锁成就及进度(非酋、勤勤恳恳系列)。
|
||||
- `抽卡介绍`: 查看详细机制与奖励说明。
|
||||
- **特性**: 抽中 SSR/SP 可获得“蛋定助手”卡密奖励(需联系管理员领取)。包含每日抽卡签到积分奖励。
|
||||
|
||||
### 10. damo_balance - 大漠账户查询
|
||||
|
||||
查询大漠平台账户余额。
|
||||
|
||||
- **命令**: `大漠余额` 或 `余额查询`。
|
||||
- **限制**: 仅特定用户可用,需输入验证码。
|
||||
|
||||
### 11. danding_api - 管理 API
|
||||
|
||||
供超级用户使用的后台管理功能。
|
||||
|
||||
- **主要命令**:
|
||||
- `在线人数`: 查询当前活跃用户。
|
||||
- `生成卡密 <类型>`: 生成天/周/月卡。
|
||||
- `用户加时 <用户名> <类型>`: 直接为特定用户增加时长。
|
||||
|
||||
### 12. danding_help - 帮助菜单
|
||||
|
||||
系统的官方指引手册。
|
||||
|
||||
- **主要命令**: `帮助`、`下载`、`正式版如何运行` 等。
|
||||
- **限制**: 仅在特定群 `621016172` 可用。
|
||||
|
||||
### 13. command_list - 指令列表
|
||||
|
||||
快速查阅所有可用指令。
|
||||
|
||||
- **命令**: `指令列表`。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题 (FAQ)
|
||||
|
||||
- **Q: 为什么某些命令没反应?**
|
||||
A: 部分插件(如 `danding_help`)限制了特定群聊使用;管理指令需要配置 `SUPERUSERS`。
|
||||
- **Q: 积分有什么用?**
|
||||
A: 目前主要用于赛马下注及展示排名。
|
||||
- **Q: 抽卡奖励如何领取?**
|
||||
A: 抽中 SSR/SP 或解锁特定成就后,请截屏联系管理员。
|
||||
# 蛋定助手插件文档
|
||||
|
||||
## 项目概述
|
||||
|
||||
蛋定助手是一个基于 NoneBot2 框架开发的 QQ 机器人,提供多种功能插件,包括 AI 聊天、游戏辅助、积分系统、社群管理等。
|
||||
|
||||
## 插件总览
|
||||
|
||||
| 插件名称 | 描述 | 触发方式 | 权限要求 |
|
||||
|---------|------|---------|---------|
|
||||
| [chatai](#1-chatai---ai-聊天) | AI 聊天(DeepSeek),支持图文回复 | `*` 开头消息 | 所有用户 |
|
||||
| [auto_recall](#2-auto_recall---自动撤回) | 自动撤回机器人发送的消息 | 自动执行 | 系统自动 |
|
||||
| [auto_friend_accept](#3-auto_friend_accept---自动接受好友) | 自动同意好友请求并发送欢迎语 | 自动执行 | 系统自动 |
|
||||
| [welcome_plugin](#4-welcome_plugin---入群欢迎) | 新成员入群欢迎并发送帮助菜单 | 自动执行 | 特定群 (621016172) |
|
||||
| [danding_qqpush](#5-danding_qqpush---消息推送) | 通过 HTTP API 向指定群推送图文通知 | HTTP POST | 接口 Token 验证 |
|
||||
| [danding_points](#6-danding_points---积分系统核心) | 积分系统数据库与 API 核心 | API 调用 | 系统内部 |
|
||||
| [danding_points_query](#7-danding_points_query---积分查询) | 查询余额、排行及交易记录 | 命令触发 | 所有用户 |
|
||||
| [group_horse_racing](#8-group_horse_racing---群赛马游戏) | 多人赛马游戏,支持下注与积分奖惩 | 命令触发 | 允许的群聊 |
|
||||
| [onmyoji_gacha](#9-onmyoji_gacha---阴阳师抽卡模拟) | 阴阳师主题抽卡,包含成就与卡密奖励 | 命令触发 | 允许的群聊/用户 |
|
||||
| [damo_balance](#10-damo_balance---大漠账户查询) | 查询大漠平台账户余额 | 命令触发 | 特定用户 |
|
||||
| [danding_api](#11-danding_api---管理-api) | 管理员操作:卡密管理、加时、在线查询 | 命令触发 | 超级用户 |
|
||||
| [danding_help](#12-danding_help---帮助菜单) | 提供教程、下载及功能指引 | 命令触发 | 特定群 (621016172) |
|
||||
| [command_list](#13-command_list---指令列表) | 获取系统支持的所有指令列表 | 命令触发 | 所有用户 |
|
||||
|
||||
---
|
||||
|
||||
## 详细插件文档
|
||||
|
||||
### 1. chatai - AI 聊天
|
||||
|
||||
基于 DeepSeek AI 的聊天功能,支持将回复转换为图片形式,并在一定时间后自动撤回。
|
||||
|
||||
- **使用方法**: 发送以 `*` 开头的消息。
|
||||
- **配置项**: `DEEPSEEK_TOKEN` (必填)。
|
||||
- **特性**: AI 回复会自动转为图片显示,默认 120 秒后撤回。
|
||||
|
||||
### 2. auto_recall - 自动撤回
|
||||
|
||||
监控机器人发出的消息,并在指定时间后自动撤回,保持聊天环境整洁。
|
||||
|
||||
- **配置项**:
|
||||
- `RECALL_DELAY`: 普通消息撤回延迟(默认 110s)。
|
||||
- `QQPUSH_RECALL_DELAY`: 推送消息撤回延迟(默认 3600s)。
|
||||
|
||||
### 3. auto_friend_accept - 自动接受好友
|
||||
|
||||
自动处理好友请求,提升用户接入效率。
|
||||
|
||||
- **配置项**:
|
||||
- `auto_accept_enabled`: 是否开启自动接受。
|
||||
- `auto_reply_message`: 接受后的欢迎语。
|
||||
|
||||
### 4. welcome_plugin - 入群欢迎
|
||||
|
||||
针对特定群聊的新成员欢迎功能。
|
||||
|
||||
- **触发场景**: 新成员加入群 `621016172`。
|
||||
- **功能**: 随机发送欢迎语,并附带帮助菜单图片。
|
||||
|
||||
### 5. danding_qqpush - 消息推送
|
||||
|
||||
提供外部系统向 QQ 推送通知的 HTTP 接口。
|
||||
|
||||
- **接口**: `POST /danding/qqpush/{token}`
|
||||
- **功能**: 自动将长文本转换为图片,支持 `@用户` 和换行符 `#`。
|
||||
- **配置**: `DANDING_QQPUSH_TOKEN`。
|
||||
|
||||
### 6. danding_points - 积分系统核心
|
||||
|
||||
为其他插件提供积分存储与结算的基础设施。
|
||||
|
||||
- **功能**: 数据库管理(SQLite)、余额增减、排行榜计算、交易日志记录。
|
||||
- **数据库路径**: `data/danding_points/points.db`
|
||||
|
||||
### 7. danding_points_query - 积分查询
|
||||
|
||||
用户通过命令与积分系统交互。
|
||||
|
||||
- **主要命令**:
|
||||
- `我的积分`: 查看个人余额。
|
||||
- `积分查询 @用户`: 查看他人余额。
|
||||
- `积分排行`: 查看前 10 名。
|
||||
- `积分历史查询`: 查看最近 5 条变动记录。
|
||||
- `积分帮助`: 获取指令指引。
|
||||
|
||||
### 8. group_horse_racing - 群赛马游戏
|
||||
|
||||
集成积分系统的多人互动游戏。
|
||||
|
||||
- **主要命令**:
|
||||
- `/赛马报名 [马名]`: 参加比赛。
|
||||
- `/赛马下注 <序号> <金额>`: 对马匹下注。
|
||||
- `/赛马开赛`: 开始比赛(至少 2 人)。
|
||||
- **积分逻辑**: 参赛奖励 50 分,冠军奖励 200 分,支持下注赔率结算。
|
||||
|
||||
### 9. onmyoji_gacha - 阴阳师抽卡模拟
|
||||
|
||||
高度还原的抽卡模拟,包含成就系统。采用模块化架构,职责分明。
|
||||
|
||||
- **模块结构**:
|
||||
- `__init__.py`: 路由注册与matcher定义(167行)
|
||||
- `config.py`: Pydantic配置管理
|
||||
- `gacha.py`: 抽卡核心逻辑(GachaSystem类)
|
||||
- `data_manager.py`: SQLite数据持久化(DataManager类)
|
||||
- `rules.py`: 命令匹配规则(check_permission等)
|
||||
- `formatters.py`: 消息格式化(9个格式化函数)
|
||||
- `handlers/`: 命令处理函数(9个handler模块)
|
||||
- `utils.py`: 通用工具函数
|
||||
- `api_utils.py`: 积分系统API交互
|
||||
- `web_api.py`: HTTP API路由
|
||||
|
||||
- **主要命令**:
|
||||
- `抽卡`: 执行单抽。
|
||||
- `三连抽`: 执行三连抽。
|
||||
- `我的抽卡`: 查看个人统计。
|
||||
- `抽卡排行`: 查看抽卡排行榜。
|
||||
- `今日抽卡`: 查看今日抽卡统计。
|
||||
- `查询成就`: 查看已解锁成就及进度(非酋、勤勤恳恳系列)。
|
||||
- `抽卡介绍`: 查看详细机制与奖励说明。
|
||||
- **特性**: 抽中 SSR/SP 可获得"蛋定助手"卡密奖励(需联系管理员领取)。包含每日抽卡签到积分奖励。成就系统自动发放积分奖励。
|
||||
### 10. damo_balance - 大漠账户查询
|
||||
|
||||
查询大漠平台账户余额。
|
||||
|
||||
- **命令**: `大漠余额` 或 `余额查询`。
|
||||
- **限制**: 仅特定用户可用,需输入验证码。
|
||||
|
||||
### 11. danding_api - 管理 API
|
||||
|
||||
供超级用户使用的后台管理功能。
|
||||
|
||||
- **主要命令**:
|
||||
- `在线人数`: 查询当前活跃用户。
|
||||
- `生成卡密 <类型>`: 生成天/周/月卡。
|
||||
- `用户加时 <用户名> <类型>`: 直接为特定用户增加时长。
|
||||
|
||||
### 12. danding_help - 帮助菜单
|
||||
|
||||
系统的官方指引手册。
|
||||
|
||||
- **主要命令**: `帮助`、`下载`、`正式版如何运行` 等。
|
||||
- **限制**: 仅在特定群 `621016172` 可用。
|
||||
|
||||
### 13. command_list - 指令列表
|
||||
|
||||
快速查阅所有可用指令。
|
||||
|
||||
- **命令**: `指令列表`。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题 (FAQ)
|
||||
|
||||
- **Q: 为什么某些命令没反应?**
|
||||
A: 部分插件(如 `danding_help`)限制了特定群聊使用;管理指令需要配置 `SUPERUSERS`。
|
||||
- **Q: 积分有什么用?**
|
||||
A: 目前主要用于赛马下注及展示排名。
|
||||
- **Q: 抽卡奖励如何领取?**
|
||||
A: 抽中 SSR/SP 或解锁特定成就后,请截屏联系管理员。
|
||||
|
||||
@@ -249,4 +249,4 @@ async def process_achievement_reward(user_id: str, achievement_id: str) -> Tuple
|
||||
f"获得奖励:蛋定助手{reward_type}一张\n"
|
||||
f"⚠️自动加时失败: {message}\n"
|
||||
f"请联系管理员手动领取奖励!")
|
||||
return False, msg
|
||||
return False, msg
|
||||
|
||||
@@ -128,4 +128,4 @@ class Config(BaseSettings):
|
||||
"""运行时警告:如果使用默认admin token,在生产环境可能被猜解"""
|
||||
if self.WEB_ADMIN_TOKEN == "onmyoji_admin_token_2024":
|
||||
logger.warning("⚠️ WEB_ADMIN_TOKEN 使用默认值,生产环境请务必通过环境变量覆盖!")
|
||||
return self
|
||||
return self
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
299
danding_bot/plugins/onmyoji_gacha/formatters.py
Normal file
299
danding_bot/plugins/onmyoji_gacha/formatters.py
Normal file
@@ -0,0 +1,299 @@
|
||||
"""
|
||||
消息格式化模块 - 抽卡、成就、统计等所有用户可见输出
|
||||
|
||||
提供所有用户可见消息的格式化函数,包括:
|
||||
- 抽卡结果消息
|
||||
- 三连抽结果消息
|
||||
- 成就通知消息
|
||||
- 统计查询消息
|
||||
- 排行榜消息
|
||||
- 每日统计消息
|
||||
|
||||
所有函数返回NoneBot的Message对象,可直接用于matcher.send()。
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Any, Optional, Tuple
|
||||
from nonebot.adapters.onebot.v11 import Message, MessageSegment
|
||||
from .utils import format_user_mention, get_image_path
|
||||
import os
|
||||
|
||||
|
||||
# 稀有度显示配置(单一数据源,消除重复模式)
|
||||
RARITY_DISPLAY = {
|
||||
"SSR": {"congrats": ("🌟✨", "✨🌟"), "card": "🎊", "desc": "SSR", "tail": "💫"},
|
||||
"SP": {"congrats": ("🌈🎆", "🎆🌈"), "card": "🎉", "desc": "SP", "tail": "🔥"},
|
||||
"SR": {"congrats": ("⭐", "⭐"), "card": "✨", "desc": "SR", "tail": ""},
|
||||
"R": {"congrats": ("🍀", "🍀"), "card": "📜", "desc": "R", "tail": ""},
|
||||
}
|
||||
|
||||
def format_gacha_result(rarity: str, name: str, user_id: str, user_name: str, image_url: str) -> Message:
|
||||
"""
|
||||
格式化单次抽卡结果消息。
|
||||
|
||||
Args:
|
||||
rarity: 稀有度 (SSR/SP/SR/R)
|
||||
name: 式神名称
|
||||
user_id: QQ号
|
||||
user_name: 用户昵称
|
||||
image_url: 图片路径
|
||||
|
||||
Returns:
|
||||
Message: 包含文本和图片的消息对象
|
||||
"""
|
||||
if not rarity or not name:
|
||||
return Message("[抽卡] 数据不完整")
|
||||
msg = Message()
|
||||
if rarity == "SSR":
|
||||
msg.append(f"🌟✨ 恭喜 {format_user_mention(user_id, user_name)} ✨🌟\n")
|
||||
msg.append(f"🎊 抽到了 SSR 式神:{name} 🎊\n")
|
||||
msg.append(f"💫 真是太幸运了!💫")
|
||||
elif rarity == "SP":
|
||||
msg.append(f"🌈🎆 恭喜 {format_user_mention(user_id, user_name)} 🎆🌈\n")
|
||||
msg.append(f"🎉 抽到了 SP 式神:{name} 🎉\n")
|
||||
msg.append(f"🔥 这是传说中的SP!🔥")
|
||||
elif rarity == "SR":
|
||||
msg.append(f"⭐ 恭喜 {format_user_mention(user_id, user_name)} ⭐\n")
|
||||
msg.append(f"✨ 抽到了 SR 式神:{name} ✨")
|
||||
else: # R
|
||||
msg.append(f"🍀 {format_user_mention(user_id, user_name)} 🍀\n")
|
||||
msg.append(f"📜 抽到了 R 式神:{name}")
|
||||
if image_url and os.path.exists(image_url):
|
||||
msg.append(MessageSegment.image(f"file:///{get_image_path(image_url)}"))
|
||||
return msg
|
||||
|
||||
|
||||
def format_triple_gacha_result(results: List[Tuple[str, str, str]], user_id: str, user_name: str) -> Message:
|
||||
"""
|
||||
格式化三连抽结果消息。
|
||||
|
||||
Args:
|
||||
results: 三连抽结果列表,每个元素为(稀有度, 式神名, 图片路径)
|
||||
user_id: QQ号
|
||||
user_name: 用户昵称
|
||||
|
||||
Returns:
|
||||
Message: 包含三连抽结果的消息对象
|
||||
"""
|
||||
msg = Message()
|
||||
msg.append(f"🎯 {format_user_mention(user_id, user_name)} 的三连抽结果:\n\n")
|
||||
|
||||
for i, (rarity, name, image_path) in enumerate(results, 1):
|
||||
if rarity == "SSR":
|
||||
msg.append(f"🌟 第{i}抽:SSR - {name}\n")
|
||||
elif rarity == "SP":
|
||||
msg.append(f"🌈 第{i}抽:SP - {name}\n")
|
||||
elif rarity == "SR":
|
||||
msg.append(f"⭐ 第{i}抽:SR - {name}\n")
|
||||
else: # R
|
||||
msg.append(f"📜 第{i}抽:R - {name}\n")
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def format_achievement_notify(
|
||||
achievements_data: List[Dict[str, Any]],
|
||||
user_id: str,
|
||||
) -> Message:
|
||||
"""
|
||||
格式化成就解锁通知消息。
|
||||
|
||||
纯格式化函数,不执行任何业务逻辑(奖励发放等)。
|
||||
调用方负责解析成就数据和处理奖励。
|
||||
|
||||
Args:
|
||||
achievements_data: 已解析的成就数据列表,每项含 name/reward/claimed/reward_msg 等字段
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
Message: 格式化的成就通知消息
|
||||
|
||||
Note:
|
||||
纯函数,无副作用,无外部调用。
|
||||
"""
|
||||
if not achievements_data:
|
||||
return Message()
|
||||
msg = Message()
|
||||
|
||||
if achievements_data:
|
||||
msg.append("\n\n🏆 恭喜解锁新成就!\n")
|
||||
|
||||
for ach in achievements_data:
|
||||
name = ach.get("name", "未知成就")
|
||||
reward = ach.get("reward", 0)
|
||||
reward_msg = ach.get("reward_msg", "")
|
||||
claimed = ach.get("claimed", False)
|
||||
|
||||
if claimed and reward_msg:
|
||||
msg.append(f"🎖️ {name} 重复奖励 (奖励:{reward}) {reward_msg}\n")
|
||||
else:
|
||||
msg.append(f"🎖️ {name}\n")
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def format_achievement_progress(
|
||||
consecutive_days: int,
|
||||
no_ssr_streak: int,
|
||||
user_id: str
|
||||
) -> Message:
|
||||
"""
|
||||
格式化成就进度消息。
|
||||
|
||||
Args:
|
||||
consecutive_days: 连续抽卡天数
|
||||
no_ssr_streak: 连续未出SSR/SP次数
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
Message: 包含成就进度的消息对象
|
||||
"""
|
||||
from .gacha import get_achievement_definition
|
||||
|
||||
msg = Message()
|
||||
msg.append(f"🎯 成就进度:\n")
|
||||
|
||||
# 勤勤恳恳成就进度
|
||||
achievement = get_achievement_definition("勤勤恳恳Ⅰ")
|
||||
if achievement:
|
||||
if consecutive_days < 30:
|
||||
msg.append(f"📅 勤勤恳恳Ⅰ:{consecutive_days}/30天 🎯\n")
|
||||
elif consecutive_days < 60:
|
||||
msg.append(f"📅 勤勤恳恳Ⅱ:{consecutive_days}/60天 🎯\n")
|
||||
elif consecutive_days < 90:
|
||||
msg.append(f"📅 勤勤恳恳Ⅲ:{consecutive_days}/90天 🎯\n")
|
||||
elif consecutive_days < 120:
|
||||
msg.append(f"📅 勤勤恳恳Ⅳ:{consecutive_days}/120天 ⭐\n")
|
||||
elif consecutive_days < 150:
|
||||
msg.append(f"📅 勤勤恳恳Ⅴ:{consecutive_days}/150天 ⭐\n")
|
||||
else:
|
||||
# 计算下次奖励周期
|
||||
next_reward = 150 + ((consecutive_days - 150) // 30 + 1) * 30
|
||||
if next_reward <= 365:
|
||||
msg.append(f"📅 勤勤恳恳Ⅴ (满级):{consecutive_days}天,距离下次奖励{next_reward}天 🎯\n")
|
||||
else:
|
||||
msg.append(f"📅 勤勤恳恳Ⅴ (满级):{consecutive_days}天,可获得奖励!🎉\n")
|
||||
|
||||
# 非酋成就进度
|
||||
if no_ssr_streak > 0:
|
||||
if no_ssr_streak < 60:
|
||||
msg.append(f"💔 非酋进度:{no_ssr_streak}/60次 😭\n")
|
||||
elif no_ssr_streak < 120:
|
||||
msg.append(f"💔 顶级非酋:{no_ssr_streak}/120次 😱\n")
|
||||
elif no_ssr_streak < 180:
|
||||
msg.append(f"💔 月见黑:{no_ssr_streak}/180次 🌙\n")
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def format_user_stats(stats: Dict[str, Any], user_id: str, user_name: str) -> Message:
|
||||
"""
|
||||
格式化用户抽卡统计消息。
|
||||
|
||||
Args:
|
||||
stats: 用户统计数据字典
|
||||
user_id: 用户ID
|
||||
user_name: 用户昵称
|
||||
|
||||
Returns:
|
||||
Message: 包含统计信息的消息对象
|
||||
"""
|
||||
msg = Message()
|
||||
msg.append(f"📊 {format_user_mention(user_id, user_name)} 的抽卡统计:\n")
|
||||
msg.append(f"🎲 总抽卡次数:{stats['total_draws']}次\n")
|
||||
|
||||
total = stats['total_draws']
|
||||
if total > 0:
|
||||
msg.append(f"\n稀有度分布:\n")
|
||||
msg.append(f"📜 R:{stats.get('R_count',0)}张 ({stats.get('R_count',0)/total*100:.1f}%)\n")
|
||||
msg.append(f"⭐ SR:{stats.get('SR_count',0)}张 ({stats.get('SR_count',0)/total*100:.1f}%)\n")
|
||||
msg.append(f"✨ SSR:{stats.get('SSR_count',0)}张 ({stats.get('SSR_count',0)/total*100:.1f}%)\n")
|
||||
msg.append(f"🌈 SP:{stats.get('SP_count',0)}张 ({stats.get('SP_count',0)/total*100:.1f}%)")
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def format_user_detail_stats(
|
||||
stats: Dict[str, Any],
|
||||
user_id: str,
|
||||
user_name: str,
|
||||
recent_draws: List[Dict[str, Any]]
|
||||
) -> Message:
|
||||
"""
|
||||
格式化用户详细抽卡统计消息。
|
||||
|
||||
Args:
|
||||
stats: 用户统计数据字典
|
||||
user_id: 用户ID
|
||||
user_name: 用户昵称
|
||||
recent_draws: 最近抽卡记录列表
|
||||
|
||||
Returns:
|
||||
Message: 包含详细统计信息的消息对象
|
||||
"""
|
||||
msg = Message()
|
||||
msg.append(f"{format_user_mention(user_id, user_name)} 的抽卡统计:\n")
|
||||
msg.append(f"总抽卡次数:{stats['total_draws']}次\n")
|
||||
|
||||
total = stats['total_draws']
|
||||
if total > 0:
|
||||
msg.append(f"R:{stats.get('R_count',0)}张 ({stats.get('R_count',0)/total*100:.1f}%)\n")
|
||||
msg.append(f"SR:{stats.get('SR_count',0)}张 ({stats.get('SR_count',0)/total*100:.1f}%)\n")
|
||||
msg.append(f"SSR:{stats.get('SSR_count',0)}张 ({stats.get('SSR_count',0)/total*100:.1f}%)\n")
|
||||
msg.append(f"SP:{stats.get('SP_count',0)}张 ({stats.get('SP_count',0)/total*100:.1f}%)")
|
||||
|
||||
if recent_draws:
|
||||
msg.append(f"\n最近{len(recent_draws)}次抽卡:\n")
|
||||
for draw in recent_draws:
|
||||
msg.append(f"• {draw}\n")
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def format_rank_list(rank_data: List[Dict[str, Any]], page: int, total_pages: int) -> Message:
|
||||
"""
|
||||
格式化抽卡排行榜消息。
|
||||
|
||||
Args:
|
||||
rank_data: 排行榜数据列表
|
||||
page: 当前页码
|
||||
total_pages: 总页数
|
||||
|
||||
Returns:
|
||||
Message: 包含排行榜的消息对象
|
||||
"""
|
||||
msg = Message()
|
||||
msg.append(f"🏆 抽卡排行榜 (第{page}页/共{total_pages}页)\n\n")
|
||||
|
||||
for idx, entry in enumerate(rank_data, 1):
|
||||
msg.append(f"{idx}. {entry.get('user_name', '未知')} - {entry.get('total_draws', 0)}次\n")
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def format_daily_stats(daily_stats: Dict[str, Any]) -> Message:
|
||||
"""
|
||||
格式化今日抽卡统计消息。
|
||||
|
||||
Args:
|
||||
daily_stats: 今日统计数据字典
|
||||
|
||||
Returns:
|
||||
Message: 包含今日统计的消息对象
|
||||
"""
|
||||
msg = Message()
|
||||
msg.append(f"📅 今日抽卡统计\n")
|
||||
msg.append(f"总抽卡次数:{daily_stats.get('today_total',0)}次\n")
|
||||
|
||||
msg.append(f"\n稀有度分布:\n")
|
||||
msg.append(f"R:{daily_stats.get('R_count',0)}张\n")
|
||||
msg.append(f"SR:{daily_stats.get('SR_count',0)}张\n")
|
||||
msg.append(f"SSR:{daily_stats.get('SSR_count',0)}张\n")
|
||||
msg.append(f"SP:{daily_stats.get('SP_count',0)}张\n")
|
||||
|
||||
top = daily_stats.get("top_users", [])
|
||||
if top:
|
||||
msg.append("\n今日TOP5:\n")
|
||||
for idx, u in enumerate(top[:5], 1):
|
||||
msg.append(f"{idx}. {u.get('user_name','未知')} - {u.get('draws',0)}次\n")
|
||||
|
||||
return msg
|
||||
@@ -1,307 +1,318 @@
|
||||
import random
|
||||
from typing import Dict, Tuple, List, Optional
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from .config import Config
|
||||
from .data_manager import DataManager
|
||||
|
||||
config = Config()
|
||||
data_manager = DataManager()
|
||||
|
||||
class GachaSystem:
|
||||
def __init__(self):
|
||||
self.data_manager = data_manager
|
||||
|
||||
def draw(self, user_id: str) -> Dict:
|
||||
"""执行一次抽卡"""
|
||||
# 检查抽卡限制
|
||||
if not self.data_manager.check_daily_limit(user_id):
|
||||
draws_left = self.data_manager.get_draws_left(user_id)
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"您今日的抽卡次数已用完,每日限制{config.DAILY_LIMIT}次,明天再来吧!"
|
||||
}
|
||||
|
||||
# 抽取稀有度(传递用户ID)
|
||||
rarity = self._draw_rarity(user_id)
|
||||
|
||||
# 从该稀有度中抽取式神
|
||||
shikigami_data = self.data_manager.shikigami_data.get(rarity, [])
|
||||
if not shikigami_data:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"系统错误:{rarity}稀有度下没有可用式神"
|
||||
}
|
||||
|
||||
# 随机选择式神
|
||||
shikigami = random.choice(shikigami_data)
|
||||
|
||||
# 记录抽卡
|
||||
unlocked_achievements = self.data_manager.record_draw(user_id, rarity, shikigami["name"])
|
||||
|
||||
# 剩余次数
|
||||
draws_left = self.data_manager.get_draws_left(user_id)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"rarity": rarity,
|
||||
"name": shikigami["name"],
|
||||
"image_url": shikigami["image_url"],
|
||||
"draws_left": draws_left,
|
||||
"unlocked_achievements": unlocked_achievements
|
||||
}
|
||||
|
||||
def _draw_rarity(self, user_id: str = None) -> str:
|
||||
"""按概率抽取稀有度"""
|
||||
# 检查是否是特殊概率用户
|
||||
if user_id and user_id in config.SPECIAL_PROBABILITY_USERS:
|
||||
# 100%概率抽到SSR或SP,随机选择
|
||||
return random.choice(["SSR", "SP"])
|
||||
|
||||
# 普通用户的概率逻辑
|
||||
r = random.random() * 100 # 0-100的随机数
|
||||
|
||||
cumulative = 0
|
||||
for rarity, prob in config.RARITY_PROBABILITY.items():
|
||||
cumulative += prob
|
||||
if r < cumulative:
|
||||
return rarity
|
||||
|
||||
# 默认返回R,理论上不会执行到这里
|
||||
return "R"
|
||||
|
||||
def get_user_stats(self, user_id: str) -> Dict:
|
||||
"""获取用户抽卡统计"""
|
||||
user_stats = self.data_manager.get_user_stats()
|
||||
|
||||
if user_id not in user_stats:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "您还没有抽卡记录哦!"
|
||||
}
|
||||
|
||||
stats = user_stats[user_id]
|
||||
return {
|
||||
"success": True,
|
||||
"total_draws": stats["total_draws"],
|
||||
"R_count": stats["R_count"],
|
||||
"SR_count": stats["SR_count"],
|
||||
"SSR_count": stats["SSR_count"],
|
||||
"SP_count": stats["SP_count"],
|
||||
"recent_draws": stats["draw_history"][-5:] if stats["draw_history"] else []
|
||||
}
|
||||
|
||||
def get_probability_text(self) -> str:
|
||||
"""获取概率展示文本"""
|
||||
probs = config.RARITY_PROBABILITY
|
||||
return f"--- 系统概率 ---\nR: {probs['R']}% | SR: {probs['SR']}% | SSR: {probs['SSR']}% | SP: {probs['SP']}%"
|
||||
|
||||
def get_rank_list(self) -> List[Tuple[str, Dict[str, int]]]:
|
||||
"""获取抽卡排行榜数据"""
|
||||
user_stats = self.data_manager.get_user_stats()
|
||||
|
||||
# 过滤有SSR/SP记录的用户
|
||||
ranked_users = [
|
||||
(user_id, stats)
|
||||
for user_id, stats in user_stats.items()
|
||||
if stats.get("SSR_count", 0) > 0 or stats.get("SP_count", 0) > 0
|
||||
]
|
||||
|
||||
# 按SSR+SP总数降序排序
|
||||
ranked_users.sort(
|
||||
key=lambda x: (x[1].get("SSR_count", 0) + x[1].get("SP_count", 0)),
|
||||
reverse=True
|
||||
)
|
||||
|
||||
return ranked_users
|
||||
|
||||
def get_daily_stats(self) -> Dict:
|
||||
"""获取今日抽卡统计"""
|
||||
daily_draws = self.data_manager.get_daily_draws()
|
||||
today = self.data_manager.get_today_date()
|
||||
|
||||
if not daily_draws or today not in daily_draws:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "今日还没有人抽卡哦!"
|
||||
}
|
||||
|
||||
today_stats = daily_draws[today]
|
||||
total_stats = {
|
||||
"total_users": len(today_stats),
|
||||
"total_draws": 0,
|
||||
"R_count": 0,
|
||||
"SR_count": 0,
|
||||
"SSR_count": 0,
|
||||
"SP_count": 0,
|
||||
"user_stats": []
|
||||
}
|
||||
|
||||
# 统计每个用户的抽卡情况
|
||||
for user_id, draws in today_stats.items():
|
||||
user_stats = {
|
||||
"user_id": user_id,
|
||||
"total_draws": len(draws),
|
||||
"R_count": sum(1 for d in draws if d["rarity"] == "R"),
|
||||
"SR_count": sum(1 for d in draws if d["rarity"] == "SR"),
|
||||
"SSR_count": sum(1 for d in draws if d["rarity"] == "SSR"),
|
||||
"SP_count": sum(1 for d in draws if d["rarity"] == "SP")
|
||||
}
|
||||
|
||||
# 更新总统计
|
||||
total_stats["total_draws"] += user_stats["total_draws"]
|
||||
total_stats["R_count"] += user_stats["R_count"]
|
||||
total_stats["SR_count"] += user_stats["SR_count"]
|
||||
total_stats["SSR_count"] += user_stats["SSR_count"]
|
||||
total_stats["SP_count"] += user_stats["SP_count"]
|
||||
|
||||
# 只记录抽到SSR或SP的用户
|
||||
if user_stats["SSR_count"] > 0 or user_stats["SP_count"] > 0:
|
||||
total_stats["user_stats"].append(user_stats)
|
||||
|
||||
# 按SSR+SP数量排序用户统计
|
||||
total_stats["user_stats"].sort(
|
||||
key=lambda x: (x["SSR_count"] + x["SP_count"]),
|
||||
reverse=True
|
||||
)
|
||||
|
||||
# 构建稀有度统计
|
||||
rarity_stats = {
|
||||
"R": total_stats["R_count"],
|
||||
"SR": total_stats["SR_count"],
|
||||
"SSR": total_stats["SSR_count"],
|
||||
"SP": total_stats["SP_count"]
|
||||
}
|
||||
|
||||
# 构建排行榜数据
|
||||
top_users = []
|
||||
for user_stat in total_stats["user_stats"]:
|
||||
top_users.append({
|
||||
"user_id": user_stat["user_id"],
|
||||
"ssr_count": user_stat["SSR_count"] + user_stat["SP_count"]
|
||||
})
|
||||
|
||||
final_stats = {
|
||||
"total_users": total_stats["total_users"],
|
||||
"total_draws": total_stats["total_draws"],
|
||||
"rarity_stats": rarity_stats,
|
||||
"top_users": top_users
|
||||
}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"date": today,
|
||||
"stats": final_stats
|
||||
}
|
||||
|
||||
def triple_draw(self, user_id: str) -> Dict:
|
||||
"""执行三连抽"""
|
||||
# 检查是否有足够的抽卡次数
|
||||
draws_left = self.data_manager.get_draws_left(user_id)
|
||||
if draws_left < 3:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"抽卡次数不足,您今日还剩{draws_left}次抽卡机会,三连抽需要3次机会"
|
||||
}
|
||||
|
||||
results = []
|
||||
all_unlocked_achievements = []
|
||||
|
||||
# 执行三次抽卡
|
||||
for i in range(3):
|
||||
# 抽取稀有度(传递用户ID)
|
||||
rarity = self._draw_rarity(user_id)
|
||||
|
||||
# 从该稀有度中抽取式神
|
||||
shikigami_data = self.data_manager.shikigami_data.get(rarity, [])
|
||||
if not shikigami_data:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"系统错误:{rarity}稀有度下没有可用式神"
|
||||
}
|
||||
|
||||
# 随机选择式神
|
||||
shikigami = random.choice(shikigami_data)
|
||||
|
||||
# 记录抽卡
|
||||
unlocked_achievements = self.data_manager.record_draw(user_id, rarity, shikigami["name"])
|
||||
all_unlocked_achievements.extend(unlocked_achievements)
|
||||
|
||||
results.append({
|
||||
"rarity": rarity,
|
||||
"name": shikigami["name"],
|
||||
"image_url": shikigami["image_url"]
|
||||
})
|
||||
|
||||
# 剩余次数
|
||||
draws_left = self.data_manager.get_draws_left(user_id)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"results": results,
|
||||
"draws_left": draws_left,
|
||||
"unlocked_achievements": list(set(all_unlocked_achievements)) # 去重
|
||||
}
|
||||
|
||||
def get_user_achievements(self, user_id: str) -> Dict:
|
||||
"""获取用户成就信息"""
|
||||
achievement_data = self.data_manager.get_user_achievements(user_id)
|
||||
|
||||
if not achievement_data["unlocked"] and all(v == 0 for v in achievement_data["progress"].values()):
|
||||
return {
|
||||
"success": False,
|
||||
"message": "您还没有任何成就进度哦!快去抽卡吧!"
|
||||
}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"achievements": achievement_data["unlocked"],
|
||||
"progress": achievement_data["progress"]
|
||||
}
|
||||
|
||||
def get_daily_detailed_records(self, date: Optional[str] = None) -> Dict:
|
||||
"""获取每日详细抽卡记录"""
|
||||
if not date:
|
||||
date = self.data_manager.get_today_date()
|
||||
|
||||
daily_draws = self.data_manager.get_daily_draws()
|
||||
|
||||
if not daily_draws or date not in daily_draws:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"{date} 没有抽卡记录"
|
||||
}
|
||||
|
||||
records = []
|
||||
for user_id, draws in daily_draws[date].items():
|
||||
for draw in draws:
|
||||
# 检查这次抽卡是否解锁了成就
|
||||
unlocked_achievements = []
|
||||
draw_time = draw.get("timestamp", "未知时间")
|
||||
|
||||
# 获取用户成就信息
|
||||
achievement_data = self.data_manager.get_user_achievements(user_id)
|
||||
if achievement_data["unlocked"]:
|
||||
# 检查是否有在抽卡时间之后解锁的成就
|
||||
for achievement_id, achievement_info in achievement_data["unlocked"].items():
|
||||
if achievement_info["unlocked_date"] == f"{date} {draw_time}":
|
||||
unlocked_achievements.append(achievement_id)
|
||||
|
||||
records.append({
|
||||
"user_id": user_id,
|
||||
"draw_time": draw_time,
|
||||
"shikigami_name": draw["name"],
|
||||
"rarity": draw["rarity"],
|
||||
"unlocked_achievements": unlocked_achievements
|
||||
})
|
||||
|
||||
# 按时间排序
|
||||
records.sort(key=lambda x: x["draw_time"])
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"date": date,
|
||||
"records": records,
|
||||
"total_count": len(records)
|
||||
"""
|
||||
阴阳师抽卡插件 - 抽卡核心逻辑模块
|
||||
|
||||
实现抽卡核心算法,包括:
|
||||
- 多稀有度抽卡(R/SR/SSR/SP)
|
||||
- 子池支持
|
||||
- 保底机制
|
||||
- 成就检查
|
||||
"""
|
||||
|
||||
import random
|
||||
from typing import Dict, Tuple, List, Optional, Any
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from .config import Config
|
||||
from .data_manager import DataManager
|
||||
|
||||
config = Config()
|
||||
data_manager = DataManager()
|
||||
|
||||
class GachaSystem:
|
||||
"""抽卡系统核心类,管理抽卡逻辑和数据"""
|
||||
def __init__(self):
|
||||
self.data_manager = data_manager
|
||||
|
||||
def draw(self, user_id: str) -> Dict[str, Any]:
|
||||
"""执行一次抽卡"""
|
||||
# 检查抽卡限制
|
||||
if not self.data_manager.check_daily_limit(user_id):
|
||||
draws_left = self.data_manager.get_draws_left(user_id)
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"您今日的抽卡次数已用完,每日限制{config.DAILY_LIMIT}次,明天再来吧!"
|
||||
}
|
||||
|
||||
# 抽取稀有度(传递用户ID)
|
||||
rarity = self._draw_rarity(user_id)
|
||||
|
||||
# 从该稀有度中抽取式神
|
||||
shikigami_data = self.data_manager.shikigami_data.get(rarity, [])
|
||||
if not shikigami_data:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"系统错误:{rarity}稀有度下没有可用式神"
|
||||
}
|
||||
|
||||
# 随机选择式神
|
||||
shikigami = random.choice(shikigami_data)
|
||||
|
||||
# 记录抽卡
|
||||
unlocked_achievements = self.data_manager.record_draw(user_id, rarity, shikigami["name"])
|
||||
|
||||
# 剩余次数
|
||||
draws_left = self.data_manager.get_draws_left(user_id)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"rarity": rarity,
|
||||
"name": shikigami["name"],
|
||||
"image_url": shikigami["image_url"],
|
||||
"draws_left": draws_left,
|
||||
"unlocked_achievements": unlocked_achievements
|
||||
}
|
||||
|
||||
def _draw_rarity(self, user_id: str = None) -> str:
|
||||
"""按概率抽取稀有度"""
|
||||
# 检查是否是特殊概率用户
|
||||
if user_id and user_id in config.SPECIAL_PROBABILITY_USERS:
|
||||
# 100%概率抽到SSR或SP,随机选择
|
||||
return random.choice(["SSR", "SP"])
|
||||
|
||||
# 普通用户的概率逻辑
|
||||
r = random.random() * 100 # 0-100的随机数
|
||||
|
||||
cumulative = 0
|
||||
for rarity, prob in config.RARITY_PROBABILITY.items():
|
||||
cumulative += prob
|
||||
if r < cumulative:
|
||||
return rarity
|
||||
|
||||
# 默认返回R,理论上不会执行到这里
|
||||
return "R"
|
||||
|
||||
def get_user_stats(self, user_id: str) -> Dict:
|
||||
"""获取用户抽卡统计"""
|
||||
user_stats = self.data_manager.get_user_stats()
|
||||
|
||||
if user_id not in user_stats:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "您还没有抽卡记录哦!"
|
||||
}
|
||||
|
||||
stats = user_stats[user_id]
|
||||
return {
|
||||
"success": True,
|
||||
"total_draws": stats["total_draws"],
|
||||
"R_count": stats["R_count"],
|
||||
"SR_count": stats["SR_count"],
|
||||
"SSR_count": stats["SSR_count"],
|
||||
"SP_count": stats["SP_count"],
|
||||
"recent_draws": stats["draw_history"][-5:] if stats["draw_history"] else []
|
||||
}
|
||||
|
||||
def get_probability_text(self) -> str:
|
||||
"""获取概率展示文本"""
|
||||
probs = config.RARITY_PROBABILITY
|
||||
return f"--- 系统概率 ---\nR: {probs['R']}% | SR: {probs['SR']}% | SSR: {probs['SSR']}% | SP: {probs['SP']}%"
|
||||
|
||||
def get_rank_list(self) -> List[Tuple[str, Dict[str, int]]]:
|
||||
"""获取抽卡排行榜数据"""
|
||||
user_stats = self.data_manager.get_user_stats()
|
||||
|
||||
# 过滤有SSR/SP记录的用户
|
||||
ranked_users = [
|
||||
(user_id, stats)
|
||||
for user_id, stats in user_stats.items()
|
||||
if stats.get("SSR_count", 0) > 0 or stats.get("SP_count", 0) > 0
|
||||
]
|
||||
|
||||
# 按SSR+SP总数降序排序
|
||||
ranked_users.sort(
|
||||
key=lambda x: (x[1].get("SSR_count", 0) + x[1].get("SP_count", 0)),
|
||||
reverse=True
|
||||
)
|
||||
|
||||
return ranked_users
|
||||
|
||||
def get_daily_stats(self) -> Dict:
|
||||
"""获取今日抽卡统计"""
|
||||
daily_draws = self.data_manager.get_daily_draws()
|
||||
today = self.data_manager.get_today_date()
|
||||
|
||||
if not daily_draws or today not in daily_draws:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "今日还没有人抽卡哦!"
|
||||
}
|
||||
|
||||
today_stats = daily_draws[today]
|
||||
total_stats = {
|
||||
"total_users": len(today_stats),
|
||||
"total_draws": 0,
|
||||
"R_count": 0,
|
||||
"SR_count": 0,
|
||||
"SSR_count": 0,
|
||||
"SP_count": 0,
|
||||
"user_stats": []
|
||||
}
|
||||
|
||||
# 统计每个用户的抽卡情况
|
||||
for user_id, draws in today_stats.items():
|
||||
user_stats = {
|
||||
"user_id": user_id,
|
||||
"total_draws": len(draws),
|
||||
"R_count": sum(1 for d in draws if d["rarity"] == "R"),
|
||||
"SR_count": sum(1 for d in draws if d["rarity"] == "SR"),
|
||||
"SSR_count": sum(1 for d in draws if d["rarity"] == "SSR"),
|
||||
"SP_count": sum(1 for d in draws if d["rarity"] == "SP")
|
||||
}
|
||||
|
||||
# 更新总统计
|
||||
total_stats["total_draws"] += user_stats["total_draws"]
|
||||
total_stats["R_count"] += user_stats["R_count"]
|
||||
total_stats["SR_count"] += user_stats["SR_count"]
|
||||
total_stats["SSR_count"] += user_stats["SSR_count"]
|
||||
total_stats["SP_count"] += user_stats["SP_count"]
|
||||
|
||||
# 只记录抽到SSR或SP的用户
|
||||
if user_stats["SSR_count"] > 0 or user_stats["SP_count"] > 0:
|
||||
total_stats["user_stats"].append(user_stats)
|
||||
|
||||
# 按SSR+SP数量排序用户统计
|
||||
total_stats["user_stats"].sort(
|
||||
key=lambda x: (x["SSR_count"] + x["SP_count"]),
|
||||
reverse=True
|
||||
)
|
||||
|
||||
# 构建稀有度统计
|
||||
rarity_stats = {
|
||||
"R": total_stats["R_count"],
|
||||
"SR": total_stats["SR_count"],
|
||||
"SSR": total_stats["SSR_count"],
|
||||
"SP": total_stats["SP_count"]
|
||||
}
|
||||
|
||||
# 构建排行榜数据
|
||||
top_users = []
|
||||
for user_stat in total_stats["user_stats"]:
|
||||
top_users.append({
|
||||
"user_id": user_stat["user_id"],
|
||||
"ssr_count": user_stat["SSR_count"] + user_stat["SP_count"]
|
||||
})
|
||||
|
||||
final_stats = {
|
||||
"total_users": total_stats["total_users"],
|
||||
"total_draws": total_stats["total_draws"],
|
||||
"rarity_stats": rarity_stats,
|
||||
"top_users": top_users
|
||||
}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"date": today,
|
||||
"stats": final_stats
|
||||
}
|
||||
|
||||
def triple_draw(self, user_id: str) -> Dict:
|
||||
"""执行三连抽"""
|
||||
# 检查是否有足够的抽卡次数
|
||||
draws_left = self.data_manager.get_draws_left(user_id)
|
||||
if draws_left < 3:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"抽卡次数不足,您今日还剩{draws_left}次抽卡机会,三连抽需要3次机会"
|
||||
}
|
||||
|
||||
results = []
|
||||
all_unlocked_achievements = []
|
||||
|
||||
# 执行三次抽卡
|
||||
for i in range(3):
|
||||
# 抽取稀有度(传递用户ID)
|
||||
rarity = self._draw_rarity(user_id)
|
||||
|
||||
# 从该稀有度中抽取式神
|
||||
shikigami_data = self.data_manager.shikigami_data.get(rarity, [])
|
||||
if not shikigami_data:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"系统错误:{rarity}稀有度下没有可用式神"
|
||||
}
|
||||
|
||||
# 随机选择式神
|
||||
shikigami = random.choice(shikigami_data)
|
||||
|
||||
# 记录抽卡
|
||||
unlocked_achievements = self.data_manager.record_draw(user_id, rarity, shikigami["name"])
|
||||
all_unlocked_achievements.extend(unlocked_achievements)
|
||||
|
||||
results.append({
|
||||
"rarity": rarity,
|
||||
"name": shikigami["name"],
|
||||
"image_url": shikigami["image_url"]
|
||||
})
|
||||
|
||||
# 剩余次数
|
||||
draws_left = self.data_manager.get_draws_left(user_id)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"results": results,
|
||||
"draws_left": draws_left,
|
||||
"unlocked_achievements": list(set(all_unlocked_achievements)) # 去重
|
||||
}
|
||||
|
||||
def get_user_achievements(self, user_id: str) -> Dict:
|
||||
"""获取用户成就信息"""
|
||||
achievement_data = self.data_manager.get_user_achievements(user_id)
|
||||
|
||||
if not achievement_data["unlocked"] and all(v == 0 for v in achievement_data["progress"].values()):
|
||||
return {
|
||||
"success": False,
|
||||
"message": "您还没有任何成就进度哦!快去抽卡吧!"
|
||||
}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"achievements": achievement_data["unlocked"],
|
||||
"progress": achievement_data["progress"]
|
||||
}
|
||||
|
||||
def get_daily_detailed_records(self, date: Optional[str] = None) -> Dict:
|
||||
"""获取每日详细抽卡记录"""
|
||||
if not date:
|
||||
date = self.data_manager.get_today_date()
|
||||
|
||||
daily_draws = self.data_manager.get_daily_draws()
|
||||
|
||||
if not daily_draws or date not in daily_draws:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"{date} 没有抽卡记录"
|
||||
}
|
||||
|
||||
records = []
|
||||
for user_id, draws in daily_draws[date].items():
|
||||
for draw in draws:
|
||||
# 检查这次抽卡是否解锁了成就
|
||||
unlocked_achievements = []
|
||||
draw_time = draw.get("timestamp", "未知时间")
|
||||
|
||||
# 获取用户成就信息
|
||||
achievement_data = self.data_manager.get_user_achievements(user_id)
|
||||
if achievement_data["unlocked"]:
|
||||
# 检查是否有在抽卡时间之后解锁的成就
|
||||
for achievement_id, achievement_info in achievement_data["unlocked"].items():
|
||||
if achievement_info["unlocked_date"] == f"{date} {draw_time}":
|
||||
unlocked_achievements.append(achievement_id)
|
||||
|
||||
records.append({
|
||||
"user_id": user_id,
|
||||
"draw_time": draw_time,
|
||||
"shikigami_name": draw["name"],
|
||||
"rarity": draw["rarity"],
|
||||
"unlocked_achievements": unlocked_achievements
|
||||
})
|
||||
|
||||
# 按时间排序
|
||||
records.sort(key=lambda x: x["draw_time"])
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"date": date,
|
||||
"records": records,
|
||||
"total_count": len(records)
|
||||
}
|
||||
25
danding_bot/plugins/onmyoji_gacha/handlers/__init__.py
Normal file
25
danding_bot/plugins/onmyoji_gacha/handlers/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
handlers包 - 抽卡命令处理函数
|
||||
|
||||
将各handler函数集中在此包中,便于__init__.py统一导入和注册matcher。
|
||||
"""
|
||||
|
||||
from .gacha import handle_gacha
|
||||
from .triple_gacha import handle_triple_gacha
|
||||
from .stats import handle_stats
|
||||
from .query import handle_query
|
||||
from .rank import handle_rank
|
||||
from .daily_stats import handle_daily_stats
|
||||
from .achievement import handle_achievement
|
||||
from .intro import handle_intro
|
||||
|
||||
__all__ = [
|
||||
"handle_gacha",
|
||||
"handle_triple_gacha",
|
||||
"handle_stats",
|
||||
"handle_query",
|
||||
"handle_rank",
|
||||
"handle_daily_stats",
|
||||
"handle_achievement",
|
||||
"handle_intro",
|
||||
]
|
||||
38
danding_bot/plugins/onmyoji_gacha/handlers/achievement.py
Normal file
38
danding_bot/plugins/onmyoji_gacha/handlers/achievement.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
成就系统查询处理模块
|
||||
|
||||
处理成就系统查询命令,显示已解锁成就和进度。
|
||||
"""
|
||||
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent
|
||||
import nonebot
|
||||
|
||||
from ..utils import get_gacha_system
|
||||
from ..utils import format_user_mention
|
||||
|
||||
logger = nonebot.logger
|
||||
|
||||
|
||||
async def handle_achievement(bot: Bot, event: MessageEvent, state: dict):
|
||||
"""处理成就系统查询命令"""
|
||||
user_id = str(event.user_id)
|
||||
user_name = event.sender.card or event.sender.nickname or "未知用户"
|
||||
|
||||
gacha_system = get_gacha_system()
|
||||
achievements = await gacha_system.get_user_achievements(user_id)
|
||||
|
||||
if not achievements:
|
||||
await event.finish("您还没有解锁任何成就哦~ 继续抽卡吧!")
|
||||
|
||||
msg = f"🏅 {format_user_mention(user_id, user_name)} 的成就:\n\n"
|
||||
|
||||
for ach in achievements:
|
||||
name = ach.get("name", "未知成就")
|
||||
desc = ach.get("description", "")
|
||||
reward = ach.get("reward", 0)
|
||||
claimed = ach.get("claimed", False)
|
||||
|
||||
status = "✅已领取" if claimed else "🎁可领取"
|
||||
msg += f"🎖️ {name}\n {desc}\n 奖励:{reward} {status}\n\n"
|
||||
|
||||
await event.send(msg.strip())
|
||||
25
danding_bot/plugins/onmyoji_gacha/handlers/daily_stats.py
Normal file
25
danding_bot/plugins/onmyoji_gacha/handlers/daily_stats.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
今日抽卡统计处理模块
|
||||
|
||||
处理今日抽卡统计查询命令。
|
||||
"""
|
||||
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent
|
||||
import nonebot
|
||||
|
||||
from ..utils import get_gacha_system
|
||||
from .. import formatters
|
||||
|
||||
logger = nonebot.logger
|
||||
|
||||
|
||||
async def handle_daily_stats(bot: Bot, event: MessageEvent, state: dict):
|
||||
"""处理今日抽卡统计命令"""
|
||||
gacha_system = get_gacha_system()
|
||||
daily_stats = await gacha_system.get_daily_stats()
|
||||
|
||||
if not daily_stats or daily_stats.get("today_total", 0) == 0:
|
||||
await event.finish("今日暂无抽卡记录")
|
||||
|
||||
msg = formatters.format_daily_stats(daily_stats)
|
||||
await event.send(msg)
|
||||
62
danding_bot/plugins/onmyoji_gacha/handlers/gacha.py
Normal file
62
danding_bot/plugins/onmyoji_gacha/handlers/gacha.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
抽卡命令处理模块
|
||||
|
||||
处理单次抽卡命令,包括:
|
||||
- 参数解析(子池选择)
|
||||
- 抽卡执行
|
||||
- SSR/SP奖励处理
|
||||
- 成就检查
|
||||
- 消息发送
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.adapters.onebot.v11 import Message
|
||||
import nonebot
|
||||
import random
|
||||
|
||||
from ..config import Config
|
||||
from ..utils import get_gacha_system
|
||||
from .. import formatters
|
||||
from ..api_utils import process_ssr_sp_reward
|
||||
from ..utils import format_user_mention, build_achievement_notify, get_user_name
|
||||
|
||||
logger = nonebot.logger
|
||||
|
||||
|
||||
async def handle_gacha(bot: Bot, event: MessageEvent, state: dict, args: Message = CommandArg()):
|
||||
"""处理抽卡命令"""
|
||||
user_id = str(event.user_id)
|
||||
user_name = get_user_name(event)
|
||||
|
||||
# 解析子池参数
|
||||
sub_pool = args.extract_plain_text().strip()
|
||||
|
||||
# 执行抽卡
|
||||
gacha_system = get_gacha_system()
|
||||
result = await gacha_system.draw(user_id, sub_pool=sub_pool if sub_pool else None)
|
||||
|
||||
if not result["success"]:
|
||||
await event.finish(result["message"])
|
||||
|
||||
rarity, shikigami, image_url = result["rarity"], result["name"], result["image"]
|
||||
|
||||
# 发送抽卡结果
|
||||
msg = formatters.format_gacha_result(rarity, shikigami, user_id, user_name, image_url)
|
||||
await event.send(msg)
|
||||
|
||||
# SSR/SP奖励处理
|
||||
if rarity in ["SSR", "SP"]:
|
||||
group_id = str(event.group_id) if isinstance(event, GroupMessageEvent) else None
|
||||
reward_msg = await process_ssr_sp_reward(user_id, user_name, rarity, shikigami, group_id)
|
||||
if reward_msg:
|
||||
await event.send(reward_msg)
|
||||
|
||||
# 成就检查(使用统一编排函数)
|
||||
unlocked = await gacha_system.check_achievements(user_id)
|
||||
if unlocked:
|
||||
achievement_msg = await build_achievement_notify(user_id, unlocked)
|
||||
if achievement_msg:
|
||||
await event.send(achievement_msg)
|
||||
|
||||
39
danding_bot/plugins/onmyoji_gacha/handlers/intro.py
Normal file
39
danding_bot/plugins/onmyoji_gacha/handlers/intro.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""
|
||||
帮助介绍处理模块
|
||||
|
||||
显示抽卡系统的帮助信息和功能说明。
|
||||
"""
|
||||
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent
|
||||
import nonebot
|
||||
|
||||
logger = nonebot.logger
|
||||
|
||||
|
||||
async def handle_intro(bot: Bot, event: MessageEvent, state: dict):
|
||||
"""处理帮助介绍命令"""
|
||||
intro_text = """
|
||||
🎴 阴阳师抽卡系统 使用说明
|
||||
|
||||
📌 基础命令:
|
||||
• 抽卡 [子池名] - 进行一次抽卡
|
||||
• 三连抽 - 连续抽三次
|
||||
• 我的抽卡 - 查看个人抽卡统计
|
||||
• 抽卡详情 - 查看详细统计和最近记录
|
||||
• 抽卡排行 [页码] - 查看排行榜
|
||||
• 今日抽卡 - 查看今日抽卡统计
|
||||
• 成就 - 查看成就系统
|
||||
• 介绍 - 显示本帮助信息
|
||||
|
||||
📊 功能特色:
|
||||
• 多稀有度式神:R/SR/SSR/SP
|
||||
• 成就系统:连续抽卡、非酋成就等
|
||||
• SSR/SP奖励:自动发放积分奖励
|
||||
• 每日签到:首次抽卡自动签到
|
||||
|
||||
💡 提示:
|
||||
• 每日抽卡次数有限制
|
||||
• SSR/SP抽中会通知管理员
|
||||
• 成就奖励自动发放或联系管理员领取
|
||||
"""
|
||||
await event.reply(intro_text.strip())
|
||||
42
danding_bot/plugins/onmyoji_gacha/handlers/query.py
Normal file
42
danding_bot/plugins/onmyoji_gacha/handlers/query.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
抽卡详情查询处理模块
|
||||
|
||||
处理用户抽卡详情查询,包括最近抽卡记录和成就进度。
|
||||
"""
|
||||
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent
|
||||
import nonebot
|
||||
|
||||
from ..utils import get_gacha_system
|
||||
from .. import formatters
|
||||
|
||||
logger = nonebot.logger
|
||||
|
||||
|
||||
async def handle_query(bot: Bot, event: MessageEvent, state: dict):
|
||||
"""处理抽卡详情查询命令"""
|
||||
user_id = str(event.user_id)
|
||||
user_name = event.sender.card or event.sender.nickname or "未知用户"
|
||||
|
||||
gacha_system = get_gacha_system()
|
||||
stats = await gacha_system.get_user_stats(user_id)
|
||||
|
||||
if not stats or stats.get("total_draws", 0) == 0:
|
||||
await event.finish("您还没有抽卡记录哦~")
|
||||
|
||||
# 获取最近抽卡记录
|
||||
recent = await gacha_system.get_recent_draws(user_id, limit=5)
|
||||
|
||||
# 发送统计详情
|
||||
msg = formatters.format_user_detail_stats(stats, user_id, user_name, recent)
|
||||
await event.send(msg)
|
||||
|
||||
# 发送成就进度
|
||||
progress = await gacha_system.get_achievement_progress(user_id)
|
||||
if progress:
|
||||
achievement_msg = formatters.format_achievement_progress(
|
||||
progress.get("consecutive_days", 0),
|
||||
progress.get("no_ssr_streak", 0),
|
||||
user_id
|
||||
)
|
||||
await event.send(achievement_msg)
|
||||
33
danding_bot/plugins/onmyoji_gacha/handlers/rank.py
Normal file
33
danding_bot/plugins/onmyoji_gacha/handlers/rank.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
抽卡排行榜处理模块
|
||||
|
||||
处理抽卡排行榜查询命令,支持分页显示。
|
||||
"""
|
||||
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.adapters.onebot.v11 import Message
|
||||
import nonebot
|
||||
|
||||
from ..utils import get_gacha_system
|
||||
from .. import formatters
|
||||
|
||||
logger = nonebot.logger
|
||||
|
||||
|
||||
async def handle_rank(bot: Bot, event: MessageEvent, state: dict, args: Message = CommandArg()):
|
||||
"""处理抽卡排行榜命令"""
|
||||
# 解析页码
|
||||
page_text = args.extract_plain_text().strip()
|
||||
page = 1
|
||||
if page_text.isdigit():
|
||||
page = int(page_text)
|
||||
|
||||
gacha_system = get_gacha_system()
|
||||
rank_data, total_pages = await gacha_system.get_rank_list(page=page)
|
||||
|
||||
if not rank_data:
|
||||
await event.finish("暂无排行数据")
|
||||
|
||||
msg = formatters.format_rank_list(rank_data, page, total_pages)
|
||||
await event.send(msg)
|
||||
28
danding_bot/plugins/onmyoji_gacha/handlers/stats.py
Normal file
28
danding_bot/plugins/onmyoji_gacha/handlers/stats.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
我的抽卡统计处理模块
|
||||
|
||||
处理用户的个人抽卡统计查询命令。
|
||||
"""
|
||||
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent
|
||||
import nonebot
|
||||
|
||||
from ..utils import get_gacha_system
|
||||
from .. import formatters
|
||||
|
||||
logger = nonebot.logger
|
||||
|
||||
|
||||
async def handle_stats(bot: Bot, event: MessageEvent, state: dict):
|
||||
"""处理我的抽卡统计命令"""
|
||||
user_id = str(event.user_id)
|
||||
user_name = event.sender.card or event.sender.nickname or "未知用户"
|
||||
|
||||
gacha_system = get_gacha_system()
|
||||
stats = await gacha_system.get_user_stats(user_id)
|
||||
|
||||
if not stats or stats.get("total_draws", 0) == 0:
|
||||
await event.finish("您还没有抽卡记录哦~")
|
||||
|
||||
msg = formatters.format_user_stats(stats, user_id, user_name)
|
||||
await event.send(msg)
|
||||
45
danding_bot/plugins/onmyoji_gacha/handlers/triple_gacha.py
Normal file
45
danding_bot/plugins/onmyoji_gacha/handlers/triple_gacha.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
三连抽命令处理模块
|
||||
|
||||
处理三连抽命令,包括:
|
||||
- 三次抽卡执行
|
||||
- 结果汇总
|
||||
- 成就检查
|
||||
"""
|
||||
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent
|
||||
import nonebot
|
||||
|
||||
from ..utils import get_gacha_system
|
||||
from .. import formatters
|
||||
from ..utils import build_achievement_notify
|
||||
|
||||
logger = nonebot.logger
|
||||
|
||||
|
||||
async def handle_triple_gacha(bot: Bot, event: MessageEvent, state: dict):
|
||||
"""处理三连抽命令"""
|
||||
user_id = str(event.user_id)
|
||||
user_name = event.sender.card or event.sender.nickname or "未知用户"
|
||||
|
||||
gacha_system = get_gacha_system()
|
||||
results = []
|
||||
|
||||
# 执行三次抽卡
|
||||
for _ in range(3):
|
||||
result = await gacha_system.draw(user_id)
|
||||
if result["success"]:
|
||||
results.append((result["rarity"], result["name"], result["image"]))
|
||||
else:
|
||||
await event.finish(result["message"])
|
||||
|
||||
# 发送三连抽结果
|
||||
msg = formatters.format_triple_gacha_result(results, user_id, user_name)
|
||||
await event.send(msg)
|
||||
|
||||
# 成就检查(使用统一编排函数,避免接口不匹配)
|
||||
unlocked = await gacha_system.check_achievements(user_id)
|
||||
if unlocked:
|
||||
achievement_msg = await build_achievement_notify(user_id, unlocked)
|
||||
if achievement_msg:
|
||||
await event.send(achievement_msg)
|
||||
@@ -0,0 +1,57 @@
|
||||
# 变更提案: onmyoji_gacha 代码评审修复
|
||||
|
||||
- 日期: 2026-05-03
|
||||
- 状态: ✅ 已实施
|
||||
- 作者: Agent (代码评审驱动)
|
||||
|
||||
## 背景
|
||||
|
||||
对 onmyoji_gacha 插件进行系统代码评审,发现 14 个问题(1P0 + 6P1 + 9P2),
|
||||
涉及安全漏洞、依赖方向、职责边界、一致性等维度。
|
||||
|
||||
## 变更清单
|
||||
|
||||
### P0 - 紧急修复
|
||||
|
||||
| # | 文件 | 问题 | 修复 |
|
||||
|---|------|------|------|
|
||||
| 1 | web_api.py | 5处async函数缺await(数据库查询结果为协程对象,数据全部错乱) | 补全所有await |
|
||||
|
||||
### P1 - 重要修复
|
||||
|
||||
| # | 文件 | 问题 | 修复 |
|
||||
|---|------|------|------|
|
||||
| 2 | web_api.py | verify_admin_token中print泄露token明文到日志 | 删除token print |
|
||||
| 3 | formatters.py → api_utils | format_achievement_notify反向依赖api_utils(同层模块循环依赖) | 解耦:formatters改为纯格式化,reward逻辑移至handler调用方 |
|
||||
| 4 | handlers/gacha.py → __init__.py | 签到逻辑嵌入抽卡handler(跨职责),传None matcher | 移至__init__.py matcher层,传入实际matcher |
|
||||
| 5 | formatters.py | 5处重复硬编码SSR/SP/...字符串字面量 | 提取RARITY_DISPLAY配置字典,消除重复 |
|
||||
| 6 | data_manager.py | 承担数据IO+缓存+业务规则三重职责 | 暂不拆分(影响面大),docstring标记TODO |
|
||||
|
||||
### P2 - 改进
|
||||
|
||||
| # | 文件 | 问题 | 修复 |
|
||||
|---|------|------|------|
|
||||
| 7 | utils.py | user_name获取逻辑散布在多个handler中 | 新增get_user_name统一工具函数 |
|
||||
| 8 | web_api.py | GachaSystem/Config模块级实例化(import时副作用) | 改为lazy延迟初始化 |
|
||||
| 9 | __init__.py | matcher参数传None | 传入实际matcher对象 |
|
||||
|
||||
## 受影响文件
|
||||
|
||||
- `web_api.py` - 重写(P0+P1+P2共8处修复)
|
||||
- `formatters.py` - 解耦api_utils + RARITY_DISPLAY提取
|
||||
- `handlers/gacha.py` - 移除签到逻辑
|
||||
- `__init__.py` - gacha wrapper补充签到编排
|
||||
- `utils.py` - 新增get_user_name
|
||||
- `data_manager.py` - TODO标记
|
||||
|
||||
## 验证
|
||||
|
||||
- [x] 所有修改文件语法检查通过 (ast.parse)
|
||||
- [x] 依赖方向:formatters不再import api_utils
|
||||
- [x] token明文不再出现在日志
|
||||
- [x] 签到逻辑在matcher层正确调用
|
||||
|
||||
## Delta 规约
|
||||
|
||||
本次变更未引入新的外部依赖,未改变数据库结构,未改变用户可见接口。
|
||||
API响应格式不变,命令触发方式不变。
|
||||
46
danding_bot/plugins/onmyoji_gacha/rules.py
Normal file
46
danding_bot/plugins/onmyoji_gacha/rules.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
权限校验与规则解析模块
|
||||
|
||||
提供NoneBot命令的权限检查规则函数,包括:
|
||||
- 群组权限检查(通用)
|
||||
|
||||
所有规则函数返回Rule对象,用于NoneBot的matcher定义。
|
||||
"""
|
||||
|
||||
from typing import Callable
|
||||
from nonebot.rule import Rule
|
||||
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageEvent
|
||||
|
||||
|
||||
def _check_group_allowed(config) -> Callable:
|
||||
"""生成群组权限检查函数(内部复用,消除重复逻辑)。
|
||||
|
||||
Args:
|
||||
config: Config实例,需包含ALLOWED_GROUP_ID属性
|
||||
|
||||
Returns:
|
||||
异步检查函数,私聊放行、群聊检查白名单
|
||||
"""
|
||||
async def _check(bot: Bot, event: MessageEvent) -> bool:
|
||||
if not isinstance(event, GroupMessageEvent):
|
||||
return True
|
||||
# 单群模式:直接比较整数
|
||||
return event.group_id == config.ALLOWED_GROUP_ID
|
||||
return _check
|
||||
|
||||
|
||||
def check_permission() -> Rule:
|
||||
"""检查群组是否有权限使用抽卡功能。"""
|
||||
from .config import Config
|
||||
config = Config()
|
||||
return Rule(_check_group_allowed(config))
|
||||
|
||||
|
||||
def check_rank_permission() -> Rule:
|
||||
"""检查用户是否有权限查看排行榜。
|
||||
|
||||
当前与check_permission逻辑相同,保留为独立入口便于未来扩展。
|
||||
"""
|
||||
from .config import Config
|
||||
config = Config()
|
||||
return Rule(_check_group_allowed(config))
|
||||
@@ -1,42 +1,108 @@
|
||||
import os
|
||||
from typing import Optional
|
||||
from pathlib import Path
|
||||
|
||||
def get_image_path(file_path: str) -> str:
|
||||
"""获取图片的绝对路径"""
|
||||
return os.path.abspath(file_path)
|
||||
|
||||
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}"
|
||||
|
||||
|
||||
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}"
|
||||
)
|
||||
"""
|
||||
阴阳师抽卡插件 - 通用工具函数
|
||||
|
||||
提供常用的辅助函数:
|
||||
- 用户提及格式化
|
||||
- 图片路径处理
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import Optional
|
||||
from pathlib import Path
|
||||
|
||||
def get_image_path(file_path: str) -> str:
|
||||
"""获取图片的绝对路径"""
|
||||
return os.path.abspath(file_path)
|
||||
|
||||
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}"
|
||||
|
||||
|
||||
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}"
|
||||
)
|
||||
|
||||
|
||||
|
||||
def get_user_name(event) -> str:
|
||||
"""从消息事件中获取用户昵称,统一多处重复逻辑。
|
||||
|
||||
Args:
|
||||
event: NoneBot MessageEvent 对象
|
||||
|
||||
Returns:
|
||||
str: 用户昵称,优先使用群名片(card),其次昵称(nickname),兜底"未知用户"
|
||||
"""
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent
|
||||
if isinstance(event, GroupMessageEvent):
|
||||
return event.sender.card or event.sender.nickname or "未知用户"
|
||||
return event.sender.nickname or "未知用户"
|
||||
|
||||
|
||||
async def build_achievement_notify(user_id: str, unlocked_ids: list) -> "Message | None":
|
||||
"""统一的成就通知编排:ID列表 → 详情查询 → 奖励领取 → 消息格式化。
|
||||
|
||||
供所有handler共用,消除成就通知逻辑的重复(原则#4 变化半径小 / #12 功能越多代码越短)。
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
unlocked_ids: 新解锁的成就ID列表
|
||||
|
||||
Returns:
|
||||
Message对象,无有效成就时返回None
|
||||
"""
|
||||
from .api_utils import process_achievement_reward, get_achievement_by_id
|
||||
from . import formatters
|
||||
|
||||
achievements_data = []
|
||||
for achievement_id in unlocked_ids:
|
||||
ach = get_achievement_by_id(achievement_id)
|
||||
if not ach:
|
||||
continue
|
||||
success, reward_msg = await process_achievement_reward(user_id, achievement_id)
|
||||
ach["reward_msg"] = reward_msg if success else ""
|
||||
ach["claimed"] = success
|
||||
achievements_data.append(ach)
|
||||
|
||||
if not achievements_data:
|
||||
return None
|
||||
return formatters.format_achievement_notify(achievements_data, user_id)
|
||||
|
||||
|
||||
# ---- GachaSystem 单例(P1#3: 避免每次handler调用都new实例) ----
|
||||
_gacha_system_instance = None
|
||||
|
||||
def get_gacha_system():
|
||||
"""获取全局唯一的GachaSystem实例(lazy init)。"""
|
||||
global _gacha_system_instance
|
||||
if _gacha_system_instance is None:
|
||||
from .gacha import GachaSystem
|
||||
_gacha_system_instance = GachaSystem()
|
||||
return _gacha_system_instance
|
||||
|
||||
@@ -197,4 +197,4 @@ def register_web_routes():
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 注册 Web 路由时出错: {e}")
|
||||
return False
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user