Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
094a8701f6 |
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
<div align=center>
|
<div align=center>
|
||||||
|
|
||||||
[文档](https://zhenxun-org.github.io/zhenxun_bot/)
|
[文档](https://hibikier.github.io/zhenxun_bot/)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ 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) | 插件 | [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/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) | 第三方 |
|
| [一键安装](https://github.com/soloxiaoye2022/zhenxun_bot-deploy) | 安装 | [soloxiaoye2022](https://github.com/soloxiaoye2022) | 第三方 |
|
||||||
| [WebUi](https://github.com/zhenxun-org/zhenxun_bot) | 管理 | [hibikier](https://github.com/HibiKier) | 基于真寻 WebApi 的 webui 实现 [预览](#-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) | 第三方 |
|
| [安卓 app(WebUi)](https://github.com/YuS1aN/zhenxun_bot_android_ui) | 安装 | [YuS1aN](https://github.com/YuS1aN) | 第三方 |
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -121,32 +121,10 @@ AccessToken: PUBLIC_ZHENXUN_TEST
|
|||||||
|
|
||||||
- 实现了许多功能,且提供了大量功能管理命令,进行了多平台适配,兼容 nb2 商店插件
|
- 实现了许多功能,且提供了大量功能管理命令,进行了多平台适配,兼容 nb2 商店插件
|
||||||
- 拥有完善可用的 webui
|
- 拥有完善可用的 webui
|
||||||
- 通过 Config 配置项将所有插件配置统一保存至 config.yaml,利于统一用户修改
|
- 通过 Config 配置项将所有插件配置统计保存至 config.yaml,利于统一用户修改
|
||||||
- 方便增删插件,原生 nonebot2 matcher,不需要额外修改,仅仅通过简单的配置属性就可以生成`帮助图片`和`帮助信息`
|
- 方便增删插件,原生 nonebot2 matcher,不需要额外修改,仅仅通过简单的配置属性就可以生成`帮助图片`和`帮助信息`
|
||||||
- 提供了 cd,阻塞,每日次数等限制,仅仅通过简单的属性就可以生成一个限制,例如:`PluginCdBlock` 等
|
- 提供了 cd,阻塞,每日次数等限制,仅仅通过简单的属性就可以生成一个限制,例如:`PluginCdBlock` 等
|
||||||
- **更多详细请通过 [传送门](https://zhenxun-org.github.io/zhenxun_bot/) 查看文档!**
|
- **更多详细请通过 [传送门](https://hibikier.github.io/zhenxun_bot/) 查看文档!**
|
||||||
|
|
||||||
## 🐣 小白整合
|
|
||||||
|
|
||||||
如果你系统是 **Windows** 且不想下载 Python
|
|
||||||
可以使用整合包(Python3.10+zhenxun+webui)
|
|
||||||
|
|
||||||
文档地址:[整合包文档](https://hibikier.github.io/zhenxun_bot/beginner/)
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>下载地址</summary>
|
|
||||||
|
|
||||||
- **百度云:**
|
|
||||||
https://pan.baidu.com/s/1ph4yzx1vdNbkxm9VBKDdgQ?pwd=971j
|
|
||||||
|
|
||||||
- **天翼云:**
|
|
||||||
https://cloud.189.cn/web/share?code=jq67r2i2E7Fb
|
|
||||||
访问码:8wxm
|
|
||||||
|
|
||||||
- **Google Drive:**
|
|
||||||
https://drive.google.com/file/d/1cc3Dqjk0x5hWGLNeMkrFwWl8BvsK6KfD/view?usp=drive_link
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## 🛠️ 简单部署
|
## 🛠️ 简单部署
|
||||||
|
|
||||||
@@ -172,7 +150,7 @@ poetry run python bot.py
|
|||||||
|
|
||||||
1.在 .env.dev 文件中填写你的机器人配置项
|
1.在 .env.dev 文件中填写你的机器人配置项
|
||||||
|
|
||||||
2.在 data/config.yaml 文件中修改你需要修改的插件配置项
|
2.在 configs/config.yaml 文件中修改你需要修改的插件配置项
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>数据库地址(DB_URL)配置说明</summary>
|
<summary>数据库地址(DB_URL)配置说明</summary>
|
||||||
@@ -294,12 +272,12 @@ DB_URL 是基于 Tortoise ORM 的数据库连接字符串,用于指定项目
|
|||||||
## ❔ 需要帮助?
|
## ❔ 需要帮助?
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> 发起 [issue](https://github.com/zhenxun-org/zhenxun_bot/issues/new/choose) 前,我们希望你能够阅读过或者了解 [提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)
|
> 发起 [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/)
|
> - 善用[搜索引擎](https://www.google.com/)
|
||||||
> - 查阅 issue 中是否有类似问题,如果没有请按照模板发起 issue
|
> - 查阅 issue 中是否有类似问题,如果没有请按照模板发起 issue
|
||||||
|
|
||||||
欢迎前往 [issue](https://github.com/zhenxun-org/zhenxun_bot/issues/new/choose) 中提出你遇到的问题,或者加入我们的 [用户群](https://qm.qq.com/q/mRNtLSl6uc) 或 [技术群](https://qm.qq.com/q/YYYt5rkMYc)与我们联系
|
欢迎前往 [issue](https://github.com/HibiKier/zhenxun_bot/issues/new/choose) 中提出你遇到的问题,或者加入我们的 [用户群](https://qm.qq.com/q/mRNtLSl6uc) 或 [技术群](https://qm.qq.com/q/YYYt5rkMYc)与我们联系
|
||||||
|
|
||||||
## 🛠️ 进度追踪
|
## 🛠️ 进度追踪
|
||||||
|
|
||||||
@@ -309,8 +287,6 @@ Project [zhenxun_bot](https://github.com/users/HibiKier/projects/2)
|
|||||||
|
|
||||||
首席设计师:[酥酥/coldly-ss](https://github.com/coldly-ss)
|
首席设计师:[酥酥/coldly-ss](https://github.com/coldly-ss)
|
||||||
|
|
||||||
LOGO 设计:[FrostN0v0](https://github.com/FrostN0v0)
|
|
||||||
|
|
||||||
## 🙏 感谢
|
## 🙏 感谢
|
||||||
|
|
||||||
[botuniverse / onebot](https://github.com/botuniverse/onebot) :超棒的机器人协议
|
[botuniverse / onebot](https://github.com/botuniverse/onebot) :超棒的机器人协议
|
||||||
@@ -350,68 +326,34 @@ LOGO 设计:[FrostN0v0](https://github.com/FrostN0v0)
|
|||||||
<img src="https://contrib.rocks/image?repo=HibiKier/zhenxun_bot&max=1000" alt="contributors"/>
|
<img src="https://contrib.rocks/image?repo=HibiKier/zhenxun_bot&max=1000" alt="contributors"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## 📸 WebUI 界面展示(仅展示默认主题下的 pc 端)
|
## 📸 WebUI 界面展示
|
||||||
|
|
||||||
<div style="display: flex; flex-wrap: wrap; justify-content: space-between;">
|
<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;">
|
||||||
#### API 设置
|
</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>
|
</div>
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ driver.register_adapter(OneBotV11Adapter)
|
|||||||
# driver.register_adapter(DoDoAdapter)
|
# driver.register_adapter(DoDoAdapter)
|
||||||
# driver.register_adapter(DiscordAdapter)
|
# driver.register_adapter(DiscordAdapter)
|
||||||
|
|
||||||
from zhenxun.services.db_context import disconnect
|
from zhenxun.services.db_context import disconnect, init
|
||||||
|
|
||||||
# driver.on_startup(init)
|
driver.on_startup(init)
|
||||||
driver.on_shutdown(disconnect)
|
driver.on_shutdown(disconnect)
|
||||||
|
|
||||||
# nonebot.load_builtin_plugins("echo")
|
# nonebot.load_builtin_plugins("echo")
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
services:
|
|
||||||
db:
|
|
||||||
image: postgres:15
|
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: password
|
|
||||||
POSTGRES_DB: zhenxun
|
|
||||||
volumes:
|
|
||||||
- pgdata:/var/lib/postgresql/data
|
|
||||||
labels:
|
|
||||||
- "prometheus.io/scrape=true"
|
|
||||||
- "prometheus.io/port=9187"
|
|
||||||
|
|
||||||
postgres-exporter:
|
|
||||||
image: prometheuscommunity/postgres-exporter
|
|
||||||
environment:
|
|
||||||
DATA_SOURCE_NAME: "postgresql://postgres:password@db:5432/zhenxun?sslmode=disable"
|
|
||||||
ports:
|
|
||||||
- "9187:9187"
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: redis:7
|
|
||||||
ports:
|
|
||||||
- "6379:6379"
|
|
||||||
labels:
|
|
||||||
- "prometheus.io/scrape=true"
|
|
||||||
- "prometheus.io/port=9121"
|
|
||||||
|
|
||||||
redis-exporter:
|
|
||||||
image: oliver006/redis_exporter
|
|
||||||
environment:
|
|
||||||
REDIS_ADDR: redis://redis:6379
|
|
||||||
ports:
|
|
||||||
- "9121:9121"
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
|
|
||||||
prometheus:
|
|
||||||
image: prom/prometheus
|
|
||||||
ports:
|
|
||||||
- "9090:9090"
|
|
||||||
volumes:
|
|
||||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
|
||||||
- prometheus_data:/prometheus
|
|
||||||
command:
|
|
||||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
|
||||||
- '--storage.tsdb.path=/prometheus'
|
|
||||||
- '--web.console.libraries=/etc/prometheus/console_libraries'
|
|
||||||
- '--web.console.templates=/etc/prometheus/consoles'
|
|
||||||
|
|
||||||
grafana:
|
|
||||||
image: grafana/grafana
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
volumes:
|
|
||||||
- grafana_data:/var/lib/grafana
|
|
||||||
depends_on:
|
|
||||||
- prometheus
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
pgdata:
|
|
||||||
prometheus_data:
|
|
||||||
grafana_data:
|
|
||||||
|
Before Width: | Height: | Size: 388 KiB |
|
Before Width: | Height: | Size: 315 KiB |
|
Before Width: | Height: | Size: 630 KiB |
|
Before Width: | Height: | Size: 708 KiB |
|
Before Width: | Height: | Size: 598 KiB |
|
Before Width: | Height: | Size: 405 KiB |
|
Before Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 504 KiB |
|
Before Width: | Height: | Size: 423 KiB |
|
Before Width: | Height: | Size: 551 KiB |
|
Before Width: | Height: | Size: 453 KiB |
|
Before Width: | Height: | Size: 400 KiB |
|
Before Width: | Height: | Size: 336 KiB |
|
Before Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 315 KiB |
|
After Width: | Height: | Size: 352 KiB |
|
After Width: | Height: | Size: 279 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 193 KiB |
@@ -1,4 +1,4 @@
|
|||||||
# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiocache"
|
name = "aiocache"
|
||||||
@@ -258,7 +258,7 @@ description = "Timeout context manager for asyncio programs"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
markers = "python_version == \"3.10\""
|
markers = "python_version < \"3.11.0\""
|
||||||
files = [
|
files = [
|
||||||
{file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
|
{file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
|
||||||
{file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
|
{file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
|
||||||
@@ -2002,104 +2002,104 @@ reference = "aliyun"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "multidict"
|
name = "multidict"
|
||||||
version = "6.2.0"
|
version = "6.3.2"
|
||||||
description = "multidict implementation"
|
description = "multidict implementation"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
groups = ["main", "dev"]
|
groups = ["main", "dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "multidict-6.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b9f6392d98c0bd70676ae41474e2eecf4c7150cb419237a41f8f96043fcb81d1"},
|
{file = "multidict-6.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8b3dc0eec9304fa04d84a51ea13b0ec170bace5b7ddeaac748149efd316f1504"},
|
||||||
{file = "multidict-6.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3501621d5e86f1a88521ea65d5cad0a0834c77b26f193747615b7c911e5422d2"},
|
{file = "multidict-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9534f3d84addd3b6018fa83f97c9d4247aaa94ac917d1ed7b2523306f99f5c16"},
|
||||||
{file = "multidict-6.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32ed748ff9ac682eae7859790d3044b50e3076c7d80e17a44239683769ff485e"},
|
{file = "multidict-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a003ce1413ae01f0b8789c1c987991346a94620a4d22210f7a8fe753646d3209"},
|
||||||
{file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc826b9a8176e686b67aa60fd6c6a7047b0461cae5591ea1dc73d28f72332a8a"},
|
{file = "multidict-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b43f7384e68b1b982c99f489921a459467b5584bdb963b25e0df57c9039d0ad"},
|
||||||
{file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:214207dcc7a6221d9942f23797fe89144128a71c03632bf713d918db99bd36de"},
|
{file = "multidict-6.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d142ae84047262dc75c1f92eaf95b20680f85ce11d35571b4c97e267f96fadc4"},
|
||||||
{file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05fefbc3cddc4e36da209a5e49f1094bbece9a581faa7f3589201fd95df40e5d"},
|
{file = "multidict-6.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec7e86fbc48aa1d6d686501a8547818ba8d645e7e40eaa98232a5d43ee4380ad"},
|
||||||
{file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e851e6363d0dbe515d8de81fd544a2c956fdec6f8a049739562286727d4a00c3"},
|
{file = "multidict-6.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe019fb437632b016e6cac67a7e964f1ef827ef4023f1ca0227b54be354da97e"},
|
||||||
{file = "multidict-6.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32c9b4878f48be3e75808ea7e499d6223b1eea6d54c487a66bc10a1871e3dc6a"},
|
{file = "multidict-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b60cb81214a9da7cfd8ae2853d5e6e47225ece55fe5833142fe0af321c35299"},
|
||||||
{file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7243c5a6523c5cfeca76e063efa5f6a656d1d74c8b1fc64b2cd1e84e507f7e2a"},
|
{file = "multidict-6.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32d9e8ef2e0312d4e96ca9adc88e0675b6d8e144349efce4a7c95d5ccb6d88e0"},
|
||||||
{file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0e5a644e50ef9fb87878d4d57907f03a12410d2aa3b93b3acdf90a741df52c49"},
|
{file = "multidict-6.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:335d584312e3fa43633d63175dfc1a5f137dd7aa03d38d1310237d54c3032774"},
|
||||||
{file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0dc25a3293c50744796e87048de5e68996104d86d940bb24bc3ec31df281b191"},
|
{file = "multidict-6.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b8df917faa6b8cac3d6870fc21cb7e4d169faca68e43ffe568c156c9c6408a4d"},
|
||||||
{file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a49994481b99cd7dedde07f2e7e93b1d86c01c0fca1c32aded18f10695ae17eb"},
|
{file = "multidict-6.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:cc060b9b89b701dd8fedef5b99e1f1002b8cb95072693233a63389d37e48212d"},
|
||||||
{file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:641cf2e3447c9ecff2f7aa6e9eee9eaa286ea65d57b014543a4911ff2799d08a"},
|
{file = "multidict-6.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f2ce3be2500658f3c644494b934628bb0c82e549dde250d2119689ce791cc8b8"},
|
||||||
{file = "multidict-6.2.0-cp310-cp310-win32.whl", hash = "sha256:0c383d28857f66f5aebe3e91d6cf498da73af75fbd51cedbe1adfb85e90c0460"},
|
{file = "multidict-6.3.2-cp310-cp310-win32.whl", hash = "sha256:dbcb4490d8e74b484449abd51751b8f560dd0a4812eb5dacc6a588498222a9ab"},
|
||||||
{file = "multidict-6.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:a33273a541f1e1a8219b2a4ed2de355848ecc0254264915b9290c8d2de1c74e1"},
|
{file = "multidict-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:06944f9ced30f8602be873563ed4df7e3f40958f60b2db39732c11d615a33687"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84e87a7d75fa36839a3a432286d719975362d230c70ebfa0948549cc38bd5b46"},
|
{file = "multidict-6.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45a034f41fcd16968c0470d8912d293d7b0d0822fc25739c5c2ff7835b85bc56"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8de4d42dffd5ced9117af2ce66ba8722402541a3aa98ffdf78dde92badb68932"},
|
{file = "multidict-6.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:352585cec45f5d83d886fc522955492bb436fca032b11d487b12d31c5a81b9e3"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d91a230c7f8af86c904a5a992b8c064b66330544693fd6759c3d6162382ecf"},
|
{file = "multidict-6.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:da9d89d293511fd0a83a90559dc131f8b3292b6975eb80feff19e5f4663647e2"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f6cad071960ba1914fa231677d21b1b4a3acdcce463cee41ea30bc82e6040cf"},
|
{file = "multidict-6.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fa716592224aa652b9347a586cfe018635229074565663894eb4eb21f8307f"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f74f2fc51555f4b037ef278efc29a870d327053aba5cb7d86ae572426c7cccc"},
|
{file = "multidict-6.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0326278a44c56e94792475268e5cd3d47fbc0bd41ee56928c3bbb103ba7f58fe"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14ed9ed1bfedd72a877807c71113deac292bf485159a29025dfdc524c326f3e1"},
|
{file = "multidict-6.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bb1ea87f7fe45e5079f6315e95d64d4ca8b43ef656d98bed63a02e3756853a22"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac3fcf9a2d369bd075b2c2965544036a27ccd277fc3c04f708338cc57533081"},
|
{file = "multidict-6.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cff3c5a98d037024a9065aafc621a8599fad7b423393685dc83cf7a32f8b691"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fc6af8e39f7496047c7876314f4317736eac82bf85b54c7c76cf1a6f8e35d98"},
|
{file = "multidict-6.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed99834b053c655d980fb98029003cb24281e47a796052faad4543aa9e01b8e8"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f8cb1329f42fadfb40d6211e5ff568d71ab49be36e759345f91c69d1033d633"},
|
{file = "multidict-6.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7048440e505d2b4741e5d0b32bd2f427c901f38c7760fc245918be2cf69b3b85"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5389445f0173c197f4a3613713b5fb3f3879df1ded2a1a2e4bc4b5b9c5441b7e"},
|
{file = "multidict-6.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27248c27b563f5889556da8a96e18e98a56ff807ac1a7d56cf4453c2c9e4cd91"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:94a7bb972178a8bfc4055db80c51efd24baefaced5e51c59b0d598a004e8305d"},
|
{file = "multidict-6.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6323b4ba0e018bd266f776c35f3f0943fc4ee77e481593c9f93bd49888f24e94"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da51d8928ad8b4244926fe862ba1795f0b6e68ed8c42cd2f822d435db9c2a8f4"},
|
{file = "multidict-6.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:81f7ce5ec7c27d0b45c10449c8f0fed192b93251e2e98cb0b21fec779ef1dc4d"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:063be88bd684782a0715641de853e1e58a2f25b76388538bd62d974777ce9bc2"},
|
{file = "multidict-6.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03bfcf2825b3bed0ba08a9d854acd18b938cab0d2dba3372b51c78e496bac811"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-win32.whl", hash = "sha256:52b05e21ff05729fbea9bc20b3a791c3c11da61649ff64cce8257c82a020466d"},
|
{file = "multidict-6.3.2-cp311-cp311-win32.whl", hash = "sha256:f32c2790512cae6ca886920e58cdc8c784bdc4bb2a5ec74127c71980369d18dc"},
|
||||||
{file = "multidict-6.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1e2a2193d3aa5cbf5758f6d5680a52aa848e0cf611da324f71e5e48a9695cc86"},
|
{file = "multidict-6.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:0b0c15e58e038a2cd75ef7cf7e072bc39b5e0488b165902efb27978984bbad70"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:437c33561edb6eb504b5a30203daf81d4a9b727e167e78b0854d9a4e18e8950b"},
|
{file = "multidict-6.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d1e0ba1ce1b8cc79117196642d95f4365e118eaf5fb85f57cdbcc5a25640b2a4"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9f49585f4abadd2283034fc605961f40c638635bc60f5162276fec075f2e37a4"},
|
{file = "multidict-6.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:029bbd7d782251a78975214b78ee632672310f9233d49531fc93e8e99154af25"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5dd7106d064d05896ce28c97da3f46caa442fe5a43bc26dfb258e90853b39b44"},
|
{file = "multidict-6.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7db41e3b56817d9175264e5fe00192fbcb8e1265307a59f53dede86161b150e"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e25b11a0417475f093d0f0809a149aff3943c2c56da50fdf2c3c88d57fe3dfbd"},
|
{file = "multidict-6.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fcab18e65cc555ac29981a581518c23311f2b1e72d8f658f9891590465383be"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac380cacdd3b183338ba63a144a34e9044520a6fb30c58aa14077157a033c13e"},
|
{file = "multidict-6.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d50eff89aa4d145a5486b171a2177042d08ea5105f813027eb1050abe91839f"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61d5541f27533f803a941d3a3f8a3d10ed48c12cf918f557efcbf3cd04ef265c"},
|
{file = "multidict-6.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:643e57b403d3e240045a3681f9e6a04d35a33eddc501b4cbbbdbc9c70122e7bc"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:facaf11f21f3a4c51b62931feb13310e6fe3475f85e20d9c9fdce0d2ea561b87"},
|
{file = "multidict-6.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d17b37b9715b30605b5bab1460569742d0c309e5c20079263b440f5d7746e7e"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:095a2eabe8c43041d3e6c2cb8287a257b5f1801c2d6ebd1dd877424f1e89cf29"},
|
{file = "multidict-6.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68acd51fa94e63312b8ddf84bfc9c3d3442fe1f9988bbe1b6c703043af8867fe"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0cc398350ef31167e03f3ca7c19313d4e40a662adcb98a88755e4e861170bdd"},
|
{file = "multidict-6.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:347eea2852ab7f697cc5ed9b1aae96b08f8529cca0c6468f747f0781b1842898"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7c611345bbe7cb44aabb877cb94b63e86f2d0db03e382667dbd037866d44b4f8"},
|
{file = "multidict-6.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4d3f8e57027dcda84a1aa181501c15c45eab9566eb6fcc274cbd1e7561224f8"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8cd1a0644ccaf27e9d2f6d9c9474faabee21f0578fe85225cc5af9a61e1653df"},
|
{file = "multidict-6.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9ca57a841ffcf712e47875d026aa49d6e67f9560624d54b51628603700d5d287"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:89b3857652183b8206a891168af47bac10b970d275bba1f6ee46565a758c078d"},
|
{file = "multidict-6.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7cafdafb44c4e646118410368307693e49d19167e5f119cbe3a88697d2d1a636"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:125dd82b40f8c06d08d87b3510beaccb88afac94e9ed4a6f6c71362dc7dbb04b"},
|
{file = "multidict-6.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:430120c6ce3715a9c6075cabcee557daccbcca8ba25a9fedf05c7bf564532f2d"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-win32.whl", hash = "sha256:76b34c12b013d813e6cb325e6bd4f9c984db27758b16085926bbe7ceeaace626"},
|
{file = "multidict-6.3.2-cp312-cp312-win32.whl", hash = "sha256:13bec31375235a68457ab887ce1bbf4f59d5810d838ae5d7e5b416242e1f3ed4"},
|
||||||
{file = "multidict-6.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:0b183a959fb88ad1be201de2c4bdf52fa8e46e6c185d76201286a97b6f5ee65c"},
|
{file = "multidict-6.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:c3b6d7620e6e90c6d97eaf3a63bf7fbd2ba253aab89120a4a9c660bf2d675391"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5c5e7d2e300d5cb3b2693b6d60d3e8c8e7dd4ebe27cd17c9cb57020cac0acb80"},
|
{file = "multidict-6.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b9ca24700322816ae0d426aa33671cf68242f8cc85cee0d0e936465ddaee90b5"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:256d431fe4583c5f1e0f2e9c4d9c22f3a04ae96009b8cfa096da3a8723db0a16"},
|
{file = "multidict-6.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d9fbbe23667d596ff4f9f74d44b06e40ebb0ab6b262cf14a284f859a66f86457"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a3c0ff89fe40a152e77b191b83282c9664357dce3004032d42e68c514ceff27e"},
|
{file = "multidict-6.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9cb602c5bea0589570ad3a4a6f2649c4f13cc7a1e97b4c616e5e9ff8dc490987"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef7d48207926edbf8b16b336f779c557dd8f5a33035a85db9c4b0febb0706817"},
|
{file = "multidict-6.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93ca81dd4d1542e20000ed90f4cc84b7713776f620d04c2b75b8efbe61106c99"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3c099d3899b14e1ce52262eb82a5f5cb92157bb5106bf627b618c090a0eadc"},
|
{file = "multidict-6.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:18b6310b5454c62242577a128c87df8897f39dd913311cf2e1298e47dfc089eb"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e16e7297f29a544f49340012d6fc08cf14de0ab361c9eb7529f6a57a30cbfda1"},
|
{file = "multidict-6.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a6dda57de1fc9aedfdb600a8640c99385cdab59a5716cb714b52b6005797f77"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:042028348dc5a1f2be6c666437042a98a5d24cee50380f4c0902215e5ec41844"},
|
{file = "multidict-6.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d8ec42d03cc6b29845552a68151f9e623c541f1708328353220af571e24a247"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08549895e6a799bd551cf276f6e59820aa084f0f90665c0f03dd3a50db5d3c48"},
|
{file = "multidict-6.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80681969cee2fa84dafeb53615d51d24246849984e3e87fbe4fe39956f2e23bf"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ccfd74957ef53fa7380aaa1c961f523d582cd5e85a620880ffabd407f8202c0"},
|
{file = "multidict-6.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:01489b0c3592bb9d238e5690e9566db7f77a5380f054b57077d2c4deeaade0eb"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83b78c680d4b15d33042d330c2fa31813ca3974197bddb3836a5c635a5fd013f"},
|
{file = "multidict-6.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:522d9f1fd995d04dfedc0a40bca7e2591bc577d920079df50b56245a4a252c1c"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b4c153863dd6569f6511845922c53e39c8d61f6e81f228ad5443e690fca403de"},
|
{file = "multidict-6.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2014e9cf0b4e9c75bbad49c1758e5a9bf967a56184fc5fcc51527425baf5abba"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:98aa8325c7f47183b45588af9c434533196e241be0a4e4ae2190b06d17675c02"},
|
{file = "multidict-6.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:78ced9fcbee79e446ff4bb3018ac7ba1670703de7873d9c1f6f9883db53c71bc"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e658d1373c424457ddf6d55ec1db93c280b8579276bebd1f72f113072df8a5d"},
|
{file = "multidict-6.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1faf01af972bd01216a107c195f5294f9f393531bc3e4faddc9b333581255d4d"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-win32.whl", hash = "sha256:3157126b028c074951839233647bd0e30df77ef1fedd801b48bdcad242a60f4e"},
|
{file = "multidict-6.3.2-cp313-cp313-win32.whl", hash = "sha256:7a699ab13d8d8e1f885de1535b4f477fb93836c87168318244c2685da7b7f655"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:2e87f1926e91855ae61769ba3e3f7315120788c099677e0842e697b0bfb659f2"},
|
{file = "multidict-6.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:8666bb0d883310c83be01676e302587834dfd185b52758caeab32ef0eb387bc6"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2529ddbdaa424b2c6c2eb668ea684dd6b75b839d0ad4b21aad60c168269478d7"},
|
{file = "multidict-6.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:d82c95aabee29612b1c4f48b98be98181686eb7d6c0152301f72715705cc787b"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:13551d0e2d7201f0959725a6a769b6f7b9019a168ed96006479c9ac33fe4096b"},
|
{file = "multidict-6.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f47709173ea9e87a7fd05cd7e5cf1e5d4158924ff988a9a8e0fbd853705f0e68"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d1996ee1330e245cd3aeda0887b4409e3930524c27642b046e4fae88ffa66c5e"},
|
{file = "multidict-6.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c7f9d0276ceaab41b8ae78534ff28ea33d5de85db551cbf80c44371f2b55d13"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c537da54ce4ff7c15e78ab1292e5799d0d43a2108e006578a57f531866f64025"},
|
{file = "multidict-6.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6eab22df44a25acab2e738f882f5ec551282ab45b2bbda5301e6d2cfb323036"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f249badb360b0b4d694307ad40f811f83df4da8cef7b68e429e4eea939e49dd"},
|
{file = "multidict-6.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a947cb7c657f57874021b9b70c7aac049c877fb576955a40afa8df71d01a1390"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48d39b1824b8d6ea7de878ef6226efbe0773f9c64333e1125e0efcfdd18a24c7"},
|
{file = "multidict-6.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5faa346e8e1c371187cf345ab1e02a75889f9f510c9cbc575c31b779f7df084d"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b99aac6bb2c37db336fa03a39b40ed4ef2818bf2dfb9441458165ebe88b793af"},
|
{file = "multidict-6.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc6e08d977aebf1718540533b4ba5b351ccec2db093370958a653b1f7f9219cc"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bfa8bc649783e703263f783f73e27fef8cd37baaad4389816cf6a133141331"},
|
{file = "multidict-6.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98eab7acf55275b5bf09834125fa3a80b143a9f241cdcdd3f1295ffdc3c6d097"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2c00ad31fbc2cbac85d7d0fcf90853b2ca2e69d825a2d3f3edb842ef1544a2c"},
|
{file = "multidict-6.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:36863655630becc224375c0b99364978a0f95aebfb27fb6dd500f7fb5fb36e79"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d57a01a2a9fa00234aace434d8c131f0ac6e0ac6ef131eda5962d7e79edfb5b"},
|
{file = "multidict-6.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d9c0979c096c0d46a963331b0e400d3a9e560e41219df4b35f0d7a2f28f39710"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:abf5b17bc0cf626a8a497d89ac691308dbd825d2ac372aa990b1ca114e470151"},
|
{file = "multidict-6.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0efc04f70f05e70e5945890767e8874da5953a196f5b07c552d305afae0f3bf6"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:f7716f7e7138252d88607228ce40be22660d6608d20fd365d596e7ca0738e019"},
|
{file = "multidict-6.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:2c519b3b82c34539fae3e22e4ea965869ac6b628794b1eb487780dde37637ab7"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d5a36953389f35f0a4e88dc796048829a2f467c9197265504593f0e420571547"},
|
{file = "multidict-6.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:329160e301f2afd7b43725d3dda8a7ef8ee41d4ceac2083fc0d8c1cc8a4bd56b"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-win32.whl", hash = "sha256:e653d36b1bf48fa78c7fcebb5fa679342e025121ace8c87ab05c1cefd33b34fc"},
|
{file = "multidict-6.3.2-cp313-cp313t-win32.whl", hash = "sha256:420e5144a5f598dad8db3128f1695cd42a38a0026c2991091dab91697832f8cc"},
|
||||||
{file = "multidict-6.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ca23db5fb195b5ef4fd1f77ce26cadefdf13dba71dab14dadd29b34d457d7c44"},
|
{file = "multidict-6.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:875faded2861c7af2682c67088e6313fec35ede811e071c96d36b081873cea14"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b4f3d66dd0354b79761481fc15bdafaba0b9d9076f1f42cc9ce10d7fcbda205a"},
|
{file = "multidict-6.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2516c5eb5732d6c4e29fa93323bfdc55186895124bc569e2404e3820934be378"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e2a2d6749e1ff2c9c76a72c6530d5baa601205b14e441e6d98011000f47a7ac"},
|
{file = "multidict-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:be5c8622e665cc5491c13c0fcd52915cdbae991a3514251d71129691338cdfb2"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cca83a629f77402cfadd58352e394d79a61c8015f1694b83ab72237ec3941f88"},
|
{file = "multidict-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ef33150eea7953cfdb571d862cff894e0ad97ab80d97731eb4b9328fc32d52b"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:781b5dd1db18c9e9eacc419027b0acb5073bdec9de1675c0be25ceb10e2ad133"},
|
{file = "multidict-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40b357738ce46e998f1b1bad9c4b79b2a9755915f71b87a8c01ce123a22a4f99"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf8d370b2fea27fb300825ec3984334f7dd54a581bde6456799ba3776915a656"},
|
{file = "multidict-6.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:27c60e059fcd3655a653ba99fec2556cd0260ec57f9cb138d3e6ffc413638a2e"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25bb96338512e2f46f615a2bb7c6012fe92a4a5ebd353e5020836a7e33120349"},
|
{file = "multidict-6.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:629e7c5e75bde83e54a22c7043ce89d68691d1f103be6d09a1c82b870df3b4b8"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19e2819b0b468174de25c0ceed766606a07cedeab132383f1e83b9a4e96ccb4f"},
|
{file = "multidict-6.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6c8fc97d893fdf1fff15a619fee8de2f31c9b289ef7594730e35074fa0cefb"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aed763b6a1b28c46c055692836879328f0b334a6d61572ee4113a5d0c859872"},
|
{file = "multidict-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52081d2f27e0652265d4637b03f09b82f6da5ce5e1474f07dc64674ff8bfc04c"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a1133414b771619aa3c3000701c11b2e4624a7f492f12f256aedde97c28331a2"},
|
{file = "multidict-6.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:64529dc395b5fd0a7826ffa70d2d9a7f4abd8f5333d6aaaba67fdf7bedde9f21"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:639556758c36093b35e2e368ca485dada6afc2bd6a1b1207d85ea6dfc3deab27"},
|
{file = "multidict-6.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2b7c3fad827770840f5399348c89635ed6d6e9bba363baad7d3c7f86a9cf1da3"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:163f4604e76639f728d127293d24c3e208b445b463168af3d031b92b0998bb90"},
|
{file = "multidict-6.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:24aa42b1651c654ae9e5273e06c3b7ccffe9f7cc76fbde40c37e9ae65f170818"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2325105e16d434749e1be8022f942876a936f9bece4ec41ae244e3d7fae42aaf"},
|
{file = "multidict-6.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:04ceea01e9991357164b12882e120ce6b4d63a0424bb9f9cd37910aa56d30830"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e4371591e621579cb6da8401e4ea405b33ff25a755874a3567c4075ca63d56e2"},
|
{file = "multidict-6.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:943897a41160945416617db567d867ab34e9258adaffc56a25a4c3f99d919598"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-win32.whl", hash = "sha256:d1175b0e0d6037fab207f05774a176d71210ebd40b1c51f480a04b65ec5c786d"},
|
{file = "multidict-6.3.2-cp39-cp39-win32.whl", hash = "sha256:76157a9a0c5380aadd3b5ff7b8deee355ff5adecc66c837b444fa633b4d409a2"},
|
||||||
{file = "multidict-6.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad81012b24b88aad4c70b2cbc2dad84018783221b7f923e926f4690ff8569da3"},
|
{file = "multidict-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:d091d123e44035cd5664554308477aff0b58db37e701e7598a67e907b98d1925"},
|
||||||
{file = "multidict-6.2.0-py3-none-any.whl", hash = "sha256:5d26547423e5e71dcc562c4acdc134b900640a39abd9066d7326a7cc2324c530"},
|
{file = "multidict-6.3.2-py3-none-any.whl", hash = "sha256:71409d4579f716217f23be2f5e7afca5ca926aaeb398aa11b72d793bff637a1f"},
|
||||||
{file = "multidict-6.2.0.tar.gz", hash = "sha256:0085b0afb2446e57050140240a8595846ed64d1cbd26cef936bfab3192c673b8"},
|
{file = "multidict-6.3.2.tar.gz", hash = "sha256:c1035eea471f759fa853dd6e76aaa1e389f93b3e1403093fa0fd3ab4db490678"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -4367,7 +4367,7 @@ files = [
|
|||||||
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
|
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
|
||||||
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
|
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
|
||||||
]
|
]
|
||||||
markers = {main = "python_version == \"3.10\"", dev = "python_full_version <= \"3.11.0a6\""}
|
markers = {main = "python_version < \"3.11\"", dev = "python_full_version <= \"3.11.0a6\""}
|
||||||
|
|
||||||
[package.source]
|
[package.source]
|
||||||
type = "legacy"
|
type = "legacy"
|
||||||
@@ -5104,4 +5104,4 @@ reference = "aliyun"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = "^3.10"
|
python-versions = "^3.10"
|
||||||
content-hash = "48aa6fabc582a0c75b333f9bd3418264a1fd15a5c8c50220b456ba00d03cd35e"
|
content-hash = "f1d23ae2feff2f5c96a97f1a7e52e1738858817b13281b9253a41851cacf0740"
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
global:
|
|
||||||
scrape_interval: 15s
|
|
||||||
evaluation_interval: 15s
|
|
||||||
|
|
||||||
scrape_configs:
|
|
||||||
- job_name: 'postgresql'
|
|
||||||
static_configs:
|
|
||||||
- targets: [ 'postgres-exporter:9187' ]
|
|
||||||
|
|
||||||
- job_name: 'redis'
|
|
||||||
static_configs:
|
|
||||||
- targets: [ 'redis-exporter:9121' ]
|
|
||||||
@@ -45,7 +45,6 @@ nonebot-plugin-alconna = "^0.54.0"
|
|||||||
tenacity = "^9.0.0"
|
tenacity = "^9.0.0"
|
||||||
nonebot-plugin-uninfo = ">0.4.1"
|
nonebot-plugin-uninfo = ">0.4.1"
|
||||||
nonebot-plugin-waiter = "^0.8.1"
|
nonebot-plugin-waiter = "^0.8.1"
|
||||||
multidict = ">=6.0.0,!=6.3.2"
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
nonebug = "^0.4"
|
nonebug = "^0.4"
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ from zhenxun.models.sign_user import SignUser
|
|||||||
from zhenxun.models.user_console import UserConsole
|
from zhenxun.models.user_console import UserConsole
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
from zhenxun.utils.decorator.shop import shop_register
|
from zhenxun.utils.decorator.shop import shop_register
|
||||||
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
|
||||||
from zhenxun.utils.manager.resource_manager import ResourceManager
|
from zhenxun.utils.manager.resource_manager import ResourceManager
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
from zhenxun.utils.platform import PlatformUtils
|
||||||
|
|
||||||
@@ -71,7 +70,7 @@ from public.bag_users t1
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@PriorityLifecycle.on_startup(priority=5)
|
@driver.on_startup
|
||||||
async def _():
|
async def _():
|
||||||
await ResourceManager.init_resources()
|
await ResourceManager.init_resources()
|
||||||
"""签到与用户的数据迁移"""
|
"""签到与用户的数据迁移"""
|
||||||
|
|||||||
@@ -26,21 +26,6 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
_matcher = on_alconna(Alconna("关于"), priority=5, block=True, rule=to_me())
|
_matcher = on_alconna(Alconna("关于"), priority=5, block=True, rule=to_me())
|
||||||
|
|
||||||
|
|
||||||
QQ_INFO = """
|
|
||||||
『绪山真寻Bot』
|
|
||||||
版本:{version}
|
|
||||||
简介:基于Nonebot2开发,支持多平台,是一个非常可爱的Bot呀,希望与大家要好好相处
|
|
||||||
""".strip()
|
|
||||||
|
|
||||||
INFO = """
|
|
||||||
『绪山真寻Bot』
|
|
||||||
版本:{version}
|
|
||||||
简介:基于Nonebot2开发,支持多平台,是一个非常可爱的Bot呀,希望与大家要好好相处
|
|
||||||
项目地址:https://github.com/zhenxun-org/zhenxun_bot
|
|
||||||
文档地址:https://zhenxun-org.github.io/zhenxun_bot/
|
|
||||||
""".strip()
|
|
||||||
|
|
||||||
|
|
||||||
@_matcher.handle()
|
@_matcher.handle()
|
||||||
async def _(session: Uninfo, arparma: Arparma):
|
async def _(session: Uninfo, arparma: Arparma):
|
||||||
ver_file = Path() / "__version__"
|
ver_file = Path() / "__version__"
|
||||||
@@ -50,11 +35,25 @@ async def _(session: Uninfo, arparma: Arparma):
|
|||||||
if text := await f.read():
|
if text := await f.read():
|
||||||
version = text.split(":")[-1].strip()
|
version = text.split(":")[-1].strip()
|
||||||
if PlatformUtils.is_qbot(session):
|
if PlatformUtils.is_qbot(session):
|
||||||
result: list[str | Path] = [QQ_INFO.format(version=version)]
|
info: list[str | Path] = [
|
||||||
|
f"""
|
||||||
|
『绪山真寻Bot』
|
||||||
|
版本:{version}
|
||||||
|
简介:基于Nonebot2开发,支持多平台,是一个非常可爱的Bot呀,希望与大家要好好相处
|
||||||
|
""".strip()
|
||||||
|
]
|
||||||
path = DATA_PATH / "about.png"
|
path = DATA_PATH / "about.png"
|
||||||
if path.exists():
|
if path.exists():
|
||||||
result.append(path)
|
info.append(path)
|
||||||
await MessageUtils.build_message(result).send() # type: ignore
|
|
||||||
else:
|
else:
|
||||||
await MessageUtils.build_message(INFO.format(version=version)).send()
|
info = [
|
||||||
logger.info("查看关于", arparma.header_result, session=session)
|
f"""
|
||||||
|
『绪山真寻Bot』
|
||||||
|
版本:{version}
|
||||||
|
简介:基于Nonebot2开发,支持多平台,是一个非常可爱的Bot呀,希望与大家要好好相处
|
||||||
|
项目地址:https://github.com/HibiKier/zhenxun_bot
|
||||||
|
文档地址:https://hibikier.github.io/zhenxun_bot/
|
||||||
|
""".strip()
|
||||||
|
]
|
||||||
|
await MessageUtils.build_message(info).send() # type: ignore
|
||||||
|
logger.info("查看关于", arparma.header_result, session=session)
|
||||||
|
|||||||
@@ -14,19 +14,13 @@ from nonebot_plugin_alconna import (
|
|||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_session import EventSession
|
||||||
|
|
||||||
from zhenxun.configs.config import BotConfig, Config
|
from zhenxun.configs.config import BotConfig, Config
|
||||||
from zhenxun.configs.utils import (
|
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||||
AICallableParam,
|
|
||||||
AICallableProperties,
|
|
||||||
AICallableTag,
|
|
||||||
PluginExtraData,
|
|
||||||
RegisterConfig,
|
|
||||||
)
|
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
from zhenxun.utils.enum import PluginType
|
from zhenxun.utils.enum import PluginType
|
||||||
from zhenxun.utils.message import MessageUtils
|
from zhenxun.utils.message import MessageUtils
|
||||||
from zhenxun.utils.rules import admin_check
|
from zhenxun.utils.rules import admin_check
|
||||||
|
|
||||||
from ._data_source import BanManage, call_ban
|
from ._data_source import BanManage
|
||||||
|
|
||||||
base_config = Config.get("ban")
|
base_config = Config.get("ban")
|
||||||
|
|
||||||
@@ -84,22 +78,6 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
type=int,
|
type=int,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
smart_tools=[
|
|
||||||
AICallableTag(
|
|
||||||
name="call_ban",
|
|
||||||
description="某人多次(至少三次)辱骂你,调用此方法进行封禁",
|
|
||||||
parameters=AICallableParam(
|
|
||||||
type="object",
|
|
||||||
properties={
|
|
||||||
"user_id": AICallableProperties(
|
|
||||||
type="string", description="用户的id"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
required=["user_id"],
|
|
||||||
),
|
|
||||||
func=call_ban,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
).to_dict(),
|
).to_dict(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,20 +5,9 @@ from nonebot_plugin_session import EventSession
|
|||||||
|
|
||||||
from zhenxun.models.ban_console import BanConsole
|
from zhenxun.models.ban_console import BanConsole
|
||||||
from zhenxun.models.level_user import LevelUser
|
from zhenxun.models.level_user import LevelUser
|
||||||
from zhenxun.services.log import logger
|
|
||||||
from zhenxun.utils.image_utils import BuildImage, ImageTemplate
|
from zhenxun.utils.image_utils import BuildImage, ImageTemplate
|
||||||
|
|
||||||
|
|
||||||
async def call_ban(user_id: str):
|
|
||||||
"""调用ban
|
|
||||||
|
|
||||||
参数:
|
|
||||||
user_id: 用户id
|
|
||||||
"""
|
|
||||||
await BanConsole.ban(user_id, None, 9, 60 * 12)
|
|
||||||
logger.info("辱骂次数过多,已将用户加入黑名单...", "ban", session=user_id)
|
|
||||||
|
|
||||||
|
|
||||||
class BanManage:
|
class BanManage:
|
||||||
@classmethod
|
@classmethod
|
||||||
async def build_ban_image(
|
async def build_ban_image(
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from zhenxun.services.log import logger
|
|||||||
from zhenxun.utils._build_image import BuildImage
|
from zhenxun.utils._build_image import BuildImage
|
||||||
from zhenxun.utils._image_template import ImageTemplate
|
from zhenxun.utils._image_template import ImageTemplate
|
||||||
from zhenxun.utils.http_utils import AsyncHttpx
|
from zhenxun.utils.http_utils import AsyncHttpx
|
||||||
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
from zhenxun.utils.platform import PlatformUtils
|
||||||
|
|
||||||
BASE_PATH = DATA_PATH / "welcome_message"
|
BASE_PATH = DATA_PATH / "welcome_message"
|
||||||
@@ -92,7 +91,7 @@ def migrate(path: Path):
|
|||||||
json.dump(new_data, f, ensure_ascii=False, indent=4)
|
json.dump(new_data, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
|
||||||
@PriorityLifecycle.on_startup(priority=5)
|
@driver.on_startup
|
||||||
def _():
|
def _():
|
||||||
"""数据迁移
|
"""数据迁移
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot_plugin_alconna import (
|
from nonebot_plugin_alconna import (
|
||||||
@@ -15,38 +14,35 @@ from nonebot_plugin_alconna import (
|
|||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_session import EventSession
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from zhenxun.configs.config import Config
|
from zhenxun.configs.utils import Command, PluginExtraData
|
||||||
from zhenxun.configs.utils import Command, PluginExtraData, RegisterConfig
|
|
||||||
from zhenxun.models.chat_history import ChatHistory
|
from zhenxun.models.chat_history import ChatHistory
|
||||||
from zhenxun.models.group_member_info import GroupInfoUser
|
from zhenxun.models.group_member_info import GroupInfoUser
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
from zhenxun.utils.enum import PluginType
|
from zhenxun.utils.enum import PluginType
|
||||||
from zhenxun.utils.image_utils import BuildImage, ImageTemplate
|
from zhenxun.utils.image_utils import ImageTemplate
|
||||||
from zhenxun.utils.message import MessageUtils
|
from zhenxun.utils.message import MessageUtils
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="消息统计",
|
name="消息统计",
|
||||||
description="消息统计查询",
|
description="消息统计查询",
|
||||||
usage="""
|
usage="""
|
||||||
格式:
|
格式:
|
||||||
消息排行 ?[type [日,周,月,季,年]] ?[--des]
|
消息排行 ?[type [日,周,月,年]] ?[--des]
|
||||||
|
|
||||||
快捷:
|
快捷:
|
||||||
[日,周,月,季,年]消息排行 ?[数量]
|
[日,周,月,年]消息排行 ?[数量]
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
消息排行 : 所有记录排行
|
消息排行 : 所有记录排行
|
||||||
日消息排行 : 今日记录排行
|
日消息排行 : 今日记录排行
|
||||||
周消息排行 : 本周记录排行
|
周消息排行 : 今日记录排行
|
||||||
月消息排行 : 本月记录排行
|
月消息排行 : 今日记录排行
|
||||||
季消息排行 : 本季度记录排行
|
年消息排行 : 今日记录排行
|
||||||
年消息排行 : 本年记录排行
|
|
||||||
消息排行 周 --des : 逆序周记录排行
|
消息排行 周 --des : 逆序周记录排行
|
||||||
""".strip(),
|
""".strip(),
|
||||||
extra=PluginExtraData(
|
extra=PluginExtraData(
|
||||||
author="HibiKier",
|
author="HibiKier",
|
||||||
version="0.2",
|
version="0.1",
|
||||||
plugin_type=PluginType.NORMAL,
|
plugin_type=PluginType.NORMAL,
|
||||||
menu_type="数据统计",
|
menu_type="数据统计",
|
||||||
commands=[
|
commands=[
|
||||||
@@ -54,19 +50,8 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
Command(command="日消息统计"),
|
Command(command="日消息统计"),
|
||||||
Command(command="周消息排行"),
|
Command(command="周消息排行"),
|
||||||
Command(command="月消息排行"),
|
Command(command="月消息排行"),
|
||||||
Command(command="季消息排行"),
|
|
||||||
Command(command="年消息排行"),
|
Command(command="年消息排行"),
|
||||||
],
|
],
|
||||||
configs=[
|
|
||||||
RegisterConfig(
|
|
||||||
module="chat_history",
|
|
||||||
key="SHOW_QUIT_MEMBER",
|
|
||||||
value=True,
|
|
||||||
help="是否在消息排行中显示已退群用户",
|
|
||||||
default_value=True,
|
|
||||||
type=bool,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
).to_dict(),
|
).to_dict(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -75,7 +60,7 @@ _matcher = on_alconna(
|
|||||||
Alconna(
|
Alconna(
|
||||||
"消息排行",
|
"消息排行",
|
||||||
Option("--des", action=store_true, help_text="逆序"),
|
Option("--des", action=store_true, help_text="逆序"),
|
||||||
Args["type?", ["日", "周", "月", "季", "年"]]["count?", int, 10],
|
Args["type?", ["日", "周", "月", "年"]]["count?", int, 10],
|
||||||
),
|
),
|
||||||
aliases={"消息统计"},
|
aliases={"消息统计"},
|
||||||
priority=5,
|
priority=5,
|
||||||
@@ -83,7 +68,7 @@ _matcher = on_alconna(
|
|||||||
)
|
)
|
||||||
|
|
||||||
_matcher.shortcut(
|
_matcher.shortcut(
|
||||||
r"(?P<type>['日', '周', '月', '季', '年'])?消息(排行|统计)\s?(?P<cnt>\d+)?",
|
r"(?P<type>['日', '周', '月', '年'])?消息(排行|统计)\s?(?P<cnt>\d+)?",
|
||||||
command="消息排行",
|
command="消息排行",
|
||||||
arguments=["{type}", "{cnt}"],
|
arguments=["{type}", "{cnt}"],
|
||||||
prefix=True,
|
prefix=True,
|
||||||
@@ -111,57 +96,20 @@ async def _(
|
|||||||
date_scope = (time_now - timedelta(days=7), time_now)
|
date_scope = (time_now - timedelta(days=7), time_now)
|
||||||
elif date in ["月"]:
|
elif date in ["月"]:
|
||||||
date_scope = (time_now - timedelta(days=30), time_now)
|
date_scope = (time_now - timedelta(days=30), time_now)
|
||||||
elif date in ["季"]:
|
column_name = ["名次", "昵称", "发言次数"]
|
||||||
date_scope = (time_now - timedelta(days=90), time_now)
|
|
||||||
column_name = ["名次", "头像", "昵称", "发言次数"]
|
|
||||||
show_quit_member = Config.get_config("chat_history", "SHOW_QUIT_MEMBER", True)
|
|
||||||
|
|
||||||
fetch_count = count.result
|
|
||||||
if not show_quit_member:
|
|
||||||
fetch_count = count.result * 2
|
|
||||||
|
|
||||||
if rank_data := await ChatHistory.get_group_msg_rank(
|
if rank_data := await ChatHistory.get_group_msg_rank(
|
||||||
group_id, fetch_count, "DES" if arparma.find("des") else "DESC", date_scope
|
group_id, count.result, "DES" if arparma.find("des") else "DESC", date_scope
|
||||||
):
|
):
|
||||||
idx = 1
|
idx = 1
|
||||||
data_list = []
|
data_list = []
|
||||||
|
|
||||||
for uid, num in rank_data:
|
for uid, num in rank_data:
|
||||||
if len(data_list) >= count.result:
|
if user := await GroupInfoUser.filter(
|
||||||
break
|
|
||||||
|
|
||||||
user_in_group = await GroupInfoUser.filter(
|
|
||||||
user_id=uid, group_id=group_id
|
user_id=uid, group_id=group_id
|
||||||
).first()
|
).first():
|
||||||
|
user_name = user.user_name
|
||||||
if not user_in_group and not show_quit_member:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if user_in_group:
|
|
||||||
user_name = user_in_group.user_name
|
|
||||||
else:
|
else:
|
||||||
user_name = f"{uid}(已退群)"
|
user_name = uid
|
||||||
|
data_list.append([idx, user_name, num])
|
||||||
avatar_size = 40
|
|
||||||
try:
|
|
||||||
avatar_bytes = await PlatformUtils.get_user_avatar(str(uid), "qq")
|
|
||||||
if avatar_bytes:
|
|
||||||
avatar_img = BuildImage(
|
|
||||||
avatar_size, avatar_size, background=BytesIO(avatar_bytes)
|
|
||||||
)
|
|
||||||
await avatar_img.circle()
|
|
||||||
avatar_tuple = (avatar_img, avatar_size, avatar_size)
|
|
||||||
else:
|
|
||||||
avatar_img = BuildImage(avatar_size, avatar_size, color="#CCCCCC")
|
|
||||||
await avatar_img.circle()
|
|
||||||
avatar_tuple = (avatar_img, avatar_size, avatar_size)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"获取用户头像失败: {e}", "chat_history")
|
|
||||||
avatar_img = BuildImage(avatar_size, avatar_size, color="#CCCCCC")
|
|
||||||
await avatar_img.circle()
|
|
||||||
avatar_tuple = (avatar_img, avatar_size, avatar_size)
|
|
||||||
|
|
||||||
data_list.append([idx, avatar_tuple, user_name, num])
|
|
||||||
idx += 1
|
idx += 1
|
||||||
if not date_scope:
|
if not date_scope:
|
||||||
if date_scope := await ChatHistory.get_group_first_msg_datetime(group_id):
|
if date_scope := await ChatHistory.get_group_first_msg_datetime(group_id):
|
||||||
@@ -184,3 +132,13 @@ async def _(
|
|||||||
)
|
)
|
||||||
await MessageUtils.build_message(A).finish(reply_to=True)
|
await MessageUtils.build_message(A).finish(reply_to=True)
|
||||||
await MessageUtils.build_message("群组消息记录为空...").finish()
|
await MessageUtils.build_message("群组消息记录为空...").finish()
|
||||||
|
|
||||||
|
|
||||||
|
# # @test.handle()
|
||||||
|
# # async def _(event: MessageEvent):
|
||||||
|
# # print(await ChatHistory.get_user_msg(event.user_id, "private"))
|
||||||
|
# # print(await ChatHistory.get_user_msg_count(event.user_id, "private"))
|
||||||
|
# # print(await ChatHistory.get_user_msg(event.user_id, "group"))
|
||||||
|
# # print(await ChatHistory.get_user_msg_count(event.user_id, "group"))
|
||||||
|
# # print(await ChatHistory.get_group_msg(event.group_id))
|
||||||
|
# # print(await ChatHistory.get_group_msg_count(event.group_id))
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
configs=[
|
configs=[
|
||||||
RegisterConfig(
|
RegisterConfig(
|
||||||
key="type",
|
key="type",
|
||||||
value="zhenxun",
|
value="normal",
|
||||||
help="帮助图片样式 [normal, HTML, zhenxun]",
|
help="帮助图片样式 ['normal', 'HTML', 'zhenxun']",
|
||||||
default_value="zhenxun",
|
default_value="zhenxun",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ async def create_help_img(
|
|||||||
|
|
||||||
match help_type:
|
match help_type:
|
||||||
case "html":
|
case "html":
|
||||||
result = BuildImage.open(
|
result = BuildImage.open(await build_html_image(group_id, is_detail))
|
||||||
await build_html_image(session, group_id, is_detail)
|
|
||||||
)
|
|
||||||
case "zhenxun":
|
case "zhenxun":
|
||||||
result = BuildImage.open(
|
result = BuildImage.open(
|
||||||
await build_zhenxun_image(session, group_id, is_detail)
|
await build_zhenxun_image(session, group_id, is_detail)
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
|
||||||
from nonebot_plugin_uninfo import Uninfo
|
|
||||||
|
|
||||||
from zhenxun.models.bot_console import BotConsole
|
|
||||||
from zhenxun.models.group_console import GroupConsole
|
from zhenxun.models.group_console import GroupConsole
|
||||||
from zhenxun.models.plugin_info import PluginInfo
|
from zhenxun.models.plugin_info import PluginInfo
|
||||||
from zhenxun.utils.enum import PluginType
|
from zhenxun.utils.enum import PluginType
|
||||||
@@ -30,15 +27,13 @@ async def sort_type() -> dict[str, list[PluginInfo]]:
|
|||||||
|
|
||||||
|
|
||||||
async def classify_plugin(
|
async def classify_plugin(
|
||||||
session: Uninfo, group_id: str | None, is_detail: bool, handle: Callable
|
group_id: str | None, is_detail: bool, handle: Callable
|
||||||
) -> dict[str, list]:
|
) -> dict[str, list]:
|
||||||
"""对插件进行分类并判断状态
|
"""对插件进行分类并判断状态
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
session: Uninfo对象
|
|
||||||
group_id: 群组id
|
group_id: 群组id
|
||||||
is_detail: 是否详细帮助
|
is_detail: 是否详细帮助
|
||||||
handle: 回调方法
|
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
dict[str, list[Item]]: 分类插件数据
|
dict[str, list[Item]]: 分类插件数据
|
||||||
@@ -46,10 +41,9 @@ async def classify_plugin(
|
|||||||
sort_data = await sort_type()
|
sort_data = await sort_type()
|
||||||
classify: dict[str, list] = {}
|
classify: dict[str, list] = {}
|
||||||
group = await GroupConsole.get_or_none(group_id=group_id) if group_id else None
|
group = await GroupConsole.get_or_none(group_id=group_id) if group_id else None
|
||||||
bot = await BotConsole.get_or_none(bot_id=session.self_id)
|
|
||||||
for menu, value in sort_data.items():
|
for menu, value in sort_data.items():
|
||||||
for plugin in value:
|
for plugin in value:
|
||||||
if not classify.get(menu):
|
if not classify.get(menu):
|
||||||
classify[menu] = []
|
classify[menu] = []
|
||||||
classify[menu].append(handle(bot, plugin, group, is_detail))
|
classify[menu].append(handle(plugin, group, is_detail))
|
||||||
return classify
|
return classify
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ import os
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
from nonebot_plugin_htmlrender import template_to_pic
|
from nonebot_plugin_htmlrender import template_to_pic
|
||||||
from nonebot_plugin_uninfo import Uninfo
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from zhenxun.configs.path_config import TEMPLATE_PATH
|
from zhenxun.configs.path_config import TEMPLATE_PATH
|
||||||
from zhenxun.models.bot_console import BotConsole
|
|
||||||
from zhenxun.models.group_console import GroupConsole
|
from zhenxun.models.group_console import GroupConsole
|
||||||
from zhenxun.models.plugin_info import PluginInfo
|
from zhenxun.models.plugin_info import PluginInfo
|
||||||
from zhenxun.utils.enum import BlockType
|
from zhenxun.utils.enum import BlockType
|
||||||
@@ -50,12 +48,11 @@ ICON2STR = {
|
|||||||
|
|
||||||
|
|
||||||
def __handle_item(
|
def __handle_item(
|
||||||
bot: BotConsole, plugin: PluginInfo, group: GroupConsole | None, is_detail: bool
|
plugin: PluginInfo, group: GroupConsole | None, is_detail: bool
|
||||||
) -> Item:
|
) -> Item:
|
||||||
"""构造Item
|
"""构造Item
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
bot: BotConsole
|
|
||||||
plugin: PluginInfo
|
plugin: PluginInfo
|
||||||
group: 群组
|
group: 群组
|
||||||
is_detail: 是否详细
|
is_detail: 是否详细
|
||||||
@@ -76,13 +73,10 @@ def __handle_item(
|
|||||||
]:
|
]:
|
||||||
sta = 2
|
sta = 2
|
||||||
if group:
|
if group:
|
||||||
if f"{plugin.module}," in group.superuser_block_plugin:
|
if f"{plugin.module}:super," in group.block_plugin:
|
||||||
sta = 2
|
sta = 2
|
||||||
if f"{plugin.module}," in group.block_plugin:
|
if f"{plugin.module}," in group.block_plugin:
|
||||||
sta = 1
|
sta = 1
|
||||||
if bot:
|
|
||||||
if f"{plugin.module}," in bot.block_plugins:
|
|
||||||
sta = 2
|
|
||||||
return Item(plugin_name=plugin.name, sta=sta)
|
return Item(plugin_name=plugin.name, sta=sta)
|
||||||
|
|
||||||
|
|
||||||
@@ -125,17 +119,14 @@ def build_plugin_data(classify: dict[str, list[Item]]) -> list[dict[str, str]]:
|
|||||||
return plugin_list
|
return plugin_list
|
||||||
|
|
||||||
|
|
||||||
async def build_html_image(
|
async def build_html_image(group_id: str | None, is_detail: bool) -> bytes:
|
||||||
session: Uninfo, group_id: str | None, is_detail: bool
|
|
||||||
) -> bytes:
|
|
||||||
"""构造HTML帮助图片
|
"""构造HTML帮助图片
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
session: Uninfo
|
|
||||||
group_id: 群号
|
group_id: 群号
|
||||||
is_detail: 是否详细帮助
|
is_detail: 是否详细帮助
|
||||||
"""
|
"""
|
||||||
classify = await classify_plugin(session, group_id, is_detail, __handle_item)
|
classify = await classify_plugin(group_id, is_detail, __handle_item)
|
||||||
plugin_list = build_plugin_data(classify)
|
plugin_list = build_plugin_data(classify)
|
||||||
return await template_to_pic(
|
return await template_to_pic(
|
||||||
template_path=str((TEMPLATE_PATH / "menu").absolute()),
|
template_path=str((TEMPLATE_PATH / "menu").absolute()),
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from pydantic import BaseModel
|
|||||||
from zhenxun.configs.config import BotConfig
|
from zhenxun.configs.config import BotConfig
|
||||||
from zhenxun.configs.path_config import TEMPLATE_PATH
|
from zhenxun.configs.path_config import TEMPLATE_PATH
|
||||||
from zhenxun.configs.utils import PluginExtraData
|
from zhenxun.configs.utils import PluginExtraData
|
||||||
from zhenxun.models.bot_console import BotConsole
|
|
||||||
from zhenxun.models.group_console import GroupConsole
|
from zhenxun.models.group_console import GroupConsole
|
||||||
from zhenxun.models.plugin_info import PluginInfo
|
from zhenxun.models.plugin_info import PluginInfo
|
||||||
from zhenxun.utils.enum import BlockType
|
from zhenxun.utils.enum import BlockType
|
||||||
@@ -22,19 +21,12 @@ class Item(BaseModel):
|
|||||||
"""插件命令"""
|
"""插件命令"""
|
||||||
|
|
||||||
|
|
||||||
def __handle_item(
|
def __handle_item(plugin: PluginInfo, group: GroupConsole | None, is_detail: bool):
|
||||||
bot: BotConsole | None,
|
|
||||||
plugin: PluginInfo,
|
|
||||||
group: GroupConsole | None,
|
|
||||||
is_detail: bool,
|
|
||||||
):
|
|
||||||
"""构造Item
|
"""构造Item
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
bot: BotConsole
|
|
||||||
plugin: PluginInfo
|
plugin: PluginInfo
|
||||||
group: 群组
|
group: 群组
|
||||||
is_detail: 是否为详细
|
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
Item: Item
|
Item: Item
|
||||||
@@ -48,8 +40,6 @@ def __handle_item(
|
|||||||
plugin.name = f"{plugin.name}(不可用)"
|
plugin.name = f"{plugin.name}(不可用)"
|
||||||
elif group and f"{plugin.module}," in group.block_plugin:
|
elif group and f"{plugin.module}," in group.block_plugin:
|
||||||
plugin.name = f"{plugin.name}(不可用)"
|
plugin.name = f"{plugin.name}(不可用)"
|
||||||
elif bot and f"{plugin.module}," in bot.block_plugins:
|
|
||||||
plugin.name = f"{plugin.name}(不可用)"
|
|
||||||
commands = []
|
commands = []
|
||||||
nb_plugin = nonebot.get_plugin_by_module_name(plugin.module_path)
|
nb_plugin = nonebot.get_plugin_by_module_name(plugin.module_path)
|
||||||
if is_detail and nb_plugin and nb_plugin.metadata and nb_plugin.metadata.extra:
|
if is_detail and nb_plugin and nb_plugin.metadata and nb_plugin.metadata.extra:
|
||||||
@@ -152,7 +142,7 @@ async def build_zhenxun_image(
|
|||||||
group_id: 群号
|
group_id: 群号
|
||||||
is_detail: 是否详细帮助
|
is_detail: 是否详细帮助
|
||||||
"""
|
"""
|
||||||
classify = await classify_plugin(session, group_id, is_detail, __handle_item)
|
classify = await classify_plugin(group_id, is_detail, __handle_item)
|
||||||
plugin_list = build_plugin_data(classify)
|
plugin_list = build_plugin_data(classify)
|
||||||
platform = PlatformUtils.get_platform(session)
|
platform = PlatformUtils.get_platform(session)
|
||||||
bot_id = BotConfig.get_qbot_uid(session.self_id) or session.self_id
|
bot_id = BotConfig.get_qbot_uid(session.self_id) or session.self_id
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from zhenxun.utils.message import MessageUtils
|
|||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="笨蛋检测",
|
name="笨蛋检测",
|
||||||
description="功能名称当命令检测",
|
description="功能名称当命令检测",
|
||||||
usage="""当一些笨蛋直接输入功能名称时,提示笨蛋使用帮助指令查看功能帮助""".strip(),
|
usage="""被动""".strip(),
|
||||||
extra=PluginExtraData(
|
extra=PluginExtraData(
|
||||||
author="HibiKier",
|
author="HibiKier",
|
||||||
version="0.1",
|
version="0.1",
|
||||||
|
|||||||
@@ -49,14 +49,4 @@ Config.add_plugin_config(
|
|||||||
type=bool,
|
type=bool,
|
||||||
)
|
)
|
||||||
|
|
||||||
Config.add_plugin_config(
|
|
||||||
"hook",
|
|
||||||
"RECORD_BOT_SENT_MESSAGES",
|
|
||||||
True,
|
|
||||||
help="记录bot消息发送",
|
|
||||||
default_value=True,
|
|
||||||
type=bool,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
|
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
|
||||||
|
|||||||
@@ -1,85 +1,23 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from nonebot.adapters import Bot, Message
|
from nonebot.adapters import Bot
|
||||||
|
|
||||||
from zhenxun.configs.config import Config
|
|
||||||
from zhenxun.models.bot_message_store import BotMessageStore
|
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
from zhenxun.utils.enum import BotSentType
|
|
||||||
from zhenxun.utils.manager.message_manager import MessageManager
|
from zhenxun.utils.manager.message_manager import MessageManager
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
|
||||||
|
|
||||||
|
|
||||||
def replace_message(message: Message) -> str:
|
|
||||||
"""将消息中的at、image、record、face替换为字符串
|
|
||||||
|
|
||||||
参数:
|
|
||||||
message: Message
|
|
||||||
|
|
||||||
返回:
|
|
||||||
str: 文本消息
|
|
||||||
"""
|
|
||||||
result = ""
|
|
||||||
for msg in message:
|
|
||||||
if isinstance(msg, str):
|
|
||||||
result += msg
|
|
||||||
elif msg.type == "at":
|
|
||||||
result += f"@{msg.data['qq']}"
|
|
||||||
elif msg.type == "image":
|
|
||||||
result += "[image]"
|
|
||||||
elif msg.type == "record":
|
|
||||||
result += "[record]"
|
|
||||||
elif msg.type == "face":
|
|
||||||
result += f"[face:{msg.data['id']}]"
|
|
||||||
elif msg.type == "reply":
|
|
||||||
result += ""
|
|
||||||
else:
|
|
||||||
result += str(msg)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@Bot.on_called_api
|
@Bot.on_called_api
|
||||||
async def handle_api_result(
|
async def handle_api_result(
|
||||||
bot: Bot, exception: Exception | None, api: str, data: dict[str, Any], result: Any
|
bot: Bot, exception: Exception | None, api: str, data: dict[str, Any], result: Any
|
||||||
):
|
):
|
||||||
if exception or api != "send_msg":
|
if not exception and api == "send_msg":
|
||||||
return
|
try:
|
||||||
user_id = data.get("user_id")
|
if (uid := data.get("user_id")) and (msg_id := result.get("message_id")):
|
||||||
group_id = data.get("group_id")
|
MessageManager.add(str(uid), str(msg_id))
|
||||||
message_id = result.get("message_id")
|
logger.debug(
|
||||||
message: Message = data.get("message", "")
|
f"收集消息id,user_id: {uid}, msg_id: {msg_id}", "msg_hook"
|
||||||
message_type = data.get("message_type")
|
)
|
||||||
try:
|
except Exception as e:
|
||||||
# 记录消息id
|
logger.warning(
|
||||||
if user_id and message_id:
|
f"收集消息id发生错误...data: {data}, result: {result}", "msg_hook", e=e
|
||||||
MessageManager.add(str(user_id), str(message_id))
|
|
||||||
logger.debug(
|
|
||||||
f"收集消息id,user_id: {user_id}, msg_id: {message_id}", "msg_hook"
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
|
||||||
logger.warning(
|
|
||||||
f"收集消息id发生错误...data: {data}, result: {result}", "msg_hook", e=e
|
|
||||||
)
|
|
||||||
if not Config.get_config("hook", "RECORD_BOT_SENT_MESSAGES"):
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await BotMessageStore.create(
|
|
||||||
bot_id=bot.self_id,
|
|
||||||
user_id=user_id,
|
|
||||||
group_id=group_id,
|
|
||||||
sent_type=BotSentType.GROUP
|
|
||||||
if message_type == "group"
|
|
||||||
else BotSentType.PRIVATE,
|
|
||||||
text=replace_message(message),
|
|
||||||
plain_text=message.extract_plain_text()
|
|
||||||
if isinstance(message, Message)
|
|
||||||
else replace_message(message),
|
|
||||||
platform=PlatformUtils.get_platform(bot),
|
|
||||||
)
|
|
||||||
logger.debug(f"消息发送记录,message: {message}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(
|
|
||||||
f"消息发送记录发生错误...data: {data}, result: {result}",
|
|
||||||
"msg_hook",
|
|
||||||
e=e,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from zhenxun.configs.config import Config
|
|||||||
from zhenxun.configs.path_config import DATA_PATH
|
from zhenxun.configs.path_config import DATA_PATH
|
||||||
from zhenxun.configs.utils import RegisterConfig
|
from zhenxun.configs.utils import RegisterConfig
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
|
||||||
|
|
||||||
_yaml = YAML(pure=True)
|
_yaml = YAML(pure=True)
|
||||||
_yaml.allow_unicode = True
|
_yaml.allow_unicode = True
|
||||||
@@ -103,7 +102,7 @@ def _generate_simple_config(exists_module: list[str]):
|
|||||||
temp_file.unlink()
|
temp_file.unlink()
|
||||||
|
|
||||||
|
|
||||||
@PriorityLifecycle.on_startup(priority=0)
|
@driver.on_startup
|
||||||
def _():
|
def _():
|
||||||
"""
|
"""
|
||||||
初始化插件数据配置
|
初始化插件数据配置
|
||||||
@@ -126,4 +125,3 @@ def _():
|
|||||||
with plugins2config_file.open("w", encoding="utf8") as wf:
|
with plugins2config_file.open("w", encoding="utf8") as wf:
|
||||||
_yaml.dump(_data, wf)
|
_yaml.dump(_data, wf)
|
||||||
_generate_simple_config(exists_module)
|
_generate_simple_config(exists_module)
|
||||||
Config.reload()
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from zhenxun.utils.enum import (
|
|||||||
PluginLimitType,
|
PluginLimitType,
|
||||||
PluginType,
|
PluginType,
|
||||||
)
|
)
|
||||||
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
|
||||||
|
|
||||||
from .manager import manager
|
from .manager import manager
|
||||||
|
|
||||||
@@ -96,7 +95,7 @@ async def _handle_setting(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@PriorityLifecycle.on_startup(priority=5)
|
@driver.on_startup
|
||||||
async def _():
|
async def _():
|
||||||
"""
|
"""
|
||||||
初始化插件数据配置
|
初始化插件数据配置
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from zhenxun.models.group_console import GroupConsole
|
|||||||
from zhenxun.models.task_info import TaskInfo
|
from zhenxun.models.task_info import TaskInfo
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
from zhenxun.utils.common_utils import CommonUtils
|
from zhenxun.utils.common_utils import CommonUtils
|
||||||
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
|
||||||
|
|
||||||
driver: Driver = nonebot.get_driver()
|
driver: Driver = nonebot.get_driver()
|
||||||
|
|
||||||
@@ -133,7 +132,7 @@ async def create_schedule(task: Task):
|
|||||||
logger.error(f"动态创建定时任务 {task.name}({task.module}) 失败", e=e)
|
logger.error(f"动态创建定时任务 {task.name}({task.module}) 失败", e=e)
|
||||||
|
|
||||||
|
|
||||||
@PriorityLifecycle.on_startup(priority=5)
|
@driver.on_startup
|
||||||
async def _():
|
async def _():
|
||||||
"""
|
"""
|
||||||
初始化插件数据配置
|
初始化插件数据配置
|
||||||
|
|||||||
@@ -1,252 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, Subcommand, on_alconna
|
|
||||||
from nonebot_plugin_apscheduler import scheduler
|
|
||||||
from nonebot_plugin_uninfo import Uninfo
|
|
||||||
from nonebot_plugin_waiter import prompt_until
|
|
||||||
|
|
||||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
|
||||||
from zhenxun.services.log import logger
|
|
||||||
from zhenxun.utils.depends import UserName
|
|
||||||
from zhenxun.utils.message import MessageUtils
|
|
||||||
from zhenxun.utils.utils import is_number
|
|
||||||
|
|
||||||
from .data_source import BankManager
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="小真寻银行",
|
|
||||||
description="""
|
|
||||||
小真寻银行,提供高品质的存款!当好感度等级达到指初识时,小真寻会偷偷的帮助你哦。
|
|
||||||
存款额度与好感度有关,每日存款次数有限制。
|
|
||||||
基础存款提供基础利息
|
|
||||||
每日存款提供高额利息
|
|
||||||
""".strip(),
|
|
||||||
usage="""
|
|
||||||
指令:
|
|
||||||
存款 [金额]
|
|
||||||
取款 [金额]
|
|
||||||
银行信息
|
|
||||||
我的银行信息
|
|
||||||
""".strip(),
|
|
||||||
extra=PluginExtraData(
|
|
||||||
author="HibiKier",
|
|
||||||
version="0.1",
|
|
||||||
menu_type="群内小游戏",
|
|
||||||
configs=[
|
|
||||||
RegisterConfig(
|
|
||||||
key="sign_max_deposit",
|
|
||||||
value=100,
|
|
||||||
help="好感度换算存款金额比例,当值是100时,最大存款金额=好感度*100,存款的最低金额是100(强制)",
|
|
||||||
default_value=100,
|
|
||||||
type=int,
|
|
||||||
),
|
|
||||||
RegisterConfig(
|
|
||||||
key="max_daily_deposit_count",
|
|
||||||
value=3,
|
|
||||||
help="每日最大存款次数",
|
|
||||||
default_value=3,
|
|
||||||
type=int,
|
|
||||||
),
|
|
||||||
RegisterConfig(
|
|
||||||
key="rate_range",
|
|
||||||
value=[0.0005, 0.001],
|
|
||||||
help="小时利率范围",
|
|
||||||
default_value=[0.0005, 0.001],
|
|
||||||
type=list[float],
|
|
||||||
),
|
|
||||||
RegisterConfig(
|
|
||||||
key="impression_event",
|
|
||||||
value=25,
|
|
||||||
help="到达指定好感度时随机提高或降低利率",
|
|
||||||
default_value=25,
|
|
||||||
type=int,
|
|
||||||
),
|
|
||||||
RegisterConfig(
|
|
||||||
key="impression_event_range",
|
|
||||||
value=[0.00001, 0.0003],
|
|
||||||
help="到达指定好感度时随机提高或降低利率",
|
|
||||||
default_value=[0.00001, 0.0003],
|
|
||||||
type=list[float],
|
|
||||||
),
|
|
||||||
RegisterConfig(
|
|
||||||
key="impression_event_prop",
|
|
||||||
value=0.3,
|
|
||||||
help="到达指定好感度时随机提高或降低利率触发概率",
|
|
||||||
default_value=0.3,
|
|
||||||
type=float,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).to_dict(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
_matcher = on_alconna(
|
|
||||||
Alconna(
|
|
||||||
"mahiro-bank",
|
|
||||||
Subcommand("deposit", Args["amount?", int]),
|
|
||||||
Subcommand("withdraw", Args["amount?", int]),
|
|
||||||
Subcommand("user-info"),
|
|
||||||
Subcommand("bank-info"),
|
|
||||||
# Subcommand("loan", Args["amount?", int]),
|
|
||||||
# Subcommand("repayment", Args["amount?", int]),
|
|
||||||
),
|
|
||||||
priority=5,
|
|
||||||
block=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
_matcher.shortcut(
|
|
||||||
r"存款\s*(?P<amount>\d+)?",
|
|
||||||
command="mahiro-bank",
|
|
||||||
arguments=["deposit", "{amount}"],
|
|
||||||
prefix=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
_matcher.shortcut(
|
|
||||||
r"取款\s*(?P<withdraw>\d+)?",
|
|
||||||
command="mahiro-bank",
|
|
||||||
arguments=["withdraw", "{withdraw}"],
|
|
||||||
prefix=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
_matcher.shortcut(
|
|
||||||
r"我的银行信息",
|
|
||||||
command="mahiro-bank",
|
|
||||||
arguments=["user-info"],
|
|
||||||
prefix=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
_matcher.shortcut(
|
|
||||||
r"银行信息",
|
|
||||||
command="mahiro-bank",
|
|
||||||
arguments=["bank-info"],
|
|
||||||
prefix=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_amount(handle_type: str) -> int:
|
|
||||||
amount_num = await prompt_until(
|
|
||||||
f"请输入{handle_type}金币数量",
|
|
||||||
lambda msg: is_number(msg.extract_plain_text()),
|
|
||||||
timeout=60,
|
|
||||||
retry=3,
|
|
||||||
retry_prompt="输入错误,请输入数字。剩余次数:{count}",
|
|
||||||
)
|
|
||||||
if not amount_num:
|
|
||||||
await MessageUtils.build_message(
|
|
||||||
"输入超时了哦,小真寻柜员以取消本次存款操作..."
|
|
||||||
).finish()
|
|
||||||
return int(amount_num.extract_plain_text())
|
|
||||||
|
|
||||||
|
|
||||||
@_matcher.assign("deposit")
|
|
||||||
async def _(session: Uninfo, arparma: Arparma, amount: Match[int]):
|
|
||||||
amount_num = amount.result if amount.available else await get_amount("存款")
|
|
||||||
if result := await BankManager.deposit_check(session.user.id, amount_num):
|
|
||||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
|
||||||
_, rate, event_rate = await BankManager.deposit(session.user.id, amount_num)
|
|
||||||
result = (
|
|
||||||
f"存款成功!\n此次存款金额为: {amount.result}\n"
|
|
||||||
f"当前小时利率为: {rate * 100:.2f}%"
|
|
||||||
)
|
|
||||||
effective_hour = int(24 - datetime.now().hour)
|
|
||||||
if event_rate:
|
|
||||||
result += f"(小真寻偷偷将小时利率给你增加了 {event_rate:.2f}% 哦)"
|
|
||||||
result += (
|
|
||||||
f"\n预计总收益为: {int(amount.result * rate * effective_hour) or 1} 金币。"
|
|
||||||
)
|
|
||||||
logger.info(
|
|
||||||
f"小真寻银行存款:{amount_num},当前存款数:{amount.result},存款小时利率: {rate}",
|
|
||||||
arparma.header_result,
|
|
||||||
session=session,
|
|
||||||
)
|
|
||||||
await MessageUtils.build_message(result).finish(at_sender=True)
|
|
||||||
|
|
||||||
|
|
||||||
@_matcher.assign("withdraw")
|
|
||||||
async def _(session: Uninfo, arparma: Arparma, amount: Match[int]):
|
|
||||||
amount_num = amount.result if amount.available else await get_amount("取款")
|
|
||||||
if result := await BankManager.withdraw_check(session.user.id, amount_num):
|
|
||||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
|
||||||
try:
|
|
||||||
user = await BankManager.withdraw(session.user.id, amount_num)
|
|
||||||
result = (
|
|
||||||
f"取款成功!\n当前取款金额为: {amount_num}\n当前存款金额为: {user.amount}"
|
|
||||||
)
|
|
||||||
logger.info(
|
|
||||||
f"小真寻银行取款:{amount_num}, 当前存款数:{user.amount},"
|
|
||||||
f" 存款小时利率:{user.rate}",
|
|
||||||
arparma.header_result,
|
|
||||||
session=session,
|
|
||||||
)
|
|
||||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
|
||||||
except ValueError:
|
|
||||||
await MessageUtils.build_message("你的银行内的存款数量不足哦...").finish(
|
|
||||||
reply_to=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@_matcher.assign("user-info")
|
|
||||||
async def _(session: Uninfo, arparma: Arparma, uname: str = UserName()):
|
|
||||||
result = await BankManager.get_user_info(session, uname)
|
|
||||||
await MessageUtils.build_message(result).send()
|
|
||||||
logger.info("查看银行个人信息", arparma.header_result, session=session)
|
|
||||||
|
|
||||||
|
|
||||||
@_matcher.assign("bank-info")
|
|
||||||
async def _(session: Uninfo, arparma: Arparma):
|
|
||||||
result = await BankManager.get_bank_info()
|
|
||||||
await MessageUtils.build_message(result).send()
|
|
||||||
logger.info("查看银行信息", arparma.header_result, session=session)
|
|
||||||
|
|
||||||
|
|
||||||
# @_matcher.assign("loan")
|
|
||||||
# async def _(session: Uninfo, arparma: Arparma, amount: Match[int]):
|
|
||||||
# amount_num = amount.result if amount.available else await get_amount("贷款")
|
|
||||||
# if amount_num <= 0:
|
|
||||||
# await MessageUtils.build_message("贷款数量必须大于 0 啊笨蛋!").finish()
|
|
||||||
# try:
|
|
||||||
# user, event_rate = await BankManager.loan(session.user.id, amount_num)
|
|
||||||
# result = (
|
|
||||||
# f"贷款成功!\n当前贷金额为: {user.loan_amount}"
|
|
||||||
# f"\n当前利率为: {user.loan_rate * 100}%"
|
|
||||||
# )
|
|
||||||
# if event_rate:
|
|
||||||
# result += f"(小真寻偷偷将利率给你降低了 {event_rate}% 哦)"
|
|
||||||
# result += f"\n预计每小时利息为:{int(user.loan_amount * user.loan_rate)}金币。"
|
|
||||||
# logger.info(
|
|
||||||
# f"小真寻银行贷款: {amount_num}, 当前贷款数: {user.loan_amount}, "
|
|
||||||
# f"贷款利率: {user.loan_rate}",
|
|
||||||
# arparma.header_result,
|
|
||||||
# session=session,
|
|
||||||
# )
|
|
||||||
# except ValueError:
|
|
||||||
# await MessageUtils.build_message(
|
|
||||||
# "贷款数量超过最大限制,请签到提升好感度获取更多额度吧..."
|
|
||||||
# ).finish(reply_to=True)
|
|
||||||
|
|
||||||
|
|
||||||
# @_matcher.assign("repayment")
|
|
||||||
# async def _(session: Uninfo, arparma: Arparma, amount: Match[int]):
|
|
||||||
# amount_num = amount.result if amount.available else await get_amount("还款")
|
|
||||||
# if amount_num <= 0:
|
|
||||||
# await MessageUtils.build_message("还款数量必须大于 0 啊笨蛋!").finish()
|
|
||||||
# user = await BankManager.repayment(session.user.id, amount_num)
|
|
||||||
# result = (f"还款成功!\n当前还款金额为: {amount_num}\n"
|
|
||||||
# f"当前贷款金额为: {user.loan_amount}")
|
|
||||||
# logger.info(
|
|
||||||
# f"小真寻银行还款:{amount_num},当前贷款数:{user.amount}, 贷款利率:{user.rate}",
|
|
||||||
# arparma.header_result,
|
|
||||||
# session=session,
|
|
||||||
# )
|
|
||||||
# await MessageUtils.build_message(result).finish(at_sender=True)
|
|
||||||
|
|
||||||
|
|
||||||
@scheduler.scheduled_job(
|
|
||||||
"cron",
|
|
||||||
hour=0,
|
|
||||||
minute=0,
|
|
||||||
)
|
|
||||||
async def _():
|
|
||||||
await BankManager.settlement()
|
|
||||||
logger.info("小真寻银行结算", "定时任务")
|
|
||||||
@@ -1,450 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import random
|
|
||||||
|
|
||||||
from nonebot_plugin_htmlrender import template_to_pic
|
|
||||||
from nonebot_plugin_uninfo import Uninfo
|
|
||||||
from tortoise.expressions import RawSQL
|
|
||||||
from tortoise.functions import Count, Sum
|
|
||||||
|
|
||||||
from zhenxun.configs.config import Config
|
|
||||||
from zhenxun.configs.path_config import TEMPLATE_PATH
|
|
||||||
from zhenxun.models.mahiro_bank import MahiroBank
|
|
||||||
from zhenxun.models.mahiro_bank_log import MahiroBankLog
|
|
||||||
from zhenxun.models.sign_user import SignUser
|
|
||||||
from zhenxun.models.user_console import UserConsole
|
|
||||||
from zhenxun.utils.enum import BankHandleType, GoldHandle
|
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
|
||||||
|
|
||||||
base_config = Config.get("mahiro_bank")
|
|
||||||
|
|
||||||
|
|
||||||
class BankManager:
|
|
||||||
@classmethod
|
|
||||||
async def random_event(cls, impression: float):
|
|
||||||
"""随机事件"""
|
|
||||||
impression_event = base_config.get("impression_event")
|
|
||||||
impression_event_prop = base_config.get("impression_event_prop")
|
|
||||||
impression_event_range = base_config.get("impression_event_range")
|
|
||||||
if impression >= impression_event and random.random() < impression_event_prop:
|
|
||||||
"""触发好感度事件"""
|
|
||||||
return random.uniform(impression_event_range[0], impression_event_range[1])
|
|
||||||
return None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def deposit_check(cls, user_id: str, amount: int) -> str | None:
|
|
||||||
"""检查存款是否合法
|
|
||||||
|
|
||||||
参数:
|
|
||||||
user_id: 用户id
|
|
||||||
amount: 存款金额
|
|
||||||
|
|
||||||
返回:
|
|
||||||
str | None: 存款信息
|
|
||||||
"""
|
|
||||||
if amount <= 0:
|
|
||||||
return "存款数量必须大于 0 啊笨蛋!"
|
|
||||||
user, sign_user, bank_user = await asyncio.gather(
|
|
||||||
*[
|
|
||||||
UserConsole.get_user(user_id),
|
|
||||||
SignUser.get_user(user_id),
|
|
||||||
cls.get_user(user_id),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
sign_max_deposit: int = base_config.get("sign_max_deposit")
|
|
||||||
max_deposit = max(int(float(sign_user.impression) * sign_max_deposit), 100)
|
|
||||||
if user.gold < amount:
|
|
||||||
return f"金币数量不足,当前你的金币为:{user.gold}."
|
|
||||||
if bank_user.amount + amount > max_deposit:
|
|
||||||
return (
|
|
||||||
f"存款超过上限,存款上限为:{max_deposit},"
|
|
||||||
f"当前你的还可以存款金额:{max_deposit - bank_user.amount}。"
|
|
||||||
)
|
|
||||||
max_daily_deposit_count: int = base_config.get("max_daily_deposit_count")
|
|
||||||
today_deposit_count = len(await cls.get_user_deposit(user_id))
|
|
||||||
if today_deposit_count >= max_daily_deposit_count:
|
|
||||||
return f"存款次数超过上限,每日存款次数上限为:{max_daily_deposit_count}。"
|
|
||||||
return None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def withdraw_check(cls, user_id: str, amount: int) -> str | None:
|
|
||||||
"""检查取款是否合法
|
|
||||||
|
|
||||||
参数:
|
|
||||||
user_id: 用户id
|
|
||||||
amount: 取款金额
|
|
||||||
|
|
||||||
返回:
|
|
||||||
str | None: 取款信息
|
|
||||||
"""
|
|
||||||
if amount <= 0:
|
|
||||||
return "取款数量必须大于 0 啊笨蛋!"
|
|
||||||
user = await cls.get_user(user_id)
|
|
||||||
data_list = await cls.get_user_deposit(user_id)
|
|
||||||
lock_amount = sum(data.amount for data in data_list)
|
|
||||||
if user.amount - lock_amount < amount:
|
|
||||||
return (
|
|
||||||
"取款金额不足,当前你的存款为:"
|
|
||||||
f"{user.amount}({lock_amount}已被锁定)!"
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def get_user_deposit(
|
|
||||||
cls, user_id: str, is_completed: bool = False
|
|
||||||
) -> list[MahiroBankLog]:
|
|
||||||
"""获取用户今日存款次数
|
|
||||||
|
|
||||||
参数:
|
|
||||||
user_id: 用户id
|
|
||||||
|
|
||||||
返回:
|
|
||||||
list[MahiroBankLog]: 存款列表
|
|
||||||
"""
|
|
||||||
return await MahiroBankLog.filter(
|
|
||||||
user_id=user_id,
|
|
||||||
handle_type=BankHandleType.DEPOSIT,
|
|
||||||
is_completed=is_completed,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def get_user(cls, user_id: str) -> MahiroBank:
|
|
||||||
"""查询余额
|
|
||||||
|
|
||||||
参数:
|
|
||||||
user_id: 用户id
|
|
||||||
|
|
||||||
返回:
|
|
||||||
MahiroBank
|
|
||||||
"""
|
|
||||||
user, _ = await MahiroBank.get_or_create(user_id=user_id)
|
|
||||||
return user
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def get_user_data(
|
|
||||||
cls,
|
|
||||||
user_id: str,
|
|
||||||
data_type: BankHandleType,
|
|
||||||
is_completed: bool = False,
|
|
||||||
count: int = 5,
|
|
||||||
) -> list[MahiroBankLog]:
|
|
||||||
return (
|
|
||||||
await MahiroBankLog.filter(
|
|
||||||
user_id=user_id, handle_type=data_type, is_completed=is_completed
|
|
||||||
)
|
|
||||||
.order_by("-id")
|
|
||||||
.limit(count)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def complete_projected_revenue(cls, user_id: str) -> int:
|
|
||||||
"""预计收益
|
|
||||||
|
|
||||||
参数:
|
|
||||||
user_id: 用户id
|
|
||||||
|
|
||||||
返回:
|
|
||||||
int: 预计收益金额
|
|
||||||
"""
|
|
||||||
deposit_list = await cls.get_user_deposit(user_id)
|
|
||||||
if not deposit_list:
|
|
||||||
return 0
|
|
||||||
return int(
|
|
||||||
sum(
|
|
||||||
deposit.rate * deposit.amount * deposit.effective_hour
|
|
||||||
for deposit in deposit_list
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def get_user_info(cls, session: Uninfo, uname: str) -> bytes:
|
|
||||||
"""获取用户数据
|
|
||||||
|
|
||||||
参数:
|
|
||||||
session: Uninfo
|
|
||||||
uname: 用户id
|
|
||||||
|
|
||||||
返回:
|
|
||||||
bytes: 图片数据
|
|
||||||
"""
|
|
||||||
user_id = session.user.id
|
|
||||||
user = await cls.get_user(user_id=user_id)
|
|
||||||
(
|
|
||||||
rank,
|
|
||||||
deposit_count,
|
|
||||||
user_today_deposit,
|
|
||||||
projected_revenue,
|
|
||||||
sum_data,
|
|
||||||
) = await asyncio.gather(
|
|
||||||
*[
|
|
||||||
MahiroBank.filter(amount__gt=user.amount).count(),
|
|
||||||
MahiroBankLog.filter(user_id=user_id).count(),
|
|
||||||
cls.get_user_deposit(user_id),
|
|
||||||
cls.complete_projected_revenue(user_id),
|
|
||||||
MahiroBankLog.filter(
|
|
||||||
user_id=user_id, handle_type=BankHandleType.INTEREST
|
|
||||||
)
|
|
||||||
.annotate(sum=Sum("amount"))
|
|
||||||
.values("sum"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
now = datetime.now()
|
|
||||||
end_time = (
|
|
||||||
now
|
|
||||||
+ timedelta(days=1)
|
|
||||||
- timedelta(hours=now.hour, minutes=now.minute, seconds=now.second)
|
|
||||||
)
|
|
||||||
today_deposit_amount = sum(deposit.amount for deposit in user_today_deposit)
|
|
||||||
deposit_list = [
|
|
||||||
{
|
|
||||||
"id": deposit.id,
|
|
||||||
"date": now.date(),
|
|
||||||
"start_time": str(deposit.create_time).split(".")[0],
|
|
||||||
"end_time": end_time.replace(microsecond=0),
|
|
||||||
"amount": deposit.amount,
|
|
||||||
"rate": f"{deposit.rate * 100:.2f}",
|
|
||||||
"projected_revenue": int(
|
|
||||||
deposit.amount * deposit.rate * deposit.effective_hour
|
|
||||||
)
|
|
||||||
or 1,
|
|
||||||
}
|
|
||||||
for deposit in user_today_deposit
|
|
||||||
]
|
|
||||||
platform = PlatformUtils.get_platform(session)
|
|
||||||
data = {
|
|
||||||
"name": uname,
|
|
||||||
"rank": rank + 1,
|
|
||||||
"avatar_url": PlatformUtils.get_user_avatar_url(
|
|
||||||
user_id, platform, session.self_id
|
|
||||||
),
|
|
||||||
"amount": user.amount,
|
|
||||||
"deposit_count": deposit_count,
|
|
||||||
"today_deposit_count": len(user_today_deposit),
|
|
||||||
"cumulative_gain": sum_data[0]["sum"] or 0,
|
|
||||||
"projected_revenue": projected_revenue,
|
|
||||||
"today_deposit_amount": today_deposit_amount,
|
|
||||||
"deposit_list": deposit_list,
|
|
||||||
"create_time": now.replace(microsecond=0),
|
|
||||||
}
|
|
||||||
return await template_to_pic(
|
|
||||||
template_path=str((TEMPLATE_PATH / "mahiro_bank").absolute()),
|
|
||||||
template_name="user.html",
|
|
||||||
templates={"data": data},
|
|
||||||
pages={
|
|
||||||
"viewport": {"width": 386, "height": 700},
|
|
||||||
"base_url": f"file://{TEMPLATE_PATH}",
|
|
||||||
},
|
|
||||||
wait=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def get_bank_info(cls) -> bytes:
|
|
||||||
now = datetime.now()
|
|
||||||
now_start = now - timedelta(
|
|
||||||
hours=now.hour, minutes=now.minute, seconds=now.second
|
|
||||||
)
|
|
||||||
(
|
|
||||||
bank_data,
|
|
||||||
today_count,
|
|
||||||
interest_amount,
|
|
||||||
active_user_count,
|
|
||||||
date_data,
|
|
||||||
) = await asyncio.gather(
|
|
||||||
*[
|
|
||||||
MahiroBank.annotate(
|
|
||||||
amount_sum=Sum("amount"), user_count=Count("id")
|
|
||||||
).values("amount_sum", "user_count"),
|
|
||||||
MahiroBankLog.filter(
|
|
||||||
create_time__gt=now_start, handle_type=BankHandleType.DEPOSIT
|
|
||||||
).count(),
|
|
||||||
MahiroBankLog.filter(handle_type=BankHandleType.INTEREST)
|
|
||||||
.annotate(amount_sum=Sum("amount"))
|
|
||||||
.values("amount_sum"),
|
|
||||||
MahiroBankLog.filter(
|
|
||||||
create_time__gte=now_start - timedelta(days=7),
|
|
||||||
handle_type=BankHandleType.DEPOSIT,
|
|
||||||
)
|
|
||||||
.annotate(count=Count("user_id", distinct=True))
|
|
||||||
.values("count"),
|
|
||||||
MahiroBankLog.filter(
|
|
||||||
create_time__gte=now_start - timedelta(days=7),
|
|
||||||
handle_type=BankHandleType.DEPOSIT,
|
|
||||||
)
|
|
||||||
.annotate(date=RawSQL("DATE(create_time)"), total_amount=Sum("amount"))
|
|
||||||
.group_by("date")
|
|
||||||
.values("date", "total_amount"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
date2cnt = {str(date["date"]): date["total_amount"] for date in date_data}
|
|
||||||
date = now.date()
|
|
||||||
e_date, e_amount = [], []
|
|
||||||
for _ in range(7):
|
|
||||||
if str(date) in date2cnt:
|
|
||||||
e_amount.append(date2cnt[str(date)])
|
|
||||||
else:
|
|
||||||
e_amount.append(0)
|
|
||||||
e_date.append(str(date)[5:])
|
|
||||||
date -= timedelta(days=1)
|
|
||||||
e_date.reverse()
|
|
||||||
e_amount.reverse()
|
|
||||||
date = 1
|
|
||||||
lasted_log = await MahiroBankLog.annotate().order_by("create_time").first()
|
|
||||||
if lasted_log:
|
|
||||||
date = now.date() - lasted_log.create_time.date()
|
|
||||||
date = (date.days or 1) + 1
|
|
||||||
data = {
|
|
||||||
"amount_sum": bank_data[0]["amount_sum"],
|
|
||||||
"user_count": bank_data[0]["user_count"],
|
|
||||||
"today_count": today_count,
|
|
||||||
"day_amount": int(bank_data[0]["amount_sum"] / date),
|
|
||||||
"interest_amount": interest_amount[0]["amount_sum"] or 0,
|
|
||||||
"active_user_count": active_user_count[0]["count"] or 0,
|
|
||||||
"e_data": e_date,
|
|
||||||
"e_amount": e_amount,
|
|
||||||
"create_time": now.replace(microsecond=0),
|
|
||||||
}
|
|
||||||
return await template_to_pic(
|
|
||||||
template_path=str((TEMPLATE_PATH / "mahiro_bank").absolute()),
|
|
||||||
template_name="bank.html",
|
|
||||||
templates={"data": data},
|
|
||||||
pages={
|
|
||||||
"viewport": {"width": 450, "height": 750},
|
|
||||||
"base_url": f"file://{TEMPLATE_PATH}",
|
|
||||||
},
|
|
||||||
wait=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def deposit(
|
|
||||||
cls, user_id: str, amount: int
|
|
||||||
) -> tuple[MahiroBank, float, float | None]:
|
|
||||||
"""存款
|
|
||||||
|
|
||||||
参数:
|
|
||||||
user_id: 用户id
|
|
||||||
amount: 存款数量
|
|
||||||
|
|
||||||
返回:
|
|
||||||
tuple[MahiroBank, float, float]: MahiroBank,利率,增加的利率
|
|
||||||
"""
|
|
||||||
rate_range = base_config.get("rate_range")
|
|
||||||
rate = random.uniform(rate_range[0], rate_range[1])
|
|
||||||
sign_user = await SignUser.get_user(user_id)
|
|
||||||
random_add_rate = await cls.random_event(float(sign_user.impression))
|
|
||||||
if random_add_rate:
|
|
||||||
rate += random_add_rate
|
|
||||||
await UserConsole.reduce_gold(user_id, amount, GoldHandle.PLUGIN, "bank")
|
|
||||||
return await MahiroBank.deposit(user_id, amount, rate), rate, random_add_rate
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def withdraw(cls, user_id: str, amount: int) -> MahiroBank:
|
|
||||||
"""取款
|
|
||||||
|
|
||||||
参数:
|
|
||||||
user_id: 用户id
|
|
||||||
amount: 取款数量
|
|
||||||
|
|
||||||
返回:
|
|
||||||
MahiroBank
|
|
||||||
"""
|
|
||||||
await UserConsole.add_gold(user_id, amount, "bank")
|
|
||||||
return await MahiroBank.withdraw(user_id, amount)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def loan(cls, user_id: str, amount: int) -> tuple[MahiroBank, float | None]:
|
|
||||||
"""贷款
|
|
||||||
|
|
||||||
参数:
|
|
||||||
user_id: 用户id
|
|
||||||
amount: 贷款数量
|
|
||||||
|
|
||||||
返回:
|
|
||||||
tuple[MahiroBank, float]: MahiroBank,贷款利率
|
|
||||||
"""
|
|
||||||
rate_range = base_config.get("rate_range")
|
|
||||||
rate = random.uniform(rate_range[0], rate_range[1])
|
|
||||||
sign_user = await SignUser.get_user(user_id)
|
|
||||||
user, _ = await MahiroBank.get_or_create(user_id=user_id)
|
|
||||||
if user.loan_amount + amount > sign_user.impression * 150:
|
|
||||||
raise ValueError("贷款数量超过最大限制,请签到提升好感度获取更多额度吧...")
|
|
||||||
random_reduce_rate = await cls.random_event(float(sign_user.impression))
|
|
||||||
if random_reduce_rate:
|
|
||||||
rate -= random_reduce_rate
|
|
||||||
await UserConsole.add_gold(user_id, amount, "bank")
|
|
||||||
return await MahiroBank.loan(user_id, amount, rate), random_reduce_rate
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def repayment(cls, user_id: str, amount: int) -> MahiroBank:
|
|
||||||
"""还款
|
|
||||||
|
|
||||||
参数:
|
|
||||||
user_id: 用户id
|
|
||||||
amount: 还款数量
|
|
||||||
|
|
||||||
返回:
|
|
||||||
MahiroBank
|
|
||||||
"""
|
|
||||||
await UserConsole.reduce_gold(user_id, amount, GoldHandle.PLUGIN, "bank")
|
|
||||||
return await MahiroBank.repayment(user_id, amount)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def settlement(cls):
|
|
||||||
"""结算每日利率"""
|
|
||||||
bank_user_list = await MahiroBank.filter(amount__gt=0).all()
|
|
||||||
log_list = await MahiroBankLog.filter(
|
|
||||||
is_completed=False, handle_type=BankHandleType.DEPOSIT
|
|
||||||
).all()
|
|
||||||
user_list = await UserConsole.filter(
|
|
||||||
user_id__in=[user.user_id for user in bank_user_list]
|
|
||||||
).all()
|
|
||||||
user_data = {user.user_id: user for user in user_list}
|
|
||||||
bank_data: dict[str, list[MahiroBankLog]] = {}
|
|
||||||
for log in log_list:
|
|
||||||
if log.user_id not in bank_data:
|
|
||||||
bank_data[log.user_id] = []
|
|
||||||
bank_data[log.user_id].append(log)
|
|
||||||
log_create_list = []
|
|
||||||
log_update_list = []
|
|
||||||
# 计算每日默认金币
|
|
||||||
for bank_user in bank_user_list:
|
|
||||||
if user := user_data.get(bank_user.user_id):
|
|
||||||
amount = bank_user.amount
|
|
||||||
if logs := bank_data.get(bank_user.user_id):
|
|
||||||
amount -= sum(log.amount for log in logs)
|
|
||||||
if not amount:
|
|
||||||
continue
|
|
||||||
# 计算每日默认金币
|
|
||||||
gold = int(amount * bank_user.rate)
|
|
||||||
user.gold += gold
|
|
||||||
log_create_list.append(
|
|
||||||
MahiroBankLog(
|
|
||||||
user_id=bank_user.user_id,
|
|
||||||
amount=gold,
|
|
||||||
rate=bank_user.rate,
|
|
||||||
handle_type=BankHandleType.INTEREST,
|
|
||||||
is_completed=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# 计算每日存款金币
|
|
||||||
for user_id, logs in bank_data.items():
|
|
||||||
if user := user_data.get(user_id):
|
|
||||||
for log in logs:
|
|
||||||
gold = int(log.amount * log.rate * log.effective_hour) or 1
|
|
||||||
user.gold += gold
|
|
||||||
log.is_completed = True
|
|
||||||
log_update_list.append(log)
|
|
||||||
log_create_list.append(
|
|
||||||
MahiroBankLog(
|
|
||||||
user_id=user_id,
|
|
||||||
amount=gold,
|
|
||||||
rate=log.rate,
|
|
||||||
handle_type=BankHandleType.INTEREST,
|
|
||||||
is_completed=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if log_create_list:
|
|
||||||
await MahiroBankLog.bulk_create(log_create_list, 10)
|
|
||||||
if log_update_list:
|
|
||||||
await MahiroBankLog.bulk_update(log_update_list, ["is_completed"], 10)
|
|
||||||
await UserConsole.bulk_update(user_list, ["gold"], 10)
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from nonebot import on_notice
|
from nonebot import on_notice, on_request
|
||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
from nonebot.adapters.onebot.v11 import (
|
from nonebot.adapters.onebot.v11 import (
|
||||||
GroupDecreaseNoticeEvent,
|
GroupDecreaseNoticeEvent,
|
||||||
@@ -14,10 +14,9 @@ from nonebot_plugin_uninfo import Uninfo
|
|||||||
from zhenxun.builtin_plugins.platform.qq.exception import ForceAddGroupError
|
from zhenxun.builtin_plugins.platform.qq.exception import ForceAddGroupError
|
||||||
from zhenxun.configs.config import BotConfig, Config
|
from zhenxun.configs.config import BotConfig, Config
|
||||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task
|
from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task
|
||||||
from zhenxun.models.event_log import EventLog
|
|
||||||
from zhenxun.models.group_console import GroupConsole
|
from zhenxun.models.group_console import GroupConsole
|
||||||
from zhenxun.utils.common_utils import CommonUtils
|
from zhenxun.utils.common_utils import CommonUtils
|
||||||
from zhenxun.utils.enum import EventLogType, PluginType
|
from zhenxun.utils.enum import PluginType
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
from zhenxun.utils.platform import PlatformUtils
|
||||||
from zhenxun.utils.rules import notice_rule
|
from zhenxun.utils.rules import notice_rule
|
||||||
|
|
||||||
@@ -107,6 +106,8 @@ group_decrease_handle = on_notice(
|
|||||||
rule=notice_rule([GroupMemberDecreaseEvent, GroupDecreaseNoticeEvent]),
|
rule=notice_rule([GroupMemberDecreaseEvent, GroupDecreaseNoticeEvent]),
|
||||||
)
|
)
|
||||||
"""群员减少处理"""
|
"""群员减少处理"""
|
||||||
|
add_group = on_request(priority=1, block=False)
|
||||||
|
"""加群同意请求"""
|
||||||
|
|
||||||
|
|
||||||
@group_increase_handle.handle()
|
@group_increase_handle.handle()
|
||||||
@@ -141,20 +142,7 @@ async def _(
|
|||||||
if event.sub_type == "kick_me":
|
if event.sub_type == "kick_me":
|
||||||
"""踢出Bot"""
|
"""踢出Bot"""
|
||||||
await GroupManager.kick_bot(bot, group_id, str(event.operator_id))
|
await GroupManager.kick_bot(bot, group_id, str(event.operator_id))
|
||||||
await EventLog.create(
|
|
||||||
user_id=user_id, group_id=group_id, event_type=EventLogType.KICK_BOT
|
|
||||||
)
|
|
||||||
elif event.sub_type in ["leave", "kick"]:
|
elif event.sub_type in ["leave", "kick"]:
|
||||||
if event.sub_type == "leave":
|
|
||||||
"""主动退群"""
|
|
||||||
await EventLog.create(
|
|
||||||
user_id=user_id, group_id=group_id, event_type=EventLogType.LEAVE_MEMBER
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
"""被踢出群"""
|
|
||||||
await EventLog.create(
|
|
||||||
user_id=user_id, group_id=group_id, event_type=EventLogType.KICK_MEMBER
|
|
||||||
)
|
|
||||||
result = await GroupManager.run_user(
|
result = await GroupManager.run_user(
|
||||||
bot, user_id, group_id, str(event.operator_id), event.sub_type
|
bot, user_id, group_id, str(event.operator_id), event.sub_type
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
from datetime import datetime
|
|
||||||
import random
|
|
||||||
|
|
||||||
from nonebot.adapters import Bot
|
|
||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
from nonebot.rule import to_me
|
|
||||||
from nonebot_plugin_alconna import Alconna, Args, Arparma, Field, on_alconna
|
|
||||||
from nonebot_plugin_uninfo import Uninfo
|
|
||||||
|
|
||||||
from zhenxun.configs.utils import PluginCdBlock, PluginExtraData
|
|
||||||
from zhenxun.models.fg_request import FgRequest
|
|
||||||
from zhenxun.services.log import logger
|
|
||||||
from zhenxun.utils.depends import UserName
|
|
||||||
from zhenxun.utils.enum import RequestHandleType, RequestType
|
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="群组申请",
|
|
||||||
description="""
|
|
||||||
一些小群直接邀请入群导致无法正常生成审核请求,需要用该方法手动生成审核请求。
|
|
||||||
当管理员同意同意时会发送消息进行提示,之后再进行拉群不会退出。
|
|
||||||
该消息会发送至管理员,多次发送不存在的群组id或相同群组id可能导致ban。
|
|
||||||
""".strip(),
|
|
||||||
usage="""
|
|
||||||
指令:
|
|
||||||
申请入群 [群号]
|
|
||||||
示例: 申请入群 123123123
|
|
||||||
""".strip(),
|
|
||||||
extra=PluginExtraData(
|
|
||||||
author="HibiKier",
|
|
||||||
version="0.1",
|
|
||||||
menu_type="其他",
|
|
||||||
limits=[PluginCdBlock(cd=300, result="每5分钟只能申请一次哦~")],
|
|
||||||
).to_dict(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
_matcher = on_alconna(
|
|
||||||
Alconna(
|
|
||||||
"申请入群",
|
|
||||||
Args[
|
|
||||||
"group_id",
|
|
||||||
int,
|
|
||||||
Field(
|
|
||||||
missing_tips=lambda: "请在命令后跟随群组id!",
|
|
||||||
unmatch_tips=lambda _: "群组id必须为数字!",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
skip_for_unmatch=False,
|
|
||||||
priority=5,
|
|
||||||
block=True,
|
|
||||||
rule=to_me(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@_matcher.handle()
|
|
||||||
async def _(
|
|
||||||
bot: Bot, session: Uninfo, arparma: Arparma, group_id: int, uname: str = UserName()
|
|
||||||
):
|
|
||||||
# 旧请求全部设置为过期
|
|
||||||
await FgRequest.filter(
|
|
||||||
request_type=RequestType.GROUP,
|
|
||||||
user_id=session.user.id,
|
|
||||||
group_id=str(group_id),
|
|
||||||
handle_type__isnull=True,
|
|
||||||
).update(handle_type=RequestHandleType.EXPIRE)
|
|
||||||
f = await FgRequest.create(
|
|
||||||
request_type=RequestType.GROUP,
|
|
||||||
platform=PlatformUtils.get_platform(session),
|
|
||||||
bot_id=bot.self_id,
|
|
||||||
flag="0",
|
|
||||||
user_id=session.user.id,
|
|
||||||
nickname=uname,
|
|
||||||
group_id=str(group_id),
|
|
||||||
)
|
|
||||||
results = await PlatformUtils.send_superuser(
|
|
||||||
bot,
|
|
||||||
f"*****一份入群申请*****\n"
|
|
||||||
f"ID:{f.id}\n"
|
|
||||||
f"申请人:{uname}({session.user.id})\n群聊:"
|
|
||||||
f"{group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}\n"
|
|
||||||
"注:该请求为手动申请入群",
|
|
||||||
)
|
|
||||||
if message_ids := [
|
|
||||||
str(r[1].msg_ids[0]["message_id"]) for r in results if r[1] and r[1].msg_ids
|
|
||||||
]:
|
|
||||||
f.message_ids = ",".join(message_ids)
|
|
||||||
await f.save(update_fields=["message_ids"])
|
|
||||||
await asyncio.sleep(random.randint(1, 5))
|
|
||||||
await bot.send_private_msg(
|
|
||||||
user_id=int(session.user.id),
|
|
||||||
message=f"已发送申请,请等待管理员审核,ID:{f.id}。",
|
|
||||||
)
|
|
||||||
logger.info(
|
|
||||||
f"用户 {uname}({session.user.id}) 申请入群 {group_id},ID:{f.id}。",
|
|
||||||
arparma.header_result,
|
|
||||||
session=session,
|
|
||||||
)
|
|
||||||
@@ -18,12 +18,6 @@ from zhenxun.utils.utils import is_number
|
|||||||
|
|
||||||
from .config import BASE_PATH, DEFAULT_GITHUB_URL, EXTRA_GITHUB_URL
|
from .config import BASE_PATH, DEFAULT_GITHUB_URL, EXTRA_GITHUB_URL
|
||||||
|
|
||||||
BAT_FILE = Path() / "win启动.bat"
|
|
||||||
|
|
||||||
WIN_COMMAND = ["./Python310/python.exe", "-m", "pip", "install", "-r"]
|
|
||||||
|
|
||||||
DEFAULT_COMMAND = ["poetry", "run", "pip", "install", "-r"]
|
|
||||||
|
|
||||||
|
|
||||||
def row_style(column: str, text: str) -> RowStyle:
|
def row_style(column: str, text: str) -> RowStyle:
|
||||||
"""被动技能文本风格
|
"""被动技能文本风格
|
||||||
@@ -56,10 +50,8 @@ def install_requirement(plugin_path: Path):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
command = WIN_COMMAND if BAT_FILE.exists() else DEFAULT_COMMAND
|
|
||||||
command.append(str(existing_requirements))
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
command,
|
["poetry", "run", "pip", "install", "-r", str(existing_requirements)],
|
||||||
check=True,
|
check=True,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
@@ -99,8 +91,8 @@ class ShopManage:
|
|||||||
"plugins.json"
|
"plugins.json"
|
||||||
)
|
)
|
||||||
extra_github_url = await extra_github_repo.get_raw_download_urls("plugins.json")
|
extra_github_url = await extra_github_repo.get_raw_download_urls("plugins.json")
|
||||||
res = await AsyncHttpx.get(default_github_url, check_status_code=200)
|
res = await AsyncHttpx.get(default_github_url)
|
||||||
res2 = await AsyncHttpx.get(extra_github_url, check_status_code=200)
|
res2 = await AsyncHttpx.get(extra_github_url)
|
||||||
|
|
||||||
# 检查请求结果
|
# 检查请求结果
|
||||||
if res.status_code != 200 or res2.status_code != 200:
|
if res.status_code != 200 or res2.status_code != 200:
|
||||||
|
|||||||
@@ -17,12 +17,11 @@ from nonebot_plugin_session import EventSession
|
|||||||
|
|
||||||
from zhenxun.configs.config import BotConfig, Config
|
from zhenxun.configs.config import BotConfig, Config
|
||||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||||
from zhenxun.models.event_log import EventLog
|
|
||||||
from zhenxun.models.fg_request import FgRequest
|
from zhenxun.models.fg_request import FgRequest
|
||||||
from zhenxun.models.friend_user import FriendUser
|
from zhenxun.models.friend_user import FriendUser
|
||||||
from zhenxun.models.group_console import GroupConsole
|
from zhenxun.models.group_console import GroupConsole
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
from zhenxun.utils.enum import EventLogType, PluginType, RequestHandleType, RequestType
|
from zhenxun.utils.enum import PluginType, RequestHandleType, RequestType
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
from zhenxun.utils.platform import PlatformUtils
|
||||||
|
|
||||||
base_config = Config.get("invite_manager")
|
base_config = Config.get("invite_manager")
|
||||||
@@ -113,29 +112,21 @@ async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSessi
|
|||||||
nickname=nickname,
|
nickname=nickname,
|
||||||
comment=comment,
|
comment=comment,
|
||||||
)
|
)
|
||||||
results = await PlatformUtils.send_superuser(
|
await PlatformUtils.send_superuser(
|
||||||
bot,
|
bot,
|
||||||
f"*****一份好友申请*****\n"
|
f"*****一份好友申请*****\n"
|
||||||
f"ID: {f.id}\n"
|
f"ID: {f.id}\n"
|
||||||
f"昵称:{nickname}({event.user_id})\n"
|
f"昵称:{nickname}({event.user_id})\n"
|
||||||
f"自动同意:{'√' if base_config.get('AUTO_ADD_FRIEND') else '×'}\n"
|
f"自动同意:{'√' if base_config.get('AUTO_ADD_FRIEND') else '×'}\n"
|
||||||
f"日期:{datetime.now().replace(microsecond=0)}\n"
|
f"日期:{str(datetime.now()).split('.')[0]}\n"
|
||||||
f"备注:{event.comment}",
|
f"备注:{event.comment}",
|
||||||
)
|
)
|
||||||
if message_ids := [
|
|
||||||
str(r[1].msg_ids[0]["message_id"])
|
|
||||||
for r in results
|
|
||||||
if r[1] and r[1].msg_ids
|
|
||||||
]:
|
|
||||||
f.message_ids = ",".join(message_ids)
|
|
||||||
await f.save(update_fields=["message_ids"])
|
|
||||||
else:
|
else:
|
||||||
logger.debug("好友请求五分钟内重复, 已忽略", "好友请求", target=event.user_id)
|
logger.debug("好友请求五分钟内重复, 已忽略", "好友请求", target=event.user_id)
|
||||||
|
|
||||||
|
|
||||||
@group_req.handle()
|
@group_req.handle()
|
||||||
async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSession):
|
async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSession):
|
||||||
# sourcery skip: low-code-quality
|
|
||||||
if event.sub_type != "invite":
|
if event.sub_type != "invite":
|
||||||
return
|
return
|
||||||
if str(event.user_id) in bot.config.superusers or base_config.get("AUTO_ADD_GROUP"):
|
if str(event.user_id) in bot.config.superusers or base_config.get("AUTO_ADD_GROUP"):
|
||||||
@@ -195,7 +186,7 @@ async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSessio
|
|||||||
group_id=str(event.group_id),
|
group_id=str(event.group_id),
|
||||||
handle_type=RequestHandleType.APPROVE,
|
handle_type=RequestHandleType.APPROVE,
|
||||||
)
|
)
|
||||||
results = await PlatformUtils.send_superuser(
|
await PlatformUtils.send_superuser(
|
||||||
bot,
|
bot,
|
||||||
f"*****一份入群申请*****\n"
|
f"*****一份入群申请*****\n"
|
||||||
f"ID:{f.id}\n"
|
f"ID:{f.id}\n"
|
||||||
@@ -239,27 +230,13 @@ async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSessio
|
|||||||
nickname=nickname,
|
nickname=nickname,
|
||||||
group_id=str(event.group_id),
|
group_id=str(event.group_id),
|
||||||
)
|
)
|
||||||
kick_count = await EventLog.filter(
|
await PlatformUtils.send_superuser(
|
||||||
group_id=str(event.group_id), event_type=EventLogType.KICK_BOT
|
|
||||||
).count()
|
|
||||||
kick_message = (
|
|
||||||
f"\n该群累计踢出{BotConfig.self_nickname} <{kick_count}>次"
|
|
||||||
if kick_count
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
results = await PlatformUtils.send_superuser(
|
|
||||||
bot,
|
bot,
|
||||||
f"*****一份入群申请*****\n"
|
f"*****一份入群申请*****\n"
|
||||||
f"ID:{f.id}\n"
|
f"ID:{f.id}\n"
|
||||||
f"申请人:{nickname}({event.user_id})\n群聊:"
|
f"申请人:{nickname}({event.user_id})\n群聊:"
|
||||||
f"{event.group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}"
|
f"{event.group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}",
|
||||||
f"{kick_message}",
|
|
||||||
)
|
)
|
||||||
if message_ids := [
|
|
||||||
str(r[1].msg_ids[0]["message_id"]) for r in results if r[1] and r[1].msg_ids
|
|
||||||
]:
|
|
||||||
f.message_ids = ",".join(message_ids)
|
|
||||||
await f.save(update_fields=["message_ids"])
|
|
||||||
else:
|
else:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"群聊请求五分钟内重复, 已忽略",
|
"群聊请求五分钟内重复, 已忽略",
|
||||||
|
|||||||
@@ -1,8 +1,67 @@
|
|||||||
|
from asyncio.exceptions import TimeoutError
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
|
import nonebot
|
||||||
|
from nonebot.drivers import Driver
|
||||||
|
from nonebot_plugin_apscheduler import scheduler
|
||||||
|
import ujson as json
|
||||||
|
|
||||||
|
from zhenxun.configs.path_config import TEXT_PATH
|
||||||
from zhenxun.models.group_console import GroupConsole
|
from zhenxun.models.group_console import GroupConsole
|
||||||
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
from zhenxun.services.log import logger
|
||||||
|
from zhenxun.utils.http_utils import AsyncHttpx
|
||||||
|
|
||||||
|
driver: Driver = nonebot.get_driver()
|
||||||
|
|
||||||
|
|
||||||
@PriorityLifecycle.on_startup(priority=5)
|
@driver.on_startup
|
||||||
|
async def update_city():
|
||||||
|
"""
|
||||||
|
部分插件需要中国省份城市
|
||||||
|
这里直接更新,避免插件内代码重复
|
||||||
|
"""
|
||||||
|
china_city = TEXT_PATH / "china_city.json"
|
||||||
|
if not china_city.exists():
|
||||||
|
data = {}
|
||||||
|
try:
|
||||||
|
logger.debug("开始更新城市列表...")
|
||||||
|
res = await AsyncHttpx.get(
|
||||||
|
"http://www.weather.com.cn/data/city3jdata/china.html", timeout=5
|
||||||
|
)
|
||||||
|
res.encoding = "utf8"
|
||||||
|
provinces_data = json.loads(res.text)
|
||||||
|
for province in provinces_data.keys():
|
||||||
|
data[provinces_data[province]] = []
|
||||||
|
res = await AsyncHttpx.get(
|
||||||
|
f"http://www.weather.com.cn/data/city3jdata/provshi/{province}.html",
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
res.encoding = "utf8"
|
||||||
|
city_data = json.loads(res.text)
|
||||||
|
for city in city_data.keys():
|
||||||
|
data[provinces_data[province]].append(city_data[city])
|
||||||
|
async with aiofiles.open(china_city, "w", encoding="utf8") as f:
|
||||||
|
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||||
|
logger.info("自动更新城市列表完成.....")
|
||||||
|
except TimeoutError as e:
|
||||||
|
logger.warning("自动更新城市列表超时...", e=e)
|
||||||
|
except ValueError as e:
|
||||||
|
logger.warning("自动城市列表失败.....", e=e)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("自动城市列表未知错误", e=e)
|
||||||
|
|
||||||
|
|
||||||
|
# 自动更新城市列表
|
||||||
|
@scheduler.scheduled_job(
|
||||||
|
"cron",
|
||||||
|
hour=6,
|
||||||
|
minute=1,
|
||||||
|
)
|
||||||
|
async def _():
|
||||||
|
await update_city()
|
||||||
|
|
||||||
|
|
||||||
|
@driver.on_startup
|
||||||
async def _():
|
async def _():
|
||||||
"""开启/禁用插件格式修改"""
|
"""开启/禁用插件格式修改"""
|
||||||
_, is_create = await GroupConsole.get_or_create(group_id=133133133)
|
_, is_create = await GroupConsole.get_or_create(group_id=133133133)
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ from nonebot_plugin_alconna import (
|
|||||||
AlconnaQuery,
|
AlconnaQuery,
|
||||||
Args,
|
Args,
|
||||||
Arparma,
|
Arparma,
|
||||||
At,
|
|
||||||
Match,
|
Match,
|
||||||
MultiVar,
|
|
||||||
Option,
|
Option,
|
||||||
Query,
|
Query,
|
||||||
Subcommand,
|
Subcommand,
|
||||||
@@ -35,7 +33,6 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
usage="""
|
usage="""
|
||||||
商品操作
|
商品操作
|
||||||
指令:
|
指令:
|
||||||
商店
|
|
||||||
我的金币
|
我的金币
|
||||||
我的道具
|
我的道具
|
||||||
使用道具 [名称/Id]
|
使用道具 [名称/Id]
|
||||||
@@ -49,7 +46,6 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
plugin_type=PluginType.NORMAL,
|
plugin_type=PluginType.NORMAL,
|
||||||
menu_type="商店",
|
menu_type="商店",
|
||||||
commands=[
|
commands=[
|
||||||
Command(command="商店"),
|
|
||||||
Command(command="我的金币"),
|
Command(command="我的金币"),
|
||||||
Command(command="我的道具"),
|
Command(command="我的道具"),
|
||||||
Command(command="购买道具"),
|
Command(command="购买道具"),
|
||||||
@@ -78,21 +74,13 @@ _matcher = on_alconna(
|
|||||||
Subcommand("my-cost", help_text="我的金币"),
|
Subcommand("my-cost", help_text="我的金币"),
|
||||||
Subcommand("my-props", help_text="我的道具"),
|
Subcommand("my-props", help_text="我的道具"),
|
||||||
Subcommand("buy", Args["name?", str]["num?", int], help_text="购买道具"),
|
Subcommand("buy", Args["name?", str]["num?", int], help_text="购买道具"),
|
||||||
|
Subcommand("use", Args["name?", str]["num?", int], help_text="使用道具"),
|
||||||
Subcommand("gold-list", Args["num?", int], help_text="金币排行"),
|
Subcommand("gold-list", Args["num?", int], help_text="金币排行"),
|
||||||
),
|
),
|
||||||
priority=5,
|
priority=5,
|
||||||
block=True,
|
block=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
_use_matcher = on_alconna(
|
|
||||||
Alconna(
|
|
||||||
"使用道具",
|
|
||||||
Args["name?", str]["num?", int]["at_users?", MultiVar(At)],
|
|
||||||
),
|
|
||||||
priority=5,
|
|
||||||
block=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
_matcher.shortcut(
|
_matcher.shortcut(
|
||||||
"我的金币",
|
"我的金币",
|
||||||
command="商店",
|
command="商店",
|
||||||
@@ -114,6 +102,13 @@ _matcher.shortcut(
|
|||||||
prefix=True,
|
prefix=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_matcher.shortcut(
|
||||||
|
"使用道具(?P<name>.*?)",
|
||||||
|
command="商店",
|
||||||
|
arguments=["use", "{name}"],
|
||||||
|
prefix=True,
|
||||||
|
)
|
||||||
|
|
||||||
_matcher.shortcut(
|
_matcher.shortcut(
|
||||||
"金币排行",
|
"金币排行",
|
||||||
command="商店",
|
command="商店",
|
||||||
@@ -177,7 +172,7 @@ async def _(
|
|||||||
await MessageUtils.build_message(result).send(reply_to=True)
|
await MessageUtils.build_message(result).send(reply_to=True)
|
||||||
|
|
||||||
|
|
||||||
@_use_matcher.handle()
|
@_matcher.assign("use")
|
||||||
async def _(
|
async def _(
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
event: Event,
|
event: Event,
|
||||||
@@ -186,7 +181,6 @@ async def _(
|
|||||||
arparma: Arparma,
|
arparma: Arparma,
|
||||||
name: Match[str],
|
name: Match[str],
|
||||||
num: Query[int] = AlconnaQuery("num", 1),
|
num: Query[int] = AlconnaQuery("num", 1),
|
||||||
at_users: Query[list[At]] = AlconnaQuery("at_users", []),
|
|
||||||
):
|
):
|
||||||
if not name.available:
|
if not name.available:
|
||||||
await MessageUtils.build_message(
|
await MessageUtils.build_message(
|
||||||
@@ -194,7 +188,7 @@ async def _(
|
|||||||
).finish(reply_to=True)
|
).finish(reply_to=True)
|
||||||
try:
|
try:
|
||||||
result = await ShopManage.use(
|
result = await ShopManage.use(
|
||||||
bot, event, session, message, name.result, num.result, "", at_users.result
|
bot, event, session, message, name.result, num.result, ""
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"使用道具 {name.result}, 数量: {num.result}",
|
f"使用道具 {name.result}, 数量: {num.result}",
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ from datetime import datetime, timedelta
|
|||||||
import inspect
|
import inspect
|
||||||
import time
|
import time
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
from typing import Any, Literal
|
from typing import Any, ClassVar, Literal
|
||||||
|
|
||||||
from nonebot.adapters import Bot, Event
|
from nonebot.adapters import Bot, Event
|
||||||
from nonebot.compat import model_dump
|
from nonebot.compat import model_dump
|
||||||
from nonebot_plugin_alconna import At, UniMessage, UniMsg
|
from nonebot_plugin_alconna import UniMessage, UniMsg
|
||||||
from nonebot_plugin_uninfo import Uninfo
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
from pydantic import BaseModel, Field, create_model
|
from pydantic import BaseModel, Field, create_model
|
||||||
from tortoise.expressions import Q
|
from tortoise.expressions import Q
|
||||||
@@ -48,10 +48,6 @@ class Goods(BaseModel):
|
|||||||
"""model"""
|
"""model"""
|
||||||
session: Uninfo | None = None
|
session: Uninfo | None = None
|
||||||
"""Uninfo"""
|
"""Uninfo"""
|
||||||
at_user: str | None = None
|
|
||||||
"""At对象"""
|
|
||||||
at_users: list[str] = []
|
|
||||||
"""At对象列表"""
|
|
||||||
|
|
||||||
|
|
||||||
class ShopParam(BaseModel):
|
class ShopParam(BaseModel):
|
||||||
@@ -69,19 +65,15 @@ class ShopParam(BaseModel):
|
|||||||
"""道具单次使用数量"""
|
"""道具单次使用数量"""
|
||||||
text: str
|
text: str
|
||||||
"""text"""
|
"""text"""
|
||||||
send_success_msg: bool = True
|
send_success_msg: ClassVar[bool] = True
|
||||||
"""是否发送使用成功信息"""
|
"""是否发送使用成功信息"""
|
||||||
max_num_limit: int = 1
|
max_num_limit: ClassVar[int] = 1
|
||||||
"""单次使用最大次数"""
|
"""单次使用最大次数"""
|
||||||
session: Uninfo | None = None
|
session: Uninfo | None = None
|
||||||
"""Uninfo"""
|
"""Uninfo"""
|
||||||
message: UniMsg
|
message: UniMsg
|
||||||
"""UniMessage"""
|
"""UniMessage"""
|
||||||
at_user: str | None = None
|
extra_data: ClassVar[dict[str, Any]] = {}
|
||||||
"""At对象"""
|
|
||||||
at_users: list[str] = []
|
|
||||||
"""At对象列表"""
|
|
||||||
extra_data: dict[str, Any] = Field(default_factory=dict)
|
|
||||||
"""额外数据"""
|
"""额外数据"""
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
@@ -164,7 +156,6 @@ class ShopManage:
|
|||||||
goods: Goods,
|
goods: Goods,
|
||||||
num: int,
|
num: int,
|
||||||
text: str,
|
text: str,
|
||||||
at_users: list[str] = [],
|
|
||||||
) -> tuple[ShopParam, dict[str, Any]]:
|
) -> tuple[ShopParam, dict[str, Any]]:
|
||||||
"""构造参数
|
"""构造参数
|
||||||
|
|
||||||
@@ -174,7 +165,6 @@ class ShopManage:
|
|||||||
goods_name: 商品名称
|
goods_name: 商品名称
|
||||||
num: 数量
|
num: 数量
|
||||||
text: 其他信息
|
text: 其他信息
|
||||||
at_users: at用户
|
|
||||||
"""
|
"""
|
||||||
group_id = None
|
group_id = None
|
||||||
if session.group:
|
if session.group:
|
||||||
@@ -182,7 +172,6 @@ class ShopManage:
|
|||||||
session.group.parent.id if session.group.parent else session.group.id
|
session.group.parent.id if session.group.parent else session.group.id
|
||||||
)
|
)
|
||||||
_kwargs = goods.params
|
_kwargs = goods.params
|
||||||
at_user = at_users[0] if at_users else None
|
|
||||||
model = goods.model(
|
model = goods.model(
|
||||||
**{
|
**{
|
||||||
"goods_name": goods.name,
|
"goods_name": goods.name,
|
||||||
@@ -194,8 +183,6 @@ class ShopManage:
|
|||||||
"text": text,
|
"text": text,
|
||||||
"session": session,
|
"session": session,
|
||||||
"message": message,
|
"message": message,
|
||||||
"at_user": at_user,
|
|
||||||
"at_users": at_users,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return model, {
|
return model, {
|
||||||
@@ -207,8 +194,6 @@ class ShopManage:
|
|||||||
"num": num,
|
"num": num,
|
||||||
"text": text,
|
"text": text,
|
||||||
"goods_name": goods.name,
|
"goods_name": goods.name,
|
||||||
"at_user": at_user,
|
|
||||||
"at_users": at_users,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -238,7 +223,6 @@ class ShopManage:
|
|||||||
**param.extra_data,
|
**param.extra_data,
|
||||||
"session": session,
|
"session": session,
|
||||||
"message": message,
|
"message": message,
|
||||||
"shop_param": ShopParam,
|
|
||||||
}
|
}
|
||||||
for key in list(param_json.keys()):
|
for key in list(param_json.keys()):
|
||||||
if key not in args:
|
if key not in args:
|
||||||
@@ -324,7 +308,6 @@ class ShopManage:
|
|||||||
goods_name: str,
|
goods_name: str,
|
||||||
num: int,
|
num: int,
|
||||||
text: str,
|
text: str,
|
||||||
at_users: list[At] = [],
|
|
||||||
) -> str | UniMessage | None:
|
) -> str | UniMessage | None:
|
||||||
"""使用道具
|
"""使用道具
|
||||||
|
|
||||||
@@ -336,7 +319,6 @@ class ShopManage:
|
|||||||
goods_name: 商品名称
|
goods_name: 商品名称
|
||||||
num: 使用数量
|
num: 使用数量
|
||||||
text: 其他信息
|
text: 其他信息
|
||||||
at_users: at用户
|
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
str | MessageFactory | None: 使用完成后返回信息
|
str | MessageFactory | None: 使用完成后返回信息
|
||||||
@@ -357,18 +339,16 @@ class ShopManage:
|
|||||||
goods = cls.uuid2goods.get(goods_info.uuid)
|
goods = cls.uuid2goods.get(goods_info.uuid)
|
||||||
if not goods or not goods.func:
|
if not goods or not goods.func:
|
||||||
return f"{goods_info.goods_name} 未注册使用函数, 无法使用..."
|
return f"{goods_info.goods_name} 未注册使用函数, 无法使用..."
|
||||||
at_user_ids = [at.target for at in at_users]
|
|
||||||
param, kwargs = cls.__build_params(
|
param, kwargs = cls.__build_params(
|
||||||
bot, event, session, message, goods, num, text, at_user_ids
|
bot, event, session, message, goods, num, text
|
||||||
)
|
)
|
||||||
if num > param.max_num_limit:
|
if num > param.max_num_limit:
|
||||||
return f"{goods_info.goods_name} 单次使用最大数量为{param.max_num_limit}..."
|
return f"{goods_info.goods_name} 单次使用最大数量为{param.max_num_limit}..."
|
||||||
await cls.run_before_after(goods, param, session, message, "before", **kwargs)
|
await cls.run_before_after(goods, param, session, message, "before", **kwargs)
|
||||||
|
result = await cls.__run(goods, param, session, message, **kwargs)
|
||||||
await UserConsole.use_props(
|
await UserConsole.use_props(
|
||||||
session.user.id, goods_info.uuid, num, PlatformUtils.get_platform(session)
|
session.user.id, goods_info.uuid, num, PlatformUtils.get_platform(session)
|
||||||
)
|
)
|
||||||
result = await cls.__run(goods, param, session, message, **kwargs)
|
|
||||||
|
|
||||||
await cls.run_before_after(goods, param, session, message, "after", **kwargs)
|
await cls.run_before_after(goods, param, session, message, "after", **kwargs)
|
||||||
if not result and param.send_success_msg:
|
if not result and param.send_success_msg:
|
||||||
result = f"使用道具 {goods.name} {num} 次成功!"
|
result = f"使用道具 {goods.name} {num} 次成功!"
|
||||||
@@ -404,10 +384,10 @@ class ShopManage:
|
|||||||
cls.uuid2goods[uuid] = Goods(
|
cls.uuid2goods[uuid] = Goods(
|
||||||
model=create_model(
|
model=create_model(
|
||||||
f"{uuid}_model",
|
f"{uuid}_model",
|
||||||
|
send_success_msg=send_success_msg,
|
||||||
|
max_num_limit=max_num_limit,
|
||||||
__base__=ShopParam,
|
__base__=ShopParam,
|
||||||
send_success_msg=(bool, Field(default=send_success_msg)),
|
extra_data=kwargs,
|
||||||
max_num_limit=(int, Field(default=max_num_limit)),
|
|
||||||
extra_data=(dict[str, Any], Field(default=kwargs)),
|
|
||||||
),
|
),
|
||||||
params=kwargs,
|
params=kwargs,
|
||||||
before_handle=before_handle,
|
before_handle=before_handle,
|
||||||
@@ -499,13 +479,10 @@ class ShopManage:
|
|||||||
if not user.props:
|
if not user.props:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
user.props = {uuid: count for uuid, count in user.props.items() if count > 0}
|
||||||
|
|
||||||
goods_list = await GoodsInfo.filter(uuid__in=user.props.keys()).all()
|
goods_list = await GoodsInfo.filter(uuid__in=user.props.keys()).all()
|
||||||
goods_by_uuid = {item.uuid: item for item in goods_list}
|
goods_by_uuid = {item.uuid: item for item in goods_list}
|
||||||
user.props = {
|
|
||||||
uuid: count
|
|
||||||
for uuid, count in user.props.items()
|
|
||||||
if count > 0 and goods_by_uuid.get(uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
table_rows = []
|
table_rows = []
|
||||||
for i, prop_uuid in enumerate(user.props):
|
for i, prop_uuid in enumerate(user.props):
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from nonebot_plugin_alconna import (
|
|||||||
store_true,
|
store_true,
|
||||||
)
|
)
|
||||||
from nonebot_plugin_apscheduler import scheduler
|
from nonebot_plugin_apscheduler import scheduler
|
||||||
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
|
|
||||||
from zhenxun.configs.utils import (
|
from zhenxun.configs.utils import (
|
||||||
Command,
|
Command,
|
||||||
@@ -22,7 +23,7 @@ from zhenxun.utils.depends import UserName
|
|||||||
from zhenxun.utils.message import MessageUtils
|
from zhenxun.utils.message import MessageUtils
|
||||||
|
|
||||||
from ._data_source import SignManage
|
from ._data_source import SignManage
|
||||||
from .goods_register import Uninfo
|
from .goods_register import driver # noqa: F401
|
||||||
from .utils import clear_sign_data_pic
|
from .utils import clear_sign_data_pic
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
|
from nonebot.drivers import Driver
|
||||||
from nonebot_plugin_uninfo import Uninfo
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
|
|
||||||
from zhenxun.models.sign_user import SignUser
|
from zhenxun.models.sign_user import SignUser
|
||||||
@@ -8,7 +9,14 @@ from zhenxun.models.user_console import UserConsole
|
|||||||
from zhenxun.utils.decorator.shop import shop_register
|
from zhenxun.utils.decorator.shop import shop_register
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
from zhenxun.utils.platform import PlatformUtils
|
||||||
|
|
||||||
driver = nonebot.get_driver()
|
driver: Driver = nonebot.get_driver()
|
||||||
|
|
||||||
|
|
||||||
|
# @driver.on_startup
|
||||||
|
# async def _():
|
||||||
|
# """
|
||||||
|
# 导入内置的三个商品
|
||||||
|
# """
|
||||||
|
|
||||||
|
|
||||||
@shop_register(
|
@shop_register(
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ from zhenxun.models.sign_log import SignLog
|
|||||||
from zhenxun.models.sign_user import SignUser
|
from zhenxun.models.sign_user import SignUser
|
||||||
from zhenxun.utils.http_utils import AsyncHttpx
|
from zhenxun.utils.http_utils import AsyncHttpx
|
||||||
from zhenxun.utils.image_utils import BuildImage
|
from zhenxun.utils.image_utils import BuildImage
|
||||||
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
from zhenxun.utils.platform import PlatformUtils
|
||||||
|
|
||||||
from .config import (
|
from .config import (
|
||||||
@@ -55,7 +54,7 @@ LG_MESSAGE = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@PriorityLifecycle.on_startup(priority=5)
|
@driver.on_startup
|
||||||
async def init_image():
|
async def init_image():
|
||||||
SIGN_RESOURCE_PATH.mkdir(parents=True, exist_ok=True)
|
SIGN_RESOURCE_PATH.mkdir(parents=True, exist_ok=True)
|
||||||
SIGN_TODAY_CARD_PATH.mkdir(exist_ok=True, parents=True)
|
SIGN_TODAY_CARD_PATH.mkdir(exist_ok=True, parents=True)
|
||||||
|
|||||||
@@ -53,7 +53,10 @@ async def _(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@scheduler.scheduled_job("interval", minutes=1, max_instances=5)
|
@scheduler.scheduled_job(
|
||||||
|
"interval",
|
||||||
|
minutes=1,
|
||||||
|
)
|
||||||
async def _():
|
async def _():
|
||||||
try:
|
try:
|
||||||
call_list = TEMP_LIST.copy()
|
call_list = TEMP_LIST.copy()
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ async def enable_plugin(
|
|||||||
)
|
)
|
||||||
await BotConsole.enable_plugin(None, plugin.module)
|
await BotConsole.enable_plugin(None, plugin.module)
|
||||||
await MessageUtils.build_message(
|
await MessageUtils.build_message(
|
||||||
f"已开启全部 bot 的插件: {plugin_name.result}"
|
f"已禁用全部 bot 的插件: {plugin_name.result}"
|
||||||
).finish()
|
).finish()
|
||||||
elif bot_id.available:
|
elif bot_id.available:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ async def enable_task(
|
|||||||
)
|
)
|
||||||
await BotConsole.enable_task(None, task.module)
|
await BotConsole.enable_task(None, task.module)
|
||||||
await MessageUtils.build_message(
|
await MessageUtils.build_message(
|
||||||
f"已开启全部 bot 的被动: {task_name.available}"
|
f"已禁用全部 bot 的被动: {task_name.available}"
|
||||||
).finish()
|
).finish()
|
||||||
elif bot_id.available:
|
elif bot_id.available:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
@@ -1,77 +1,32 @@
|
|||||||
from arclet.alconna import AllParam
|
from typing import Annotated
|
||||||
from nepattern import UnionPattern
|
|
||||||
from nonebot.adapters import Bot, Event
|
from nonebot import on_command
|
||||||
|
from nonebot.adapters import Bot
|
||||||
|
from nonebot.params import Command
|
||||||
from nonebot.permission import SUPERUSER
|
from nonebot.permission import SUPERUSER
|
||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot.rule import to_me
|
from nonebot.rule import to_me
|
||||||
import nonebot_plugin_alconna as alc
|
from nonebot_plugin_alconna import Text as alcText
|
||||||
from nonebot_plugin_alconna import (
|
from nonebot_plugin_alconna import UniMsg
|
||||||
Alconna,
|
|
||||||
Args,
|
|
||||||
on_alconna,
|
|
||||||
)
|
|
||||||
from nonebot_plugin_alconna.uniseg.segment import (
|
|
||||||
At,
|
|
||||||
AtAll,
|
|
||||||
Audio,
|
|
||||||
Button,
|
|
||||||
Emoji,
|
|
||||||
File,
|
|
||||||
Hyper,
|
|
||||||
Image,
|
|
||||||
Keyboard,
|
|
||||||
Reference,
|
|
||||||
Reply,
|
|
||||||
Text,
|
|
||||||
Video,
|
|
||||||
Voice,
|
|
||||||
)
|
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_session import EventSession
|
||||||
|
|
||||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task
|
from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task
|
||||||
|
from zhenxun.services.log import logger
|
||||||
from zhenxun.utils.enum import PluginType
|
from zhenxun.utils.enum import PluginType
|
||||||
from zhenxun.utils.message import MessageUtils
|
from zhenxun.utils.message import MessageUtils
|
||||||
|
|
||||||
from .broadcast_manager import BroadcastManager
|
from ._data_source import BroadcastManage
|
||||||
from .message_processor import (
|
|
||||||
_extract_broadcast_content,
|
|
||||||
get_broadcast_target_groups,
|
|
||||||
send_broadcast_and_notify,
|
|
||||||
)
|
|
||||||
|
|
||||||
BROADCAST_SEND_DELAY_RANGE = (1, 3)
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="广播",
|
name="广播",
|
||||||
description="昭告天下!",
|
description="昭告天下!",
|
||||||
usage="""
|
usage="""
|
||||||
广播 [消息内容]
|
广播 [消息] [图片]
|
||||||
- 直接发送消息到除当前群组外的所有群组
|
示例:广播 你们好!
|
||||||
- 支持文本、图片、@、表情、视频等多种消息类型
|
|
||||||
- 示例:广播 你们好!
|
|
||||||
- 示例:广播 [图片] 新活动开始啦!
|
|
||||||
|
|
||||||
广播 + 引用消息
|
|
||||||
- 将引用的消息作为广播内容发送
|
|
||||||
- 支持引用普通消息或合并转发消息
|
|
||||||
- 示例:(引用一条消息) 广播
|
|
||||||
|
|
||||||
广播撤回
|
|
||||||
- 撤回最近一次由您触发的广播消息
|
|
||||||
- 仅能撤回短时间内的消息
|
|
||||||
- 示例:广播撤回
|
|
||||||
|
|
||||||
特性:
|
|
||||||
- 在群组中使用广播时,不会将消息发送到当前群组
|
|
||||||
- 在私聊中使用广播时,会发送到所有群组
|
|
||||||
|
|
||||||
别名:
|
|
||||||
- bc (广播的简写)
|
|
||||||
- recall (广播撤回的别名)
|
|
||||||
""".strip(),
|
""".strip(),
|
||||||
extra=PluginExtraData(
|
extra=PluginExtraData(
|
||||||
author="HibiKier",
|
author="HibiKier",
|
||||||
version="1.2",
|
version="0.1",
|
||||||
plugin_type=PluginType.SUPERUSER,
|
plugin_type=PluginType.SUPERUSER,
|
||||||
configs=[
|
configs=[
|
||||||
RegisterConfig(
|
RegisterConfig(
|
||||||
@@ -87,106 +42,26 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
).to_dict(),
|
).to_dict(),
|
||||||
)
|
)
|
||||||
|
|
||||||
AnySeg = (
|
_matcher = on_command(
|
||||||
UnionPattern(
|
"广播", priority=1, permission=SUPERUSER, block=True, rule=to_me()
|
||||||
[
|
|
||||||
Text,
|
|
||||||
Image,
|
|
||||||
At,
|
|
||||||
AtAll,
|
|
||||||
Audio,
|
|
||||||
Video,
|
|
||||||
File,
|
|
||||||
Emoji,
|
|
||||||
Reply,
|
|
||||||
Reference,
|
|
||||||
Hyper,
|
|
||||||
Button,
|
|
||||||
Keyboard,
|
|
||||||
Voice,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
@ "AnySeg"
|
|
||||||
)
|
|
||||||
|
|
||||||
_matcher = on_alconna(
|
|
||||||
Alconna(
|
|
||||||
"广播",
|
|
||||||
Args["content?", AllParam],
|
|
||||||
),
|
|
||||||
aliases={"bc"},
|
|
||||||
priority=1,
|
|
||||||
permission=SUPERUSER,
|
|
||||||
block=True,
|
|
||||||
rule=to_me(),
|
|
||||||
use_origin=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
_recall_matcher = on_alconna(
|
|
||||||
Alconna("广播撤回"),
|
|
||||||
aliases={"recall"},
|
|
||||||
priority=1,
|
|
||||||
permission=SUPERUSER,
|
|
||||||
block=True,
|
|
||||||
rule=to_me(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@_matcher.handle()
|
@_matcher.handle()
|
||||||
async def handle_broadcast(
|
async def _(
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
event: Event,
|
|
||||||
session: EventSession,
|
session: EventSession,
|
||||||
arp: alc.Arparma,
|
message: UniMsg,
|
||||||
|
command: Annotated[tuple[str, ...], Command()],
|
||||||
):
|
):
|
||||||
broadcast_content_msg = await _extract_broadcast_content(bot, event, arp, session)
|
for msg in message:
|
||||||
if not broadcast_content_msg:
|
if isinstance(msg, alcText) and msg.text.strip().startswith(command[0]):
|
||||||
return
|
msg.text = msg.text.replace(command[0], "", 1).strip()
|
||||||
|
break
|
||||||
target_groups, enabled_groups = await get_broadcast_target_groups(bot, session)
|
await MessageUtils.build_message("正在发送..请等一下哦!").send()
|
||||||
if not target_groups or not enabled_groups:
|
count, error_count = await BroadcastManage.send(bot, message, session)
|
||||||
return
|
result = f"成功广播 {count} 个群组"
|
||||||
|
if error_count:
|
||||||
try:
|
result += f"\n广播失败 {error_count} 个群组"
|
||||||
await send_broadcast_and_notify(
|
await MessageUtils.build_message(f"发送广播完成!\n{result}").send(reply_to=True)
|
||||||
bot, event, broadcast_content_msg, enabled_groups, target_groups, session
|
logger.info(f"发送广播信息: {message}", "广播", session=session)
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
error_msg = "发送广播失败"
|
|
||||||
BroadcastManager.log_error(error_msg, e, session)
|
|
||||||
await MessageUtils.build_message(f"{error_msg}。").send(reply_to=True)
|
|
||||||
|
|
||||||
|
|
||||||
@_recall_matcher.handle()
|
|
||||||
async def handle_broadcast_recall(
|
|
||||||
bot: Bot,
|
|
||||||
event: Event,
|
|
||||||
session: EventSession,
|
|
||||||
):
|
|
||||||
"""处理广播撤回命令"""
|
|
||||||
await MessageUtils.build_message("正在尝试撤回最近一次广播...").send()
|
|
||||||
|
|
||||||
try:
|
|
||||||
success_count, error_count = await BroadcastManager.recall_last_broadcast(
|
|
||||||
bot, session
|
|
||||||
)
|
|
||||||
|
|
||||||
user_id = str(event.get_user_id())
|
|
||||||
if success_count == 0 and error_count == 0:
|
|
||||||
await bot.send_private_msg(
|
|
||||||
user_id=user_id,
|
|
||||||
message="没有找到最近的广播消息记录,可能已经撤回或超过可撤回时间。",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
result = f"广播撤回完成!\n成功撤回 {success_count} 条消息"
|
|
||||||
if error_count:
|
|
||||||
result += f"\n撤回失败 {error_count} 条消息 (可能已过期或无权限)"
|
|
||||||
await bot.send_private_msg(user_id=user_id, message=result)
|
|
||||||
BroadcastManager.log_info(
|
|
||||||
f"广播撤回完成: 成功 {success_count}, 失败 {error_count}", session
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
error_msg = "撤回广播消息失败"
|
|
||||||
BroadcastManager.log_error(error_msg, e, session)
|
|
||||||
user_id = str(event.get_user_id())
|
|
||||||
await bot.send_private_msg(user_id=user_id, message=f"{error_msg}。")
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import asyncio
|
||||||
|
import random
|
||||||
|
|
||||||
|
from nonebot.adapters import Bot
|
||||||
|
import nonebot_plugin_alconna as alc
|
||||||
|
from nonebot_plugin_alconna import Image, UniMsg
|
||||||
|
from nonebot_plugin_session import EventSession
|
||||||
|
|
||||||
|
from zhenxun.services.log import logger
|
||||||
|
from zhenxun.utils.common_utils import CommonUtils
|
||||||
|
from zhenxun.utils.message import MessageUtils
|
||||||
|
from zhenxun.utils.platform import PlatformUtils
|
||||||
|
|
||||||
|
|
||||||
|
class BroadcastManage:
|
||||||
|
@classmethod
|
||||||
|
async def send(
|
||||||
|
cls, bot: Bot, message: UniMsg, session: EventSession
|
||||||
|
) -> tuple[int, int]:
|
||||||
|
"""发送广播消息
|
||||||
|
|
||||||
|
参数:
|
||||||
|
bot: Bot
|
||||||
|
message: 消息内容
|
||||||
|
session: Session
|
||||||
|
|
||||||
|
返回:
|
||||||
|
tuple[int, int]: 发送成功的群组数量, 发送失败的群组数量
|
||||||
|
"""
|
||||||
|
message_list = []
|
||||||
|
for msg in message:
|
||||||
|
if isinstance(msg, alc.Image) and msg.url:
|
||||||
|
message_list.append(Image(url=msg.url))
|
||||||
|
elif isinstance(msg, alc.Text):
|
||||||
|
message_list.append(msg.text)
|
||||||
|
group_list, _ = await PlatformUtils.get_group_list(bot)
|
||||||
|
if group_list:
|
||||||
|
error_count = 0
|
||||||
|
for group in group_list:
|
||||||
|
try:
|
||||||
|
if not await CommonUtils.task_is_block(
|
||||||
|
bot,
|
||||||
|
"broadcast", # group.channel_id
|
||||||
|
group.group_id,
|
||||||
|
):
|
||||||
|
target = PlatformUtils.get_target(
|
||||||
|
group_id=group.group_id, channel_id=group.channel_id
|
||||||
|
)
|
||||||
|
if target:
|
||||||
|
await MessageUtils.build_message(message_list).send(
|
||||||
|
target, bot
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
"发送成功",
|
||||||
|
"广播",
|
||||||
|
session=session,
|
||||||
|
target=f"{group.group_id}:{group.channel_id}",
|
||||||
|
)
|
||||||
|
await asyncio.sleep(random.randint(1, 3))
|
||||||
|
else:
|
||||||
|
logger.warning("target为空", "广播", session=session)
|
||||||
|
except Exception as e:
|
||||||
|
error_count += 1
|
||||||
|
logger.error(
|
||||||
|
"发送失败",
|
||||||
|
"广播",
|
||||||
|
session=session,
|
||||||
|
target=f"{group.group_id}:{group.channel_id}",
|
||||||
|
e=e,
|
||||||
|
)
|
||||||
|
return len(group_list) - error_count, error_count
|
||||||
|
return 0, 0
|
||||||
@@ -1,490 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import random
|
|
||||||
import traceback
|
|
||||||
from typing import ClassVar
|
|
||||||
|
|
||||||
from nonebot.adapters import Bot
|
|
||||||
from nonebot.adapters.onebot.v11 import Bot as V11Bot
|
|
||||||
from nonebot.exception import ActionFailed
|
|
||||||
from nonebot_plugin_alconna import UniMessage
|
|
||||||
from nonebot_plugin_alconna.uniseg import Receipt, Reference
|
|
||||||
from nonebot_plugin_session import EventSession
|
|
||||||
|
|
||||||
from zhenxun.models.group_console import GroupConsole
|
|
||||||
from zhenxun.services.log import logger
|
|
||||||
from zhenxun.utils.common_utils import CommonUtils
|
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
|
||||||
|
|
||||||
from .models import BroadcastDetailResult, BroadcastResult
|
|
||||||
from .utils import custom_nodes_to_v11_nodes, uni_message_to_v11_list_of_dicts
|
|
||||||
|
|
||||||
|
|
||||||
class BroadcastManager:
|
|
||||||
"""广播管理器"""
|
|
||||||
|
|
||||||
_last_broadcast_msg_ids: ClassVar[dict[str, int]] = {}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_session_info(session: EventSession | None) -> str:
|
|
||||||
"""获取会话信息字符串"""
|
|
||||||
if not session:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
platform = getattr(session, "platform", "unknown")
|
|
||||||
session_id = str(session)
|
|
||||||
return f"[{platform}:{session_id}]"
|
|
||||||
except Exception:
|
|
||||||
return "[session-info-error]"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def log_error(
|
|
||||||
message: str, error: Exception, session: EventSession | None = None, **kwargs
|
|
||||||
):
|
|
||||||
"""记录错误日志"""
|
|
||||||
session_info = BroadcastManager._get_session_info(session)
|
|
||||||
error_type = type(error).__name__
|
|
||||||
stack_trace = traceback.format_exc()
|
|
||||||
error_details = f"\n类型: {error_type}\n信息: {error!s}\n堆栈: {stack_trace}"
|
|
||||||
|
|
||||||
logger.error(
|
|
||||||
f"{session_info} {message}{error_details}", "广播", e=error, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def log_warning(message: str, session: EventSession | None = None, **kwargs):
|
|
||||||
"""记录警告级别日志"""
|
|
||||||
session_info = BroadcastManager._get_session_info(session)
|
|
||||||
logger.warning(f"{session_info} {message}", "广播", **kwargs)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def log_info(message: str, session: EventSession | None = None, **kwargs):
|
|
||||||
"""记录信息级别日志"""
|
|
||||||
session_info = BroadcastManager._get_session_info(session)
|
|
||||||
logger.info(f"{session_info} {message}", "广播", **kwargs)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_last_broadcast_msg_ids(cls) -> dict[str, int]:
|
|
||||||
"""获取最近广播消息ID"""
|
|
||||||
return cls._last_broadcast_msg_ids.copy()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def clear_last_broadcast_msg_ids(cls) -> None:
|
|
||||||
"""清空消息ID记录"""
|
|
||||||
cls._last_broadcast_msg_ids.clear()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def get_all_groups(cls, bot: Bot) -> tuple[list[GroupConsole], str]:
|
|
||||||
"""获取群组列表"""
|
|
||||||
return await PlatformUtils.get_group_list(bot)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def send(
|
|
||||||
cls, bot: Bot, message: UniMessage, session: EventSession
|
|
||||||
) -> BroadcastResult:
|
|
||||||
"""发送广播到所有群组"""
|
|
||||||
logger.debug(
|
|
||||||
f"开始广播(send - 广播到所有群组),Bot ID: {bot.self_id}",
|
|
||||||
"广播",
|
|
||||||
session=session,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug("清空上一次的广播消息ID记录", "广播", session=session)
|
|
||||||
cls.clear_last_broadcast_msg_ids()
|
|
||||||
|
|
||||||
all_groups, _ = await cls.get_all_groups(bot)
|
|
||||||
return await cls.send_to_specific_groups(bot, message, all_groups, session)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def send_to_specific_groups(
|
|
||||||
cls,
|
|
||||||
bot: Bot,
|
|
||||||
message: UniMessage,
|
|
||||||
target_groups: list[GroupConsole],
|
|
||||||
session_info: EventSession | str | None = None,
|
|
||||||
) -> BroadcastResult:
|
|
||||||
"""发送广播到指定群组"""
|
|
||||||
log_session = session_info or bot.self_id
|
|
||||||
logger.debug(
|
|
||||||
f"开始广播,目标 {len(target_groups)} 个群组,Bot ID: {bot.self_id}",
|
|
||||||
"广播",
|
|
||||||
session=log_session,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not target_groups:
|
|
||||||
logger.debug("目标群组列表为空,广播结束", "广播", session=log_session)
|
|
||||||
return 0, 0
|
|
||||||
|
|
||||||
platform = PlatformUtils.get_platform(bot)
|
|
||||||
is_forward_broadcast = any(
|
|
||||||
isinstance(seg, Reference) and getattr(seg, "nodes", None)
|
|
||||||
for seg in message
|
|
||||||
)
|
|
||||||
|
|
||||||
if platform == "qq" and isinstance(bot, V11Bot) and is_forward_broadcast:
|
|
||||||
if (
|
|
||||||
len(message) == 1
|
|
||||||
and isinstance(message[0], Reference)
|
|
||||||
and getattr(message[0], "nodes", None)
|
|
||||||
):
|
|
||||||
nodes_list = getattr(message[0], "nodes", [])
|
|
||||||
v11_nodes = custom_nodes_to_v11_nodes(nodes_list)
|
|
||||||
node_count = len(v11_nodes)
|
|
||||||
logger.debug(
|
|
||||||
f"从 UniMessage<Reference> 构造转发节点数: {node_count}",
|
|
||||||
"广播",
|
|
||||||
session=log_session,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
"广播消息包含合并转发段和其他段,将尝试打平成一个节点发送",
|
|
||||||
"广播",
|
|
||||||
session=log_session,
|
|
||||||
)
|
|
||||||
v11_content_list = uni_message_to_v11_list_of_dicts(message)
|
|
||||||
v11_nodes = (
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"data": {
|
|
||||||
"user_id": bot.self_id,
|
|
||||||
"nickname": "广播",
|
|
||||||
"content": v11_content_list,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
if v11_content_list
|
|
||||||
else []
|
|
||||||
)
|
|
||||||
|
|
||||||
if not v11_nodes:
|
|
||||||
logger.warning(
|
|
||||||
"构造出的 V11 合并转发节点为空,无法发送",
|
|
||||||
"广播",
|
|
||||||
session=log_session,
|
|
||||||
)
|
|
||||||
return 0, len(target_groups)
|
|
||||||
success_count, error_count, skip_count = await cls._broadcast_forward(
|
|
||||||
bot, log_session, target_groups, v11_nodes
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if is_forward_broadcast:
|
|
||||||
logger.warning(
|
|
||||||
f"合并转发消息在适配器 ({platform}) 不支持,将作为普通消息发送",
|
|
||||||
"广播",
|
|
||||||
session=log_session,
|
|
||||||
)
|
|
||||||
success_count, error_count, skip_count = await cls._broadcast_normal(
|
|
||||||
bot, log_session, target_groups, message
|
|
||||||
)
|
|
||||||
|
|
||||||
total = len(target_groups)
|
|
||||||
stats = f"成功: {success_count}, 失败: {error_count}"
|
|
||||||
stats += f", 跳过: {skip_count}, 总计: {total}"
|
|
||||||
logger.debug(
|
|
||||||
f"广播统计 - {stats}",
|
|
||||||
"广播",
|
|
||||||
session=log_session,
|
|
||||||
)
|
|
||||||
|
|
||||||
msg_ids = cls.get_last_broadcast_msg_ids()
|
|
||||||
if msg_ids:
|
|
||||||
id_list_str = ", ".join([f"{k}:{v}" for k, v in msg_ids.items()])
|
|
||||||
logger.debug(
|
|
||||||
f"广播结束,记录了 {len(msg_ids)} 条消息ID: {id_list_str}",
|
|
||||||
"广播",
|
|
||||||
session=log_session,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
"广播结束,但没有记录任何消息ID",
|
|
||||||
"广播",
|
|
||||||
session=log_session,
|
|
||||||
)
|
|
||||||
|
|
||||||
return success_count, error_count
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def _extract_message_id_from_result(
|
|
||||||
cls,
|
|
||||||
result: dict | Receipt,
|
|
||||||
group_key: str,
|
|
||||||
session_info: EventSession | str,
|
|
||||||
msg_type: str = "普通",
|
|
||||||
) -> None:
|
|
||||||
"""提取消息ID并记录"""
|
|
||||||
if isinstance(result, dict) and "message_id" in result:
|
|
||||||
msg_id = result["message_id"]
|
|
||||||
try:
|
|
||||||
msg_id_int = int(msg_id)
|
|
||||||
cls._last_broadcast_msg_ids[group_key] = msg_id_int
|
|
||||||
logger.debug(
|
|
||||||
f"记录群 {group_key} 的{msg_type}消息ID: {msg_id_int}",
|
|
||||||
"广播",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
logger.warning(
|
|
||||||
f"{msg_type}结果中的 message_id 不是有效整数: {msg_id}",
|
|
||||||
"广播",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
elif isinstance(result, Receipt) and result.msg_ids:
|
|
||||||
try:
|
|
||||||
first_id_info = result.msg_ids[0]
|
|
||||||
msg_id = None
|
|
||||||
if isinstance(first_id_info, dict) and "message_id" in first_id_info:
|
|
||||||
msg_id = first_id_info["message_id"]
|
|
||||||
logger.debug(
|
|
||||||
f"从 Receipt.msg_ids[0] 提取到 ID: {msg_id}",
|
|
||||||
"广播",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
elif isinstance(first_id_info, int | str):
|
|
||||||
msg_id = first_id_info
|
|
||||||
logger.debug(
|
|
||||||
f"从 Receipt.msg_ids[0] 提取到原始ID: {msg_id}",
|
|
||||||
"广播",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
|
|
||||||
if msg_id is not None:
|
|
||||||
try:
|
|
||||||
msg_id_int = int(msg_id)
|
|
||||||
cls._last_broadcast_msg_ids[group_key] = msg_id_int
|
|
||||||
logger.debug(
|
|
||||||
f"记录群 {group_key} 的消息ID: {msg_id_int}",
|
|
||||||
"广播",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
logger.warning(
|
|
||||||
f"提取的ID ({msg_id}) 不是有效整数",
|
|
||||||
"广播",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
info_str = str(first_id_info)
|
|
||||||
logger.warning(
|
|
||||||
f"无法从 Receipt.msg_ids[0] 提取ID: {info_str}",
|
|
||||||
"广播",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
except IndexError:
|
|
||||||
logger.warning("Receipt.msg_ids 为空", "广播", session=session_info)
|
|
||||||
except Exception as e_extract:
|
|
||||||
logger.error(
|
|
||||||
f"从 Receipt 提取 msg_id 时出错: {e_extract}",
|
|
||||||
"广播",
|
|
||||||
session=session_info,
|
|
||||||
e=e_extract,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
f"发送成功但无法从结果获取消息 ID. 结果: {result}",
|
|
||||||
"广播",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def _check_group_availability(cls, bot: Bot, group: GroupConsole) -> bool:
|
|
||||||
"""检查群组是否可用"""
|
|
||||||
if not group.group_id:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if await CommonUtils.task_is_block(bot, "broadcast", group.group_id):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def _broadcast_forward(
|
|
||||||
cls,
|
|
||||||
bot: V11Bot,
|
|
||||||
session_info: EventSession | str,
|
|
||||||
group_list: list[GroupConsole],
|
|
||||||
v11_nodes: list[dict],
|
|
||||||
) -> BroadcastDetailResult:
|
|
||||||
"""发送合并转发"""
|
|
||||||
success_count = 0
|
|
||||||
error_count = 0
|
|
||||||
skip_count = 0
|
|
||||||
|
|
||||||
for _, group in enumerate(group_list):
|
|
||||||
group_key = group.group_id or group.channel_id
|
|
||||||
|
|
||||||
if not await cls._check_group_availability(bot, group):
|
|
||||||
skip_count += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = await bot.send_group_forward_msg(
|
|
||||||
group_id=int(group.group_id), messages=v11_nodes
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
f"合并转发消息发送结果: {result}, 类型: {type(result)}",
|
|
||||||
"广播",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
|
|
||||||
await cls._extract_message_id_from_result(
|
|
||||||
result, group_key, session_info, "合并转发"
|
|
||||||
)
|
|
||||||
|
|
||||||
success_count += 1
|
|
||||||
await asyncio.sleep(random.randint(1, 3))
|
|
||||||
except ActionFailed as af_e:
|
|
||||||
error_count += 1
|
|
||||||
logger.error(
|
|
||||||
f"发送失败(合并转发) to {group_key}: {af_e}",
|
|
||||||
"广播",
|
|
||||||
session=session_info,
|
|
||||||
e=af_e,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
error_count += 1
|
|
||||||
logger.error(
|
|
||||||
f"发送失败(合并转发) to {group_key}: {e}",
|
|
||||||
"广播",
|
|
||||||
session=session_info,
|
|
||||||
e=e,
|
|
||||||
)
|
|
||||||
|
|
||||||
return success_count, error_count, skip_count
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def _broadcast_normal(
|
|
||||||
cls,
|
|
||||||
bot: Bot,
|
|
||||||
session_info: EventSession | str,
|
|
||||||
group_list: list[GroupConsole],
|
|
||||||
message: UniMessage,
|
|
||||||
) -> BroadcastDetailResult:
|
|
||||||
"""发送普通消息"""
|
|
||||||
success_count = 0
|
|
||||||
error_count = 0
|
|
||||||
skip_count = 0
|
|
||||||
|
|
||||||
for _, group in enumerate(group_list):
|
|
||||||
group_key = (
|
|
||||||
f"{group.group_id}:{group.channel_id}"
|
|
||||||
if group.channel_id
|
|
||||||
else str(group.group_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not await cls._check_group_availability(bot, group):
|
|
||||||
skip_count += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
target = PlatformUtils.get_target(
|
|
||||||
group_id=group.group_id, channel_id=group.channel_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if target:
|
|
||||||
receipt: Receipt = await message.send(target, bot=bot)
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
f"广播消息发送结果: {receipt}, 类型: {type(receipt)}",
|
|
||||||
"广播",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
|
|
||||||
await cls._extract_message_id_from_result(
|
|
||||||
receipt, group_key, session_info
|
|
||||||
)
|
|
||||||
|
|
||||||
success_count += 1
|
|
||||||
await asyncio.sleep(random.randint(1, 3))
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
"target为空", "广播", session=session_info, target=group_key
|
|
||||||
)
|
|
||||||
skip_count += 1
|
|
||||||
except Exception as e:
|
|
||||||
error_count += 1
|
|
||||||
logger.error(
|
|
||||||
f"发送失败(普通) to {group_key}: {e}",
|
|
||||||
"广播",
|
|
||||||
session=session_info,
|
|
||||||
e=e,
|
|
||||||
)
|
|
||||||
|
|
||||||
return success_count, error_count, skip_count
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def recall_last_broadcast(
|
|
||||||
cls, bot: Bot, session_info: EventSession | str
|
|
||||||
) -> BroadcastResult:
|
|
||||||
"""撤回最近广播"""
|
|
||||||
msg_ids_to_recall = cls.get_last_broadcast_msg_ids()
|
|
||||||
|
|
||||||
if not msg_ids_to_recall:
|
|
||||||
logger.warning(
|
|
||||||
"没有找到最近的广播消息ID记录", "广播撤回", session=session_info
|
|
||||||
)
|
|
||||||
return 0, 0
|
|
||||||
|
|
||||||
id_list_str = ", ".join([f"{k}:{v}" for k, v in msg_ids_to_recall.items()])
|
|
||||||
logger.debug(
|
|
||||||
f"找到 {len(msg_ids_to_recall)} 条广播消息ID记录: {id_list_str}",
|
|
||||||
"广播撤回",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
|
|
||||||
success_count = 0
|
|
||||||
error_count = 0
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"准备撤回 {len(msg_ids_to_recall)} 条广播消息",
|
|
||||||
"广播撤回",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
|
|
||||||
for group_key, msg_id in msg_ids_to_recall.items():
|
|
||||||
try:
|
|
||||||
logger.debug(
|
|
||||||
f"尝试撤回消息 (ID: {msg_id}) in {group_key}",
|
|
||||||
"广播撤回",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
await bot.call_api("delete_msg", message_id=msg_id)
|
|
||||||
success_count += 1
|
|
||||||
except ActionFailed as af_e:
|
|
||||||
retcode = getattr(af_e, "retcode", None)
|
|
||||||
wording = getattr(af_e, "wording", "")
|
|
||||||
if retcode == 100 and "MESSAGE_NOT_FOUND" in wording.upper():
|
|
||||||
logger.warning(
|
|
||||||
f"消息 (ID: {msg_id}) 可能已被撤回或不存在于 {group_key}",
|
|
||||||
"广播撤回",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
elif retcode == 300 and "delete message" in wording.lower():
|
|
||||||
logger.warning(
|
|
||||||
f"消息 (ID: {msg_id}) 可能已被撤回或不存在于 {group_key}",
|
|
||||||
"广播撤回",
|
|
||||||
session=session_info,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
error_count += 1
|
|
||||||
logger.error(
|
|
||||||
f"撤回消息失败 (ID: {msg_id}) in {group_key}: {af_e}",
|
|
||||||
"广播撤回",
|
|
||||||
session=session_info,
|
|
||||||
e=af_e,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
error_count += 1
|
|
||||||
logger.error(
|
|
||||||
f"撤回消息时发生未知错误 (ID: {msg_id}) in {group_key}: {e}",
|
|
||||||
"广播撤回",
|
|
||||||
session=session_info,
|
|
||||||
e=e,
|
|
||||||
)
|
|
||||||
await asyncio.sleep(0.2)
|
|
||||||
|
|
||||||
logger.debug("撤回操作完成,清空消息ID记录", "广播撤回", session=session_info)
|
|
||||||
cls.clear_last_broadcast_msg_ids()
|
|
||||||
|
|
||||||
return success_count, error_count
|
|
||||||
@@ -1,584 +0,0 @@
|
|||||||
import base64
|
|
||||||
import json
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from nonebot.adapters import Bot, Event
|
|
||||||
from nonebot.adapters.onebot.v11 import Message as V11Message
|
|
||||||
from nonebot.adapters.onebot.v11 import MessageSegment as V11MessageSegment
|
|
||||||
from nonebot.exception import ActionFailed
|
|
||||||
import nonebot_plugin_alconna as alc
|
|
||||||
from nonebot_plugin_alconna import UniMessage
|
|
||||||
from nonebot_plugin_alconna.uniseg.segment import (
|
|
||||||
At,
|
|
||||||
AtAll,
|
|
||||||
CustomNode,
|
|
||||||
Image,
|
|
||||||
Reference,
|
|
||||||
Reply,
|
|
||||||
Text,
|
|
||||||
Video,
|
|
||||||
)
|
|
||||||
from nonebot_plugin_alconna.uniseg.tools import reply_fetch
|
|
||||||
from nonebot_plugin_session import EventSession
|
|
||||||
|
|
||||||
from zhenxun.services.log import logger
|
|
||||||
from zhenxun.utils.common_utils import CommonUtils
|
|
||||||
from zhenxun.utils.message import MessageUtils
|
|
||||||
|
|
||||||
from .broadcast_manager import BroadcastManager
|
|
||||||
|
|
||||||
MAX_FORWARD_DEPTH = 3
|
|
||||||
|
|
||||||
|
|
||||||
async def _process_forward_content(
|
|
||||||
forward_content: Any, forward_id: str | None, bot: Bot, depth: int
|
|
||||||
) -> list[CustomNode]:
|
|
||||||
"""处理转发消息内容"""
|
|
||||||
nodes_for_alc = []
|
|
||||||
content_parsed = False
|
|
||||||
|
|
||||||
if forward_content:
|
|
||||||
nodes_from_content = None
|
|
||||||
if isinstance(forward_content, list):
|
|
||||||
nodes_from_content = forward_content
|
|
||||||
elif isinstance(forward_content, str):
|
|
||||||
try:
|
|
||||||
parsed_content = json.loads(forward_content)
|
|
||||||
if isinstance(parsed_content, list):
|
|
||||||
nodes_from_content = parsed_content
|
|
||||||
except Exception as json_e:
|
|
||||||
logger.debug(
|
|
||||||
f"[Depth {depth}] JSON解析失败: {json_e}",
|
|
||||||
"广播",
|
|
||||||
)
|
|
||||||
|
|
||||||
if nodes_from_content is not None:
|
|
||||||
logger.debug(
|
|
||||||
f"[D{depth}] 节点数: {len(nodes_from_content)}",
|
|
||||||
"广播",
|
|
||||||
)
|
|
||||||
content_parsed = True
|
|
||||||
for node_data in nodes_from_content:
|
|
||||||
node = await _create_custom_node_from_data(node_data, bot, depth + 1)
|
|
||||||
if node:
|
|
||||||
nodes_for_alc.append(node)
|
|
||||||
|
|
||||||
if not content_parsed and forward_id:
|
|
||||||
logger.debug(
|
|
||||||
f"[D{depth}] 尝试API调用ID: {forward_id}",
|
|
||||||
"广播",
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
forward_data = await bot.call_api("get_forward_msg", id=forward_id)
|
|
||||||
nodes_list = None
|
|
||||||
|
|
||||||
if isinstance(forward_data, dict) and "messages" in forward_data:
|
|
||||||
nodes_list = forward_data["messages"]
|
|
||||||
elif (
|
|
||||||
isinstance(forward_data, dict)
|
|
||||||
and "data" in forward_data
|
|
||||||
and isinstance(forward_data["data"], dict)
|
|
||||||
and "message" in forward_data["data"]
|
|
||||||
):
|
|
||||||
nodes_list = forward_data["data"]["message"]
|
|
||||||
elif isinstance(forward_data, list):
|
|
||||||
nodes_list = forward_data
|
|
||||||
|
|
||||||
if nodes_list:
|
|
||||||
node_count = len(nodes_list)
|
|
||||||
logger.debug(
|
|
||||||
f"[D{depth + 1}] 节点:{node_count}",
|
|
||||||
"广播",
|
|
||||||
)
|
|
||||||
for node_data in nodes_list:
|
|
||||||
node = await _create_custom_node_from_data(
|
|
||||||
node_data, bot, depth + 1
|
|
||||||
)
|
|
||||||
if node:
|
|
||||||
nodes_for_alc.append(node)
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
f"[D{depth + 1}] ID:{forward_id}无节点",
|
|
||||||
"广播",
|
|
||||||
)
|
|
||||||
nodes_for_alc.append(
|
|
||||||
CustomNode(
|
|
||||||
uid="0",
|
|
||||||
name="错误",
|
|
||||||
content="[嵌套转发消息获取失败]",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except ActionFailed as af_e:
|
|
||||||
logger.error(
|
|
||||||
f"[D{depth + 1}] API失败: {af_e}",
|
|
||||||
"广播",
|
|
||||||
e=af_e,
|
|
||||||
)
|
|
||||||
nodes_for_alc.append(
|
|
||||||
CustomNode(
|
|
||||||
uid="0",
|
|
||||||
name="错误",
|
|
||||||
content="[嵌套转发消息获取失败]",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"[D{depth + 1}] 处理出错: {e}",
|
|
||||||
"广播",
|
|
||||||
e=e,
|
|
||||||
)
|
|
||||||
nodes_for_alc.append(
|
|
||||||
CustomNode(
|
|
||||||
uid="0",
|
|
||||||
name="错误",
|
|
||||||
content="[处理嵌套转发时出错]",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif not content_parsed and not forward_id:
|
|
||||||
logger.warning(
|
|
||||||
f"[D{depth}] 转发段无内容也无ID",
|
|
||||||
"广播",
|
|
||||||
)
|
|
||||||
nodes_for_alc.append(
|
|
||||||
CustomNode(
|
|
||||||
uid="0",
|
|
||||||
name="错误",
|
|
||||||
content="[嵌套转发消息无法解析]",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif content_parsed and not nodes_for_alc:
|
|
||||||
logger.warning(
|
|
||||||
f"[D{depth}] 解析成功但无有效节点",
|
|
||||||
"广播",
|
|
||||||
)
|
|
||||||
nodes_for_alc.append(
|
|
||||||
CustomNode(
|
|
||||||
uid="0",
|
|
||||||
name="信息",
|
|
||||||
content="[嵌套转发内容为空]",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return nodes_for_alc
|
|
||||||
|
|
||||||
|
|
||||||
async def _create_custom_node_from_data(
|
|
||||||
node_data: dict, bot: Bot, depth: int
|
|
||||||
) -> CustomNode | None:
|
|
||||||
"""从节点数据创建CustomNode"""
|
|
||||||
node_content_raw = node_data.get("message") or node_data.get("content")
|
|
||||||
if not node_content_raw:
|
|
||||||
logger.warning(f"[D{depth}] 节点缺少消息内容", "广播")
|
|
||||||
return None
|
|
||||||
|
|
||||||
sender = node_data.get("sender", {})
|
|
||||||
uid = str(sender.get("user_id", "10000"))
|
|
||||||
name = sender.get("nickname", f"用户{uid[:4]}")
|
|
||||||
|
|
||||||
extracted_uni_msg = await _extract_content_from_message(
|
|
||||||
node_content_raw, bot, depth
|
|
||||||
)
|
|
||||||
if not extracted_uni_msg:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return CustomNode(uid=uid, name=name, content=extracted_uni_msg)
|
|
||||||
|
|
||||||
|
|
||||||
async def _extract_broadcast_content(
|
|
||||||
bot: Bot,
|
|
||||||
event: Event,
|
|
||||||
arp: alc.Arparma,
|
|
||||||
session: EventSession,
|
|
||||||
) -> UniMessage | None:
|
|
||||||
"""从命令参数或引用消息中提取广播内容"""
|
|
||||||
broadcast_content_msg: UniMessage | None = None
|
|
||||||
|
|
||||||
command_content_list = arp.all_matched_args.get("content", [])
|
|
||||||
|
|
||||||
processed_command_list = []
|
|
||||||
has_command_content = False
|
|
||||||
|
|
||||||
if command_content_list:
|
|
||||||
for item in command_content_list:
|
|
||||||
if isinstance(item, alc.Segment):
|
|
||||||
processed_command_list.append(item)
|
|
||||||
if not (isinstance(item, Text) and not item.text.strip()):
|
|
||||||
has_command_content = True
|
|
||||||
elif isinstance(item, str):
|
|
||||||
if item.strip():
|
|
||||||
processed_command_list.append(Text(item.strip()))
|
|
||||||
has_command_content = True
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
f"Unexpected type in command content: {type(item)}", "广播"
|
|
||||||
)
|
|
||||||
|
|
||||||
if has_command_content:
|
|
||||||
logger.debug("检测到命令参数内容,优先使用参数内容", "广播", session=session)
|
|
||||||
broadcast_content_msg = UniMessage(processed_command_list)
|
|
||||||
|
|
||||||
if not broadcast_content_msg.filter(
|
|
||||||
lambda x: not (isinstance(x, Text) and not x.text.strip())
|
|
||||||
):
|
|
||||||
logger.warning(
|
|
||||||
"命令参数内容解析后为空或只包含空白", "广播", session=session
|
|
||||||
)
|
|
||||||
broadcast_content_msg = None
|
|
||||||
|
|
||||||
if not broadcast_content_msg:
|
|
||||||
reply_segment_obj: Reply | None = await reply_fetch(event, bot)
|
|
||||||
if (
|
|
||||||
reply_segment_obj
|
|
||||||
and hasattr(reply_segment_obj, "msg")
|
|
||||||
and reply_segment_obj.msg
|
|
||||||
):
|
|
||||||
logger.debug(
|
|
||||||
"未检测到有效命令参数,检测到引用消息", "广播", session=session
|
|
||||||
)
|
|
||||||
raw_quoted_content = reply_segment_obj.msg
|
|
||||||
is_forward = False
|
|
||||||
forward_id = None
|
|
||||||
|
|
||||||
if isinstance(raw_quoted_content, V11Message):
|
|
||||||
for seg in raw_quoted_content:
|
|
||||||
if isinstance(seg, V11MessageSegment):
|
|
||||||
if seg.type == "forward":
|
|
||||||
forward_id = seg.data.get("id")
|
|
||||||
is_forward = bool(forward_id)
|
|
||||||
break
|
|
||||||
elif seg.type == "json":
|
|
||||||
try:
|
|
||||||
json_data_str = seg.data.get("data", "{}")
|
|
||||||
if isinstance(json_data_str, str):
|
|
||||||
import json
|
|
||||||
|
|
||||||
json_data = json.loads(json_data_str)
|
|
||||||
if (
|
|
||||||
json_data.get("app") == "com.tencent.multimsg"
|
|
||||||
or json_data.get("view") == "Forward"
|
|
||||||
) and json_data.get("meta", {}).get(
|
|
||||||
"detail", {}
|
|
||||||
).get("resid"):
|
|
||||||
forward_id = json_data["meta"]["detail"][
|
|
||||||
"resid"
|
|
||||||
]
|
|
||||||
is_forward = True
|
|
||||||
break
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if is_forward and forward_id:
|
|
||||||
logger.info(
|
|
||||||
f"尝试获取并构造合并转发内容 (ID: {forward_id})",
|
|
||||||
"广播",
|
|
||||||
session=session,
|
|
||||||
)
|
|
||||||
nodes_to_forward: list[CustomNode] = []
|
|
||||||
try:
|
|
||||||
forward_data = await bot.call_api("get_forward_msg", id=forward_id)
|
|
||||||
nodes_list = None
|
|
||||||
if isinstance(forward_data, dict) and "messages" in forward_data:
|
|
||||||
nodes_list = forward_data["messages"]
|
|
||||||
elif (
|
|
||||||
isinstance(forward_data, dict)
|
|
||||||
and "data" in forward_data
|
|
||||||
and isinstance(forward_data["data"], dict)
|
|
||||||
and "message" in forward_data["data"]
|
|
||||||
):
|
|
||||||
nodes_list = forward_data["data"]["message"]
|
|
||||||
elif isinstance(forward_data, list):
|
|
||||||
nodes_list = forward_data
|
|
||||||
|
|
||||||
if nodes_list is not None:
|
|
||||||
for node_data in nodes_list:
|
|
||||||
node_sender = node_data.get("sender", {})
|
|
||||||
node_user_id = str(node_sender.get("user_id", "10000"))
|
|
||||||
node_nickname = node_sender.get(
|
|
||||||
"nickname", f"用户{node_user_id[:4]}"
|
|
||||||
)
|
|
||||||
node_content_raw = node_data.get(
|
|
||||||
"message"
|
|
||||||
) or node_data.get("content")
|
|
||||||
if node_content_raw:
|
|
||||||
extracted_node_uni_msg = (
|
|
||||||
await _extract_content_from_message(
|
|
||||||
node_content_raw, bot
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if extracted_node_uni_msg:
|
|
||||||
nodes_to_forward.append(
|
|
||||||
CustomNode(
|
|
||||||
uid=node_user_id,
|
|
||||||
name=node_nickname,
|
|
||||||
content=extracted_node_uni_msg,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if nodes_to_forward:
|
|
||||||
broadcast_content_msg = UniMessage(
|
|
||||||
Reference(nodes=nodes_to_forward)
|
|
||||||
)
|
|
||||||
except ActionFailed:
|
|
||||||
await MessageUtils.build_message(
|
|
||||||
"获取合并转发消息失败,可能不支持此 API。"
|
|
||||||
).send(reply_to=True)
|
|
||||||
return None
|
|
||||||
except Exception as api_e:
|
|
||||||
logger.error(f"处理合并转发时出错: {api_e}", "广播", e=api_e)
|
|
||||||
await MessageUtils.build_message(
|
|
||||||
"处理合并转发消息时发生内部错误。"
|
|
||||||
).send(reply_to=True)
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
broadcast_content_msg = await _extract_content_from_message(
|
|
||||||
raw_quoted_content, bot
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.debug("未检测到命令参数和引用消息", "广播", session=session)
|
|
||||||
await MessageUtils.build_message("请提供广播内容或引用要广播的消息").send(
|
|
||||||
reply_to=True
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
if not broadcast_content_msg:
|
|
||||||
logger.error(
|
|
||||||
"未能从命令参数或引用消息中获取有效的广播内容", "广播", session=session
|
|
||||||
)
|
|
||||||
await MessageUtils.build_message("错误:未能获取有效的广播内容。").send(
|
|
||||||
reply_to=True
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
return broadcast_content_msg
|
|
||||||
|
|
||||||
|
|
||||||
async def _process_v11_segment(
|
|
||||||
seg_obj: V11MessageSegment | dict, depth: int, index: int, bot: Bot
|
|
||||||
) -> list[alc.Segment]:
|
|
||||||
"""处理V11消息段"""
|
|
||||||
result = []
|
|
||||||
seg_type = None
|
|
||||||
data_dict = None
|
|
||||||
|
|
||||||
if isinstance(seg_obj, V11MessageSegment):
|
|
||||||
seg_type = seg_obj.type
|
|
||||||
data_dict = seg_obj.data
|
|
||||||
elif isinstance(seg_obj, dict):
|
|
||||||
seg_type = seg_obj.get("type")
|
|
||||||
data_dict = seg_obj.get("data")
|
|
||||||
else:
|
|
||||||
return result
|
|
||||||
|
|
||||||
if not (seg_type and data_dict is not None):
|
|
||||||
logger.warning(f"[D{depth}] 跳过无效数据: {type(seg_obj)}", "广播")
|
|
||||||
return result
|
|
||||||
|
|
||||||
if seg_type == "text":
|
|
||||||
text_content = data_dict.get("text", "")
|
|
||||||
if isinstance(text_content, str) and text_content.strip():
|
|
||||||
result.append(Text(text_content))
|
|
||||||
elif seg_type == "image":
|
|
||||||
img_seg = None
|
|
||||||
if data_dict.get("url"):
|
|
||||||
img_seg = Image(url=data_dict["url"])
|
|
||||||
elif data_dict.get("file"):
|
|
||||||
file_val = data_dict["file"]
|
|
||||||
if isinstance(file_val, str) and file_val.startswith("base64://"):
|
|
||||||
b64_data = file_val[9:]
|
|
||||||
raw_bytes = base64.b64decode(b64_data)
|
|
||||||
img_seg = Image(raw=raw_bytes)
|
|
||||||
else:
|
|
||||||
img_seg = Image(path=file_val)
|
|
||||||
if img_seg:
|
|
||||||
result.append(img_seg)
|
|
||||||
else:
|
|
||||||
logger.warning(f"[Depth {depth}] V11 图片 {index} 缺少URL/文件", "广播")
|
|
||||||
elif seg_type == "at":
|
|
||||||
target_qq = data_dict.get("qq", "")
|
|
||||||
if target_qq.lower() == "all":
|
|
||||||
result.append(AtAll())
|
|
||||||
elif target_qq:
|
|
||||||
result.append(At(flag="user", target=target_qq))
|
|
||||||
elif seg_type == "video":
|
|
||||||
video_seg = None
|
|
||||||
if data_dict.get("url"):
|
|
||||||
video_seg = Video(url=data_dict["url"])
|
|
||||||
elif data_dict.get("file"):
|
|
||||||
file_val = data_dict["file"]
|
|
||||||
if isinstance(file_val, str) and file_val.startswith("base64://"):
|
|
||||||
b64_data = file_val[9:]
|
|
||||||
raw_bytes = base64.b64decode(b64_data)
|
|
||||||
video_seg = Video(raw=raw_bytes)
|
|
||||||
else:
|
|
||||||
video_seg = Video(path=file_val)
|
|
||||||
if video_seg:
|
|
||||||
result.append(video_seg)
|
|
||||||
logger.debug(f"[Depth {depth}] 处理视频消息成功", "广播")
|
|
||||||
else:
|
|
||||||
logger.warning(f"[Depth {depth}] V11 视频 {index} 缺少URL/文件", "广播")
|
|
||||||
elif seg_type == "forward":
|
|
||||||
nested_forward_id = data_dict.get("id") or data_dict.get("resid")
|
|
||||||
nested_forward_content = data_dict.get("content")
|
|
||||||
|
|
||||||
logger.debug(f"[D{depth}] 嵌套转发ID: {nested_forward_id}", "广播")
|
|
||||||
|
|
||||||
nested_nodes = await _process_forward_content(
|
|
||||||
nested_forward_content, nested_forward_id, bot, depth
|
|
||||||
)
|
|
||||||
|
|
||||||
if nested_nodes:
|
|
||||||
result.append(Reference(nodes=nested_nodes))
|
|
||||||
else:
|
|
||||||
logger.warning(f"[D{depth}] 跳过类型: {seg_type}", "广播")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
async def _extract_content_from_message(
|
|
||||||
message_content: Any, bot: Bot, depth: int = 0
|
|
||||||
) -> UniMessage:
|
|
||||||
"""提取消息内容到UniMessage"""
|
|
||||||
temp_msg = UniMessage()
|
|
||||||
input_type_str = str(type(message_content))
|
|
||||||
|
|
||||||
if depth >= MAX_FORWARD_DEPTH:
|
|
||||||
logger.warning(
|
|
||||||
f"[Depth {depth}] 达到最大递归深度 {MAX_FORWARD_DEPTH},停止解析嵌套转发。",
|
|
||||||
"广播",
|
|
||||||
)
|
|
||||||
temp_msg.append(Text("[嵌套转发层数过多,内容已省略]"))
|
|
||||||
return temp_msg
|
|
||||||
|
|
||||||
segments_to_process = []
|
|
||||||
|
|
||||||
if isinstance(message_content, UniMessage):
|
|
||||||
segments_to_process = list(message_content)
|
|
||||||
elif isinstance(message_content, V11Message):
|
|
||||||
segments_to_process = list(message_content)
|
|
||||||
elif isinstance(message_content, list):
|
|
||||||
segments_to_process = message_content
|
|
||||||
elif (
|
|
||||||
isinstance(message_content, dict)
|
|
||||||
and "type" in message_content
|
|
||||||
and "data" in message_content
|
|
||||||
):
|
|
||||||
segments_to_process = [message_content]
|
|
||||||
elif isinstance(message_content, str):
|
|
||||||
if message_content.strip():
|
|
||||||
temp_msg.append(Text(message_content))
|
|
||||||
return temp_msg
|
|
||||||
else:
|
|
||||||
logger.warning(f"[Depth {depth}] 无法处理的输入类型: {input_type_str}", "广播")
|
|
||||||
return temp_msg
|
|
||||||
|
|
||||||
if segments_to_process:
|
|
||||||
for index, seg_obj in enumerate(segments_to_process):
|
|
||||||
try:
|
|
||||||
if isinstance(seg_obj, Text):
|
|
||||||
text_content = getattr(seg_obj, "text", None)
|
|
||||||
if isinstance(text_content, str) and text_content.strip():
|
|
||||||
temp_msg.append(seg_obj)
|
|
||||||
elif isinstance(seg_obj, Image):
|
|
||||||
if (
|
|
||||||
getattr(seg_obj, "url", None)
|
|
||||||
or getattr(seg_obj, "path", None)
|
|
||||||
or getattr(seg_obj, "raw", None)
|
|
||||||
):
|
|
||||||
temp_msg.append(seg_obj)
|
|
||||||
elif isinstance(seg_obj, At):
|
|
||||||
temp_msg.append(seg_obj)
|
|
||||||
elif isinstance(seg_obj, AtAll):
|
|
||||||
temp_msg.append(seg_obj)
|
|
||||||
elif isinstance(seg_obj, Video):
|
|
||||||
if (
|
|
||||||
getattr(seg_obj, "url", None)
|
|
||||||
or getattr(seg_obj, "path", None)
|
|
||||||
or getattr(seg_obj, "raw", None)
|
|
||||||
):
|
|
||||||
temp_msg.append(seg_obj)
|
|
||||||
logger.debug(f"[D{depth}] 处理Video对象成功", "广播")
|
|
||||||
else:
|
|
||||||
processed_segments = await _process_v11_segment(
|
|
||||||
seg_obj, depth, index, bot
|
|
||||||
)
|
|
||||||
temp_msg.extend(processed_segments)
|
|
||||||
except Exception as e_conv_seg:
|
|
||||||
logger.warning(
|
|
||||||
f"[D{depth}] 处理段 {index} 出错: {e_conv_seg}",
|
|
||||||
"广播",
|
|
||||||
e=e_conv_seg,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not temp_msg and message_content:
|
|
||||||
logger.warning(f"未能从类型 {input_type_str} 中提取内容", "广播")
|
|
||||||
|
|
||||||
return temp_msg
|
|
||||||
|
|
||||||
|
|
||||||
async def get_broadcast_target_groups(
|
|
||||||
bot: Bot, session: EventSession
|
|
||||||
) -> tuple[list, list]:
|
|
||||||
"""获取广播目标群组和启用了广播功能的群组"""
|
|
||||||
target_groups = []
|
|
||||||
all_groups, _ = await BroadcastManager.get_all_groups(bot)
|
|
||||||
|
|
||||||
current_group_id = None
|
|
||||||
if hasattr(session, "id2") and session.id2:
|
|
||||||
current_group_id = session.id2
|
|
||||||
|
|
||||||
if current_group_id:
|
|
||||||
target_groups = [
|
|
||||||
group for group in all_groups if group.group_id != current_group_id
|
|
||||||
]
|
|
||||||
logger.info(
|
|
||||||
f"向除当前群组({current_group_id})外的所有群组广播", "广播", session=session
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
target_groups = all_groups
|
|
||||||
logger.info("向所有群组广播", "广播", session=session)
|
|
||||||
|
|
||||||
if not target_groups:
|
|
||||||
await MessageUtils.build_message("没有找到符合条件的广播目标群组。").send(
|
|
||||||
reply_to=True
|
|
||||||
)
|
|
||||||
return [], []
|
|
||||||
|
|
||||||
enabled_groups = []
|
|
||||||
for group in target_groups:
|
|
||||||
if not await CommonUtils.task_is_block(bot, "broadcast", group.group_id):
|
|
||||||
enabled_groups.append(group)
|
|
||||||
|
|
||||||
if not enabled_groups:
|
|
||||||
await MessageUtils.build_message(
|
|
||||||
"没有启用了广播功能的目标群组可供立即发送。"
|
|
||||||
).send(reply_to=True)
|
|
||||||
return target_groups, []
|
|
||||||
|
|
||||||
return target_groups, enabled_groups
|
|
||||||
|
|
||||||
|
|
||||||
async def send_broadcast_and_notify(
|
|
||||||
bot: Bot,
|
|
||||||
event: Event,
|
|
||||||
message: UniMessage,
|
|
||||||
enabled_groups: list,
|
|
||||||
target_groups: list,
|
|
||||||
session: EventSession,
|
|
||||||
) -> None:
|
|
||||||
"""发送广播并通知结果"""
|
|
||||||
BroadcastManager.clear_last_broadcast_msg_ids()
|
|
||||||
count, error_count = await BroadcastManager.send_to_specific_groups(
|
|
||||||
bot, message, enabled_groups, session
|
|
||||||
)
|
|
||||||
|
|
||||||
result = f"成功广播 {count} 个群组"
|
|
||||||
if error_count:
|
|
||||||
result += f"\n发送失败 {error_count} 个群组"
|
|
||||||
result += f"\n有效: {len(enabled_groups)} / 总计: {len(target_groups)}"
|
|
||||||
|
|
||||||
user_id = str(event.get_user_id())
|
|
||||||
await bot.send_private_msg(user_id=user_id, message=f"发送广播完成!\n{result}")
|
|
||||||
|
|
||||||
BroadcastManager.log_info(
|
|
||||||
f"广播完成,有效/总计: {len(enabled_groups)}/{len(target_groups)}",
|
|
||||||
session,
|
|
||||||
)
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from nonebot_plugin_alconna import UniMessage
|
|
||||||
|
|
||||||
from zhenxun.models.group_console import GroupConsole
|
|
||||||
|
|
||||||
GroupKey = str
|
|
||||||
MessageID = int
|
|
||||||
BroadcastResult = tuple[int, int]
|
|
||||||
BroadcastDetailResult = tuple[int, int, int]
|
|
||||||
|
|
||||||
|
|
||||||
class BroadcastTarget:
|
|
||||||
"""广播目标"""
|
|
||||||
|
|
||||||
def __init__(self, group_id: str, channel_id: str | None = None):
|
|
||||||
self.group_id = group_id
|
|
||||||
self.channel_id = channel_id
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, str | None]:
|
|
||||||
"""转换为字典格式"""
|
|
||||||
return {"group_id": self.group_id, "channel_id": self.channel_id}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_group_console(cls, group: GroupConsole) -> "BroadcastTarget":
|
|
||||||
"""从 GroupConsole 对象创建"""
|
|
||||||
return cls(group_id=group.group_id, channel_id=group.channel_id)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def key(self) -> str:
|
|
||||||
"""获取群组的唯一标识"""
|
|
||||||
if self.channel_id:
|
|
||||||
return f"{self.group_id}:{self.channel_id}"
|
|
||||||
return str(self.group_id)
|
|
||||||
|
|
||||||
|
|
||||||
class BroadcastTask:
|
|
||||||
"""广播任务"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
bot_id: str,
|
|
||||||
message: UniMessage,
|
|
||||||
targets: list[BroadcastTarget],
|
|
||||||
scheduled_time: datetime | None = None,
|
|
||||||
task_id: str | None = None,
|
|
||||||
):
|
|
||||||
self.bot_id = bot_id
|
|
||||||
self.message = message
|
|
||||||
self.targets = targets
|
|
||||||
self.scheduled_time = scheduled_time
|
|
||||||
self.task_id = task_id
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
|
||||||
"""转换为字典格式,用于序列化"""
|
|
||||||
return {
|
|
||||||
"bot_id": self.bot_id,
|
|
||||||
"targets": [t.to_dict() for t in self.targets],
|
|
||||||
"scheduled_time": self.scheduled_time.isoformat()
|
|
||||||
if self.scheduled_time
|
|
||||||
else None,
|
|
||||||
"task_id": self.task_id,
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
import base64
|
|
||||||
|
|
||||||
import nonebot_plugin_alconna as alc
|
|
||||||
from nonebot_plugin_alconna import UniMessage
|
|
||||||
from nonebot_plugin_alconna.uniseg import Reference
|
|
||||||
from nonebot_plugin_alconna.uniseg.segment import CustomNode, Video
|
|
||||||
|
|
||||||
from zhenxun.services.log import logger
|
|
||||||
|
|
||||||
|
|
||||||
def uni_segment_to_v11_segment_dict(
|
|
||||||
seg: alc.Segment, depth: int = 0
|
|
||||||
) -> dict | list[dict] | None:
|
|
||||||
"""UniSeg段转V11字典"""
|
|
||||||
if isinstance(seg, alc.Text):
|
|
||||||
return {"type": "text", "data": {"text": seg.text}}
|
|
||||||
elif isinstance(seg, alc.Image):
|
|
||||||
if getattr(seg, "url", None):
|
|
||||||
return {
|
|
||||||
"type": "image",
|
|
||||||
"data": {"file": seg.url},
|
|
||||||
}
|
|
||||||
elif getattr(seg, "raw", None):
|
|
||||||
raw_data = seg.raw
|
|
||||||
if isinstance(raw_data, str):
|
|
||||||
if len(raw_data) >= 9 and raw_data[:9] == "base64://":
|
|
||||||
return {"type": "image", "data": {"file": raw_data}}
|
|
||||||
elif isinstance(raw_data, bytes):
|
|
||||||
b64_str = base64.b64encode(raw_data).decode()
|
|
||||||
return {"type": "image", "data": {"file": f"base64://{b64_str}"}}
|
|
||||||
else:
|
|
||||||
logger.warning(f"无法处理 Image.raw 的类型: {type(raw_data)}", "广播")
|
|
||||||
elif getattr(seg, "path", None):
|
|
||||||
logger.warning(
|
|
||||||
f"在合并转发中使用了本地图片路径,可能无法显示: {seg.path}", "广播"
|
|
||||||
)
|
|
||||||
return {"type": "image", "data": {"file": f"file:///{seg.path}"}}
|
|
||||||
else:
|
|
||||||
logger.warning(f"alc.Image 缺少有效数据,无法转换为 V11 段: {seg}", "广播")
|
|
||||||
elif isinstance(seg, alc.At):
|
|
||||||
return {"type": "at", "data": {"qq": seg.target}}
|
|
||||||
elif isinstance(seg, alc.AtAll):
|
|
||||||
return {"type": "at", "data": {"qq": "all"}}
|
|
||||||
elif isinstance(seg, Video):
|
|
||||||
if getattr(seg, "url", None):
|
|
||||||
return {
|
|
||||||
"type": "video",
|
|
||||||
"data": {"file": seg.url},
|
|
||||||
}
|
|
||||||
elif getattr(seg, "raw", None):
|
|
||||||
raw_data = seg.raw
|
|
||||||
if isinstance(raw_data, str):
|
|
||||||
if len(raw_data) >= 9 and raw_data[:9] == "base64://":
|
|
||||||
return {"type": "video", "data": {"file": raw_data}}
|
|
||||||
elif isinstance(raw_data, bytes):
|
|
||||||
b64_str = base64.b64encode(raw_data).decode()
|
|
||||||
return {"type": "video", "data": {"file": f"base64://{b64_str}"}}
|
|
||||||
else:
|
|
||||||
logger.warning(f"无法处理 Video.raw 的类型: {type(raw_data)}", "广播")
|
|
||||||
elif getattr(seg, "path", None):
|
|
||||||
logger.warning(
|
|
||||||
f"在合并转发中使用了本地视频路径,可能无法显示: {seg.path}", "广播"
|
|
||||||
)
|
|
||||||
return {"type": "video", "data": {"file": f"file:///{seg.path}"}}
|
|
||||||
else:
|
|
||||||
logger.warning(f"Video 缺少有效数据,无法转换为 V11 段: {seg}", "广播")
|
|
||||||
elif isinstance(seg, Reference) and getattr(seg, "nodes", None):
|
|
||||||
if depth >= 3:
|
|
||||||
logger.warning(
|
|
||||||
f"嵌套转发深度超过限制 (depth={depth}),不再继续解析", "广播"
|
|
||||||
)
|
|
||||||
return {"type": "text", "data": {"text": "[嵌套转发层数过多,内容已省略]"}}
|
|
||||||
|
|
||||||
nested_v11_content_list = []
|
|
||||||
nodes_list = getattr(seg, "nodes", [])
|
|
||||||
for node in nodes_list:
|
|
||||||
if isinstance(node, CustomNode):
|
|
||||||
node_v11_content = []
|
|
||||||
if isinstance(node.content, UniMessage):
|
|
||||||
for nested_seg in node.content:
|
|
||||||
converted_dict = uni_segment_to_v11_segment_dict(
|
|
||||||
nested_seg, depth + 1
|
|
||||||
)
|
|
||||||
if isinstance(converted_dict, list):
|
|
||||||
node_v11_content.extend(converted_dict)
|
|
||||||
elif converted_dict:
|
|
||||||
node_v11_content.append(converted_dict)
|
|
||||||
elif isinstance(node.content, str):
|
|
||||||
node_v11_content.append(
|
|
||||||
{"type": "text", "data": {"text": node.content}}
|
|
||||||
)
|
|
||||||
if node_v11_content:
|
|
||||||
separator = {
|
|
||||||
"type": "text",
|
|
||||||
"data": {
|
|
||||||
"text": f"\n--- 来自 {node.name} ({node.uid}) 的消息 ---\n"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
nested_v11_content_list.insert(0, separator)
|
|
||||||
nested_v11_content_list.extend(node_v11_content)
|
|
||||||
nested_v11_content_list.append(
|
|
||||||
{"type": "text", "data": {"text": "\n---\n"}}
|
|
||||||
)
|
|
||||||
|
|
||||||
return nested_v11_content_list
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.warning(f"广播时跳过不支持的 UniSeg 段类型: {type(seg)}", "广播")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def uni_message_to_v11_list_of_dicts(uni_msg: UniMessage | str | list) -> list[dict]:
|
|
||||||
"""UniMessage转V11字典列表"""
|
|
||||||
try:
|
|
||||||
if isinstance(uni_msg, str):
|
|
||||||
return [{"type": "text", "data": {"text": uni_msg}}]
|
|
||||||
|
|
||||||
if isinstance(uni_msg, list):
|
|
||||||
if not uni_msg:
|
|
||||||
return []
|
|
||||||
|
|
||||||
if all(isinstance(item, str) for item in uni_msg):
|
|
||||||
return [{"type": "text", "data": {"text": item}} for item in uni_msg]
|
|
||||||
|
|
||||||
result = []
|
|
||||||
for item in uni_msg:
|
|
||||||
if hasattr(item, "__iter__") and not isinstance(item, str | bytes):
|
|
||||||
result.extend(uni_message_to_v11_list_of_dicts(item))
|
|
||||||
elif hasattr(item, "text") and not isinstance(item, str | bytes):
|
|
||||||
text_value = getattr(item, "text", "")
|
|
||||||
result.append({"type": "text", "data": {"text": str(text_value)}})
|
|
||||||
elif hasattr(item, "url") and not isinstance(item, str | bytes):
|
|
||||||
url_value = getattr(item, "url", "")
|
|
||||||
if isinstance(item, Video):
|
|
||||||
result.append(
|
|
||||||
{"type": "video", "data": {"file": str(url_value)}}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
result.append(
|
|
||||||
{"type": "image", "data": {"file": str(url_value)}}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
result.append({"type": "text", "data": {"text": str(item)}})
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"无法转换列表元素: {item}, 错误: {e}", "广播")
|
|
||||||
return result
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"消息转换过程中出错: {e}", "广播")
|
|
||||||
|
|
||||||
return [{"type": "text", "data": {"text": str(uni_msg)}}]
|
|
||||||
|
|
||||||
|
|
||||||
def custom_nodes_to_v11_nodes(custom_nodes: list[CustomNode]) -> list[dict]:
|
|
||||||
"""CustomNode列表转V11节点"""
|
|
||||||
v11_nodes = []
|
|
||||||
for node in custom_nodes:
|
|
||||||
v11_content_list = uni_message_to_v11_list_of_dicts(node.content)
|
|
||||||
|
|
||||||
if v11_content_list:
|
|
||||||
v11_nodes.append(
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"data": {
|
|
||||||
"user_id": str(node.uid),
|
|
||||||
"nickname": node.name,
|
|
||||||
"content": v11_content_list,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
f"CustomNode (uid={node.uid}) 内容转换后为空,跳过此节点", "广播"
|
|
||||||
)
|
|
||||||
return v11_nodes
|
|
||||||
@@ -2,7 +2,7 @@ from io import BytesIO
|
|||||||
|
|
||||||
from arclet.alconna import Args, Option
|
from arclet.alconna import Args, Option
|
||||||
from arclet.alconna.typing import CommandMeta
|
from arclet.alconna.typing import CommandMeta
|
||||||
from nonebot.adapters import Bot, Event
|
from nonebot.adapters import Bot
|
||||||
from nonebot.permission import SUPERUSER
|
from nonebot.permission import SUPERUSER
|
||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot.rule import to_me
|
from nonebot.rule import to_me
|
||||||
@@ -10,13 +10,10 @@ from nonebot_plugin_alconna import (
|
|||||||
Alconna,
|
Alconna,
|
||||||
AlconnaQuery,
|
AlconnaQuery,
|
||||||
Arparma,
|
Arparma,
|
||||||
Match,
|
|
||||||
Query,
|
Query,
|
||||||
Reply,
|
|
||||||
on_alconna,
|
on_alconna,
|
||||||
store_true,
|
store_true,
|
||||||
)
|
)
|
||||||
from nonebot_plugin_alconna.uniseg.tools import reply_fetch
|
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_session import EventSession
|
||||||
|
|
||||||
from zhenxun.configs.config import BotConfig
|
from zhenxun.configs.config import BotConfig
|
||||||
@@ -57,7 +54,7 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
_req_matcher = on_alconna(
|
_req_matcher = on_alconna(
|
||||||
Alconna(
|
Alconna(
|
||||||
"请求处理",
|
"请求处理",
|
||||||
Args["handle", ["-fa", "-fr", "-fi", "-ga", "-gr", "-gi"]]["id?", int],
|
Args["handle", ["-fa", "-fr", "-fi", "-ga", "-gr", "-gi"]]["id", int],
|
||||||
meta=CommandMeta(
|
meta=CommandMeta(
|
||||||
description="好友/群组请求处理",
|
description="好友/群组请求处理",
|
||||||
usage=usage,
|
usage=usage,
|
||||||
@@ -108,12 +105,12 @@ _clear_matcher = on_alconna(
|
|||||||
)
|
)
|
||||||
|
|
||||||
reg_arg_list = [
|
reg_arg_list = [
|
||||||
(r"同意好友请求\s*(?P<id>\d*)", ["-fa", "{id}"]),
|
(r"同意好友请求", ["-fa", "{%0}"]),
|
||||||
(r"拒绝好友请求\s*(?P<id>\d*)", ["-fr", "{id}"]),
|
(r"拒绝好友请求", ["-fr", "{%0}"]),
|
||||||
(r"忽略好友请求\s*(?P<id>\d*)", ["-fi", "{id}"]),
|
(r"忽略好友请求", ["-fi", "{%0}"]),
|
||||||
(r"同意群组请求\s*(?P<id>\d*)", ["-ga", "{id}"]),
|
(r"同意群组请求", ["-ga", "{%0}"]),
|
||||||
(r"拒绝群组请求\s*(?P<id>\d*)", ["-gr", "{id}"]),
|
(r"拒绝群组请求", ["-gr", "{%0}"]),
|
||||||
(r"忽略群组请求\s*(?P<id>\d*)", ["-gi", "{id}"]),
|
(r"忽略群组请求", ["-gi", "{%0}"]),
|
||||||
]
|
]
|
||||||
|
|
||||||
for r in reg_arg_list:
|
for r in reg_arg_list:
|
||||||
@@ -128,48 +125,32 @@ for r in reg_arg_list:
|
|||||||
@_req_matcher.handle()
|
@_req_matcher.handle()
|
||||||
async def _(
|
async def _(
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
event: Event,
|
|
||||||
session: EventSession,
|
session: EventSession,
|
||||||
handle: str,
|
handle: str,
|
||||||
id: Match[int],
|
id: int,
|
||||||
arparma: Arparma,
|
arparma: Arparma,
|
||||||
):
|
):
|
||||||
reply: Reply | None = None
|
|
||||||
type_dict = {
|
type_dict = {
|
||||||
"a": RequestHandleType.APPROVE,
|
"a": RequestHandleType.APPROVE,
|
||||||
"r": RequestHandleType.REFUSED,
|
"r": RequestHandleType.REFUSED,
|
||||||
"i": RequestHandleType.IGNORE,
|
"i": RequestHandleType.IGNORE,
|
||||||
}
|
}
|
||||||
if not id.available:
|
|
||||||
reply = await reply_fetch(event, bot)
|
|
||||||
if not reply:
|
|
||||||
await MessageUtils.build_message("请引用消息处理或添加处理Id.").finish()
|
|
||||||
handle_id = id.result
|
|
||||||
if reply:
|
|
||||||
db_data = await FgRequest.get_or_none(message_ids__contains=reply.id)
|
|
||||||
if not db_data:
|
|
||||||
await MessageUtils.build_message(
|
|
||||||
"未发现此消息的Id,请使用Id进行处理..."
|
|
||||||
).finish(reply_to=True)
|
|
||||||
handle_id = db_data.id
|
|
||||||
req = None
|
req = None
|
||||||
handle_type = type_dict[handle[-1]]
|
handle_type = type_dict[handle[-1]]
|
||||||
try:
|
try:
|
||||||
if handle_type == RequestHandleType.APPROVE:
|
if handle_type == RequestHandleType.APPROVE:
|
||||||
req = await FgRequest.approve(bot, handle_id)
|
req = await FgRequest.approve(bot, id)
|
||||||
if handle_type == RequestHandleType.REFUSED:
|
if handle_type == RequestHandleType.REFUSED:
|
||||||
req = await FgRequest.refused(bot, handle_id)
|
req = await FgRequest.refused(bot, id)
|
||||||
if handle_type == RequestHandleType.IGNORE:
|
if handle_type == RequestHandleType.IGNORE:
|
||||||
req = await FgRequest.ignore(handle_id)
|
req = await FgRequest.ignore(id)
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
await MessageUtils.build_message("未发现此id的请求...").finish(reply_to=True)
|
await MessageUtils.build_message("未发现此id的请求...").finish(reply_to=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
await MessageUtils.build_message("其他错误, 可能flag已失效...").finish(
|
await MessageUtils.build_message("其他错误, 可能flag已失效...").finish(
|
||||||
reply_to=True
|
reply_to=True
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info("处理请求", arparma.header_result, session=session)
|
||||||
f"处理请求 Id: {req.id if req else ''}", arparma.header_result, session=session
|
|
||||||
)
|
|
||||||
await MessageUtils.build_message("成功处理请求!").send(reply_to=True)
|
await MessageUtils.build_message("成功处理请求!").send(reply_to=True)
|
||||||
if req and handle_type == RequestHandleType.APPROVE:
|
if req and handle_type == RequestHandleType.APPROVE:
|
||||||
await bot.send_private_msg(
|
await bot.send_private_msg(
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ from zhenxun.configs.config import Config as gConfig
|
|||||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||||
from zhenxun.services.log import logger, logger_
|
from zhenxun.services.log import logger, logger_
|
||||||
from zhenxun.utils.enum import PluginType
|
from zhenxun.utils.enum import PluginType
|
||||||
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
|
||||||
|
|
||||||
from .api.configure import router as configure_router
|
|
||||||
from .api.logs import router as ws_log_routes
|
from .api.logs import router as ws_log_routes
|
||||||
from .api.logs.log_manager import LOG_STORAGE
|
from .api.logs.log_manager import LOG_STORAGE
|
||||||
from .api.menu import router as menu_router
|
from .api.menu import router as menu_router
|
||||||
@@ -31,7 +29,8 @@ from .public import init_public
|
|||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="WebUi",
|
name="WebUi",
|
||||||
description="WebUi API",
|
description="WebUi API",
|
||||||
usage='"""\n """.strip(),',
|
usage="""
|
||||||
|
""".strip(),
|
||||||
extra=PluginExtraData(
|
extra=PluginExtraData(
|
||||||
author="HibiKier",
|
author="HibiKier",
|
||||||
version="0.1",
|
version="0.1",
|
||||||
@@ -83,7 +82,7 @@ BaseApiRouter.include_router(database_router)
|
|||||||
BaseApiRouter.include_router(plugin_router)
|
BaseApiRouter.include_router(plugin_router)
|
||||||
BaseApiRouter.include_router(system_router)
|
BaseApiRouter.include_router(system_router)
|
||||||
BaseApiRouter.include_router(menu_router)
|
BaseApiRouter.include_router(menu_router)
|
||||||
BaseApiRouter.include_router(configure_router)
|
|
||||||
|
|
||||||
WsApiRouter = APIRouter(prefix="/zhenxun/socket")
|
WsApiRouter = APIRouter(prefix="/zhenxun/socket")
|
||||||
|
|
||||||
@@ -92,11 +91,9 @@ WsApiRouter.include_router(status_routes)
|
|||||||
WsApiRouter.include_router(chat_routes)
|
WsApiRouter.include_router(chat_routes)
|
||||||
|
|
||||||
|
|
||||||
@PriorityLifecycle.on_startup(priority=0)
|
@driver.on_startup
|
||||||
async def _():
|
async def _():
|
||||||
try:
|
try:
|
||||||
# 存储任务引用的列表,防止任务被垃圾回收
|
|
||||||
_tasks = []
|
|
||||||
|
|
||||||
async def log_sink(message: str):
|
async def log_sink(message: str):
|
||||||
loop = None
|
loop = None
|
||||||
@@ -107,8 +104,7 @@ async def _():
|
|||||||
logger.warning("Web Ui log_sink", e=e)
|
logger.warning("Web Ui log_sink", e=e)
|
||||||
if not loop:
|
if not loop:
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
# 存储任务引用到外部列表中
|
loop.create_task(LOG_STORAGE.add(message.rstrip("\n"))) # noqa: RUF006
|
||||||
_tasks.append(loop.create_task(LOG_STORAGE.add(message.rstrip("\n"))))
|
|
||||||
|
|
||||||
logger_.add(
|
logger_.add(
|
||||||
log_sink, colorize=True, filter=default_filter, format=default_format
|
log_sink, colorize=True, filter=default_filter, format=default_format
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
import nonebot
|
|
||||||
|
|
||||||
from zhenxun.configs.config import BotConfig, Config
|
|
||||||
|
|
||||||
from ...base_model import Result
|
|
||||||
from .data_source import test_db_connection
|
|
||||||
from .model import Setting
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/configure")
|
|
||||||
|
|
||||||
driver = nonebot.get_driver()
|
|
||||||
|
|
||||||
port = driver.config.port
|
|
||||||
|
|
||||||
BAT_FILE = Path() / "win启动.bat"
|
|
||||||
|
|
||||||
FILE_NAME = ".configure_restart"
|
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
|
||||||
"/set_configure",
|
|
||||||
response_model=Result,
|
|
||||||
response_class=JSONResponse,
|
|
||||||
description="设置基础配置",
|
|
||||||
)
|
|
||||||
async def _(setting: Setting) -> Result:
|
|
||||||
global port
|
|
||||||
password = Config.get_config("web-ui", "password")
|
|
||||||
if password or BotConfig.db_url:
|
|
||||||
return Result.fail("配置已存在,请先删除DB_URL内容和前端密码再进行设置。")
|
|
||||||
env_file = Path() / ".env.dev"
|
|
||||||
if not env_file.exists():
|
|
||||||
return Result.fail("配置文件.env.dev不存在。")
|
|
||||||
env_text = env_file.read_text(encoding="utf-8")
|
|
||||||
if setting.db_url:
|
|
||||||
if setting.db_url.startswith("sqlite"):
|
|
||||||
base_dir = Path().resolve()
|
|
||||||
# 清理和验证数据库路径
|
|
||||||
db_path_str = setting.db_url.split(":")[-1].strip()
|
|
||||||
# 移除任何可能的路径遍历尝试
|
|
||||||
db_path_str = re.sub(r"[\\/]\.\.[\\/]", "", db_path_str)
|
|
||||||
# 规范化路径
|
|
||||||
db_path = Path(db_path_str).resolve()
|
|
||||||
parent_path = db_path.parent
|
|
||||||
|
|
||||||
# 验证路径是否在项目根目录内
|
|
||||||
try:
|
|
||||||
if not parent_path.absolute().is_relative_to(base_dir):
|
|
||||||
return Result.fail("数据库路径不在项目根目录内。")
|
|
||||||
except ValueError:
|
|
||||||
return Result.fail("无效的数据库路径。")
|
|
||||||
|
|
||||||
# 创建目录
|
|
||||||
try:
|
|
||||||
parent_path.mkdir(parents=True, exist_ok=True)
|
|
||||||
except Exception as e:
|
|
||||||
return Result.fail(f"创建数据库目录失败: {e!s}")
|
|
||||||
|
|
||||||
env_text = env_text.replace('DB_URL = ""', f'DB_URL = "{setting.db_url}"')
|
|
||||||
if setting.superusers:
|
|
||||||
superusers = ", ".join([f'"{s}"' for s in setting.superusers])
|
|
||||||
env_text = re.sub(r"SUPERUSERS=\[.*?\]", f"SUPERUSERS=[{superusers}]", env_text)
|
|
||||||
if setting.host:
|
|
||||||
env_text = env_text.replace("HOST = 127.0.0.1", f"HOST = {setting.host}")
|
|
||||||
if setting.port:
|
|
||||||
env_text = env_text.replace("PORT = 8080", f"PORT = {setting.port}")
|
|
||||||
port = setting.port
|
|
||||||
if setting.username:
|
|
||||||
Config.set_config("web-ui", "username", setting.username)
|
|
||||||
Config.set_config("web-ui", "password", setting.password, True)
|
|
||||||
env_file.write_text(env_text, encoding="utf-8")
|
|
||||||
if BAT_FILE.exists():
|
|
||||||
for file in os.listdir(Path()):
|
|
||||||
if file.startswith(FILE_NAME):
|
|
||||||
Path(file).unlink()
|
|
||||||
flag_file = Path() / f"{FILE_NAME}_{int(time.time())}"
|
|
||||||
flag_file.touch()
|
|
||||||
return Result.ok(BAT_FILE.exists(), info="设置成功,请重启真寻以完成配置!")
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/test_db",
|
|
||||||
response_model=Result,
|
|
||||||
response_class=JSONResponse,
|
|
||||||
description="设置基础配置",
|
|
||||||
)
|
|
||||||
async def _(db_url: str) -> Result:
|
|
||||||
result = await test_db_connection(db_url)
|
|
||||||
if isinstance(result, str):
|
|
||||||
return Result.fail(result)
|
|
||||||
return Result.ok(info="数据库连接成功!")
|
|
||||||
|
|
||||||
|
|
||||||
async def run_restart_command(bat_path: Path, port: int):
|
|
||||||
"""在后台执行重启命令"""
|
|
||||||
await asyncio.sleep(1) # 确保 FastAPI 已返回响应
|
|
||||||
subprocess.Popen([bat_path, str(port)], shell=True) # noqa: ASYNC220
|
|
||||||
sys.exit(0) # 退出当前进程
|
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
|
||||||
"/restart",
|
|
||||||
response_model=Result,
|
|
||||||
response_class=JSONResponse,
|
|
||||||
description="重启",
|
|
||||||
)
|
|
||||||
async def _() -> Result:
|
|
||||||
if not BAT_FILE.exists():
|
|
||||||
return Result.fail("自动重启仅支持意见整合包,请尝试手动重启")
|
|
||||||
flag_file = next(
|
|
||||||
(Path() / file for file in os.listdir(Path()) if file.startswith(FILE_NAME)),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
if not flag_file or not flag_file.exists():
|
|
||||||
return Result.fail("重启标志文件不存在...")
|
|
||||||
set_time = flag_file.name.split("_")[-1]
|
|
||||||
if time.time() - float(set_time) > 10 * 60:
|
|
||||||
return Result.fail("重启标志文件已过期,请重新设置配置。")
|
|
||||||
flag_file.unlink()
|
|
||||||
try:
|
|
||||||
return Result.ok(info="执行重启命令成功")
|
|
||||||
finally:
|
|
||||||
asyncio.create_task(run_restart_command(BAT_FILE, port)) # noqa: RUF006
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
from tortoise import Tortoise
|
|
||||||
|
|
||||||
|
|
||||||
async def test_db_connection(db_url: str) -> bool | str:
|
|
||||||
try:
|
|
||||||
# 初始化 Tortoise ORM
|
|
||||||
await Tortoise.init(
|
|
||||||
db_url=db_url,
|
|
||||||
modules={"models": ["__main__"]}, # 这里不需要实际模型
|
|
||||||
)
|
|
||||||
# 测试连接
|
|
||||||
await Tortoise.get_connection("default").execute_query("SELECT 1")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
return str(e)
|
|
||||||
finally:
|
|
||||||
# 关闭连接
|
|
||||||
await Tortoise.close_connections()
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class Setting(BaseModel):
|
|
||||||
superusers: list[str]
|
|
||||||
"""超级用户列表"""
|
|
||||||
db_url: str
|
|
||||||
"""数据库地址"""
|
|
||||||
host: str
|
|
||||||
"""主机地址"""
|
|
||||||
port: int
|
|
||||||
"""端口"""
|
|
||||||
username: str
|
|
||||||
"""前端用户名"""
|
|
||||||
password: str
|
|
||||||
"""前端密码"""
|
|
||||||
@@ -5,63 +5,51 @@ from zhenxun.services.log import logger
|
|||||||
|
|
||||||
from .model import MenuData, MenuItem
|
from .model import MenuData, MenuItem
|
||||||
|
|
||||||
default_menus = [
|
|
||||||
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"),
|
|
||||||
MenuItem(name="关于我们", module="about", router="/about", icon="about"),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
class MenuManage:
|
||||||
class MenuManager:
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.file = DATA_PATH / "web_ui" / "menu.json"
|
self.file = DATA_PATH / "web_ui" / "menu.json"
|
||||||
self.menu = []
|
self.menu = []
|
||||||
if self.file.exists():
|
if self.file.exists():
|
||||||
try:
|
try:
|
||||||
temp_menu = []
|
|
||||||
self.menu = json.load(self.file.open(encoding="utf8"))
|
self.menu = json.load(self.file.open(encoding="utf8"))
|
||||||
self_menu_name = [menu["name"] for menu in self.menu]
|
|
||||||
for module in [m.module for m in default_menus]:
|
|
||||||
if module in self_menu_name:
|
|
||||||
temp_menu.append(
|
|
||||||
MenuItem(
|
|
||||||
**next(m for m in self.menu if m["module"] == module)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
temp_menu.append(self.__get_menu_model(module))
|
|
||||||
self.menu = temp_menu
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("菜单文件损坏,已重新生成...", "WebUi", e=e)
|
logger.warning("菜单文件损坏,已重新生成...", "WebUi", e=e)
|
||||||
if not self.menu:
|
if not self.menu:
|
||||||
self.menu = default_menus
|
self.menu = [
|
||||||
self.save()
|
MenuItem(
|
||||||
|
name="仪表盘",
|
||||||
def __get_menu_model(self, module: str):
|
module="dashboard",
|
||||||
return default_menus[
|
router="/dashboard",
|
||||||
next(i for i, m in enumerate(default_menus) if m.module == module)
|
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):
|
def get_menus(self):
|
||||||
return MenuData(menus=self.menu)
|
return MenuData(menus=self.menu)
|
||||||
@@ -73,4 +61,4 @@ class MenuManager:
|
|||||||
json.dump(temp, f, ensure_ascii=False, indent=4)
|
json.dump(temp, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
|
||||||
menu_manage = MenuManager()
|
menu_manage = MenuManage()
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from zhenxun.models.bot_connect_log import BotConnectLog
|
|||||||
from zhenxun.models.chat_history import ChatHistory
|
from zhenxun.models.chat_history import ChatHistory
|
||||||
from zhenxun.models.statistics import Statistics
|
from zhenxun.models.statistics import Statistics
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
from zhenxun.utils.platform import PlatformUtils
|
||||||
|
|
||||||
from ....base_model import BaseResultModel, QueryModel
|
from ....base_model import BaseResultModel, QueryModel
|
||||||
@@ -32,7 +31,7 @@ driver: Driver = nonebot.get_driver()
|
|||||||
CONNECT_TIME = 0
|
CONNECT_TIME = 0
|
||||||
|
|
||||||
|
|
||||||
@PriorityLifecycle.on_startup(priority=5)
|
@driver.on_startup
|
||||||
async def _():
|
async def _():
|
||||||
global CONNECT_TIME
|
global CONNECT_TIME
|
||||||
CONNECT_TIME = int(time.time())
|
CONNECT_TIME = int(time.time())
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from zhenxun.configs.config import BotConfig
|
|||||||
from zhenxun.models.plugin_info import PluginInfo
|
from zhenxun.models.plugin_info import PluginInfo
|
||||||
from zhenxun.models.task_info import TaskInfo
|
from zhenxun.models.task_info import TaskInfo
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
|
||||||
|
|
||||||
from ....base_model import BaseResultModel, QueryModel, Result
|
from ....base_model import BaseResultModel, QueryModel, Result
|
||||||
from ....utils import authentication
|
from ....utils import authentication
|
||||||
@@ -22,7 +21,7 @@ router = APIRouter(prefix="/database")
|
|||||||
driver: Driver = nonebot.get_driver()
|
driver: Driver = nonebot.get_driver()
|
||||||
|
|
||||||
|
|
||||||
@PriorityLifecycle.on_startup(priority=5)
|
@driver.on_startup
|
||||||
async def _():
|
async def _():
|
||||||
for plugin in nonebot.get_loaded_plugins():
|
for plugin in nonebot.get_loaded_plugins():
|
||||||
module = plugin.name
|
module = plugin.name
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from zhenxun.utils.platform import PlatformUtils
|
|||||||
|
|
||||||
from ....base_model import Result
|
from ....base_model import Result
|
||||||
from ....config import QueryDateType
|
from ....config import QueryDateType
|
||||||
from ....utils import authentication, clear_help_image, get_system_status
|
from ....utils import authentication, get_system_status
|
||||||
from .data_source import ApiDataSource
|
from .data_source import ApiDataSource
|
||||||
from .model import (
|
from .model import (
|
||||||
ActiveGroup,
|
ActiveGroup,
|
||||||
@@ -234,7 +234,6 @@ async def _(param: BotManageUpdateParam):
|
|||||||
bot_data.block_plugins = CommonUtils.convert_module_format(param.block_plugins)
|
bot_data.block_plugins = CommonUtils.convert_module_format(param.block_plugins)
|
||||||
bot_data.block_tasks = CommonUtils.convert_module_format(param.block_tasks)
|
bot_data.block_tasks = CommonUtils.convert_module_format(param.block_tasks)
|
||||||
await bot_data.save(update_fields=["block_plugins", "block_tasks"])
|
await bot_data.save(update_fields=["block_plugins", "block_tasks"])
|
||||||
clear_help_image()
|
|
||||||
return Result.ok()
|
return Result.ok()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{router.prefix}/update_bot_manage 调用错误", "WebUi", e=e)
|
logger.error(f"{router.prefix}/update_bot_manage 调用错误", "WebUi", e=e)
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class ApiDataSource:
|
|||||||
"""
|
"""
|
||||||
version_file = Path() / "__version__"
|
version_file = Path() / "__version__"
|
||||||
if version_file.exists():
|
if version_file.exists():
|
||||||
if text := version_file.open(encoding="utf-8").read():
|
if text := version_file.open().read():
|
||||||
return text.replace("__version__: ", "").strip()
|
return text.replace("__version__: ", "").strip()
|
||||||
return "unknown"
|
return "unknown"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
import nonebot
|
import nonebot
|
||||||
from nonebot import on_message
|
from nonebot import on_message
|
||||||
@@ -51,14 +49,13 @@ async def message_handle(
|
|||||||
message: UniMsg,
|
message: UniMsg,
|
||||||
group_id: str | None,
|
group_id: str | None,
|
||||||
):
|
):
|
||||||
time = str(datetime.now().replace(microsecond=0))
|
|
||||||
messages = []
|
messages = []
|
||||||
for m in message:
|
for m in message:
|
||||||
if isinstance(m, Text | str):
|
if isinstance(m, Text | str):
|
||||||
messages.append(MessageItem(type="text", msg=str(m), time=time))
|
messages.append(MessageItem(type="text", msg=str(m)))
|
||||||
elif isinstance(m, Image):
|
elif isinstance(m, Image):
|
||||||
if m.url:
|
if m.url:
|
||||||
messages.append(MessageItem(type="img", msg=m.url, time=time))
|
messages.append(MessageItem(type="img", msg=m.url))
|
||||||
elif isinstance(m, At):
|
elif isinstance(m, At):
|
||||||
if group_id:
|
if group_id:
|
||||||
if m.target == "0":
|
if m.target == "0":
|
||||||
@@ -75,9 +72,9 @@ async def message_handle(
|
|||||||
uname = group_user.user_name
|
uname = group_user.user_name
|
||||||
if m.target not in ID2NAME[group_id]:
|
if m.target not in ID2NAME[group_id]:
|
||||||
ID2NAME[group_id][m.target] = uname
|
ID2NAME[group_id][m.target] = uname
|
||||||
messages.append(MessageItem(type="at", msg=f"@{uname}", time=time))
|
messages.append(MessageItem(type="at", msg=f"@{uname}"))
|
||||||
elif isinstance(m, Hyper):
|
elif isinstance(m, Hyper):
|
||||||
messages.append(MessageItem(type="text", msg="[分享消息]", time=time))
|
messages.append(MessageItem(type="text", msg="[分享消息]"))
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -237,8 +237,6 @@ class MessageItem(BaseModel):
|
|||||||
"""消息类型"""
|
"""消息类型"""
|
||||||
msg: str
|
msg: str
|
||||||
"""内容"""
|
"""内容"""
|
||||||
time: str
|
|
||||||
"""发送日期"""
|
|
||||||
|
|
||||||
|
|
||||||
class Message(BaseModel):
|
class Message(BaseModel):
|
||||||
|
|||||||
@@ -6,16 +6,13 @@ from zhenxun.services.log import logger
|
|||||||
from zhenxun.utils.enum import BlockType, PluginType
|
from zhenxun.utils.enum import BlockType, PluginType
|
||||||
|
|
||||||
from ....base_model import Result
|
from ....base_model import Result
|
||||||
from ....utils import authentication, clear_help_image
|
from ....utils import authentication
|
||||||
from .data_source import ApiDataSource
|
from .data_source import ApiDataSource
|
||||||
from .model import (
|
from .model import (
|
||||||
BatchUpdatePlugins,
|
|
||||||
BatchUpdateResult,
|
|
||||||
PluginCount,
|
PluginCount,
|
||||||
PluginDetail,
|
PluginDetail,
|
||||||
PluginInfo,
|
PluginInfo,
|
||||||
PluginSwitch,
|
PluginSwitch,
|
||||||
RenameMenuTypePayload,
|
|
||||||
UpdatePlugin,
|
UpdatePlugin,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,8 +30,9 @@ async def _(
|
|||||||
plugin_type: list[PluginType] = Query(None), menu_type: str | None = None
|
plugin_type: list[PluginType] = Query(None), menu_type: str | None = None
|
||||||
) -> Result[list[PluginInfo]]:
|
) -> Result[list[PluginInfo]]:
|
||||||
try:
|
try:
|
||||||
result = await ApiDataSource.get_plugin_list(plugin_type, menu_type)
|
return Result.ok(
|
||||||
return Result.ok(result, "拿到信息啦!")
|
await ApiDataSource.get_plugin_list(plugin_type, menu_type), "拿到信息啦!"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{router.prefix}/get_plugin_list 调用错误", "WebUi", e=e)
|
logger.error(f"{router.prefix}/get_plugin_list 调用错误", "WebUi", e=e)
|
||||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||||
@@ -80,7 +78,6 @@ async def _() -> Result[PluginCount]:
|
|||||||
async def _(param: UpdatePlugin) -> Result:
|
async def _(param: UpdatePlugin) -> Result:
|
||||||
try:
|
try:
|
||||||
await ApiDataSource.update_plugin(param)
|
await ApiDataSource.update_plugin(param)
|
||||||
clear_help_image()
|
|
||||||
return Result.ok(info="已经帮你写好啦!")
|
return Result.ok(info="已经帮你写好啦!")
|
||||||
except (ValueError, KeyError):
|
except (ValueError, KeyError):
|
||||||
return Result.fail("插件数据不存在...")
|
return Result.fail("插件数据不存在...")
|
||||||
@@ -108,7 +105,6 @@ async def _(param: PluginSwitch) -> Result:
|
|||||||
db_plugin.block_type = None
|
db_plugin.block_type = None
|
||||||
db_plugin.status = True
|
db_plugin.status = True
|
||||||
await db_plugin.save()
|
await db_plugin.save()
|
||||||
clear_help_image()
|
|
||||||
return Result.ok(info="成功改变了开关状态!")
|
return Result.ok(info="成功改变了开关状态!")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{router.prefix}/change_switch 调用错误", "WebUi", e=e)
|
logger.error(f"{router.prefix}/change_switch 调用错误", "WebUi", e=e)
|
||||||
@@ -148,68 +144,11 @@ async def _() -> Result[list[str]]:
|
|||||||
)
|
)
|
||||||
async def _(module: str) -> Result[PluginDetail]:
|
async def _(module: str) -> Result[PluginDetail]:
|
||||||
try:
|
try:
|
||||||
detail = await ApiDataSource.get_plugin_detail(module)
|
return Result.ok(
|
||||||
return Result.ok(detail, "已经帮你写好啦!")
|
await ApiDataSource.get_plugin_detail(module), "已经帮你写好啦!"
|
||||||
|
)
|
||||||
except (ValueError, KeyError):
|
except (ValueError, KeyError):
|
||||||
return Result.fail("插件数据不存在...")
|
return Result.fail("插件数据不存在...")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{router.prefix}/get_plugin 调用错误", "WebUi", e=e)
|
logger.error(f"{router.prefix}/get_plugin 调用错误", "WebUi", e=e)
|
||||||
return Result.fail(f"{type(e)}: {e}")
|
return Result.fail(f"{type(e)}: {e}")
|
||||||
|
|
||||||
|
|
||||||
@router.put(
|
|
||||||
"/plugins/batch_update",
|
|
||||||
dependencies=[authentication()],
|
|
||||||
response_model=Result[BatchUpdateResult],
|
|
||||||
response_class=JSONResponse,
|
|
||||||
summary="批量更新插件配置",
|
|
||||||
)
|
|
||||||
async def batch_update_plugin_config_api(
|
|
||||||
params: BatchUpdatePlugins,
|
|
||||||
) -> Result[BatchUpdateResult]:
|
|
||||||
"""批量更新插件配置,如开关、类型等"""
|
|
||||||
try:
|
|
||||||
result_dict = await ApiDataSource.batch_update_plugins(params=params)
|
|
||||||
result_model = BatchUpdateResult(
|
|
||||||
success=result_dict["success"],
|
|
||||||
updated_count=result_dict["updated_count"],
|
|
||||||
errors=result_dict["errors"],
|
|
||||||
)
|
|
||||||
clear_help_image()
|
|
||||||
return Result.ok(result_model, "插件配置更新完成")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{router.prefix}/plugins/batch_update 调用错误", "WebUi", e=e)
|
|
||||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
# 新增:重命名菜单类型路由
|
|
||||||
@router.put(
|
|
||||||
"/menu_type/rename",
|
|
||||||
dependencies=[authentication()],
|
|
||||||
response_model=Result,
|
|
||||||
summary="重命名菜单类型",
|
|
||||||
)
|
|
||||||
async def rename_menu_type_api(payload: RenameMenuTypePayload) -> Result:
|
|
||||||
try:
|
|
||||||
result = await ApiDataSource.rename_menu_type(
|
|
||||||
old_name=payload.old_name, new_name=payload.new_name
|
|
||||||
)
|
|
||||||
if result.get("success"):
|
|
||||||
clear_help_image()
|
|
||||||
return Result.ok(
|
|
||||||
info=result.get(
|
|
||||||
"info",
|
|
||||||
f"成功将 {result.get('updated_count', 0)} 个插件的菜单类型从 "
|
|
||||||
f"'{payload.old_name}' 修改为 '{payload.new_name}'",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return Result.fail(info=result.get("info", "重命名失败"))
|
|
||||||
except ValueError as ve:
|
|
||||||
return Result.fail(info=str(ve))
|
|
||||||
except RuntimeError as re:
|
|
||||||
logger.error(f"{router.prefix}/menu_type/rename 调用错误", "WebUi", e=re)
|
|
||||||
return Result.fail(info=str(re))
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{router.prefix}/menu_type/rename 调用错误", "WebUi", e=e)
|
|
||||||
return Result.fail(info=f"发生未知错误: {type(e).__name__}")
|
|
||||||
|
|||||||
@@ -2,20 +2,13 @@ import re
|
|||||||
|
|
||||||
import cattrs
|
import cattrs
|
||||||
from fastapi import Query
|
from fastapi import Query
|
||||||
from tortoise.exceptions import DoesNotExist
|
|
||||||
|
|
||||||
from zhenxun.configs.config import Config
|
from zhenxun.configs.config import Config
|
||||||
from zhenxun.configs.utils import ConfigGroup
|
from zhenxun.configs.utils import ConfigGroup
|
||||||
from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo
|
from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo
|
||||||
from zhenxun.utils.enum import BlockType, PluginType
|
from zhenxun.utils.enum import BlockType, PluginType
|
||||||
|
|
||||||
from .model import (
|
from .model import PluginConfig, PluginDetail, PluginInfo, UpdatePlugin
|
||||||
BatchUpdatePlugins,
|
|
||||||
PluginConfig,
|
|
||||||
PluginDetail,
|
|
||||||
PluginInfo,
|
|
||||||
UpdatePlugin,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ApiDataSource:
|
class ApiDataSource:
|
||||||
@@ -51,11 +44,6 @@ class ApiDataSource:
|
|||||||
level=plugin.level,
|
level=plugin.level,
|
||||||
status=plugin.status,
|
status=plugin.status,
|
||||||
author=plugin.author,
|
author=plugin.author,
|
||||||
block_type=plugin.block_type,
|
|
||||||
is_builtin="builtin_plugins" in plugin.module_path
|
|
||||||
or plugin.plugin_type == PluginType.HIDDEN,
|
|
||||||
allow_setting=plugin.plugin_type != PluginType.HIDDEN,
|
|
||||||
allow_switch=plugin.plugin_type != PluginType.HIDDEN,
|
|
||||||
)
|
)
|
||||||
plugin_list.append(plugin_info)
|
plugin_list.append(plugin_info)
|
||||||
return plugin_list
|
return plugin_list
|
||||||
@@ -81,6 +69,7 @@ class ApiDataSource:
|
|||||||
db_plugin.block_type = param.block_type
|
db_plugin.block_type = param.block_type
|
||||||
db_plugin.status = param.block_type != BlockType.ALL
|
db_plugin.status = param.block_type != BlockType.ALL
|
||||||
await db_plugin.save()
|
await db_plugin.save()
|
||||||
|
# 配置项
|
||||||
if param.configs and (configs := Config.get(param.module)):
|
if param.configs and (configs := Config.get(param.module)):
|
||||||
for key in param.configs:
|
for key in param.configs:
|
||||||
if c := configs.configs.get(key):
|
if c := configs.configs.get(key):
|
||||||
@@ -91,87 +80,6 @@ class ApiDataSource:
|
|||||||
Config.save(save_simple_data=True)
|
Config.save(save_simple_data=True)
|
||||||
return db_plugin
|
return db_plugin
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def batch_update_plugins(cls, params: BatchUpdatePlugins) -> dict:
|
|
||||||
"""批量更新插件数据
|
|
||||||
|
|
||||||
参数:
|
|
||||||
params: BatchUpdatePlugins
|
|
||||||
|
|
||||||
返回:
|
|
||||||
dict: 更新结果, 例如 {'success': True, 'updated_count': 5, 'errors': []}
|
|
||||||
"""
|
|
||||||
plugins_to_update_other_fields = []
|
|
||||||
other_update_fields = set()
|
|
||||||
updated_count = 0
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
for item in params.updates:
|
|
||||||
try:
|
|
||||||
db_plugin = await DbPluginInfo.get(module=item.module)
|
|
||||||
plugin_changed_other = False
|
|
||||||
plugin_changed_block = False
|
|
||||||
|
|
||||||
if db_plugin.block_type != item.block_type:
|
|
||||||
db_plugin.block_type = item.block_type
|
|
||||||
db_plugin.status = item.block_type != BlockType.ALL
|
|
||||||
plugin_changed_block = True
|
|
||||||
|
|
||||||
if item.menu_type is not None and db_plugin.menu_type != item.menu_type:
|
|
||||||
db_plugin.menu_type = item.menu_type
|
|
||||||
other_update_fields.add("menu_type")
|
|
||||||
plugin_changed_other = True
|
|
||||||
|
|
||||||
if (
|
|
||||||
item.default_status is not None
|
|
||||||
and db_plugin.default_status != item.default_status
|
|
||||||
):
|
|
||||||
db_plugin.default_status = item.default_status
|
|
||||||
other_update_fields.add("default_status")
|
|
||||||
plugin_changed_other = True
|
|
||||||
|
|
||||||
if plugin_changed_block:
|
|
||||||
try:
|
|
||||||
await db_plugin.save(update_fields=["block_type", "status"])
|
|
||||||
updated_count += 1
|
|
||||||
except Exception as e_save:
|
|
||||||
errors.append(
|
|
||||||
{
|
|
||||||
"module": item.module,
|
|
||||||
"error": f"Save block_type failed: {e_save!s}",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
plugin_changed_other = False
|
|
||||||
|
|
||||||
if plugin_changed_other:
|
|
||||||
plugins_to_update_other_fields.append(db_plugin)
|
|
||||||
|
|
||||||
except DoesNotExist:
|
|
||||||
errors.append({"module": item.module, "error": "Plugin not found"})
|
|
||||||
except Exception as e:
|
|
||||||
errors.append({"module": item.module, "error": str(e)})
|
|
||||||
|
|
||||||
bulk_updated_count = 0
|
|
||||||
if plugins_to_update_other_fields and other_update_fields:
|
|
||||||
try:
|
|
||||||
await DbPluginInfo.bulk_update(
|
|
||||||
plugins_to_update_other_fields, list(other_update_fields)
|
|
||||||
)
|
|
||||||
bulk_updated_count = len(plugins_to_update_other_fields)
|
|
||||||
except Exception as e_bulk:
|
|
||||||
errors.append(
|
|
||||||
{
|
|
||||||
"module": "batch_update_other",
|
|
||||||
"error": f"Bulk update failed: {e_bulk!s}",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"success": len(errors) == 0,
|
|
||||||
"updated_count": updated_count + bulk_updated_count,
|
|
||||||
"errors": errors,
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __build_plugin_config(
|
def __build_plugin_config(
|
||||||
cls, module: str, cfg: str, config: ConfigGroup
|
cls, module: str, cfg: str, config: ConfigGroup
|
||||||
@@ -207,41 +115,6 @@ class ApiDataSource:
|
|||||||
type_inner=type_inner, # type: ignore
|
type_inner=type_inner, # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def rename_menu_type(cls, old_name: str, new_name: str) -> dict:
|
|
||||||
"""重命名菜单类型,并更新所有相关插件
|
|
||||||
|
|
||||||
参数:
|
|
||||||
old_name: 旧菜单类型名称
|
|
||||||
new_name: 新菜单类型名称
|
|
||||||
|
|
||||||
返回:
|
|
||||||
dict: 更新结果, 例如 {'success': True, 'updated_count': 3}
|
|
||||||
"""
|
|
||||||
if not old_name or not new_name:
|
|
||||||
raise ValueError("旧名称和新名称都不能为空")
|
|
||||||
if old_name == new_name:
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"updated_count": 0,
|
|
||||||
"info": "新旧名称相同,无需更新",
|
|
||||||
}
|
|
||||||
|
|
||||||
# 检查新名称是否已存在(理论上前端会校验,后端再保险一次)
|
|
||||||
exists = await DbPluginInfo.filter(menu_type=new_name).exists()
|
|
||||||
if exists:
|
|
||||||
raise ValueError(f"新的菜单类型名称 '{new_name}' 已被其他插件使用")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 使用 filter().update() 进行批量更新
|
|
||||||
updated_count = await DbPluginInfo.filter(menu_type=old_name).update(
|
|
||||||
menu_type=new_name
|
|
||||||
)
|
|
||||||
return {"success": True, "updated_count": updated_count}
|
|
||||||
except Exception as e:
|
|
||||||
# 可以添加更详细的日志记录
|
|
||||||
raise RuntimeError(f"数据库更新菜单类型失败: {e!s}")
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_plugin_detail(cls, module: str) -> PluginDetail:
|
async def get_plugin_detail(cls, module: str) -> PluginDetail:
|
||||||
"""获取插件详情
|
"""获取插件详情
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from zhenxun.utils.enum import BlockType
|
from zhenxun.utils.enum import BlockType
|
||||||
|
|
||||||
@@ -37,19 +37,19 @@ class UpdatePlugin(BaseModel):
|
|||||||
module: str
|
module: str
|
||||||
"""模块"""
|
"""模块"""
|
||||||
default_status: bool
|
default_status: bool
|
||||||
"""是否默认开启"""
|
"""默认开关"""
|
||||||
limit_superuser: bool
|
limit_superuser: bool
|
||||||
"""是否限制超级用户"""
|
"""限制超级用户"""
|
||||||
level: int
|
|
||||||
"""等级"""
|
|
||||||
cost_gold: int
|
cost_gold: int
|
||||||
"""花费金币"""
|
"""金币花费"""
|
||||||
menu_type: str
|
menu_type: str
|
||||||
"""菜单类型"""
|
"""插件菜单类型"""
|
||||||
|
level: int
|
||||||
|
"""插件所需群权限"""
|
||||||
block_type: BlockType | None = None
|
block_type: BlockType | None = None
|
||||||
"""禁用类型"""
|
"""禁用类型"""
|
||||||
configs: dict[str, Any] | None = None
|
configs: dict[str, Any] | None = None
|
||||||
"""设置项"""
|
"""配置项"""
|
||||||
|
|
||||||
|
|
||||||
class PluginInfo(BaseModel):
|
class PluginInfo(BaseModel):
|
||||||
@@ -58,33 +58,27 @@ class PluginInfo(BaseModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
module: str
|
module: str
|
||||||
"""模块"""
|
|
||||||
plugin_name: str
|
|
||||||
"""插件名称"""
|
"""插件名称"""
|
||||||
|
plugin_name: str
|
||||||
|
"""插件中文名称"""
|
||||||
default_status: bool
|
default_status: bool
|
||||||
"""是否默认开启"""
|
"""默认开关"""
|
||||||
limit_superuser: bool
|
limit_superuser: bool
|
||||||
"""是否限制超级用户"""
|
"""限制超级用户"""
|
||||||
level: int
|
|
||||||
"""等级"""
|
|
||||||
cost_gold: int
|
cost_gold: int
|
||||||
"""花费金币"""
|
"""花费金币"""
|
||||||
menu_type: str
|
menu_type: str
|
||||||
"""菜单类型"""
|
"""插件菜单类型"""
|
||||||
version: str
|
version: str
|
||||||
"""版本"""
|
"""插件版本"""
|
||||||
|
level: int
|
||||||
|
"""群权限"""
|
||||||
status: bool
|
status: bool
|
||||||
"""状态"""
|
"""当前状态"""
|
||||||
author: str | None = None
|
author: str | None = None
|
||||||
"""作者"""
|
"""作者"""
|
||||||
block_type: BlockType | None = Field(None, description="插件禁用状态 (None: 启用)")
|
block_type: BlockType | None = None
|
||||||
"""禁用状态"""
|
"""禁用类型"""
|
||||||
is_builtin: bool = False
|
|
||||||
"""是否为内置插件"""
|
|
||||||
allow_switch: bool = True
|
|
||||||
"""是否允许开关"""
|
|
||||||
allow_setting: bool = True
|
|
||||||
"""是否允许设置"""
|
|
||||||
|
|
||||||
|
|
||||||
class PluginConfig(BaseModel):
|
class PluginConfig(BaseModel):
|
||||||
@@ -92,13 +86,20 @@ class PluginConfig(BaseModel):
|
|||||||
插件配置项
|
插件配置项
|
||||||
"""
|
"""
|
||||||
|
|
||||||
module: str = Field(..., description="模块名")
|
module: str
|
||||||
key: str = Field(..., description="键")
|
"""模块"""
|
||||||
value: Any = Field(None, description="值")
|
key: str
|
||||||
help: str | None = Field(None, description="帮助信息")
|
"""键"""
|
||||||
default_value: Any = Field(None, description="默认值")
|
value: Any
|
||||||
type: str | None = Field(None, description="类型")
|
"""值"""
|
||||||
type_inner: list[str] | None = Field(None, description="内部类型")
|
help: str | None = None
|
||||||
|
"""帮助"""
|
||||||
|
default_value: Any
|
||||||
|
"""默认值"""
|
||||||
|
type: Any = None
|
||||||
|
"""值类型"""
|
||||||
|
type_inner: list[str] | None = None
|
||||||
|
"""List Tuple等内部类型检验"""
|
||||||
|
|
||||||
|
|
||||||
class PluginCount(BaseModel):
|
class PluginCount(BaseModel):
|
||||||
@@ -116,21 +117,6 @@ class PluginCount(BaseModel):
|
|||||||
"""其他插件"""
|
"""其他插件"""
|
||||||
|
|
||||||
|
|
||||||
class BatchUpdatePluginItem(BaseModel):
|
|
||||||
module: str = Field(..., description="插件模块名")
|
|
||||||
default_status: bool | None = Field(None, description="默认状态(开关)")
|
|
||||||
menu_type: str | None = Field(None, description="菜单类型")
|
|
||||||
block_type: BlockType | None = Field(
|
|
||||||
None, description="插件禁用状态 (None: 启用, ALL: 禁用)"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BatchUpdatePlugins(BaseModel):
|
|
||||||
updates: list[BatchUpdatePluginItem] = Field(
|
|
||||||
..., description="要批量更新的插件列表"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PluginDetail(PluginInfo):
|
class PluginDetail(PluginInfo):
|
||||||
"""
|
"""
|
||||||
插件详情
|
插件详情
|
||||||
@@ -139,26 +125,6 @@ class PluginDetail(PluginInfo):
|
|||||||
config_list: list[PluginConfig]
|
config_list: list[PluginConfig]
|
||||||
|
|
||||||
|
|
||||||
class RenameMenuTypePayload(BaseModel):
|
|
||||||
old_name: str = Field(..., description="旧菜单类型名称")
|
|
||||||
new_name: str = Field(..., description="新菜单类型名称")
|
|
||||||
|
|
||||||
|
|
||||||
class PluginIr(BaseModel):
|
class PluginIr(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
"""插件id"""
|
"""插件id"""
|
||||||
|
|
||||||
|
|
||||||
class BatchUpdateResult(BaseModel):
|
|
||||||
"""
|
|
||||||
批量更新插件结果
|
|
||||||
"""
|
|
||||||
|
|
||||||
success: bool = Field(..., description="是否全部成功")
|
|
||||||
"""是否全部成功"""
|
|
||||||
updated_count: int = Field(..., description="更新成功的数量")
|
|
||||||
"""更新成功的数量"""
|
|
||||||
errors: list[dict[str, str]] = Field(
|
|
||||||
default_factory=list, description="错误信息列表"
|
|
||||||
)
|
|
||||||
"""错误信息列表"""
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from fastapi.responses import JSONResponse
|
|||||||
from zhenxun.utils._build_image import BuildImage
|
from zhenxun.utils._build_image import BuildImage
|
||||||
|
|
||||||
from ....base_model import Result, SystemFolderSize
|
from ....base_model import Result, SystemFolderSize
|
||||||
from ....utils import authentication, get_system_disk, validate_path
|
from ....utils import authentication, get_system_disk
|
||||||
from .model import AddFile, DeleteFile, DirFile, RenameFile, SaveFile
|
from .model import AddFile, DeleteFile, DirFile, RenameFile, SaveFile
|
||||||
|
|
||||||
router = APIRouter(prefix="/system")
|
router = APIRouter(prefix="/system")
|
||||||
@@ -25,29 +25,20 @@ IMAGE_TYPE = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"]
|
|||||||
description="获取文件列表",
|
description="获取文件列表",
|
||||||
)
|
)
|
||||||
async def _(path: str | None = None) -> Result[list[DirFile]]:
|
async def _(path: str | None = None) -> Result[list[DirFile]]:
|
||||||
try:
|
base_path = Path(path) if path else Path()
|
||||||
base_path, error = validate_path(path)
|
data_list = []
|
||||||
if error:
|
for file in os.listdir(base_path):
|
||||||
return Result.fail(error)
|
file_path = base_path / file
|
||||||
if not base_path:
|
is_image = any(file.endswith(f".{t}") for t in IMAGE_TYPE)
|
||||||
return Result.fail("无效的路径")
|
data_list.append(
|
||||||
data_list = []
|
DirFile(
|
||||||
for file in os.listdir(base_path):
|
is_file=not file_path.is_dir(),
|
||||||
file_path = base_path / file
|
is_image=is_image,
|
||||||
is_image = any(file.endswith(f".{t}") for t in IMAGE_TYPE)
|
name=file,
|
||||||
data_list.append(
|
parent=path,
|
||||||
DirFile(
|
|
||||||
is_file=not file_path.is_dir(),
|
|
||||||
is_image=is_image,
|
|
||||||
name=file,
|
|
||||||
parent=path,
|
|
||||||
size=None if file_path.is_dir() else file_path.stat().st_size,
|
|
||||||
mtime=file_path.stat().st_mtime,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return Result.ok(data_list)
|
)
|
||||||
except Exception as e:
|
return Result.ok(data_list)
|
||||||
return Result.fail(f"获取文件列表失败: {e!s}")
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
@@ -69,12 +60,8 @@ async def _(full_path: str | None = None) -> Result[list[SystemFolderSize]]:
|
|||||||
description="删除文件",
|
description="删除文件",
|
||||||
)
|
)
|
||||||
async def _(param: DeleteFile) -> Result:
|
async def _(param: DeleteFile) -> Result:
|
||||||
path, error = validate_path(param.full_path)
|
path = Path(param.full_path)
|
||||||
if error:
|
if not path or not path.exists():
|
||||||
return Result.fail(error)
|
|
||||||
if not path:
|
|
||||||
return Result.fail("无效的路径")
|
|
||||||
if not path.exists():
|
|
||||||
return Result.warning_("文件不存在...")
|
return Result.warning_("文件不存在...")
|
||||||
try:
|
try:
|
||||||
path.unlink()
|
path.unlink()
|
||||||
@@ -91,12 +78,8 @@ async def _(param: DeleteFile) -> Result:
|
|||||||
description="删除文件夹",
|
description="删除文件夹",
|
||||||
)
|
)
|
||||||
async def _(param: DeleteFile) -> Result:
|
async def _(param: DeleteFile) -> Result:
|
||||||
path, error = validate_path(param.full_path)
|
path = Path(param.full_path)
|
||||||
if error:
|
if not path or not path.exists() or path.is_file():
|
||||||
return Result.fail(error)
|
|
||||||
if not path:
|
|
||||||
return Result.fail("无效的路径")
|
|
||||||
if not path.exists() or path.is_file():
|
|
||||||
return Result.warning_("文件夹不存在...")
|
return Result.warning_("文件夹不存在...")
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(path.absolute())
|
shutil.rmtree(path.absolute())
|
||||||
@@ -113,14 +96,10 @@ async def _(param: DeleteFile) -> Result:
|
|||||||
description="重命名文件",
|
description="重命名文件",
|
||||||
)
|
)
|
||||||
async def _(param: RenameFile) -> Result:
|
async def _(param: RenameFile) -> Result:
|
||||||
parent_path, error = validate_path(param.parent)
|
path = (
|
||||||
if error:
|
(Path(param.parent) / param.old_name) if param.parent else Path(param.old_name)
|
||||||
return Result.fail(error)
|
)
|
||||||
if not parent_path:
|
if not path or not path.exists():
|
||||||
return Result.fail("无效的路径")
|
|
||||||
|
|
||||||
path = (parent_path / param.old_name) if param.parent else Path(param.old_name)
|
|
||||||
if not path.exists():
|
|
||||||
return Result.warning_("文件不存在...")
|
return Result.warning_("文件不存在...")
|
||||||
try:
|
try:
|
||||||
path.rename(path.parent / param.name)
|
path.rename(path.parent / param.name)
|
||||||
@@ -137,14 +116,10 @@ async def _(param: RenameFile) -> Result:
|
|||||||
description="重命名文件夹",
|
description="重命名文件夹",
|
||||||
)
|
)
|
||||||
async def _(param: RenameFile) -> Result:
|
async def _(param: RenameFile) -> Result:
|
||||||
parent_path, error = validate_path(param.parent)
|
path = (
|
||||||
if error:
|
(Path(param.parent) / param.old_name) if param.parent else Path(param.old_name)
|
||||||
return Result.fail(error)
|
)
|
||||||
if not parent_path:
|
if not path or not path.exists() or path.is_file():
|
||||||
return Result.fail("无效的路径")
|
|
||||||
|
|
||||||
path = (parent_path / param.old_name) if param.parent else Path(param.old_name)
|
|
||||||
if not path.exists() or path.is_file():
|
|
||||||
return Result.warning_("文件夹不存在...")
|
return Result.warning_("文件夹不存在...")
|
||||||
try:
|
try:
|
||||||
new_path = path.parent / param.name
|
new_path = path.parent / param.name
|
||||||
@@ -162,13 +137,7 @@ async def _(param: RenameFile) -> Result:
|
|||||||
description="新建文件",
|
description="新建文件",
|
||||||
)
|
)
|
||||||
async def _(param: AddFile) -> Result:
|
async def _(param: AddFile) -> Result:
|
||||||
parent_path, error = validate_path(param.parent)
|
path = (Path(param.parent) / param.name) if param.parent else Path(param.name)
|
||||||
if error:
|
|
||||||
return Result.fail(error)
|
|
||||||
if not parent_path:
|
|
||||||
return Result.fail("无效的路径")
|
|
||||||
|
|
||||||
path = (parent_path / param.name) if param.parent else Path(param.name)
|
|
||||||
if path.exists():
|
if path.exists():
|
||||||
return Result.warning_("文件已存在...")
|
return Result.warning_("文件已存在...")
|
||||||
try:
|
try:
|
||||||
@@ -186,13 +155,7 @@ async def _(param: AddFile) -> Result:
|
|||||||
description="新建文件夹",
|
description="新建文件夹",
|
||||||
)
|
)
|
||||||
async def _(param: AddFile) -> Result:
|
async def _(param: AddFile) -> Result:
|
||||||
parent_path, error = validate_path(param.parent)
|
path = (Path(param.parent) / param.name) if param.parent else Path(param.name)
|
||||||
if error:
|
|
||||||
return Result.fail(error)
|
|
||||||
if not parent_path:
|
|
||||||
return Result.fail("无效的路径")
|
|
||||||
|
|
||||||
path = (parent_path / param.name) if param.parent else Path(param.name)
|
|
||||||
if path.exists():
|
if path.exists():
|
||||||
return Result.warning_("文件夹已存在...")
|
return Result.warning_("文件夹已存在...")
|
||||||
try:
|
try:
|
||||||
@@ -210,11 +173,7 @@ async def _(param: AddFile) -> Result:
|
|||||||
description="读取文件",
|
description="读取文件",
|
||||||
)
|
)
|
||||||
async def _(full_path: str) -> Result:
|
async def _(full_path: str) -> Result:
|
||||||
path, error = validate_path(full_path)
|
path = Path(full_path)
|
||||||
if error:
|
|
||||||
return Result.fail(error)
|
|
||||||
if not path:
|
|
||||||
return Result.fail("无效的路径")
|
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return Result.warning_("文件不存在...")
|
return Result.warning_("文件不存在...")
|
||||||
try:
|
try:
|
||||||
@@ -232,13 +191,9 @@ async def _(full_path: str) -> Result:
|
|||||||
description="读取文件",
|
description="读取文件",
|
||||||
)
|
)
|
||||||
async def _(param: SaveFile) -> Result[str]:
|
async def _(param: SaveFile) -> Result[str]:
|
||||||
path, error = validate_path(param.full_path)
|
path = Path(param.full_path)
|
||||||
if error:
|
|
||||||
return Result.fail(error)
|
|
||||||
if not path:
|
|
||||||
return Result.fail("无效的路径")
|
|
||||||
try:
|
try:
|
||||||
async with aiofiles.open(str(path), "w", encoding="utf-8") as f:
|
async with aiofiles.open(path, "w", encoding="utf-8") as f:
|
||||||
await f.write(param.content)
|
await f.write(param.content)
|
||||||
return Result.ok("更新成功!")
|
return Result.ok("更新成功!")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -253,24 +208,10 @@ async def _(param: SaveFile) -> Result[str]:
|
|||||||
description="读取图片base64",
|
description="读取图片base64",
|
||||||
)
|
)
|
||||||
async def _(full_path: str) -> Result[str]:
|
async def _(full_path: str) -> Result[str]:
|
||||||
path, error = validate_path(full_path)
|
path = Path(full_path)
|
||||||
if error:
|
|
||||||
return Result.fail(error)
|
|
||||||
if not path:
|
|
||||||
return Result.fail("无效的路径")
|
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return Result.warning_("文件不存在...")
|
return Result.warning_("文件不存在...")
|
||||||
try:
|
try:
|
||||||
return Result.ok(BuildImage.open(path).pic2bs4())
|
return Result.ok(BuildImage.open(path).pic2bs4())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return Result.warning_(f"获取图片失败: {e!s}")
|
return Result.warning_(f"获取图片失败: {e!s}")
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/ping",
|
|
||||||
response_model=Result[str],
|
|
||||||
response_class=JSONResponse,
|
|
||||||
description="检查服务器状态",
|
|
||||||
)
|
|
||||||
async def _() -> Result[str]:
|
|
||||||
return Result.ok("pong")
|
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ class DirFile(BaseModel):
|
|||||||
"""文件夹或文件名称"""
|
"""文件夹或文件名称"""
|
||||||
parent: str | None = None
|
parent: str | None = None
|
||||||
"""父级"""
|
"""父级"""
|
||||||
size: int | None = None
|
|
||||||
"""文件大小"""
|
|
||||||
mtime: float | None = None
|
|
||||||
"""修改时间"""
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteFile(BaseModel):
|
class DeleteFile(BaseModel):
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import sys
|
|
||||||
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
import nonebot
|
import nonebot
|
||||||
|
from strenum import StrEnum
|
||||||
if sys.version_info >= (3, 11):
|
|
||||||
from enum import StrEnum
|
|
||||||
else:
|
|
||||||
from strenum import StrEnum
|
|
||||||
|
|
||||||
from zhenxun.configs.path_config import DATA_PATH, TEMP_PATH
|
from zhenxun.configs.path_config import DATA_PATH, TEMP_PATH
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ async def update_webui_assets():
|
|||||||
download_url = await GithubUtils.parse_github_url(
|
download_url = await GithubUtils.parse_github_url(
|
||||||
WEBUI_DIST_GITHUB_URL
|
WEBUI_DIST_GITHUB_URL
|
||||||
).get_archive_download_urls()
|
).get_archive_download_urls()
|
||||||
logger.info("开始下载 webui_assets 资源...", COMMAND_NAME)
|
|
||||||
if await AsyncHttpx.download_file(
|
if await AsyncHttpx.download_file(
|
||||||
download_url, webui_assets_path, follow_redirects=True
|
download_url, webui_assets_path, follow_redirects=True
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import contextlib
|
|||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
|
||||||
|
|
||||||
from fastapi import Depends, HTTPException
|
from fastapi import Depends, HTTPException
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
@@ -12,7 +11,7 @@ import psutil
|
|||||||
import ujson as json
|
import ujson as json
|
||||||
|
|
||||||
from zhenxun.configs.config import Config
|
from zhenxun.configs.config import Config
|
||||||
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
|
from zhenxun.configs.path_config import DATA_PATH
|
||||||
|
|
||||||
from .base_model import SystemFolderSize, SystemStatus, User
|
from .base_model import SystemFolderSize, SystemStatus, User
|
||||||
|
|
||||||
@@ -29,61 +28,6 @@ if token_file.exists():
|
|||||||
token_data = json.load(open(token_file, encoding="utf8"))
|
token_data = json.load(open(token_file, encoding="utf8"))
|
||||||
|
|
||||||
|
|
||||||
def validate_path(path_str: str | None) -> tuple[Path | None, str | None]:
|
|
||||||
"""验证路径是否安全
|
|
||||||
|
|
||||||
参数:
|
|
||||||
path_str: 用户输入的路径
|
|
||||||
|
|
||||||
返回:
|
|
||||||
tuple[Path | None, str | None]: (验证后的路径, 错误信息)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if not path_str:
|
|
||||||
return Path().resolve(), None
|
|
||||||
|
|
||||||
# 1. 移除任何可能的路径遍历尝试
|
|
||||||
path_str = re.sub(r"[\\/]\.\.[\\/]", "", path_str)
|
|
||||||
|
|
||||||
# 2. 规范化路径并转换为绝对路径
|
|
||||||
path = Path(path_str).resolve()
|
|
||||||
|
|
||||||
# 3. 获取项目根目录
|
|
||||||
root_dir = Path().resolve()
|
|
||||||
|
|
||||||
# 4. 验证路径是否在项目根目录内
|
|
||||||
try:
|
|
||||||
if not path.is_relative_to(root_dir):
|
|
||||||
return None, "访问路径超出允许范围"
|
|
||||||
except ValueError:
|
|
||||||
return None, "无效的路径格式"
|
|
||||||
|
|
||||||
# 5. 验证路径是否包含任何危险字符
|
|
||||||
if any(c in str(path) for c in ["..", "~", "*", "?", ">", "<", "|", '"']):
|
|
||||||
return None, "路径包含非法字符"
|
|
||||||
|
|
||||||
# 6. 验证路径长度是否合理
|
|
||||||
return (None, "路径长度超出限制") if len(str(path)) > 4096 else (path, None)
|
|
||||||
except Exception as e:
|
|
||||||
return None, f"路径验证失败: {e!s}"
|
|
||||||
|
|
||||||
|
|
||||||
GROUP_HELP_PATH = DATA_PATH / "group_help"
|
|
||||||
SIMPLE_HELP_IMAGE = IMAGE_PATH / "SIMPLE_HELP.png"
|
|
||||||
SIMPLE_DETAIL_HELP_IMAGE = IMAGE_PATH / "SIMPLE_DETAIL_HELP.png"
|
|
||||||
|
|
||||||
|
|
||||||
def clear_help_image():
|
|
||||||
"""清理帮助图片"""
|
|
||||||
if SIMPLE_HELP_IMAGE.exists():
|
|
||||||
SIMPLE_HELP_IMAGE.unlink()
|
|
||||||
if SIMPLE_DETAIL_HELP_IMAGE.exists():
|
|
||||||
SIMPLE_DETAIL_HELP_IMAGE.unlink()
|
|
||||||
for file in GROUP_HELP_PATH.iterdir():
|
|
||||||
if file.is_file():
|
|
||||||
file.unlink()
|
|
||||||
|
|
||||||
|
|
||||||
def get_user(uname: str) -> User | None:
|
def get_user(uname: str) -> User | None:
|
||||||
"""获取账号密码
|
"""获取账号密码
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
from tortoise import fields
|
|
||||||
|
|
||||||
from zhenxun.services.db_context import Model
|
|
||||||
from zhenxun.utils.enum import BotSentType
|
|
||||||
|
|
||||||
|
|
||||||
class BotMessageStore(Model):
|
|
||||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
|
||||||
"""自增id"""
|
|
||||||
bot_id = fields.CharField(255, null=True)
|
|
||||||
"""bot id"""
|
|
||||||
user_id = fields.CharField(255, null=True)
|
|
||||||
"""目标id"""
|
|
||||||
group_id = fields.CharField(255, null=True)
|
|
||||||
"""群组id"""
|
|
||||||
sent_type = fields.CharEnumField(BotSentType)
|
|
||||||
"""类型"""
|
|
||||||
text = fields.TextField(null=True)
|
|
||||||
"""文本内容"""
|
|
||||||
plain_text = fields.TextField(null=True)
|
|
||||||
"""纯文本"""
|
|
||||||
platform = fields.CharField(255, null=True)
|
|
||||||
"""平台"""
|
|
||||||
create_time = fields.DatetimeField(auto_now_add=True)
|
|
||||||
"""创建时间"""
|
|
||||||
|
|
||||||
class Meta: # pyright: ignore [reportIncompatibleVariableOverride]
|
|
||||||
table = "bot_message_store"
|
|
||||||
table_description = "Bot发送消息列表"
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
from tortoise import fields
|
|
||||||
|
|
||||||
from zhenxun.services.db_context import Model
|
|
||||||
from zhenxun.utils.enum import EventLogType
|
|
||||||
|
|
||||||
|
|
||||||
class EventLog(Model):
|
|
||||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
|
||||||
"""自增id"""
|
|
||||||
user_id = fields.CharField(255, description="用户id")
|
|
||||||
"""用户id"""
|
|
||||||
group_id = fields.CharField(255, description="群组id")
|
|
||||||
"""群组id"""
|
|
||||||
event_type = fields.CharEnumField(EventLogType, default=None, description="类型")
|
|
||||||
"""类型"""
|
|
||||||
create_time = fields.DatetimeField(auto_now_add=True, description="创建时间")
|
|
||||||
"""创建时间"""
|
|
||||||
|
|
||||||
class Meta: # pyright: ignore [reportIncompatibleVariableOverride]
|
|
||||||
table = "event_log"
|
|
||||||
table_description = "各种请求通知记录表"
|
|
||||||
@@ -3,10 +3,8 @@ from typing_extensions import Self
|
|||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
from tortoise import fields
|
from tortoise import fields
|
||||||
|
|
||||||
from zhenxun.configs.config import BotConfig
|
|
||||||
from zhenxun.models.group_console import GroupConsole
|
from zhenxun.models.group_console import GroupConsole
|
||||||
from zhenxun.services.db_context import Model
|
from zhenxun.services.db_context import Model
|
||||||
from zhenxun.utils.common_utils import SqlUtils
|
|
||||||
from zhenxun.utils.enum import RequestHandleType, RequestType
|
from zhenxun.utils.enum import RequestHandleType, RequestType
|
||||||
from zhenxun.utils.exception import NotFoundError
|
from zhenxun.utils.exception import NotFoundError
|
||||||
|
|
||||||
@@ -36,8 +34,6 @@ class FgRequest(Model):
|
|||||||
RequestHandleType, null=True, description="处理类型"
|
RequestHandleType, null=True, description="处理类型"
|
||||||
)
|
)
|
||||||
"""处理类型"""
|
"""处理类型"""
|
||||||
message_ids = fields.CharField(max_length=255, null=True, description="消息id列表")
|
|
||||||
"""消息id列表"""
|
|
||||||
|
|
||||||
class Meta: # pyright: ignore [reportIncompatibleVariableOverride]
|
class Meta: # pyright: ignore [reportIncompatibleVariableOverride]
|
||||||
table = "fg_request"
|
table = "fg_request"
|
||||||
@@ -127,24 +123,9 @@ class FgRequest(Model):
|
|||||||
await GroupConsole.update_or_create(
|
await GroupConsole.update_or_create(
|
||||||
group_id=req.group_id, defaults={"group_flag": 1}
|
group_id=req.group_id, defaults={"group_flag": 1}
|
||||||
)
|
)
|
||||||
if req.flag == "0":
|
await bot.set_group_add_request(
|
||||||
# 用户手动申请入群,创建群认证后提醒用户拉群
|
flag=req.flag,
|
||||||
await bot.send_private_msg(
|
sub_type="invite",
|
||||||
user_id=req.user_id,
|
approve=handle_type == RequestHandleType.APPROVE,
|
||||||
message=f"已同意你对{BotConfig.self_nickname}的申请群组:"
|
)
|
||||||
f"{req.group_id},可以直接手动拉入群组,{BotConfig.self_nickname}会自动同意。",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# 正常同意群组请求
|
|
||||||
await bot.set_group_add_request(
|
|
||||||
flag=req.flag,
|
|
||||||
sub_type="invite",
|
|
||||||
approve=handle_type == RequestHandleType.APPROVE,
|
|
||||||
)
|
|
||||||
return req
|
return req
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def _run_script(cls):
|
|
||||||
return [
|
|
||||||
SqlUtils.add_column("fg_request", "message_ids", "character varying(255)")
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ def convert_module_format(data: str | list[str]) -> str | list[str]:
|
|||||||
str | list[str]: 根据输入类型返回转换后的数据。
|
str | list[str]: 根据输入类型返回转换后的数据。
|
||||||
"""
|
"""
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
return [item.strip(",") for item in data.split("<") if item.strip()]
|
return [item.strip(",") for item in data.split("<") if item]
|
||||||
else:
|
else:
|
||||||
return "".join(add_disable_marker(item) for item in data)
|
return "".join(format(item) for item in data)
|
||||||
|
|
||||||
|
|
||||||
class GroupConsole(Model):
|
class GroupConsole(Model):
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
from typing_extensions import Self
|
|
||||||
|
|
||||||
from tortoise import fields
|
|
||||||
|
|
||||||
from zhenxun.services.db_context import Model
|
|
||||||
|
|
||||||
from .mahiro_bank_log import BankHandleType, MahiroBankLog
|
|
||||||
|
|
||||||
|
|
||||||
class MahiroBank(Model):
|
|
||||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
|
||||||
"""自增id"""
|
|
||||||
user_id = fields.CharField(255, description="用户id")
|
|
||||||
"""用户id"""
|
|
||||||
amount = fields.BigIntField(default=0, description="存款")
|
|
||||||
"""用户存款"""
|
|
||||||
rate = fields.FloatField(default=0.0005, description="小时利率")
|
|
||||||
"""小时利率"""
|
|
||||||
loan_amount = fields.BigIntField(default=0, description="贷款")
|
|
||||||
"""用户贷款"""
|
|
||||||
loan_rate = fields.FloatField(default=0.0005, description="贷款利率")
|
|
||||||
"""贷款利率"""
|
|
||||||
update_time = fields.DatetimeField(auto_now=True)
|
|
||||||
"""修改时间"""
|
|
||||||
create_time = fields.DatetimeField(auto_now_add=True)
|
|
||||||
"""创建时间"""
|
|
||||||
|
|
||||||
class Meta: # pyright: ignore [reportIncompatibleVariableOverride]
|
|
||||||
table = "mahiro_bank"
|
|
||||||
table_description = "小真寻银行"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def deposit(cls, user_id: str, amount: int, rate: float) -> Self:
|
|
||||||
"""存款
|
|
||||||
|
|
||||||
参数:
|
|
||||||
user_id: 用户id
|
|
||||||
amount: 金币数量
|
|
||||||
rate: 小时利率
|
|
||||||
|
|
||||||
返回:
|
|
||||||
Self: MahiroBank
|
|
||||||
"""
|
|
||||||
effective_hour = int(24 - datetime.now().hour)
|
|
||||||
user, _ = await cls.get_or_create(user_id=user_id)
|
|
||||||
user.amount += amount
|
|
||||||
await user.save(update_fields=["amount", "rate"])
|
|
||||||
await MahiroBankLog.create(
|
|
||||||
user_id=user_id,
|
|
||||||
amount=amount,
|
|
||||||
rate=rate,
|
|
||||||
effective_hour=effective_hour,
|
|
||||||
handle_type=BankHandleType.DEPOSIT,
|
|
||||||
)
|
|
||||||
return user
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def withdraw(cls, user_id: str, amount: int) -> Self:
|
|
||||||
"""取款
|
|
||||||
|
|
||||||
参数:
|
|
||||||
user_id: 用户id
|
|
||||||
amount: 金币数量
|
|
||||||
|
|
||||||
返回:
|
|
||||||
Self: MahiroBank
|
|
||||||
"""
|
|
||||||
if amount <= 0:
|
|
||||||
raise ValueError("取款金额必须大于0")
|
|
||||||
user, _ = await cls.get_or_create(user_id=user_id)
|
|
||||||
if user.amount < amount:
|
|
||||||
raise ValueError("取款金额不能大于存款金额")
|
|
||||||
user.amount -= amount
|
|
||||||
await user.save(update_fields=["amount"])
|
|
||||||
await MahiroBankLog.create(
|
|
||||||
user_id=user_id, amount=amount, handle_type=BankHandleType.WITHDRAW
|
|
||||||
)
|
|
||||||
return user
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def loan(cls, user_id: str, amount: int, rate: float) -> Self:
|
|
||||||
"""贷款
|
|
||||||
|
|
||||||
参数:
|
|
||||||
user_id: 用户id
|
|
||||||
amount: 贷款金额
|
|
||||||
rate: 贷款利率
|
|
||||||
|
|
||||||
返回:
|
|
||||||
Self: MahiroBank
|
|
||||||
"""
|
|
||||||
user, _ = await cls.get_or_create(user_id=user_id)
|
|
||||||
user.loan_amount += amount
|
|
||||||
user.loan_rate = rate
|
|
||||||
await user.save(update_fields=["loan_amount", "loan_rate"])
|
|
||||||
await MahiroBankLog.create(
|
|
||||||
user_id=user_id, amount=amount, rate=rate, handle_type=BankHandleType.LOAN
|
|
||||||
)
|
|
||||||
return user
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def repayment(cls, user_id: str, amount: int) -> Self:
|
|
||||||
"""还款
|
|
||||||
|
|
||||||
参数:
|
|
||||||
user_id: 用户id
|
|
||||||
amount: 还款金额
|
|
||||||
|
|
||||||
返回:
|
|
||||||
Self: MahiroBank
|
|
||||||
"""
|
|
||||||
if amount <= 0:
|
|
||||||
raise ValueError("还款金额必须大于0")
|
|
||||||
user, _ = await cls.get_or_create(user_id=user_id)
|
|
||||||
if user.loan_amount < amount:
|
|
||||||
raise ValueError("还款金额不能大于贷款金额")
|
|
||||||
user.loan_amount -= amount
|
|
||||||
await user.save(update_fields=["loan_amount"])
|
|
||||||
await MahiroBankLog.create(
|
|
||||||
user_id=user_id, amount=amount, handle_type=BankHandleType.REPAYMENT
|
|
||||||
)
|
|
||||||
return user
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
from tortoise import fields
|
|
||||||
|
|
||||||
from zhenxun.services.db_context import Model
|
|
||||||
from zhenxun.utils.enum import BankHandleType
|
|
||||||
|
|
||||||
|
|
||||||
class MahiroBankLog(Model):
|
|
||||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
|
||||||
"""自增id"""
|
|
||||||
user_id = fields.CharField(255, description="用户id")
|
|
||||||
"""用户id"""
|
|
||||||
amount = fields.BigIntField(default=0, description="存款")
|
|
||||||
"""金币数量"""
|
|
||||||
rate = fields.FloatField(default=0, description="小时利率")
|
|
||||||
"""小时利率"""
|
|
||||||
handle_type = fields.CharEnumField(
|
|
||||||
BankHandleType, null=True, description="处理类型"
|
|
||||||
)
|
|
||||||
"""处理类型"""
|
|
||||||
is_completed = fields.BooleanField(default=False, description="是否完成")
|
|
||||||
"""是否完成"""
|
|
||||||
effective_hour = fields.IntField(default=0, description="有效小时")
|
|
||||||
"""有效小时"""
|
|
||||||
update_time = fields.DatetimeField(auto_now=True)
|
|
||||||
"""修改时间"""
|
|
||||||
create_time = fields.DatetimeField(auto_now_add=True)
|
|
||||||
"""创建时间"""
|
|
||||||
|
|
||||||
class Meta: # pyright: ignore [reportIncompatibleVariableOverride]
|
|
||||||
table = "mahiro_bank_log"
|
|
||||||
table_description = "小真寻银行日志"
|
|
||||||
@@ -60,41 +60,27 @@ class PluginInfo(Model):
|
|||||||
table_description = "插件基本信息"
|
table_description = "插件基本信息"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_plugin(
|
async def get_plugin(cls, load_status: bool = True, **kwargs) -> Self | None:
|
||||||
cls, load_status: bool = True, filter_parent: bool = True, **kwargs
|
|
||||||
) -> Self | None:
|
|
||||||
"""获取插件列表
|
"""获取插件列表
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
load_status: 加载状态.
|
load_status: 加载状态.
|
||||||
filter_parent: 过滤父组件
|
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
Self | None: 插件
|
Self | None: 插件
|
||||||
"""
|
"""
|
||||||
if filter_parent:
|
|
||||||
return await cls.get_or_none(
|
|
||||||
load_status=load_status, plugin_type__not=PluginType.PARENT, **kwargs
|
|
||||||
)
|
|
||||||
return await cls.get_or_none(load_status=load_status, **kwargs)
|
return await cls.get_or_none(load_status=load_status, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_plugins(
|
async def get_plugins(cls, load_status: bool = True, **kwargs) -> list[Self]:
|
||||||
cls, load_status: bool = True, filter_parent: bool = True, **kwargs
|
|
||||||
) -> list[Self]:
|
|
||||||
"""获取插件列表
|
"""获取插件列表
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
load_status: 加载状态.
|
load_status: 加载状态.
|
||||||
filter_parent: 过滤父组件
|
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
list[Self]: 插件列表
|
list[Self]: 插件列表
|
||||||
"""
|
"""
|
||||||
if filter_parent:
|
|
||||||
return await cls.filter(
|
|
||||||
load_status=load_status, plugin_type__not=PluginType.PARENT, **kwargs
|
|
||||||
).all()
|
|
||||||
return await cls.filter(load_status=load_status, **kwargs).all()
|
return await cls.filter(load_status=load_status, **kwargs).all()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import nonebot
|
|
||||||
from nonebot.utils import is_coroutine_callable
|
from nonebot.utils import is_coroutine_callable
|
||||||
from tortoise import Tortoise
|
from tortoise import Tortoise
|
||||||
from tortoise.connection import connections
|
from tortoise.connection import connections
|
||||||
from tortoise.models import Model as Model_
|
from tortoise.models import Model as Model_
|
||||||
|
|
||||||
from zhenxun.configs.config import BotConfig
|
from zhenxun.configs.config import BotConfig
|
||||||
from zhenxun.utils.exception import HookPriorityException
|
|
||||||
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
|
||||||
|
|
||||||
from .log import logger
|
from .log import logger
|
||||||
|
|
||||||
@@ -14,9 +11,6 @@ SCRIPT_METHOD = []
|
|||||||
MODELS: list[str] = []
|
MODELS: list[str] = []
|
||||||
|
|
||||||
|
|
||||||
driver = nonebot.get_driver()
|
|
||||||
|
|
||||||
|
|
||||||
class Model(Model_):
|
class Model(Model_):
|
||||||
"""
|
"""
|
||||||
自动添加模块
|
自动添加模块
|
||||||
@@ -32,7 +26,7 @@ class Model(Model_):
|
|||||||
SCRIPT_METHOD.append((cls.__module__, func))
|
SCRIPT_METHOD.append((cls.__module__, func))
|
||||||
|
|
||||||
|
|
||||||
class DbUrlIsNode(HookPriorityException):
|
class DbUrlIsNode(Exception):
|
||||||
"""
|
"""
|
||||||
数据库链接地址为空
|
数据库链接地址为空
|
||||||
"""
|
"""
|
||||||
@@ -48,19 +42,9 @@ class DbConnectError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@PriorityLifecycle.on_startup(priority=1)
|
|
||||||
async def init():
|
async def init():
|
||||||
if not BotConfig.db_url:
|
if not BotConfig.db_url:
|
||||||
# raise DbUrlIsNode("数据库配置为空,请在.env.dev中配置DB_URL...")
|
raise DbUrlIsNode("数据库配置为空,请在.env.dev中配置DB_URL...")
|
||||||
error = f"""
|
|
||||||
**********************************************************************
|
|
||||||
🌟 **************************** 配置为空 ************************* 🌟
|
|
||||||
🚀 请打开 WebUi 进行基础配置 🚀
|
|
||||||
🌐 配置地址:http://{driver.config.host}:{driver.config.port}/#/configure 🌐
|
|
||||||
***********************************************************************
|
|
||||||
***********************************************************************
|
|
||||||
"""
|
|
||||||
raise DbUrlIsNode("\n" + error.strip())
|
|
||||||
try:
|
try:
|
||||||
await Tortoise.init(
|
await Tortoise.init(
|
||||||
db_url=BotConfig.db_url,
|
db_url=BotConfig.db_url,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from datetime import timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, overload
|
from typing import Any, overload
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
@@ -17,7 +17,7 @@ driver = nonebot.get_driver()
|
|||||||
log_level = driver.config.log_level or "INFO"
|
log_level = driver.config.log_level or "INFO"
|
||||||
|
|
||||||
logger_.add(
|
logger_.add(
|
||||||
LOG_PATH / "{time:YYYY-MM-DD}.log",
|
LOG_PATH / f"{datetime.now().date()}.log",
|
||||||
level=log_level,
|
level=log_level,
|
||||||
rotation="00:00",
|
rotation="00:00",
|
||||||
format=default_format,
|
format=default_format,
|
||||||
@@ -26,7 +26,7 @@ logger_.add(
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger_.add(
|
logger_.add(
|
||||||
LOG_PATH / "error_{time:YYYY-MM-DD}.log",
|
LOG_PATH / f"error_{datetime.now().date()}.log",
|
||||||
level="ERROR",
|
level="ERROR",
|
||||||
rotation="00:00",
|
rotation="00:00",
|
||||||
format=default_format,
|
format=default_format,
|
||||||
@@ -36,92 +36,26 @@ logger_.add(
|
|||||||
|
|
||||||
|
|
||||||
class logger:
|
class logger:
|
||||||
"""
|
TEMPLATE_A = "Adapter[{}] {}"
|
||||||
一个经过优化的、支持多种上下文和格式的日志记录器。
|
TEMPLATE_B = "Adapter[{}] [<u><c>{}</c></u>]: {}"
|
||||||
"""
|
TEMPLATE_C = "Adapter[{}] 用户[<u><e>{}</e></u>] 触发 [<u><c>{}</c></u>]: {}"
|
||||||
|
TEMPLATE_D = "Adapter[{}] 群聊[<u><e>{}</e></u>] 用户[<u><e>{}</e></u>] 触发"
|
||||||
|
" [<u><c>{}</c></u>]: {}"
|
||||||
|
TEMPLATE_E = "Adapter[{}] 群聊[<u><e>{}</e></u>] 用户[<u><e>{}</e></u>] 触发"
|
||||||
|
" [<u><c>{}</c></u>] [Target](<u><e>{}</e></u>): {}"
|
||||||
|
|
||||||
|
TEMPLATE_ADAPTER = "Adapter[<m>{}</m>] "
|
||||||
|
TEMPLATE_USER = "用户[<u><e>{}</e></u>] "
|
||||||
|
TEMPLATE_GROUP = "群聊[<u><e>{}</e></u>] "
|
||||||
|
TEMPLATE_COMMAND = "CMD[<u><c>{}</c></u>] "
|
||||||
|
TEMPLATE_PLATFORM = "平台[<u><m>{}</m></u>] "
|
||||||
|
TEMPLATE_TARGET = "[Target]([<u><e>{}</e></u>]) "
|
||||||
|
|
||||||
TEMPLATE_ADAPTER = "Adapter[<m>{}</m>]"
|
|
||||||
TEMPLATE_USER = "用户[<u><e>{}</e></u>]"
|
|
||||||
TEMPLATE_GROUP = "群聊[<u><e>{}</e></u>]"
|
|
||||||
TEMPLATE_COMMAND = "CMD[<u><c>{}</c></u>]"
|
|
||||||
TEMPLATE_PLATFORM = "平台[<u><m>{}</m></u>]"
|
|
||||||
TEMPLATE_TARGET = "[Target]([<u><e>{}</e></u>])"
|
|
||||||
SUCCESS_TEMPLATE = "[<u><c>{}</c></u>]: {} | 参数[{}] 返回: [<y>{}</y>]"
|
SUCCESS_TEMPLATE = "[<u><c>{}</c></u>]: {} | 参数[{}] 返回: [<y>{}</y>]"
|
||||||
|
|
||||||
@classmethod
|
WARNING_TEMPLATE = "[<u><y>{}</y></u>]: {}"
|
||||||
def __parser_template(
|
|
||||||
cls,
|
|
||||||
info: str,
|
|
||||||
command: str | None = None,
|
|
||||||
user_id: int | str | None = None,
|
|
||||||
group_id: int | str | None = None,
|
|
||||||
adapter: str | None = None,
|
|
||||||
target: Any = None,
|
|
||||||
platform: str | None = None,
|
|
||||||
) -> str:
|
|
||||||
"""
|
|
||||||
优化后的模板解析器,构建并连接日志信息片段。
|
|
||||||
"""
|
|
||||||
parts = []
|
|
||||||
if adapter:
|
|
||||||
parts.append(cls.TEMPLATE_ADAPTER.format(adapter))
|
|
||||||
if platform:
|
|
||||||
parts.append(cls.TEMPLATE_PLATFORM.format(platform))
|
|
||||||
if group_id:
|
|
||||||
parts.append(cls.TEMPLATE_GROUP.format(group_id))
|
|
||||||
if user_id:
|
|
||||||
parts.append(cls.TEMPLATE_USER.format(user_id))
|
|
||||||
if command:
|
|
||||||
parts.append(cls.TEMPLATE_COMMAND.format(command))
|
|
||||||
if target:
|
|
||||||
parts.append(cls.TEMPLATE_TARGET.format(target))
|
|
||||||
|
|
||||||
parts.append(info)
|
ERROR_TEMPLATE = "[<u><r>{}</r></u>]: {}"
|
||||||
return " ".join(parts)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _log(
|
|
||||||
cls,
|
|
||||||
level: str,
|
|
||||||
info: str,
|
|
||||||
command: str | None = None,
|
|
||||||
session: int | str | Session | uninfoSession | None = None,
|
|
||||||
group_id: int | str | None = None,
|
|
||||||
adapter: str | None = None,
|
|
||||||
target: Any = None,
|
|
||||||
platform: str | None = None,
|
|
||||||
e: Exception | None = None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
核心日志处理方法,处理所有日志级别的通用逻辑。
|
|
||||||
"""
|
|
||||||
user_id: str | None = str(session) if isinstance(session, int | str) else None
|
|
||||||
|
|
||||||
if isinstance(session, Session):
|
|
||||||
user_id = session.id1
|
|
||||||
adapter = session.bot_type
|
|
||||||
group_id = f"{session.id3}:{session.id2}" if session.id3 else session.id2
|
|
||||||
platform = platform or session.platform
|
|
||||||
elif isinstance(session, uninfoSession):
|
|
||||||
user_id = session.user.id
|
|
||||||
adapter = session.adapter
|
|
||||||
if session.group:
|
|
||||||
group_id = session.group.id
|
|
||||||
platform = session.basic.get("scope")
|
|
||||||
|
|
||||||
template = cls.__parser_template(
|
|
||||||
info, command, user_id, group_id, adapter, target, platform
|
|
||||||
)
|
|
||||||
|
|
||||||
if e:
|
|
||||||
template += f" || 错误 <r>{type(e).__name__}: {e}</r>"
|
|
||||||
|
|
||||||
try:
|
|
||||||
log_func = getattr(logger_.opt(colors=True), level)
|
|
||||||
log_func(template)
|
|
||||||
except Exception:
|
|
||||||
log_func_fallback = getattr(logger_, level)
|
|
||||||
log_func_fallback(template)
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -136,6 +70,7 @@ class logger:
|
|||||||
target: Any = None,
|
target: Any = None,
|
||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
): ...
|
): ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
def info(
|
def info(
|
||||||
@@ -147,6 +82,7 @@ class logger:
|
|||||||
target: Any = None,
|
target: Any = None,
|
||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
): ...
|
): ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
def info(
|
def info(
|
||||||
@@ -171,16 +107,28 @@ class logger:
|
|||||||
target: Any = None,
|
target: Any = None,
|
||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
):
|
):
|
||||||
cls._log(
|
user_id: str | None = session # type: ignore
|
||||||
"info",
|
if isinstance(session, Session):
|
||||||
info=info,
|
user_id = session.id1
|
||||||
command=command,
|
adapter = session.bot_type
|
||||||
session=session,
|
if session.id3:
|
||||||
group_id=group_id,
|
group_id = f"{session.id3}:{session.id2}"
|
||||||
adapter=adapter,
|
elif session.id2:
|
||||||
target=target,
|
group_id = f"{session.id2}"
|
||||||
platform=platform,
|
platform = platform or session.platform
|
||||||
|
elif isinstance(session, uninfoSession):
|
||||||
|
user_id = session.user.id
|
||||||
|
adapter = session.adapter
|
||||||
|
if session.group:
|
||||||
|
group_id = session.group.id
|
||||||
|
platform = session.basic["scope"]
|
||||||
|
template = cls.__parser_template(
|
||||||
|
info, command, user_id, group_id, adapter, target, platform
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
logger_.opt(colors=True).info(template)
|
||||||
|
except Exception:
|
||||||
|
logger_.info(template)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def success(
|
def success(
|
||||||
@@ -190,11 +138,9 @@ class logger:
|
|||||||
param: dict[str, Any] | None = None,
|
param: dict[str, Any] | None = None,
|
||||||
result: str = "",
|
result: str = "",
|
||||||
):
|
):
|
||||||
param_str = (
|
param_str = ""
|
||||||
",".join([f"<m>{k}</m>:<g>{v}</g>" for k, v in param.items()])
|
if param:
|
||||||
if param
|
param_str = ",".join([f"<m>{k}</m>:<g>{v}</g>" for k, v in param.items()])
|
||||||
else ""
|
|
||||||
)
|
|
||||||
logger_.opt(colors=True).success(
|
logger_.opt(colors=True).success(
|
||||||
cls.SUCCESS_TEMPLATE.format(command, info, param_str, result)
|
cls.SUCCESS_TEMPLATE.format(command, info, param_str, result)
|
||||||
)
|
)
|
||||||
@@ -213,6 +159,7 @@ class logger:
|
|||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
e: Exception | None = None,
|
e: Exception | None = None,
|
||||||
): ...
|
): ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
def warning(
|
def warning(
|
||||||
@@ -221,10 +168,12 @@ class logger:
|
|||||||
command: str | None = None,
|
command: str | None = None,
|
||||||
*,
|
*,
|
||||||
session: Session | None = None,
|
session: Session | None = None,
|
||||||
|
adapter: str | None = None,
|
||||||
target: Any = None,
|
target: Any = None,
|
||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
e: Exception | None = None,
|
e: Exception | None = None,
|
||||||
): ...
|
): ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
def warning(
|
def warning(
|
||||||
@@ -233,6 +182,7 @@ class logger:
|
|||||||
command: str | None = None,
|
command: str | None = None,
|
||||||
*,
|
*,
|
||||||
session: uninfoSession | None = None,
|
session: uninfoSession | None = None,
|
||||||
|
adapter: str | None = None,
|
||||||
target: Any = None,
|
target: Any = None,
|
||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
e: Exception | None = None,
|
e: Exception | None = None,
|
||||||
@@ -251,17 +201,30 @@ class logger:
|
|||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
e: Exception | None = None,
|
e: Exception | None = None,
|
||||||
):
|
):
|
||||||
cls._log(
|
user_id: str | None = session # type: ignore
|
||||||
"warning",
|
if isinstance(session, Session):
|
||||||
info=info,
|
user_id = session.id1
|
||||||
command=command,
|
adapter = session.bot_type
|
||||||
session=session,
|
if session.id3:
|
||||||
group_id=group_id,
|
group_id = f"{session.id3}:{session.id2}"
|
||||||
adapter=adapter,
|
elif session.id2:
|
||||||
target=target,
|
group_id = f"{session.id2}"
|
||||||
platform=platform,
|
platform = platform or session.platform
|
||||||
e=e,
|
elif isinstance(session, uninfoSession):
|
||||||
|
user_id = session.user.id
|
||||||
|
adapter = session.adapter
|
||||||
|
if session.group:
|
||||||
|
group_id = session.group.id
|
||||||
|
platform = session.basic["scope"]
|
||||||
|
template = cls.__parser_template(
|
||||||
|
info, command, user_id, group_id, adapter, target, platform
|
||||||
)
|
)
|
||||||
|
if e:
|
||||||
|
template += f" || 错误<r>{type(e)}: {e}</r>"
|
||||||
|
try:
|
||||||
|
logger_.opt(colors=True).warning(template)
|
||||||
|
except Exception as e:
|
||||||
|
logger_.warning(template)
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -277,6 +240,7 @@ class logger:
|
|||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
e: Exception | None = None,
|
e: Exception | None = None,
|
||||||
): ...
|
): ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
def error(
|
def error(
|
||||||
@@ -289,6 +253,7 @@ class logger:
|
|||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
e: Exception | None = None,
|
e: Exception | None = None,
|
||||||
): ...
|
): ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
def error(
|
def error(
|
||||||
@@ -315,17 +280,30 @@ class logger:
|
|||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
e: Exception | None = None,
|
e: Exception | None = None,
|
||||||
):
|
):
|
||||||
cls._log(
|
user_id: str | None = session # type: ignore
|
||||||
"error",
|
if isinstance(session, Session):
|
||||||
info=info,
|
user_id = session.id1
|
||||||
command=command,
|
adapter = session.bot_type
|
||||||
session=session,
|
if session.id3:
|
||||||
group_id=group_id,
|
group_id = f"{session.id3}:{session.id2}"
|
||||||
adapter=adapter,
|
elif session.id2:
|
||||||
target=target,
|
group_id = f"{session.id2}"
|
||||||
platform=platform,
|
platform = platform or session.platform
|
||||||
e=e,
|
elif isinstance(session, uninfoSession):
|
||||||
|
user_id = session.user.id
|
||||||
|
adapter = session.adapter
|
||||||
|
if session.group:
|
||||||
|
group_id = session.group.id
|
||||||
|
platform = session.basic["scope"]
|
||||||
|
template = cls.__parser_template(
|
||||||
|
info, command, user_id, group_id, adapter, target, platform
|
||||||
)
|
)
|
||||||
|
if e:
|
||||||
|
template += f" || 错误 <r>{type(e)}: {e}</r>"
|
||||||
|
try:
|
||||||
|
logger_.opt(colors=True).error(template)
|
||||||
|
except Exception as e:
|
||||||
|
logger_.error(template)
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -341,6 +319,7 @@ class logger:
|
|||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
e: Exception | None = None,
|
e: Exception | None = None,
|
||||||
): ...
|
): ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
def debug(
|
def debug(
|
||||||
@@ -353,6 +332,7 @@ class logger:
|
|||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
e: Exception | None = None,
|
e: Exception | None = None,
|
||||||
): ...
|
): ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
def debug(
|
def debug(
|
||||||
@@ -379,78 +359,62 @@ class logger:
|
|||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
e: Exception | None = None,
|
e: Exception | None = None,
|
||||||
):
|
):
|
||||||
cls._log(
|
user_id: str | None = session # type: ignore
|
||||||
"debug",
|
if isinstance(session, Session):
|
||||||
info=info,
|
user_id = session.id1
|
||||||
command=command,
|
adapter = session.bot_type
|
||||||
session=session,
|
if session.id3:
|
||||||
group_id=group_id,
|
group_id = f"{session.id3}:{session.id2}"
|
||||||
adapter=adapter,
|
elif session.id2:
|
||||||
target=target,
|
group_id = f"{session.id2}"
|
||||||
platform=platform,
|
platform = platform or session.platform
|
||||||
e=e,
|
elif isinstance(session, uninfoSession):
|
||||||
|
user_id = session.user.id
|
||||||
|
adapter = session.adapter
|
||||||
|
if session.group:
|
||||||
|
group_id = session.group.id
|
||||||
|
platform = session.basic["scope"]
|
||||||
|
template = cls.__parser_template(
|
||||||
|
info, command, user_id, group_id, adapter, target, platform
|
||||||
)
|
)
|
||||||
|
if e:
|
||||||
|
template += f" || 错误 <r>{type(e)}: {e}</r>"
|
||||||
|
try:
|
||||||
|
logger_.opt(colors=True).debug(template)
|
||||||
|
except Exception as e:
|
||||||
|
logger_.debug(template)
|
||||||
|
|
||||||
@overload
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def trace(
|
def __parser_template(
|
||||||
cls,
|
cls,
|
||||||
info: str,
|
info: str,
|
||||||
command: str | None = None,
|
command: str | None = None,
|
||||||
*,
|
user_id: int | str | None = None,
|
||||||
session: int | str | None = None,
|
|
||||||
group_id: int | str | None = None,
|
group_id: int | str | None = None,
|
||||||
adapter: str | None = None,
|
adapter: str | None = None,
|
||||||
target: Any = None,
|
target: Any = None,
|
||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
e: Exception | None = None,
|
) -> str:
|
||||||
): ...
|
arg_list = []
|
||||||
@overload
|
template = ""
|
||||||
@classmethod
|
if adapter is not None:
|
||||||
def trace(
|
template += cls.TEMPLATE_ADAPTER
|
||||||
cls,
|
arg_list.append(adapter)
|
||||||
info: str,
|
if platform is not None:
|
||||||
command: str | None = None,
|
template += cls.TEMPLATE_PLATFORM
|
||||||
*,
|
arg_list.append(platform)
|
||||||
session: Session | None = None,
|
if group_id is not None:
|
||||||
target: Any = None,
|
template += cls.TEMPLATE_GROUP
|
||||||
platform: str | None = None,
|
arg_list.append(group_id)
|
||||||
e: Exception | None = None,
|
if user_id is not None:
|
||||||
): ...
|
template += cls.TEMPLATE_USER
|
||||||
@overload
|
arg_list.append(user_id)
|
||||||
@classmethod
|
if command is not None:
|
||||||
def trace(
|
template += cls.TEMPLATE_COMMAND
|
||||||
cls,
|
arg_list.append(command)
|
||||||
info: str,
|
if target is not None:
|
||||||
command: str | None = None,
|
template += cls.TEMPLATE_TARGET
|
||||||
*,
|
arg_list.append(target)
|
||||||
session: uninfoSession | None = None,
|
arg_list.append(info)
|
||||||
target: Any = None,
|
template += "{}"
|
||||||
platform: str | None = None,
|
return template.format(*arg_list)
|
||||||
e: Exception | None = None,
|
|
||||||
): ...
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def trace(
|
|
||||||
cls,
|
|
||||||
info: str,
|
|
||||||
command: str | None = None,
|
|
||||||
*,
|
|
||||||
session: int | str | Session | uninfoSession | None = None,
|
|
||||||
group_id: int | str | None = None,
|
|
||||||
adapter: str | None = None,
|
|
||||||
target: Any = None,
|
|
||||||
platform: str | None = None,
|
|
||||||
e: Exception | None = None,
|
|
||||||
):
|
|
||||||
cls._log(
|
|
||||||
"trace",
|
|
||||||
info=info,
|
|
||||||
command=command,
|
|
||||||
session=session,
|
|
||||||
group_id=group_id,
|
|
||||||
adapter=adapter,
|
|
||||||
target=target,
|
|
||||||
platform=platform,
|
|
||||||
e=e,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from nonebot.utils import is_coroutine_callable
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
|
||||||
|
|
||||||
driver = nonebot.get_driver()
|
driver = nonebot.get_driver()
|
||||||
|
|
||||||
@@ -101,6 +100,6 @@ class PluginInitManager:
|
|||||||
logger.error(f"执行: {module_path}:remove 失败", e=e)
|
logger.error(f"执行: {module_path}:remove 失败", e=e)
|
||||||
|
|
||||||
|
|
||||||
@PriorityLifecycle.on_startup(priority=5)
|
@driver.on_startup
|
||||||
async def _():
|
async def _():
|
||||||
await PluginInitManager.install_all()
|
await PluginInitManager.install_all()
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import random
|
import random
|
||||||
import sys
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
from strenum import StrEnum
|
||||||
|
|
||||||
from ._build_image import BuildImage
|
from ._build_image import BuildImage
|
||||||
|
|
||||||
if sys.version_info >= (3, 11):
|
|
||||||
from enum import StrEnum
|
|
||||||
else:
|
|
||||||
from strenum import StrEnum
|
|
||||||
|
|
||||||
|
|
||||||
class MatType(StrEnum):
|
class MatType(StrEnum):
|
||||||
LINE = "LINE"
|
LINE = "LINE"
|
||||||
|
|||||||
@@ -1,47 +1,4 @@
|
|||||||
import sys
|
from strenum import StrEnum
|
||||||
|
|
||||||
if sys.version_info >= (3, 11):
|
|
||||||
from enum import StrEnum
|
|
||||||
else:
|
|
||||||
from strenum import StrEnum
|
|
||||||
|
|
||||||
|
|
||||||
class PriorityLifecycleType(StrEnum):
|
|
||||||
STARTUP = "STARTUP"
|
|
||||||
"""启动"""
|
|
||||||
SHUTDOWN = "SHUTDOWN"
|
|
||||||
"""关闭"""
|
|
||||||
|
|
||||||
|
|
||||||
class BotSentType(StrEnum):
|
|
||||||
GROUP = "GROUP"
|
|
||||||
PRIVATE = "PRIVATE"
|
|
||||||
|
|
||||||
|
|
||||||
class BankHandleType(StrEnum):
|
|
||||||
DEPOSIT = "DEPOSIT"
|
|
||||||
"""存款"""
|
|
||||||
WITHDRAW = "WITHDRAW"
|
|
||||||
"""取款"""
|
|
||||||
LOAN = "LOAN"
|
|
||||||
"""贷款"""
|
|
||||||
REPAYMENT = "REPAYMENT"
|
|
||||||
"""还款"""
|
|
||||||
INTEREST = "INTEREST"
|
|
||||||
"""利息"""
|
|
||||||
|
|
||||||
|
|
||||||
class EventLogType(StrEnum):
|
|
||||||
GROUP_MEMBER_INCREASE = "GROUP_MEMBER_INCREASE"
|
|
||||||
"""群成员增加"""
|
|
||||||
GROUP_MEMBER_DECREASE = "GROUP_MEMBER_DECREASE"
|
|
||||||
"""群成员减少"""
|
|
||||||
KICK_MEMBER = "KICK_MEMBER"
|
|
||||||
"""踢出群成员"""
|
|
||||||
KICK_BOT = "KICK_BOT"
|
|
||||||
"""踢出Bot"""
|
|
||||||
LEAVE_MEMBER = "LEAVE_MEMBER"
|
|
||||||
"""主动退群"""
|
|
||||||
|
|
||||||
|
|
||||||
class GoldHandle(StrEnum):
|
class GoldHandle(StrEnum):
|
||||||
@@ -135,9 +92,7 @@ class RequestType(StrEnum):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
FRIEND = "FRIEND"
|
FRIEND = "FRIEND"
|
||||||
"""好友"""
|
|
||||||
GROUP = "GROUP"
|
GROUP = "GROUP"
|
||||||
"""群组"""
|
|
||||||
|
|
||||||
|
|
||||||
class RequestHandleType(StrEnum):
|
class RequestHandleType(StrEnum):
|
||||||
|
|||||||
@@ -1,15 +1,3 @@
|
|||||||
class HookPriorityException(BaseException):
|
|
||||||
"""
|
|
||||||
钩子优先级异常
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, info: str = "") -> None:
|
|
||||||
self.info = info
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return self.info
|
|
||||||
|
|
||||||
|
|
||||||
class NotFoundError(Exception):
|
class NotFoundError(Exception):
|
||||||
"""
|
"""
|
||||||
未发现
|
未发现
|
||||||
|
|||||||