mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2025-12-14 13:42:56 +08:00
重构webui适配 (#1801)
* ♻️ 使用Uninfo重构PlatformUtils基础方法 * 🩹 优化插件加载与模块格式转换逻辑 * 🚑 修复商店道具无法使用 * 🚑 修复道具无法正常使用 * 🔧 增加Bot状态管理及模块禁用功能 * 🎨 优化Web UI代码结构,修改target方法 * 🚨 auto fix by pre-commit hooks * 🎨 添加菜单API及优化异常处理 * 🐛 优化菜单API及模型结构,修复WebUi插件列表Api * 📝 更新仓库readme * 🚨 add mdlint file * 📝 Add help chapter. * 🐛 修复优化AuthChecker逻辑 * 🐛 优化数据库API,移除冗余导入及修正SQL_DICT引用 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: BalconyJH <balconyjh@gmail.com>
This commit is contained in:
parent
ebf05fd884
commit
35014e4048
4
.markdownlint.yaml
Normal file
4
.markdownlint.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
MD013: false
|
||||
MD024: # 重复标题
|
||||
siblings_only: true
|
||||
MD033: false # 允许 html
|
||||
830
README.md
830
README.md
@ -1,21 +1,43 @@
|
||||
<!-- markdownlint-disable MD033 MD041 -->
|
||||
<div align=center>
|
||||
|
||||
<img width="250" height="312" src="https://github.com/HibiKier/zhenxun_bot/blob/main/docs_image/tt.jpg"/>
|
||||
<img width="250" height="312" src=./docs_image/tt.jpg alt="zhenxun_bot"/>
|
||||
|
||||
</div>
|
||||
|
||||
<div align=center>
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
<a href="./LICENSE">
|
||||
<img src="https://img.shields.io/badge/license-AGPL3.0-FE7D37" alt="license">
|
||||
</a>
|
||||
<a href="https://www.python.org">
|
||||
<img src="https://img.shields.io/badge/Python-3.10%20%7C%203.11%20%7C%203.12-blue" alt="python">
|
||||
</a>
|
||||
<a href="https://nonebot.dev/">
|
||||
<img src="https://img.shields.io/badge/nonebot-v2.1.3-EA5252" alt="nonebot">
|
||||
</a>
|
||||
<a href="https://onebot.dev/">
|
||||
<img src="https://img.shields.io/badge/OneBot-v11-black?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAIVBMVEUAAAAAAAADAwMHBwceHh4UFBQNDQ0ZGRkoKCgvLy8iIiLWSdWYAAAAAXRSTlMAQObYZgAAAQVJREFUSMftlM0RgjAQhV+0ATYK6i1Xb+iMd0qgBEqgBEuwBOxU2QDKsjvojQPvkJ/ZL5sXkgWrFirK4MibYUdE3OR2nEpuKz1/q8CdNxNQgthZCXYVLjyoDQftaKuniHHWRnPh2GCUetR2/9HsMAXyUT4/3UHwtQT2AggSCGKeSAsFnxBIOuAggdh3AKTL7pDuCyABcMb0aQP7aM4AnAbc/wHwA5D2wDHTTe56gIIOUA/4YYV2e1sg713PXdZJAuncdZMAGkAukU9OAn40O849+0ornPwT93rphWF0mgAbauUrEOthlX8Zu7P5A6kZyKCJy75hhw1Mgr9RAUvX7A3csGqZegEdniCx30c3agAAAABJRU5ErkJggg==" alt="onebot">
|
||||
</a>
|
||||
<a href="https://onebot.dev/">
|
||||
<img src="https://img.shields.io/badge/OneBot-v12-black?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAIVBMVEUAAAAAAAADAwMHBwceHh4UFBQNDQ0ZGRkoKCgvLy8iIiLWSdWYAAAAAXRSTlMAQObYZgAAAQVJREFUSMftlM0RgjAQhV+0ATYK6i1Xb+iMd0qgBEqgBEuwBOxU2QDKsjvojQPvkJ/ZL5sXkgWrFirK4MibYUdE3OR2nEpuKz1/q8CdNxNQgthZCXYVLjyoDQftaKuniHHWRnPh2GCUetR2/9HsMAXyUT4/3UHwtQT2AggSCGKeSAsFnxBIOuAggdh3AKTL7pDuCyABcMb0aQP7aM4AnAbc/wHwA5D2wDHTTe56gIIOUA/4YYV2e1sg713PXdZJAuncdZMAGkAukU9OAn40O849+0ornPwT93rphWF0mgAbauUrEOthlX8Zu7P5A6kZyKCJy75hhw1Mgr9RAUvX7A3csGqZegEdniCx30c3agAAAABJRU5ErkJggg==" alt="onebot">
|
||||
</a>
|
||||
<a href="https://bot.q.qq.com/wiki/">
|
||||
<img src="https://img.shields.io/badge/QQ-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTIuODIgMTMwLjg5Ij48ZyBkYXRhLW5hbWU9IuWbvuWxgiAyIj48ZyBkYXRhLW5hbWU9IuWbvuWxgiAxIj48cGF0aCBkPSJNNTUuNjMgMTMwLjhjLTcgMC0xMy45LjA4LTIwLjg2IDAtMTkuMTUtLjI1LTMxLjcxLTExLjQtMzQuMjItMzAuMy00LjA3LTMwLjY2IDE0LjkzLTU5LjIgNDQuODMtNjYuNjQgMi0uNTEgNS4yMS0uMzEgNS4yMS0xLjYzIDAtMi4xMy4xNC0yLjEzLjE0LTUuNTcgMC0uODktMS4zLTEuNDYtMi4yMi0yLjMxLTYuNzMtNi4yMy03LjY3LTEzLjQxLTEtMjAuMTggNS40LTUuNTIgMTEuODctNS40IDE3LjgtLjU5IDYuNDkgNS4yNiA2LjMxIDEzLjA4LS44NiAyMS0uNjguNzQtMS43OCAxLjYtMS43OCAyLjY3djQuMjFjMCAxLjM1IDIuMiAxLjYyIDQuNzkgMi4zNSAzMS4wOSA4LjY1IDQ4LjE3IDM0LjEzIDQ1IDY2LjM3LTEuNzYgMTguMTUtMTQuNTYgMzAuMjMtMzIuNyAzMC42My04LjAyLjE5LTE2LjA3LS4wMS0yNC4xMy0uMDF6IiBmaWxsPSIjMDI5OWZlIi8+PHBhdGggZD0iTTMxLjQ2IDExOC4zOGMtMTAuNS0uNjktMTYuOC02Ljg2LTE4LjM4LTE3LjI3LTMtMTkuNDIgMi43OC0zNS44NiAxOC40Ni00Ny44MyAxNC4xNi0xMC44IDI5Ljg3LTEyIDQ1LjM4LTMuMTkgMTcuMjUgOS44NCAyNC41OSAyNS44MSAyNCA0NS4yOS0uNDkgMTUuOS04LjQyIDIzLjE0LTI0LjM4IDIzLjUtNi41OS4xNC0xMy4xOSAwLTE5Ljc5IDAiIGZpbGw9IiNmZWZlZmUiLz48cGF0aCBkPSJNNDYuMDUgNzkuNThjLjA5IDUgLjIzIDkuODItNyA5Ljc3LTcuODItLjA2LTYuMS01LjY5LTYuMjQtMTAuMTktLjE1LTQuODItLjczLTEwIDYuNzMtOS44NHM2LjM3IDUuNTUgNi41MSAxMC4yNnoiIGZpbGw9IiMxMDlmZmUiLz48cGF0aCBkPSJNODAuMjcgNzkuMjdjLS41MyAzLjkxIDEuNzUgOS42NC01Ljg4IDEwLTcuNDcuMzctNi44MS00LjgyLTYuNjEtOS41LjItNC4zMi0xLjgzLTEwIDUuNzgtMTAuNDJzNi41OSA0Ljg5IDYuNzEgOS45MnoiIGZpbGw9IiMwODljZmUiLz48L2c+PC9nPjwvc3ZnPg==" alt="QQ">
|
||||
</a>
|
||||
<a href="https://github.com/psf/black">
|
||||
<img src="https://img.shields.io/badge/code%20style-black-000000.svg?logo=python&logoColor=edb641" alt="black">
|
||||
</a>
|
||||
<a href="https://github.com/Microsoft/pyright">
|
||||
<img src="https://img.shields.io/badge/types-pyright-797952.svg?logo=python&logoColor=edb641" alt="pyright">
|
||||
</a>
|
||||
<a href="https://github.com/astral-sh/ruff">
|
||||
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json" alt="ruff">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div align=center>
|
||||
|
||||
[](https://github.com/HibiKier/zhenxun_bot/blob/main/LICENSE)
|
||||
[](https://jq.qq.com/?_wv=1027&k=u8PgBkMZ)
|
||||
[](https://qm.qq.com/q/mRNtLSl6uc)
|
||||
[](https://qm.qq.com/q/YYYt5rkMYc)
|
||||
|
||||
</div>
|
||||
@ -36,7 +58,7 @@
|
||||
|
||||
“真寻是<strong>[椛椛](https://github.com/FloatTech/ZeroBot-Plugin)</strong>的好朋友!”
|
||||
|
||||
:tada:喜欢真寻,于是真寻就来了!:tada:
|
||||
🎉喜欢真寻,于是真寻就来了!🎉
|
||||
|
||||
本项目符合 [OneBot](https://github.com/howmanybots/onebot) 标准,可基于以下项目与机器人框架/平台进行交互
|
||||
|
||||
@ -50,28 +72,27 @@
|
||||
|
||||
<div align=center>
|
||||
|
||||

|
||||
<img src="https://api.star-history.com/svg?repos=HibiKier/zhenxun_bot&type=Timeline" alt="Star Trend" width="800" />
|
||||
|
||||
</div>
|
||||
|
||||
## 真寻觉得你需要帮助
|
||||
## 🤝 帮助页面
|
||||
|
||||
<div align=center>
|
||||
<details>
|
||||
<summary>点击展开查看图片</summary>
|
||||
<img width="300" height="auto" src="./docs_image/zhenxun_help.png" alt="zhenxun_help"/>
|
||||
<img width="300" height="auto" src="./docs_image/html_help.png" alt="html_help"/>
|
||||
<img width="300" height="auto" src="./docs_image/help.png" alt="help"/>
|
||||
</details>
|
||||
|
||||
<img width="350" height="350" src="https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/help.png"/>
|
||||
<img width="250" height="500" src="https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/html_help.png"/>
|
||||
<img width="180" height="450" src="https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/zhenxun_help.png"/>
|
||||
|
||||
</div>
|
||||
|
||||
## 这是一份扩展
|
||||
## 📦 这是一份扩展
|
||||
|
||||
### 1. 体验一下?
|
||||
|
||||
这是一个免费的,版本为 dev 的 zhenxun,你可以通过 [napcat](https://github.com/NapNeko/NapCatQQ) 或 [拉格朗日](https://github.com/LagrangeDev/Lagrange.Core) 以及 [matcha](https://github.com/A-kirami/matcha) 等直接连接用于体验与测试
|
||||
(球球了测试君!)
|
||||
|
||||
```
|
||||
```text
|
||||
Url: ws://test.zhenxun.org:8080/onebot/v11/ws
|
||||
AccessToken: PUBLIC_ZHENXUN_TEST
|
||||
|
||||
@ -89,39 +110,23 @@ AccessToken: PUBLIC_ZHENXUN_TEST
|
||||
| [插件库](https://github.com/zhenxun-org/zhenxun_bot_plugins) | 插件 | [zhenxun-org](https://github.com/zhenxun-org) | 原 plugins 文件夹插件 |
|
||||
| [插件索引库](https://github.com/zhenxun-org/zhenxun_bot_plugins_index) | 插件 | [zhenxun-org](https://github.com/zhenxun-org) | 扩展插件索引库 |
|
||||
| [一键安装](https://github.com/soloxiaoye2022/zhenxun_bot-deploy) | 安装 | [soloxiaoye2022](https://github.com/soloxiaoye2022) | 第三方 |
|
||||
| [WebUi](https://github.com/HibiKier/zhenxun_bot_webui) | 管理 | [hibikier](https://github.com/HibiKier) | 基于真寻 WebApi 的 webui 实现 |
|
||||
| [WebUi](https://github.com/HibiKier/zhenxun_bot_webui) | 管理 | [hibikier](https://github.com/HibiKier) | 基于真寻 WebApi 的 webui 实现 [预览](#-webui界面展示) |
|
||||
| [安卓 app(WebUi)](https://github.com/YuS1aN/zhenxun_bot_android_ui) | 安装 | [YuS1aN](https://github.com/YuS1aN) | 第三方 |
|
||||
|
||||
<details>
|
||||
<summary> <strong> WebUI </strong>后台示例图 </summary>
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<br/>
|
||||
|
||||
</div>
|
||||
|
||||
## ~~来点优点?~~ 可爱难道还不够吗
|
||||
## 🥰 ~~来点优点?~~ 可爱难道还不够吗
|
||||
|
||||
- 实现了许多功能,且提供了大量功能管理命令,进行了多平台适配,兼容 nb2 商店插件
|
||||
- 拥有完善可用的 webui
|
||||
- 通过 Config 配置项将所有插件配置统计保存至 config.yaml,利于统一用户修改
|
||||
- 方便增删插件,原生 nonebot2 matcher,不需要额外修改,仅仅通过简单的配置属性就可以生成`帮助图片`和`帮助信息`
|
||||
- 提供了 cd,阻塞,每日次数等限制,仅仅通过简单的属性就可以生成一个限制,例如:`PluginCdBlock` 等
|
||||
- **..... 更多详细请通过[[传送门](https://hibikier.github.io/zhenxun_bot/)]查看文档!**
|
||||
- **更多详细请通过 [传送门](https://hibikier.github.io/zhenxun_bot/) 查看文档!**
|
||||
|
||||
## 简单部署
|
||||
## 🛠️ 简单部署
|
||||
|
||||
```
|
||||
```bash
|
||||
# 获取代码
|
||||
git clone https://github.com/HibiKier/zhenxun_bot.git
|
||||
|
||||
@ -134,636 +139,154 @@ poetry install # 安装依赖
|
||||
|
||||
# 开始运行
|
||||
poetry shell # 进入虚拟环境
|
||||
python bot.py
|
||||
|
||||
# 首次后会在data目录下生成config.yaml文件
|
||||
# config.yaml用户配置插件
|
||||
python bot.py # 运行机器人
|
||||
```
|
||||
|
||||
## 简单配置
|
||||
## 📝 简单配置
|
||||
|
||||
```
|
||||
1.在.env.dev文件中
|
||||
> [!TIP]
|
||||
> config.yaml 需要启动一次 Bot 后生成
|
||||
|
||||
SUPERUSERS = [""] # 填写你的QQ
|
||||
1.在 .env.dev 文件中填写你的机器人配置项
|
||||
|
||||
PLATFORM_SUPERUSERS = '
|
||||
{
|
||||
"qq": [""], # 在此处填写你的qq
|
||||
"dodo": [],
|
||||
"kaiheila": [],
|
||||
"discord": []
|
||||
}
|
||||
'
|
||||
# 此处填写你的数据库地址
|
||||
# 示例: "postgres://user:password@127.0.0.1:5432/database"
|
||||
# 示例: "mysql://user:password@127.0.0.1:5432/database"
|
||||
# 示例: "sqlite:data/db/zhenxun.db" 在data目录下建立db文件夹
|
||||
DB_URL = "" # 数据库地址
|
||||
2.在 configs/config.yaml 文件中修改你需要修改的插件配置项
|
||||
|
||||
<details>
|
||||
<summary>数据库地址(DB_URL)配置说明</summary>
|
||||
|
||||
2.在configs/config.yaml文件中 # 该文件需要启动一次后生成
|
||||
* 修改插件配置项
|
||||
DB_URL 是基于 Tortoise ORM 的数据库连接字符串,用于指定项目所使用的数据库。以下是 DB_URL 的组成部分以及示例:
|
||||
|
||||
```
|
||||
格式为: ```<数据库类型>://<用户名>:<密码>@<主机>:<端口>/<数据库名>?<参数>```
|
||||
|
||||
## 功能列表
|
||||
- 数据库类型:表示数据库类型,例如 postgres、mysql、sqlite 等。
|
||||
- 用户名:数据库的用户名,例如 root。
|
||||
- 密码:数据库的密码,例如 123456。
|
||||
- 主机:数据库的主机地址,例如 127.0.0.1(本地)或远程服务器 IP。
|
||||
- 端口:数据库的端口号,例如:PostgreSQL:5432, MySQL:3306
|
||||
- 数据库名:指定要使用的数据库名称,例如 zhenxun。
|
||||
- 参数(可选):用于传递额外的配置,例如字符集设置。
|
||||
|
||||
</details>
|
||||
|
||||
## 📋 功能列表
|
||||
|
||||
> [!NOTE]
|
||||
> 真寻原 `plugins` 插件文件夹已迁移至 [插件仓库](https://github.com/zhenxun-org/zhenxun_bot_plugins) ,现在本体仅保留核心功能
|
||||
|
||||
<details>
|
||||
<summary>内置功能</summary>
|
||||
|
||||
**真寻原 `plugins` 插件文件夹已迁移至其他仓库,当前内置仅保留必要的功能**
|
||||
### 🔧 基础功能
|
||||
|
||||
### 基础功能
|
||||
- 昵称系统(群与群与私聊分开)
|
||||
- 签到/我的签到/好感度排行/好感度总排行(影响色图概率和开箱次数,支持配置)
|
||||
- 商店/我的金币/购买道具/使用道具/金币排行(完整的商店添加/购买/使用流程)
|
||||
- 查看当前群欢迎消息
|
||||
- 个人信息查看(群组内权限,聊天频率等)
|
||||
- 消息撤回
|
||||
- 功能统计可视化
|
||||
- 关于
|
||||
- 三种样式的帮助菜单
|
||||
|
||||
- [x] 昵称系统(群与群与私聊分开)
|
||||
- [x] 签到/我的签到/好感度排行/好感度总排行(影响色图概率和开箱次数,支持配置)
|
||||
- [x] 商店/我的金币/购买道具/使用道具/金币排行(完整的商店添加/购买/使用流程)
|
||||
- [x] 查看当前群欢迎消息
|
||||
- [x] 个人信息查看(群组内权限,聊天频率等)
|
||||
- [x] 消息撤回
|
||||
- [x] 功能统计可视化
|
||||
- [x] 关于
|
||||
- [x] 三种样式的帮助菜单
|
||||
### 🛠️ 管理员功能
|
||||
|
||||
### 管理员功能
|
||||
- 管理员帮助
|
||||
- 更新群组成员信息
|
||||
- 95%的群功能开关
|
||||
- 查看群内被动技能状态
|
||||
- 自定义群欢迎消息(是真寻的不是管家的!)
|
||||
- ban/unban(支持设置 ban 时长)= 群组及用户的黑名单
|
||||
- 休息吧/醒来(群组内真寻状态)
|
||||
|
||||
- [x] 管理员帮助
|
||||
- [x] 更新群组成员信息
|
||||
- [x] 95%的群功能开关
|
||||
- [x] 查看群内被动技能状态
|
||||
- [x] 自定义群欢迎消息(是真寻的不是管家的!)
|
||||
- [x] ban/unban(支持设置 ban 时长)= 群组及用户的黑名单
|
||||
- [x] 休息吧/醒来(群组内真寻状态)
|
||||
### 🧑💼 超级用户功能
|
||||
|
||||
### 超级用户功能
|
||||
- 超级用户帮助
|
||||
- 添加/删除权限(是真寻的管理员权限,不是群管理员)
|
||||
- 群组管理,退群指令等
|
||||
- 广播
|
||||
- 自检(检查系统状态)
|
||||
- 所有群组/所有好友
|
||||
- 退出指定群
|
||||
- 更新好友信息/更新群信息
|
||||
- 修改群权限
|
||||
- 检查更新
|
||||
- 重启
|
||||
- 添加/删除/查看群白名单
|
||||
- 功能开关(更多设置)
|
||||
- 功能状态
|
||||
- 执行 SQL
|
||||
- 重载配置
|
||||
- 清理临时数据
|
||||
- 增删群认证
|
||||
- 同意/拒绝好友/群聊请求
|
||||
- 添加/移除/更新插件/插件商店(plugins 库以及扩展库)
|
||||
- WebUI API(对真寻前端的支持)
|
||||
|
||||
- [x] 超级用户帮助
|
||||
- [x] 添加/删除权限(是真寻的管理员权限,不是群管理员)
|
||||
- [x] 群组管理,退群指令等
|
||||
- [x] 广播
|
||||
- [x] 自检(检查系统状态)
|
||||
- [x] 所有群组/所有好友
|
||||
- [x] 退出指定群
|
||||
- [x] 更新好友信息/更新群信息
|
||||
- [x] 修改群权限
|
||||
- [x] 检查更新
|
||||
- [x] 重启
|
||||
- [x] 添加/删除/查看群白名单
|
||||
- [x] 功能开关(更多设置)
|
||||
- [x] 功能状态
|
||||
- [x] 执行 SQL
|
||||
- [x] 重载配置
|
||||
- [x] 清理临时数据
|
||||
- [x] 增删群认证
|
||||
- [x] 同意/拒绝好友/群聊请求
|
||||
- [x] 添加/移除/更新插件/插件商店(plugins 库以及扩展库)
|
||||
- [x] WebUI API(对真寻前端的支持)
|
||||
#### 🛡️ 超级用户的被动技能
|
||||
|
||||
#### 超级用户的被动技能
|
||||
- 邀请入群提醒(别人邀请真寻入群,可配置自动同意)
|
||||
|
||||
- [x] 邀请入群提醒(别人邀请真寻入群,可配置自动同意)
|
||||
- 添加好友提醒(别人添加真寻好友,可配置自动同意)
|
||||
|
||||
- [x] 添加好友提醒(别人添加真寻好友,可配置自动同意)
|
||||
### 🤖 被动技能
|
||||
|
||||
### 被动技能
|
||||
- 群早晚安
|
||||
|
||||
- [x] 群早晚安
|
||||
### 👻 看不见的技能
|
||||
|
||||
### 看不见的技能
|
||||
|
||||
- [x] 功能调用统计
|
||||
- [x] 聊天记录统计
|
||||
- [x] 检测恶意触发命令(将被最高权限 ban 掉 30 分钟,只有最高权限(9 级)可以进行 unban)
|
||||
- [x] 自动同意好友/群组请求,加群请求将会提醒管理员,退群提示,加群欢迎等等
|
||||
- [x] 群聊时间检测(当群聊最后一人发言时间大于当前 48 小时后将关闭该群所有通知(即被动技能))
|
||||
- [x] 群管理员监控,自动为新晋管理员增加权限,为失去群管理员的用户删除权限
|
||||
- [x] 群权限系统
|
||||
- [x] 定时更新权限
|
||||
- [x] 自动配置重载
|
||||
- [x] 强制入群保护
|
||||
- [x] 自定备份(可配置)
|
||||
- [x] 笨蛋检测(当使用功能名称当指令时真寻会跳出来狠狠嘲笑并帮助)
|
||||
|
||||
### 更多插件
|
||||
|
||||
- [更多插件](https://github.com/zhenxun-org/zhenxun_bot_plugins)
|
||||
|
||||
- [第三方插件索引库](https://github.com/zhenxun-org/zhenxun_bot_plugins_index)
|
||||
- 功能调用统计
|
||||
- 聊天记录统计
|
||||
- 检测恶意触发命令(将被最高权限 ban 掉 30 分钟,只有最高权限(9 级)可以进行 unban)
|
||||
- 自动同意好友/群组请求,加群请求将会提醒管理员,退群提示,加群欢迎等等
|
||||
- 群聊时间检测(当群聊最后一人发言时间大于当前 48 小时后将关闭该群所有通知(即被动技能))
|
||||
- 群管理员监控,自动为新晋管理员增加权限,为失去群管理员的用户删除权限
|
||||
- 群权限系统
|
||||
- 定时更新权限
|
||||
- 自动配置重载
|
||||
- 强制入群保护
|
||||
- 自定备份(可配置)
|
||||
- 笨蛋检测(当使用功能名称当指令时真寻会跳出来狠狠嘲笑并帮助)
|
||||
|
||||
</details>
|
||||
|
||||
## [爱发电](https://afdian.com/a/HibiKier)
|
||||
## 💖 赞助
|
||||
|
||||
<details>
|
||||
<summary>爱发电 以及 感谢投喂 </summary>
|
||||
<img width="365px" height="450px" src="https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/afd.jpg">
|
||||
<summary>爱发电</summary>
|
||||
<a href="https://afdian.com/a/HibiKier">
|
||||
<img width="365px" height="450px" src=./docs_image/afd.jpg>
|
||||
</a>
|
||||
</details>
|
||||
|
||||
### 感谢名单
|
||||
### 赞助名单
|
||||
|
||||
(可以告诉我你的 **github** 地址,我偷偷换掉 0v|)
|
||||
|
||||
[shenqi](https://afdian.net/u/fa923a8cfe3d11eba61752540025c377)
|
||||
[A_Kyuu](https://afdian.net/u/b83954fc2c1211eba9eb52540025c377)
|
||||
[疯狂混沌](https://afdian.net/u/789a2f9200cd11edb38352540025c377)
|
||||
[投冥](https://afdian.net/a/144514mm)
|
||||
[茶喵](https://afdian.net/u/fd22382eac4d11ecbfc652540025c377)
|
||||
[AemokpaTNR](https://afdian.net/u/1169bb8c8a9611edb0c152540025c377)
|
||||
[爱发电用户\_wrxn](https://afdian.net/u/4aa03d20db4311ecb1e752540025c377)
|
||||
[qqw](https://afdian.net/u/b71db4e2cc3e11ebb76652540025c377)
|
||||
[溫一壺月光下酒](https://afdian.net/u/ad667a5c650c11ed89bf52540025c377)
|
||||
[伝木](https://afdian.net/u/246b80683f9511edba7552540025c377)
|
||||
[阿奎](https://afdian.net/u/da41f72845d511ed930d52540025c377)
|
||||
[醉梦尘逸](https://afdian.net/u/bc11d2683cd011ed99b552540025c377)
|
||||
[Abc](https://afdian.net/u/870dc10a3cd311ed828852540025c377)
|
||||
[本喵无敌哒](https://afdian.net/u/dffaa9005bc911ebb69b52540025c377)
|
||||
[椎名冬羽](https://afdian.net/u/ca1ebd64395e11ed81b452540025c377)
|
||||
[kaito](https://afdian.net/u/a055e20a498811eab1f052540025c377)
|
||||
[笑柒 XIAO_Q7](https://afdian.net/u/4696db5c529111ec84ea52540025c377)
|
||||
[请问一份爱多少钱](https://afdian.net/u/f57ef6602dbd11ed977f52540025c377)
|
||||
[咸鱼鱼鱼鱼](https://afdian.net/u/8e39b9a400e011ed9f4a52540025c377)
|
||||
[Kafka](https://afdian.net/u/41d66798ef6911ecbc5952540025c377)
|
||||
[墨然](https://afdian.net/u/8aa5874a644d11eb8a6752540025c377)
|
||||
[爱发电用户\_T9e4](https://afdian.net/u/2ad1bb82f3a711eca22852540025c377)
|
||||
[笑柒 XIAO_Q7](https://afdian.net/u/4696db5c529111ec84ea52540025c377)
|
||||
[noahzark](https://afdian.net/a/noahzark)
|
||||
[腊条](https://afdian.net/u/f739c4d69eca11eba94b52540025c377)
|
||||
[ze roller](https://afdian.net/u/0e599e96257211ed805152540025c377)
|
||||
[爱发电用户\_4jrf](https://afdian.net/u/6b2cdcc817c611ed949152540025c377)
|
||||
[爱发电用户\_TBsd](https://afdian.net/u/db638b60217911ed9efd52540025c377)
|
||||
[烟寒若雨](https://afdian.net/u/067bd2161eec11eda62b52540025c377)
|
||||
[ln](https://afdian.net/u/b51914ba1c6611ed8a4e52540025c377)
|
||||
[爱发电用户\_b9S4](https://afdian.net/u/3d8f30581a2911edba6d52540025c377)
|
||||
[爱发电用户\_c58s](https://afdian.net/u/a6ad8dda195e11ed9a4152540025c377)
|
||||
[爱发电用户\_eNr9](https://afdian.net/u/05fdb41c0c9a11ed814952540025c377)
|
||||
[MangataAkihi](https://github.com/Sakuracio)
|
||||
[炀](https://afdian.net/u/69b76e9ec77b11ec874f52540025c377)
|
||||
[爱发电用户\_Bc6j](https://afdian.net/u/8546be24f44111eca64052540025c377)
|
||||
[大魔王](https://github.com/xipesoy)
|
||||
[CopilotLaLaLa](https://github.com/CopilotLaLaLa)
|
||||
[嘿小欧](https://afdian.net/u/daa4bec4f24911ec82e552540025c377)
|
||||
[回忆的秋千](https://afdian.net/u/e315d9c6f14f11ecbeef52540025c377)
|
||||
[十年くん](https://github.com/shinianj)
|
||||
[哇](https://afdian.net/u/9b266244f23911eca19052540025c377)
|
||||
[yajiwa](https://github.com/yajiwa)
|
||||
[爆金币](https://afdian.net/u/0d78879ef23711ecb22452540025c377)
|
||||
...
|
||||
[shenqi](https://afdian.net/u/fa923a8cfe3d11eba61752540025c377) [A_Kyuu](https://afdian.net/u/b83954fc2c1211eba9eb52540025c377) [疯狂混沌](https://afdian.net/u/789a2f9200cd11edb38352540025c377) [投冥](https://afdian.net/a/144514mm) [茶喵](https://afdian.net/u/fd22382eac4d11ecbfc652540025c377) [AemokpaTNR](https://afdian.net/u/1169bb8c8a9611edb0c152540025c377) [爱发电用户\_wrxn](https://afdian.net/u/4aa03d20db4311ecb1e752540025c377) [qqw](https://afdian.net/u/b71db4e2cc3e11ebb76652540025c377) [溫一壺月光下酒](https://afdian.net/u/ad667a5c650c11ed89bf52540025c377) [伝木](https://afdian.net/u/246b80683f9511edba7552540025c377) [阿奎](https://afdian.net/u/da41f72845d511ed930d52540025c377) [醉梦尘逸](https://afdian.net/u/bc11d2683cd011ed99b552540025c377) [Abc](https://afdian.net/u/870dc10a3cd311ed828852540025c377) [本喵无敌哒](https://afdian.net/u/dffaa9005bc911ebb69b52540025c377) [椎名冬羽](https://afdian.net/u/ca1ebd64395e11ed81b452540025c377) [kaito](https://afdian.net/u/a055e20a498811eab1f052540025c377) [笑柒 XIAO_Q7](https://afdian.net/u/4696db5c529111ec84ea52540025c377) [请问一份爱多少钱](https://afdian.net/u/f57ef6602dbd11ed977f52540025c377) [咸鱼鱼鱼鱼](https://afdian.net/u/8e39b9a400e011ed9f4a52540025c377) [Kafka](https://afdian.net/u/41d66798ef6911ecbc5952540025c377) [墨然](https://afdian.net/u/8aa5874a644d11eb8a6752540025c377) [爱发电用户\_T9e4](https://afdian.net/u/2ad1bb82f3a711eca22852540025c377) [笑柒 XIAO_Q7](https://afdian.net/u/4696db5c529111ec84ea52540025c377) [noahzark](https://afdian.net/a/noahzark) [腊条](https://afdian.net/u/f739c4d69eca11eba94b52540025c377) [ze roller](https://afdian.net/u/0e599e96257211ed805152540025c377) [爱发电用户\_4jrf](https://afdian.net/u/6b2cdcc817c611ed949152540025c377) [爱发电用户\_TBsd](https://afdian.net/u/db638b60217911ed9efd52540025c377) [烟寒若雨](https://afdian.net/u/067bd2161eec11eda62b52540025c377) [ln](https://afdian.net/u/b51914ba1c6611ed8a4e52540025c377) [爱发电用户\_b9S4](https://afdian.net/u/3d8f30581a2911edba6d52540025c377) [爱发电用户\_c58s](https://afdian.net/u/a6ad8dda195e11ed9a4152540025c377) [爱发电用户\_eNr9](https://afdian.net/u/05fdb41c0c9a11ed814952540025c377) [MangataAkihi](https://github.com/Sakuracio) [炀](https://afdian.net/u/69b76e9ec77b11ec874f52540025c377) [爱发电用户\_Bc6j](https://afdian.net/u/8546be24f44111eca64052540025c377) [大魔王](https://github.com/xipesoy) [CopilotLaLaLa](https://github.com/CopilotLaLaLa) [嘿小欧](https://afdian.net/u/daa4bec4f24911ec82e552540025c377) [回忆的秋千](https://afdian.net/u/e315d9c6f14f11ecbeef52540025c377) [十年くん](https://github.com/shinianj) [哇](https://afdian.net/u/9b266244f23911eca19052540025c377) [yajiwa](https://github.com/yajiwa) [爆金币](https://afdian.net/u/0d78879ef23711ecb22452540025c377)...
|
||||
|
||||
</details>
|
||||
## 📜 贡献指南
|
||||
|
||||
<!-- ## 更新
|
||||
欢迎查看我们的 [贡献指南](CONTRIBUTING.md) 和 [行为守则](CODE_OF_CONDUCT.md) 以了解如何参与贡献。
|
||||
|
||||
### 2024/8/11
|
||||
## ❔ 需要帮助?
|
||||
|
||||
- 更新 dev -->
|
||||
> [!TIP]
|
||||
> 发起 [issue](https://github.com/HibiKier/zhenxun_bot/issues/new/choose) 前,我们希望你能够阅读过或者了解 [提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)
|
||||
>
|
||||
> - 善用[搜索引擎](https://www.google.com/)
|
||||
> - 查阅 issue 中是否有类似问题,如果没有请按照模板发起 issue
|
||||
|
||||
<!-- ### 2024/1/25
|
||||
欢迎前往 [issue](https://github.com/HibiKier/zhenxun_bot/issues/new/choose) 中提出你遇到的问题,或者加入我们的 [用户群](https://qm.qq.com/q/mRNtLSl6uc) 或 [技术群](https://qm.qq.com/q/YYYt5rkMYc)与我们联系
|
||||
|
||||
* 重构webui
|
||||
## 🛠️ 进度追踪
|
||||
|
||||
### 2023/12/28
|
||||
Project [zhenxun_bot](https://github.com/users/HibiKier/projects/2)
|
||||
|
||||
* 修复B站动态获取失败的时候,会发送空消息
|
||||
|
||||
### 2023/9/6
|
||||
|
||||
* 修正b站订阅
|
||||
|
||||
### 2023/8/28
|
||||
|
||||
* 重构`红包`功能, 允许一个群聊中有多个用户发起的红包,发送`开`等命令会开启群中所有条件允许的红包,新增`红包结算排行`,在红包退回或抢完时统计,在`塞红包`时at可以发送专属红包
|
||||
* 开箱添加`更新武器箱图片`超级用户命令,用于导入数据表后更新图片
|
||||
|
||||
### 2023/8/20
|
||||
|
||||
* 修复词条回答包含at时使用模糊|正则等问时无法正确匹配问题
|
||||
* 修复开箱时最后开箱日期数据未更新
|
||||
|
||||
### 2023/8/7
|
||||
|
||||
* 添加 本地图库插件 防吞图特性 [@pull/1468](https://github.com/HibiKier/zhenxun_bot/pull/1468)
|
||||
|
||||
### 2023/5/28
|
||||
|
||||
* 修复群聊数据无法初始化
|
||||
|
||||
### 2023/5/24
|
||||
|
||||
* 轮盘结算信息使用图片发送
|
||||
|
||||
### 2023/5/23
|
||||
|
||||
* 修复群聊数据无法初始化
|
||||
* 修复修改图库配置重载后上传图片时提示的图库与配置不符
|
||||
|
||||
### 2023/5/22
|
||||
|
||||
* 群聊中B站订阅所有管理员共享增删操作
|
||||
* 数据库中所有user_qq改名以及user_id和group_id改为字符串
|
||||
* 修改查看词条图片等显示问题
|
||||
|
||||
### 2023/5/16
|
||||
|
||||
* 修复因明日方舟新增“中坚寻访”导致抽卡模拟不可用的问题 [@pull/1418](https://github.com/HibiKier/zhenxun_bot/pull/1418)
|
||||
|
||||
### 2023/4/16
|
||||
|
||||
* 修复开箱更新未登录时没有停止更新
|
||||
* 修复更新色图问题
|
||||
* fix bug [@pull/1368](https://github.com/HibiKier/zhenxun_bot/pull/1368)
|
||||
* `BilibiliSub`的部分字段改为字符串
|
||||
|
||||
### 2023/4/5
|
||||
|
||||
* 词条正则回答中允许使用$1.$2..来获取()捕获组
|
||||
|
||||
### 2023/4/3
|
||||
|
||||
* 修复帮助命令`-super`无效
|
||||
|
||||
### 2023/4/1
|
||||
|
||||
* 修复开箱偶尔出现`未抽取到任何皮肤`
|
||||
* 修改优化开箱显示图片
|
||||
|
||||
### 2023/3/28
|
||||
|
||||
* 补全注释`SCRIPT`中的sql语句
|
||||
* 罕见物品更新时会收录所有包含该物品的箱子,可以通过`更新皮肤ALL1 -S`强制更新所有罕见物品所属箱子
|
||||
|
||||
### 2023/3/27
|
||||
|
||||
* 优化开箱更新
|
||||
|
||||
### 2023/3/25
|
||||
|
||||
* 删除BUFF_SKIN表约束,新增`skin_id`字段
|
||||
* 开箱新增更新指定刀具皮肤命令(某些箱子金色无法通过api获取)
|
||||
* 修复词条At时bug与模糊查询时无法替换占位符问题
|
||||
|
||||
### 2023/3/20
|
||||
|
||||
* 修复BuildImage类text居中类型bug [@pull/1301](https://github.com/HibiKier/zhenxun_bot/pull/1317)
|
||||
* 修复原神今日素材有时发不出图片的问题 [@pull/1301](https://github.com/HibiKier/zhenxun_bot/pull/1317)
|
||||
* 修复首次签到时使用道具后签到报错
|
||||
* 修复词条添加错误
|
||||
|
||||
### 2023/3/19
|
||||
|
||||
* 优化代码
|
||||
* 查看武器箱及皮肤添加更新次数
|
||||
* 修复添加群认证会检测群聊是否存在
|
||||
* 修复色图r连发时未检测当前会话是否为群聊
|
||||
|
||||
### 2023/3/18
|
||||
|
||||
* 修复色图重复发送相同图片
|
||||
* 修复签到好感度进度条错误
|
||||
|
||||
### 2023/3/12 \[v0.1.6.7]
|
||||
|
||||
* 新增`更新武器箱ALL`命令来更新所有武器箱
|
||||
* 新增`查看武器箱`命令
|
||||
* 色图bug修复、增加指令 [@pull/1301](https://github.com/HibiKier/zhenxun_bot/pull/1301)
|
||||
|
||||
### 2023/3/9
|
||||
|
||||
* 更正sql语句 [@pull/1302](https://github.com/HibiKier/zhenxun_bot/pull/1302)
|
||||
* 修改签到卡片中签到增加好感度显示错误 [@pull/1299](https://github.com/HibiKier/zhenxun_bot/pull/1299)
|
||||
|
||||
### 2023/3/5
|
||||
|
||||
* 更新开箱会记录箱子数据以及开箱时箱子价格加入花费
|
||||
* 修复开箱BUG
|
||||
|
||||
### 2023/3/4
|
||||
|
||||
* 重写翻译,使用百度翻译API
|
||||
* 新增开箱日志以及自动更新武器箱
|
||||
|
||||
### 2023/3/2
|
||||
|
||||
* 修复config.yaml中把False也当成None的问题 [@pull/1288](https://github.com/HibiKier/zhenxun_bot/pull/1288)
|
||||
* 删除道具表无用字段(props) [@pull/1287](https://github.com/HibiKier/zhenxun_bot/pull/1287)
|
||||
* 修复词云
|
||||
* 修复我的签到签到图片
|
||||
* 更正BuffSkin添加语句
|
||||
* 修复词条单图片/表情/at无法添加
|
||||
|
||||
### 2022/3/1
|
||||
|
||||
* 重写开箱更新箱子,允许更新目前所有箱子的皮肤
|
||||
* 修复消息统计
|
||||
|
||||
### 2023/2/28
|
||||
|
||||
* 把Config的type字段默认类型由str改为None [@pull/1283](https://github.com/HibiKier/zhenxun_bot/pull/1283)
|
||||
* 修复同意群聊请求以及添加群认证 更新变成查询的问题 [@pull/1282](https://github.com/HibiKier/zhenxun_bot/pull/1282)
|
||||
|
||||
### 2023/2/26
|
||||
|
||||
* Config提供`type`字段确定配置项类型
|
||||
* 重写开箱功能
|
||||
|
||||
### 2023/2/25
|
||||
|
||||
* 修复ys查询,尘歌壶背景尺寸与内容不匹配的问题 [@pull/1270](https://github.com/HibiKier/zhenxun_bot/pull/1275)
|
||||
* 更换cos url [@pull/1270](https://github.com/HibiKier/zhenxun_bot/pull/1274)
|
||||
|
||||
### 2023/2/20
|
||||
|
||||
* chat_history部分字段调整为可null [@pull/1270](https://github.com/HibiKier/zhenxun_bot/pull/1270)
|
||||
|
||||
### 2023/2/19
|
||||
|
||||
* 修正了`重载插件`的帮助提示
|
||||
* 修改BUG
|
||||
|
||||
### 2023/2/18
|
||||
|
||||
* 数据库舍弃`gino`使用`tortoise`
|
||||
* 昵称提供命令`全局昵称设置`
|
||||
* `manager_group`群管理操作中`退群`,`修改群权限`,`添加/删除群白名单`,`添加/删除群认证`在群聊中使用命令时且未指定群聊时,默认指定当前群聊
|
||||
* 修复插件帮助命令不生效的问题 [@pull/1263](https://github.com/HibiKier/zhenxun_bot/pull/1263)
|
||||
* 解决开红包经常误触的问题,有红包和未领取的时候才会触发“开”命令 [@pull/1257](https://github.com/HibiKier/zhenxun_bot/pull/1257)
|
||||
* 细节优化,原神今日素材重写 [@pull/1258](https://github.com/HibiKier/zhenxun_bot/pull/1258)
|
||||
|
||||
### 2023/1/31
|
||||
|
||||
* 修复B站转发卡片BUG [@pull/1249](https://github.com/HibiKier/zhenxun_bot/pull/1249)
|
||||
|
||||
### 2023/1/27
|
||||
|
||||
* 替换pixiv反向代理地址 [@pull/1244](https://github.com/HibiKier/zhenxun_bot/pull/1244)
|
||||
|
||||
### 2022/12/31
|
||||
|
||||
* 修复epic报错,优化简介 [@pull/1226](https://github.com/HibiKier/zhenxun_bot/pull/1226)
|
||||
* 修复词条在某些回答下出错
|
||||
* 原神黄历改为PIL
|
||||
* 允许真寻自身触发命令,提供配置项 `self_message:STATUS`
|
||||
|
||||
### 2022/12/27 \[v0.1.6.6]
|
||||
|
||||
* 添加权限检查依赖注入
|
||||
|
||||
### 2022/12/26
|
||||
|
||||
* 优化`gamedraw`插件
|
||||
* 提供全局被动控制
|
||||
* 群被动状态改为图片
|
||||
* 修复epic获取到的简介不是中文的bug [@pull/1221](https://github.com/HibiKier/zhenxun_bot/pull/1221)
|
||||
|
||||
## 2022/12/24
|
||||
|
||||
* 修复群管理员权限检测会阻挡超级用户权限
|
||||
|
||||
### 2022/12/23
|
||||
|
||||
* 优化`管理员帮助`,`超级用户帮助`图片
|
||||
* 重新移植`gamedraw`
|
||||
* 修复pil帮助私聊时无法生成
|
||||
|
||||
### 2022/12/17
|
||||
|
||||
* 修复查看插件仓库当已安装插件版本不一致时出错
|
||||
|
||||
### 2022/12/15
|
||||
|
||||
* 修复自定义群欢迎消息无法使用
|
||||
|
||||
### 2022/12/13
|
||||
|
||||
* 修复.unban
|
||||
|
||||
### 2022/12/12
|
||||
|
||||
* 修改HTML帮助禁用提示文本错误
|
||||
* 修复HTML帮助私聊无法生成
|
||||
|
||||
### 2022/12/11
|
||||
|
||||
* 词条问题支持真寻的昵称开头与at真寻开头并优化回复
|
||||
* 帮助新增HTML生成(新布局),添加配置`TYPE`切换
|
||||
* 更正私聊时功能管理回复错误
|
||||
* 修复加入新群聊时初始化功能开关错误
|
||||
* 添加单例注解
|
||||
* 添加统计表
|
||||
|
||||
### 2022/12/10
|
||||
|
||||
* 重写帮助,删除 `详细帮助` 命令
|
||||
|
||||
### 2022/12/4
|
||||
|
||||
* 优化管理代码
|
||||
|
||||
### 2022/11/28
|
||||
|
||||
* 修复web_ui群组无法获取
|
||||
* 修复web_ui修改插件数据时cmd格式错误
|
||||
|
||||
### 2022/11/28
|
||||
|
||||
* :bug: Fix a bug in open_cases to get vanilla knives' prices [@pull/1188](https://github.com/HibiKier/zhenxun_bot/pull/1188)
|
||||
|
||||
### 2022/11/24
|
||||
|
||||
* 修复管理员插件加载路径错误
|
||||
|
||||
### 2022/11/23
|
||||
|
||||
* 修复webui插件无法获取修改
|
||||
|
||||
### 2022/11/22
|
||||
|
||||
* fix switch_rule [@pull/1185](https://github.com/HibiKier/zhenxun_bot/pull/1185)
|
||||
|
||||
### 2022/11/21 \[v0.1.6.5]
|
||||
|
||||
* 优化manager, hook代码
|
||||
* 修复pid搜图 [@pull/1180](https://github.com/HibiKier/zhenxun_bot/pull/1180)
|
||||
|
||||
### 2022/11/19
|
||||
|
||||
* 修改优化帮助图片生成逻辑
|
||||
|
||||
### 2022/11/18
|
||||
|
||||
* poetry添加适配器依赖,更新支持py3.10 [@pull/1176](https://github.com/HibiKier/zhenxun_bot/pull/1176)
|
||||
|
||||
### 2022/11/13
|
||||
|
||||
* 更新天气api
|
||||
* 使用道具可以附带额外信息供函数使用
|
||||
* 限制帮助图片最小宽度
|
||||
|
||||
### 2022/11/12
|
||||
|
||||
* 更新yiqing插件数据显示 [@pull/1168](https://github.com/HibiKier/zhenxun_bot/pull/1168)
|
||||
|
||||
### 2022/11/11
|
||||
|
||||
* fix: B站直播订阅的相关问题 [@pull/1158](https://github.com/HibiKier/zhenxun_bot/pull/1158)
|
||||
|
||||
### 2022/10/30
|
||||
|
||||
* 商店简介动态行数,根据文字长度自动换行
|
||||
|
||||
### 2022/10/28
|
||||
|
||||
* 为exec指令进行了SELECT语句适配,添加了查看所有表指令 [@pull/1155](https://github.com/HibiKier/zhenxun_bot/pull/1155)
|
||||
* 修复复读 [@pull/1154](https://github.com/HibiKier/zhenxun_bot/pull/1154)
|
||||
|
||||
### 2022/10/23
|
||||
|
||||
* 复读修改回图片下载
|
||||
|
||||
### 2022/10/22
|
||||
|
||||
* 更新依赖注入
|
||||
|
||||
### 2022/10/16 \[v0.1.6.4]
|
||||
|
||||
* 修改商店道具icon可以为空
|
||||
|
||||
### 2022/10/15
|
||||
|
||||
* nonebot2版本更新为rc1
|
||||
* 我的道具改为图片形式
|
||||
* 商品添加图标与是否为被动道具(被动道具无法被主动使用)
|
||||
* 商品添加使用前方法和使用后方法(类似hook),使用方法具体查看文档或签到商品文件中注册的例子
|
||||
* 新增用户使用道具,花费金币(包括插件)及用途记录
|
||||
* 更细致的金币使用依赖注入
|
||||
* 更多的依赖注入(包含图片获取等等..
|
||||
* 修复我的道具仅有被动或主动道具时图片显示错误
|
||||
* 色图插件p站反向代理失效 [@pull/1139](https://github.com/HibiKier/zhenxun_bot/pull/1139)
|
||||
|
||||
### 2022/10/9
|
||||
|
||||
* 修复碧蓝档案角色获取问题,换源 [@pull/1124](https://github.com/HibiKier/zhenxun_bot/pull/1124)
|
||||
|
||||
### 2022/10/7
|
||||
|
||||
* 修复 B 站请求返回 -401 错误 [@pull/1119](https://github.com/HibiKier/zhenxun_bot/pull/1119)
|
||||
* 关闭功能与被动时不再区分大小写,同名时仅被动关闭操作生效
|
||||
|
||||
### 2022/9/30
|
||||
|
||||
* 修改重置开箱的使用权限 [@pull/1118](https://github.com/HibiKier/zhenxun_bot/pull/1118)
|
||||
|
||||
### 2022/9/27
|
||||
|
||||
* 更新b站转发解析 [@pull/1117](https://github.com/HibiKier/zhenxun_bot/pull/1117)
|
||||
|
||||
### 2022/9/24
|
||||
|
||||
* 修复b站订阅 [@pull/1112](https://github.com/HibiKier/zhenxun_bot/pull/1112)
|
||||
* fix: 重载赛马娘卡池失败 [@pull/1114](https://github.com/HibiKier/zhenxun_bot/pull/1114)
|
||||
|
||||
### 2022/9/19
|
||||
|
||||
* 更换bilibili_sub获取用户昵称用的API&尝试修了一下get_video() [@pull/1097](https://github.com/HibiKier/zhenxun_bot/pull/1097)
|
||||
* 修复csgo每日开箱可以多开一次
|
||||
|
||||
### 2022/9/18
|
||||
|
||||
* 修复 bilireq 版本过低导致 B 站视频解析错误 [@pull/1090](https://github.com/HibiKier/zhenxun_bot/pull/1096)
|
||||
|
||||
### 2022/9/16
|
||||
|
||||
* fix: bilibili_sub, azur_draw_card [@pull/1090](https://github.com/HibiKier/zhenxun_bot/pull/1090)
|
||||
* 修复原神资源查询查询完毕后图片存储错误
|
||||
* b站订阅发送 与 b站订阅 使用相同开关,即:关闭b站订阅
|
||||
|
||||
### 2022/9/10
|
||||
|
||||
* 自定义群欢迎消息参数不完全时提示报错
|
||||
* 修改bt插件的url地址 [@pull/1067](https://github.com/HibiKier/zhenxun_bot/pull/1067)
|
||||
|
||||
### 2022/9/8
|
||||
|
||||
* 添加插件数据初始化判断
|
||||
|
||||
### 2022/9/4
|
||||
|
||||
* 旧词条提供图片迁移(需要重新获取old_model文件,并将数据库中user_qq为0的数据删除)
|
||||
|
||||
### 2022/9/3
|
||||
|
||||
* 原神玩家查询增加须弥地区 [@pull/1053](https://github.com/HibiKier/zhenxun_bot/pull/1053)
|
||||
* av号覆盖全面,且修复av号链接 [@pull/1033](https://github.com/HibiKier/zhenxun_bot/pull/1033)
|
||||
* 修复词条含有CQ回答的模糊匹配无法被解析
|
||||
* 禁言检测图片在内存中获取图片hash
|
||||
* B站订阅在群里中任意群管理员可以统一管理(原来为管理员1无法删除管理员2的订阅)
|
||||
* 修复原神资源查询地图api数据变更导致更新的地图不完全
|
||||
|
||||
### 2022/8/27
|
||||
|
||||
* 修复签到积分双倍后,日志记录获得积分变4倍问题 [@pull/1044](https://github.com/HibiKier/zhenxun_bot/pull/1044)
|
||||
|
||||
### 2022/8/26
|
||||
|
||||
* 修复群管理员无法添加词条
|
||||
* 修复词条关键词"问"前空格问题
|
||||
|
||||
### 2022/8/23
|
||||
|
||||
* 修了下模糊匹配 issue#1026 [@pull/1026](https://github.com/HibiKier/zhenxun_bot/pull/1026)
|
||||
|
||||
### 2022/8/22
|
||||
|
||||
* 修复首次安装时词条旧表出错(因为根本就没有这张表!)
|
||||
* 取消配置替换定时任务,统一存储
|
||||
* 对米游社cookie进行判断,整合米游社签到信息 [@pull/1014](https://github.com/HibiKier/zhenxun_bot/pull/1014)
|
||||
* 修正尘歌壶和质变仪图片获取地址 [@pull/1010](https://github.com/HibiKier/zhenxun_bot/pull/1010)
|
||||
* 修复词库问答 **很多** 问题[@pull/1012](https://github.com/HibiKier/zhenxun_bot/pull/1012)
|
||||
|
||||
### 2022/8/21 \[v0.1.6.3]
|
||||
|
||||
* 重构群词条,改为词库Plus,增加 精准|模糊|正则 问题匹配,问题与回答均支持at,image,face,超级用户额外提供 全局|私聊 词库设置,数据迁移目前只提供了问题和回答都是纯文本的词条
|
||||
* 修复b站转发解析av号无法解析
|
||||
* B站订阅直播订阅支持短号
|
||||
* 开箱提供重置开箱命令,重置今日所有开箱数据(重置次数,并不会删除今日已开箱记录)
|
||||
* 提供全局字典GDict,通过from utils.utils import GDict导入
|
||||
* 适配omega 13w张图的数据结构表(建议删表重导)
|
||||
* 除首次启动外将配置替换加入单次定时任务,加快启动速度
|
||||
* fix: WordBank.check() [@pull/1008](https://github.com/HibiKier/zhenxun_bot/pull/1008)
|
||||
* 改进插件 `我有一个朋友`,避免触发过于频繁 [@pull/1001](https://github.com/HibiKier/zhenxun_bot/pull/1001)
|
||||
* 原神便笺新增洞天宝钱和参量质变仪提示 [@pull/1005](https://github.com/HibiKier/zhenxun_bot/pull/1005)
|
||||
* 新增米游社签到功能,自动领取(白嫖)米游币 [@pull/991](https://github.com/HibiKier/zhenxun_bot/pull/991)
|
||||
|
||||
### 2022/8/14
|
||||
|
||||
* 修复epic未获取到时间时出错
|
||||
* 修复订阅主播时动态获取的id是直播间id
|
||||
|
||||
### 2022/8/8
|
||||
|
||||
* 修复赛马娘重载卡池失败的问题 [@pull/969](https://github.com/HibiKier/zhenxun_bot/pull/969)
|
||||
|
||||
### 2022/8/3
|
||||
|
||||
* 修复 bili动态链接在投稿视频时URL和分割线连在一起 [@pull/951](https://github.com/HibiKier/zhenxun_bot/pull/961)
|
||||
* 更新 Epic 免费游戏商城链接拼接规则 [@pull/957](https://github.com/HibiKier/zhenxun_bot/pull/957)
|
||||
|
||||
### 2022/8/6
|
||||
|
||||
* 修复了原神自动签到返回invalid request的问题,新增查看我的cookie命令 [@pull/971](https://github.com/HibiKier/zhenxun_bot/pull/971) -->
|
||||
|
||||
<br>
|
||||
|
||||
**..... 更多更新信息请查看文档**
|
||||
|
||||
## Todo
|
||||
|
||||
- [x] web 管理
|
||||
|
||||
## **特别感谢**
|
||||
## 🌟 特别感谢
|
||||
|
||||
首席设计师:[酥酥/coldly-ss](https://github.com/coldly-ss)
|
||||
|
||||
## 感谢
|
||||
## 🙏 感谢
|
||||
|
||||
[botuniverse / onebot](https://github.com/botuniverse/onebot) :超棒的机器人协议
|
||||
[Mrs4s / go-cqhttp](https://github.com/Mrs4s/go-cqhttp) :cqhttp 的 golang 实现,轻量、原生跨平台.
|
||||
@ -778,3 +301,58 @@ python bot.py
|
||||
[Kyomotoi / AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus) :一个~~特二刺螈~~(文爱)的适用于任何 bot 的词库
|
||||
[Ailitonia / omega-miya](https://github.com/Ailitonia/omega-miya) :基于 nonebot2 的 qq 机器人
|
||||
[KimigaiiWuyi / GenshinUID](https://github.com/KimigaiiWuyi/GenshinUID) :一个基于 HoshinoBot/NoneBot2 的原神 UID 查询插件
|
||||
|
||||
## 📊 统计与活跃贡献者
|
||||
|
||||
<a href="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats?repo_id=368008334" target="_blank" style="display: block" align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats/thumbnail.png?repo_id=368008334&image_size=auto&color_scheme=dark" width="800" height="auto">
|
||||
<img alt="Performance Stats of HibiKier/zhenxun_bot - Last 28 days" src="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats/thumbnail.png?repo_id=368008334&image_size=auto&color_scheme=light" width="800" height="auto">
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors?repo_id=368008334&limit=30" target="_blank" style="display: block" align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=368008334&limit=30&image_size=auto&color_scheme=dark" width="800" height="auto">
|
||||
<img alt="Active Contributors of HibiKier/zhenxun_bot - Last 28 days" src="https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=368008334&limit=30&image_size=auto&color_scheme=light" width="800" height="auto">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## 👨💻 开发者
|
||||
|
||||
感谢以下开发者对 绪山真寻 Bot 作出的贡献:
|
||||
|
||||
<a href="https://github.com/HibiKier/zhenxun_bot/graphs/contributors" style="display: block" align="center">
|
||||
<img src="https://contrib.rocks/image?repo=HibiKier/zhenxun_bot&max=1000" alt="contributors"/>
|
||||
</a>
|
||||
|
||||
## 📸 WebUI界面展示
|
||||
|
||||
<div style="display: flex; flex-wrap: wrap; justify-content: space-between;">
|
||||
<div style="width: 48%; margin-bottom: 10px;">
|
||||
<img src="./docs_image/webui00.png" alt="webui00" style="width: 100%; height: auto;">
|
||||
</div>
|
||||
<div style="width: 48%; margin-bottom: 10px;">
|
||||
<img src="./docs_image/webui01.png" alt="webui01" style="width: 100%; height: auto;">
|
||||
</div>
|
||||
|
||||
<div style="width: 48%; margin-bottom: 10px;">
|
||||
<img src="./docs_image/webui02.png" alt="webui02" style="width: 100%; height: auto;">
|
||||
</div>
|
||||
<div style="width: 48%; margin-bottom: 10px;">
|
||||
<img src="./docs_image/webui03.png" alt="webui03" style="width: 100%; height: auto;">
|
||||
</div>
|
||||
|
||||
<div style="width: 48%; margin-bottom: 10px;">
|
||||
<img src="./docs_image/webui04.png" alt="webui04" style="width: 100%; height: auto;">
|
||||
</div>
|
||||
<div style="width: 48%; margin-bottom: 10px;">
|
||||
<img src="./docs_image/webui05.png" alt="webui05" style="width: 100%; height: auto;">
|
||||
</div>
|
||||
|
||||
<div style="width: 48%; margin-bottom: 10px;">
|
||||
<img src="./docs_image/webui06.png" alt="webui06" style="width: 100%; height: auto;">
|
||||
</div>
|
||||
<div style="width: 48%; margin-bottom: 10px;">
|
||||
<img src="./docs_image/webui07.png" alt="webui07" style="width: 100%; height: auto;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -44,7 +44,7 @@ body {
|
||||
}
|
||||
|
||||
.main {
|
||||
height: 448px;
|
||||
height: 444px;
|
||||
width: 335px;
|
||||
padding: 0 30px;
|
||||
position: relative;
|
||||
|
||||
@ -2,20 +2,12 @@ from datetime import datetime
|
||||
import uuid
|
||||
|
||||
import nonebot
|
||||
from nonebot import require
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.drivers import Driver
|
||||
from tortoise import Tortoise
|
||||
from tortoise.exceptions import OperationalError
|
||||
import ujson as json
|
||||
|
||||
require("nonebot_plugin_apscheduler")
|
||||
require("nonebot_plugin_alconna")
|
||||
require("nonebot_plugin_session")
|
||||
require("nonebot_plugin_userinfo")
|
||||
require("nonebot_plugin_htmlrender")
|
||||
# require("nonebot_plugin_uninfo")
|
||||
|
||||
from zhenxun.models.bot_connect_log import BotConnectLog
|
||||
from zhenxun.models.bot_console import BotConsole
|
||||
from zhenxun.models.goods_info import GoodsInfo
|
||||
|
||||
@ -387,7 +387,7 @@ class AuthChecker:
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("该群未开启此功能...")
|
||||
if not plugin.status and plugin.block_type == BlockType.GROUP:
|
||||
if plugin.block_type == BlockType.GROUP:
|
||||
"""全局群组禁用"""
|
||||
try:
|
||||
if self.is_send_limit_message(plugin, sid) and not is_poke:
|
||||
@ -410,7 +410,7 @@ class AuthChecker:
|
||||
raise IgnoredException("该插件在群组中已被禁用...")
|
||||
else:
|
||||
sid = user_id
|
||||
if not plugin.status and plugin.block_type == BlockType.PRIVATE:
|
||||
if plugin.block_type == BlockType.PRIVATE:
|
||||
"""全局私聊禁用"""
|
||||
try:
|
||||
if self.is_send_limit_message(plugin, sid) and not is_poke:
|
||||
|
||||
@ -120,7 +120,7 @@ async def _():
|
||||
if module_list := await PluginInfo.all().values("id", "module_path"):
|
||||
module2id = {m["module_path"]: m["id"] for m in module_list}
|
||||
for plugin in get_loaded_plugins():
|
||||
load_plugin.append(plugin.name)
|
||||
load_plugin.append(plugin.module_name)
|
||||
await _handle_setting(plugin, plugin_list, limit_list, task_list)
|
||||
create_list = []
|
||||
update_list = []
|
||||
@ -198,8 +198,8 @@ async def _():
|
||||
10,
|
||||
)
|
||||
await data_migration()
|
||||
await PluginInfo.filter(module__in=load_plugin).update(load_status=True)
|
||||
await PluginInfo.filter(module__not_in=load_plugin).update(load_status=False)
|
||||
await PluginInfo.filter(module_path__in=load_plugin).update(load_status=True)
|
||||
await PluginInfo.filter(module_path__not_in=load_plugin).update(load_status=False)
|
||||
manager.init()
|
||||
if limit_list:
|
||||
for limit in limit_list:
|
||||
|
||||
@ -89,7 +89,7 @@ async def _(bot: Bot):
|
||||
async with aiofiles.open(RESTART_MARK, encoding="utf8") as f:
|
||||
bot_id, user_id = (await f.read()).split()
|
||||
if bot := nonebot.get_bot(bot_id):
|
||||
if target := PlatformUtils.get_target(bot, user_id):
|
||||
if target := PlatformUtils.get_target(user_id=user_id):
|
||||
await MessageUtils.build_message(
|
||||
f"{BotConfig.self_nickname}已成功重启!"
|
||||
).send(target, bot=bot)
|
||||
|
||||
@ -37,8 +37,7 @@ async def _():
|
||||
update_list = []
|
||||
if modules := await TaskInfo.annotate().values_list("module", flat=True):
|
||||
for bot in nonebot.get_bots().values():
|
||||
group_list, _ = await PlatformUtils.get_group_list(bot)
|
||||
group_list = [g for g in group_list if g.channel_id is None]
|
||||
group_list, _ = await PlatformUtils.get_group_list(bot, True)
|
||||
for group in group_list:
|
||||
try:
|
||||
last_message = (
|
||||
|
||||
@ -126,7 +126,9 @@ async def _(session: Uninfo, arparma: Arparma):
|
||||
async def _(session: Uninfo, arparma: Arparma, nickname: str = UserName()):
|
||||
logger.info("查看道具", arparma.header_result, session=session)
|
||||
if image := await ShopManage.my_props(
|
||||
session.user.id, nickname, PlatformUtils.get_platform(session)
|
||||
session.user.id,
|
||||
nickname,
|
||||
PlatformUtils.get_platform(session),
|
||||
):
|
||||
await MessageUtils.build_message(image.pic2bytes()).finish(reply_to=True)
|
||||
return await MessageUtils.build_message("你的道具为空捏...").send(reply_to=True)
|
||||
|
||||
@ -47,7 +47,7 @@ class BroadcastManage:
|
||||
group.group_id,
|
||||
):
|
||||
target = PlatformUtils.get_target(
|
||||
bot, None, group.channel_id or group.group_id
|
||||
group_id=group.group_id, channel_id=group.channel_id
|
||||
)
|
||||
if target:
|
||||
await MessageUtils.build_message(message_list).send(
|
||||
|
||||
@ -13,6 +13,7 @@ from zhenxun.utils.enum import PluginType
|
||||
|
||||
from .api.logs import router as ws_log_routes
|
||||
from .api.logs.log_manager import LOG_STORAGE
|
||||
from .api.menu import router as menu_router
|
||||
from .api.tabs.dashboard import router as dashboard_router
|
||||
from .api.tabs.database import router as database_router
|
||||
from .api.tabs.main import router as main_router
|
||||
@ -80,6 +81,7 @@ BaseApiRouter.include_router(manage_router)
|
||||
BaseApiRouter.include_router(database_router)
|
||||
BaseApiRouter.include_router(plugin_router)
|
||||
BaseApiRouter.include_router(system_router)
|
||||
BaseApiRouter.include_router(menu_router)
|
||||
|
||||
|
||||
WsApiRouter = APIRouter(prefix="/zhenxun/socket")
|
||||
@ -112,6 +114,6 @@ async def _():
|
||||
app.include_router(BaseApiRouter)
|
||||
app.include_router(WsApiRouter)
|
||||
await init_public(app)
|
||||
logger.info("<g>API启动成功</g>", "Web UI")
|
||||
logger.info("<g>API启动成功</g>", "WebUi")
|
||||
except Exception as e:
|
||||
logger.error("<g>API启动失败</g>", "Web UI", e=e)
|
||||
logger.error("<g>API启动失败</g>", "WebUi", e=e)
|
||||
|
||||
@ -1 +1,2 @@
|
||||
from .tabs import * # noqa: F403
|
||||
from .menu import * # noqa: F403f
|
||||
from .tabs import * # noqa: F403f
|
||||
|
||||
26
zhenxun/builtin_plugins/web_ui/api/menu/__init__.py
Normal file
26
zhenxun/builtin_plugins/web_ui/api/menu/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ...base_model import Result
|
||||
from ...utils import authentication
|
||||
from .data_source import menu_manage
|
||||
from .model import MenuData
|
||||
|
||||
router = APIRouter(prefix="/menu")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/get_menus",
|
||||
dependencies=[authentication()],
|
||||
response_model=Result[list[MenuData]],
|
||||
response_class=JSONResponse,
|
||||
deprecated="获取菜单列表", # type: ignore
|
||||
)
|
||||
async def _() -> Result[list[MenuData]]:
|
||||
try:
|
||||
return Result.ok(menu_manage.get_menus(), "拿到菜单了哦!")
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_menus 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
64
zhenxun/builtin_plugins/web_ui/api/menu/data_source.py
Normal file
64
zhenxun/builtin_plugins/web_ui/api/menu/data_source.py
Normal file
@ -0,0 +1,64 @@
|
||||
import ujson as json
|
||||
|
||||
from zhenxun.configs.path_config import DATA_PATH
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from .model import MenuData, MenuItem
|
||||
|
||||
|
||||
class MenuManage:
|
||||
def __init__(self) -> None:
|
||||
self.file = DATA_PATH / "web_ui" / "menu.json"
|
||||
self.menu = []
|
||||
if self.file.exists():
|
||||
try:
|
||||
self.menu = json.load(self.file.open(encoding="utf8"))
|
||||
except Exception as e:
|
||||
logger.warning("菜单文件损坏,已重新生成...", "WebUi", e=e)
|
||||
if not self.menu:
|
||||
self.menu = [
|
||||
MenuItem(
|
||||
name="仪表盘",
|
||||
module="dashboard",
|
||||
router="/dashboard",
|
||||
icon="dashboard",
|
||||
default=True,
|
||||
),
|
||||
MenuItem(
|
||||
name="真寻控制台",
|
||||
module="command",
|
||||
router="/command",
|
||||
icon="command",
|
||||
),
|
||||
MenuItem(
|
||||
name="插件列表", module="plugin", router="/plugin", icon="plugin"
|
||||
),
|
||||
MenuItem(
|
||||
name="插件商店", module="store", router="/store", icon="store"
|
||||
),
|
||||
MenuItem(
|
||||
name="好友/群组", module="manage", router="/manage", icon="user"
|
||||
),
|
||||
MenuItem(
|
||||
name="数据库管理",
|
||||
module="database",
|
||||
router="/database",
|
||||
icon="database",
|
||||
),
|
||||
MenuItem(
|
||||
name="系统信息", module="system", router="/system", icon="system"
|
||||
),
|
||||
]
|
||||
self.save()
|
||||
|
||||
def get_menus(self):
|
||||
return MenuData(menus=self.menu)
|
||||
|
||||
def save(self):
|
||||
self.file.parent.mkdir(parents=True, exist_ok=True)
|
||||
temp = [menu.dict() for menu in self.menu]
|
||||
with self.file.open("w", encoding="utf8") as f:
|
||||
json.dump(temp, f, ensure_ascii=False, indent=4)
|
||||
|
||||
|
||||
menu_manage = MenuManage()
|
||||
21
zhenxun/builtin_plugins/web_ui/api/menu/model.py
Normal file
21
zhenxun/builtin_plugins/web_ui/api/menu/model.py
Normal file
@ -0,0 +1,21 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class MenuItem(BaseModel):
|
||||
module: str
|
||||
"""模块名称"""
|
||||
name: str
|
||||
"""菜单名称"""
|
||||
router: str
|
||||
"""路由"""
|
||||
icon: str
|
||||
"""图标"""
|
||||
default: bool = False
|
||||
"""默认选中"""
|
||||
|
||||
|
||||
class MenuData(BaseModel):
|
||||
bot_type: str = "zhenxun"
|
||||
"""bot类型"""
|
||||
menus: list[MenuItem]
|
||||
"""菜单列表"""
|
||||
@ -1,20 +1,14 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import JSONResponse
|
||||
import nonebot
|
||||
from nonebot import require
|
||||
from nonebot.config import Config
|
||||
from tortoise.expressions import RawSQL
|
||||
from tortoise.functions import Count
|
||||
|
||||
from zhenxun.models.bot_connect_log import BotConnectLog
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
from zhenxun.models.statistics import Statistics
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ....base_model import BaseResultModel, QueryModel, Result
|
||||
from ....utils import authentication
|
||||
from .data_source import BotManage
|
||||
from .data_source import ApiDataSource
|
||||
from .model import AllChatAndCallCount, BotInfo, ChatCallMonthCount, QueryChatCallCount
|
||||
|
||||
require("plugin_store")
|
||||
@ -33,8 +27,9 @@ driver = nonebot.get_driver()
|
||||
)
|
||||
async def _() -> Result[list[BotInfo]]:
|
||||
try:
|
||||
return Result.ok(await BotManage.get_bot_list(), "拿到信息啦!")
|
||||
return Result.ok(await ApiDataSource.get_bot_list(), "拿到信息啦!")
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_bot_list 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@ -46,29 +41,13 @@ async def _() -> Result[list[BotInfo]]:
|
||||
description="获取聊天/调用记录的全部和今日数量",
|
||||
)
|
||||
async def _(bot_id: str | None = None) -> Result[QueryChatCallCount]:
|
||||
now = datetime.now()
|
||||
query = ChatHistory
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
chat_all_count = await query.annotate().count()
|
||||
chat_day_count = await query.filter(
|
||||
create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
query = Statistics
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
call_all_count = await query.annotate().count()
|
||||
call_day_count = await query.filter(
|
||||
create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
return Result.ok(
|
||||
QueryChatCallCount(
|
||||
chat_num=chat_all_count,
|
||||
chat_day=chat_day_count,
|
||||
call_num=call_all_count,
|
||||
call_day=call_day_count,
|
||||
try:
|
||||
return Result.ok(
|
||||
await ApiDataSource.get_chat_and_call_count(bot_id), "拿到信息啦!"
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_chat_and_call_count 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -79,41 +58,15 @@ async def _(bot_id: str | None = None) -> Result[QueryChatCallCount]:
|
||||
description="获取聊天/调用记录的全部数据次数",
|
||||
)
|
||||
async def _(bot_id: str | None = None) -> Result[AllChatAndCallCount]:
|
||||
now = datetime.now()
|
||||
query = ChatHistory
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
chat_week_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=7, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
chat_month_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=30, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
chat_year_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=365, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
query = Statistics
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
call_week_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=7, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
call_month_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=30, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
call_year_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=365, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
return Result.ok(
|
||||
AllChatAndCallCount(
|
||||
chat_week=chat_week_count,
|
||||
chat_month=chat_month_count,
|
||||
chat_year=chat_year_count,
|
||||
call_week=call_week_count,
|
||||
call_month=call_month_count,
|
||||
call_year=call_year_count,
|
||||
try:
|
||||
return Result.ok(
|
||||
await ApiDataSource.get_all_chat_and_call_count(bot_id), "拿到信息啦!"
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"{router.prefix}/get_all_chat_and_call_count 调用错误", "WebUi", e=e
|
||||
)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -124,48 +77,13 @@ async def _(bot_id: str | None = None) -> Result[AllChatAndCallCount]:
|
||||
deprecated="获取聊天/调用记录的一个月数量", # type: ignore
|
||||
)
|
||||
async def _(bot_id: str | None = None) -> Result[ChatCallMonthCount]:
|
||||
now = datetime.now()
|
||||
filter_date = now - timedelta(days=30, hours=now.hour, minutes=now.minute)
|
||||
chat_query = ChatHistory
|
||||
call_query = Statistics
|
||||
if bot_id:
|
||||
chat_query = chat_query.filter(bot_id=bot_id)
|
||||
call_query = call_query.filter(bot_id=bot_id)
|
||||
chat_date_list = (
|
||||
await chat_query.filter(create_time__gte=filter_date)
|
||||
.annotate(date=RawSQL("DATE(create_time)"), count=Count("id"))
|
||||
.group_by("date")
|
||||
.values("date", "count")
|
||||
)
|
||||
call_date_list = (
|
||||
await call_query.filter(create_time__gte=filter_date)
|
||||
.annotate(date=RawSQL("DATE(create_time)"), count=Count("id"))
|
||||
.group_by("date")
|
||||
.values("date", "count")
|
||||
)
|
||||
date_list = []
|
||||
chat_count_list = []
|
||||
call_count_list = []
|
||||
chat_date2cnt = {str(date["date"]): date["count"] for date in chat_date_list}
|
||||
call_date2cnt = {str(date["date"]): date["count"] for date in call_date_list}
|
||||
date = now.date()
|
||||
for _ in range(30):
|
||||
if str(date) in chat_date2cnt:
|
||||
chat_count_list.append(chat_date2cnt[str(date)])
|
||||
else:
|
||||
chat_count_list.append(0)
|
||||
if str(date) in call_date2cnt:
|
||||
call_count_list.append(call_date2cnt[str(date)])
|
||||
else:
|
||||
call_count_list.append(0)
|
||||
date_list.append(str(date)[5:])
|
||||
date -= timedelta(days=1)
|
||||
chat_count_list.reverse()
|
||||
call_count_list.reverse()
|
||||
date_list.reverse()
|
||||
return Result.ok(
|
||||
ChatCallMonthCount(chat=chat_count_list, call=call_count_list, date=date_list)
|
||||
)
|
||||
try:
|
||||
return Result.ok(
|
||||
await ApiDataSource.get_chat_and_call_month(bot_id), "拿到信息啦!"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_chat_and_call_month 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -176,18 +94,11 @@ async def _(bot_id: str | None = None) -> Result[ChatCallMonthCount]:
|
||||
deprecated="获取Bot连接记录", # type: ignore
|
||||
)
|
||||
async def _(query: QueryModel) -> Result[BaseResultModel]:
|
||||
total = await BotConnectLog.all().count()
|
||||
if total % query.size:
|
||||
total += 1
|
||||
data = (
|
||||
await BotConnectLog.all()
|
||||
.order_by("-id")
|
||||
.offset((query.index - 1) * query.size)
|
||||
.limit(query.size)
|
||||
)
|
||||
for v in data:
|
||||
v.connect_time = v.connect_time.replace(tzinfo=None).replace(microsecond=0)
|
||||
return Result.ok(BaseResultModel(total=total, data=data))
|
||||
try:
|
||||
return Result.ok(await ApiDataSource.get_connect_log(query), "拿到信息啦!")
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_connect_log 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
|
||||
@ -4,13 +4,17 @@ import time
|
||||
import nonebot
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.drivers import Driver
|
||||
from tortoise.expressions import RawSQL
|
||||
from tortoise.functions import Count
|
||||
|
||||
from zhenxun.models.bot_connect_log import BotConnectLog
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
from zhenxun.models.statistics import Statistics
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
|
||||
from ....base_model import BaseResultModel, QueryModel
|
||||
from ..main.data_source import bot_live
|
||||
from .model import BotInfo
|
||||
from .model import AllChatAndCallCount, BotInfo, ChatCallMonthCount, QueryChatCallCount
|
||||
|
||||
driver: Driver = nonebot.get_driver()
|
||||
|
||||
@ -24,7 +28,7 @@ async def _():
|
||||
CONNECT_TIME = int(time.time())
|
||||
|
||||
|
||||
class BotManage:
|
||||
class ApiDataSource:
|
||||
@classmethod
|
||||
async def __build_bot_info(cls, bot: Bot) -> BotInfo:
|
||||
"""构建Bot信息
|
||||
@ -47,8 +51,7 @@ class BotManage:
|
||||
bot_info = BotInfo(
|
||||
self_id=bot.self_id, nickname=nickname, ava_url=ava_url, platform=platform
|
||||
)
|
||||
group_list, _ = await PlatformUtils.get_group_list(bot)
|
||||
group_list = [g for g in group_list if g.channel_id is None]
|
||||
group_list, _ = await PlatformUtils.get_group_list(bot, True)
|
||||
friend_list, _ = await PlatformUtils.get_friend_list(bot)
|
||||
bot_info.group_count = len(group_list)
|
||||
bot_info.friend_count = len(friend_list)
|
||||
@ -77,3 +80,161 @@ class BotManage:
|
||||
for _, bot in nonebot.get_bots().items():
|
||||
bot_list.append(await cls.__build_bot_info(bot))
|
||||
return bot_list
|
||||
|
||||
@classmethod
|
||||
async def get_chat_and_call_count(cls, bot_id: str | None) -> QueryChatCallCount:
|
||||
"""获取今日聊天和调用次数
|
||||
|
||||
参数:
|
||||
bot_id: bot id
|
||||
|
||||
返回:
|
||||
QueryChatCallCount: 数据内容
|
||||
"""
|
||||
now = datetime.now()
|
||||
query = ChatHistory
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
chat_all_count = await query.annotate().count()
|
||||
chat_day_count = await query.filter(
|
||||
create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
query = Statistics
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
call_all_count = await query.annotate().count()
|
||||
call_day_count = await query.filter(
|
||||
create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
return QueryChatCallCount(
|
||||
chat_num=chat_all_count,
|
||||
chat_day=chat_day_count,
|
||||
call_num=call_all_count,
|
||||
call_day=call_day_count,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_all_chat_and_call_count(
|
||||
cls, bot_id: str | None
|
||||
) -> AllChatAndCallCount:
|
||||
"""获取全部聊天和调用记录
|
||||
|
||||
参数:
|
||||
bot_id: bot id
|
||||
|
||||
返回:
|
||||
AllChatAndCallCount: 数据内容
|
||||
"""
|
||||
now = datetime.now()
|
||||
query = ChatHistory
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
chat_week_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=7, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
chat_month_count = await query.filter(
|
||||
create_time__gte=now
|
||||
- timedelta(days=30, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
chat_year_count = await query.filter(
|
||||
create_time__gte=now
|
||||
- timedelta(days=365, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
query = Statistics
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
call_week_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=7, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
call_month_count = await query.filter(
|
||||
create_time__gte=now
|
||||
- timedelta(days=30, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
call_year_count = await query.filter(
|
||||
create_time__gte=now
|
||||
- timedelta(days=365, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
return AllChatAndCallCount(
|
||||
chat_week=chat_week_count,
|
||||
chat_month=chat_month_count,
|
||||
chat_year=chat_year_count,
|
||||
call_week=call_week_count,
|
||||
call_month=call_month_count,
|
||||
call_year=call_year_count,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_chat_and_call_month(cls, bot_id: str | None) -> ChatCallMonthCount:
|
||||
"""获取一个月内的调用/消息记录次数,并根据日期对数据填充0
|
||||
|
||||
参数:
|
||||
bot_id: bot id
|
||||
|
||||
返回:
|
||||
ChatCallMonthCount: 数据内容
|
||||
"""
|
||||
now = datetime.now()
|
||||
filter_date = now - timedelta(days=30, hours=now.hour, minutes=now.minute)
|
||||
chat_query = ChatHistory
|
||||
call_query = Statistics
|
||||
if bot_id:
|
||||
chat_query = chat_query.filter(bot_id=bot_id)
|
||||
call_query = call_query.filter(bot_id=bot_id)
|
||||
chat_date_list = (
|
||||
await chat_query.filter(create_time__gte=filter_date)
|
||||
.annotate(date=RawSQL("DATE(create_time)"), count=Count("id"))
|
||||
.group_by("date")
|
||||
.values("date", "count")
|
||||
)
|
||||
call_date_list = (
|
||||
await call_query.filter(create_time__gte=filter_date)
|
||||
.annotate(date=RawSQL("DATE(create_time)"), count=Count("id"))
|
||||
.group_by("date")
|
||||
.values("date", "count")
|
||||
)
|
||||
date_list = []
|
||||
chat_count_list = []
|
||||
call_count_list = []
|
||||
chat_date2cnt = {str(date["date"]): date["count"] for date in chat_date_list}
|
||||
call_date2cnt = {str(date["date"]): date["count"] for date in call_date_list}
|
||||
date = now.date()
|
||||
for _ in range(30):
|
||||
if str(date) in chat_date2cnt:
|
||||
chat_count_list.append(chat_date2cnt[str(date)])
|
||||
else:
|
||||
chat_count_list.append(0)
|
||||
if str(date) in call_date2cnt:
|
||||
call_count_list.append(call_date2cnt[str(date)])
|
||||
else:
|
||||
call_count_list.append(0)
|
||||
date_list.append(str(date)[5:])
|
||||
date -= timedelta(days=1)
|
||||
chat_count_list.reverse()
|
||||
call_count_list.reverse()
|
||||
date_list.reverse()
|
||||
return ChatCallMonthCount(
|
||||
chat=chat_count_list, call=call_count_list, date=date_list
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_connect_log(cls, query: QueryModel) -> BaseResultModel:
|
||||
"""获取bot连接日志
|
||||
|
||||
参数:
|
||||
query: 查询模型
|
||||
|
||||
返回:
|
||||
BaseResultModel: 数据内容
|
||||
"""
|
||||
total = await BotConnectLog.all().count()
|
||||
if total % query.size:
|
||||
total += 1
|
||||
data = (
|
||||
await BotConnectLog.all()
|
||||
.order_by("-id")
|
||||
.offset((query.index - 1) * query.size)
|
||||
.limit(query.size)
|
||||
)
|
||||
for v in data:
|
||||
v.connect_time = v.connect_time.replace(tzinfo=None).replace(microsecond=0)
|
||||
return BaseResultModel(total=total, data=data)
|
||||
|
||||
@ -3,14 +3,15 @@ from fastapi.responses import JSONResponse
|
||||
import nonebot
|
||||
from nonebot.drivers import Driver
|
||||
from tortoise import Tortoise
|
||||
from tortoise.exceptions import OperationalError
|
||||
|
||||
from zhenxun.configs.config import BotConfig
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ....base_model import BaseResultModel, QueryModel, Result
|
||||
from ....utils import authentication
|
||||
from .data_source import ApiDataSource, type2sql
|
||||
from .models.model import Column, SqlModel, SqlText
|
||||
from .models.sql_log import SqlLog
|
||||
|
||||
@ -20,52 +21,6 @@ router = APIRouter(prefix="/database")
|
||||
driver: Driver = nonebot.get_driver()
|
||||
|
||||
|
||||
SQL_DICT = {}
|
||||
|
||||
|
||||
SELECT_TABLE_MYSQL_SQL = """
|
||||
SELECT table_name AS name, table_comment AS `desc`
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE();
|
||||
"""
|
||||
|
||||
SELECT_TABLE_SQLITE_SQL = """
|
||||
SELECT name FROM sqlite_master WHERE type='table';
|
||||
"""
|
||||
|
||||
SELECT_TABLE_PSQL_SQL = """
|
||||
select a.tablename as name,d.description as desc from pg_tables a
|
||||
left join pg_class c on relname=tablename
|
||||
left join pg_description d on oid=objoid and objsubid=0 where a.schemaname='public'
|
||||
"""
|
||||
|
||||
SELECT_TABLE_COLUMN_PSQL_SQL = """
|
||||
SELECT column_name, data_type, character_maximum_length as max_length, is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = '{}';
|
||||
"""
|
||||
|
||||
SELECT_TABLE_COLUMN_MYSQL_SQL = """
|
||||
SHOW COLUMNS FROM {};
|
||||
"""
|
||||
|
||||
SELECT_TABLE_COLUMN_SQLITE_SQL = """
|
||||
PRAGMA table_info({});
|
||||
"""
|
||||
|
||||
type2sql = {
|
||||
"mysql": SELECT_TABLE_MYSQL_SQL,
|
||||
"sqlite": SELECT_TABLE_SQLITE_SQL,
|
||||
"postgres": SELECT_TABLE_PSQL_SQL,
|
||||
}
|
||||
|
||||
type2sql_column = {
|
||||
"mysql": SELECT_TABLE_COLUMN_MYSQL_SQL,
|
||||
"sqlite": SELECT_TABLE_COLUMN_SQLITE_SQL,
|
||||
"postgres": SELECT_TABLE_COLUMN_PSQL_SQL,
|
||||
}
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def _():
|
||||
for plugin in nonebot.get_loaded_plugins():
|
||||
@ -73,7 +28,7 @@ async def _():
|
||||
sql_list = []
|
||||
if plugin.metadata and plugin.metadata.extra:
|
||||
sql_list = plugin.metadata.extra.get("sql_list")
|
||||
if module in SQL_DICT:
|
||||
if module in ApiDataSource.SQL_DICT:
|
||||
raise ValueError(f"{module} 常用SQL module 重复")
|
||||
if sql_list:
|
||||
SqlModel(
|
||||
@ -81,15 +36,15 @@ async def _():
|
||||
module=module,
|
||||
sql_list=sql_list,
|
||||
)
|
||||
SQL_DICT[module] = SqlModel
|
||||
if SQL_DICT:
|
||||
result = await PluginInfo.filter(module__in=SQL_DICT.keys()).values_list(
|
||||
"module", "name"
|
||||
)
|
||||
ApiDataSource.SQL_DICT[module] = SqlModel
|
||||
if ApiDataSource.SQL_DICT:
|
||||
result = await PluginInfo.filter(
|
||||
module__in=ApiDataSource.SQL_DICT.keys()
|
||||
).values_list("module", "name")
|
||||
module2name = {r[0]: r[1] for r in result}
|
||||
for s in SQL_DICT:
|
||||
module = SQL_DICT[s].module
|
||||
SQL_DICT[s].name = module2name.get(module, module)
|
||||
for s in ApiDataSource.SQL_DICT:
|
||||
module = ApiDataSource.SQL_DICT[s].module
|
||||
ApiDataSource.SQL_DICT[s].name = module2name.get(module, module)
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -100,10 +55,14 @@ async def _():
|
||||
description="获取数据库表",
|
||||
)
|
||||
async def _() -> Result[list[dict]]:
|
||||
db = Tortoise.get_connection("default")
|
||||
sql_type = BotConfig.get_sql_type()
|
||||
query = await db.execute_query_dict(type2sql[sql_type])
|
||||
return Result.ok(query)
|
||||
try:
|
||||
db = Tortoise.get_connection("default")
|
||||
sql_type = BotConfig.get_sql_type()
|
||||
query = await db.execute_query_dict(type2sql[sql_type])
|
||||
return Result.ok(query)
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_table_list 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -114,34 +73,13 @@ async def _() -> Result[list[dict]]:
|
||||
description="获取表字段",
|
||||
)
|
||||
async def _(table_name: str) -> Result[list[Column]]:
|
||||
db = Tortoise.get_connection("default")
|
||||
sql_type = BotConfig.get_sql_type()
|
||||
sql = type2sql_column[sql_type]
|
||||
query = await db.execute_query_dict(sql.format(table_name))
|
||||
result_list = []
|
||||
if sql_type == "sqlite":
|
||||
result_list.extend(
|
||||
Column(
|
||||
column_name=result["name"],
|
||||
data_type=result["type"],
|
||||
max_length=-1,
|
||||
is_nullable="YES" if result["notnull"] == 1 else "NO",
|
||||
)
|
||||
for result in query
|
||||
try:
|
||||
return Result.ok(
|
||||
await ApiDataSource.get_table_column(table_name), "拿到信息啦!"
|
||||
)
|
||||
elif sql_type == "mysql":
|
||||
result_list.extend(
|
||||
Column(
|
||||
column_name=result["Field"],
|
||||
data_type=result["Type"],
|
||||
max_length=-1,
|
||||
is_nullable=result["Null"],
|
||||
)
|
||||
for result in query
|
||||
)
|
||||
else:
|
||||
result_list.extend(Column(**result) for result in query)
|
||||
return Result.ok(result_list)
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_table_column 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -163,7 +101,8 @@ async def _(sql: SqlText, request: Request) -> Result[list[dict]]:
|
||||
result = await TaskInfo.raw(sql.sql)
|
||||
await SqlLog.add(ip or "0.0.0.0", sql.sql, str(result))
|
||||
return Result.ok(info="执行成功啦!")
|
||||
except OperationalError as e:
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/exec_sql 调用错误", "WebUi", e=e)
|
||||
await SqlLog.add(ip or "0.0.0.0", sql.sql, str(e), False)
|
||||
return Result.warning_(f"sql执行错误: {e}")
|
||||
|
||||
@ -176,16 +115,20 @@ async def _(sql: SqlText, request: Request) -> Result[list[dict]]:
|
||||
description="sql日志列表",
|
||||
)
|
||||
async def _(query: QueryModel) -> Result[BaseResultModel]:
|
||||
total = await SqlLog.all().count()
|
||||
if total % query.size:
|
||||
total += 1
|
||||
data = (
|
||||
await SqlLog.all()
|
||||
.order_by("-id")
|
||||
.offset((query.index - 1) * query.size)
|
||||
.limit(query.size)
|
||||
)
|
||||
return Result.ok(BaseResultModel(total=total, data=data))
|
||||
try:
|
||||
total = await SqlLog.all().count()
|
||||
if total % query.size:
|
||||
total += 1
|
||||
data = (
|
||||
await SqlLog.all()
|
||||
.order_by("-id")
|
||||
.offset((query.index - 1) * query.size)
|
||||
.limit(query.size)
|
||||
)
|
||||
return Result.ok(BaseResultModel(total=total, data=data))
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_sql_log 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -197,5 +140,5 @@ async def _(query: QueryModel) -> Result[BaseResultModel]:
|
||||
)
|
||||
async def _(plugin_name: str | None = None) -> Result[dict]:
|
||||
if plugin_name:
|
||||
return Result.ok(SQL_DICT.get(plugin_name))
|
||||
return Result.ok(str(SQL_DICT))
|
||||
return Result.ok(ApiDataSource.SQL_DICT.get(plugin_name))
|
||||
return Result.ok(str(ApiDataSource.SQL_DICT))
|
||||
|
||||
@ -0,0 +1,90 @@
|
||||
from tortoise import Tortoise
|
||||
|
||||
from zhenxun.configs.config import BotConfig
|
||||
|
||||
from .models.model import Column
|
||||
|
||||
SELECT_TABLE_MYSQL_SQL = """
|
||||
SELECT table_name AS name, table_comment AS `desc`
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE();
|
||||
"""
|
||||
|
||||
SELECT_TABLE_SQLITE_SQL = """
|
||||
SELECT name FROM sqlite_master WHERE type='table';
|
||||
"""
|
||||
|
||||
SELECT_TABLE_PSQL_SQL = """
|
||||
select a.tablename as name,d.description as desc from pg_tables a
|
||||
left join pg_class c on relname=tablename
|
||||
left join pg_description d on oid=objoid and objsubid=0 where a.schemaname='public'
|
||||
"""
|
||||
|
||||
SELECT_TABLE_COLUMN_PSQL_SQL = """
|
||||
SELECT column_name, data_type, character_maximum_length as max_length, is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = '{}';
|
||||
"""
|
||||
|
||||
SELECT_TABLE_COLUMN_MYSQL_SQL = """
|
||||
SHOW COLUMNS FROM {};
|
||||
"""
|
||||
|
||||
SELECT_TABLE_COLUMN_SQLITE_SQL = """
|
||||
PRAGMA table_info({});
|
||||
"""
|
||||
|
||||
type2sql = {
|
||||
"mysql": SELECT_TABLE_MYSQL_SQL,
|
||||
"sqlite": SELECT_TABLE_SQLITE_SQL,
|
||||
"postgres": SELECT_TABLE_PSQL_SQL,
|
||||
}
|
||||
|
||||
type2sql_column = {
|
||||
"mysql": SELECT_TABLE_COLUMN_MYSQL_SQL,
|
||||
"sqlite": SELECT_TABLE_COLUMN_SQLITE_SQL,
|
||||
"postgres": SELECT_TABLE_COLUMN_PSQL_SQL,
|
||||
}
|
||||
|
||||
|
||||
class ApiDataSource:
|
||||
SQL_DICT = {} # noqa: RUF012
|
||||
|
||||
@classmethod
|
||||
async def get_table_column(cls, table_name: str) -> list[Column]:
|
||||
"""获取表字段信息
|
||||
|
||||
参数:
|
||||
table_name: 表名
|
||||
|
||||
返回:
|
||||
list[Column]: 字段数据
|
||||
"""
|
||||
db = Tortoise.get_connection("default")
|
||||
sql_type = BotConfig.get_sql_type()
|
||||
sql = type2sql_column[sql_type]
|
||||
query = await db.execute_query_dict(sql.format(table_name))
|
||||
result_list = []
|
||||
if sql_type == "sqlite":
|
||||
result_list.extend(
|
||||
Column(
|
||||
column_name=result["name"],
|
||||
data_type=result["type"],
|
||||
max_length=-1,
|
||||
is_nullable="YES" if result["notnull"] == 1 else "NO",
|
||||
)
|
||||
for result in query
|
||||
)
|
||||
elif sql_type == "mysql":
|
||||
result_list.extend(
|
||||
Column(
|
||||
column_name=result["Field"],
|
||||
data_type=result["Type"],
|
||||
max_length=-1,
|
||||
is_nullable=result["Null"],
|
||||
)
|
||||
for result in query
|
||||
)
|
||||
else:
|
||||
result_list.extend(Column(**result) for result in query)
|
||||
return result_list
|
||||
@ -1,7 +1,5 @@
|
||||
import asyncio
|
||||
import contextlib
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
import time
|
||||
|
||||
from fastapi import APIRouter
|
||||
@ -9,28 +7,26 @@ from fastapi.responses import JSONResponse
|
||||
import nonebot
|
||||
from nonebot.config import Config
|
||||
from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState
|
||||
from tortoise.functions import Count
|
||||
from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK
|
||||
|
||||
from zhenxun.models.bot_connect_log import BotConnectLog
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.statistics import Statistics
|
||||
from zhenxun.models.bot_console import BotConsole
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.common_utils import CommonUtils
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
|
||||
from ....base_model import Result
|
||||
from ....config import AVA_URL, GROUP_AVA_URL, QueryDateType
|
||||
from ....config import QueryDateType
|
||||
from ....utils import authentication, get_system_status
|
||||
from .data_source import bot_live
|
||||
from .data_source import ApiDataSource
|
||||
from .model import (
|
||||
ActiveGroup,
|
||||
BaseInfo,
|
||||
BotBlockModule,
|
||||
BotManageUpdateParam,
|
||||
BotStatusParam,
|
||||
HotPlugin,
|
||||
NonebotData,
|
||||
QueryCount,
|
||||
TemplateBaseInfo,
|
||||
)
|
||||
|
||||
driver = nonebot.get_driver()
|
||||
@ -56,64 +52,14 @@ async def _(bot_id: str | None = None) -> Result[list[BaseInfo]]:
|
||||
返回:
|
||||
Result: 获取指定bot信息与bot列表
|
||||
"""
|
||||
global run_time
|
||||
bot_list: list[TemplateBaseInfo] = []
|
||||
if bots := nonebot.get_bots():
|
||||
select_bot: BaseInfo
|
||||
for _, bot in bots.items():
|
||||
login_info = await bot.get_login_info()
|
||||
bot_list.append(
|
||||
TemplateBaseInfo(
|
||||
bot=bot, # type: ignore
|
||||
self_id=bot.self_id,
|
||||
nickname=login_info["nickname"],
|
||||
ava_url=AVA_URL.format(bot.self_id),
|
||||
)
|
||||
)
|
||||
# 获取指定qq号的bot信息,若无指定 则获取第一个
|
||||
if _bl := [b for b in bot_list if b.self_id == bot_id]:
|
||||
select_bot = _bl[0]
|
||||
else:
|
||||
select_bot = bot_list[0]
|
||||
select_bot.is_select = True
|
||||
now = datetime.now()
|
||||
# 今日累计接收消息
|
||||
select_bot.received_messages = await ChatHistory.filter(
|
||||
bot_id=select_bot.self_id,
|
||||
create_time__gte=now - timedelta(hours=now.hour),
|
||||
).count()
|
||||
# 群聊数量
|
||||
select_bot.group_count = len(await select_bot.bot.get_group_list())
|
||||
# 好友数量
|
||||
select_bot.friend_count = len(await select_bot.bot.get_friend_list())
|
||||
for bot in bot_list:
|
||||
bot.bot = None # type: ignore
|
||||
# 插件加载数量
|
||||
select_bot.plugin_count = await PluginInfo.all().count()
|
||||
fail_count = await PluginInfo.filter(load_status=False).count()
|
||||
select_bot.fail_plugin_count = fail_count
|
||||
select_bot.success_plugin_count = (
|
||||
select_bot.plugin_count - select_bot.fail_plugin_count
|
||||
)
|
||||
# 连接时间
|
||||
select_bot.connect_time = bot_live.get(select_bot.self_id) or 0
|
||||
if select_bot.connect_time:
|
||||
connect_date = datetime.fromtimestamp(select_bot.connect_time)
|
||||
select_bot.connect_date = connect_date.strftime("%Y-%m-%d %H:%M:%S")
|
||||
version_file = Path() / "__version__"
|
||||
if version_file.exists():
|
||||
if text := version_file.open().read():
|
||||
if ver := text.replace("__version__: ", "").strip():
|
||||
select_bot.version = ver
|
||||
day_call = await Statistics.filter(
|
||||
create_time__gte=now - timedelta(hours=now.hour)
|
||||
).count()
|
||||
select_bot.day_call = day_call
|
||||
select_bot.connect_count = await BotConnectLog.filter(
|
||||
bot_id=select_bot.self_id
|
||||
).count()
|
||||
return Result.ok([BaseInfo(**e.dict()) for e in bot_list], "拿到信息啦!")
|
||||
return Result.warning_("无Bot连接...")
|
||||
try:
|
||||
result = await ApiDataSource.get_base_info(bot_id)
|
||||
if not result:
|
||||
Result.warning_("无Bot连接...")
|
||||
return Result.ok(result, "拿到信息啦!")
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_base_info 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -124,32 +70,11 @@ async def _(bot_id: str | None = None) -> Result[list[BaseInfo]]:
|
||||
description="获取接收消息数量",
|
||||
)
|
||||
async def _(bot_id: str | None = None) -> Result[QueryCount]:
|
||||
now = datetime.now()
|
||||
query = ChatHistory
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
all_count = await query.annotate().count()
|
||||
day_count = await query.filter(
|
||||
create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
week_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=7, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
month_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=30, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
year_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=365, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
return Result.ok(
|
||||
QueryCount(
|
||||
num=all_count,
|
||||
day=day_count,
|
||||
week=week_count,
|
||||
month=month_count,
|
||||
year=year_count,
|
||||
)
|
||||
)
|
||||
try:
|
||||
return Result.ok(await ApiDataSource.get_all_chat_count(bot_id), "拿到信息啦!")
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_all_chat_count 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -160,32 +85,11 @@ async def _(bot_id: str | None = None) -> Result[QueryCount]:
|
||||
description="获取调用次数",
|
||||
)
|
||||
async def _(bot_id: str | None = None) -> Result[QueryCount]:
|
||||
now = datetime.now()
|
||||
query = Statistics
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
all_count = await query.annotate().count()
|
||||
day_count = await query.filter(
|
||||
create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
week_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=7, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
month_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=30, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
year_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=365, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
return Result.ok(
|
||||
QueryCount(
|
||||
num=all_count,
|
||||
day=day_count,
|
||||
week=week_count,
|
||||
month=month_count,
|
||||
year=year_count,
|
||||
)
|
||||
)
|
||||
try:
|
||||
return Result.ok(await ApiDataSource.get_all_call_count(bot_id), "拿到信息啦!")
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_all_call_count 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -196,19 +100,18 @@ async def _(bot_id: str | None = None) -> Result[QueryCount]:
|
||||
description="好友/群组数量",
|
||||
)
|
||||
async def _(bot_id: str) -> Result[dict[str, int]]:
|
||||
if bots := nonebot.get_bots():
|
||||
if bot_id not in bots:
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
bot = bots[bot_id]
|
||||
platform = PlatformUtils.get_platform(bot)
|
||||
if platform == "qq":
|
||||
data = {
|
||||
"friend_count": len(await bot.get_friend_list()),
|
||||
"group_count": len(await bot.get_group_list()),
|
||||
}
|
||||
return Result.ok(data)
|
||||
return Result.warning_("暂不支持该平台...")
|
||||
return Result.warning_("无Bot连接...")
|
||||
try:
|
||||
bot = nonebot.get_bot(bot_id)
|
||||
data = {
|
||||
"friend_count": len(await PlatformUtils.get_friend_list(bot)),
|
||||
"group_count": len(await PlatformUtils.get_group_list(bot)),
|
||||
}
|
||||
return Result.ok(data, "拿到信息啦!")
|
||||
except (ValueError, KeyError):
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_fg_count 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -219,6 +122,7 @@ async def _(bot_id: str) -> Result[dict[str, int]]:
|
||||
description="获取nb数据",
|
||||
)
|
||||
async def _() -> Result[NonebotData]:
|
||||
global run_time
|
||||
return Result.ok(NonebotData(config=driver.config, run_time=int(run_time)))
|
||||
|
||||
|
||||
@ -241,6 +145,7 @@ async def _() -> Result[Config]:
|
||||
description="获取nb运行时间",
|
||||
)
|
||||
async def _() -> Result[int]:
|
||||
global run_time
|
||||
return Result.ok(int(run_time))
|
||||
|
||||
|
||||
@ -254,48 +159,13 @@ async def _() -> Result[int]:
|
||||
async def _(
|
||||
date_type: QueryDateType | None = None, bot_id: str | None = None
|
||||
) -> Result[list[ActiveGroup]]:
|
||||
query = ChatHistory
|
||||
now = datetime.now()
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
if date_type == QueryDateType.DAY:
|
||||
query = query.filter(create_time__gte=now - timedelta(hours=now.hour))
|
||||
if date_type == QueryDateType.WEEK:
|
||||
query = query.filter(create_time__gte=now - timedelta(days=7))
|
||||
if date_type == QueryDateType.MONTH:
|
||||
query = query.filter(create_time__gte=now - timedelta(days=30))
|
||||
if date_type == QueryDateType.YEAR:
|
||||
query = query.filter(create_time__gte=now - timedelta(days=365))
|
||||
data_list = (
|
||||
await query.annotate(count=Count("id"))
|
||||
.filter(group_id__not_isnull=True)
|
||||
.group_by("group_id")
|
||||
.order_by("-count")
|
||||
.limit(5)
|
||||
.values_list("group_id", "count")
|
||||
)
|
||||
id2name = {}
|
||||
if data_list:
|
||||
if info_list := await GroupConsole.filter(
|
||||
group_id__in=[x[0] for x in data_list]
|
||||
).all():
|
||||
for group_info in info_list:
|
||||
id2name[group_info.group_id] = group_info.group_name
|
||||
active_group_list = [
|
||||
ActiveGroup(
|
||||
group_id=data[0],
|
||||
name=id2name.get(data[0]) or data[0],
|
||||
chat_num=data[1],
|
||||
ava_img=GROUP_AVA_URL.format(data[0], data[0]),
|
||||
try:
|
||||
return Result.ok(
|
||||
await ApiDataSource.get_active_group(date_type, bot_id), "拿到信息啦!"
|
||||
)
|
||||
for data in data_list
|
||||
]
|
||||
active_group_list = sorted(
|
||||
active_group_list, key=lambda x: x.chat_num, reverse=True
|
||||
)
|
||||
if len(active_group_list) > 5:
|
||||
active_group_list = active_group_list[:5]
|
||||
return Result.ok(active_group_list)
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_active_group 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -308,37 +178,66 @@ async def _(
|
||||
async def _(
|
||||
date_type: QueryDateType | None = None, bot_id: str | None = None
|
||||
) -> Result[list[HotPlugin]]:
|
||||
query = Statistics
|
||||
now = datetime.now()
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
if date_type == QueryDateType.DAY:
|
||||
query = query.filter(create_time__gte=now - timedelta(hours=now.hour))
|
||||
if date_type == QueryDateType.WEEK:
|
||||
query = query.filter(create_time__gte=now - timedelta(days=7))
|
||||
if date_type == QueryDateType.MONTH:
|
||||
query = query.filter(create_time__gte=now - timedelta(days=30))
|
||||
if date_type == QueryDateType.YEAR:
|
||||
query = query.filter(create_time__gte=now - timedelta(days=365))
|
||||
data_list = (
|
||||
await query.annotate(count=Count("id"))
|
||||
.group_by("plugin_name")
|
||||
.order_by("-count")
|
||||
.limit(5)
|
||||
.values_list("plugin_name", "count")
|
||||
)
|
||||
hot_plugin_list = []
|
||||
module_list = [x[0] for x in data_list]
|
||||
plugins = await PluginInfo.filter(module__in=module_list).all()
|
||||
module2name = {p.module: p.name for p in plugins}
|
||||
for data in data_list:
|
||||
module = data[0]
|
||||
name = module2name.get(module) or module
|
||||
hot_plugin_list.append(HotPlugin(module=module, name=name, count=data[1]))
|
||||
hot_plugin_list = sorted(hot_plugin_list, key=lambda x: x.count, reverse=True)
|
||||
if len(hot_plugin_list) > 5:
|
||||
hot_plugin_list = hot_plugin_list[:5]
|
||||
return Result.ok(hot_plugin_list)
|
||||
try:
|
||||
return Result.ok(
|
||||
await ApiDataSource.get_hot_plugin(date_type, bot_id), "拿到信息啦!"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_hot_plugin 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/change_bot_status",
|
||||
dependencies=[authentication()],
|
||||
response_model=Result,
|
||||
response_class=JSONResponse,
|
||||
description="修改bot全局开关",
|
||||
)
|
||||
async def _(param: BotStatusParam):
|
||||
try:
|
||||
await BotConsole.set_bot_status(param.status, param.bot_id)
|
||||
return Result.ok(info="修改bot全局开关成功!")
|
||||
except (ValueError, KeyError):
|
||||
return Result.fail("Bot未初始化...")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/get_bot_block_module",
|
||||
dependencies=[authentication()],
|
||||
response_model=Result[BotBlockModule],
|
||||
response_class=JSONResponse,
|
||||
description="获取bot层面的禁用模块",
|
||||
)
|
||||
async def _(bot_id: str) -> Result[BotBlockModule]:
|
||||
try:
|
||||
return Result.ok(
|
||||
await ApiDataSource.get_bot_block_module(bot_id), "拿到信息啦!"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_bot_block_module 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/update_bot_manage",
|
||||
dependencies=[authentication()],
|
||||
response_model=Result,
|
||||
response_class=JSONResponse,
|
||||
description="修改bot全局开关",
|
||||
)
|
||||
async def _(param: BotManageUpdateParam):
|
||||
try:
|
||||
bot_data = await BotConsole.get_or_none(bot_id=param.bot_id)
|
||||
if not bot_data:
|
||||
return Result.fail("Bot数据不存在...")
|
||||
bot_data.block_plugins = CommonUtils.convert_module_format(param.block_plugins)
|
||||
bot_data.block_tasks = CommonUtils.convert_module_format(param.block_tasks)
|
||||
await bot_data.save(update_fields=["block_plugins", "block_tasks"])
|
||||
return Result.ok()
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/update_bot_manage 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@ws_router.websocket("/system_status")
|
||||
|
||||
@ -1,8 +1,33 @@
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
import time
|
||||
|
||||
import nonebot
|
||||
from nonebot.adapters.onebot.v11 import Bot
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.drivers import Driver
|
||||
from tortoise.functions import Count
|
||||
|
||||
from zhenxun.models.bot_connect_log import BotConnectLog
|
||||
from zhenxun.models.bot_console import BotConsole
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.statistics import Statistics
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.common_utils import CommonUtils
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
|
||||
from ....config import AVA_URL, GROUP_AVA_URL, QueryDateType
|
||||
from .model import (
|
||||
ActiveGroup,
|
||||
BaseInfo,
|
||||
BotBlockModule,
|
||||
HotPlugin,
|
||||
QueryCount,
|
||||
TemplateBaseInfo,
|
||||
)
|
||||
|
||||
driver: Driver = nonebot.get_driver()
|
||||
|
||||
@ -33,3 +58,313 @@ async def _(bot: Bot):
|
||||
@driver.on_bot_disconnect
|
||||
async def _(bot: Bot):
|
||||
bot_live.remove(bot.self_id)
|
||||
|
||||
|
||||
class ApiDataSource:
|
||||
@classmethod
|
||||
async def __build_bot_info(cls, bot: Bot) -> TemplateBaseInfo:
|
||||
"""构建bot信息
|
||||
|
||||
参数:
|
||||
bot: bot实例
|
||||
|
||||
返回:
|
||||
TemplateBaseInfo: bot信息
|
||||
"""
|
||||
login_info = None
|
||||
try:
|
||||
login_info = await bot.get_login_info()
|
||||
except Exception as e:
|
||||
logger.warning("调用接口get_login_info失败", "WebUi", e=e)
|
||||
return TemplateBaseInfo(
|
||||
bot=bot,
|
||||
self_id=bot.self_id,
|
||||
nickname=login_info["nickname"] if login_info else bot.self_id,
|
||||
ava_url=AVA_URL.format(bot.self_id),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def __get_bot_version(cls) -> str:
|
||||
"""获取bot版本
|
||||
|
||||
返回:
|
||||
str | None: 版本
|
||||
"""
|
||||
version_file = Path() / "__version__"
|
||||
if version_file.exists():
|
||||
if text := version_file.open().read():
|
||||
return text.replace("__version__: ", "").strip()
|
||||
return "unknown"
|
||||
|
||||
@classmethod
|
||||
async def __init_bot_base_data(cls, select_bot: TemplateBaseInfo):
|
||||
"""初始化bot的基础数据
|
||||
|
||||
参数:
|
||||
select_bot: bot
|
||||
"""
|
||||
now = datetime.now()
|
||||
# 今日累计接收消息
|
||||
select_bot.received_messages = await ChatHistory.filter(
|
||||
bot_id=select_bot.self_id,
|
||||
create_time__gte=now - timedelta(hours=now.hour),
|
||||
).count()
|
||||
# 群聊数量
|
||||
select_bot.group_count = len(await PlatformUtils.get_group_list(select_bot.bot))
|
||||
# 好友数量
|
||||
select_bot.friend_count = len(
|
||||
await PlatformUtils.get_friend_list(select_bot.bot)
|
||||
)
|
||||
select_bot.status = await BotConsole.get_bot_status(select_bot.self_id)
|
||||
# 连接时间
|
||||
select_bot.connect_time = bot_live.get(select_bot.self_id) or 0
|
||||
if select_bot.connect_time:
|
||||
connect_date = datetime.fromtimestamp(select_bot.connect_time)
|
||||
select_bot.connect_date = connect_date.strftime("%Y-%m-%d %H:%M:%S")
|
||||
select_bot.version = cls.__get_bot_version()
|
||||
day_call = await Statistics.filter(
|
||||
create_time__gte=now - timedelta(hours=now.hour)
|
||||
).count()
|
||||
select_bot.day_call = day_call
|
||||
select_bot.connect_count = await BotConnectLog.filter(
|
||||
bot_id=select_bot.self_id
|
||||
).count()
|
||||
|
||||
@classmethod
|
||||
async def get_base_info(cls, bot_id: str | None) -> list[BaseInfo] | None:
|
||||
"""获取bot信息
|
||||
|
||||
参数:
|
||||
bot_id: bot id
|
||||
|
||||
返回:
|
||||
list[BaseInfo] | None: bot列表
|
||||
"""
|
||||
bots = nonebot.get_bots()
|
||||
if not bots:
|
||||
return None
|
||||
select_bot: BaseInfo
|
||||
bot_list = [await cls.__build_bot_info(bot) for _, bot in bots.items()]
|
||||
# 获取指定qq号的bot信息,若无指定 则获取第一个
|
||||
if _bl := [b for b in bot_list if b.self_id == bot_id]:
|
||||
select_bot = _bl[0]
|
||||
else:
|
||||
select_bot = bot_list[0]
|
||||
await cls.__init_bot_base_data(select_bot)
|
||||
for bot in bot_list:
|
||||
bot.bot = None # type: ignore
|
||||
select_bot.is_select = True
|
||||
return [BaseInfo(**e.dict()) for e in bot_list]
|
||||
|
||||
@classmethod
|
||||
async def get_all_chat_count(cls, bot_id: str | None) -> QueryCount:
|
||||
"""获取年/月/周/日聊天次数
|
||||
|
||||
参数:
|
||||
bot_id: bot id
|
||||
|
||||
返回:
|
||||
QueryCount: 数据内容
|
||||
"""
|
||||
now = datetime.now()
|
||||
query = ChatHistory
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
all_count = await query.annotate().count()
|
||||
day_count = await query.filter(
|
||||
create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
week_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=7, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
month_count = await query.filter(
|
||||
create_time__gte=now
|
||||
- timedelta(days=30, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
year_count = await query.filter(
|
||||
create_time__gte=now
|
||||
- timedelta(days=365, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
return QueryCount(
|
||||
num=all_count,
|
||||
day=day_count,
|
||||
week=week_count,
|
||||
month=month_count,
|
||||
year=year_count,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_all_call_count(cls, bot_id: str | None) -> QueryCount:
|
||||
"""获取年/月/周/日调用次数
|
||||
|
||||
参数:
|
||||
bot_id: bot id
|
||||
|
||||
返回:
|
||||
QueryCount: 数据内容
|
||||
"""
|
||||
now = datetime.now()
|
||||
query = Statistics
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
all_count = await query.annotate().count()
|
||||
day_count = await query.filter(
|
||||
create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
week_count = await query.filter(
|
||||
create_time__gte=now - timedelta(days=7, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
month_count = await query.filter(
|
||||
create_time__gte=now
|
||||
- timedelta(days=30, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
year_count = await query.filter(
|
||||
create_time__gte=now
|
||||
- timedelta(days=365, hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
return QueryCount(
|
||||
num=all_count,
|
||||
day=day_count,
|
||||
week=week_count,
|
||||
month=month_count,
|
||||
year=year_count,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def __get_query(
|
||||
cls,
|
||||
base_query: type[ChatHistory | Statistics],
|
||||
date_type: QueryDateType | None = None,
|
||||
bot_id: str | None = None,
|
||||
):
|
||||
"""构建日期查询条件
|
||||
|
||||
参数:
|
||||
date_type: 日期类型.
|
||||
bot_id: bot id.
|
||||
"""
|
||||
query = base_query
|
||||
now = datetime.now()
|
||||
if bot_id:
|
||||
query = query.filter(bot_id=bot_id)
|
||||
if date_type == QueryDateType.DAY:
|
||||
query = query.filter(create_time__gte=now - timedelta(hours=now.hour))
|
||||
if date_type == QueryDateType.WEEK:
|
||||
query = query.filter(create_time__gte=now - timedelta(days=7))
|
||||
if date_type == QueryDateType.MONTH:
|
||||
query = query.filter(create_time__gte=now - timedelta(days=30))
|
||||
if date_type == QueryDateType.YEAR:
|
||||
query = query.filter(create_time__gte=now - timedelta(days=365))
|
||||
return query
|
||||
|
||||
@classmethod
|
||||
async def get_active_group(
|
||||
cls, date_type: QueryDateType | None = None, bot_id: str | None = None
|
||||
) -> list[ActiveGroup]:
|
||||
"""获取活跃群组
|
||||
|
||||
参数:
|
||||
date_type: 日期类型.
|
||||
bot_id: bot id.
|
||||
|
||||
返回:
|
||||
list[ActiveGroup]: 活跃群组列表
|
||||
"""
|
||||
query = cls.__get_query(ChatHistory, date_type, bot_id)
|
||||
data_list = (
|
||||
await query.annotate(count=Count("id"))
|
||||
.filter(group_id__not_isnull=True)
|
||||
.group_by("group_id")
|
||||
.order_by("-count")
|
||||
.limit(5)
|
||||
.values_list("group_id", "count")
|
||||
)
|
||||
id2name = {}
|
||||
if data_list:
|
||||
if info_list := await GroupConsole.filter(
|
||||
group_id__in=[x[0] for x in data_list]
|
||||
).all():
|
||||
for group_info in info_list:
|
||||
id2name[group_info.group_id] = group_info.group_name
|
||||
active_group_list = [
|
||||
ActiveGroup(
|
||||
group_id=data[0],
|
||||
name=id2name.get(data[0]) or data[0],
|
||||
chat_num=data[1],
|
||||
ava_img=GROUP_AVA_URL.format(data[0], data[0]),
|
||||
)
|
||||
for data in data_list
|
||||
]
|
||||
active_group_list = sorted(
|
||||
active_group_list, key=lambda x: x.chat_num, reverse=True
|
||||
)
|
||||
if len(active_group_list) > 5:
|
||||
active_group_list = active_group_list[:5]
|
||||
return active_group_list
|
||||
|
||||
@classmethod
|
||||
async def get_hot_plugin(
|
||||
cls, date_type: QueryDateType | None = None, bot_id: str | None = None
|
||||
) -> list[HotPlugin]:
|
||||
"""获取热门插件
|
||||
|
||||
参数:
|
||||
date_type: 日期类型.
|
||||
bot_id: bot id.
|
||||
|
||||
返回:
|
||||
list[HotPlugin]: 热门插件列表
|
||||
"""
|
||||
query = cls.__get_query(Statistics, date_type, bot_id)
|
||||
data_list = (
|
||||
await query.annotate(count=Count("id"))
|
||||
.group_by("plugin_name")
|
||||
.order_by("-count")
|
||||
.limit(5)
|
||||
.values_list("plugin_name", "count")
|
||||
)
|
||||
hot_plugin_list = []
|
||||
module_list = [x[0] for x in data_list]
|
||||
plugins = await PluginInfo.filter(module__in=module_list).all()
|
||||
module2name = {p.module: p.name for p in plugins}
|
||||
for data in data_list:
|
||||
module = data[0]
|
||||
name = module2name.get(module) or module
|
||||
hot_plugin_list.append(HotPlugin(module=module, name=name, count=data[1]))
|
||||
hot_plugin_list = sorted(hot_plugin_list, key=lambda x: x.count, reverse=True)
|
||||
if len(hot_plugin_list) > 5:
|
||||
hot_plugin_list = hot_plugin_list[:5]
|
||||
return hot_plugin_list
|
||||
|
||||
@classmethod
|
||||
async def get_bot_block_module(cls, bot_id: str) -> BotBlockModule | None:
|
||||
"""获取bot层面的禁用模块
|
||||
|
||||
参数:
|
||||
bot_id: bot id
|
||||
|
||||
返回:
|
||||
BotBlockModule | None: 数据内容
|
||||
"""
|
||||
bot_data = await BotConsole.get_or_none(bot_id=bot_id)
|
||||
if not bot_data:
|
||||
return None
|
||||
block_tasks = []
|
||||
block_plugins = []
|
||||
all_plugins = await PluginInfo.filter(
|
||||
load_status=True, plugin_type=PluginType.NORMAL
|
||||
).values("module", "name")
|
||||
all_task = await TaskInfo.annotate().values("module", "name")
|
||||
if bot_data.block_tasks:
|
||||
tasks = CommonUtils.convert_module_format(bot_data.block_tasks)
|
||||
block_tasks = [t["module"] for t in all_task if t["module"] in tasks]
|
||||
if bot_data.block_plugins:
|
||||
plugins = CommonUtils.convert_module_format(bot_data.block_plugins)
|
||||
block_plugins = [t["module"] for t in all_plugins if t["module"] in plugins]
|
||||
return BotBlockModule(
|
||||
bot_id=bot_id,
|
||||
block_tasks=block_tasks,
|
||||
block_plugins=block_plugins,
|
||||
all_plugins=all_plugins,
|
||||
all_tasks=all_task,
|
||||
)
|
||||
|
||||
@ -1,8 +1,45 @@
|
||||
from typing import Any
|
||||
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.config import Config
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class BotManageUpdateParam(BaseModel):
|
||||
"""bot更新参数"""
|
||||
|
||||
bot_id: str
|
||||
"""bot id"""
|
||||
block_plugins: list[str]
|
||||
"""禁用插件"""
|
||||
block_tasks: list[str]
|
||||
"""禁用被动"""
|
||||
|
||||
|
||||
class BotStatusParam(BaseModel):
|
||||
"""bot状态参数"""
|
||||
|
||||
bot_id: str
|
||||
"""bot id"""
|
||||
status: bool
|
||||
"""状态"""
|
||||
|
||||
|
||||
class BotBlockModule(BaseModel):
|
||||
"""bot禁用模块参数"""
|
||||
|
||||
bot_id: str
|
||||
"""bot id"""
|
||||
block_plugins: list[str]
|
||||
"""禁用插件"""
|
||||
block_tasks: list[str]
|
||||
"""禁用被动"""
|
||||
all_plugins: list[dict[str, Any]]
|
||||
"""所有插件"""
|
||||
all_tasks: list[dict[str, Any]]
|
||||
"""所有被动"""
|
||||
|
||||
|
||||
class SystemStatus(BaseModel):
|
||||
"""
|
||||
系统状态
|
||||
@ -36,13 +73,8 @@ class BaseInfo(BaseModel):
|
||||
"""连接日期"""
|
||||
connect_count: int = 0
|
||||
"""连接次数"""
|
||||
|
||||
plugin_count: int = 0
|
||||
"""加载插件数量"""
|
||||
success_plugin_count: int = 0
|
||||
"""加载成功插件数量"""
|
||||
fail_plugin_count: int = 0
|
||||
"""加载失败插件数量"""
|
||||
status: bool = False
|
||||
"""全局状态"""
|
||||
|
||||
is_select: bool = False
|
||||
"""当前选择"""
|
||||
|
||||
@ -2,16 +2,9 @@ from fastapi import APIRouter
|
||||
from fastapi.responses import JSONResponse
|
||||
import nonebot
|
||||
from nonebot.adapters.onebot.v11 import ActionFailed
|
||||
from tortoise.functions import Count
|
||||
|
||||
from zhenxun.configs.config import BotConfig
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
from zhenxun.models.fg_request import FgRequest
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.statistics import Statistics
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import RequestHandleType, RequestType
|
||||
from zhenxun.utils.exception import NotFoundError
|
||||
@ -20,20 +13,17 @@ from zhenxun.utils.platform import PlatformUtils
|
||||
from ....base_model import Result
|
||||
from ....config import AVA_URL, GROUP_AVA_URL
|
||||
from ....utils import authentication
|
||||
from .data_source import ApiDataSource
|
||||
from .model import (
|
||||
ClearRequest,
|
||||
DeleteFriend,
|
||||
Friend,
|
||||
FriendRequestResult,
|
||||
GroupDetail,
|
||||
GroupRequestResult,
|
||||
GroupResult,
|
||||
HandleRequest,
|
||||
LeaveGroup,
|
||||
Plugin,
|
||||
ReqResult,
|
||||
SendMessage,
|
||||
Task,
|
||||
SendMessageParam,
|
||||
UpdateGroup,
|
||||
UserDetail,
|
||||
)
|
||||
@ -52,19 +42,21 @@ async def _(bot_id: str) -> Result:
|
||||
"""
|
||||
获取群信息
|
||||
"""
|
||||
if not (bots := nonebot.get_bots()):
|
||||
return Result.warning_("无Bot连接...")
|
||||
if bot_id not in bots:
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
group_list_result = []
|
||||
try:
|
||||
group_list = await bots[bot_id].get_group_list()
|
||||
bot = nonebot.get_bot(bot_id)
|
||||
group_list, _ = await PlatformUtils.get_group_list(bot)
|
||||
for g in group_list:
|
||||
gid = g["group_id"]
|
||||
g["ava_url"] = GROUP_AVA_URL.format(gid, gid)
|
||||
group_list_result.append(GroupResult(**g))
|
||||
ava_url = GROUP_AVA_URL.format(g.group_id, g.group_id)
|
||||
group_list_result.append(
|
||||
GroupResult(
|
||||
group_id=g.group_id, group_name=g.group_name, ava_url=ava_url
|
||||
)
|
||||
)
|
||||
except (ValueError, KeyError):
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
except Exception as e:
|
||||
logger.error("调用API错误", "/get_group_list", e=e)
|
||||
logger.error(f"{router.prefix}/get_group_list 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
return Result.ok(group_list_result, "拿到了新鲜出炉的数据!")
|
||||
|
||||
@ -78,25 +70,11 @@ async def _(bot_id: str) -> Result:
|
||||
)
|
||||
async def _(group: UpdateGroup) -> Result[str]:
|
||||
try:
|
||||
group_id = group.group_id
|
||||
if db_group := await GroupConsole.get_group(group_id):
|
||||
task_list = await TaskInfo.all().values_list("module", flat=True)
|
||||
db_group.level = group.level
|
||||
db_group.status = group.status
|
||||
if group.close_plugins:
|
||||
group.close_plugins = [f"<{module}" for module in group.close_plugins]
|
||||
db_group.block_plugin = ",".join(group.close_plugins) + ","
|
||||
if group.task:
|
||||
if block_task := [t for t in task_list if t not in group.task]:
|
||||
block_task = [f"<{module}" for module in block_task]
|
||||
db_group.block_task = ",".join(block_task) + "," # type: ignore
|
||||
await db_group.save(
|
||||
update_fields=["level", "status", "block_plugin", "block_task"]
|
||||
)
|
||||
await ApiDataSource.update_group(group)
|
||||
return Result.ok(info="已完成记录!")
|
||||
except Exception as e:
|
||||
logger.error("调用API错误", "/get_group", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
return Result.ok(info="已完成记录!")
|
||||
logger.error(f"{router.prefix}/update_group 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -110,24 +88,24 @@ async def _(bot_id: str) -> Result[list[Friend]]:
|
||||
"""
|
||||
获取群信息
|
||||
"""
|
||||
if bots := nonebot.get_bots():
|
||||
if bot_id not in bots:
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
try:
|
||||
platform = PlatformUtils.get_platform(bots[bot_id])
|
||||
if platform != "qq":
|
||||
return Result.warning_("该平台暂不支持该功能...")
|
||||
friend_list = await bots[bot_id].get_friend_list()
|
||||
for f in friend_list:
|
||||
f["ava_url"] = AVA_URL.format(f["user_id"])
|
||||
return Result.ok(
|
||||
[Friend(**f) for f in friend_list if str(f["user_id"]) != bot_id],
|
||||
"拿到了新鲜出炉的数据!",
|
||||
try:
|
||||
bot = nonebot.get_bot(bot_id)
|
||||
friend_list, _ = await PlatformUtils.get_friend_list(bot)
|
||||
result_list = []
|
||||
for f in friend_list:
|
||||
ava_url = AVA_URL.format(f.user_id)
|
||||
result_list.append(
|
||||
Friend(user_id=f.user_id, nickname=f.nickname, ava_url=ava_url)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("调用API错误", "/get_group_list", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
return Result.warning_("无Bot连接...")
|
||||
return Result.ok(
|
||||
result_list,
|
||||
"拿到了新鲜出炉的数据!",
|
||||
)
|
||||
except (ValueError, KeyError):
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
except Exception as e:
|
||||
logger.error("调用API错误", "/get_group_list", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -138,17 +116,21 @@ async def _(bot_id: str) -> Result[list[Friend]]:
|
||||
description="获取请求数量",
|
||||
)
|
||||
async def _() -> Result[dict[str, int]]:
|
||||
f_count = await FgRequest.filter(
|
||||
request_type=RequestType.FRIEND, handle_type__isnull=True
|
||||
).count()
|
||||
g_count = await FgRequest.filter(
|
||||
request_type=RequestType.GROUP, handle_type__isnull=True
|
||||
).count()
|
||||
data = {
|
||||
"friend_count": f_count,
|
||||
"group_count": g_count,
|
||||
}
|
||||
return Result.ok(data, f"{BotConfig.self_nickname}带来了最新的数据!")
|
||||
try:
|
||||
f_count = await FgRequest.filter(
|
||||
request_type=RequestType.FRIEND, handle_type__isnull=True
|
||||
).count()
|
||||
g_count = await FgRequest.filter(
|
||||
request_type=RequestType.GROUP, handle_type__isnull=True
|
||||
).count()
|
||||
data = {
|
||||
"friend_count": f_count,
|
||||
"group_count": g_count,
|
||||
}
|
||||
return Result.ok(data, "拿到了新鲜出炉的数据!")
|
||||
except Exception as e:
|
||||
logger.error("调用API错误", "/get_request_count", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -160,43 +142,10 @@ async def _() -> Result[dict[str, int]]:
|
||||
)
|
||||
async def _() -> Result[ReqResult]:
|
||||
try:
|
||||
req_result = ReqResult()
|
||||
data_list = await FgRequest.filter(handle_type__isnull=True).all()
|
||||
for req in data_list:
|
||||
if req.request_type == RequestType.FRIEND:
|
||||
req_result.friend.append(
|
||||
FriendRequestResult(
|
||||
oid=req.id,
|
||||
bot_id=req.bot_id,
|
||||
id=req.user_id,
|
||||
flag=req.flag,
|
||||
nickname=req.nickname,
|
||||
comment=req.comment,
|
||||
ava_url=AVA_URL.format(req.user_id),
|
||||
type=str(req.request_type).lower(),
|
||||
)
|
||||
)
|
||||
else:
|
||||
req_result.group.append(
|
||||
GroupRequestResult(
|
||||
oid=req.id,
|
||||
bot_id=req.bot_id,
|
||||
id=req.user_id,
|
||||
flag=req.flag,
|
||||
nickname=req.nickname,
|
||||
comment=req.comment,
|
||||
ava_url=GROUP_AVA_URL.format(req.group_id, req.group_id),
|
||||
type=str(req.request_type).lower(),
|
||||
invite_group=req.group_id,
|
||||
group_name=None,
|
||||
)
|
||||
)
|
||||
req_result.friend.reverse()
|
||||
req_result.group.reverse()
|
||||
return Result.ok(await ApiDataSource.get_request_list(), "拿到信息啦!")
|
||||
except Exception as e:
|
||||
logger.error("调用API错误", "/get_request", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
return Result.ok(req_result, f"{BotConfig.self_nickname}带来了最新的数据!")
|
||||
logger.error(f"{router.prefix}/get_request_list 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -220,23 +169,21 @@ async def _(cr: ClearRequest) -> Result:
|
||||
response_class=JSONResponse,
|
||||
description="拒绝请求",
|
||||
)
|
||||
async def _(parma: HandleRequest) -> Result:
|
||||
async def _(param: HandleRequest) -> Result:
|
||||
try:
|
||||
if bots := nonebot.get_bots():
|
||||
bot_id = parma.bot_id
|
||||
if bot_id not in nonebot.get_bots():
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
try:
|
||||
await FgRequest.refused(bots[bot_id], parma.id)
|
||||
except ActionFailed:
|
||||
await FgRequest.expire(parma.id)
|
||||
return Result.warning_("请求失败,可能该请求已失效或请求数据错误...")
|
||||
except NotFoundError:
|
||||
return Result.warning_("未找到此Id请求...")
|
||||
return Result.ok(info="成功处理了请求!")
|
||||
return Result.warning_("无Bot连接...")
|
||||
bot = nonebot.get_bot(param.bot_id)
|
||||
try:
|
||||
await FgRequest.refused(bot, param.id)
|
||||
except ActionFailed:
|
||||
await FgRequest.expire(param.id)
|
||||
return Result.warning_("请求失败,可能该请求已失效或请求数据错误...")
|
||||
except NotFoundError:
|
||||
return Result.warning_("未找到此Id请求...")
|
||||
return Result.ok(info="成功处理了请求!")
|
||||
except (ValueError, KeyError):
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
except Exception as e:
|
||||
logger.error("调用API错误", "/refuse_request", e=e)
|
||||
logger.error(f"{router.prefix}/refuse_request 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
|
||||
|
||||
@ -247,8 +194,8 @@ async def _(parma: HandleRequest) -> Result:
|
||||
response_class=JSONResponse,
|
||||
description="忽略请求",
|
||||
)
|
||||
async def _(parma: HandleRequest) -> Result:
|
||||
await FgRequest.ignore(parma.id)
|
||||
async def _(param: HandleRequest) -> Result:
|
||||
await FgRequest.ignore(param.id)
|
||||
return Result.ok(info="成功处理了请求!")
|
||||
|
||||
|
||||
@ -259,32 +206,30 @@ async def _(parma: HandleRequest) -> Result:
|
||||
response_class=JSONResponse,
|
||||
description="同意请求",
|
||||
)
|
||||
async def _(parma: HandleRequest) -> Result:
|
||||
async def _(param: HandleRequest) -> Result:
|
||||
try:
|
||||
if bots := nonebot.get_bots():
|
||||
bot_id = parma.bot_id
|
||||
if bot_id not in nonebot.get_bots():
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
if not (req := await FgRequest.get_or_none(id=parma.id)):
|
||||
return Result.warning_("未找到此Id请求...")
|
||||
if req.request_type == RequestType.GROUP:
|
||||
if group := await GroupConsole.get_group(group_id=req.group_id):
|
||||
group.group_flag = 1
|
||||
await group.save(update_fields=["group_flag"])
|
||||
else:
|
||||
await GroupConsole.update_or_create(
|
||||
group_id=req.group_id,
|
||||
defaults={"group_flag": 1},
|
||||
)
|
||||
try:
|
||||
await FgRequest.approve(bots[bot_id], parma.id)
|
||||
return Result.ok(info="成功处理了请求!")
|
||||
except ActionFailed:
|
||||
await FgRequest.expire(parma.id)
|
||||
return Result.warning_("请求失败,可能该请求已失效或请求数据错误...")
|
||||
return Result.warning_("无Bot连接...")
|
||||
bot = nonebot.get_bot(param.bot_id)
|
||||
if not (req := await FgRequest.get_or_none(id=param.id)):
|
||||
return Result.warning_("未找到此Id请求...")
|
||||
if req.request_type == RequestType.GROUP:
|
||||
if group := await GroupConsole.get_group(group_id=req.group_id):
|
||||
group.group_flag = 1
|
||||
await group.save(update_fields=["group_flag"])
|
||||
else:
|
||||
await GroupConsole.update_or_create(
|
||||
group_id=req.group_id,
|
||||
defaults={"group_flag": 1},
|
||||
)
|
||||
try:
|
||||
await FgRequest.approve(bot, param.id)
|
||||
return Result.ok(info="成功处理了请求!")
|
||||
except ActionFailed:
|
||||
await FgRequest.expire(param.id)
|
||||
return Result.warning_("请求失败,可能该请求已失效或请求数据错误...")
|
||||
except (ValueError, KeyError):
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
except Exception as e:
|
||||
logger.error("调用API错误", "/approve_request", e=e)
|
||||
logger.error(f"{router.prefix}/approve_request 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
|
||||
|
||||
@ -297,19 +242,19 @@ async def _(parma: HandleRequest) -> Result:
|
||||
)
|
||||
async def _(param: LeaveGroup) -> Result:
|
||||
try:
|
||||
if bots := nonebot.get_bots():
|
||||
bot_id = param.bot_id
|
||||
platform = PlatformUtils.get_platform(bots[bot_id])
|
||||
if platform != "qq":
|
||||
return Result.warning_("该平台不支持退群操作...")
|
||||
group_list = await bots[bot_id].get_group_list()
|
||||
if param.group_id not in [str(g["group_id"]) for g in group_list]:
|
||||
return Result.warning_("Bot未在该群聊中...")
|
||||
await bots[bot_id].set_group_leave(group_id=param.group_id)
|
||||
return Result.ok(info="成功处理了请求!")
|
||||
return Result.warning_("无Bot连接...")
|
||||
bot = nonebot.get_bot(param.bot_id)
|
||||
platform = PlatformUtils.get_platform(bot)
|
||||
if platform != "qq":
|
||||
return Result.warning_("该平台不支持退群操作...")
|
||||
group_list, _ = await PlatformUtils.get_group_list(bot)
|
||||
if param.group_id not in [g.group_id for g in group_list]:
|
||||
return Result.warning_("Bot未在该群聊中...")
|
||||
await bot.set_group_leave(group_id=param.group_id)
|
||||
return Result.ok(info="成功处理了请求!")
|
||||
except (ValueError, KeyError):
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
except Exception as e:
|
||||
logger.error("调用API错误", "/leave_group", e=e)
|
||||
logger.error(f"{router.prefix}/leave_group 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
|
||||
|
||||
@ -322,19 +267,19 @@ async def _(param: LeaveGroup) -> Result:
|
||||
)
|
||||
async def _(param: DeleteFriend) -> Result:
|
||||
try:
|
||||
if bots := nonebot.get_bots():
|
||||
bot_id = param.bot_id
|
||||
platform = PlatformUtils.get_platform(bots[bot_id])
|
||||
if platform != "qq":
|
||||
return Result.warning_("该平台不支持删除好友操作...")
|
||||
friend_list = await bots[bot_id].get_friend_list()
|
||||
if param.user_id not in [str(g["user_id"]) for g in friend_list]:
|
||||
return Result.warning_("Bot未有其好友...")
|
||||
await bots[bot_id].delete_friend(user_id=param.user_id)
|
||||
return Result.ok(info="成功处理了请求!")
|
||||
return Result.warning_("Bot未连接...")
|
||||
bot = nonebot.get_bot(param.bot_id)
|
||||
platform = PlatformUtils.get_platform(bot)
|
||||
if platform != "qq":
|
||||
return Result.warning_("该平台不支持删除好友操作...")
|
||||
friend_list, _ = await PlatformUtils.get_friend_list(bot)
|
||||
if param.user_id not in [f.user_id for f in friend_list]:
|
||||
return Result.warning_("Bot未有其好友...")
|
||||
await bot.delete_friend(user_id=param.user_id)
|
||||
return Result.ok(info="成功处理了请求!")
|
||||
except (ValueError, KeyError):
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
except Exception as e:
|
||||
logger.error("调用API错误", "/delete_friend", e=e)
|
||||
logger.error(f"{router.prefix}/delete_friend 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
|
||||
|
||||
@ -346,43 +291,18 @@ async def _(param: DeleteFriend) -> Result:
|
||||
description="获取好友详情",
|
||||
)
|
||||
async def _(bot_id: str, user_id: str) -> Result[UserDetail]:
|
||||
if bots := nonebot.get_bots():
|
||||
if bot_id in bots:
|
||||
if fd := [
|
||||
x
|
||||
for x in await bots[bot_id].get_friend_list()
|
||||
if str(x["user_id"]) == user_id
|
||||
]:
|
||||
like_plugin_list = (
|
||||
await Statistics.filter(user_id=user_id)
|
||||
.annotate(count=Count("id"))
|
||||
.group_by("plugin_name")
|
||||
.order_by("-count")
|
||||
.limit(5)
|
||||
.values_list("plugin_name", "count")
|
||||
)
|
||||
like_plugin = {}
|
||||
module_list = [x[0] for x in like_plugin_list]
|
||||
plugins = await PluginInfo.filter(module__in=module_list).all()
|
||||
module2name = {p.module: p.name for p in plugins}
|
||||
for data in like_plugin_list:
|
||||
name = module2name.get(data[0]) or data[0]
|
||||
like_plugin[name] = data[1]
|
||||
user = fd[0]
|
||||
user_detail = UserDetail(
|
||||
user_id=user_id,
|
||||
ava_url=AVA_URL.format(user_id),
|
||||
nickname=user["nickname"],
|
||||
remark=user["remark"],
|
||||
is_ban=await BanConsole.is_ban(user_id),
|
||||
chat_count=await ChatHistory.filter(user_id=user_id).count(),
|
||||
call_count=await Statistics.filter(user_id=user_id).count(),
|
||||
like_plugin=like_plugin,
|
||||
)
|
||||
return Result.ok(user_detail)
|
||||
else:
|
||||
return Result.warning_("未添加指定好友...")
|
||||
return Result.warning_("无Bot连接...")
|
||||
try:
|
||||
result = await ApiDataSource.get_friend_detail(bot_id, user_id)
|
||||
return (
|
||||
Result.ok(result, "拿到信息啦!")
|
||||
if result
|
||||
else Result.warning_("未找到该好友...")
|
||||
)
|
||||
except (ValueError, KeyError):
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_friend_detail 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -392,90 +312,12 @@ async def _(bot_id: str, user_id: str) -> Result[UserDetail]:
|
||||
response_class=JSONResponse,
|
||||
description="获取群组详情",
|
||||
)
|
||||
async def _(bot_id: str, group_id: str) -> Result[GroupDetail]:
|
||||
if not (bots := nonebot.get_bots()):
|
||||
return Result.warning_("无Bot连接...")
|
||||
if bot_id not in bots:
|
||||
return Result.warning_("未添加指定群组...")
|
||||
group = await GroupConsole.get_or_none(group_id=group_id)
|
||||
if not group:
|
||||
return Result.warning_("指定群组未被收录...")
|
||||
like_plugin_list = (
|
||||
await Statistics.filter(group_id=group_id)
|
||||
.annotate(count=Count("id"))
|
||||
.group_by("plugin_name")
|
||||
.order_by("-count")
|
||||
.limit(5)
|
||||
.values_list("plugin_name", "count")
|
||||
)
|
||||
like_plugin = {}
|
||||
plugins = await PluginInfo.get_plugins()
|
||||
module2name = {p.module: p.name for p in plugins}
|
||||
for data in like_plugin_list:
|
||||
name = module2name.get(data[0]) or data[0]
|
||||
like_plugin[name] = data[1]
|
||||
close_plugins: list[Plugin] = []
|
||||
if group.block_plugin:
|
||||
for module in group.block_plugin.replace("<", "").split(","):
|
||||
if module:
|
||||
plugin = Plugin(
|
||||
module=module,
|
||||
plugin_name=module,
|
||||
is_super_block=False,
|
||||
)
|
||||
plugin.plugin_name = module2name.get(module) or module
|
||||
close_plugins.append(plugin)
|
||||
exists_modules = [p.module for p in close_plugins]
|
||||
if group.superuser_block_plugin:
|
||||
for module in group.superuser_block_plugin.replace("<", "").split(","):
|
||||
if module and module not in exists_modules:
|
||||
plugin = Plugin(
|
||||
module=module,
|
||||
plugin_name=module,
|
||||
is_super_block=True,
|
||||
)
|
||||
plugin.plugin_name = module2name.get(module) or module
|
||||
close_plugins.append(plugin)
|
||||
all_task = await TaskInfo.annotate().values_list("module", "name")
|
||||
task_module2name = {x[0]: x[1] for x in all_task}
|
||||
task_list = []
|
||||
if group.block_task or group.superuser_block_plugin:
|
||||
sbp = group.superuser_block_plugin.replace("<", "").split(",")
|
||||
split_task = group.block_task.replace("<", "").split(",")
|
||||
for task in all_task:
|
||||
task_list.append(
|
||||
Task(
|
||||
name=task[0],
|
||||
zh_name=task_module2name.get(task[0]) or task[0],
|
||||
status=task[0] not in split_task and task[0] not in sbp,
|
||||
is_super_block=task[0] in sbp,
|
||||
)
|
||||
)
|
||||
else:
|
||||
for task in all_task:
|
||||
task_list.append(
|
||||
Task(
|
||||
name=task[0],
|
||||
zh_name=task_module2name.get(task[0]) or task[0],
|
||||
status=True,
|
||||
is_super_block=False,
|
||||
)
|
||||
)
|
||||
group_detail = GroupDetail(
|
||||
group_id=group_id,
|
||||
ava_url=GROUP_AVA_URL.format(group_id, group_id),
|
||||
name=group.group_name,
|
||||
member_count=group.member_count,
|
||||
max_member_count=group.max_member_count,
|
||||
chat_count=await ChatHistory.filter(group_id=group_id).count(),
|
||||
call_count=await Statistics.filter(group_id=group_id).count(),
|
||||
like_plugin=like_plugin,
|
||||
level=group.level,
|
||||
status=group.status,
|
||||
close_plugins=close_plugins,
|
||||
task=task_list,
|
||||
)
|
||||
return Result.ok(group_detail)
|
||||
async def _(group_id: str) -> Result[GroupDetail]:
|
||||
try:
|
||||
return Result.ok(await ApiDataSource.get_group_detail(group_id), "拿到信息啦!")
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_group_detail 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -483,25 +325,17 @@ async def _(bot_id: str, group_id: str) -> Result[GroupDetail]:
|
||||
dependencies=[authentication()],
|
||||
response_model=Result,
|
||||
response_class=JSONResponse,
|
||||
description="获取群组详情",
|
||||
description="发送消息",
|
||||
)
|
||||
async def _(param: SendMessage) -> Result:
|
||||
if not (bots := nonebot.get_bots()):
|
||||
return Result.warning_("无Bot连接...")
|
||||
if param.bot_id in bots:
|
||||
platform = PlatformUtils.get_platform(bots[param.bot_id])
|
||||
if platform != "qq":
|
||||
return Result.warning_("暂不支持该平台...")
|
||||
try:
|
||||
if param.user_id:
|
||||
await bots[param.bot_id].send_private_msg(
|
||||
user_id=str(param.user_id), message=param.message
|
||||
)
|
||||
else:
|
||||
await bots[param.bot_id].send_group_msg(
|
||||
group_id=str(param.group_id), message=param.message
|
||||
)
|
||||
except Exception as e:
|
||||
return Result.fail(str(e))
|
||||
async def _(param: SendMessageParam) -> Result:
|
||||
try:
|
||||
bot = nonebot.get_bot(param.bot_id)
|
||||
await PlatformUtils.send_message(
|
||||
bot, param.user_id, param.group_id, param.message
|
||||
)
|
||||
return Result.ok("发送成功!")
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
except (ValueError, KeyError):
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/send_message 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
@ -3,7 +3,7 @@ import nonebot
|
||||
from nonebot import on_message
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent
|
||||
from nonebot_plugin_alconna import At, Hyper, Image, Text, UniMsg
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_uninfo import Uninfo
|
||||
from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState
|
||||
|
||||
from zhenxun.models.group_member_info import GroupInfoUser
|
||||
@ -28,7 +28,7 @@ matcher = on_message(block=False, priority=1, rule=lambda: bool(ws_conn))
|
||||
|
||||
@driver.on_shutdown
|
||||
async def _():
|
||||
if ws_conn:
|
||||
if ws_conn and ws_conn.client_state == WebSocketState.CONNECTED:
|
||||
await ws_conn.close()
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ async def _():
|
||||
async def _(websocket: WebSocket):
|
||||
global ws_conn
|
||||
await websocket.accept()
|
||||
if not ws_conn:
|
||||
if not ws_conn or ws_conn.client_state != WebSocketState.CONNECTED:
|
||||
ws_conn = websocket
|
||||
try:
|
||||
while websocket.client_state == WebSocketState.CONNECTED:
|
||||
@ -80,25 +80,24 @@ async def message_handle(
|
||||
|
||||
@matcher.handle()
|
||||
async def _(
|
||||
message: UniMsg, event: MessageEvent, session: EventSession, uname: str = UserName()
|
||||
message: UniMsg, event: MessageEvent, session: Uninfo, uname: str = UserName()
|
||||
):
|
||||
global ws_conn, ID2NAME, ID_LIST
|
||||
uid = session.id1
|
||||
if ws_conn and ws_conn.client_state == WebSocketState.CONNECTED and uid:
|
||||
if ws_conn and ws_conn.client_state == WebSocketState.CONNECTED:
|
||||
msg_id = event.message_id
|
||||
if msg_id in ID_LIST:
|
||||
return
|
||||
ID_LIST.append(msg_id)
|
||||
if len(ID_LIST) > 50:
|
||||
ID_LIST = ID_LIST[40:]
|
||||
gid = session.id3 or session.id2
|
||||
gid = session.group.id if session.group else None
|
||||
messages = await message_handle(message, gid)
|
||||
data = Message(
|
||||
object_id=gid or uid,
|
||||
user_id=uid,
|
||||
object_id=gid or session.user.id,
|
||||
user_id=session.user.id,
|
||||
group_id=gid,
|
||||
message=messages,
|
||||
name=uname,
|
||||
ava_url=AVA_URL.format(uid),
|
||||
ava_url=AVA_URL.format(session.user.id),
|
||||
)
|
||||
await ws_conn.send_json(data.dict())
|
||||
|
||||
274
zhenxun/builtin_plugins/web_ui/api/tabs/manage/data_source.py
Normal file
274
zhenxun/builtin_plugins/web_ui/api/tabs/manage/data_source.py
Normal file
@ -0,0 +1,274 @@
|
||||
import nonebot
|
||||
from tortoise.functions import Count
|
||||
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
from zhenxun.models.fg_request import FgRequest
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.statistics import Statistics
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.utils.common_utils import CommonUtils
|
||||
from zhenxun.utils.enum import RequestType
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
|
||||
from ....config import AVA_URL, GROUP_AVA_URL
|
||||
from .model import (
|
||||
FriendRequestResult,
|
||||
GroupDetail,
|
||||
GroupRequestResult,
|
||||
Plugin,
|
||||
ReqResult,
|
||||
Task,
|
||||
UpdateGroup,
|
||||
UserDetail,
|
||||
)
|
||||
|
||||
|
||||
class ApiDataSource:
|
||||
@classmethod
|
||||
async def update_group(cls, group: UpdateGroup):
|
||||
"""更新群组数据
|
||||
|
||||
参数:
|
||||
group: UpdateGroup
|
||||
"""
|
||||
db_group = await GroupConsole.get_group(group.group_id) or GroupConsole(
|
||||
group_id=group.group_id
|
||||
)
|
||||
task_list = await TaskInfo.all().values_list("module", flat=True)
|
||||
db_group.level = group.level
|
||||
db_group.status = group.status
|
||||
if group.close_plugins:
|
||||
db_group.block_plugin = CommonUtils.convert_module_format(
|
||||
group.close_plugins
|
||||
)
|
||||
else:
|
||||
db_group.block_plugin = ""
|
||||
if group.task:
|
||||
if block_task := [t for t in task_list if t not in group.task]:
|
||||
db_group.block_task = CommonUtils.convert_module_format(block_task) # type: ignore
|
||||
else:
|
||||
db_group.block_task = CommonUtils.convert_module_format(task_list) # type: ignore
|
||||
await db_group.save()
|
||||
|
||||
@classmethod
|
||||
async def get_request_list(cls) -> ReqResult:
|
||||
"""获取好友与群组请求列表
|
||||
|
||||
返回:
|
||||
ReqResult: 数据内容
|
||||
"""
|
||||
req_result = ReqResult()
|
||||
data_list = await FgRequest.filter(handle_type__isnull=True).all()
|
||||
for req in data_list:
|
||||
if req.request_type == RequestType.FRIEND:
|
||||
req_result.friend.append(
|
||||
FriendRequestResult(
|
||||
oid=req.id,
|
||||
bot_id=req.bot_id,
|
||||
id=req.user_id,
|
||||
flag=req.flag,
|
||||
nickname=req.nickname,
|
||||
comment=req.comment,
|
||||
ava_url=AVA_URL.format(req.user_id),
|
||||
type=str(req.request_type).lower(),
|
||||
)
|
||||
)
|
||||
else:
|
||||
req_result.group.append(
|
||||
GroupRequestResult(
|
||||
oid=req.id,
|
||||
bot_id=req.bot_id,
|
||||
id=req.user_id,
|
||||
flag=req.flag,
|
||||
nickname=req.nickname,
|
||||
comment=req.comment,
|
||||
ava_url=GROUP_AVA_URL.format(req.group_id, req.group_id),
|
||||
type=str(req.request_type).lower(),
|
||||
invite_group=req.group_id,
|
||||
group_name=None,
|
||||
)
|
||||
)
|
||||
req_result.friend.reverse()
|
||||
req_result.group.reverse()
|
||||
return req_result
|
||||
|
||||
@classmethod
|
||||
async def get_friend_detail(cls, bot_id: str, user_id: str) -> UserDetail | None:
|
||||
"""获取好友详情
|
||||
|
||||
参数:
|
||||
bot_id: bot id
|
||||
user_id: 用户id
|
||||
|
||||
返回:
|
||||
UserDetail | None: 详情数据
|
||||
"""
|
||||
bot = nonebot.get_bot(bot_id)
|
||||
friend_list, _ = await PlatformUtils.get_friend_list(bot)
|
||||
fd = [x for x in friend_list if x == user_id]
|
||||
if not fd:
|
||||
return None
|
||||
like_plugin_list = (
|
||||
await Statistics.filter(user_id=user_id)
|
||||
.annotate(count=Count("id"))
|
||||
.group_by("plugin_name")
|
||||
.order_by("-count")
|
||||
.limit(5)
|
||||
.values_list("plugin_name", "count")
|
||||
)
|
||||
like_plugin = {}
|
||||
module_list = [x[0] for x in like_plugin_list]
|
||||
plugins = await PluginInfo.filter(module__in=module_list).all()
|
||||
module2name = {p.module: p.name for p in plugins}
|
||||
for data in like_plugin_list:
|
||||
name = module2name.get(data[0]) or data[0]
|
||||
like_plugin[name] = data[1]
|
||||
user = fd[0]
|
||||
return UserDetail(
|
||||
user_id=user_id,
|
||||
ava_url=AVA_URL.format(user_id),
|
||||
nickname=user.user_name,
|
||||
remark="",
|
||||
is_ban=await BanConsole.is_ban(user_id),
|
||||
chat_count=await ChatHistory.filter(user_id=user_id).count(),
|
||||
call_count=await Statistics.filter(user_id=user_id).count(),
|
||||
like_plugin=like_plugin,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def __get_group_detail_like_plugin(cls, group_id: str) -> dict[str, int]:
|
||||
"""获取群组喜爱的插件
|
||||
|
||||
参数:
|
||||
group_id: 群组id
|
||||
|
||||
返回:
|
||||
dict[str, int]: 插件与调用次数
|
||||
"""
|
||||
like_plugin_list = (
|
||||
await Statistics.filter(group_id=group_id)
|
||||
.annotate(count=Count("id"))
|
||||
.group_by("plugin_name")
|
||||
.order_by("-count")
|
||||
.limit(5)
|
||||
.values_list("plugin_name", "count")
|
||||
)
|
||||
like_plugin = {}
|
||||
plugins = await PluginInfo.get_plugins()
|
||||
module2name = {p.module: p.name for p in plugins}
|
||||
for data in like_plugin_list:
|
||||
name = module2name.get(data[0]) or data[0]
|
||||
like_plugin[name] = data[1]
|
||||
return like_plugin
|
||||
|
||||
@classmethod
|
||||
async def __get_group_detail_disable_plugin(
|
||||
cls, group: GroupConsole
|
||||
) -> list[Plugin]:
|
||||
"""获取群组禁用插件
|
||||
|
||||
参数:
|
||||
group: GroupConsole
|
||||
|
||||
返回:
|
||||
list[Plugin]: 禁用插件数据列表
|
||||
"""
|
||||
disable_plugins: list[Plugin] = []
|
||||
plugins = await PluginInfo.get_plugins()
|
||||
module2name = {p.module: p.name for p in plugins}
|
||||
if group.block_plugin:
|
||||
for module in CommonUtils.convert_module_format(group.block_plugin):
|
||||
if module:
|
||||
plugin = Plugin(
|
||||
module=module,
|
||||
plugin_name=module,
|
||||
is_super_block=False,
|
||||
)
|
||||
plugin.plugin_name = module2name.get(module) or module
|
||||
disable_plugins.append(plugin)
|
||||
exists_modules = [p.module for p in disable_plugins]
|
||||
if group.superuser_block_plugin:
|
||||
for module in CommonUtils.convert_module_format(
|
||||
group.superuser_block_plugin
|
||||
):
|
||||
if module and module not in exists_modules:
|
||||
plugin = Plugin(
|
||||
module=module,
|
||||
plugin_name=module,
|
||||
is_super_block=True,
|
||||
)
|
||||
plugin.plugin_name = module2name.get(module) or module
|
||||
disable_plugins.append(plugin)
|
||||
return disable_plugins
|
||||
|
||||
@classmethod
|
||||
async def __get_group_detail_task(cls, group: GroupConsole) -> list[Task]:
|
||||
"""获取群组被动技能状态
|
||||
|
||||
参数:
|
||||
group: GroupConsole
|
||||
|
||||
返回:
|
||||
list[Task]: 群组被动列表
|
||||
"""
|
||||
all_task = await TaskInfo.annotate().values_list("module", "name")
|
||||
task_module2name = {x[0]: x[1] for x in all_task}
|
||||
task_list = []
|
||||
if group.block_task or group.superuser_block_plugin:
|
||||
sbp = CommonUtils.convert_module_format(group.superuser_block_task)
|
||||
tasks = CommonUtils.convert_module_format(group.block_task)
|
||||
task_list.extend(
|
||||
Task(
|
||||
name=task[0],
|
||||
zh_name=task_module2name.get(task[0]) or task[0],
|
||||
status=task[0] not in tasks and task[0] not in sbp,
|
||||
is_super_block=task[0] in sbp,
|
||||
)
|
||||
for task in all_task
|
||||
)
|
||||
else:
|
||||
task_list.extend(
|
||||
Task(
|
||||
name=task[0],
|
||||
zh_name=task_module2name.get(task[0]) or task[0],
|
||||
status=True,
|
||||
is_super_block=False,
|
||||
)
|
||||
for task in all_task
|
||||
)
|
||||
return task_list
|
||||
|
||||
@classmethod
|
||||
async def get_group_detail(cls, group_id: str) -> GroupDetail | None:
|
||||
"""获取群组详情
|
||||
|
||||
参数:
|
||||
group_id: 群组id
|
||||
|
||||
返回:
|
||||
GroupDetail | None: 群组详情数据
|
||||
"""
|
||||
group = await GroupConsole.get_or_none(group_id=group_id)
|
||||
if not group:
|
||||
return None
|
||||
like_plugin = await cls.__get_group_detail_like_plugin(group_id)
|
||||
disable_plugins: list[Plugin] = await cls.__get_group_detail_disable_plugin(
|
||||
group
|
||||
)
|
||||
task_list = await cls.__get_group_detail_task(group)
|
||||
return GroupDetail(
|
||||
group_id=group_id,
|
||||
ava_url=GROUP_AVA_URL.format(group_id, group_id),
|
||||
name=group.group_name,
|
||||
member_count=group.member_count,
|
||||
max_member_count=group.max_member_count,
|
||||
chat_count=await ChatHistory.filter(group_id=group_id).count(),
|
||||
call_count=await Statistics.filter(group_id=group_id).count(),
|
||||
like_plugin=like_plugin,
|
||||
level=group.level,
|
||||
status=group.status,
|
||||
close_plugins=disable_plugins,
|
||||
task=task_list,
|
||||
)
|
||||
@ -257,7 +257,7 @@ class Message(BaseModel):
|
||||
"""用户头像"""
|
||||
|
||||
|
||||
class SendMessage(BaseModel):
|
||||
class SendMessageParam(BaseModel):
|
||||
"""
|
||||
发送消息
|
||||
"""
|
||||
|
||||
@ -1,18 +1,14 @@
|
||||
import re
|
||||
|
||||
import cattrs
|
||||
from fastapi import APIRouter, Query
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import BlockType, PluginType
|
||||
|
||||
from ....base_model import Result
|
||||
from ....utils import authentication
|
||||
from .data_source import ApiDataSource
|
||||
from .model import (
|
||||
PluginConfig,
|
||||
PluginCount,
|
||||
PluginDetail,
|
||||
PluginInfo,
|
||||
@ -34,31 +30,12 @@ async def _(
|
||||
plugin_type: list[PluginType] = Query(None), menu_type: str | None = None
|
||||
) -> Result[list[PluginInfo]]:
|
||||
try:
|
||||
plugin_list: list[PluginInfo] = []
|
||||
query = DbPluginInfo
|
||||
if plugin_type:
|
||||
query = query.filter(plugin_type__in=plugin_type, load_status=True)
|
||||
if menu_type:
|
||||
query = query.filter(menu_type=menu_type)
|
||||
plugins = await query.all()
|
||||
for plugin in plugins:
|
||||
plugin_info = PluginInfo(
|
||||
module=plugin.module,
|
||||
plugin_name=plugin.name,
|
||||
default_status=plugin.default_status,
|
||||
limit_superuser=plugin.limit_superuser,
|
||||
cost_gold=plugin.cost_gold,
|
||||
menu_type=plugin.menu_type,
|
||||
version=plugin.version or "0",
|
||||
level=plugin.level,
|
||||
status=plugin.status,
|
||||
author=plugin.author,
|
||||
)
|
||||
plugin_list.append(plugin_info)
|
||||
return Result.ok(
|
||||
await ApiDataSource.get_plugin_list(plugin_type, menu_type), "拿到信息啦!"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("调用API错误", "/get_plugins", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
return Result.ok(plugin_list, "拿到了新鲜出炉的数据!")
|
||||
logger.error(f"{router.prefix}/get_plugin_list 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -69,21 +46,26 @@ async def _(
|
||||
deprecated="获取插件数量", # type: ignore
|
||||
)
|
||||
async def _() -> Result[int]:
|
||||
plugin_count = PluginCount()
|
||||
plugin_count.normal = await DbPluginInfo.filter(
|
||||
plugin_type=PluginType.NORMAL, load_status=True
|
||||
).count()
|
||||
plugin_count.admin = await DbPluginInfo.filter(
|
||||
plugin_type__in=[PluginType.ADMIN, PluginType.SUPER_AND_ADMIN], load_status=True
|
||||
).count()
|
||||
plugin_count.superuser = await DbPluginInfo.filter(
|
||||
plugin_type__in=[PluginType.SUPERUSER, PluginType.SUPER_AND_ADMIN],
|
||||
load_status=True,
|
||||
).count()
|
||||
plugin_count.other = await DbPluginInfo.filter(
|
||||
plugin_type__in=[PluginType.HIDDEN, PluginType.DEPENDANT], load_status=True
|
||||
).count()
|
||||
return Result.ok(plugin_count)
|
||||
try:
|
||||
plugin_count = PluginCount()
|
||||
plugin_count.normal = await DbPluginInfo.filter(
|
||||
plugin_type=PluginType.NORMAL, load_status=True
|
||||
).count()
|
||||
plugin_count.admin = await DbPluginInfo.filter(
|
||||
plugin_type__in=[PluginType.ADMIN, PluginType.SUPER_AND_ADMIN],
|
||||
load_status=True,
|
||||
).count()
|
||||
plugin_count.superuser = await DbPluginInfo.filter(
|
||||
plugin_type__in=[PluginType.SUPERUSER, PluginType.SUPER_AND_ADMIN],
|
||||
load_status=True,
|
||||
).count()
|
||||
plugin_count.other = await DbPluginInfo.filter(
|
||||
plugin_type__in=[PluginType.HIDDEN, PluginType.DEPENDANT], load_status=True
|
||||
).count()
|
||||
return Result.ok(plugin_count, "拿到信息啦!")
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_plugin_count 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -93,33 +75,15 @@ async def _() -> Result[int]:
|
||||
response_class=JSONResponse,
|
||||
description="更新插件参数",
|
||||
)
|
||||
async def _(plugin: UpdatePlugin) -> Result:
|
||||
async def _(param: UpdatePlugin) -> Result:
|
||||
try:
|
||||
db_plugin = await DbPluginInfo.get_or_none(
|
||||
module=plugin.module, load_status=True
|
||||
)
|
||||
if not db_plugin:
|
||||
return Result.fail("插件不存在...")
|
||||
db_plugin.default_status = plugin.default_status
|
||||
db_plugin.limit_superuser = plugin.limit_superuser
|
||||
db_plugin.cost_gold = plugin.cost_gold
|
||||
db_plugin.level = plugin.level
|
||||
db_plugin.menu_type = plugin.menu_type
|
||||
db_plugin.block_type = plugin.block_type
|
||||
db_plugin.status = plugin.block_type != BlockType.ALL
|
||||
await db_plugin.save()
|
||||
# 配置项
|
||||
if plugin.configs and (configs := Config.get(plugin.module)):
|
||||
for key in plugin.configs:
|
||||
if c := configs.configs.get(key):
|
||||
value = plugin.configs[key]
|
||||
if c.type and value is not None:
|
||||
value = cattrs.structure(value, c.type)
|
||||
Config.set_config(plugin.module, key, value)
|
||||
await ApiDataSource.update_plugin(param)
|
||||
return Result.ok(info="已经帮你写好啦!")
|
||||
except (ValueError, KeyError):
|
||||
return Result.fail("插件数据不存在...")
|
||||
except Exception as e:
|
||||
logger.error("调用API错误", "/update_plugins", e=e)
|
||||
logger.error(f"{router.prefix}/update_plugin 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
return Result.ok(info="已经帮你写好啦!")
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -130,17 +94,21 @@ async def _(plugin: UpdatePlugin) -> Result:
|
||||
description="开关插件",
|
||||
)
|
||||
async def _(param: PluginSwitch) -> Result:
|
||||
db_plugin = await DbPluginInfo.get_or_none(module=param.module, load_status=True)
|
||||
if not db_plugin:
|
||||
return Result.fail("插件不存在...")
|
||||
if not param.status:
|
||||
db_plugin.block_type = BlockType.ALL
|
||||
db_plugin.status = False
|
||||
else:
|
||||
db_plugin.block_type = None
|
||||
db_plugin.status = True
|
||||
await db_plugin.save()
|
||||
return Result.ok(info="成功改变了开关状态!")
|
||||
try:
|
||||
db_plugin = await DbPluginInfo.get_plugin(module=param.module)
|
||||
if not db_plugin:
|
||||
return Result.fail("插件不存在...")
|
||||
if not param.status:
|
||||
db_plugin.block_type = BlockType.ALL
|
||||
db_plugin.status = False
|
||||
else:
|
||||
db_plugin.block_type = None
|
||||
db_plugin.status = True
|
||||
await db_plugin.save()
|
||||
return Result.ok(info="成功改变了开关状态!")
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/change_switch 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -151,12 +119,20 @@ async def _(param: PluginSwitch) -> Result:
|
||||
description="获取插件类型",
|
||||
)
|
||||
async def _() -> Result[list[str]]:
|
||||
menu_type_list = []
|
||||
result = await DbPluginInfo.annotate().values_list("menu_type", flat=True)
|
||||
for r in result:
|
||||
if r not in menu_type_list and r:
|
||||
menu_type_list.append(r)
|
||||
return Result.ok(menu_type_list)
|
||||
try:
|
||||
menu_type_list = []
|
||||
result = (
|
||||
await DbPluginInfo.filter(load_status=True)
|
||||
.annotate()
|
||||
.values_list("menu_type", flat=True)
|
||||
)
|
||||
for r in result:
|
||||
if r not in menu_type_list and r:
|
||||
menu_type_list.append(r)
|
||||
return Result.ok(menu_type_list)
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_plugin_menu_type 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -167,46 +143,12 @@ async def _() -> Result[list[str]]:
|
||||
description="获取插件详情",
|
||||
)
|
||||
async def _(module: str) -> Result[PluginDetail]:
|
||||
db_plugin = await DbPluginInfo.get_or_none(module=module, load_status=True)
|
||||
if not db_plugin:
|
||||
return Result.fail("插件不存在...")
|
||||
config_list = []
|
||||
if config := Config.get(module):
|
||||
for cfg in config.configs:
|
||||
type_str = ""
|
||||
type_inner = None
|
||||
if r := re.search(r"<class '(.*)'>", str(config.configs[cfg].type)):
|
||||
type_str = r[1]
|
||||
elif r := re.search(r"typing\.(.*)\[(.*)\]", str(config.configs[cfg].type)):
|
||||
type_str = r[1]
|
||||
if type_str:
|
||||
type_str = type_str.lower()
|
||||
type_inner = r[2]
|
||||
if type_inner:
|
||||
type_inner = [x.strip() for x in type_inner.split(",")]
|
||||
config_list.append(
|
||||
PluginConfig(
|
||||
module=module,
|
||||
key=cfg,
|
||||
value=config.configs[cfg].value,
|
||||
help=config.configs[cfg].help,
|
||||
default_value=config.configs[cfg].default_value,
|
||||
type=type_str,
|
||||
type_inner=type_inner, # type: ignore
|
||||
)
|
||||
)
|
||||
plugin_info = PluginDetail(
|
||||
module=module,
|
||||
plugin_name=db_plugin.name,
|
||||
default_status=db_plugin.default_status,
|
||||
limit_superuser=db_plugin.limit_superuser,
|
||||
cost_gold=db_plugin.cost_gold,
|
||||
menu_type=db_plugin.menu_type,
|
||||
version=db_plugin.version or "0",
|
||||
level=db_plugin.level,
|
||||
status=db_plugin.status,
|
||||
author=db_plugin.author,
|
||||
config_list=config_list,
|
||||
block_type=db_plugin.block_type,
|
||||
)
|
||||
return Result.ok(plugin_info)
|
||||
try:
|
||||
return Result.ok(
|
||||
await ApiDataSource.get_plugin_detail(module), "已经帮你写好啦!"
|
||||
)
|
||||
except (ValueError, KeyError):
|
||||
return Result.fail("插件数据不存在...")
|
||||
except Exception as e:
|
||||
logger.error(f"{router.prefix}/get_plugin 调用错误", "WebUi", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
|
||||
@ -0,0 +1,152 @@
|
||||
import re
|
||||
|
||||
import cattrs
|
||||
from fastapi import Query
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.utils import ConfigGroup
|
||||
from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo
|
||||
from zhenxun.utils.enum import BlockType, PluginType
|
||||
|
||||
from .model import PluginConfig, PluginDetail, PluginInfo, UpdatePlugin
|
||||
|
||||
|
||||
class ApiDataSource:
|
||||
@classmethod
|
||||
async def get_plugin_list(
|
||||
cls, plugin_type: list[PluginType] = Query(None), menu_type: str | None = None
|
||||
) -> list[PluginInfo]:
|
||||
"""获取插件列表
|
||||
|
||||
参数:
|
||||
plugin_type: 插件类型.
|
||||
menu_type: 菜单类型.
|
||||
|
||||
返回:
|
||||
list[PluginInfo]: 插件数据列表
|
||||
"""
|
||||
plugin_list: list[PluginInfo] = []
|
||||
query = DbPluginInfo
|
||||
if plugin_type:
|
||||
query = query.filter(plugin_type__in=plugin_type, load_status=True)
|
||||
if menu_type:
|
||||
query = query.filter(menu_type=menu_type, load_status=True)
|
||||
plugins = await query.all()
|
||||
for plugin in plugins:
|
||||
plugin_info = PluginInfo(
|
||||
module=plugin.module,
|
||||
plugin_name=plugin.name,
|
||||
default_status=plugin.default_status,
|
||||
limit_superuser=plugin.limit_superuser,
|
||||
cost_gold=plugin.cost_gold,
|
||||
menu_type=plugin.menu_type,
|
||||
version=plugin.version or "0",
|
||||
level=plugin.level,
|
||||
status=plugin.status,
|
||||
author=plugin.author,
|
||||
)
|
||||
plugin_list.append(plugin_info)
|
||||
return plugin_list
|
||||
|
||||
@classmethod
|
||||
async def update_plugin(cls, param: UpdatePlugin) -> DbPluginInfo:
|
||||
"""更新插件数据
|
||||
|
||||
参数:
|
||||
param: UpdatePlugin
|
||||
|
||||
返回:
|
||||
DbPluginInfo | None: 插件数据
|
||||
"""
|
||||
db_plugin = await DbPluginInfo.get_plugin(module=param.module)
|
||||
if not db_plugin:
|
||||
raise ValueError("插件不存在")
|
||||
db_plugin.default_status = param.default_status
|
||||
db_plugin.limit_superuser = param.limit_superuser
|
||||
db_plugin.cost_gold = param.cost_gold
|
||||
db_plugin.level = param.level
|
||||
db_plugin.menu_type = param.menu_type
|
||||
db_plugin.block_type = param.block_type
|
||||
db_plugin.status = param.block_type != BlockType.ALL
|
||||
await db_plugin.save()
|
||||
# 配置项
|
||||
if param.configs and (configs := Config.get(param.module)):
|
||||
for key in param.configs:
|
||||
if c := configs.configs.get(key):
|
||||
value = param.configs[key]
|
||||
if c.type and value is not None:
|
||||
value = cattrs.structure(value, c.type)
|
||||
Config.set_config(param.module, key, value)
|
||||
Config.save(save_simple_data=True)
|
||||
return db_plugin
|
||||
|
||||
@classmethod
|
||||
def __build_plugin_config(
|
||||
cls, module: str, cfg: str, config: ConfigGroup
|
||||
) -> PluginConfig:
|
||||
"""获取插件配置项
|
||||
|
||||
参数:
|
||||
module: 模块名
|
||||
cfg: cfg
|
||||
config: ConfigGroup
|
||||
|
||||
返回:
|
||||
lPluginConfig: 配置数据
|
||||
"""
|
||||
type_str = ""
|
||||
type_inner = None
|
||||
if r := re.search(r"<class '(.*)'>", str(config.configs[cfg].type)):
|
||||
type_str = r[1]
|
||||
elif r := re.search(r"typing\.(.*)\[(.*)\]", str(config.configs[cfg].type)):
|
||||
type_str = r[1]
|
||||
if type_str:
|
||||
type_str = type_str.lower()
|
||||
type_inner = r[2]
|
||||
if type_inner:
|
||||
type_inner = [x.strip() for x in type_inner.split(",")]
|
||||
return PluginConfig(
|
||||
module=module,
|
||||
key=cfg,
|
||||
value=config.configs[cfg].value,
|
||||
help=config.configs[cfg].help,
|
||||
default_value=config.configs[cfg].default_value,
|
||||
type=type_str,
|
||||
type_inner=type_inner, # type: ignore
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_plugin_detail(cls, module: str) -> PluginDetail:
|
||||
"""获取插件详情
|
||||
|
||||
参数:
|
||||
module: 模块名
|
||||
|
||||
异常:
|
||||
ValueError: 插件不存在
|
||||
|
||||
返回:
|
||||
PluginDetail: 插件详情数据
|
||||
"""
|
||||
db_plugin = await DbPluginInfo.get_plugin(module=module)
|
||||
if not db_plugin:
|
||||
raise ValueError("插件不存在")
|
||||
config_list = []
|
||||
if config := Config.get(module):
|
||||
config_list.extend(
|
||||
cls.__build_plugin_config(module, cfg, config) for cfg in config.configs
|
||||
)
|
||||
return PluginDetail(
|
||||
module=module,
|
||||
plugin_name=db_plugin.name,
|
||||
default_status=db_plugin.default_status,
|
||||
limit_superuser=db_plugin.limit_superuser,
|
||||
cost_gold=db_plugin.cost_gold,
|
||||
menu_type=db_plugin.menu_type,
|
||||
version=db_plugin.version or "0",
|
||||
level=db_plugin.level,
|
||||
status=db_plugin.status,
|
||||
author=db_plugin.author,
|
||||
config_list=config_list,
|
||||
block_type=db_plugin.block_type,
|
||||
)
|
||||
@ -31,7 +31,7 @@ class Result(Generic[RT], BaseModel):
|
||||
"""info"""
|
||||
warning: str | None = None
|
||||
"""警告信息"""
|
||||
data: RT = None
|
||||
data: RT | None = None
|
||||
"""返回数据"""
|
||||
|
||||
@classmethod
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
from nonebot import require
|
||||
|
||||
require("nonebot_plugin_apscheduler")
|
||||
require("nonebot_plugin_alconna")
|
||||
require("nonebot_plugin_session")
|
||||
require("nonebot_plugin_userinfo")
|
||||
require("nonebot_plugin_htmlrender")
|
||||
require("nonebot_plugin_uninfo")
|
||||
@ -1,3 +1,5 @@
|
||||
from typing import overload
|
||||
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot_plugin_uninfo import Session, SupportScope, Uninfo, get_interface
|
||||
|
||||
@ -62,6 +64,34 @@ class CommonUtils:
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def format(name: str) -> str:
|
||||
return f"<{name},"
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
def convert_module_format(cls, data: str) -> list[str]: ...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
def convert_module_format(cls, data: list[str]) -> str: ...
|
||||
|
||||
@classmethod
|
||||
def convert_module_format(cls, data: str | list[str]) -> str | list[str]:
|
||||
"""
|
||||
在 `<aaa,<bbb,<ccc,` 和 `["aaa", "bbb", "ccc"]` 之间进行相互转换。
|
||||
|
||||
参数:
|
||||
data (str | list[str]): 输入数据,可能是格式化字符串或字符串列表。
|
||||
|
||||
返回:
|
||||
str | list[str]: 根据输入类型返回转换后的数据。
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
return [item.strip(",") for item in data.split("<") if item]
|
||||
elif isinstance(data, list):
|
||||
return "".join(cls.format(item) for item in data)
|
||||
|
||||
|
||||
class SqlUtils:
|
||||
@classmethod
|
||||
|
||||
@ -5,14 +5,11 @@ from typing import Literal
|
||||
import httpx
|
||||
import nonebot
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.dodo import Bot as DodoBot
|
||||
from nonebot.adapters.kaiheila import Bot as KaiheilaBot
|
||||
from nonebot.adapters.onebot.v11 import Bot as v11Bot
|
||||
from nonebot.adapters.onebot.v12 import Bot as v12Bot
|
||||
from nonebot.utils import is_coroutine_callable
|
||||
from nonebot_plugin_alconna import SupportScope
|
||||
from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage
|
||||
from nonebot_plugin_uninfo import Uninfo, get_interface
|
||||
from nonebot_plugin_uninfo import SceneType, Uninfo, get_interface
|
||||
from nonebot_plugin_uninfo.model import Member
|
||||
from pydantic import BaseModel
|
||||
|
||||
from zhenxun.configs.config import BotConfig
|
||||
@ -35,6 +32,8 @@ class UserData(BaseModel):
|
||||
"""用户id"""
|
||||
group_id: str | None = None
|
||||
"""群组id"""
|
||||
channel_id: str | None = None
|
||||
"""频道id"""
|
||||
role: str | None = None
|
||||
"""角色"""
|
||||
avatar_url: str | None = None
|
||||
@ -68,7 +67,7 @@ class PlatformUtils:
|
||||
group_id: 群组id
|
||||
duration: 禁言时长(分钟)
|
||||
"""
|
||||
if isinstance(bot, v11Bot):
|
||||
if cls.get_platform(bot) == "qq":
|
||||
await bot.set_group_ban(
|
||||
group_id=int(group_id),
|
||||
user_id=int(user_id),
|
||||
@ -116,148 +115,80 @@ class PlatformUtils:
|
||||
返回:
|
||||
list[UserData]: 用户数据列表
|
||||
"""
|
||||
if isinstance(bot, v11Bot):
|
||||
if member_list := await bot.get_group_member_list(group_id=int(group_id)):
|
||||
return [
|
||||
UserData(
|
||||
name=user["nickname"],
|
||||
card=user["card"],
|
||||
user_id=user["user_id"],
|
||||
group_id=user["group_id"],
|
||||
role=user["role"],
|
||||
join_time=user["join_time"],
|
||||
)
|
||||
for user in member_list
|
||||
]
|
||||
if isinstance(bot, v12Bot):
|
||||
if member_list := await bot.get_group_member_list(group_id=group_id):
|
||||
return [
|
||||
UserData(
|
||||
name=user["user_name"],
|
||||
card=user["user_displayname"],
|
||||
user_id=user["user_id"],
|
||||
group_id=group_id,
|
||||
)
|
||||
for user in member_list
|
||||
]
|
||||
if isinstance(bot, DodoBot):
|
||||
if result_data := await bot.get_member_list(
|
||||
island_source_id=group_id, page_size=100, max_id=0
|
||||
):
|
||||
max_id = result_data.max_id
|
||||
result_list = result_data.list
|
||||
while max_id == 100:
|
||||
result_data = await bot.get_member_list(
|
||||
island_source_id=group_id, page_size=100, max_id=0
|
||||
)
|
||||
result_list += result_data.list
|
||||
max_id = result_data.max_id
|
||||
return [
|
||||
UserData(
|
||||
name=user.nick_name,
|
||||
card=user.personal_nick_name,
|
||||
avatar_url=user.avatar_url,
|
||||
user_id=user.dodo_source_id,
|
||||
group_id=user.island_source_id,
|
||||
join_time=int(user.join_time.timestamp()),
|
||||
)
|
||||
for user in result_list
|
||||
]
|
||||
if isinstance(bot, KaiheilaBot):
|
||||
if result_data := await bot.guild_userList(guild_id=group_id):
|
||||
if result_data.users:
|
||||
data_list = []
|
||||
for user in result_data.users:
|
||||
second = None
|
||||
if user.joined_at:
|
||||
second = int(user.joined_at / 1000)
|
||||
data_list.append(
|
||||
UserData(
|
||||
name=user.nickname or "",
|
||||
avatar_url=user.avatar,
|
||||
user_id=user.id_, # type: ignore
|
||||
group_id=group_id,
|
||||
join_time=second,
|
||||
)
|
||||
)
|
||||
return data_list
|
||||
if interface := get_interface(bot):
|
||||
members: list[Member] = await interface.get_members(
|
||||
SceneType.GROUP, group_id
|
||||
)
|
||||
return [
|
||||
UserData(
|
||||
name=member.user.name or "",
|
||||
card=member.nick,
|
||||
user_id=member.user.id,
|
||||
group_id=group_id,
|
||||
role=member.role.id if member.role else "",
|
||||
avatar_url=member.user.avatar,
|
||||
join_time=int(member.joined_at.timestamp())
|
||||
if member.joined_at
|
||||
else None,
|
||||
)
|
||||
for member in members
|
||||
]
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
async def get_user(
|
||||
cls, bot: Bot, user_id: str, group_id: str | None = None
|
||||
cls,
|
||||
bot: Bot,
|
||||
user_id: str,
|
||||
group_id: str | None = None,
|
||||
channel_id: str | None = None,
|
||||
) -> UserData | None:
|
||||
"""获取用户信息
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
user_id: 用户id
|
||||
group_id: 群组/频道id.
|
||||
group_id: 群组id.
|
||||
channel_id: 频道id.
|
||||
|
||||
返回:
|
||||
UserData | None: 用户数据
|
||||
"""
|
||||
if isinstance(bot, v11Bot):
|
||||
if group_id:
|
||||
if user := await bot.get_group_member_info(
|
||||
group_id=int(group_id), user_id=int(user_id)
|
||||
):
|
||||
return UserData(
|
||||
name=user["nickname"],
|
||||
card=user["card"],
|
||||
user_id=user["user_id"],
|
||||
group_id=user["group_id"],
|
||||
role=user["role"],
|
||||
join_time=user["join_time"],
|
||||
)
|
||||
elif friend_list := await bot.get_friend_list():
|
||||
for f in friend_list:
|
||||
if f["user_id"] == int(user_id):
|
||||
return UserData(
|
||||
name=f["nickname"],
|
||||
card=f["remark"],
|
||||
user_id=f["user_id"],
|
||||
)
|
||||
if isinstance(bot, v12Bot):
|
||||
if group_id:
|
||||
if user := await bot.get_group_member_info(
|
||||
group_id=group_id, user_id=user_id
|
||||
):
|
||||
return UserData(
|
||||
name=user["user_name"],
|
||||
card=user["user_displayname"],
|
||||
user_id=user["user_id"],
|
||||
group_id=group_id,
|
||||
)
|
||||
elif friend_list := await bot.get_friend_list():
|
||||
for f in friend_list:
|
||||
if f["user_id"] == int(user_id):
|
||||
return UserData(
|
||||
name=f["user_name"],
|
||||
card=f["user_remark"],
|
||||
user_id=f["user_id"],
|
||||
)
|
||||
if isinstance(bot, DodoBot) and group_id:
|
||||
if user := await bot.get_member_info(
|
||||
island_source_id=group_id, dodo_source_id=user_id
|
||||
):
|
||||
return UserData(
|
||||
name=user.nick_name,
|
||||
card=user.personal_nick_name,
|
||||
avatar_url=user.avatar_url,
|
||||
user_id=user.dodo_source_id,
|
||||
group_id=user.island_source_id,
|
||||
join_time=int(user.join_time.timestamp()),
|
||||
if interface := get_interface(bot):
|
||||
member = None
|
||||
user = None
|
||||
if channel_id:
|
||||
member = await interface.get_member(
|
||||
SceneType.CHANNEL_TEXT, channel_id, user_id
|
||||
)
|
||||
if isinstance(bot, KaiheilaBot) and group_id:
|
||||
if user := await bot.user_view(guild_id=group_id, user_id=user_id):
|
||||
second = int(user.joined_at / 1000) if user.joined_at else None
|
||||
if member:
|
||||
user = member.user
|
||||
elif group_id:
|
||||
member = await interface.get_member(SceneType.GROUP, group_id, user_id)
|
||||
if member:
|
||||
user = member.user
|
||||
else:
|
||||
user = await interface.get_user(user_id)
|
||||
if not user:
|
||||
return None
|
||||
if member:
|
||||
return UserData(
|
||||
name=user.nickname or "",
|
||||
avatar_url=user.avatar,
|
||||
user_id=user_id,
|
||||
name=user.name or "",
|
||||
card=member.nick,
|
||||
user_id=user.id,
|
||||
group_id=group_id,
|
||||
join_time=second,
|
||||
channel_id=channel_id,
|
||||
role=member.role.id if member.role else None,
|
||||
join_time=int(member.joined_at.timestamp())
|
||||
if member.joined_at
|
||||
else None,
|
||||
)
|
||||
else:
|
||||
return UserData(
|
||||
name=user.name or "",
|
||||
user_id=user.id,
|
||||
group_id=group_id,
|
||||
channel_id=channel_id,
|
||||
)
|
||||
return None
|
||||
|
||||
@ -337,7 +268,7 @@ class PlatformUtils:
|
||||
返回:
|
||||
Receipt | None: 是否发送成功
|
||||
"""
|
||||
if target := cls.get_target(bot, user_id, group_id):
|
||||
if target := cls.get_target(user_id=user_id, group_id=group_id):
|
||||
send_message = (
|
||||
MessageUtils.build_message(message)
|
||||
if isinstance(message, str)
|
||||
@ -361,7 +292,9 @@ class PlatformUtils:
|
||||
group_list, platform = await cls.get_group_list(bot)
|
||||
if group_list:
|
||||
db_group = await GroupConsole.all()
|
||||
db_group_id = [(group.group_id, group.channel_id) for group in db_group]
|
||||
db_group_id: list[tuple[str, str]] = [
|
||||
(group.group_id, group.channel_id) for group in db_group
|
||||
]
|
||||
for group in group_list:
|
||||
group.platform = platform
|
||||
if (group.group_id, group.channel_id) not in db_group_id:
|
||||
@ -411,69 +344,43 @@ class PlatformUtils:
|
||||
return "unknown"
|
||||
|
||||
@classmethod
|
||||
async def get_group_list(cls, bot: Bot) -> tuple[list[GroupConsole], str]:
|
||||
async def get_group_list(
|
||||
cls, bot: Bot, only_group: bool = False
|
||||
) -> tuple[list[GroupConsole], str]:
|
||||
"""获取群组列表
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
only_group: 是否只获取群组(不获取channel)
|
||||
|
||||
返回:
|
||||
tuple[list[GroupConsole], str]: 群组列表, 平台
|
||||
"""
|
||||
if isinstance(bot, v11Bot):
|
||||
group_list = await bot.get_group_list()
|
||||
return [
|
||||
GroupConsole(
|
||||
group_id=str(g["group_id"]),
|
||||
group_name=g["group_name"],
|
||||
max_member_count=g["max_member_count"],
|
||||
member_count=g["member_count"],
|
||||
)
|
||||
for g in group_list
|
||||
], "qq"
|
||||
if isinstance(bot, v12Bot):
|
||||
group_list = await bot.get_group_list()
|
||||
return [
|
||||
GroupConsole(
|
||||
group_id=g.group_id, # type: ignore
|
||||
user_name=g.group_name, # type: ignore
|
||||
)
|
||||
for g in group_list
|
||||
], "qq"
|
||||
if isinstance(bot, DodoBot):
|
||||
island_list = await bot.get_island_list()
|
||||
source_id_list = [
|
||||
(g.island_source_id, g.island_name)
|
||||
for g in island_list
|
||||
if g.island_source_id
|
||||
]
|
||||
group_list = []
|
||||
for id, name in source_id_list:
|
||||
channel_list = await bot.get_channel_list(island_source_id=id)
|
||||
group_list.append(GroupConsole(group_id=id, group_name=name))
|
||||
group_list += [
|
||||
if interface := get_interface(bot):
|
||||
platform = cls.get_platform(bot)
|
||||
result_list = []
|
||||
scenes = await interface.get_scenes(SceneType.GROUP)
|
||||
for scene in scenes:
|
||||
group_id = scene.id
|
||||
result_list.append(
|
||||
GroupConsole(
|
||||
group_id=id, group_name=c.channel_name, channel_id=c.channel_id
|
||||
group_id=scene.id,
|
||||
group_name=scene.name,
|
||||
)
|
||||
for c in channel_list
|
||||
]
|
||||
return group_list, "dodo"
|
||||
if isinstance(bot, KaiheilaBot):
|
||||
group_list = []
|
||||
guilds = await bot.guild_list()
|
||||
if guilds.guilds:
|
||||
for guild_id, name in [(g.id_, g.name) for g in guilds.guilds if g.id_]:
|
||||
view = await bot.guild_view(guild_id=guild_id)
|
||||
group_list.append(GroupConsole(group_id=guild_id, group_name=name))
|
||||
if view.channels:
|
||||
group_list += [
|
||||
GroupConsole(
|
||||
group_id=guild_id, group_name=c.name, channel_id=c.id_
|
||||
)
|
||||
if not only_group and platform != "qq":
|
||||
if channel_list := await interface.get_scenes(
|
||||
parent_scene_id=group_id
|
||||
):
|
||||
for channel in channel_list:
|
||||
result_list.append(
|
||||
GroupConsole(
|
||||
group_id=scene.id,
|
||||
group_name=channel.name,
|
||||
channel_id=channel.id,
|
||||
)
|
||||
)
|
||||
for c in view.channels
|
||||
if c.type != 0
|
||||
]
|
||||
return group_list, "kaiheila"
|
||||
return result_list, platform
|
||||
return [], ""
|
||||
|
||||
@classmethod
|
||||
@ -508,36 +415,17 @@ class PlatformUtils:
|
||||
返回:
|
||||
list[FriendUser]: 好友列表
|
||||
"""
|
||||
if isinstance(bot, v11Bot):
|
||||
friend_list = await bot.get_friend_list()
|
||||
if interface := get_interface(bot):
|
||||
user_list = await interface.get_users()
|
||||
return [
|
||||
FriendUser(user_id=str(f["user_id"]), user_name=f["nickname"])
|
||||
for f in friend_list
|
||||
], "qq"
|
||||
if isinstance(bot, v12Bot):
|
||||
friend_list = await bot.get_friend_list()
|
||||
return [
|
||||
FriendUser(
|
||||
user_id=f.user_id, # type: ignore
|
||||
user_name=f.user_displayname or f.user_remark or f.user_name, # type: ignore
|
||||
)
|
||||
for f in friend_list
|
||||
], "qq"
|
||||
# if isinstance(bot, DodoBot):
|
||||
# # TODO: dodo好友列表
|
||||
# pass
|
||||
# if isinstance(bot, KaiheilaBot):
|
||||
# # TODO: kaiheila好友列表
|
||||
# pass
|
||||
# if isinstance(bot, DiscordBot):
|
||||
# # TODO: discord好友列表
|
||||
# pass
|
||||
FriendUser(user_id=u.id, user_name=u.name) for u in user_list
|
||||
], cls.get_platform(bot)
|
||||
return [], ""
|
||||
|
||||
@classmethod
|
||||
def get_target(
|
||||
cls,
|
||||
bot: Bot,
|
||||
*,
|
||||
user_id: str | None = None,
|
||||
group_id: str | None = None,
|
||||
channel_id: str | None = None,
|
||||
@ -554,16 +442,12 @@ class PlatformUtils:
|
||||
target: 对应平台Target
|
||||
"""
|
||||
target = None
|
||||
if isinstance(bot, v11Bot | v12Bot):
|
||||
if group_id:
|
||||
target = Target(group_id)
|
||||
elif user_id:
|
||||
target = Target(user_id, private=True)
|
||||
elif isinstance(bot, DodoBot | KaiheilaBot):
|
||||
if group_id and channel_id:
|
||||
target = Target(channel_id, parent_id=group_id, channel=True)
|
||||
elif user_id:
|
||||
target = Target(user_id, private=True)
|
||||
if group_id and channel_id:
|
||||
target = Target(channel_id, parent_id=group_id, channel=True)
|
||||
elif group_id:
|
||||
target = Target(group_id)
|
||||
elif user_id:
|
||||
target = Target(user_id, private=True)
|
||||
return target
|
||||
|
||||
|
||||
@ -646,7 +530,9 @@ async def broadcast_group(
|
||||
)
|
||||
continue
|
||||
target = PlatformUtils.get_target(
|
||||
_bot, None, group.group_id, group.channel_id
|
||||
user_id=None,
|
||||
group_id=group.group_id,
|
||||
channel_id=group.channel_id,
|
||||
)
|
||||
if target:
|
||||
_used_group.append(key)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user