chore: 清理测试脚本并更新插件文档
- 删除过时的测试脚本,包括测试配置、路由、API、积分、签到等文件 - 更新 PLUGINS.md 文档,重新组织插件结构,提供更清晰的功能说明和权限要求 - 改进文档格式,增加表格和详细说明,便于用户理解各插件功能
This commit is contained in:
283
PLUGINS.md
283
PLUGINS.md
@@ -2,174 +2,145 @@
|
|||||||
|
|
||||||
## 项目概述
|
## 项目概述
|
||||||
|
|
||||||
蛋定助手是一个基于NoneBot2框架开发的QQ机器人,提供多种功能插件,包括AI聊天、管理API、自动撤回消息等。该机器人主要面向特定用户群体,提供游戏辅助和社群管理功能。
|
蛋定助手是一个基于 NoneBot2 框架开发的 QQ 机器人,提供多种功能插件,包括 AI 聊天、游戏辅助、积分系统、社群管理等。
|
||||||
|
|
||||||
## 插件总览
|
## 插件总览
|
||||||
|
|
||||||
| 插件名称 | 描述 | 权限要求 |
|
| 插件名称 | 描述 | 触发方式 | 权限要求 |
|
||||||
|---------|------|---------|
|
|---------|------|---------|---------|
|
||||||
| chatai | AI聊天功能,对接DeepSeek | 所有用户 |
|
| [chatai](#1-chatai---ai-聊天) | AI 聊天(DeepSeek),支持图文回复 | `*` 开头消息 | 所有用户 |
|
||||||
| auto_recall | 消息自动撤回 | 系统自动执行 |
|
| [auto_recall](#2-auto_recall---自动撤回) | 自动撤回机器人发送的消息 | 自动执行 | 系统自动 |
|
||||||
| damo_balance | 大漠账户余额查询 | 特定用户 |
|
| [auto_friend_accept](#3-auto_friend_accept---自动接受好友) | 自动同意好友请求并发送欢迎语 | 自动执行 | 系统自动 |
|
||||||
| danding_api | 蛋定助手管理API | 超级用户 |
|
| [welcome_plugin](#4-welcome_plugin---入群欢迎) | 新成员入群欢迎并发送帮助菜单 | 自动执行 | 特定群 (621016172) |
|
||||||
| danding_help | 帮助信息 | 特定群用户 |
|
| [danding_qqpush](#5-danding_qqpush---消息推送) | 通过 HTTP API 向指定群推送图文通知 | HTTP POST | 接口 Token 验证 |
|
||||||
| command_list | 命令列表管理 | 系统使用 |
|
| [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聊天插件
|
### 1. chatai - AI 聊天
|
||||||
|
|
||||||
#### 功能描述
|
基于 DeepSeek AI 的聊天功能,支持将回复转换为图片形式,并在一定时间后自动撤回。
|
||||||
基于DeepSeek AI的聊天功能,支持将AI回复转换为图片形式,并在一定时间后自动撤回。
|
|
||||||
|
|
||||||
#### 使用方法
|
- **使用方法**: 发送以 `*` 开头的消息。
|
||||||
- 发送以 `*` 开头的消息触发AI回复
|
- **配置项**: `DEEPSEEK_TOKEN` (必填)。
|
||||||
- AI回复会自动转为图片显示
|
- **特性**: AI 回复会自动转为图片显示,默认 120 秒后撤回。
|
||||||
- 回复会在120秒后自动撤回
|
|
||||||
|
|
||||||
#### 配置项
|
### 2. auto_recall - 自动撤回
|
||||||
```env
|
|
||||||
DEEPSEEK_TOKEN=你的DeepSeek API密钥
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 技术实现
|
监控机器人发出的消息,并在指定时间后自动撤回,保持聊天环境整洁。
|
||||||
- 使用OpenAI客户端连接DeepSeek API
|
|
||||||
- 使用Pyppeteer将Markdown转为图片
|
|
||||||
- 内置Chrome浏览器实例管理
|
|
||||||
|
|
||||||
#### 示例
|
- **配置项**:
|
||||||
用户: *你好,请介绍一下自己
|
- `RECALL_DELAY`: 普通消息撤回延迟(默认 110s)。
|
||||||
AI: [图片形式回复] 👋 你好呀!我是蛋定助手,一个活泼可爱的AI助手!😊 很高兴认识你!有什么我能帮到你的吗?✨
|
- `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 - 指令列表
|
||||||
|
|
||||||
|
快速查阅所有可用指令。
|
||||||
|
|
||||||
|
- **命令**: `指令列表`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 2. auto_recall - 自动撤回插件
|
## 常见问题 (FAQ)
|
||||||
|
|
||||||
#### 功能描述
|
- **Q: 为什么某些命令没反应?**
|
||||||
监控所有发出的消息,并在指定时间后自动撤回,保持聊天环境整洁。
|
A: 部分插件(如 `danding_help`)限制了特定群聊使用;管理指令需要配置 `SUPERUSERS`。
|
||||||
|
- **Q: 积分有什么用?**
|
||||||
#### 使用方法
|
A: 目前主要用于赛马下注及展示排名。
|
||||||
- 无需手动调用,插件会自动监控并撤回消息
|
- **Q: 抽卡奖励如何领取?**
|
||||||
|
A: 抽中 SSR/SP 或解锁特定成就后,请截屏联系管理员。
|
||||||
#### 配置项
|
|
||||||
```env
|
|
||||||
RECALL_DELAY=110 # 撤回延迟时间,单位为秒
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 技术实现
|
|
||||||
- 使用NoneBot的API拦截功能
|
|
||||||
- 异步定时任务管理
|
|
||||||
- 错误处理与日志记录
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. damo_balance - 大漠账户余额查询
|
|
||||||
|
|
||||||
#### 功能描述
|
|
||||||
查询大漠平台账户余额,需要验证码验证。
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
- 命令:`大漠余额`或`余额查询`
|
|
||||||
- 需要验证码验证
|
|
||||||
|
|
||||||
#### 权限要求
|
|
||||||
- 仅特定用户(ID:1424473282)可使用
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. danding_api - 蛋定助手管理API
|
|
||||||
|
|
||||||
#### 功能描述
|
|
||||||
提供管理员操作接口,包括在线人数查询、卡密管理和用户时长管理功能。
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
主要命令:
|
|
||||||
- `在线人数`:查询当前在线用户数
|
|
||||||
- `添加卡密 [类型] [卡密]`:添加指定类型的卡密
|
|
||||||
- `生成卡密 [类型]`:生成新卡密
|
|
||||||
- `用户加时 [用户名] [类型]`:为指定用户增加使用时长
|
|
||||||
|
|
||||||
#### 卡密类型
|
|
||||||
- 天卡/day/Day/DAY/天
|
|
||||||
- 周卡/week/Week/WEEK/周
|
|
||||||
- 月卡/month/Month/MONTH/月
|
|
||||||
|
|
||||||
#### 权限要求
|
|
||||||
- 仅超级用户可使用
|
|
||||||
|
|
||||||
#### 配置项
|
|
||||||
```env
|
|
||||||
SUPERUSERS=["1424473282"] # 超级用户ID列表
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 示例
|
|
||||||
```
|
|
||||||
在线人数
|
|
||||||
> 当前在线用户数: 42
|
|
||||||
|
|
||||||
添加卡密 天卡 ABCD1234
|
|
||||||
> 添加卡密成功:天卡 ABCD1234
|
|
||||||
|
|
||||||
生成卡密 周卡
|
|
||||||
> 生成卡密成功:周卡 XYZ789ABC
|
|
||||||
|
|
||||||
用户加时 test_user 月卡
|
|
||||||
> 用户加时成功:test_user 增加了30天
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. danding_help - 帮助信息
|
|
||||||
|
|
||||||
#### 功能描述
|
|
||||||
提供各种帮助信息和指南,支持图片形式的教程和指引。
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
主要命令:
|
|
||||||
- `帮助`:显示帮助菜单
|
|
||||||
- `下载`:显示下载信息
|
|
||||||
- `公益版`/`正式版`:显示版本信息
|
|
||||||
- `正式版御魂双开`:显示双开教程
|
|
||||||
- `正式版如何运行`:显示运行教程
|
|
||||||
- `正式版内测计划`:显示内测信息
|
|
||||||
|
|
||||||
#### 权限要求
|
|
||||||
- 仅在特定群(621016172)可用
|
|
||||||
|
|
||||||
#### 技术实现
|
|
||||||
- 使用图片回复提供直观的教程
|
|
||||||
- 文本与图片混合响应
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. command_list - 命令列表管理
|
|
||||||
|
|
||||||
#### 功能描述
|
|
||||||
管理系统命令列表,提供命令过滤和权限控制。
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
- 系统内部使用,不直接暴露给用户
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
### Q1: 如何启动蛋定助手?
|
|
||||||
A1: 使用`nb run`命令启动,确保已安装所有依赖。
|
|
||||||
|
|
||||||
### Q2: 机器人回复后自动撤回的时间可以修改吗?
|
|
||||||
A2: 可以,在`.env`文件中修改`RECALL_DELAY`的值(单位为秒)。
|
|
||||||
|
|
||||||
### Q3: 如何成为超级用户?
|
|
||||||
A3: 在`.env`文件的`SUPERUSERS`列表中添加您的QQ号。
|
|
||||||
|
|
||||||
### Q4: AI聊天功能如何配置?
|
|
||||||
A4: 需要在`.env`文件中设置`DEEPSEEK_TOKEN`,填入您的DeepSeek API密钥。
|
|
||||||
|
|
||||||
### Q5: 为什么帮助命令在某些群不可用?
|
|
||||||
A5: 帮助命令仅在特定群(621016172)内可用,这是一种权限控制机制。
|
|
||||||
|
|
||||||
## 技术支持
|
|
||||||
|
|
||||||
如有问题,可以:
|
|
||||||
1. 在群内@机器人并提问
|
|
||||||
2. 访问帮助文档:https://www.danding.icu
|
|
||||||
3. 联系超级用户获取支持
|
|
||||||
|
|||||||
73
test_api.py
73
test_api.py
@@ -1,73 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
测试 onmyoji_gacha Web API
|
|
||||||
"""
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
|
|
||||||
# API 配置
|
|
||||||
BASE_URL = "http://localhost:8080"
|
|
||||||
API_BASE = f"{BASE_URL}/onmyoji_gacha/api"
|
|
||||||
ADMIN_TOKEN = "onmyoji_admin_token_2024"
|
|
||||||
|
|
||||||
# 请求头
|
|
||||||
headers = {
|
|
||||||
"Authorization": f"Bearer {ADMIN_TOKEN}",
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_api():
|
|
||||||
print("🧪 测试 onmyoji_gacha Web API")
|
|
||||||
print("=" * 50)
|
|
||||||
|
|
||||||
# 测试每日统计
|
|
||||||
print("\n📊 测试每日统计 API...")
|
|
||||||
try:
|
|
||||||
response = requests.get(f"{API_BASE}/stats/daily", headers=headers)
|
|
||||||
print(f"状态码: {response.status_code}")
|
|
||||||
if response.status_code == 200:
|
|
||||||
data = response.json()
|
|
||||||
print(f"响应: {json.dumps(data, indent=2, ensure_ascii=False)}")
|
|
||||||
else:
|
|
||||||
print(f"错误: {response.text}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"请求失败: {e}")
|
|
||||||
|
|
||||||
# 测试排行榜
|
|
||||||
print("\n🏆 测试排行榜 API...")
|
|
||||||
try:
|
|
||||||
response = requests.get(f"{API_BASE}/stats/rank", headers=headers)
|
|
||||||
print(f"状态码: {response.status_code}")
|
|
||||||
if response.status_code == 200:
|
|
||||||
data = response.json()
|
|
||||||
print(f"响应: {json.dumps(data, indent=2, ensure_ascii=False)}")
|
|
||||||
else:
|
|
||||||
print(f"错误: {response.text}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"请求失败: {e}")
|
|
||||||
|
|
||||||
# 测试用户统计(使用示例用户ID)
|
|
||||||
print("\n👤 测试用户统计 API...")
|
|
||||||
try:
|
|
||||||
test_user_id = "123456789" # 示例用户ID
|
|
||||||
response = requests.get(f"{API_BASE}/stats/user/{test_user_id}", headers=headers)
|
|
||||||
print(f"状态码: {response.status_code}")
|
|
||||||
if response.status_code == 200:
|
|
||||||
data = response.json()
|
|
||||||
print(f"响应: {json.dumps(data, indent=2, ensure_ascii=False)}")
|
|
||||||
else:
|
|
||||||
print(f"错误: {response.text}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"请求失败: {e}")
|
|
||||||
|
|
||||||
# 测试无令牌访问
|
|
||||||
print("\n🔒 测试无令牌访问...")
|
|
||||||
try:
|
|
||||||
response = requests.get(f"{API_BASE}/stats/daily")
|
|
||||||
print(f"状态码: {response.status_code}")
|
|
||||||
print(f"响应: {response.text}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"请求失败: {e}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_api()
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# Print environment variables
|
|
||||||
print("=== Environment Variables ===")
|
|
||||||
for key in os.environ:
|
|
||||||
if "GROUP_HORSE_RACING" in key:
|
|
||||||
print(f"{key}={os.environ[key]}")
|
|
||||||
|
|
||||||
print("\n=== Loading Config ===")
|
|
||||||
from danding_bot.plugins.group_horse_racing.config import Config
|
|
||||||
|
|
||||||
config = Config()
|
|
||||||
print(f"TEST_MODE: {config.TEST_MODE} (type: {type(config.TEST_MODE).__name__})")
|
|
||||||
print(f"TESTERS: {config.TESTERS} (type: {type(config.TESTERS).__name__})")
|
|
||||||
print(f"TEST_GROUPS: {config.TEST_GROUPS} (type: {type(config.TEST_GROUPS).__name__})")
|
|
||||||
print(f"ALLOWED_GROUPS: {config.ALLOWED_GROUPS} (type: {type(config.ALLOWED_GROUPS).__name__})")
|
|
||||||
|
|
||||||
print("\n=== Testing Permission Check ===")
|
|
||||||
test_user_id = 1424473282
|
|
||||||
print(f"Test user_id: {test_user_id}")
|
|
||||||
print(f"Is in TESTERS: {test_user_id in config.TESTERS}")
|
|
||||||
print(f"TEST_MODE enabled: {config.TEST_MODE}")
|
|
||||||
print(f"Should have access: {config.TEST_MODE and test_user_id in config.TESTERS}")
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Manual test script for danding_points plugin."""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Add project root to path
|
|
||||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
||||||
|
|
||||||
from danding_bot.plugins.danding_points import points_api
|
|
||||||
|
|
||||||
|
|
||||||
async def test_basic_operations():
|
|
||||||
"""Test basic points operations."""
|
|
||||||
print("Testing basic operations...")
|
|
||||||
|
|
||||||
# Test 1: Get balance for non-existent user
|
|
||||||
balance = await points_api.get_balance("test_user_1")
|
|
||||||
assert balance == 0, f"Expected 0, got {balance}"
|
|
||||||
print("✓ Non-existent user returns 0 balance")
|
|
||||||
|
|
||||||
# Test 2: Add points (auto-create user)
|
|
||||||
success, new_balance = await points_api.add_points(
|
|
||||||
"test_user_1", 100, "test_source", "test reason"
|
|
||||||
)
|
|
||||||
assert success and new_balance == 100, f"Add failed: {success}, {new_balance}"
|
|
||||||
print("✓ Add points works and auto-creates user")
|
|
||||||
|
|
||||||
# Test 3: Get balance after add
|
|
||||||
balance = await points_api.get_balance("test_user_1")
|
|
||||||
assert balance == 100, f"Expected 100, got {balance}"
|
|
||||||
print("✓ Balance updated correctly")
|
|
||||||
|
|
||||||
# Test 4: Spend points
|
|
||||||
success, new_balance = await points_api.spend_points(
|
|
||||||
"test_user_1", 30, "test_source", "spend reason"
|
|
||||||
)
|
|
||||||
assert success and new_balance == 70, f"Spend failed: {success}, {new_balance}"
|
|
||||||
print("✓ Spend points works")
|
|
||||||
|
|
||||||
# Test 5: Spend more than balance (should fail)
|
|
||||||
success, new_balance = await points_api.spend_points(
|
|
||||||
"test_user_1", 100, "test_source", "should fail"
|
|
||||||
)
|
|
||||||
assert not success, "Should fail when spending more than balance"
|
|
||||||
print("✓ Spend fails when insufficient balance")
|
|
||||||
|
|
||||||
# Test 6: Set points
|
|
||||||
success, new_balance = await points_api.set_points(
|
|
||||||
"test_user_1", 50, "test_source", "set reason"
|
|
||||||
)
|
|
||||||
assert success and new_balance == 50, f"Set failed: {success}, {new_balance}"
|
|
||||||
print("✓ Set points works")
|
|
||||||
|
|
||||||
# Test 7: Get transactions
|
|
||||||
transactions = await points_api.get_transactions("test_user_1", limit=10)
|
|
||||||
assert len(transactions) > 0, "Should have transactions"
|
|
||||||
print(f"✓ Get transactions works ({len(transactions)} transactions)")
|
|
||||||
|
|
||||||
# Test 8: Get ranking
|
|
||||||
ranking = await points_api.get_ranking(limit=10)
|
|
||||||
assert len(ranking) > 0, "Should have ranking entries"
|
|
||||||
assert "rank" in ranking[0], "Ranking should have rank field"
|
|
||||||
print(f"✓ Get ranking works ({len(ranking)} entries)")
|
|
||||||
|
|
||||||
print("\n✅ All tests passed!")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(test_basic_operations())
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
import tempfile
|
|
||||||
import unittest
|
|
||||||
import importlib.util
|
|
||||||
import sys
|
|
||||||
import types
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
PROJECT_ROOT = Path(__file__).resolve().parent
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_package(package_name: str, package_path: Path) -> None:
|
|
||||||
if package_name in sys.modules:
|
|
||||||
return
|
|
||||||
package = types.ModuleType(package_name)
|
|
||||||
package.__path__ = [str(package_path)]
|
|
||||||
sys.modules[package_name] = package
|
|
||||||
|
|
||||||
|
|
||||||
def load_module(module_name: str, relative_path: str):
|
|
||||||
module_path = PROJECT_ROOT / relative_path
|
|
||||||
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
|
||||||
module = importlib.util.module_from_spec(spec)
|
|
||||||
assert spec and spec.loader
|
|
||||||
sys.modules[module_name] = module
|
|
||||||
spec.loader.exec_module(module)
|
|
||||||
return module
|
|
||||||
|
|
||||||
|
|
||||||
ensure_package("danding_bot", PROJECT_ROOT / "danding_bot")
|
|
||||||
ensure_package("danding_bot.plugins", PROJECT_ROOT / "danding_bot/plugins")
|
|
||||||
ensure_package(
|
|
||||||
"danding_bot.plugins.onmyoji_gacha",
|
|
||||||
PROJECT_ROOT / "danding_bot/plugins/onmyoji_gacha",
|
|
||||||
)
|
|
||||||
load_module(
|
|
||||||
"danding_bot.plugins.onmyoji_gacha.config",
|
|
||||||
"danding_bot/plugins/onmyoji_gacha/config.py",
|
|
||||||
)
|
|
||||||
data_manager_module = load_module(
|
|
||||||
"danding_bot.plugins.onmyoji_gacha.data_manager",
|
|
||||||
"danding_bot/plugins/onmyoji_gacha/data_manager.py",
|
|
||||||
)
|
|
||||||
utils = load_module(
|
|
||||||
"danding_bot.plugins.onmyoji_gacha.utils",
|
|
||||||
"danding_bot/plugins/onmyoji_gacha/utils.py",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DataManagerSignInTests(unittest.TestCase):
|
|
||||||
def setUp(self) -> None:
|
|
||||||
self.temp_dir = tempfile.TemporaryDirectory()
|
|
||||||
base_path = Path(self.temp_dir.name)
|
|
||||||
self.original_db_file = data_manager_module.config.DB_FILE
|
|
||||||
self.original_img_dir = data_manager_module.config.SHIKIGAMI_IMG_DIR
|
|
||||||
data_manager_module.config.DB_FILE = str(base_path / "gacha.db")
|
|
||||||
data_manager_module.config.SHIKIGAMI_IMG_DIR = str(base_path / "images")
|
|
||||||
Path(data_manager_module.config.SHIKIGAMI_IMG_DIR).mkdir(parents=True, exist_ok=True)
|
|
||||||
self.manager = data_manager_module.DataManager()
|
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
|
||||||
data_manager_module.config.DB_FILE = self.original_db_file
|
|
||||||
data_manager_module.config.SHIKIGAMI_IMG_DIR = self.original_img_dir
|
|
||||||
self.temp_dir.cleanup()
|
|
||||||
|
|
||||||
def test_record_sign_in_is_idempotent_for_same_day(self) -> None:
|
|
||||||
user_id = "10001"
|
|
||||||
|
|
||||||
self.assertFalse(self.manager.has_signed_in_today(user_id))
|
|
||||||
self.assertTrue(self.manager.record_sign_in(user_id, 88))
|
|
||||||
self.assertTrue(self.manager.has_signed_in_today(user_id))
|
|
||||||
self.assertFalse(self.manager.record_sign_in(user_id, 99))
|
|
||||||
|
|
||||||
|
|
||||||
class UtilsSignInTests(unittest.TestCase):
|
|
||||||
def test_get_luck_description_uses_document_ranges(self) -> None:
|
|
||||||
self.assertEqual(utils.get_luck_description(1), ("非酋", "😭"))
|
|
||||||
self.assertEqual(utils.get_luck_description(11), ("一般", "😐"))
|
|
||||||
self.assertEqual(utils.get_luck_description(31), ("小欧", "😊"))
|
|
||||||
self.assertEqual(utils.get_luck_description(61), ("大欧", "🎉"))
|
|
||||||
self.assertEqual(utils.get_luck_description(91), ("欧皇", "👑"))
|
|
||||||
|
|
||||||
def test_format_sign_in_message_matches_required_format(self) -> None:
|
|
||||||
message = utils.format_sign_in_message("10001", "小夏", 87, 1247)
|
|
||||||
expected = (
|
|
||||||
"@小夏 📅 每日签到成功!\n"
|
|
||||||
"🎁 获得积分:87\n"
|
|
||||||
"🎉 今日运气:大欧\n"
|
|
||||||
"💰 当前积分:1247"
|
|
||||||
)
|
|
||||||
self.assertEqual(message, expected)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
"""Danding_QqPush 插件测试脚本"""
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
def test_qqpush_api():
|
|
||||||
"""测试 QQ 推送 API"""
|
|
||||||
|
|
||||||
# 配置
|
|
||||||
base_url = "http://localhost:8080" # NoneBot 默认端口
|
|
||||||
token = "danding-8HkL9xQ2" # 默认 Token
|
|
||||||
|
|
||||||
# 构造请求数据
|
|
||||||
data = {
|
|
||||||
"group_id": 574727392, # 替换为实际群号
|
|
||||||
"qq": 1424473282, # 替换为实际 QQ 号
|
|
||||||
"text": "系统告警#数据库连接失败#请立即处理#测试消息"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 发送请求
|
|
||||||
url = f"{base_url}/danding/qqpush/{token}"
|
|
||||||
|
|
||||||
print(f"正在测试 API: {url}")
|
|
||||||
print(f"请求数据: {json.dumps(data, ensure_ascii=False, indent=2)}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.post(
|
|
||||||
url,
|
|
||||||
json=data,
|
|
||||||
headers={"Content-Type": "application/json"},
|
|
||||||
timeout=10
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"\n响应状态码: {response.status_code}")
|
|
||||||
print(f"响应内容: {json.dumps(response.json(), ensure_ascii=False, indent=2)}")
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
print("\n✅ 测试成功!")
|
|
||||||
else:
|
|
||||||
print(f"\n❌ 测试失败,状态码: {response.status_code}")
|
|
||||||
|
|
||||||
except requests.exceptions.ConnectionError:
|
|
||||||
print("\n❌ 连接失败,请确认 NoneBot 是否已启动")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"\n❌ 测试异常: {str(e)}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_qqpush_api()
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
测试 onmyoji_gacha Web API 路由是否正确注册
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
# 添加项目路径
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'danding_bot'))
|
|
||||||
|
|
||||||
# 设置环境变量
|
|
||||||
os.environ.setdefault("ENVIRONMENT", "dev")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 模拟 NoneBot 环境
|
|
||||||
from nonebot import get_driver
|
|
||||||
from nonebot import init
|
|
||||||
|
|
||||||
# 初始化 NoneBot
|
|
||||||
init()
|
|
||||||
|
|
||||||
# 尝试获取驱动
|
|
||||||
driver = get_driver()
|
|
||||||
print(f"✅ 成功获取驱动: {type(driver)}")
|
|
||||||
|
|
||||||
# 检查是否有 server_app 属性
|
|
||||||
if hasattr(driver, 'server_app'):
|
|
||||||
print(f"✅ 找到 server_app: {type(driver.server_app)}")
|
|
||||||
|
|
||||||
# 检查路由是否已注册
|
|
||||||
app = driver.server_app
|
|
||||||
routes = app.routes
|
|
||||||
print(f"✅ 当前注册的路由数量: {len(routes)}")
|
|
||||||
|
|
||||||
# 查找我们的路由
|
|
||||||
onmyoji_routes = [route for route in routes if hasattr(route, 'path') and '/onmyoji_gacha' in str(route.path)]
|
|
||||||
print(f"✅ 找到 onmyoji_gacha 路由: {len(onmyoji_routes)} 个")
|
|
||||||
|
|
||||||
for route in onmyoji_routes:
|
|
||||||
print(f" - {route.path} ({route.methods if hasattr(route, 'methods') else 'N/A'})")
|
|
||||||
else:
|
|
||||||
print("❌ 驱动没有 server_app 属性")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ 测试失败: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>令牌测试页面</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container mt-5">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h4>管理员令牌测试</h4>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="tokenInput" class="form-label">管理员令牌</label>
|
|
||||||
<input type="text" class="form-control" id="tokenInput" placeholder="请输入管理员令牌">
|
|
||||||
<small class="text-muted">正确的令牌应该是: onmyoji_admin_token_2024</small>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-primary" onclick="testToken()">测试令牌</button>
|
|
||||||
<div id="result" class="mt-3"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
async function testToken() {
|
|
||||||
const token = document.getElementById('tokenInput').value.trim();
|
|
||||||
const resultDiv = document.getElementById('result');
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
resultDiv.innerHTML = '<div class="alert alert-warning">请输入令牌</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resultDiv.innerHTML = '<div class="alert alert-info">正在测试...</div>';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/onmyoji_gacha/api/stats/daily', {
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('响应状态:', response.status);
|
|
||||||
console.log('响应头:', response.headers);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
resultDiv.innerHTML = `
|
|
||||||
<div class="alert alert-success">
|
|
||||||
<h5>✅ 令牌验证成功!</h5>
|
|
||||||
<pre>${JSON.stringify(data, null, 2)}</pre>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
const errorText = await response.text();
|
|
||||||
resultDiv.innerHTML = `
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
<h5>❌ 令牌验证失败</h5>
|
|
||||||
<p>状态码: ${response.status}</p>
|
|
||||||
<p>错误信息: ${errorText}</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
resultDiv.innerHTML = `
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
<h5>❌ 请求失败</h5>
|
|
||||||
<p>${error.message}</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user