Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea6759824e | ||
|
|
2542436d5b | ||
|
|
e93b3b998e | ||
|
|
401fc5e203 | ||
|
|
4a103f5675 | ||
|
|
46a924ca46 | ||
|
|
40a81efa24 | ||
|
|
6ddfbe2ac1 | ||
|
|
2ccd05f4cf | ||
|
|
358c7f502c | ||
|
|
ae4aa5c29c | ||
|
|
7ec11474f8 | ||
|
|
601738c421 | ||
|
|
94939d4665 | ||
|
|
cd2fd77789 | ||
|
|
ea8d874f0c | ||
|
|
96ba8d5a21 | ||
|
|
f86beb928f | ||
|
|
52b32915cc | ||
|
|
be316a5caf | ||
|
|
ff0b37123e | ||
|
|
82dbdb91a4 | ||
|
|
5e8ce3239e | ||
|
|
587396eb49 | ||
|
|
a9ceb33adb | ||
|
|
af75d7fc5a | ||
|
|
a8251165fa | ||
|
|
ed23ad319a | ||
|
|
cb9c5834df | ||
|
|
93ad6b354c | ||
|
|
420f7e2bfc | ||
|
|
4fd816fa3b | ||
|
|
47a40492ae | ||
|
|
142afde336 | ||
|
|
36667f9e19 | ||
|
|
564e1b07b2 | ||
|
|
c89e75e268 | ||
|
|
e6fd27018d | ||
|
|
2c457b7595 | ||
|
|
6f139b3afa | ||
|
|
b74f8dfd33 | ||
|
|
4a76c86e2e | ||
|
|
47ec5bc7b9 | ||
|
|
a3cbfefaa1 | ||
|
|
632dff3bad | ||
|
|
e5ea00eb1a | ||
|
|
4b225a3be9 | ||
|
|
26150c2924 | ||
|
|
0939013a89 | ||
|
|
c9f0a8b9d9 | ||
|
|
e5b2a872d3 | ||
|
|
68460d18cc | ||
|
|
c839b44256 | ||
|
|
70bde00757 | ||
|
|
eb6d90ae88 | ||
|
|
4b8013d2d6 | ||
|
|
d528711641 | ||
|
|
1cc18bb195 | ||
|
|
74a9f3a843 | ||
|
|
e7f3c210df | ||
|
|
f94121080f | ||
|
|
761c8daac4 | ||
|
|
c667fc215e | ||
|
|
07be73c1b7 | ||
|
|
7e6896fa01 | ||
|
|
3cc882b116 | ||
|
|
ee699fb345 | ||
|
|
631e66d54f | ||
|
|
c7ef6fdb17 | ||
|
|
fb0a9813e1 | ||
|
|
6940c2f37b | ||
|
|
74ce848127 | ||
|
|
9e5c4aa3e7 | ||
|
|
7f460296dd | ||
|
|
b505307f2f | ||
|
|
4ab9382205 | ||
|
|
1e2aa99207 | ||
|
|
7472cabd48 | ||
|
|
d9e65057cf | ||
|
|
b12168b6b9 | ||
|
|
a63f26c3b6 | ||
|
|
095a123c3c | ||
|
|
f9a38a26b2 | ||
|
|
6124e217d0 | ||
|
|
11524bcb04 | ||
|
|
d5e5fac02d | ||
|
|
55da0046a2 | ||
|
|
977f0b13b3 | ||
|
|
2fed781350 | ||
|
|
6d1789bbee | ||
|
|
3deffcb46c | ||
|
|
be86e0bb7f | ||
|
|
67a2560de4 | ||
|
|
5c96761fd0 | ||
|
|
872d2499a2 | ||
|
|
7719be9866 | ||
|
|
7c153721f0 | ||
|
|
59d72c3b3d | ||
|
|
c571bfb133 | ||
|
|
da6d5b4be4 | ||
|
|
62fac483f2 | ||
|
|
61251ce137 | ||
|
|
30fe5a5393 | ||
|
|
3cf7c1d237 | ||
|
|
91f35ad63a | ||
|
|
a0b57b6bea | ||
|
|
205f4ff1fa | ||
|
|
b993450a23 | ||
|
|
d218c569d4 | ||
|
|
faa91b8bd4 | ||
|
|
582ad8c996 | ||
|
|
46a0768a45 | ||
|
|
8649aaaa54 | ||
|
|
6283c3d13d | ||
|
|
8f1e35954b | ||
|
|
9686a31419 | ||
|
|
632ec3e46e | ||
|
|
fb8811207e | ||
|
|
99eacdfc12 | ||
|
|
acfed0837a | ||
|
|
4bcc5aeea5 | ||
|
|
bd62698ea5 | ||
|
|
2921aed248 | ||
|
|
579558e59b | ||
|
|
fcb385cf01 | ||
|
|
c3193dd784 | ||
|
|
48cbb2bf1d | ||
|
|
1e7ae38684 | ||
|
|
1c5f66beee | ||
|
|
e0773174d0 | ||
|
|
8996cdf8f1 | ||
|
|
87f02fd0ef | ||
|
|
9625bdcd26 | ||
|
|
8b9ae7255b | ||
|
|
b537ec15b7 | ||
|
|
d0f296bc9c | ||
|
|
a020ea5c87 | ||
|
|
14f5842f10 | ||
|
|
6298685e09 | ||
|
|
96db5bf2a5 | ||
|
|
d6d54175f6 | ||
|
|
10e883f0ca | ||
|
|
a4ddfcd8ac | ||
|
|
ee9a2a6cb0 | ||
|
|
62b0b02466 | ||
|
|
4cc800c832 | ||
|
|
13579f5842 | ||
|
|
99f1388e23 | ||
|
|
bcfb47d9fd | ||
|
|
e1bf1ba87f | ||
|
|
46a652bb27 | ||
|
|
e7bae73c8f | ||
|
|
611f0b62ba | ||
|
|
a8447b7074 | ||
|
|
7baf9f296c | ||
|
|
53eb8c8484 | ||
|
|
b0461865a5 | ||
|
|
b5c2850e28 | ||
|
|
8c0a600525 | ||
|
|
9cda0e5d8f | ||
|
|
6546eb990b | ||
|
|
ff75e2ee92 | ||
|
|
6769c724cb | ||
|
|
b38509b2f5 | ||
|
|
5a0af6a64b | ||
|
|
bc2e06a9ec | ||
|
|
b5f101546a | ||
|
|
ccc4f27e3d | ||
|
|
2d8320b5a0 |
@@ -10,6 +10,9 @@ SESSION_EXPIRE_TIMEOUT=00:00:30
|
|||||||
|
|
||||||
ALCONNA_USE_COMMAND_START=True
|
ALCONNA_USE_COMMAND_START=True
|
||||||
|
|
||||||
|
# ws连接密钥,若bot能被公网访问则建议打开该注释并设置该配置项
|
||||||
|
# ONEBOT_ACCESS_TOKEN=""
|
||||||
|
|
||||||
# 全局图片统一使用bytes发送,当真寻与协议端不在同一服务器上时为True
|
# 全局图片统一使用bytes发送,当真寻与协议端不在同一服务器上时为True
|
||||||
IMAGE_TO_BYTES = True
|
IMAGE_TO_BYTES = True
|
||||||
|
|
||||||
@@ -27,6 +30,19 @@ QBOT_ID_DATA = '{
|
|||||||
# 示例: "sqlite:data/db/zhenxun.db" 在data目录下建立db文件夹
|
# 示例: "sqlite:data/db/zhenxun.db" 在data目录下建立db文件夹
|
||||||
DB_URL = ""
|
DB_URL = ""
|
||||||
|
|
||||||
|
# NONE: 不使用缓存, MEMORY: 使用内存缓存, REDIS: 使用Redis缓存
|
||||||
|
CACHE_MODE = NONE
|
||||||
|
|
||||||
|
# REDIS配置,使用REDIS替换Cache内存缓存
|
||||||
|
# REDIS地址
|
||||||
|
# REDIS_HOST = "127.0.0.1"
|
||||||
|
# REDIS端口
|
||||||
|
# REDIS_PORT = 6379
|
||||||
|
# REDIS密码
|
||||||
|
# REDIS_PASSWORD = ""
|
||||||
|
# REDIS过期时间
|
||||||
|
# REDIS_EXPIRE = 600
|
||||||
|
|
||||||
# 系统代理
|
# 系统代理
|
||||||
# SYSTEM_PROXY = "http://127.0.0.1:7890"
|
# SYSTEM_PROXY = "http://127.0.0.1:7890"
|
||||||
|
|
||||||
@@ -45,12 +45,9 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- language: python
|
- language: python
|
||||||
build-mode: none
|
build-mode: none
|
||||||
- language: javascript-typescript
|
|
||||||
build-mode: none
|
|
||||||
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
|
||||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#
|
||||||
|
name: Create and publish a Docker image
|
||||||
|
|
||||||
|
# Configures this workflow to run on demand via workflow_dispatch.
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
|
||||||
|
jobs:
|
||||||
|
build-and-push-image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
attestations: write
|
||||||
|
id-token: write
|
||||||
|
#
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
|
||||||
|
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see [Usage](https://github.com/docker/build-push-action#usage) in the README of the `docker/build-push-action` repository.
|
||||||
|
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: push
|
||||||
|
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
|
# This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see [Using artifact attestations to establish provenance for builds](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds).
|
||||||
|
- name: Generate artifact attestation
|
||||||
|
uses: actions/attest-build-provenance@v2
|
||||||
|
with:
|
||||||
|
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
|
||||||
|
subject-digest: ${{ steps.push.outputs.digest }}
|
||||||
|
push-to-registry: true
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
name: Force Sync to Aliyun
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
jobs:
|
||||||
|
sync:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config --global http.postBuffer 524288000
|
||||||
|
git config --global core.compression 0
|
||||||
|
|
||||||
|
- name: Add aliyun remote
|
||||||
|
run: |
|
||||||
|
git remote add aliyun https://${{secrets.ALIYUN_ACCOUNT}}:${{secrets.ALIYUN_PASSWORD}}@codeup.aliyun.com/67a361cf556e6cdab537117a/zhenxun-org/zhenxun_bot.git
|
||||||
|
git fetch aliyun main --force # 强制更新本地引用
|
||||||
|
|
||||||
|
- name: Force push
|
||||||
|
run: git push --progress --force aliyun HEAD:main
|
||||||
@@ -32,6 +32,7 @@ MANIFEST
|
|||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
*.manifest
|
*.manifest
|
||||||
*.spec
|
*.spec
|
||||||
|
!resources.spec
|
||||||
|
|
||||||
# Installer logs
|
# Installer logs
|
||||||
pip-log.txt
|
pip-log.txt
|
||||||
@@ -139,22 +140,11 @@ dmypy.json
|
|||||||
# Cython debug symbols
|
# Cython debug symbols
|
||||||
cython_debug/
|
cython_debug/
|
||||||
|
|
||||||
demo.py
|
|
||||||
test.py
|
|
||||||
server_ip.py
|
|
||||||
member_activity_handle.py
|
|
||||||
Yu-Gi-Oh/
|
|
||||||
csgo/
|
|
||||||
fantasy_card/
|
|
||||||
data/
|
data/
|
||||||
log/
|
log/
|
||||||
backup/
|
backup/
|
||||||
extensive_plugin/
|
|
||||||
test/
|
|
||||||
bot.py
|
|
||||||
.idea/
|
.idea/
|
||||||
resources/
|
resources/
|
||||||
/configs/config.py
|
|
||||||
configs/config.yaml
|
|
||||||
.vscode/launch.json
|
.vscode/launch.json
|
||||||
plugins_/
|
|
||||||
|
./.env.dev
|
||||||
@@ -11,6 +11,8 @@
|
|||||||
"displayname",
|
"displayname",
|
||||||
"flmt",
|
"flmt",
|
||||||
"getbbox",
|
"getbbox",
|
||||||
|
"gitcode",
|
||||||
|
"GITEE",
|
||||||
"hibiapi",
|
"hibiapi",
|
||||||
"httpx",
|
"httpx",
|
||||||
"jsdelivr",
|
"jsdelivr",
|
||||||
@@ -27,6 +29,7 @@
|
|||||||
"unban",
|
"unban",
|
||||||
"Uninfo",
|
"Uninfo",
|
||||||
"userinfo",
|
"userinfo",
|
||||||
|
"webui",
|
||||||
"zhenxun"
|
"zhenxun"
|
||||||
],
|
],
|
||||||
"python.analysis.autoImportCompletions": true,
|
"python.analysis.autoImportCompletions": true,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
<div align=center>
|
<div align=center>
|
||||||
|
|
||||||
[文档](https://hibikier.github.io/zhenxun_bot/)
|
[文档](https://zhenxun-org.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/HibiKier/zhenxun_bot_webui) | 管理 | [hibikier](https://github.com/HibiKier) | 基于真寻 WebApi 的 webui 实现 [预览](#-webui界面展示) |
|
| [WebUi](https://github.com/zhenxun-org/zhenxun_bot) | 管理 | [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,10 +121,32 @@ AccessToken: PUBLIC_ZHENXUN_TEST
|
|||||||
|
|
||||||
- 实现了许多功能,且提供了大量功能管理命令,进行了多平台适配,兼容 nb2 商店插件
|
- 实现了许多功能,且提供了大量功能管理命令,进行了多平台适配,兼容 nb2 商店插件
|
||||||
- 拥有完善可用的 webui
|
- 拥有完善可用的 webui
|
||||||
- 通过 Config 配置项将所有插件配置统计保存至 config.yaml,利于统一用户修改
|
- 通过 Config 配置项将所有插件配置统一保存至 config.yaml,利于统一用户修改
|
||||||
- 方便增删插件,原生 nonebot2 matcher,不需要额外修改,仅仅通过简单的配置属性就可以生成`帮助图片`和`帮助信息`
|
- 方便增删插件,原生 nonebot2 matcher,不需要额外修改,仅仅通过简单的配置属性就可以生成`帮助图片`和`帮助信息`
|
||||||
- 提供了 cd,阻塞,每日次数等限制,仅仅通过简单的属性就可以生成一个限制,例如:`PluginCdBlock` 等
|
- 提供了 cd,阻塞,每日次数等限制,仅仅通过简单的属性就可以生成一个限制,例如:`PluginCdBlock` 等
|
||||||
- **更多详细请通过 [传送门](https://hibikier.github.io/zhenxun_bot/) 查看文档!**
|
- **更多详细请通过 [传送门](https://zhenxun-org.github.io/zhenxun_bot/) 查看文档!**
|
||||||
|
|
||||||
|
## 🐣 小白整合
|
||||||
|
|
||||||
|
如果你系统是 **Windows** 且不想下载 Python
|
||||||
|
可以使用整合包(Python3.10+zhenxun+webui)
|
||||||
|
|
||||||
|
文档地址:[整合包文档](https://zhenxun-org.github.io/zhenxun_bot/beginner)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>下载地址</summary>
|
||||||
|
|
||||||
|
- **百度云:**
|
||||||
|
https://pan.baidu.com/s/1MKGOoIgQW1qom_KT3rNhlg?pwd=t7iz
|
||||||
|
|
||||||
|
- **夸克网盘:**
|
||||||
|
https://pan.quark.cn/s/b4dc6cb8fb08
|
||||||
|
访问码:Yi46
|
||||||
|
|
||||||
|
- **Google Drive:**
|
||||||
|
https://drive.google.com/drive/folders/1dcTWhPZhSt9WqLehyjF6Gj0CXQCS4OWh?usp=drive_link
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## 🛠️ 简单部署
|
## 🛠️ 简单部署
|
||||||
|
|
||||||
@@ -150,7 +172,7 @@ poetry run python bot.py
|
|||||||
|
|
||||||
1.在 .env.dev 文件中填写你的机器人配置项
|
1.在 .env.dev 文件中填写你的机器人配置项
|
||||||
|
|
||||||
2.在 configs/config.yaml 文件中修改你需要修改的插件配置项
|
2.在 data/config.yaml 文件中修改你需要修改的插件配置项
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>数据库地址(DB_URL)配置说明</summary>
|
<summary>数据库地址(DB_URL)配置说明</summary>
|
||||||
@@ -265,6 +287,18 @@ DB_URL 是基于 Tortoise ORM 的数据库连接字符串,用于指定项目
|
|||||||
|
|
||||||
[Zer](https://afdian.com/u/6bccdb2a60b411ec9ad452540025c377) [爱发电用户\_HTjk](https://afdian.com/u/6c7d0208064511ec8d7b52540025c377) [shenghuo2](https://afdian.com/u/bca13286102111eda2a052540025c377) [术樱](https://afdian.com/u/414da63a09a311ec8eb752540025c377) [飞火](https://afdian.com/u/404135f48ed711ec962152540025c377) [shenqi](https://afdian.net/u/fa923a8cfe3d11eba61752540025c377) [A_Kyuu](https://afdian.net/u/b83954fc2c1211eba9eb52540025c377) [疯狂混沌](https://afdian.net/u/789a2f9200cd11edb38352540025c377) [投冥](https://afdian.net/a/144514mm) [茶喵](https://afdian.net/u/fd22382eac4d11ecbfc652540025c377) [AemokpaTNR](https://afdian.net/u/1169bb8c8a9611edb0c152540025c377) [爱发电用户\_wrxn](https://afdian.net/u/4aa03d20db4311ecb1e752540025c377) [qqw](https://afdian.net/u/b71db4e2cc3e11ebb76652540025c377) [溫一壺月光下酒](https://afdian.net/u/ad667a5c650c11ed89bf52540025c377) [伝木](https://afdian.net/u/246b80683f9511edba7552540025c377) [阿奎](https://afdian.net/u/da41f72845d511ed930d52540025c377) [醉梦尘逸](https://afdian.net/u/bc11d2683cd011ed99b552540025c377) [Abc](https://afdian.net/u/870dc10a3cd311ed828852540025c377) [本喵无敌哒](https://afdian.net/u/dffaa9005bc911ebb69b52540025c377) [椎名冬羽](https://afdian.net/u/ca1ebd64395e11ed81b452540025c377) [kaito](https://afdian.net/u/a055e20a498811eab1f052540025c377) [笑柒 XIAO_Q7](https://afdian.net/u/4696db5c529111ec84ea52540025c377) [请问一份爱多少钱](https://afdian.net/u/f57ef6602dbd11ed977f52540025c377) [咸鱼鱼鱼鱼](https://afdian.net/u/8e39b9a400e011ed9f4a52540025c377) [Kafka](https://afdian.net/u/41d66798ef6911ecbc5952540025c377) [墨然](https://afdian.net/u/8aa5874a644d11eb8a6752540025c377) [爱发电用户\_T9e4](https://afdian.net/u/2ad1bb82f3a711eca22852540025c377) [笑柒 XIAO_Q7](https://afdian.net/u/4696db5c529111ec84ea52540025c377) [noahzark](https://afdian.net/a/noahzark) [腊条](https://afdian.net/u/f739c4d69eca11eba94b52540025c377) [ze roller](https://afdian.net/u/0e599e96257211ed805152540025c377) [爱发电用户\_4jrf](https://afdian.net/u/6b2cdcc817c611ed949152540025c377) [爱发电用户\_TBsd](https://afdian.net/u/db638b60217911ed9efd52540025c377) [烟寒若雨](https://afdian.net/u/067bd2161eec11eda62b52540025c377) [ln](https://afdian.net/u/b51914ba1c6611ed8a4e52540025c377) [爱发电用户\_b9S4](https://afdian.net/u/3d8f30581a2911edba6d52540025c377) [爱发电用户\_c58s](https://afdian.net/u/a6ad8dda195e11ed9a4152540025c377) [爱发电用户\_eNr9](https://afdian.net/u/05fdb41c0c9a11ed814952540025c377) [MangataAkihi](https://github.com/Sakuracio) [炀](https://afdian.net/u/69b76e9ec77b11ec874f52540025c377) [爱发电用户\_Bc6j](https://afdian.net/u/8546be24f44111eca64052540025c377) [大魔王](https://github.com/xipesoy) [CopilotLaLaLa](https://github.com/CopilotLaLaLa) [嘿小欧](https://afdian.net/u/daa4bec4f24911ec82e552540025c377) [回忆的秋千](https://afdian.net/u/e315d9c6f14f11ecbeef52540025c377) [十年くん](https://github.com/shinianj) [哇](https://afdian.net/u/9b266244f23911eca19052540025c377) [yajiwa](https://github.com/yajiwa) [爆金币](https://afdian.net/u/0d78879ef23711ecb22452540025c377)...
|
[Zer](https://afdian.com/u/6bccdb2a60b411ec9ad452540025c377) [爱发电用户\_HTjk](https://afdian.com/u/6c7d0208064511ec8d7b52540025c377) [shenghuo2](https://afdian.com/u/bca13286102111eda2a052540025c377) [术樱](https://afdian.com/u/414da63a09a311ec8eb752540025c377) [飞火](https://afdian.com/u/404135f48ed711ec962152540025c377) [shenqi](https://afdian.net/u/fa923a8cfe3d11eba61752540025c377) [A_Kyuu](https://afdian.net/u/b83954fc2c1211eba9eb52540025c377) [疯狂混沌](https://afdian.net/u/789a2f9200cd11edb38352540025c377) [投冥](https://afdian.net/a/144514mm) [茶喵](https://afdian.net/u/fd22382eac4d11ecbfc652540025c377) [AemokpaTNR](https://afdian.net/u/1169bb8c8a9611edb0c152540025c377) [爱发电用户\_wrxn](https://afdian.net/u/4aa03d20db4311ecb1e752540025c377) [qqw](https://afdian.net/u/b71db4e2cc3e11ebb76652540025c377) [溫一壺月光下酒](https://afdian.net/u/ad667a5c650c11ed89bf52540025c377) [伝木](https://afdian.net/u/246b80683f9511edba7552540025c377) [阿奎](https://afdian.net/u/da41f72845d511ed930d52540025c377) [醉梦尘逸](https://afdian.net/u/bc11d2683cd011ed99b552540025c377) [Abc](https://afdian.net/u/870dc10a3cd311ed828852540025c377) [本喵无敌哒](https://afdian.net/u/dffaa9005bc911ebb69b52540025c377) [椎名冬羽](https://afdian.net/u/ca1ebd64395e11ed81b452540025c377) [kaito](https://afdian.net/u/a055e20a498811eab1f052540025c377) [笑柒 XIAO_Q7](https://afdian.net/u/4696db5c529111ec84ea52540025c377) [请问一份爱多少钱](https://afdian.net/u/f57ef6602dbd11ed977f52540025c377) [咸鱼鱼鱼鱼](https://afdian.net/u/8e39b9a400e011ed9f4a52540025c377) [Kafka](https://afdian.net/u/41d66798ef6911ecbc5952540025c377) [墨然](https://afdian.net/u/8aa5874a644d11eb8a6752540025c377) [爱发电用户\_T9e4](https://afdian.net/u/2ad1bb82f3a711eca22852540025c377) [笑柒 XIAO_Q7](https://afdian.net/u/4696db5c529111ec84ea52540025c377) [noahzark](https://afdian.net/a/noahzark) [腊条](https://afdian.net/u/f739c4d69eca11eba94b52540025c377) [ze roller](https://afdian.net/u/0e599e96257211ed805152540025c377) [爱发电用户\_4jrf](https://afdian.net/u/6b2cdcc817c611ed949152540025c377) [爱发电用户\_TBsd](https://afdian.net/u/db638b60217911ed9efd52540025c377) [烟寒若雨](https://afdian.net/u/067bd2161eec11eda62b52540025c377) [ln](https://afdian.net/u/b51914ba1c6611ed8a4e52540025c377) [爱发电用户\_b9S4](https://afdian.net/u/3d8f30581a2911edba6d52540025c377) [爱发电用户\_c58s](https://afdian.net/u/a6ad8dda195e11ed9a4152540025c377) [爱发电用户\_eNr9](https://afdian.net/u/05fdb41c0c9a11ed814952540025c377) [MangataAkihi](https://github.com/Sakuracio) [炀](https://afdian.net/u/69b76e9ec77b11ec874f52540025c377) [爱发电用户\_Bc6j](https://afdian.net/u/8546be24f44111eca64052540025c377) [大魔王](https://github.com/xipesoy) [CopilotLaLaLa](https://github.com/CopilotLaLaLa) [嘿小欧](https://afdian.net/u/daa4bec4f24911ec82e552540025c377) [回忆的秋千](https://afdian.net/u/e315d9c6f14f11ecbeef52540025c377) [十年くん](https://github.com/shinianj) [哇](https://afdian.net/u/9b266244f23911eca19052540025c377) [yajiwa](https://github.com/yajiwa) [爆金币](https://afdian.net/u/0d78879ef23711ecb22452540025c377)...
|
||||||
|
|
||||||
|
### 特别赞助
|
||||||
|
|
||||||
|
<div align=center>
|
||||||
|
|
||||||
|
<img width="60%" src="https://edgeone.ai/media/34fe3a45-492d-4ea4-ae5d-ea1087ca7b4b.png" />
|
||||||
|
|
||||||
|
[亚洲最佳CDN、边缘和安全解决方案 - Tencent EdgeOne](https://edgeone.ai/zh?from=github)
|
||||||
|
|
||||||
|
**本项目 CDN 加速及安全防护由 Tencent EdgeOne 赞助**
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
## 📜 贡献指南
|
## 📜 贡献指南
|
||||||
|
|
||||||
欢迎查看我们的 [贡献指南](CONTRIBUTING.md) 和 [行为守则](CODE_OF_CONDUCT.md) 以了解如何参与贡献。
|
欢迎查看我们的 [贡献指南](CONTRIBUTING.md) 和 [行为守则](CODE_OF_CONDUCT.md) 以了解如何参与贡献。
|
||||||
@@ -272,12 +306,12 @@ DB_URL 是基于 Tortoise ORM 的数据库连接字符串,用于指定项目
|
|||||||
## ❔ 需要帮助?
|
## ❔ 需要帮助?
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> 发起 [issue](https://github.com/HibiKier/zhenxun_bot/issues/new/choose) 前,我们希望你能够阅读过或者了解 [提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)
|
> 发起 [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)
|
||||||
>
|
>
|
||||||
> - 善用[搜索引擎](https://www.google.com/)
|
> - 善用[搜索引擎](https://www.google.com/)
|
||||||
> - 查阅 issue 中是否有类似问题,如果没有请按照模板发起 issue
|
> - 查阅 issue 中是否有类似问题,如果没有请按照模板发起 issue
|
||||||
|
|
||||||
欢迎前往 [issue](https://github.com/HibiKier/zhenxun_bot/issues/new/choose) 中提出你遇到的问题,或者加入我们的 [用户群](https://qm.qq.com/q/mRNtLSl6uc) 或 [技术群](https://qm.qq.com/q/YYYt5rkMYc)与我们联系
|
欢迎前往 [issue](https://github.com/zhenxun-org/zhenxun_bot/issues/new/choose) 中提出你遇到的问题,或者加入我们的 [用户群](https://qm.qq.com/q/mRNtLSl6uc) 或 [技术群](https://qm.qq.com/q/YYYt5rkMYc)与我们联系
|
||||||
|
|
||||||
## 🛠️ 进度追踪
|
## 🛠️ 进度追踪
|
||||||
|
|
||||||
@@ -287,6 +321,8 @@ 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) :超棒的机器人协议
|
||||||
@@ -326,34 +362,68 @@ Project [zhenxun_bot](https://github.com/users/HibiKier/projects/2)
|
|||||||
<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 界面展示
|
## 📸 WebUI 界面展示(仅展示默认主题下的 pc 端)
|
||||||
|
|
||||||
<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;">
|
|
||||||
</div>
|
#### API 设置
|
||||||
<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>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__: v0.2.4-2c97eea
|
__version__: v0.2.4-da6d5b4
|
||||||
|
|||||||
@@ -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, init
|
from zhenxun.services.db_context import disconnect
|
||||||
|
|
||||||
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")
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
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:
|
||||||
|
After Width: | Height: | Size: 388 KiB |
|
After Width: | Height: | Size: 315 KiB |
|
After Width: | Height: | Size: 630 KiB |
|
After Width: | Height: | Size: 708 KiB |
|
After Width: | Height: | Size: 598 KiB |
|
After Width: | Height: | Size: 405 KiB |
|
After Width: | Height: | Size: 250 KiB |
|
After Width: | Height: | Size: 504 KiB |
|
After Width: | Height: | Size: 423 KiB |
|
After Width: | Height: | Size: 551 KiB |
|
After Width: | Height: | Size: 453 KiB |
|
After Width: | Height: | Size: 400 KiB |
|
After Width: | Height: | Size: 336 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 315 KiB |
|
Before Width: | Height: | Size: 352 KiB |
|
Before Width: | Height: | Size: 279 KiB |
|
Before Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 228 KiB |
|
Before Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 193 KiB |
@@ -14,21 +14,21 @@ priority = "primary"
|
|||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.10"
|
python = "^3.10"
|
||||||
playwright = "^1.41.1"
|
playwright = "^1.41.1"
|
||||||
nonebot-adapter-onebot = "^2.3.1"
|
nonebot-adapter-onebot = ">=2.3.1"
|
||||||
nonebot-plugin-apscheduler = "^0.5"
|
nonebot-plugin-apscheduler = "^0.5"
|
||||||
tortoise-orm = { extras = ["asyncpg"], version = "^0.20.0" }
|
tortoise-orm = "^0.20.0"
|
||||||
cattrs = "^23.2.3"
|
cattrs = "^23.2.3"
|
||||||
ruamel-yaml = "^0.18.5"
|
ruamel-yaml = "^0.18.5"
|
||||||
strenum = "^0.4.15"
|
strenum = "^0.4.15"
|
||||||
nonebot-plugin-session = "^0.2.3"
|
nonebot-plugin-session = "^0.3.2"
|
||||||
ujson = "^5.9.0"
|
ujson = ">=5.9.0"
|
||||||
nb-cli = "^1.3.0"
|
nb-cli = ">=1.3.0"
|
||||||
nonebot2 = { extras = ["fastapi"], version = "^2.3.3" }
|
nonebot2 = { extras = ["fastapi"], version = ">=2.3.3" }
|
||||||
pillow = "^10.0.0"
|
pillow = "^10.0.0"
|
||||||
retrying = "^1.3.4"
|
retrying = "^1.3.4"
|
||||||
aiofiles = "^23.2.1"
|
aiofiles = "^23.2.1"
|
||||||
nonebot-plugin-htmlrender = ">=0.6.0,<1.0.0"
|
nonebot-plugin-htmlrender = ">=0.6.0,<1.0.0"
|
||||||
pypinyin = "^0.51.0"
|
pypinyin = ">=0.51.0"
|
||||||
beautifulsoup4 = "^4.12.3"
|
beautifulsoup4 = "^4.12.3"
|
||||||
lxml = "^5.1.0"
|
lxml = "^5.1.0"
|
||||||
psutil = "^5.9.8"
|
psutil = "^5.9.8"
|
||||||
@@ -36,15 +36,20 @@ feedparser = "^6.0.11"
|
|||||||
imagehash = "^4.3.1"
|
imagehash = "^4.3.1"
|
||||||
cn2an = "^0.5.22"
|
cn2an = "^0.5.22"
|
||||||
dateparser = "^1.2.0"
|
dateparser = "^1.2.0"
|
||||||
bilireq = "0.2.3post0"
|
|
||||||
python-jose = { extras = ["cryptography"], version = "^3.3.0" }
|
python-jose = { extras = ["cryptography"], version = "^3.3.0" }
|
||||||
python-multipart = "^0.0.9"
|
python-multipart = "^0.0.9"
|
||||||
aiocache = "^0.12.2"
|
aiocache = {extras = ["redis"], version = "^0.12.3"}
|
||||||
py-cpuinfo = "^9.0.0"
|
py-cpuinfo = "^9.0.0"
|
||||||
nonebot-plugin-alconna = "^0.54.0"
|
nonebot-plugin-alconna = ">=0.56.0"
|
||||||
tenacity = "^9.0.0"
|
tenacity = "^9.0.0"
|
||||||
nonebot-plugin-uninfo = ">0.4.1"
|
nonebot-plugin-uninfo = ">=0.7.3"
|
||||||
pydantic = "1.10.18"
|
nonebot-plugin-waiter = "^0.8.1"
|
||||||
|
multidict = ">=6.0.0,!=6.3.2"
|
||||||
|
pydantic = ">=1.0.0, <2.0.0"
|
||||||
|
redis = { version = ">=5", optional = true }
|
||||||
|
asyncpg = { version = ">=0.20.0", optional = true }
|
||||||
|
alibabacloud-devops20210625 = "^5.0.2"
|
||||||
|
json_repair = "^0.54.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
nonebug = "^0.4"
|
nonebug = "^0.4"
|
||||||
@@ -56,7 +61,6 @@ respx = "^0.21.1"
|
|||||||
ruff = "^0.8.0"
|
ruff = "^0.8.0"
|
||||||
pre-commit = "^4.0.0"
|
pre-commit = "^4.0.0"
|
||||||
|
|
||||||
|
|
||||||
[tool.nonebot]
|
[tool.nonebot]
|
||||||
plugins = [
|
plugins = [
|
||||||
"nonebot_plugin_apscheduler",
|
"nonebot_plugin_apscheduler",
|
||||||
|
|||||||
@@ -14,21 +14,21 @@ priority = "primary"
|
|||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.10"
|
python = "^3.10"
|
||||||
playwright = "^1.41.1"
|
playwright = "^1.41.1"
|
||||||
nonebot-adapter-onebot = "^2.3.1"
|
nonebot-adapter-onebot = ">=2.3.1"
|
||||||
nonebot-plugin-apscheduler = "^0.5"
|
nonebot-plugin-apscheduler = "^0.5"
|
||||||
tortoise-orm = { extras = ["asyncpg"], version = "^0.20.0" }
|
tortoise-orm = "^0.20.0"
|
||||||
cattrs = "^23.2.3"
|
cattrs = "^23.2.3"
|
||||||
ruamel-yaml = "^0.18.5"
|
ruamel-yaml = "^0.18.5"
|
||||||
strenum = "^0.4.15"
|
strenum = "^0.4.15"
|
||||||
nonebot-plugin-session = "^0.2.3"
|
nonebot-plugin-session = "^0.3.2"
|
||||||
ujson = "^5.9.0"
|
ujson = ">=5.9.0"
|
||||||
nb-cli = "^1.3.0"
|
nb-cli = ">=1.3.0"
|
||||||
nonebot2 = { extras = ["fastapi"], version = "^2.3.3" }
|
nonebot2 = { extras = ["fastapi"], version = ">=2.3.3" }
|
||||||
pillow = "^10.0.0"
|
pillow = "^10.0.0"
|
||||||
retrying = "^1.3.4"
|
retrying = "^1.3.4"
|
||||||
aiofiles = "^23.2.1"
|
aiofiles = "^23.2.1"
|
||||||
nonebot-plugin-htmlrender = ">=0.6.0,<1.0.0"
|
nonebot-plugin-htmlrender = ">=0.6.0,<1.0.0"
|
||||||
pypinyin = "^0.51.0"
|
pypinyin = ">=0.51.0"
|
||||||
beautifulsoup4 = "^4.12.3"
|
beautifulsoup4 = "^4.12.3"
|
||||||
lxml = "^5.1.0"
|
lxml = "^5.1.0"
|
||||||
psutil = "^5.9.8"
|
psutil = "^5.9.8"
|
||||||
@@ -36,15 +36,20 @@ feedparser = "^6.0.11"
|
|||||||
imagehash = "^4.3.1"
|
imagehash = "^4.3.1"
|
||||||
cn2an = "^0.5.22"
|
cn2an = "^0.5.22"
|
||||||
dateparser = "^1.2.0"
|
dateparser = "^1.2.0"
|
||||||
bilireq = "0.2.3post0"
|
|
||||||
python-jose = { extras = ["cryptography"], version = "^3.3.0" }
|
python-jose = { extras = ["cryptography"], version = "^3.3.0" }
|
||||||
python-multipart = "^0.0.9"
|
python-multipart = "^0.0.9"
|
||||||
aiocache = "^0.12.2"
|
aiocache = {extras = ["redis"], version = "^0.12.3"}
|
||||||
py-cpuinfo = "^9.0.0"
|
py-cpuinfo = "^9.0.0"
|
||||||
nonebot-plugin-alconna = "^0.54.0"
|
nonebot-plugin-alconna = ">=0.56.0"
|
||||||
tenacity = "^9.0.0"
|
tenacity = "^9.0.0"
|
||||||
nonebot-plugin-uninfo = ">0.4.1"
|
nonebot-plugin-uninfo = ">=0.7.3"
|
||||||
pydantic = "2.10.6"
|
nonebot-plugin-waiter = "^0.8.1"
|
||||||
|
multidict = ">=6.0.0,!=6.3.2"
|
||||||
|
pydantic = ">=2.0.0, <3.0.0"
|
||||||
|
redis = { version = ">=5", optional = true }
|
||||||
|
asyncpg = { version = ">=0.20.0", optional = true }
|
||||||
|
alibabacloud-devops20210625 = "^5.0.2"
|
||||||
|
json_repair = "^0.54.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
nonebug = "^0.4"
|
nonebug = "^0.4"
|
||||||
|
|||||||
@@ -0,0 +1,356 @@
|
|||||||
|
# 权限检查系统优化方案
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
优化 `zhenxun_bot` 的权限检查系统,将每条消息的数据库/缓存查询次数从 **6-10 次** 降低到 **1-2 次**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 当前问题分析
|
||||||
|
|
||||||
|
### 现有查询流程
|
||||||
|
|
||||||
|
每条消息进入时,权限检查系统执行以下查询:
|
||||||
|
|
||||||
|
| 阶段 | 查询内容 | 次数 |
|
||||||
|
| --------------- | ------------------------------------- | ------ |
|
||||||
|
| `_load_context` | PluginInfo, UserConsole, GroupConsole | 3 次 |
|
||||||
|
| `auth_ban` | BanConsole | 1-2 次 |
|
||||||
|
| `auth_bot` | BotConsole | 1 次 |
|
||||||
|
| `auth_admin` | LevelUser (全局+群组) | 1-2 次 |
|
||||||
|
| `auth_limit` | PluginLimit (如果不在内存) | 0-1 次 |
|
||||||
|
|
||||||
|
**总计:6-10 次查询**
|
||||||
|
|
||||||
|
### 问题根源
|
||||||
|
|
||||||
|
1. 数据分散在多个表:`user_console`, `group_console`, `ban_console`, `bot_console`, `level_user`, `plugin_info`
|
||||||
|
2. 每个检查模块独立查询,缺乏数据共享
|
||||||
|
3. 即使有 Redis 缓存,也需要多次网络往返
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 优化方案:预聚合权限快照 (Permission Snapshot)
|
||||||
|
|
||||||
|
### 核心思想
|
||||||
|
|
||||||
|
**用一个 Hash 结构存储权限检查所需的所有数据**,消息到达时只需 1-2 次查询。
|
||||||
|
|
||||||
|
### 数据结构设计
|
||||||
|
|
||||||
|
#### 1. 权限快照 (AuthSnapshot)
|
||||||
|
|
||||||
|
```
|
||||||
|
缓存键格式: AUTH_SNAPSHOT:{user_id}:{group_id}:{bot_id}
|
||||||
|
|
||||||
|
Hash 结构:
|
||||||
|
{
|
||||||
|
# === 用户信息 ===
|
||||||
|
"user_gold": 100, # 用户金币
|
||||||
|
"user_banned": 0, # 0=未ban, -1=永久ban, >0=ban结束时间戳
|
||||||
|
"user_ban_duration": 0, # ban时长(用于计算剩余时间)
|
||||||
|
|
||||||
|
# === 用户权限等级 ===
|
||||||
|
"user_level_global": 0, # 全局权限等级
|
||||||
|
"user_level_group": 0, # 群组权限等级
|
||||||
|
|
||||||
|
# === 群组信息 ===
|
||||||
|
"group_status": 1, # 群组状态 (1=开启, 0=休眠)
|
||||||
|
"group_level": 5, # 群组等级
|
||||||
|
"group_is_super": 0, # 是否超级群组
|
||||||
|
"group_block_plugins": "", # 禁用插件列表 "<plugin1,<plugin2,"
|
||||||
|
"group_superuser_block_plugins": "", # 超级用户禁用插件列表
|
||||||
|
"group_banned": 0, # 群组是否被ban
|
||||||
|
|
||||||
|
# === Bot信息 ===
|
||||||
|
"bot_status": 1, # Bot状态
|
||||||
|
"bot_block_plugins": "", # Bot禁用插件列表
|
||||||
|
|
||||||
|
# === 元数据 ===
|
||||||
|
"version": 1, # 快照版本(用于失效判断)
|
||||||
|
"created_at": 1703859600 # 创建时间戳
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 插件信息缓存 (PluginSnapshot)
|
||||||
|
|
||||||
|
插件是全局的,变化较少,可以使用本地内存缓存 + Redis 双层缓存:
|
||||||
|
|
||||||
|
```
|
||||||
|
缓存键格式: PLUGIN_SNAPSHOT:{module}
|
||||||
|
|
||||||
|
结构:
|
||||||
|
{
|
||||||
|
"status": true, # 全局开关状态
|
||||||
|
"block_type": null, # 禁用类型 (PRIVATE/GROUP/ALL/null)
|
||||||
|
"admin_level": 0, # 调用所需权限等级
|
||||||
|
"cost_gold": 0, # 调用所需金币
|
||||||
|
"level": 5, # 所需群权限等级
|
||||||
|
"limit_superuser": false, # 是否限制超级用户
|
||||||
|
"plugin_type": "NORMAL", # 插件类型
|
||||||
|
"ignore_prompt": false # 是否忽略阻断提示
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 工作流程
|
||||||
|
|
||||||
|
```
|
||||||
|
消息到达
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────────────────────────────────────┐
|
||||||
|
│ 1. 第一次查询:获取权限快照 │
|
||||||
|
│ AUTH_SNAPSHOT:{user_id}:{group_id}:{bot_id} │
|
||||||
|
│ │
|
||||||
|
│ - 如果存在且未过期 → 直接使用 │
|
||||||
|
│ - 如果不存在 → 触发快照构建(异步) │
|
||||||
|
└──────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────────────────────────────────────┐
|
||||||
|
│ 2. 第二次查询:获取插件信息 │
|
||||||
|
│ PLUGIN_SNAPSHOT:{module} │
|
||||||
|
│ │
|
||||||
|
│ - 优先从本地内存缓存获取 │
|
||||||
|
│ - 未命中时从 Redis 获取 │
|
||||||
|
│ - 仍未命中时从 DB 加载并缓存 │
|
||||||
|
└──────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────────────────────────────────────┐
|
||||||
|
│ 3. 执行权限检查(纯内存计算,无 I/O) │
|
||||||
|
│ │
|
||||||
|
│ - ban 检查 │
|
||||||
|
│ - bot 状态检查 │
|
||||||
|
│ - 插件状态检查 │
|
||||||
|
│ - 群组状态检查 │
|
||||||
|
│ - 权限等级检查 │
|
||||||
|
│ - 金币检查 │
|
||||||
|
└──────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
权限检查完成
|
||||||
|
```
|
||||||
|
|
||||||
|
### 缓存失效策略
|
||||||
|
|
||||||
|
#### 主动失效(事件驱动)
|
||||||
|
|
||||||
|
| 事件 | 失效范围 |
|
||||||
|
| ---------------- | ----------------------------------------- |
|
||||||
|
| 用户金币变化 | `AUTH_SNAPSHOT:{user_id}:*:*` |
|
||||||
|
| 用户被 ban/unban | `AUTH_SNAPSHOT:{user_id}:*:*` |
|
||||||
|
| 群组设置变更 | `AUTH_SNAPSHOT:*:{group_id}:*` |
|
||||||
|
| Bot 配置变更 | `AUTH_SNAPSHOT:*:*:{bot_id}` |
|
||||||
|
| 插件配置变更 | `PLUGIN_SNAPSHOT:{module}` + 本地内存缓存 |
|
||||||
|
| 用户权限变更 | `AUTH_SNAPSHOT:{user_id}:{group_id}:*` |
|
||||||
|
|
||||||
|
#### 被动失效(TTL)
|
||||||
|
|
||||||
|
- 权限快照 TTL:**60 秒**(权衡实时性和性能)
|
||||||
|
- 插件快照 TTL:**300 秒**(插件配置变化较少)
|
||||||
|
- 本地内存缓存 TTL:**30 秒**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实现计划
|
||||||
|
|
||||||
|
### Phase 1: 基础设施 ✅ [已完成]
|
||||||
|
|
||||||
|
- [x] 新增 `CacheType.AUTH_SNAPSHOT` 和 `CacheType.PLUGIN_SNAPSHOT`
|
||||||
|
- [x] 创建 `AuthSnapshot` Pydantic 模型
|
||||||
|
- [x] 创建 `PluginSnapshot` Pydantic 模型
|
||||||
|
- [x] 实现快照构建器 `SnapshotBuilder`
|
||||||
|
|
||||||
|
### Phase 2: 快照服务 ✅ [已完成]
|
||||||
|
|
||||||
|
- [x] 创建 `AuthSnapshotService` 类
|
||||||
|
|
||||||
|
- [x] `get_snapshot(user_id, group_id, bot_id)` - 获取权限快照
|
||||||
|
- [x] `build_snapshot(user_id, group_id, bot_id)` - 构建权限快照
|
||||||
|
- [x] `invalidate_user(user_id)` - 失效用户相关快照
|
||||||
|
- [x] `invalidate_group(group_id)` - 失效群组相关快照
|
||||||
|
- [x] `invalidate_bot(bot_id)` - 失效 Bot 相关快照
|
||||||
|
|
||||||
|
- [x] 创建 `PluginSnapshotService` 类
|
||||||
|
- [x] `get_plugin(module)` - 获取插件信息(本地缓存优先)
|
||||||
|
- [x] `invalidate_plugin(module)` - 失效插件缓存
|
||||||
|
- [x] `warmup()` - 预热所有插件缓存
|
||||||
|
|
||||||
|
### Phase 3: 权限检查器重构 ✅ [已完成]
|
||||||
|
|
||||||
|
- [x] 创建新的 `OptimizedAuthChecker` 类
|
||||||
|
- [x] 基于快照数据的权限检查逻辑
|
||||||
|
- [x] 无 I/O 的纯内存计算
|
||||||
|
- [x] 保持与现有系统的兼容性
|
||||||
|
|
||||||
|
### Phase 4: 缓存失效集成 ⏳ [可选优化]
|
||||||
|
|
||||||
|
> 注:当前实现使用 TTL 自动过期机制,以下为可选的主动失效优化
|
||||||
|
|
||||||
|
- [ ] 在 `UserConsole` 的写操作中添加失效逻辑
|
||||||
|
- [ ] 在 `GroupConsole` 的写操作中添加失效逻辑
|
||||||
|
- [ ] 在 `BanConsole` 的写操作中添加失效逻辑
|
||||||
|
- [ ] 在 `BotConsole` 的写操作中添加失效逻辑
|
||||||
|
- [ ] 在 `LevelUser` 的写操作中添加失效逻辑
|
||||||
|
- [ ] 在 `PluginInfo` 的写操作中添加失效逻辑
|
||||||
|
|
||||||
|
### Phase 5: 测试与验证 ⏳ [待测试]
|
||||||
|
|
||||||
|
- [ ] 单元测试
|
||||||
|
- [ ] 性能对比测试
|
||||||
|
- [ ] 边界情况测试
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
zhenxun/
|
||||||
|
├── services/
|
||||||
|
│ └── auth_snapshot/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── models.py # AuthSnapshot, PluginSnapshot 模型
|
||||||
|
│ ├── builder.py # 快照构建器
|
||||||
|
│ ├── service.py # 快照服务
|
||||||
|
│ └── checker.py # 优化后的权限检查器
|
||||||
|
└── builtin_plugins/
|
||||||
|
└── hooks/
|
||||||
|
└── auth_checker_v2.py # 新版权限检查入口
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 性能预期
|
||||||
|
|
||||||
|
| 指标 | 优化前 | 优化后 | 提升 |
|
||||||
|
| -------------- | ------- | ---------- | -------- |
|
||||||
|
| DB 查询次数 | 6-10 次 | **1-3 次** | 70-85%↓ |
|
||||||
|
| 平均延迟 | ~50ms | ~10ms | 80%↓ |
|
||||||
|
| Redis 连接压力 | 高 | 低 | 显著降低 |
|
||||||
|
|
||||||
|
### 查询优化详情
|
||||||
|
|
||||||
|
**优化前(5-7 次 DB 查询):**
|
||||||
|
|
||||||
|
1. UserConsole - 用户金币
|
||||||
|
2. LevelUser (全局) - 全局权限等级
|
||||||
|
3. LevelUser (群组) - 群组权限等级
|
||||||
|
4. BanConsole (用户全局)
|
||||||
|
5. BanConsole (用户群组)
|
||||||
|
6. BanConsole (群组)
|
||||||
|
7. GroupConsole - 群组信息
|
||||||
|
8. BotConsole - Bot 信息
|
||||||
|
|
||||||
|
**优化后(1-3 次 DB 查询):**
|
||||||
|
|
||||||
|
1. **单条复合 SQL** - 使用 UNION ALL 合并 UserConsole + LevelUser + BanConsole(1 次)
|
||||||
|
- ✅ 支持 **MySQL** (使用 `%s` 占位符)
|
||||||
|
- ✅ 支持 **PostgreSQL** (使用 `$1, $2...` 占位符)
|
||||||
|
- ✅ 支持 **SQLite** (使用 `?` 占位符)
|
||||||
|
- ✅ 使用**参数化查询**防止 SQL 注入
|
||||||
|
2. GroupConsole - **内存缓存 60s**,变化时失效(0-1 次)
|
||||||
|
3. BotConsole - **内存缓存 300s**,变化时失效(0-1 次)
|
||||||
|
|
||||||
|
**最优情况**:缓存命中时只需 1 次 DB 查询
|
||||||
|
**最差情况**:3 次 DB 查询(全部未命中缓存)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 风险与缓解
|
||||||
|
|
||||||
|
| 风险 | 缓解措施 |
|
||||||
|
| ---------------- | -------------------------------------------- |
|
||||||
|
| 快照数据过期 | 合理的 TTL + 主动失效机制 |
|
||||||
|
| 快照构建延迟 | 异步构建 + 首次访问降级到旧流程 |
|
||||||
|
| 内存占用增加 | 监控内存使用 + 合理的缓存清理 |
|
||||||
|
| 数据一致性 | 写操作后立即失效缓存 |
|
||||||
|
| **DB 过载风险** | **全局 Semaphore 限制并发构建数量 (50)** |
|
||||||
|
| **并发构建重复** | **按 cache_key 的 asyncio.Lock** |
|
||||||
|
| **构建等待超时** | **3 秒超时后返回默认快照,允许请求继续处理** |
|
||||||
|
|
||||||
|
### 并发控制机制
|
||||||
|
|
||||||
|
```
|
||||||
|
大量消息同时进入时:
|
||||||
|
|
||||||
|
1. 同一 user:group:bot 组合
|
||||||
|
- 使用 asyncio.Lock 保证只构建一次
|
||||||
|
- 其他等待的协程复用同一个 Future 结果
|
||||||
|
|
||||||
|
2. 不同 user:group:bot 组合
|
||||||
|
- 使用全局 Semaphore 限制最多 50 个并发构建
|
||||||
|
- 超过限制的请求排队等待(最多 3 秒)
|
||||||
|
- 等待超时则返回默认快照,避免请求阻塞
|
||||||
|
|
||||||
|
这样即使 1000 个不同用户同时发消息:
|
||||||
|
- 最多只有 50 个并发 DB 查询
|
||||||
|
- 每个构建 5-7 次查询 = 最多 350 次并发 DB 查询
|
||||||
|
- 远低于直接查询的 6000 次
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
### 方式一:替换原有权限检查器(推荐)
|
||||||
|
|
||||||
|
修改 `zhenxun/builtin_plugins/hooks/__init__.py`,将 `auth_checker` 替换为 `auth_checker_v2`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 原来的导入
|
||||||
|
# from . import auth_checker
|
||||||
|
|
||||||
|
# 替换为
|
||||||
|
from . import auth_checker_v2
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方式二:并行测试
|
||||||
|
|
||||||
|
同时加载两个版本,通过日志对比性能:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from . import auth_checker # 原版本
|
||||||
|
from . import auth_checker_v2 # 优化版本(会覆盖原版本的 run_preprocessor)
|
||||||
|
```
|
||||||
|
|
||||||
|
### API 使用示例
|
||||||
|
|
||||||
|
```python
|
||||||
|
from zhenxun.services.auth_snapshot import (
|
||||||
|
AuthSnapshotService,
|
||||||
|
PluginSnapshotService,
|
||||||
|
AuthSnapshot,
|
||||||
|
PluginSnapshot,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取权限快照
|
||||||
|
snapshot = await AuthSnapshotService.get_snapshot(
|
||||||
|
user_id="123456",
|
||||||
|
group_id="789012",
|
||||||
|
bot_id="bot_001"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查用户是否被ban
|
||||||
|
if snapshot.is_user_banned():
|
||||||
|
print(f"用户被ban,剩余时间: {snapshot.get_user_ban_remaining()}秒")
|
||||||
|
|
||||||
|
# 获取插件快照
|
||||||
|
plugin = await PluginSnapshotService.get_plugin("example_plugin")
|
||||||
|
if plugin and plugin.cost_gold > 0:
|
||||||
|
print(f"此插件需要 {plugin.cost_gold} 金币")
|
||||||
|
|
||||||
|
# 手动失效缓存(数据更新时调用)
|
||||||
|
await AuthSnapshotService.invalidate_user("123456")
|
||||||
|
await PluginSnapshotService.invalidate_plugin("example_plugin")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 进度追踪
|
||||||
|
|
||||||
|
- 开始日期:2025-12-29
|
||||||
|
- 当前阶段:核心功能已完成
|
||||||
|
- 状态:✅ 基础功能完成,待测试验证
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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' ]
|
||||||
@@ -14,21 +14,21 @@ priority = "primary"
|
|||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.10"
|
python = "^3.10"
|
||||||
playwright = "^1.41.1"
|
playwright = "^1.41.1"
|
||||||
nonebot-adapter-onebot = "^2.3.1"
|
nonebot-adapter-onebot = ">=2.3.1"
|
||||||
nonebot-plugin-apscheduler = "^0.5"
|
nonebot-plugin-apscheduler = "^0.5"
|
||||||
tortoise-orm = { extras = ["asyncpg"], version = "^0.20.0" }
|
tortoise-orm = "^0.20.0"
|
||||||
cattrs = "^23.2.3"
|
cattrs = "^23.2.3"
|
||||||
ruamel-yaml = "^0.18.5"
|
ruamel-yaml = "^0.18.5"
|
||||||
strenum = "^0.4.15"
|
strenum = "^0.4.15"
|
||||||
nonebot-plugin-session = "^0.2.3"
|
nonebot-plugin-session = "^0.3.2"
|
||||||
ujson = "^5.9.0"
|
ujson = ">=5.9.0"
|
||||||
nb-cli = "^1.3.0"
|
nb-cli = ">=1.3.0"
|
||||||
nonebot2 = { extras = ["fastapi"], version = "^2.3.3" }
|
nonebot2 = { extras = ["fastapi"], version = ">=2.3.3" }
|
||||||
pillow = "^10.0.0"
|
pillow = "^10.0.0"
|
||||||
retrying = "^1.3.4"
|
retrying = "^1.3.4"
|
||||||
aiofiles = "^23.2.1"
|
aiofiles = "^23.2.1"
|
||||||
nonebot-plugin-htmlrender = ">=0.6.0,<1.0.0"
|
nonebot-plugin-htmlrender = ">=0.6.0,<1.0.0"
|
||||||
pypinyin = "^0.51.0"
|
pypinyin = ">=0.51.0"
|
||||||
beautifulsoup4 = "^4.12.3"
|
beautifulsoup4 = "^4.12.3"
|
||||||
lxml = "^5.1.0"
|
lxml = "^5.1.0"
|
||||||
psutil = "^5.9.8"
|
psutil = "^5.9.8"
|
||||||
@@ -36,15 +36,20 @@ feedparser = "^6.0.11"
|
|||||||
imagehash = "^4.3.1"
|
imagehash = "^4.3.1"
|
||||||
cn2an = "^0.5.22"
|
cn2an = "^0.5.22"
|
||||||
dateparser = "^1.2.0"
|
dateparser = "^1.2.0"
|
||||||
bilireq = "0.2.3post0"
|
|
||||||
python-jose = { extras = ["cryptography"], version = "^3.3.0" }
|
python-jose = { extras = ["cryptography"], version = "^3.3.0" }
|
||||||
python-multipart = "^0.0.9"
|
python-multipart = "^0.0.9"
|
||||||
aiocache = "^0.12.2"
|
aiocache = {extras = ["redis"], version = "^0.12.3"}
|
||||||
py-cpuinfo = "^9.0.0"
|
py-cpuinfo = "^9.0.0"
|
||||||
nonebot-plugin-alconna = "^0.54.0"
|
nonebot-plugin-alconna = ">=0.56.0"
|
||||||
tenacity = "^9.0.0"
|
tenacity = "^9.0.0"
|
||||||
nonebot-plugin-uninfo = ">0.4.1"
|
nonebot-plugin-uninfo = ">=0.7.3"
|
||||||
nonebot-plugin-waiter = "^0.8.1"
|
nonebot-plugin-waiter = "^0.8.1"
|
||||||
|
multidict = ">=6.0.0,!=6.3.2"
|
||||||
|
json_repair = "^0.54.0"
|
||||||
|
|
||||||
|
redis = { version = ">=5", optional = true }
|
||||||
|
asyncpg = { version = ">=0.20.0", optional = true }
|
||||||
|
alibabacloud-devops20210625 = "^5.0.2"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
nonebug = "^0.4"
|
nonebug = "^0.4"
|
||||||
@@ -56,6 +61,9 @@ respx = "^0.21.1"
|
|||||||
ruff = "^0.8.0"
|
ruff = "^0.8.0"
|
||||||
pre-commit = "^4.0.0"
|
pre-commit = "^4.0.0"
|
||||||
|
|
||||||
|
[tool.poetry.extras]
|
||||||
|
redis = ["redis"]
|
||||||
|
postgresql = ["asyncpg"]
|
||||||
|
|
||||||
[tool.nonebot]
|
[tool.nonebot]
|
||||||
plugins = [
|
plugins = [
|
||||||
|
|||||||
@@ -1,130 +1,36 @@
|
|||||||
aiocache==0.12.3 ; python_version >= "3.10" and python_version < "4.0"
|
playwright>=1.41.1,<2.0.0
|
||||||
aiofiles==23.2.1 ; python_version >= "3.10" and python_version < "4.0"
|
nonebot-adapter-onebot>=2.3.1
|
||||||
aiosqlite==0.17.0 ; python_version >= "3.10" and python_version < "4.0"
|
nonebot-plugin-apscheduler>=0.5,<0.6
|
||||||
annotated-types==0.7.0 ; python_version >= "3.10" and python_version < "4.0"
|
tortoise-orm>=0.20.0,<0.21.0
|
||||||
anyio==4.8.0 ; python_version >= "3.10" and python_version < "4.0"
|
cattrs>=23.2.3,<24.0.0
|
||||||
apscheduler==3.11.0 ; python_version >= "3.10" and python_version < "4.0"
|
ruamel.yaml>=0.18.5,<0.19.0
|
||||||
arclet-alconna-tools==0.7.10 ; python_version >= "3.10" and python_version < "4.0"
|
strenum>=0.4.15,<0.5.0
|
||||||
arclet-alconna==1.8.35 ; python_version >= "3.10" and python_version < "4.0"
|
nonebot-plugin-session>=0.3.2,<0.4.0
|
||||||
arrow==1.3.0 ; python_version >= "3.10" and python_version < "4.0"
|
ujson>=5.9.0
|
||||||
async-timeout==5.0.1 ; python_version == "3.10"
|
nb-cli>=1.3.0
|
||||||
asyncpg==0.30.0 ; python_version >= "3.10" and python_version < "4.0"
|
nonebot2[fastapi]>=2.3.3
|
||||||
attrs==25.1.0 ; python_version >= "3.10" and python_version < "4.0"
|
Pillow>=10.0.0,<11.0.0
|
||||||
beautifulsoup4==4.13.3 ; python_version >= "3.10" and python_version < "4.0"
|
retrying>=1.3.4,<2.0.0
|
||||||
bilireq==0.2.3.post0 ; python_version >= "3.10" and python_version < "4.0"
|
aiofiles>=23.2.1,<24.0.0
|
||||||
binaryornot==0.4.4 ; python_version >= "3.10" and python_version < "4.0"
|
nonebot-plugin-htmlrender>=0.6.0,<1.0.0
|
||||||
cashews==7.4.0 ; python_version >= "3.10" and python_version < "4.0"
|
pypinyin>=0.51.0
|
||||||
cattrs==23.2.3 ; python_version >= "3.10" and python_version < "4.0"
|
beautifulsoup4>=4.12.3,<5.0.0
|
||||||
certifi==2025.1.31 ; python_version >= "3.10" and python_version < "4.0"
|
lxml>=5.1.0,<6.0.0
|
||||||
cffi==1.17.1 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy"
|
psutil>=5.9.8,<6.0.0
|
||||||
chardet==5.2.0 ; python_version >= "3.10" and python_version < "4.0"
|
feedparser>=6.0.11,<7.0.0
|
||||||
charset-normalizer==3.4.1 ; python_version >= "3.10" and python_version < "4.0"
|
ImageHash>=4.3.1,<5.0.0
|
||||||
click==8.1.8 ; python_version >= "3.10" and python_version < "4.0"
|
cn2an>=0.5.22,<0.6.0
|
||||||
cn2an==0.5.23 ; python_version >= "3.10" and python_version < "4.0"
|
dateparser>=1.2.0,<2.0.0
|
||||||
colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" and (platform_system == "Windows" or sys_platform == "win32")
|
python-jose[cryptography]>=3.3.0,<4.0.0
|
||||||
cookiecutter==2.6.0 ; python_version >= "3.10" and python_version < "4.0"
|
python-multipart>=0.0.9,<0.1.0
|
||||||
cryptography==44.0.1 ; python_version >= "3.10" and python_version < "4.0"
|
aiocache[redis]>=0.12.3,<0.13.0
|
||||||
dateparser==1.2.1 ; python_version >= "3.10" and python_version < "4.0"
|
py-cpuinfo>=9.0.0,<10.0.0
|
||||||
distlib==0.3.9 ; python_version >= "3.10" and python_version < "4.0"
|
nonebot-plugin-alconna>=0.56.0
|
||||||
ecdsa==0.19.0 ; python_version >= "3.10" and python_version < "4.0"
|
tenacity>=9.0.0,<10.0.0
|
||||||
exceptiongroup==1.2.2 ; python_version >= "3.10" and python_version < "4.0"
|
nonebot-plugin-uninfo>=0.7.3
|
||||||
fastapi==0.115.8 ; python_version >= "3.10" and python_version < "4.0"
|
nonebot-plugin-waiter>=0.8.1,<0.9.0
|
||||||
feedparser==6.0.11 ; python_version >= "3.10" and python_version < "4.0"
|
multidict>=6.0.0,<7.0.0,!=6.3.2
|
||||||
filelock==3.17.0 ; python_version >= "3.10" and python_version < "4.0"
|
alibabacloud-devops20210625>=5.0.2,<6.0.0
|
||||||
greenlet==3.1.1 ; python_version >= "3.10" and python_version < "4.0"
|
json_repair>=0.54.0,<0.55.0
|
||||||
grpcio==1.70.0 ; python_version >= "3.10" and python_version < "4.0"
|
redis>=5
|
||||||
h11==0.14.0 ; python_version >= "3.10" and python_version < "4.0"
|
asyncpg>=0.20.0
|
||||||
httpcore==0.16.3 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
httptools==0.6.4 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
httpx==0.23.3 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
idna==3.10 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
imagehash==4.3.2 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
importlib-metadata==8.6.1 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
iso8601==1.1.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
jinja2==3.1.5 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
loguru==0.7.3 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
lxml==5.3.1 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
markdown-it-py==3.0.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
markdown==3.7 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
markupsafe==3.0.2 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
mdurl==0.1.2 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
msgpack==1.1.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
multidict==6.1.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
nb-cli==1.4.2 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
nepattern==0.7.7 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
nonebot-adapter-onebot==2.4.6 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
nonebot-plugin-alconna==0.54.2 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
nonebot-plugin-apscheduler==0.5.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
nonebot-plugin-htmlrender==0.6.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
nonebot-plugin-session==0.2.3 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
nonebot-plugin-uninfo==0.6.8 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
nonebot-plugin-waiter==0.8.1 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
nonebot2==2.4.1 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
nonebot2[fastapi]==2.4.1 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
noneprompt==0.1.9 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
numpy==2.2.2 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
pillow==10.4.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
platformdirs==4.3.6 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
playwright==1.50.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
proces==0.1.7 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
prompt-toolkit==3.0.50 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
propcache==0.2.1 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
protobuf==4.25.6 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
psutil==5.9.8 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
py-cpuinfo==9.0.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
pyasn1==0.6.1 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
pycparser==2.22 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy"
|
|
||||||
pydantic-core==2.27.2 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
pydantic==2.10.6 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
pyee==12.1.1 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
pyfiglet==1.0.2 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
pygments==2.19.1 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
pygtrie==2.5.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
pymdown-extensions==10.14.3 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
pypika-tortoise==0.1.6 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
pypinyin==0.51.0 ; python_version >= "3.10" and python_version < "4"
|
|
||||||
python-dateutil==2.9.0.post0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
python-dotenv==1.0.1 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
python-jose[cryptography]==3.3.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
python-markdown-math==0.8 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
python-multipart==0.0.9 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
python-slugify==8.0.4 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
pytz==2025.1 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
pywavelets==1.8.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
pyyaml==6.0.2 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
regex==2024.11.6 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
requests==2.32.3 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
retrying==1.3.4 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
rfc3986[idna2008]==1.5.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
rich==13.9.4 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
rsa==4.9 ; python_version >= "3.10" and python_version < "4"
|
|
||||||
ruamel-yaml-clib==0.2.12 ; platform_python_implementation == "CPython" and python_version < "3.13" and python_version >= "3.10"
|
|
||||||
ruamel-yaml==0.18.10 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
scipy==1.15.1 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
sgmllib3k==1.0.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
six==1.17.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
sniffio==1.3.1 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
soupsieve==2.6 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
starlette==0.45.3 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
strenum==0.4.15 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
tarina==0.6.8 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
tenacity==9.0.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
text-unidecode==1.3 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
tomli==2.2.1 ; python_version == "3.10"
|
|
||||||
tomlkit==0.13.2 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
tortoise-orm[asyncpg]==0.20.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
types-python-dateutil==2.9.0.20241206 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
typing-extensions==4.12.2 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
tzdata==2025.1 ; python_version >= "3.10" and python_version < "4.0" and platform_system == "Windows"
|
|
||||||
tzlocal==5.2 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
ujson==5.10.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
urllib3==2.3.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
uvicorn[standard]==0.34.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
uvloop==0.21.0 ; sys_platform != "win32" and sys_platform != "cygwin" and platform_python_implementation != "PyPy" and python_version >= "3.10" and python_version < "4.0"
|
|
||||||
virtualenv==20.29.2 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
watchfiles==0.24.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
wcwidth==0.2.13 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
websockets==14.2 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
win32-setctime==1.2.0 ; python_version >= "3.10" and python_version < "4.0" and sys_platform == "win32"
|
|
||||||
yarl==1.18.3 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
zipp==3.21.0 ; python_version >= "3.10" and python_version < "4.0"
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
require_resources_version: ">=1.0.0"
|
||||||
@@ -9,11 +9,16 @@ import zipfile
|
|||||||
from nonebot.adapters.onebot.v11 import Bot
|
from nonebot.adapters.onebot.v11 import Bot
|
||||||
from nonebot.adapters.onebot.v11.message import Message
|
from nonebot.adapters.onebot.v11.message import Message
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
import pytest
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from respx import MockRouter
|
from respx import MockRouter
|
||||||
|
|
||||||
from tests.config import BotId, GroupId, MessageId, UserId
|
from tests.config import BotId, GroupId, MessageId, UserId
|
||||||
from tests.utils import _v11_group_message_event, _v11_private_message_send
|
from tests.utils import (
|
||||||
|
_v11_group_message_event,
|
||||||
|
_v11_private_message_send,
|
||||||
|
get_reply_cq,
|
||||||
|
)
|
||||||
from tests.utils import get_response_json as _get_response_json
|
from tests.utils import get_response_json as _get_response_json
|
||||||
|
|
||||||
|
|
||||||
@@ -27,60 +32,32 @@ def init_mocked_api(mocked_api: MockRouter) -> None:
|
|||||||
name="release_latest",
|
name="release_latest",
|
||||||
).respond(json=get_response_json("release_latest.json"))
|
).respond(json=get_response_json("release_latest.json"))
|
||||||
|
|
||||||
mocked_api.head(
|
|
||||||
url="https://raw.githubusercontent.com/",
|
|
||||||
name="head_raw",
|
|
||||||
).respond(text="")
|
|
||||||
mocked_api.head(
|
|
||||||
url="https://github.com/",
|
|
||||||
name="head_github",
|
|
||||||
).respond(text="")
|
|
||||||
mocked_api.head(
|
|
||||||
url="https://codeload.github.com/",
|
|
||||||
name="head_codeload",
|
|
||||||
).respond(text="")
|
|
||||||
|
|
||||||
mocked_api.get(
|
|
||||||
url="https://raw.githubusercontent.com/HibiKier/zhenxun_bot/dev/__version__",
|
|
||||||
name="dev_branch_version",
|
|
||||||
).respond(text="__version__: v0.2.2-e6f17c4")
|
|
||||||
mocked_api.get(
|
|
||||||
url="https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/__version__",
|
|
||||||
name="main_branch_version",
|
|
||||||
).respond(text="__version__: v0.2.2-e6f17c4")
|
|
||||||
mocked_api.get(
|
|
||||||
url="https://api.github.com/repos/HibiKier/zhenxun_bot/tarball/v0.2.2",
|
|
||||||
name="release_download_url",
|
|
||||||
).respond(
|
|
||||||
status_code=302,
|
|
||||||
headers={
|
|
||||||
"Location": "https://codeload.github.com/HibiKier/zhenxun_bot/legacy.tar.gz/refs/tags/v0.2.2"
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
tar_buffer = io.BytesIO()
|
tar_buffer = io.BytesIO()
|
||||||
zip_bytes = io.BytesIO()
|
zip_bytes = io.BytesIO()
|
||||||
|
|
||||||
from zhenxun.builtin_plugins.auto_update.config import (
|
from zhenxun.utils.manager.zhenxun_repo_manager import ZhenxunRepoManager
|
||||||
PYPROJECT_FILE_STRING,
|
|
||||||
PYPROJECT_LOCK_FILE_STRING,
|
|
||||||
REPLACE_FOLDERS,
|
|
||||||
REQ_TXT_FILE_STRING,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 指定要添加到压缩文件中的文件路径列表
|
# 指定要添加到压缩文件中的文件路径列表
|
||||||
file_paths: list[str] = [
|
file_paths: list[str] = [
|
||||||
PYPROJECT_FILE_STRING,
|
ZhenxunRepoManager.config.PYPROJECT_FILE_STRING,
|
||||||
PYPROJECT_LOCK_FILE_STRING,
|
ZhenxunRepoManager.config.PYPROJECT_LOCK_FILE_STRING,
|
||||||
REQ_TXT_FILE_STRING,
|
ZhenxunRepoManager.config.REQUIREMENTS_FILE_STRING,
|
||||||
]
|
]
|
||||||
|
|
||||||
# 打开一个tarfile对象,写入到上面创建的BytesIO对象中
|
# 打开一个tarfile对象,写入到上面创建的BytesIO对象中
|
||||||
with tarfile.open(mode="w:gz", fileobj=tar_buffer) as tar:
|
with tarfile.open(mode="w:gz", fileobj=tar_buffer) as tar:
|
||||||
add_files_and_folders_to_tar(tar, file_paths, folders=REPLACE_FOLDERS)
|
add_files_and_folders_to_tar(
|
||||||
|
tar,
|
||||||
|
file_paths,
|
||||||
|
folders=ZhenxunRepoManager.config.ZHENXUN_BOT_UPDATE_FOLDERS,
|
||||||
|
)
|
||||||
|
|
||||||
with zipfile.ZipFile(zip_bytes, mode="w", compression=zipfile.ZIP_DEFLATED) as zipf:
|
with zipfile.ZipFile(zip_bytes, mode="w", compression=zipfile.ZIP_DEFLATED) as zipf:
|
||||||
add_files_and_folders_to_zip(zipf, file_paths, folders=REPLACE_FOLDERS)
|
add_files_and_folders_to_zip(
|
||||||
|
zipf,
|
||||||
|
file_paths,
|
||||||
|
folders=ZhenxunRepoManager.config.ZHENXUN_BOT_UPDATE_FOLDERS,
|
||||||
|
)
|
||||||
|
|
||||||
mocked_api.get(
|
mocked_api.get(
|
||||||
url="https://codeload.github.com/HibiKier/zhenxun_bot/legacy.tar.gz/refs/tags/v0.2.2",
|
url="https://codeload.github.com/HibiKier/zhenxun_bot/legacy.tar.gz/refs/tags/v0.2.2",
|
||||||
@@ -88,12 +65,6 @@ def init_mocked_api(mocked_api: MockRouter) -> None:
|
|||||||
).respond(
|
).respond(
|
||||||
content=tar_buffer.getvalue(),
|
content=tar_buffer.getvalue(),
|
||||||
)
|
)
|
||||||
mocked_api.get(
|
|
||||||
url="https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/dev.zip",
|
|
||||||
name="dev_download_url",
|
|
||||||
).respond(
|
|
||||||
content=zip_bytes.getvalue(),
|
|
||||||
)
|
|
||||||
mocked_api.get(
|
mocked_api.get(
|
||||||
url="https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/main.zip",
|
url="https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/main.zip",
|
||||||
name="main_download_url",
|
name="main_download_url",
|
||||||
@@ -195,54 +166,52 @@ def add_directory_to_tar(tarinfo, tar):
|
|||||||
|
|
||||||
|
|
||||||
def init_mocker_path(mocker: MockerFixture, tmp_path: Path):
|
def init_mocker_path(mocker: MockerFixture, tmp_path: Path):
|
||||||
from zhenxun.builtin_plugins.auto_update.config import (
|
from zhenxun.utils.manager.zhenxun_repo_manager import ZhenxunRepoManager
|
||||||
PYPROJECT_FILE_STRING,
|
|
||||||
PYPROJECT_LOCK_FILE_STRING,
|
|
||||||
REQ_TXT_FILE_STRING,
|
|
||||||
VERSION_FILE_STRING,
|
|
||||||
)
|
|
||||||
|
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
"zhenxun.builtin_plugins.auto_update._data_source.install_requirement",
|
"zhenxun.utils.manager.virtual_env_package_manager.VirtualEnvPackageManager.install_requirement",
|
||||||
return_value=None,
|
return_value=None,
|
||||||
)
|
)
|
||||||
mock_tmp_path = mocker.patch(
|
mock_tmp_path = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.auto_update._data_source.TMP_PATH",
|
"zhenxun.configs.path_config.TEMP_PATH",
|
||||||
new=tmp_path / "auto_update",
|
new=tmp_path / "auto_update",
|
||||||
)
|
)
|
||||||
mock_base_path = mocker.patch(
|
mock_base_path = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.auto_update._data_source.BASE_PATH",
|
"zhenxun.utils.manager.zhenxun_repo_manager.ZhenxunRepoManager.config.ZHENXUN_BOT_CODE_PATH",
|
||||||
new=tmp_path / "zhenxun",
|
new=tmp_path / "zhenxun",
|
||||||
)
|
)
|
||||||
mock_backup_path = mocker.patch(
|
mock_backup_path = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.auto_update._data_source.BACKUP_PATH",
|
"zhenxun.utils.manager.zhenxun_repo_manager.ZhenxunRepoManager.config.ZHENXUN_BOT_BACKUP_PATH",
|
||||||
new=tmp_path / "backup",
|
new=tmp_path / "backup",
|
||||||
)
|
)
|
||||||
mock_download_gz_file = mocker.patch(
|
mock_download_gz_file = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.auto_update._data_source.DOWNLOAD_GZ_FILE",
|
"zhenxun.utils.manager.zhenxun_repo_manager.ZhenxunRepoManager.config.ZHENXUN_BOT_DOWNLOAD_FILE",
|
||||||
new=mock_tmp_path / "download_latest_file.tar.gz",
|
new=mock_tmp_path / "download_latest_file.tar.gz",
|
||||||
)
|
)
|
||||||
mock_download_zip_file = mocker.patch(
|
mock_download_zip_file = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.auto_update._data_source.DOWNLOAD_ZIP_FILE",
|
"zhenxun.utils.manager.zhenxun_repo_manager.ZhenxunRepoManager.config.ZHENXUN_BOT_UNZIP_PATH",
|
||||||
new=mock_tmp_path / "download_latest_file.zip",
|
new=mock_tmp_path / "download_latest_file.zip",
|
||||||
)
|
)
|
||||||
mock_pyproject_file = mocker.patch(
|
mock_pyproject_file = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.auto_update._data_source.PYPROJECT_FILE",
|
"zhenxun.utils.manager.zhenxun_repo_manager.ZhenxunRepoManager.config.PYPROJECT_FILE",
|
||||||
new=tmp_path / PYPROJECT_FILE_STRING,
|
new=tmp_path / ZhenxunRepoManager.config.PYPROJECT_FILE_STRING,
|
||||||
)
|
)
|
||||||
mock_pyproject_lock_file = mocker.patch(
|
mock_pyproject_lock_file = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.auto_update._data_source.PYPROJECT_LOCK_FILE",
|
"zhenxun.utils.manager.zhenxun_repo_manager.ZhenxunRepoManager.config.PYPROJECT_LOCK_FILE",
|
||||||
new=tmp_path / PYPROJECT_LOCK_FILE_STRING,
|
new=tmp_path / ZhenxunRepoManager.config.PYPROJECT_LOCK_FILE_STRING,
|
||||||
)
|
)
|
||||||
mock_req_txt_file = mocker.patch(
|
mock_req_txt_file = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.auto_update._data_source.REQ_TXT_FILE",
|
"zhenxun.utils.manager.zhenxun_repo_manager.ZhenxunRepoManager.config.REQUIREMENTS_FILE",
|
||||||
new=tmp_path / REQ_TXT_FILE_STRING,
|
new=tmp_path / ZhenxunRepoManager.config.REQUIREMENTS_FILE_STRING,
|
||||||
)
|
)
|
||||||
mock_version_file = mocker.patch(
|
mock_version_file = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.auto_update._data_source.VERSION_FILE",
|
"zhenxun.utils.manager.zhenxun_repo_manager.ZhenxunRepoManager.config.ZHENXUN_BOT_VERSION_FILE",
|
||||||
new=tmp_path / VERSION_FILE_STRING,
|
new=tmp_path / ZhenxunRepoManager.config.ZHENXUN_BOT_VERSION_FILE_STRING,
|
||||||
)
|
)
|
||||||
open(mock_version_file, "w").write("__version__: v0.2.2")
|
open(mock_version_file, "w").write("__version__: v0.2.2")
|
||||||
|
open(ZhenxunRepoManager.config.ZHENXUN_BOT_VERSION_FILE, "w").write(
|
||||||
|
"__version__: v0.2.2"
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
mock_tmp_path,
|
mock_tmp_path,
|
||||||
mock_base_path,
|
mock_base_path,
|
||||||
@@ -256,6 +225,7 @@ def init_mocker_path(mocker: MockerFixture, tmp_path: Path):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_check_update_release(
|
async def test_check_update_release(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
@@ -267,12 +237,7 @@ async def test_check_update_release(
|
|||||||
测试检查更新(release)
|
测试检查更新(release)
|
||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.auto_update import _matcher
|
from zhenxun.builtin_plugins.auto_update import _matcher
|
||||||
from zhenxun.builtin_plugins.auto_update.config import (
|
from zhenxun.utils.manager.zhenxun_repo_manager import ZhenxunRepoManager
|
||||||
PYPROJECT_FILE_STRING,
|
|
||||||
PYPROJECT_LOCK_FILE_STRING,
|
|
||||||
REPLACE_FOLDERS,
|
|
||||||
REQ_TXT_FILE_STRING,
|
|
||||||
)
|
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
init_mocked_api(mocked_api=mocked_api)
|
||||||
|
|
||||||
@@ -291,7 +256,7 @@ async def test_check_update_release(
|
|||||||
# 确保目录下有一个子目录,以便 os.listdir() 能返回一个目录名
|
# 确保目录下有一个子目录,以便 os.listdir() 能返回一个目录名
|
||||||
mock_tmp_path.mkdir(parents=True, exist_ok=True)
|
mock_tmp_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
for folder in REPLACE_FOLDERS:
|
for folder in ZhenxunRepoManager.config.ZHENXUN_BOT_UPDATE_FOLDERS:
|
||||||
(mock_base_path / folder).mkdir(parents=True, exist_ok=True)
|
(mock_base_path / folder).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
mock_pyproject_file.write_bytes(b"")
|
mock_pyproject_file.write_bytes(b"")
|
||||||
@@ -301,7 +266,7 @@ async def test_check_update_release(
|
|||||||
async with app.test_matcher(_matcher) as ctx:
|
async with app.test_matcher(_matcher) as ctx:
|
||||||
bot = create_bot(ctx)
|
bot = create_bot(ctx)
|
||||||
bot = cast(Bot, bot)
|
bot = cast(Bot, bot)
|
||||||
raw_message = "检查更新 release"
|
raw_message = "检查更新 release -z"
|
||||||
event = _v11_group_message_event(
|
event = _v11_group_message_event(
|
||||||
raw_message,
|
raw_message,
|
||||||
self_id=BotId.QQ_BOT,
|
self_id=BotId.QQ_BOT,
|
||||||
@@ -311,17 +276,23 @@ async def test_check_update_release(
|
|||||||
to_me=True,
|
to_me=True,
|
||||||
)
|
)
|
||||||
ctx.receive_event(bot, event)
|
ctx.receive_event(bot, event)
|
||||||
|
ctx.should_call_send(
|
||||||
|
event=event,
|
||||||
|
message=Message(f"{get_reply_cq(MessageId.MESSAGE_ID)}正在进行检查更新..."),
|
||||||
|
result=None,
|
||||||
|
bot=bot,
|
||||||
|
)
|
||||||
ctx.should_call_api(
|
ctx.should_call_api(
|
||||||
"send_msg",
|
"send_msg",
|
||||||
_v11_private_message_send(
|
_v11_private_message_send(
|
||||||
message="检测真寻已更新,版本更新:v0.2.2 -> v0.2.2\n开始更新...",
|
message="检测真寻已更新,当前版本:v0.2.2\n开始更新...",
|
||||||
user_id=UserId.SUPERUSER,
|
user_id=UserId.SUPERUSER,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
ctx.should_call_send(
|
ctx.should_call_send(
|
||||||
event=event,
|
event=event,
|
||||||
message=Message(
|
message=Message(
|
||||||
"版本更新完成\n版本: v0.2.2 -> v0.2.2\n请重新启动真寻以完成更新!"
|
"版本更新完成!\n版本: v0.2.2 -> v0.2.2\n请重新启动真寻以完成更新!"
|
||||||
),
|
),
|
||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
@@ -330,9 +301,13 @@ async def test_check_update_release(
|
|||||||
assert mocked_api["release_latest"].called
|
assert mocked_api["release_latest"].called
|
||||||
assert mocked_api["release_download_url_redirect"].called
|
assert mocked_api["release_download_url_redirect"].called
|
||||||
|
|
||||||
assert (mock_backup_path / PYPROJECT_FILE_STRING).exists()
|
assert (mock_backup_path / ZhenxunRepoManager.config.PYPROJECT_FILE_STRING).exists()
|
||||||
assert (mock_backup_path / PYPROJECT_LOCK_FILE_STRING).exists()
|
assert (
|
||||||
assert (mock_backup_path / REQ_TXT_FILE_STRING).exists()
|
mock_backup_path / ZhenxunRepoManager.config.PYPROJECT_LOCK_FILE_STRING
|
||||||
|
).exists()
|
||||||
|
assert (
|
||||||
|
mock_backup_path / ZhenxunRepoManager.config.REQUIREMENTS_FILE_STRING
|
||||||
|
).exists()
|
||||||
|
|
||||||
assert not mock_download_gz_file.exists()
|
assert not mock_download_gz_file.exists()
|
||||||
assert not mock_download_zip_file.exists()
|
assert not mock_download_zip_file.exists()
|
||||||
@@ -341,12 +316,13 @@ async def test_check_update_release(
|
|||||||
assert mock_pyproject_lock_file.read_bytes() == b"new"
|
assert mock_pyproject_lock_file.read_bytes() == b"new"
|
||||||
assert mock_req_txt_file.read_bytes() == b"new"
|
assert mock_req_txt_file.read_bytes() == b"new"
|
||||||
|
|
||||||
for folder in REPLACE_FOLDERS:
|
for folder in ZhenxunRepoManager.config.ZHENXUN_BOT_UPDATE_FOLDERS:
|
||||||
assert not (mock_base_path / folder).exists()
|
assert not (mock_base_path / folder).exists()
|
||||||
for folder in REPLACE_FOLDERS:
|
for folder in ZhenxunRepoManager.config.ZHENXUN_BOT_UPDATE_FOLDERS:
|
||||||
assert (mock_backup_path / folder).exists()
|
assert (mock_backup_path / folder).exists()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_check_update_main(
|
async def test_check_update_main(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
@@ -358,12 +334,9 @@ async def test_check_update_main(
|
|||||||
测试检查更新(正式环境)
|
测试检查更新(正式环境)
|
||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.auto_update import _matcher
|
from zhenxun.builtin_plugins.auto_update import _matcher
|
||||||
from zhenxun.builtin_plugins.auto_update.config import (
|
from zhenxun.utils.manager.zhenxun_repo_manager import ZhenxunRepoManager
|
||||||
PYPROJECT_FILE_STRING,
|
|
||||||
PYPROJECT_LOCK_FILE_STRING,
|
ZhenxunRepoManager.zhenxun_zip_update = mocker.Mock(return_value="v0.2.2-e6f17c4")
|
||||||
REPLACE_FOLDERS,
|
|
||||||
REQ_TXT_FILE_STRING,
|
|
||||||
)
|
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
init_mocked_api(mocked_api=mocked_api)
|
||||||
|
|
||||||
@@ -381,7 +354,7 @@ async def test_check_update_main(
|
|||||||
|
|
||||||
# 确保目录下有一个子目录,以便 os.listdir() 能返回一个目录名
|
# 确保目录下有一个子目录,以便 os.listdir() 能返回一个目录名
|
||||||
mock_tmp_path.mkdir(parents=True, exist_ok=True)
|
mock_tmp_path.mkdir(parents=True, exist_ok=True)
|
||||||
for folder in REPLACE_FOLDERS:
|
for folder in ZhenxunRepoManager.config.ZHENXUN_BOT_UPDATE_FOLDERS:
|
||||||
(mock_base_path / folder).mkdir(parents=True, exist_ok=True)
|
(mock_base_path / folder).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
mock_pyproject_file.write_bytes(b"")
|
mock_pyproject_file.write_bytes(b"")
|
||||||
@@ -391,7 +364,7 @@ async def test_check_update_main(
|
|||||||
async with app.test_matcher(_matcher) as ctx:
|
async with app.test_matcher(_matcher) as ctx:
|
||||||
bot = create_bot(ctx)
|
bot = create_bot(ctx)
|
||||||
bot = cast(Bot, bot)
|
bot = cast(Bot, bot)
|
||||||
raw_message = "检查更新 main -r"
|
raw_message = "检查更新 main -r -z"
|
||||||
event = _v11_group_message_event(
|
event = _v11_group_message_event(
|
||||||
raw_message,
|
raw_message,
|
||||||
self_id=BotId.QQ_BOT,
|
self_id=BotId.QQ_BOT,
|
||||||
@@ -401,30 +374,39 @@ async def test_check_update_main(
|
|||||||
to_me=True,
|
to_me=True,
|
||||||
)
|
)
|
||||||
ctx.receive_event(bot, event)
|
ctx.receive_event(bot, event)
|
||||||
|
ctx.should_call_send(
|
||||||
|
event=event,
|
||||||
|
message=Message(f"{get_reply_cq(MessageId.MESSAGE_ID)}正在进行检查更新..."),
|
||||||
|
result=None,
|
||||||
|
bot=bot,
|
||||||
|
)
|
||||||
ctx.should_call_api(
|
ctx.should_call_api(
|
||||||
"send_msg",
|
"send_msg",
|
||||||
_v11_private_message_send(
|
_v11_private_message_send(
|
||||||
message="检测真寻已更新,版本更新:v0.2.2 -> v0.2.2-e6f17c4\n"
|
message="检测真寻已更新,当前版本:v0.2.2\n开始更新...",
|
||||||
"开始更新...",
|
|
||||||
user_id=UserId.SUPERUSER,
|
user_id=UserId.SUPERUSER,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
ctx.should_call_send(
|
ctx.should_call_send(
|
||||||
event=event,
|
event=event,
|
||||||
message=Message(
|
message=Message(
|
||||||
"版本更新完成\n"
|
"版本更新完成!\n"
|
||||||
"版本: v0.2.2 -> v0.2.2-e6f17c4\n"
|
"版本: v0.2.2 -> v0.2.2-e6f17c4\n"
|
||||||
"请重新启动真寻以完成更新!\n"
|
"请重新启动真寻以完成更新!\n"
|
||||||
"资源文件更新成功!"
|
"真寻资源更新完成!"
|
||||||
),
|
),
|
||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
ctx.should_finished(_matcher)
|
ctx.should_finished(_matcher)
|
||||||
assert mocked_api["main_download_url"].called
|
assert mocked_api["main_download_url"].called
|
||||||
assert (mock_backup_path / PYPROJECT_FILE_STRING).exists()
|
assert (mock_backup_path / ZhenxunRepoManager.config.PYPROJECT_FILE_STRING).exists()
|
||||||
assert (mock_backup_path / PYPROJECT_LOCK_FILE_STRING).exists()
|
assert (
|
||||||
assert (mock_backup_path / REQ_TXT_FILE_STRING).exists()
|
mock_backup_path / ZhenxunRepoManager.config.PYPROJECT_LOCK_FILE_STRING
|
||||||
|
).exists()
|
||||||
|
assert (
|
||||||
|
mock_backup_path / ZhenxunRepoManager.config.REQUIREMENTS_FILE_STRING
|
||||||
|
).exists()
|
||||||
|
|
||||||
assert not mock_download_gz_file.exists()
|
assert not mock_download_gz_file.exists()
|
||||||
assert not mock_download_zip_file.exists()
|
assert not mock_download_zip_file.exists()
|
||||||
@@ -433,7 +415,7 @@ async def test_check_update_main(
|
|||||||
assert mock_pyproject_lock_file.read_bytes() == b"new"
|
assert mock_pyproject_lock_file.read_bytes() == b"new"
|
||||||
assert mock_req_txt_file.read_bytes() == b"new"
|
assert mock_req_txt_file.read_bytes() == b"new"
|
||||||
|
|
||||||
for folder in REPLACE_FOLDERS:
|
for folder in ZhenxunRepoManager.config.ZHENXUN_BOT_UPDATE_FOLDERS:
|
||||||
assert (mock_base_path / folder).exists()
|
assert (mock_base_path / folder).exists()
|
||||||
for folder in REPLACE_FOLDERS:
|
for folder in ZhenxunRepoManager.config.ZHENXUN_BOT_UPDATE_FOLDERS:
|
||||||
assert (mock_backup_path / folder).exists()
|
assert (mock_backup_path / folder).exists()
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ from pathlib import Path
|
|||||||
import platform
|
import platform
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
import nonebot
|
|
||||||
from nonebot.adapters.onebot.v11 import Bot
|
from nonebot.adapters.onebot.v11 import Bot
|
||||||
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
|
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
import pytest
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from respx import MockRouter
|
|
||||||
|
|
||||||
from tests.config import BotId, GroupId, MessageId, UserId
|
from tests.config import BotId, GroupId, MessageId, UserId
|
||||||
from tests.utils import _v11_group_message_event
|
from tests.utils import _v11_group_message_event
|
||||||
@@ -66,9 +65,11 @@ def init_mocker(mocker: MockerFixture, tmp_path: Path):
|
|||||||
mock_platform = mocker.patch("zhenxun.builtin_plugins.check.data_source.platform")
|
mock_platform = mocker.patch("zhenxun.builtin_plugins.check.data_source.platform")
|
||||||
mock_platform.uname.return_value = platform_uname
|
mock_platform.uname.return_value = platform_uname
|
||||||
|
|
||||||
mock_template_to_pic = mocker.patch("zhenxun.builtin_plugins.check.template_to_pic")
|
mock_render_service = mocker.patch(
|
||||||
mock_template_to_pic_return = mocker.AsyncMock()
|
"zhenxun.builtin_plugins.check.renderer_service.render"
|
||||||
mock_template_to_pic.return_value = mock_template_to_pic_return
|
)
|
||||||
|
mock_render_service_return = mocker.AsyncMock()
|
||||||
|
mock_render_service.return_value = mock_render_service_return
|
||||||
|
|
||||||
mock_build_message = mocker.patch(
|
mock_build_message = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.check.MessageUtils.build_message"
|
"zhenxun.builtin_plugins.check.MessageUtils.build_message"
|
||||||
@@ -76,26 +77,21 @@ def init_mocker(mocker: MockerFixture, tmp_path: Path):
|
|||||||
mock_build_message_return = mocker.AsyncMock()
|
mock_build_message_return = mocker.AsyncMock()
|
||||||
mock_build_message.return_value = mock_build_message_return
|
mock_build_message.return_value = mock_build_message_return
|
||||||
|
|
||||||
mock_template_path_new = tmp_path / "resources" / "template"
|
|
||||||
mocker.patch(
|
|
||||||
"zhenxun.builtin_plugins.check.TEMPLATE_PATH", new=mock_template_path_new
|
|
||||||
)
|
|
||||||
return (
|
return (
|
||||||
mock_psutil,
|
mock_psutil,
|
||||||
mock_cpuinfo,
|
mock_cpuinfo,
|
||||||
mock_platform,
|
mock_platform,
|
||||||
mock_template_to_pic,
|
mock_render_service,
|
||||||
mock_template_to_pic_return,
|
mock_render_service_return,
|
||||||
mock_build_message,
|
mock_build_message,
|
||||||
mock_build_message_return,
|
mock_build_message_return,
|
||||||
mock_template_path_new,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_check(
|
async def test_check(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -103,18 +99,15 @@ async def test_check(
|
|||||||
测试自检
|
测试自检
|
||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.check import _self_check_matcher
|
from zhenxun.builtin_plugins.check import _self_check_matcher
|
||||||
from zhenxun.builtin_plugins.check.data_source import __get_version
|
|
||||||
from zhenxun.configs.config import BotConfig
|
|
||||||
|
|
||||||
(
|
(
|
||||||
mock_psutil,
|
mock_psutil,
|
||||||
mock_cpuinfo,
|
mock_cpuinfo,
|
||||||
mock_platform,
|
mock_platform,
|
||||||
mock_template_to_pic,
|
mock_render_service,
|
||||||
mock_template_to_pic_return,
|
mock_render_service_return,
|
||||||
mock_build_message,
|
mock_build_message,
|
||||||
mock_build_message_return,
|
mock_build_message_return,
|
||||||
mock_template_path_new,
|
|
||||||
) = init_mocker(mocker, tmp_path)
|
) = init_mocker(mocker, tmp_path)
|
||||||
async with app.test_matcher(_self_check_matcher) as ctx:
|
async with app.test_matcher(_self_check_matcher) as ctx:
|
||||||
bot = create_bot(ctx)
|
bot = create_bot(ctx)
|
||||||
@@ -131,49 +124,15 @@ async def test_check(
|
|||||||
ctx.receive_event(bot=bot, event=event)
|
ctx.receive_event(bot=bot, event=event)
|
||||||
ctx.should_ignore_rule(_self_check_matcher)
|
ctx.should_ignore_rule(_self_check_matcher)
|
||||||
|
|
||||||
data = {
|
mock_render_service.assert_awaited_once()
|
||||||
"cpu_info": f"{mock_psutil.cpu_percent.return_value}% "
|
mock_build_message.assert_called_once_with(mock_render_service_return)
|
||||||
+ f"- {mock_psutil.cpu_freq.return_value.current}Ghz "
|
|
||||||
+ f"[{mock_psutil.cpu_count.return_value} core]",
|
|
||||||
"cpu_process": mock_psutil.cpu_percent.return_value,
|
|
||||||
"ram_info": f"{round(mock_psutil.virtual_memory.return_value.used / (1024 ** 3), 1)}" # noqa: E501
|
|
||||||
+ f" / {round(mock_psutil.virtual_memory.return_value.total / (1024 ** 3), 1)}"
|
|
||||||
+ " GB",
|
|
||||||
"ram_process": mock_psutil.virtual_memory.return_value.percent,
|
|
||||||
"swap_info": f"{round(mock_psutil.swap_memory.return_value.used / (1024 ** 3), 1)}" # noqa: E501
|
|
||||||
+ f" / {round(mock_psutil.swap_memory.return_value.total / (1024 ** 3), 1)} GB",
|
|
||||||
"swap_process": mock_psutil.swap_memory.return_value.percent,
|
|
||||||
"disk_info": f"{round(mock_psutil.disk_usage.return_value.used / (1024 ** 3), 1)}" # noqa: E501
|
|
||||||
+ f" / {round(mock_psutil.disk_usage.return_value.total / (1024 ** 3), 1)} GB",
|
|
||||||
"disk_process": mock_psutil.disk_usage.return_value.percent,
|
|
||||||
"brand_raw": cpuinfo_get_cpu_info["brand_raw"],
|
|
||||||
"baidu": "red",
|
|
||||||
"google": "red",
|
|
||||||
"system": f"{platform_uname.system} " f"{platform_uname.release}",
|
|
||||||
"version": __get_version(),
|
|
||||||
"plugin_count": len(nonebot.get_loaded_plugins()),
|
|
||||||
"nickname": BotConfig.self_nickname,
|
|
||||||
}
|
|
||||||
|
|
||||||
mock_template_to_pic.assert_awaited_once_with(
|
|
||||||
template_path=str((mock_template_path_new / "check").absolute()),
|
|
||||||
template_name="main.html",
|
|
||||||
templates={"data": data},
|
|
||||||
pages={
|
|
||||||
"viewport": {"width": 195, "height": 750},
|
|
||||||
"base_url": f"file://{mock_template_path_new.absolute()}",
|
|
||||||
},
|
|
||||||
wait=2,
|
|
||||||
)
|
|
||||||
mock_template_to_pic.assert_awaited_once()
|
|
||||||
mock_build_message.assert_called_once_with(mock_template_to_pic_return)
|
|
||||||
mock_build_message_return.send.assert_awaited_once()
|
mock_build_message_return.send.assert_awaited_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_check_arm(
|
async def test_check_arm(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -181,8 +140,6 @@ async def test_check_arm(
|
|||||||
测试自检(arm)
|
测试自检(arm)
|
||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.check import _self_check_matcher
|
from zhenxun.builtin_plugins.check import _self_check_matcher
|
||||||
from zhenxun.builtin_plugins.check.data_source import __get_version
|
|
||||||
from zhenxun.configs.config import BotConfig
|
|
||||||
|
|
||||||
platform_uname_arm = platform.uname_result(
|
platform_uname_arm = platform.uname_result(
|
||||||
system="Linux",
|
system="Linux",
|
||||||
@@ -203,11 +160,10 @@ async def test_check_arm(
|
|||||||
mock_psutil,
|
mock_psutil,
|
||||||
mock_cpuinfo,
|
mock_cpuinfo,
|
||||||
mock_platform,
|
mock_platform,
|
||||||
mock_template_to_pic,
|
mock_render_service,
|
||||||
mock_template_to_pic_return,
|
mock_render_service_return,
|
||||||
mock_build_message,
|
mock_build_message,
|
||||||
mock_build_message_return,
|
mock_build_message_return,
|
||||||
mock_template_path_new,
|
|
||||||
) = init_mocker(mocker, tmp_path)
|
) = init_mocker(mocker, tmp_path)
|
||||||
|
|
||||||
mock_platform.uname.return_value = platform_uname_arm
|
mock_platform.uname.return_value = platform_uname_arm
|
||||||
@@ -228,35 +184,6 @@ async def test_check_arm(
|
|||||||
)
|
)
|
||||||
ctx.receive_event(bot=bot, event=event)
|
ctx.receive_event(bot=bot, event=event)
|
||||||
ctx.should_ignore_rule(_self_check_matcher)
|
ctx.should_ignore_rule(_self_check_matcher)
|
||||||
mock_template_to_pic.assert_awaited_once_with(
|
|
||||||
template_path=str((mock_template_path_new / "check").absolute()),
|
|
||||||
template_name="main.html",
|
|
||||||
templates={
|
|
||||||
"data": {
|
|
||||||
"cpu_info": "1.0% - 0.0Ghz [1 core]",
|
|
||||||
"cpu_process": 1.0,
|
|
||||||
"ram_info": "1.0 / 1.0 GB",
|
|
||||||
"ram_process": 100.0,
|
|
||||||
"swap_info": "1.0 / 1.0 GB",
|
|
||||||
"swap_process": 100.0,
|
|
||||||
"disk_info": "1.0 / 1.0 GB",
|
|
||||||
"disk_process": 100.0,
|
|
||||||
"brand_raw": "",
|
|
||||||
"baidu": "red",
|
|
||||||
"google": "red",
|
|
||||||
"system": f"{platform_uname_arm.system} "
|
|
||||||
f"{platform_uname_arm.release}",
|
|
||||||
"version": __get_version(),
|
|
||||||
"plugin_count": len(nonebot.get_loaded_plugins()),
|
|
||||||
"nickname": BotConfig.self_nickname,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pages={
|
|
||||||
"viewport": {"width": 195, "height": 750},
|
|
||||||
"base_url": f"file://{mock_template_path_new.absolute()}",
|
|
||||||
},
|
|
||||||
wait=2,
|
|
||||||
)
|
|
||||||
mock_subprocess_check_output.assert_has_calls(
|
mock_subprocess_check_output.assert_has_calls(
|
||||||
[
|
[
|
||||||
mocker.call(["lscpu"], env=mock_environ_copy_return),
|
mocker.call(["lscpu"], env=mock_environ_copy_return),
|
||||||
@@ -270,6 +197,6 @@ async def test_check_arm(
|
|||||||
mocker.call().decode().split().__getitem__().__float__(),
|
mocker.call().decode().split().__getitem__().__float__(),
|
||||||
] # type: ignore
|
] # type: ignore
|
||||||
)
|
)
|
||||||
mock_template_to_pic.assert_awaited_once()
|
mock_render_service.assert_awaited_once()
|
||||||
mock_build_message.assert_called_once_with(mock_template_to_pic_return)
|
mock_build_message.assert_called_once_with(mock_render_service_return)
|
||||||
mock_build_message_return.send.assert_awaited_once()
|
mock_build_message_return.send.assert_awaited_once()
|
||||||
|
|||||||
@@ -8,21 +8,17 @@ from nonebot.adapters.onebot.v11.message import Message
|
|||||||
from nonebug import App
|
from nonebug import App
|
||||||
import pytest
|
import pytest
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from respx import MockRouter
|
|
||||||
|
|
||||||
from tests.builtin_plugins.plugin_store.utils import init_mocked_api
|
|
||||||
from tests.config import BotId, GroupId, MessageId, UserId
|
from tests.config import BotId, GroupId, MessageId, UserId
|
||||||
from tests.utils import _v11_group_message_event
|
from tests.utils import _v11_group_message_event
|
||||||
|
|
||||||
|
test_path = Path(__file__).parent.parent.parent
|
||||||
|
|
||||||
@pytest.mark.parametrize("package_api", ["jsd", "gh"])
|
|
||||||
@pytest.mark.parametrize("is_commit", [True, False])
|
@pytest.mark.xfail
|
||||||
async def test_add_plugin_basic(
|
async def test_add_plugin_basic(
|
||||||
package_api: str,
|
|
||||||
is_commit: bool,
|
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -31,24 +27,12 @@ async def test_add_plugin_basic(
|
|||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
mock_base_path = mocker.patch(
|
mock_base_path = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
||||||
new=tmp_path / "zhenxun",
|
new=tmp_path / "zhenxun",
|
||||||
)
|
)
|
||||||
|
|
||||||
if package_api != "jsd":
|
plugin_id = "search_image"
|
||||||
mocked_api["zhenxun_bot_plugins_metadata"].respond(404)
|
|
||||||
if package_api != "gh":
|
|
||||||
mocked_api["zhenxun_bot_plugins_tree"].respond(404)
|
|
||||||
|
|
||||||
if not is_commit:
|
|
||||||
mocked_api["zhenxun_bot_plugins_commit"].respond(404)
|
|
||||||
mocked_api["zhenxun_bot_plugins_commit_proxy"].respond(404)
|
|
||||||
mocked_api["zhenxun_bot_plugins_index_commit"].respond(404)
|
|
||||||
mocked_api["zhenxun_bot_plugins_index_commit_proxy"].respond(404)
|
|
||||||
|
|
||||||
plugin_id = 1
|
|
||||||
|
|
||||||
async with app.test_matcher(_matcher) as ctx:
|
async with app.test_matcher(_matcher) as ctx:
|
||||||
bot = create_bot(ctx)
|
bot = create_bot(ctx)
|
||||||
@@ -65,7 +49,7 @@ async def test_add_plugin_basic(
|
|||||||
ctx.receive_event(bot=bot, event=event)
|
ctx.receive_event(bot=bot, event=event)
|
||||||
ctx.should_call_send(
|
ctx.should_call_send(
|
||||||
event=event,
|
event=event,
|
||||||
message=Message(message=f"正在添加插件 Id: {plugin_id}"),
|
message=Message(message=f"正在添加插件 Module: {plugin_id}"),
|
||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
@@ -75,25 +59,13 @@ async def test_add_plugin_basic(
|
|||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
if is_commit:
|
|
||||||
assert mocked_api["search_image_plugin_file_init_commit"].called
|
|
||||||
assert mocked_api["basic_plugins"].called
|
|
||||||
assert mocked_api["extra_plugins"].called
|
|
||||||
else:
|
|
||||||
assert mocked_api["search_image_plugin_file_init"].called
|
|
||||||
assert mocked_api["basic_plugins_no_commit"].called
|
|
||||||
assert mocked_api["extra_plugins_no_commit"].called
|
|
||||||
assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file()
|
assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("package_api", ["jsd", "gh"])
|
@pytest.mark.xfail
|
||||||
@pytest.mark.parametrize("is_commit", [True, False])
|
|
||||||
async def test_add_plugin_basic_commit_version(
|
async def test_add_plugin_basic_commit_version(
|
||||||
package_api: str,
|
|
||||||
is_commit: bool,
|
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -102,23 +74,12 @@ async def test_add_plugin_basic_commit_version(
|
|||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
mock_base_path = mocker.patch(
|
mock_base_path = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
||||||
new=tmp_path / "zhenxun",
|
new=tmp_path / "zhenxun",
|
||||||
)
|
)
|
||||||
|
|
||||||
if package_api != "jsd":
|
plugin_id = "bilibili_sub"
|
||||||
mocked_api["zhenxun_bot_plugins_metadata_commit"].respond(404)
|
|
||||||
if package_api != "gh":
|
|
||||||
mocked_api["zhenxun_bot_plugins_tree_commit"].respond(404)
|
|
||||||
|
|
||||||
if not is_commit:
|
|
||||||
mocked_api["zhenxun_bot_plugins_commit"].respond(404)
|
|
||||||
mocked_api["zhenxun_bot_plugins_commit_proxy"].respond(404)
|
|
||||||
mocked_api["zhenxun_bot_plugins_index_commit"].respond(404)
|
|
||||||
mocked_api["zhenxun_bot_plugins_index_commit_proxy"].respond(404)
|
|
||||||
plugin_id = 3
|
|
||||||
|
|
||||||
async with app.test_matcher(_matcher) as ctx:
|
async with app.test_matcher(_matcher) as ctx:
|
||||||
bot = create_bot(ctx)
|
bot = create_bot(ctx)
|
||||||
@@ -135,7 +96,7 @@ async def test_add_plugin_basic_commit_version(
|
|||||||
ctx.receive_event(bot=bot, event=event)
|
ctx.receive_event(bot=bot, event=event)
|
||||||
ctx.should_call_send(
|
ctx.should_call_send(
|
||||||
event=event,
|
event=event,
|
||||||
message=Message(message=f"正在添加插件 Id: {plugin_id}"),
|
message=Message(message=f"正在添加插件 Module: {plugin_id}"),
|
||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
@@ -145,28 +106,13 @@ async def test_add_plugin_basic_commit_version(
|
|||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
if package_api == "jsd":
|
|
||||||
assert mocked_api["zhenxun_bot_plugins_metadata_commit"].called
|
|
||||||
if package_api == "gh":
|
|
||||||
assert mocked_api["zhenxun_bot_plugins_tree_commit"].called
|
|
||||||
if is_commit:
|
|
||||||
assert mocked_api["basic_plugins"].called
|
|
||||||
assert mocked_api["extra_plugins"].called
|
|
||||||
else:
|
|
||||||
assert mocked_api["basic_plugins_no_commit"].called
|
|
||||||
assert mocked_api["extra_plugins_no_commit"].called
|
|
||||||
assert mocked_api["bilibili_sub_plugin_file_init"].called
|
|
||||||
assert (mock_base_path / "plugins" / "bilibili_sub" / "__init__.py").is_file()
|
assert (mock_base_path / "plugins" / "bilibili_sub" / "__init__.py").is_file()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("package_api", ["jsd", "gh"])
|
@pytest.mark.xfail
|
||||||
@pytest.mark.parametrize("is_commit", [True, False])
|
|
||||||
async def test_add_plugin_basic_is_not_dir(
|
async def test_add_plugin_basic_is_not_dir(
|
||||||
package_api: str,
|
|
||||||
is_commit: bool,
|
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -175,24 +121,12 @@ async def test_add_plugin_basic_is_not_dir(
|
|||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
mock_base_path = mocker.patch(
|
mock_base_path = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
||||||
new=tmp_path / "zhenxun",
|
new=tmp_path / "zhenxun",
|
||||||
)
|
)
|
||||||
|
|
||||||
if package_api != "jsd":
|
plugin_id = "jitang"
|
||||||
mocked_api["zhenxun_bot_plugins_metadata"].respond(404)
|
|
||||||
if package_api != "gh":
|
|
||||||
mocked_api["zhenxun_bot_plugins_tree"].respond(404)
|
|
||||||
|
|
||||||
if not is_commit:
|
|
||||||
mocked_api["zhenxun_bot_plugins_commit"].respond(404)
|
|
||||||
mocked_api["zhenxun_bot_plugins_commit_proxy"].respond(404)
|
|
||||||
mocked_api["zhenxun_bot_plugins_index_commit"].respond(404)
|
|
||||||
mocked_api["zhenxun_bot_plugins_index_commit_proxy"].respond(404)
|
|
||||||
|
|
||||||
plugin_id = 0
|
|
||||||
|
|
||||||
async with app.test_matcher(_matcher) as ctx:
|
async with app.test_matcher(_matcher) as ctx:
|
||||||
bot = create_bot(ctx)
|
bot = create_bot(ctx)
|
||||||
@@ -209,7 +143,7 @@ async def test_add_plugin_basic_is_not_dir(
|
|||||||
ctx.receive_event(bot=bot, event=event)
|
ctx.receive_event(bot=bot, event=event)
|
||||||
ctx.should_call_send(
|
ctx.should_call_send(
|
||||||
event=event,
|
event=event,
|
||||||
message=Message(message=f"正在添加插件 Id: {plugin_id}"),
|
message=Message(message=f"正在添加插件 Module: {plugin_id}"),
|
||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
@@ -219,25 +153,13 @@ async def test_add_plugin_basic_is_not_dir(
|
|||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
if is_commit:
|
assert (mock_base_path / "plugins" / "jitang.py").is_file()
|
||||||
assert mocked_api["jitang_plugin_file_commit"].called
|
|
||||||
assert mocked_api["basic_plugins"].called
|
|
||||||
assert mocked_api["extra_plugins"].called
|
|
||||||
else:
|
|
||||||
assert mocked_api["jitang_plugin_file"].called
|
|
||||||
assert mocked_api["basic_plugins_no_commit"].called
|
|
||||||
assert mocked_api["extra_plugins_no_commit"].called
|
|
||||||
assert (mock_base_path / "plugins" / "alapi" / "jitang.py").is_file()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("package_api", ["jsd", "gh"])
|
@pytest.mark.xfail
|
||||||
@pytest.mark.parametrize("is_commit", [True, False])
|
|
||||||
async def test_add_plugin_extra(
|
async def test_add_plugin_extra(
|
||||||
package_api: str,
|
|
||||||
is_commit: bool,
|
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -246,26 +168,12 @@ async def test_add_plugin_extra(
|
|||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
mock_base_path = mocker.patch(
|
mock_base_path = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
||||||
new=tmp_path / "zhenxun",
|
new=tmp_path / "zhenxun",
|
||||||
)
|
)
|
||||||
|
|
||||||
if package_api != "jsd":
|
plugin_id = "github_sub"
|
||||||
mocked_api["zhenxun_github_sub_metadata"].respond(404)
|
|
||||||
if package_api != "gh":
|
|
||||||
mocked_api["zhenxun_github_sub_tree"].respond(404)
|
|
||||||
|
|
||||||
if not is_commit:
|
|
||||||
mocked_api["zhenxun_github_sub_commit"].respond(404)
|
|
||||||
mocked_api["zhenxun_github_sub_commit_proxy"].respond(404)
|
|
||||||
mocked_api["zhenxun_bot_plugins_commit"].respond(404)
|
|
||||||
mocked_api["zhenxun_bot_plugins_commit_proxy"].respond(404)
|
|
||||||
mocked_api["zhenxun_bot_plugins_index_commit"].respond(404)
|
|
||||||
mocked_api["zhenxun_bot_plugins_index_commit_proxy"].respond(404)
|
|
||||||
|
|
||||||
plugin_id = 4
|
|
||||||
|
|
||||||
async with app.test_matcher(_matcher) as ctx:
|
async with app.test_matcher(_matcher) as ctx:
|
||||||
bot = create_bot(ctx)
|
bot = create_bot(ctx)
|
||||||
@@ -282,7 +190,7 @@ async def test_add_plugin_extra(
|
|||||||
ctx.receive_event(bot=bot, event=event)
|
ctx.receive_event(bot=bot, event=event)
|
||||||
ctx.should_call_send(
|
ctx.should_call_send(
|
||||||
event=event,
|
event=event,
|
||||||
message=Message(message=f"正在添加插件 Id: {plugin_id}"),
|
message=Message(message=f"正在添加插件 Module: {plugin_id}"),
|
||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
@@ -292,30 +200,19 @@ async def test_add_plugin_extra(
|
|||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
if is_commit:
|
|
||||||
assert mocked_api["github_sub_plugin_file_init_commit"].called
|
|
||||||
assert mocked_api["basic_plugins"].called
|
|
||||||
assert mocked_api["extra_plugins"].called
|
|
||||||
else:
|
|
||||||
assert mocked_api["github_sub_plugin_file_init"].called
|
|
||||||
assert mocked_api["basic_plugins_no_commit"].called
|
|
||||||
assert mocked_api["extra_plugins_no_commit"].called
|
|
||||||
assert (mock_base_path / "plugins" / "github_sub" / "__init__.py").is_file()
|
assert (mock_base_path / "plugins" / "github_sub" / "__init__.py").is_file()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_plugin_not_exist_add(
|
async def test_plugin_not_exist_add(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
测试插件不存在,添加插件
|
测试插件不存在,添加插件
|
||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
plugin_id = -1
|
plugin_id = -1
|
||||||
|
|
||||||
async with app.test_matcher(_matcher) as ctx:
|
async with app.test_matcher(_matcher) as ctx:
|
||||||
@@ -339,27 +236,25 @@ async def test_plugin_not_exist_add(
|
|||||||
)
|
)
|
||||||
ctx.should_call_send(
|
ctx.should_call_send(
|
||||||
event=event,
|
event=event,
|
||||||
message=Message(message="插件ID不存在..."),
|
message=Message(message="添加插件 Id: -1 失败 e: 插件ID不存在..."),
|
||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_add_plugin_exist(
|
async def test_add_plugin_exist(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
测试插件已经存在,添加插件
|
测试插件已经存在,添加插件
|
||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins",
|
"zhenxun.builtin_plugins.plugin_store.data_source.StoreManager.get_loaded_plugins",
|
||||||
return_value=[("search_image", "0.1")],
|
return_value=[("search_image", "0.1")],
|
||||||
)
|
)
|
||||||
plugin_id = 1
|
plugin_id = 1
|
||||||
@@ -385,7 +280,9 @@ async def test_add_plugin_exist(
|
|||||||
)
|
)
|
||||||
ctx.should_call_send(
|
ctx.should_call_send(
|
||||||
event=event,
|
event=event,
|
||||||
message=Message(message="插件 识图 已安装,无需重复安装"),
|
message=Message(
|
||||||
|
message="添加插件 Id: 1 失败 e: 插件 识图 已安装,无需重复安装"
|
||||||
|
),
|
||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
from collections.abc import Callable
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import cast
|
|
||||||
|
|
||||||
from nonebot.adapters.onebot.v11 import Bot, Message
|
|
||||||
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
|
|
||||||
from nonebug import App
|
|
||||||
from pytest_mock import MockerFixture
|
|
||||||
from respx import MockRouter
|
|
||||||
|
|
||||||
from tests.builtin_plugins.plugin_store.utils import init_mocked_api
|
|
||||||
from tests.config import BotId, GroupId, MessageId, UserId
|
|
||||||
from tests.utils import _v11_group_message_event
|
|
||||||
|
|
||||||
|
|
||||||
async def test_plugin_store(
|
|
||||||
app: App,
|
|
||||||
mocker: MockerFixture,
|
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
|
||||||
tmp_path: Path,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
测试插件商店
|
|
||||||
"""
|
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
|
||||||
from zhenxun.builtin_plugins.plugin_store.data_source import row_style
|
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
|
|
||||||
mock_table_page = mocker.patch(
|
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.ImageTemplate.table_page"
|
|
||||||
)
|
|
||||||
mock_table_page_return = mocker.AsyncMock()
|
|
||||||
mock_table_page.return_value = mock_table_page_return
|
|
||||||
|
|
||||||
mock_build_message = mocker.patch(
|
|
||||||
"zhenxun.builtin_plugins.plugin_store.MessageUtils.build_message"
|
|
||||||
)
|
|
||||||
mock_build_message_return = mocker.AsyncMock()
|
|
||||||
mock_build_message.return_value = mock_build_message_return
|
|
||||||
|
|
||||||
async with app.test_matcher(_matcher) as ctx:
|
|
||||||
bot = create_bot(ctx)
|
|
||||||
bot: Bot = cast(Bot, bot)
|
|
||||||
raw_message = "插件商店"
|
|
||||||
event: GroupMessageEvent = _v11_group_message_event(
|
|
||||||
message=raw_message,
|
|
||||||
self_id=BotId.QQ_BOT,
|
|
||||||
user_id=UserId.SUPERUSER,
|
|
||||||
group_id=GroupId.GROUP_ID_LEVEL_5,
|
|
||||||
message_id=MessageId.MESSAGE_ID_3,
|
|
||||||
to_me=True,
|
|
||||||
)
|
|
||||||
ctx.receive_event(bot=bot, event=event)
|
|
||||||
mock_table_page.assert_awaited_once_with(
|
|
||||||
"插件列表",
|
|
||||||
"通过添加/移除插件 ID 来管理插件",
|
|
||||||
["-", "ID", "名称", "简介", "作者", "版本", "类型"],
|
|
||||||
[
|
|
||||||
["", 0, "鸡汤", "喏,亲手为你煮的鸡汤", "HibiKier", "0.1", "普通插件"],
|
|
||||||
["", 1, "识图", "以图搜图,看破本源", "HibiKier", "0.1", "普通插件"],
|
|
||||||
["", 2, "网易云热评", "生了个人,我很抱歉", "HibiKier", "0.1", "普通插件"],
|
|
||||||
[
|
|
||||||
"",
|
|
||||||
3,
|
|
||||||
"B站订阅",
|
|
||||||
"非常便利的B站订阅通知",
|
|
||||||
"HibiKier",
|
|
||||||
"0.3-b101fbc",
|
|
||||||
"普通插件",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"",
|
|
||||||
4,
|
|
||||||
"github订阅",
|
|
||||||
"订阅github用户或仓库",
|
|
||||||
"xuanerwa",
|
|
||||||
"0.7",
|
|
||||||
"普通插件",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"",
|
|
||||||
5,
|
|
||||||
"Minecraft查服",
|
|
||||||
"Minecraft服务器状态查询,支持IPv6",
|
|
||||||
"molanp",
|
|
||||||
"1.13",
|
|
||||||
"普通插件",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
text_style=row_style,
|
|
||||||
)
|
|
||||||
mock_build_message.assert_called_once_with(mock_table_page_return)
|
|
||||||
mock_build_message_return.send.assert_awaited_once()
|
|
||||||
|
|
||||||
assert mocked_api["basic_plugins"].called
|
|
||||||
assert mocked_api["extra_plugins"].called
|
|
||||||
|
|
||||||
|
|
||||||
async def test_plugin_store_fail(
|
|
||||||
app: App,
|
|
||||||
mocker: MockerFixture,
|
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
|
||||||
tmp_path: Path,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
测试插件商店
|
|
||||||
"""
|
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
mocked_api.get(
|
|
||||||
"https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/b101fbc/plugins.json",
|
|
||||||
name="basic_plugins",
|
|
||||||
).respond(404)
|
|
||||||
|
|
||||||
async with app.test_matcher(_matcher) as ctx:
|
|
||||||
bot = create_bot(ctx)
|
|
||||||
bot: Bot = cast(Bot, bot)
|
|
||||||
raw_message = "插件商店"
|
|
||||||
event: GroupMessageEvent = _v11_group_message_event(
|
|
||||||
message=raw_message,
|
|
||||||
self_id=BotId.QQ_BOT,
|
|
||||||
user_id=UserId.SUPERUSER,
|
|
||||||
group_id=GroupId.GROUP_ID_LEVEL_5,
|
|
||||||
message_id=MessageId.MESSAGE_ID_3,
|
|
||||||
to_me=True,
|
|
||||||
)
|
|
||||||
ctx.receive_event(bot=bot, event=event)
|
|
||||||
ctx.should_call_send(
|
|
||||||
event=event,
|
|
||||||
message=Message("获取插件列表失败..."),
|
|
||||||
result=None,
|
|
||||||
exception=None,
|
|
||||||
bot=bot,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert mocked_api["basic_plugins"].called
|
|
||||||
@@ -8,18 +8,17 @@ from nonebot.adapters.onebot.v11 import Bot
|
|||||||
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
|
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
|
||||||
from nonebot.adapters.onebot.v11.message import Message
|
from nonebot.adapters.onebot.v11.message import Message
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
import pytest
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from respx import MockRouter
|
|
||||||
|
|
||||||
from tests.builtin_plugins.plugin_store.utils import get_content_bytes, init_mocked_api
|
|
||||||
from tests.config import BotId, GroupId, MessageId, UserId
|
from tests.config import BotId, GroupId, MessageId, UserId
|
||||||
from tests.utils import _v11_group_message_event
|
from tests.utils import _v11_group_message_event
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_remove_plugin(
|
async def test_remove_plugin(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -28,7 +27,6 @@ async def test_remove_plugin(
|
|||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
mock_base_path = mocker.patch(
|
mock_base_path = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
||||||
new=tmp_path / "zhenxun",
|
new=tmp_path / "zhenxun",
|
||||||
@@ -38,7 +36,7 @@ async def test_remove_plugin(
|
|||||||
plugin_path.mkdir(parents=True, exist_ok=True)
|
plugin_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
with open(plugin_path / "__init__.py", "wb") as f:
|
with open(plugin_path / "__init__.py", "wb") as f:
|
||||||
f.write(get_content_bytes("search_image.py"))
|
f.write(b"A_nmi")
|
||||||
|
|
||||||
plugin_id = 1
|
plugin_id = 1
|
||||||
|
|
||||||
@@ -61,24 +59,19 @@ async def test_remove_plugin(
|
|||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
assert mocked_api["basic_plugins"].called
|
|
||||||
assert mocked_api["extra_plugins"].called
|
|
||||||
assert not (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file()
|
assert not (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_plugin_not_exist_remove(
|
async def test_plugin_not_exist_remove(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
测试插件不存在,移除插件
|
测试插件不存在,移除插件
|
||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
plugin_id = -1
|
plugin_id = -1
|
||||||
|
|
||||||
async with app.test_matcher(_matcher) as ctx:
|
async with app.test_matcher(_matcher) as ctx:
|
||||||
@@ -96,16 +89,16 @@ async def test_plugin_not_exist_remove(
|
|||||||
ctx.receive_event(bot=bot, event=event)
|
ctx.receive_event(bot=bot, event=event)
|
||||||
ctx.should_call_send(
|
ctx.should_call_send(
|
||||||
event=event,
|
event=event,
|
||||||
message=Message(message="插件ID不存在..."),
|
message=Message(message="移除插件 Id: -1 失败 e: 插件ID不存在..."),
|
||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_remove_plugin_not_install(
|
async def test_remove_plugin_not_install(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -114,7 +107,6 @@ async def test_remove_plugin_not_install(
|
|||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
_ = mocker.patch(
|
_ = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
||||||
new=tmp_path / "zhenxun",
|
new=tmp_path / "zhenxun",
|
||||||
|
|||||||
@@ -1,33 +1,27 @@
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from nonebot.adapters.onebot.v11 import Bot
|
from nonebot.adapters.onebot.v11 import Bot
|
||||||
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
|
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
|
||||||
from nonebot.adapters.onebot.v11.message import Message
|
from nonebot.adapters.onebot.v11.message import Message
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
import pytest
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from respx import MockRouter
|
|
||||||
|
|
||||||
from tests.builtin_plugins.plugin_store.utils import init_mocked_api
|
|
||||||
from tests.config import BotId, GroupId, MessageId, UserId
|
from tests.config import BotId, GroupId, MessageId, UserId
|
||||||
from tests.utils import _v11_group_message_event
|
from tests.utils import _v11_group_message_event
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_search_plugin_name(
|
async def test_search_plugin_name(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
测试搜索插件
|
测试搜索插件
|
||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
from zhenxun.builtin_plugins.plugin_store.data_source import row_style
|
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
|
|
||||||
mock_table_page = mocker.patch(
|
mock_table_page = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.ImageTemplate.table_page"
|
"zhenxun.builtin_plugins.plugin_store.data_source.ImageTemplate.table_page"
|
||||||
@@ -56,44 +50,20 @@ async def test_search_plugin_name(
|
|||||||
to_me=True,
|
to_me=True,
|
||||||
)
|
)
|
||||||
ctx.receive_event(bot=bot, event=event)
|
ctx.receive_event(bot=bot, event=event)
|
||||||
mock_table_page.assert_awaited_once_with(
|
|
||||||
"插件列表",
|
|
||||||
"通过添加/移除插件 ID 来管理插件",
|
|
||||||
["-", "ID", "名称", "简介", "作者", "版本", "类型"],
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"",
|
|
||||||
4,
|
|
||||||
"github订阅",
|
|
||||||
"订阅github用户或仓库",
|
|
||||||
"xuanerwa",
|
|
||||||
"0.7",
|
|
||||||
"普通插件",
|
|
||||||
]
|
|
||||||
],
|
|
||||||
text_style=row_style,
|
|
||||||
)
|
|
||||||
mock_build_message.assert_called_once_with(mock_table_page_return)
|
mock_build_message.assert_called_once_with(mock_table_page_return)
|
||||||
mock_build_message_return.send.assert_awaited_once()
|
mock_build_message_return.send.assert_awaited_once()
|
||||||
|
|
||||||
assert mocked_api["basic_plugins"].called
|
|
||||||
assert mocked_api["extra_plugins"].called
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_search_plugin_author(
|
async def test_search_plugin_author(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
测试搜索插件,作者
|
测试搜索插件,作者
|
||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
from zhenxun.builtin_plugins.plugin_store.data_source import row_style
|
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
|
|
||||||
mock_table_page = mocker.patch(
|
mock_table_page = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.ImageTemplate.table_page"
|
"zhenxun.builtin_plugins.plugin_store.data_source.ImageTemplate.table_page"
|
||||||
@@ -122,43 +92,20 @@ async def test_search_plugin_author(
|
|||||||
to_me=True,
|
to_me=True,
|
||||||
)
|
)
|
||||||
ctx.receive_event(bot=bot, event=event)
|
ctx.receive_event(bot=bot, event=event)
|
||||||
mock_table_page.assert_awaited_once_with(
|
|
||||||
"插件列表",
|
|
||||||
"通过添加/移除插件 ID 来管理插件",
|
|
||||||
["-", "ID", "名称", "简介", "作者", "版本", "类型"],
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"",
|
|
||||||
4,
|
|
||||||
"github订阅",
|
|
||||||
"订阅github用户或仓库",
|
|
||||||
"xuanerwa",
|
|
||||||
"0.7",
|
|
||||||
"普通插件",
|
|
||||||
]
|
|
||||||
],
|
|
||||||
text_style=row_style,
|
|
||||||
)
|
|
||||||
mock_build_message.assert_called_once_with(mock_table_page_return)
|
mock_build_message.assert_called_once_with(mock_table_page_return)
|
||||||
mock_build_message_return.send.assert_awaited_once()
|
mock_build_message_return.send.assert_awaited_once()
|
||||||
|
|
||||||
assert mocked_api["basic_plugins"].called
|
|
||||||
assert mocked_api["extra_plugins"].called
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_plugin_not_exist_search(
|
async def test_plugin_not_exist_search(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
测试插件不存在,搜索插件
|
测试插件不存在,搜索插件
|
||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
plugin_name = "not_exist_plugin_name"
|
plugin_name = "not_exist_plugin_name"
|
||||||
|
|
||||||
async with app.test_matcher(_matcher) as ctx:
|
async with app.test_matcher(_matcher) as ctx:
|
||||||
|
|||||||
@@ -6,18 +6,17 @@ from nonebot.adapters.onebot.v11 import Bot
|
|||||||
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
|
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
|
||||||
from nonebot.adapters.onebot.v11.message import Message
|
from nonebot.adapters.onebot.v11.message import Message
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
import pytest
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from respx import MockRouter
|
|
||||||
|
|
||||||
from tests.builtin_plugins.plugin_store.utils import init_mocked_api
|
|
||||||
from tests.config import BotId, GroupId, MessageId, UserId
|
from tests.config import BotId, GroupId, MessageId, UserId
|
||||||
from tests.utils import _v11_group_message_event
|
from tests.utils import _v11_group_message_event
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_update_all_plugin_basic_need_update(
|
async def test_update_all_plugin_basic_need_update(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -26,13 +25,12 @@ async def test_update_all_plugin_basic_need_update(
|
|||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
mock_base_path = mocker.patch(
|
mock_base_path = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
||||||
new=tmp_path / "zhenxun",
|
new=tmp_path / "zhenxun",
|
||||||
)
|
)
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins",
|
"zhenxun.builtin_plugins.plugin_store.data_source.StoreManager.get_loaded_plugins",
|
||||||
return_value=[("search_image", "0.0")],
|
return_value=[("search_image", "0.0")],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,16 +61,13 @@ async def test_update_all_plugin_basic_need_update(
|
|||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
assert mocked_api["basic_plugins"].called
|
|
||||||
assert mocked_api["extra_plugins"].called
|
|
||||||
assert mocked_api["search_image_plugin_file_init_commit"].called
|
|
||||||
assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file()
|
assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_update_all_plugin_basic_is_new(
|
async def test_update_all_plugin_basic_is_new(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -81,14 +76,13 @@ async def test_update_all_plugin_basic_is_new(
|
|||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
||||||
new=tmp_path / "zhenxun",
|
new=tmp_path / "zhenxun",
|
||||||
)
|
)
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins",
|
"zhenxun.builtin_plugins.plugin_store.data_source.StoreManager.get_loaded_plugins",
|
||||||
return_value=[("search_image", "0.1")],
|
return_value=[("search_image", "0.2")],
|
||||||
)
|
)
|
||||||
|
|
||||||
async with app.test_matcher(_matcher) as ctx:
|
async with app.test_matcher(_matcher) as ctx:
|
||||||
@@ -116,5 +110,3 @@ async def test_update_all_plugin_basic_is_new(
|
|||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
assert mocked_api["basic_plugins"].called
|
|
||||||
assert mocked_api["extra_plugins"].called
|
|
||||||
|
|||||||
@@ -6,18 +6,17 @@ from nonebot.adapters.onebot.v11 import Bot
|
|||||||
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
|
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
|
||||||
from nonebot.adapters.onebot.v11.message import Message
|
from nonebot.adapters.onebot.v11.message import Message
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
import pytest
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from respx import MockRouter
|
|
||||||
|
|
||||||
from tests.builtin_plugins.plugin_store.utils import init_mocked_api
|
|
||||||
from tests.config import BotId, GroupId, MessageId, UserId
|
from tests.config import BotId, GroupId, MessageId, UserId
|
||||||
from tests.utils import _v11_group_message_event
|
from tests.utils import _v11_group_message_event
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_update_plugin_basic_need_update(
|
async def test_update_plugin_basic_need_update(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -26,13 +25,12 @@ async def test_update_plugin_basic_need_update(
|
|||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
mock_base_path = mocker.patch(
|
mock_base_path = mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
||||||
new=tmp_path / "zhenxun",
|
new=tmp_path / "zhenxun",
|
||||||
)
|
)
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins",
|
"zhenxun.builtin_plugins.plugin_store.data_source.StoreManager.get_loaded_plugins",
|
||||||
return_value=[("search_image", "0.0")],
|
return_value=[("search_image", "0.0")],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,16 +61,13 @@ async def test_update_plugin_basic_need_update(
|
|||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
assert mocked_api["basic_plugins"].called
|
|
||||||
assert mocked_api["extra_plugins"].called
|
|
||||||
assert mocked_api["search_image_plugin_file_init_commit"].called
|
|
||||||
assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file()
|
assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_update_plugin_basic_is_new(
|
async def test_update_plugin_basic_is_new(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -81,14 +76,13 @@ async def test_update_plugin_basic_is_new(
|
|||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
|
||||||
new=tmp_path / "zhenxun",
|
new=tmp_path / "zhenxun",
|
||||||
)
|
)
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
"zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins",
|
"zhenxun.builtin_plugins.plugin_store.data_source.StoreManager.get_loaded_plugins",
|
||||||
return_value=[("search_image", "0.1")],
|
return_value=[("search_image", "0.2")],
|
||||||
)
|
)
|
||||||
|
|
||||||
plugin_id = 1
|
plugin_id = 1
|
||||||
@@ -118,23 +112,18 @@ async def test_update_plugin_basic_is_new(
|
|||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
assert mocked_api["basic_plugins"].called
|
|
||||||
assert mocked_api["extra_plugins"].called
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_plugin_not_exist_update(
|
async def test_plugin_not_exist_update(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
测试插件不存在,更新插件
|
测试插件不存在,更新插件
|
||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
plugin_id = -1
|
plugin_id = -1
|
||||||
|
|
||||||
async with app.test_matcher(_matcher) as ctx:
|
async with app.test_matcher(_matcher) as ctx:
|
||||||
@@ -158,25 +147,22 @@ async def test_plugin_not_exist_update(
|
|||||||
)
|
)
|
||||||
ctx.should_call_send(
|
ctx.should_call_send(
|
||||||
event=event,
|
event=event,
|
||||||
message=Message(message="插件ID不存在..."),
|
message=Message(message="更新插件 Id: -1 失败 e: 插件ID不存在..."),
|
||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
async def test_update_plugin_not_install(
|
async def test_update_plugin_not_install(
|
||||||
app: App,
|
app: App,
|
||||||
mocker: MockerFixture,
|
|
||||||
mocked_api: MockRouter,
|
|
||||||
create_bot: Callable,
|
create_bot: Callable,
|
||||||
tmp_path: Path,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
测试插件不存在,更新插件
|
测试插件不存在,更新插件
|
||||||
"""
|
"""
|
||||||
from zhenxun.builtin_plugins.plugin_store import _matcher
|
from zhenxun.builtin_plugins.plugin_store import _matcher
|
||||||
|
|
||||||
init_mocked_api(mocked_api=mocked_api)
|
|
||||||
plugin_id = 1
|
plugin_id = 1
|
||||||
|
|
||||||
async with app.test_matcher(_matcher) as ctx:
|
async with app.test_matcher(_matcher) as ctx:
|
||||||
@@ -200,7 +186,9 @@ async def test_update_plugin_not_install(
|
|||||||
)
|
)
|
||||||
ctx.should_call_send(
|
ctx.should_call_send(
|
||||||
event=event,
|
event=event,
|
||||||
message=Message(message="插件 识图 未安装,无法更新"),
|
message=Message(
|
||||||
|
message="更新插件 Id: 1 失败 e: 插件 识图 未安装,无法更新"
|
||||||
|
),
|
||||||
result=None,
|
result=None,
|
||||||
bot=bot,
|
bot=bot,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
# ruff: noqa: ASYNC230
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from respx import MockRouter
|
|
||||||
|
|
||||||
from tests.utils import get_content_bytes as _get_content_bytes
|
|
||||||
from tests.utils import get_response_json as _get_response_json
|
|
||||||
|
|
||||||
|
|
||||||
def get_response_json(file: str) -> dict:
|
|
||||||
return _get_response_json(Path() / "plugin_store", file=file)
|
|
||||||
|
|
||||||
|
|
||||||
def get_content_bytes(file: str) -> bytes:
|
|
||||||
return _get_content_bytes(Path() / "plugin_store", file)
|
|
||||||
|
|
||||||
|
|
||||||
def init_mocked_api(mocked_api: MockRouter) -> None:
|
|
||||||
# metadata
|
|
||||||
mocked_api.get(
|
|
||||||
"https://data.jsdelivr.com/v1/packages/gh/zhenxun-org/zhenxun_bot_plugins@main",
|
|
||||||
name="zhenxun_bot_plugins_metadata",
|
|
||||||
).respond(json=get_response_json("zhenxun_bot_plugins_metadata.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://data.jsdelivr.com/v1/packages/gh/xuanerwa/zhenxun_github_sub@main",
|
|
||||||
name="zhenxun_github_sub_metadata",
|
|
||||||
).respond(json=get_response_json("zhenxun_github_sub_metadata.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://data.jsdelivr.com/v1/packages/gh/zhenxun-org/zhenxun_bot_plugins@b101fbc",
|
|
||||||
name="zhenxun_bot_plugins_metadata_commit",
|
|
||||||
).respond(json=get_response_json("zhenxun_bot_plugins_metadata.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://data.jsdelivr.com/v1/packages/gh/xuanerwa/zhenxun_github_sub@f524632f78d27f9893beebdf709e0e7885cd08f1",
|
|
||||||
name="zhenxun_github_sub_metadata_commit",
|
|
||||||
).respond(json=get_response_json("zhenxun_github_sub_metadata.json"))
|
|
||||||
|
|
||||||
# tree
|
|
||||||
mocked_api.get(
|
|
||||||
"https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/main?recursive=1",
|
|
||||||
name="zhenxun_bot_plugins_tree",
|
|
||||||
).respond(json=get_response_json("zhenxun_bot_plugins_tree.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/trees/main?recursive=1",
|
|
||||||
name="zhenxun_github_sub_tree",
|
|
||||||
).respond(json=get_response_json("zhenxun_github_sub_tree.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/b101fbc?recursive=1",
|
|
||||||
name="zhenxun_bot_plugins_tree_commit",
|
|
||||||
).respond(json=get_response_json("zhenxun_bot_plugins_tree.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/trees/f524632f78d27f9893beebdf709e0e7885cd08f1?recursive=1",
|
|
||||||
name="zhenxun_github_sub_tree_commit",
|
|
||||||
).respond(json=get_response_json("zhenxun_github_sub_tree.json"))
|
|
||||||
|
|
||||||
mocked_api.head(
|
|
||||||
"https://raw.githubusercontent.com/",
|
|
||||||
name="head_raw",
|
|
||||||
).respond(200, text="")
|
|
||||||
|
|
||||||
mocked_api.get(
|
|
||||||
"https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/b101fbc/plugins.json",
|
|
||||||
name="basic_plugins",
|
|
||||||
).respond(json=get_response_json("basic_plugins.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins@b101fbc/plugins.json",
|
|
||||||
name="basic_plugins_jsdelivr",
|
|
||||||
).respond(200, json=get_response_json("basic_plugins.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/main/plugins.json",
|
|
||||||
name="basic_plugins_no_commit",
|
|
||||||
).respond(json=get_response_json("basic_plugins.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins@main/plugins.json",
|
|
||||||
name="basic_plugins_jsdelivr_no_commit",
|
|
||||||
).respond(200, json=get_response_json("basic_plugins.json"))
|
|
||||||
|
|
||||||
mocked_api.get(
|
|
||||||
"https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins_index/2ed61284873c526802752b12a3fd3b5e1a59d948/plugins.json",
|
|
||||||
name="extra_plugins",
|
|
||||||
).respond(200, json=get_response_json("extra_plugins.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins_index@2ed61284873c526802752b12a3fd3b5e1a59d948/plugins.json",
|
|
||||||
name="extra_plugins_jsdelivr",
|
|
||||||
).respond(200, json=get_response_json("extra_plugins.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins_index/index/plugins.json",
|
|
||||||
name="extra_plugins_no_commit",
|
|
||||||
).respond(200, json=get_response_json("extra_plugins.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins_index@index/plugins.json",
|
|
||||||
name="extra_plugins_jsdelivr_no_commit",
|
|
||||||
).respond(200, json=get_response_json("extra_plugins.json"))
|
|
||||||
|
|
||||||
mocked_api.get(
|
|
||||||
"https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/main/plugins/search_image/__init__.py",
|
|
||||||
name="search_image_plugin_file_init",
|
|
||||||
).respond(content=get_content_bytes("search_image.py"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/b101fbc/plugins/search_image/__init__.py",
|
|
||||||
name="search_image_plugin_file_init_commit",
|
|
||||||
).respond(content=get_content_bytes("search_image.py"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/main/plugins/alapi/jitang.py",
|
|
||||||
name="jitang_plugin_file",
|
|
||||||
).respond(content=get_content_bytes("jitang.py"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/b101fbc/plugins/alapi/jitang.py",
|
|
||||||
name="jitang_plugin_file_commit",
|
|
||||||
).respond(content=get_content_bytes("jitang.py"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://raw.githubusercontent.com/xuanerwa/zhenxun_github_sub/main/github_sub/__init__.py",
|
|
||||||
name="github_sub_plugin_file_init",
|
|
||||||
).respond(content=get_content_bytes("github_sub.py"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://raw.githubusercontent.com/xuanerwa/zhenxun_github_sub/f524632f78d27f9893beebdf709e0e7885cd08f1/github_sub/__init__.py",
|
|
||||||
name="github_sub_plugin_file_init_commit",
|
|
||||||
).respond(content=get_content_bytes("github_sub.py"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/b101fbc/plugins/bilibili_sub/__init__.py",
|
|
||||||
name="bilibili_sub_plugin_file_init",
|
|
||||||
).respond(content=get_content_bytes("bilibili_sub.py"))
|
|
||||||
|
|
||||||
mocked_api.get(
|
|
||||||
"https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/commits/main",
|
|
||||||
name="zhenxun_bot_plugins_commit",
|
|
||||||
).respond(json=get_response_json("zhenxun_bot_plugins_commit.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://git-api.zhenxun.org/repos/zhenxun-org/zhenxun_bot_plugins/commits/main",
|
|
||||||
name="zhenxun_bot_plugins_commit_proxy",
|
|
||||||
).respond(json=get_response_json("zhenxun_bot_plugins_commit.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins_index/commits/index",
|
|
||||||
name="zhenxun_bot_plugins_index_commit",
|
|
||||||
).respond(json=get_response_json("zhenxun_bot_plugins_index_commit.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://git-api.zhenxun.org/repos/zhenxun-org/zhenxun_bot_plugins_index/commits/index",
|
|
||||||
name="zhenxun_bot_plugins_index_commit_proxy",
|
|
||||||
).respond(json=get_response_json("zhenxun_bot_plugins_index_commit.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://api.github.com/repos/xuanerwa/zhenxun_github_sub/commits/main",
|
|
||||||
name="zhenxun_github_sub_commit",
|
|
||||||
).respond(json=get_response_json("zhenxun_github_sub_commit.json"))
|
|
||||||
mocked_api.get(
|
|
||||||
"https://git-api.zhenxun.org/repos/xuanerwa/zhenxun_github_sub/commits/main",
|
|
||||||
name="zhenxun_github_sub_commit_proxy",
|
|
||||||
).respond(json=get_response_json("zhenxun_github_sub_commit.json"))
|
|
||||||
@@ -116,6 +116,7 @@ async def app(app: App, tmp_path: Path, mocker: MockerFixture):
|
|||||||
await init()
|
await init()
|
||||||
# await driver._lifespan.startup()
|
# await driver._lifespan.startup()
|
||||||
os.environ["AIOCACHE_DISABLE"] = "1"
|
os.environ["AIOCACHE_DISABLE"] = "1"
|
||||||
|
os.environ["PYTEST_CURRENT_TEST"] = "1"
|
||||||
|
|
||||||
yield app
|
yield app
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
|
|
||||||
from zhenxun.configs.utils import PluginExtraData
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="B站订阅",
|
|
||||||
description="非常便利的B站订阅通知",
|
|
||||||
usage="""
|
|
||||||
usage:
|
|
||||||
B站直播,番剧,UP动态开播等提醒
|
|
||||||
主播订阅相当于 直播间订阅 + UP订阅
|
|
||||||
指令:
|
|
||||||
添加订阅 ['主播'/'UP'/'番剧'] [id/链接/番名]
|
|
||||||
删除订阅 ['主播'/'UP'/'id'] [id]
|
|
||||||
查看订阅
|
|
||||||
示例:
|
|
||||||
添加订阅主播 2345344 <-(直播房间id)
|
|
||||||
添加订阅UP 2355543 <-(个人主页id)
|
|
||||||
添加订阅番剧 史莱姆 <-(支持模糊搜索)
|
|
||||||
添加订阅番剧 125344 <-(番剧id)
|
|
||||||
删除订阅id 2324344 <-(任意id,通过查看订阅获取)
|
|
||||||
""".strip(),
|
|
||||||
extra=PluginExtraData(
|
|
||||||
author="HibiKier",
|
|
||||||
version="0.3-b101fbc",
|
|
||||||
superuser_help="""
|
|
||||||
登录b站获取cookie防止风控:
|
|
||||||
bil_check/检测b站
|
|
||||||
bil_login/登录b站
|
|
||||||
bil_logout/退出b站 uid
|
|
||||||
示例:
|
|
||||||
登录b站
|
|
||||||
检测b站
|
|
||||||
bil_logout 12345<-(退出登录的b站uid,通过检测b站获取)
|
|
||||||
""",
|
|
||||||
).to_dict(),
|
|
||||||
)
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
|
|
||||||
from zhenxun.configs.utils import PluginExtraData
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="github订阅",
|
|
||||||
description="订阅github用户或仓库",
|
|
||||||
usage="""
|
|
||||||
usage:
|
|
||||||
github新Comment,PR,Issue等提醒
|
|
||||||
指令:
|
|
||||||
添加github ['用户'/'仓库'] [用户名/{owner/repo}]
|
|
||||||
删除github [用户名/{owner/repo}]
|
|
||||||
查看github
|
|
||||||
示例:添加github订阅 用户 HibiKier
|
|
||||||
示例:添加gb订阅 仓库 HibiKier/zhenxun_bot
|
|
||||||
示例:添加github 用户 HibiKier
|
|
||||||
示例:删除gb订阅 HibiKier
|
|
||||||
""".strip(),
|
|
||||||
extra=PluginExtraData(
|
|
||||||
author="xuanerwa",
|
|
||||||
version="0.7",
|
|
||||||
).to_dict(),
|
|
||||||
)
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
|
|
||||||
from zhenxun.configs.utils import PluginExtraData
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="鸡汤",
|
|
||||||
description="喏,亲手为你煮的鸡汤",
|
|
||||||
usage="""
|
|
||||||
不喝点什么感觉有点不舒服
|
|
||||||
指令:
|
|
||||||
鸡汤
|
|
||||||
""".strip(),
|
|
||||||
extra=PluginExtraData(
|
|
||||||
author="HibiKier",
|
|
||||||
version="0.1",
|
|
||||||
).to_dict(),
|
|
||||||
)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
|
|
||||||
from zhenxun.configs.utils import PluginExtraData
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="识图",
|
|
||||||
description="以图搜图,看破本源",
|
|
||||||
usage="""
|
|
||||||
识别图片 [二次元图片]
|
|
||||||
指令:
|
|
||||||
识图 [图片]
|
|
||||||
""".strip(),
|
|
||||||
extra=PluginExtraData(
|
|
||||||
author="HibiKier",
|
|
||||||
version="0.1",
|
|
||||||
menu_type="一些工具",
|
|
||||||
).to_dict(),
|
|
||||||
)
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"鸡汤": {
|
|
||||||
"module": "jitang",
|
|
||||||
"module_path": "plugins.alapi.jitang",
|
|
||||||
"description": "喏,亲手为你煮的鸡汤",
|
|
||||||
"usage": "不喝点什么感觉有点不舒服\n 指令:\n 鸡汤",
|
|
||||||
"author": "HibiKier",
|
|
||||||
"version": "0.1",
|
|
||||||
"plugin_type": "NORMAL",
|
|
||||||
"is_dir": false
|
|
||||||
},
|
|
||||||
"识图": {
|
|
||||||
"module": "search_image",
|
|
||||||
"module_path": "plugins.search_image",
|
|
||||||
"description": "以图搜图,看破本源",
|
|
||||||
"usage": "识别图片 [二次元图片]\n 指令:\n 识图 [图片]",
|
|
||||||
"author": "HibiKier",
|
|
||||||
"version": "0.1",
|
|
||||||
"plugin_type": "NORMAL",
|
|
||||||
"is_dir": true
|
|
||||||
},
|
|
||||||
"网易云热评": {
|
|
||||||
"module": "comments_163",
|
|
||||||
"module_path": "plugins.alapi.comments_163",
|
|
||||||
"description": "生了个人,我很抱歉",
|
|
||||||
"usage": "到点了,还是防不了下塔\n 指令:\n 网易云热评/到点了/12点了",
|
|
||||||
"author": "HibiKier",
|
|
||||||
"version": "0.1",
|
|
||||||
"plugin_type": "NORMAL",
|
|
||||||
"is_dir": false
|
|
||||||
},
|
|
||||||
"B站订阅": {
|
|
||||||
"module": "bilibili_sub",
|
|
||||||
"module_path": "plugins.bilibili_sub",
|
|
||||||
"description": "非常便利的B站订阅通知",
|
|
||||||
"usage": "B站直播,番剧,UP动态开播等提醒",
|
|
||||||
"author": "HibiKier",
|
|
||||||
"version": "0.3-b101fbc",
|
|
||||||
"plugin_type": "NORMAL",
|
|
||||||
"is_dir": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"github订阅": {
|
|
||||||
"module": "github_sub",
|
|
||||||
"module_path": "github_sub",
|
|
||||||
"description": "订阅github用户或仓库",
|
|
||||||
"usage": "usage:\n github新Comment,PR,Issue等提醒\n 指令:\n 添加github ['用户'/'仓库'] [用户名/{owner/repo}]\n 删除github [用户名/{owner/repo}]\n 查看github\n 示例:添加github订阅 用户 HibiKier\n 示例:添加gb订阅 仓库 HibiKier/zhenxun_bot\n 示例:添加github 用户 HibiKier\n 示例:删除gb订阅 HibiKier",
|
|
||||||
"author": "xuanerwa",
|
|
||||||
"version": "0.7",
|
|
||||||
"plugin_type": "NORMAL",
|
|
||||||
"is_dir": true,
|
|
||||||
"github_url": "https://github.com/xuanerwa/zhenxun_github_sub"
|
|
||||||
},
|
|
||||||
"Minecraft查服": {
|
|
||||||
"module": "mc_check",
|
|
||||||
"module_path": "mc_check",
|
|
||||||
"description": "Minecraft服务器状态查询,支持IPv6",
|
|
||||||
"usage": "Minecraft服务器状态查询,支持IPv6\n用法:\n\t查服 [ip]:[端口] / 查服 [ip]\n\t设置语言 zh-cn\n\t当前语言\n\t语言列表\neg:\t\nmcheck ip:port / mcheck ip\n\tset_lang en\n\tlang_now\n\tlang_list",
|
|
||||||
"author": "molanp",
|
|
||||||
"version": "1.13",
|
|
||||||
"plugin_type": "NORMAL",
|
|
||||||
"is_dir": true,
|
|
||||||
"github_url": "https://github.com/molanp/zhenxun_check_Minecraft"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
{
|
|
||||||
"sha": "b101fbc",
|
|
||||||
"node_id": "C_kwDOMndPGNoAKGIxMDFmYmNlODg4NjA4ZTJiYmU1YjVmZDI3OWUxNDY1MTY4ODEyYzc",
|
|
||||||
"commit": {
|
|
||||||
"author": {
|
|
||||||
"name": "xuaner",
|
|
||||||
"email": "xuaner_wa@qq.com",
|
|
||||||
"date": "2024-09-20T12:08:27Z"
|
|
||||||
},
|
|
||||||
"committer": {
|
|
||||||
"name": "xuaner",
|
|
||||||
"email": "xuaner_wa@qq.com",
|
|
||||||
"date": "2024-09-20T12:08:27Z"
|
|
||||||
},
|
|
||||||
"message": "🐛修复B站订阅bug",
|
|
||||||
"tree": {
|
|
||||||
"sha": "0566306219a434f7122798647498faef692c1879",
|
|
||||||
"url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/0566306219a434f7122798647498faef692c1879"
|
|
||||||
},
|
|
||||||
"url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/commits/b101fbce888608e2bbe5b5fd279e1465168812c7",
|
|
||||||
"comment_count": 0,
|
|
||||||
"verification": {
|
|
||||||
"verified": false,
|
|
||||||
"reason": "unsigned",
|
|
||||||
"signature": null,
|
|
||||||
"payload": null,
|
|
||||||
"verified_at": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/commits/b101fbce888608e2bbe5b5fd279e1465168812c7",
|
|
||||||
"html_url": "https://github.com/zhenxun-org/zhenxun_bot_plugins/commit/b101fbce888608e2bbe5b5fd279e1465168812c7",
|
|
||||||
"comments_url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/commits/b101fbce888608e2bbe5b5fd279e1465168812c7/comments",
|
|
||||||
"author": {
|
|
||||||
"login": "xuanerwa",
|
|
||||||
"id": 58063798,
|
|
||||||
"node_id": "MDQ6VXNlcjU4MDYzNzk4",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/58063798?v=4",
|
|
||||||
"gravatar_id": "",
|
|
||||||
"url": "https://api.github.com/users/xuanerwa",
|
|
||||||
"html_url": "https://github.com/xuanerwa",
|
|
||||||
"followers_url": "https://api.github.com/users/xuanerwa/followers",
|
|
||||||
"following_url": "https://api.github.com/users/xuanerwa/following{/other_user}",
|
|
||||||
"gists_url": "https://api.github.com/users/xuanerwa/gists{/gist_id}",
|
|
||||||
"starred_url": "https://api.github.com/users/xuanerwa/starred{/owner}{/repo}",
|
|
||||||
"subscriptions_url": "https://api.github.com/users/xuanerwa/subscriptions",
|
|
||||||
"organizations_url": "https://api.github.com/users/xuanerwa/orgs",
|
|
||||||
"repos_url": "https://api.github.com/users/xuanerwa/repos",
|
|
||||||
"events_url": "https://api.github.com/users/xuanerwa/events{/privacy}",
|
|
||||||
"received_events_url": "https://api.github.com/users/xuanerwa/received_events",
|
|
||||||
"type": "User",
|
|
||||||
"user_view_type": "public",
|
|
||||||
"site_admin": false
|
|
||||||
},
|
|
||||||
"committer": {
|
|
||||||
"login": "xuanerwa",
|
|
||||||
"id": 58063798,
|
|
||||||
"node_id": "MDQ6VXNlcjU4MDYzNzk4",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/58063798?v=4",
|
|
||||||
"gravatar_id": "",
|
|
||||||
"url": "https://api.github.com/users/xuanerwa",
|
|
||||||
"html_url": "https://github.com/xuanerwa",
|
|
||||||
"followers_url": "https://api.github.com/users/xuanerwa/followers",
|
|
||||||
"following_url": "https://api.github.com/users/xuanerwa/following{/other_user}",
|
|
||||||
"gists_url": "https://api.github.com/users/xuanerwa/gists{/gist_id}",
|
|
||||||
"starred_url": "https://api.github.com/users/xuanerwa/starred{/owner}{/repo}",
|
|
||||||
"subscriptions_url": "https://api.github.com/users/xuanerwa/subscriptions",
|
|
||||||
"organizations_url": "https://api.github.com/users/xuanerwa/orgs",
|
|
||||||
"repos_url": "https://api.github.com/users/xuanerwa/repos",
|
|
||||||
"events_url": "https://api.github.com/users/xuanerwa/events{/privacy}",
|
|
||||||
"received_events_url": "https://api.github.com/users/xuanerwa/received_events",
|
|
||||||
"type": "User",
|
|
||||||
"user_view_type": "public",
|
|
||||||
"site_admin": false
|
|
||||||
},
|
|
||||||
"parents": [
|
|
||||||
{
|
|
||||||
"sha": "a545dfa0c4e149595f7ddd50dc34c55513738fb9",
|
|
||||||
"url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/commits/a545dfa0c4e149595f7ddd50dc34c55513738fb9",
|
|
||||||
"html_url": "https://github.com/zhenxun-org/zhenxun_bot_plugins/commit/a545dfa0c4e149595f7ddd50dc34c55513738fb9"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stats": {
|
|
||||||
"total": 4,
|
|
||||||
"additions": 2,
|
|
||||||
"deletions": 2
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"sha": "0fbc9695db04c56174e3bff933f670d8d2df2abc",
|
|
||||||
"filename": "plugins/bilibili_sub/data_source.py",
|
|
||||||
"status": "modified",
|
|
||||||
"additions": 2,
|
|
||||||
"deletions": 2,
|
|
||||||
"changes": 4,
|
|
||||||
"blob_url": "https://github.com/zhenxun-org/zhenxun_bot_plugins/blob/b101fbce888608e2bbe5b5fd279e1465168812c7/plugins%2Fbilibili_sub%2Fdata_source.py",
|
|
||||||
"raw_url": "https://github.com/zhenxun-org/zhenxun_bot_plugins/raw/b101fbce888608e2bbe5b5fd279e1465168812c7/plugins%2Fbilibili_sub%2Fdata_source.py",
|
|
||||||
"contents_url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/contents/plugins%2Fbilibili_sub%2Fdata_source.py?ref=b101fbce888608e2bbe5b5fd279e1465168812c7",
|
|
||||||
"patch": "@@ -271,14 +271,14 @@ async def _get_live_status(id_: int) -> list:\n sub = await BilibiliSub.get_or_none(sub_id=id_)\n msg_list = []\n if sub.live_status != live_status:\n+ await BilibiliSub.sub_handle(id_, live_status=live_status)\n image = None\n try:\n image_bytes = await fetch_image_bytes(cover)\n image = BuildImage(background = image_bytes)\n except Exception as e:\n logger.error(f\"图片构造失败,错误信息:{e}\")\n if sub.live_status in [0, 2] and live_status == 1 and image:\n- await BilibiliSub.sub_handle(id_, live_status=live_status)\n msg_list = [\n image,\n \"\\n\",\n@@ -322,7 +322,7 @@ async def _get_up_status(id_: int) -> list:\n video = video_info[\"list\"][\"vlist\"][0]\n latest_video_created = video[\"created\"]\n msg_list = []\n- if dynamic_img:\n+ if dynamic_img and _user.dynamic_upload_time < dynamic_upload_time:\n await BilibiliSub.sub_handle(id_, dynamic_upload_time=dynamic_upload_time)\n msg_list = [f\"{uname} 发布了动态!📢\\n\", dynamic_img, f\"\\n查看详情:{link}\"]\n if ("
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
{
|
|
||||||
"sha": "2ed61284873c526802752b12a3fd3b5e1a59d948",
|
|
||||||
"node_id": "C_kwDOGK5Du9oAKDJlZDYxMjg0ODczYzUyNjgwMjc1MmIxMmEzZmQzYjVlMWE1OWQ5NDg",
|
|
||||||
"commit": {
|
|
||||||
"author": {
|
|
||||||
"name": "zhenxunflow[bot]",
|
|
||||||
"email": "179375394+zhenxunflow[bot]@users.noreply.github.com",
|
|
||||||
"date": "2025-01-26T09:04:55Z"
|
|
||||||
},
|
|
||||||
"committer": {
|
|
||||||
"name": "GitHub",
|
|
||||||
"email": "noreply@github.com",
|
|
||||||
"date": "2025-01-26T09:04:55Z"
|
|
||||||
},
|
|
||||||
"message": ":beers: publish plugin AI全家桶 (#235) (#236)\n\nCo-authored-by: molanp <molanp@users.noreply.github.com>",
|
|
||||||
"tree": {
|
|
||||||
"sha": "64ea463e084b6ab0def0322c6ad53799054ec9b3",
|
|
||||||
"url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins_index/git/trees/64ea463e084b6ab0def0322c6ad53799054ec9b3"
|
|
||||||
},
|
|
||||||
"url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins_index/git/commits/2ed61284873c526802752b12a3fd3b5e1a59d948",
|
|
||||||
"comment_count": 0,
|
|
||||||
"verification": {
|
|
||||||
"verified": true,
|
|
||||||
"reason": "valid",
|
|
||||||
"signature": "-----BEGIN PGP SIGNATURE-----\n\nwsFcBAABCAAQBQJnlfq3CRC1aQ7uu5UhlAAA+n0QADPVjQQIHFlNcTEgdq3LGQ1X\nm8+H5N07E5JD+83LdyU9/YOvqY/WURwFsQ0T4+23icUWEOD4LB5qZIdVJBYHseto\nbJNmYd1kZxpvsONoiK/2Uk6JoeVnEQIR+dTbB0wBlbL0lRt1WtTXHpLQbFXuXn3q\nJh4SdSj283UZ6D2sBADblPZ7DqaTmLlpgwrTPx0OH5wIhcuORkzOl6x0DabcVAYu\nu5zHSKM9c7g+jEmrqRuVy+ZlZMDPN4S3gDNzEhoTn4tn+KNzSIja4n7ZMRD+1a5X\nMIP3aXcVBqCyuYc6DU76IvjlaL/MjnlPwfOtx1zu+pNxZKNaSpojtqopp3blfk0E\n8s8lD9utDgUaUrdPWgpiMDjj+oNMye91CGomNDfv0fNGUlBGT6r48qaq1z8BwAAR\nzgDsF13kDuKTTkT/6T8CdgCpJtwvxMptUr2XFRtn4xwf/gJdqrbEc4fHTOSHqxzh\ncDfXuP+Sorla4oJ0duygTsulpr/zguX8RJWJml35VjERw54ARAVvhZn19G9qQVJo\n2QIp+xtyTjkM3yTeN4UDXFt4lDuxz3+l1MBduj+CHn+WTgxyJUpX2TA1GVfni9xT\npOMOtzuDQfDIxTNB6hFjSWATb1/E5ys1lfK09n+dRhmvC/Be+b5M4WlyX3cqy/za\ns0XxuZ+CHzLfHaPxFUem\n=VYpl\n-----END PGP SIGNATURE-----\n",
|
|
||||||
"payload": "tree 64ea463e084b6ab0def0322c6ad53799054ec9b3\nparent 5df26081d40e3000a7beedb73954d4df397c93fa\nauthor zhenxunflow[bot] <179375394+zhenxunflow[bot]@users.noreply.github.com> 1737882295 +0800\ncommitter GitHub <noreply@github.com> 1737882295 +0800\n\n:beers: publish plugin AI全家桶 (#235) (#236)\n\nCo-authored-by: molanp <molanp@users.noreply.github.com>",
|
|
||||||
"verified_at": "2025-01-26T09:04:58Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins_index/commits/2ed61284873c526802752b12a3fd3b5e1a59d948",
|
|
||||||
"html_url": "https://github.com/zhenxun-org/zhenxun_bot_plugins_index/commit/2ed61284873c526802752b12a3fd3b5e1a59d948",
|
|
||||||
"comments_url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins_index/commits/2ed61284873c526802752b12a3fd3b5e1a59d948/comments",
|
|
||||||
"author": {
|
|
||||||
"login": "zhenxunflow[bot]",
|
|
||||||
"id": 179375394,
|
|
||||||
"node_id": "BOT_kgDOCrENIg",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/in/978723?v=4",
|
|
||||||
"gravatar_id": "",
|
|
||||||
"url": "https://api.github.com/users/zhenxunflow%5Bbot%5D",
|
|
||||||
"html_url": "https://github.com/apps/zhenxunflow",
|
|
||||||
"followers_url": "https://api.github.com/users/zhenxunflow%5Bbot%5D/followers",
|
|
||||||
"following_url": "https://api.github.com/users/zhenxunflow%5Bbot%5D/following{/other_user}",
|
|
||||||
"gists_url": "https://api.github.com/users/zhenxunflow%5Bbot%5D/gists{/gist_id}",
|
|
||||||
"starred_url": "https://api.github.com/users/zhenxunflow%5Bbot%5D/starred{/owner}{/repo}",
|
|
||||||
"subscriptions_url": "https://api.github.com/users/zhenxunflow%5Bbot%5D/subscriptions",
|
|
||||||
"organizations_url": "https://api.github.com/users/zhenxunflow%5Bbot%5D/orgs",
|
|
||||||
"repos_url": "https://api.github.com/users/zhenxunflow%5Bbot%5D/repos",
|
|
||||||
"events_url": "https://api.github.com/users/zhenxunflow%5Bbot%5D/events{/privacy}",
|
|
||||||
"received_events_url": "https://api.github.com/users/zhenxunflow%5Bbot%5D/received_events",
|
|
||||||
"type": "Bot",
|
|
||||||
"user_view_type": "public",
|
|
||||||
"site_admin": false
|
|
||||||
},
|
|
||||||
"committer": {
|
|
||||||
"login": "web-flow",
|
|
||||||
"id": 19864447,
|
|
||||||
"node_id": "MDQ6VXNlcjE5ODY0NDQ3",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/19864447?v=4",
|
|
||||||
"gravatar_id": "",
|
|
||||||
"url": "https://api.github.com/users/web-flow",
|
|
||||||
"html_url": "https://github.com/web-flow",
|
|
||||||
"followers_url": "https://api.github.com/users/web-flow/followers",
|
|
||||||
"following_url": "https://api.github.com/users/web-flow/following{/other_user}",
|
|
||||||
"gists_url": "https://api.github.com/users/web-flow/gists{/gist_id}",
|
|
||||||
"starred_url": "https://api.github.com/users/web-flow/starred{/owner}{/repo}",
|
|
||||||
"subscriptions_url": "https://api.github.com/users/web-flow/subscriptions",
|
|
||||||
"organizations_url": "https://api.github.com/users/web-flow/orgs",
|
|
||||||
"repos_url": "https://api.github.com/users/web-flow/repos",
|
|
||||||
"events_url": "https://api.github.com/users/web-flow/events{/privacy}",
|
|
||||||
"received_events_url": "https://api.github.com/users/web-flow/received_events",
|
|
||||||
"type": "User",
|
|
||||||
"user_view_type": "public",
|
|
||||||
"site_admin": false
|
|
||||||
},
|
|
||||||
"parents": [
|
|
||||||
{
|
|
||||||
"sha": "5df26081d40e3000a7beedb73954d4df397c93fa",
|
|
||||||
"url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins_index/commits/5df26081d40e3000a7beedb73954d4df397c93fa",
|
|
||||||
"html_url": "https://github.com/zhenxun-org/zhenxun_bot_plugins_index/commit/5df26081d40e3000a7beedb73954d4df397c93fa"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stats": {
|
|
||||||
"total": 11,
|
|
||||||
"additions": 11,
|
|
||||||
"deletions": 0
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"sha": "3d98392c25d38f5d375b830aed6e2298e47e5601",
|
|
||||||
"filename": "plugins.json",
|
|
||||||
"status": "modified",
|
|
||||||
"additions": 11,
|
|
||||||
"deletions": 0,
|
|
||||||
"changes": 11,
|
|
||||||
"blob_url": "https://github.com/zhenxun-org/zhenxun_bot_plugins_index/blob/2ed61284873c526802752b12a3fd3b5e1a59d948/plugins.json",
|
|
||||||
"raw_url": "https://github.com/zhenxun-org/zhenxun_bot_plugins_index/raw/2ed61284873c526802752b12a3fd3b5e1a59d948/plugins.json",
|
|
||||||
"contents_url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins_index/contents/plugins.json?ref=2ed61284873c526802752b12a3fd3b5e1a59d948",
|
|
||||||
"patch": "@@ -53,5 +53,16 @@\n \"plugin_type\": \"NORMAL\",\n \"is_dir\": true,\n \"github_url\": \"https://github.com/PackageInstaller/zhenxun_plugin_draw_painting/tree/master\"\n+ },\n+ \"AI全家桶\": {\n+ \"module\": \"zhipu_toolkit\",\n+ \"module_path\": \"zhipu_toolkit\",\n+ \"description\": \"AI全家桶,一次安装,到处使用,省时省力省心\",\n+ \"usage\": \"AI全家桶,一次安装,到处使用,省时省力省心\\n usage:\\n 生成图片 <prompt>\\n 生成视频 <prompt>\\n 清理我的会话: 用于清理你与AI的聊天记录\\n 或者与机器人聊天,\\n 例如;\\n @Bot抱抱\\n 小真寻老婆\",\n+ \"author\": \"molanp\",\n+ \"version\": \"0.1\",\n+ \"plugin_type\": \"NORMAL\",\n+ \"is_dir\": true,\n+ \"github_url\": \"https://github.com/molanp/zhenxun_plugin_zhipu_toolkit\"\n }\n }"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "gh",
|
|
||||||
"name": "zhenxun-org/zhenxun_bot_plugins",
|
|
||||||
"version": "main",
|
|
||||||
"default": null,
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"type": "directory",
|
|
||||||
"name": "plugins",
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"type": "directory",
|
|
||||||
"name": "search_image",
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"type": "file",
|
|
||||||
"name": "__init__.py",
|
|
||||||
"hash": "a4Yp9HPoBzMwvnQDT495u0yYqTQWofkOyHxEi1FdVb0=",
|
|
||||||
"size": 3010
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "directory",
|
|
||||||
"name": "alapi",
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"type": "file",
|
|
||||||
"name": "__init__.py",
|
|
||||||
"hash": "ndDxtO0pAq3ZTb4RdqW7FTDgOGC/RjS1dnwdaQfT0uQ=",
|
|
||||||
"size": 284
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "file",
|
|
||||||
"name": "_data_source.py",
|
|
||||||
"hash": "KOLqtj4TQWWQco5bA4tWFc7A0z1ruMyDk1RiKeqJHRA=",
|
|
||||||
"size": 919
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "file",
|
|
||||||
"name": "comments_163.py",
|
|
||||||
"hash": "Q5pZsj1Pj+EJMdKYcPtLqejcXAWUQIoXVQG49PZPaSI=",
|
|
||||||
"size": 1593
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "file",
|
|
||||||
"name": "cover.py",
|
|
||||||
"hash": "QSjtcy0oVrjaRiAWZKmUJlp0L4DQqEcdYNmExNo9mgc=",
|
|
||||||
"size": 1438
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "file",
|
|
||||||
"name": "jitang.py",
|
|
||||||
"hash": "xh43Osxt0xogTH448gUMC+/DaSGmCFme8DWUqC25IbU=",
|
|
||||||
"size": 1411
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "file",
|
|
||||||
"name": "poetry.py",
|
|
||||||
"hash": "Aj2unoNQboj3/0LhIrYU+dCa5jvMdpjMYXYUayhjuz4=",
|
|
||||||
"size": 1530
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "directory",
|
|
||||||
"name": "bilibili_sub",
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"type": "file",
|
|
||||||
"name": "__init__.py",
|
|
||||||
"hash": "407DCgNFcZnuEK+d716j8EWrFQc4Nlxa35V3yemy3WQ=",
|
|
||||||
"size": 14293
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": {
|
|
||||||
"stats": "https://data.jsdelivr.com/v1/stats/packages/gh/zhenxun-org/zhenxun_bot_plugins@main"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
{
|
|
||||||
"sha": "f524632f78d27f9893beebdf709e0e7885cd08f1",
|
|
||||||
"node_id": "C_kwDOJAjBPdoAKGY1MjQ2MzJmNzhkMjdmOTg5M2JlZWJkZjcwOWUwZTc4ODVjZDA4ZjE",
|
|
||||||
"commit": {
|
|
||||||
"author": {
|
|
||||||
"name": "xuaner",
|
|
||||||
"email": "xuaner_wa@qq.com",
|
|
||||||
"date": "2024-11-18T18:17:15Z"
|
|
||||||
},
|
|
||||||
"committer": {
|
|
||||||
"name": "xuaner",
|
|
||||||
"email": "xuaner_wa@qq.com",
|
|
||||||
"date": "2024-11-18T18:17:15Z"
|
|
||||||
},
|
|
||||||
"message": "fix bug",
|
|
||||||
"tree": {
|
|
||||||
"sha": "b6b1b4f06cc869b9f38d7b51bdca3a2c575255e4",
|
|
||||||
"url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/trees/b6b1b4f06cc869b9f38d7b51bdca3a2c575255e4"
|
|
||||||
},
|
|
||||||
"url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/commits/f524632f78d27f9893beebdf709e0e7885cd08f1",
|
|
||||||
"comment_count": 0,
|
|
||||||
"verification": {
|
|
||||||
"verified": false,
|
|
||||||
"reason": "unsigned",
|
|
||||||
"signature": null,
|
|
||||||
"payload": null,
|
|
||||||
"verified_at": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/commits/f524632f78d27f9893beebdf709e0e7885cd08f1",
|
|
||||||
"html_url": "https://github.com/xuanerwa/zhenxun_github_sub/commit/f524632f78d27f9893beebdf709e0e7885cd08f1",
|
|
||||||
"comments_url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/commits/f524632f78d27f9893beebdf709e0e7885cd08f1/comments",
|
|
||||||
"author": {
|
|
||||||
"login": "xuanerwa",
|
|
||||||
"id": 58063798,
|
|
||||||
"node_id": "MDQ6VXNlcjU4MDYzNzk4",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/58063798?v=4",
|
|
||||||
"gravatar_id": "",
|
|
||||||
"url": "https://api.github.com/users/xuanerwa",
|
|
||||||
"html_url": "https://github.com/xuanerwa",
|
|
||||||
"followers_url": "https://api.github.com/users/xuanerwa/followers",
|
|
||||||
"following_url": "https://api.github.com/users/xuanerwa/following{/other_user}",
|
|
||||||
"gists_url": "https://api.github.com/users/xuanerwa/gists{/gist_id}",
|
|
||||||
"starred_url": "https://api.github.com/users/xuanerwa/starred{/owner}{/repo}",
|
|
||||||
"subscriptions_url": "https://api.github.com/users/xuanerwa/subscriptions",
|
|
||||||
"organizations_url": "https://api.github.com/users/xuanerwa/orgs",
|
|
||||||
"repos_url": "https://api.github.com/users/xuanerwa/repos",
|
|
||||||
"events_url": "https://api.github.com/users/xuanerwa/events{/privacy}",
|
|
||||||
"received_events_url": "https://api.github.com/users/xuanerwa/received_events",
|
|
||||||
"type": "User",
|
|
||||||
"user_view_type": "public",
|
|
||||||
"site_admin": false
|
|
||||||
},
|
|
||||||
"committer": {
|
|
||||||
"login": "xuanerwa",
|
|
||||||
"id": 58063798,
|
|
||||||
"node_id": "MDQ6VXNlcjU4MDYzNzk4",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/58063798?v=4",
|
|
||||||
"gravatar_id": "",
|
|
||||||
"url": "https://api.github.com/users/xuanerwa",
|
|
||||||
"html_url": "https://github.com/xuanerwa",
|
|
||||||
"followers_url": "https://api.github.com/users/xuanerwa/followers",
|
|
||||||
"following_url": "https://api.github.com/users/xuanerwa/following{/other_user}",
|
|
||||||
"gists_url": "https://api.github.com/users/xuanerwa/gists{/gist_id}",
|
|
||||||
"starred_url": "https://api.github.com/users/xuanerwa/starred{/owner}{/repo}",
|
|
||||||
"subscriptions_url": "https://api.github.com/users/xuanerwa/subscriptions",
|
|
||||||
"organizations_url": "https://api.github.com/users/xuanerwa/orgs",
|
|
||||||
"repos_url": "https://api.github.com/users/xuanerwa/repos",
|
|
||||||
"events_url": "https://api.github.com/users/xuanerwa/events{/privacy}",
|
|
||||||
"received_events_url": "https://api.github.com/users/xuanerwa/received_events",
|
|
||||||
"type": "User",
|
|
||||||
"user_view_type": "public",
|
|
||||||
"site_admin": false
|
|
||||||
},
|
|
||||||
"parents": [
|
|
||||||
{
|
|
||||||
"sha": "91e5e2c792e79193830441d555769aa54acd2d15",
|
|
||||||
"url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/commits/91e5e2c792e79193830441d555769aa54acd2d15",
|
|
||||||
"html_url": "https://github.com/xuanerwa/zhenxun_github_sub/commit/91e5e2c792e79193830441d555769aa54acd2d15"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stats": {
|
|
||||||
"total": 2,
|
|
||||||
"additions": 1,
|
|
||||||
"deletions": 1
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"sha": "764a5f7b81554c4c10d29486ea5d9105e505cec3",
|
|
||||||
"filename": "github_sub/__init__.py",
|
|
||||||
"status": "modified",
|
|
||||||
"additions": 1,
|
|
||||||
"deletions": 1,
|
|
||||||
"changes": 2,
|
|
||||||
"blob_url": "https://github.com/xuanerwa/zhenxun_github_sub/blob/f524632f78d27f9893beebdf709e0e7885cd08f1/github_sub%2F__init__.py",
|
|
||||||
"raw_url": "https://github.com/xuanerwa/zhenxun_github_sub/raw/f524632f78d27f9893beebdf709e0e7885cd08f1/github_sub%2F__init__.py",
|
|
||||||
"contents_url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/contents/github_sub%2F__init__.py?ref=f524632f78d27f9893beebdf709e0e7885cd08f1",
|
|
||||||
"patch": "@@ -168,7 +168,7 @@ async def _(session: EventSession):\n # 推送\n @scheduler.scheduled_job(\n \"interval\",\n- seconds=base_config.get(\"CHECK_API_TIME\") if base_config.get(\"CHECK_TIME\") else 30,\n+ seconds=base_config.get(\"CHECK_API_TIME\") if base_config.get(\"CHECK_API_TIME\") else 30,\n )\n async def _():\n bots = nonebot.get_bots()"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "gh",
|
|
||||||
"name": "xuanerwa/zhenxun_github_sub",
|
|
||||||
"version": "main",
|
|
||||||
"default": null,
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"type": "directory",
|
|
||||||
"name": "github_sub",
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"type": "file",
|
|
||||||
"name": "__init__.py",
|
|
||||||
"hash": "z1C5BBK0+atbDghbyRlF2xIDwk0HQdHM1yXQZkF7/t8=",
|
|
||||||
"size": 7551
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": {
|
|
||||||
"stats": "https://data.jsdelivr.com/v1/stats/packages/gh/xuanerwa/zhenxun_github_sub@main"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"sha": "438298b9e88f9dafa7020e99d7c7b4c98f93aea6",
|
|
||||||
"url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/trees/438298b9e88f9dafa7020e99d7c7b4c98f93aea6",
|
|
||||||
"tree": [
|
|
||||||
{
|
|
||||||
"path": "LICENSE",
|
|
||||||
"mode": "100644",
|
|
||||||
"type": "blob",
|
|
||||||
"sha": "f288702d2fa16d3cdf0035b15a9fcbc552cd88e7",
|
|
||||||
"size": 35149,
|
|
||||||
"url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/blobs/f288702d2fa16d3cdf0035b15a9fcbc552cd88e7"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "README.md",
|
|
||||||
"mode": "100644",
|
|
||||||
"type": "blob",
|
|
||||||
"sha": "e974cfc9b973d4a041f03e693ea20563a933b7ca",
|
|
||||||
"size": 955,
|
|
||||||
"url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/blobs/e974cfc9b973d4a041f03e693ea20563a933b7ca"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "github_sub",
|
|
||||||
"mode": "040000",
|
|
||||||
"type": "tree",
|
|
||||||
"sha": "0f7d76bcf472e2ab0610fa542b067633d6e3ae7e",
|
|
||||||
"url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/trees/0f7d76bcf472e2ab0610fa542b067633d6e3ae7e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "github_sub/__init__.py",
|
|
||||||
"mode": "100644",
|
|
||||||
"type": "blob",
|
|
||||||
"sha": "7d17fd49fe82fa3897afcef61b2c694ed93a4ba3",
|
|
||||||
"size": 7551,
|
|
||||||
"url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/blobs/7d17fd49fe82fa3897afcef61b2c694ed93a4ba3"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"truncated": false
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,10 @@ from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message, MessageSegme
|
|||||||
from nonebot.adapters.onebot.v11.event import Sender
|
from nonebot.adapters.onebot.v11.event import Sender
|
||||||
|
|
||||||
|
|
||||||
|
def get_reply_cq(uid: int | str) -> str:
|
||||||
|
return f"[CQ:reply,id={uid}]"
|
||||||
|
|
||||||
|
|
||||||
def get_response_json(base_path: Path, file: str) -> dict:
|
def get_response_json(base_path: Path, file: str) -> dict:
|
||||||
try:
|
try:
|
||||||
return json.loads(
|
return json.loads(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import nonebot
|
|||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
from nonebot.drivers import Driver
|
from nonebot.drivers import Driver
|
||||||
from tortoise import Tortoise
|
from tortoise import Tortoise
|
||||||
from tortoise.exceptions import OperationalError
|
from tortoise.exceptions import IntegrityError, OperationalError
|
||||||
import ujson as json
|
import ujson as json
|
||||||
|
|
||||||
from zhenxun.models.bot_connect_log import BotConnectLog
|
from zhenxun.models.bot_connect_log import BotConnectLog
|
||||||
@@ -16,7 +16,8 @@ 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.resource_manager import ResourceManager
|
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
||||||
|
from zhenxun.utils.manager.zhenxun_repo_manager import ZhenxunRepoManager
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
from zhenxun.utils.platform import PlatformUtils
|
||||||
|
|
||||||
driver: Driver = nonebot.get_driver()
|
driver: Driver = nonebot.get_driver()
|
||||||
@@ -29,9 +30,12 @@ async def _(bot: Bot):
|
|||||||
bot_id=bot.self_id, platform=bot.adapter, connect_time=datetime.now(), type=1
|
bot_id=bot.self_id, platform=bot.adapter, connect_time=datetime.now(), type=1
|
||||||
)
|
)
|
||||||
if not await BotConsole.exists(bot_id=bot.self_id):
|
if not await BotConsole.exists(bot_id=bot.self_id):
|
||||||
|
try:
|
||||||
await BotConsole.create(
|
await BotConsole.create(
|
||||||
bot_id=bot.self_id, platform=PlatformUtils.get_platform(bot)
|
bot_id=bot.self_id, platform=PlatformUtils.get_platform(bot)
|
||||||
)
|
)
|
||||||
|
except IntegrityError as e:
|
||||||
|
logger.warning(f"记录bot: {bot.self_id} 数据已存在...", e=e)
|
||||||
|
|
||||||
|
|
||||||
@driver.on_bot_disconnect
|
@driver.on_bot_disconnect
|
||||||
@@ -49,30 +53,40 @@ async def _(bot: Bot):
|
|||||||
|
|
||||||
|
|
||||||
SIGN_SQL = """
|
SIGN_SQL = """
|
||||||
select distinct on("user_id") t1.user_id, t1.checkin_count, t1.add_probability,
|
SELECT user_id, checkin_count, add_probability, specify_probability, impression
|
||||||
t1.specify_probability, t1.impression
|
FROM (
|
||||||
from public.sign_group_users t1
|
SELECT
|
||||||
join (
|
t1.user_id,
|
||||||
select user_id, max(t2.impression) as max_impression
|
t1.checkin_count,
|
||||||
from public.sign_group_users t2
|
t1.add_probability,
|
||||||
group by user_id
|
t1.specify_probability,
|
||||||
) t on t.user_id = t1.user_id and t.max_impression = t1.impression
|
t1.impression,
|
||||||
|
ROW_NUMBER() OVER(PARTITION BY t1.user_id ORDER BY t1.impression DESC) AS rn
|
||||||
|
FROM sign_group_users t1
|
||||||
|
INNER JOIN (
|
||||||
|
SELECT user_id, MAX(impression) AS max_impression
|
||||||
|
FROM sign_group_users
|
||||||
|
GROUP BY user_id
|
||||||
|
) t2 ON t2.user_id = t1.user_id AND t2.max_impression = t1.impression
|
||||||
|
) t
|
||||||
|
WHERE rn = 1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BAG_SQL = """
|
BAG_SQL = """
|
||||||
select t1.user_id, t1.gold, t1.property
|
select t1.user_id, t1.gold, t1.property
|
||||||
from public.bag_users t1
|
from bag_users t1
|
||||||
join (
|
join (
|
||||||
select user_id, max(t2.gold) as max_gold
|
select user_id, max(t2.gold) as max_gold
|
||||||
from public.bag_users t2
|
from bag_users t2
|
||||||
group by user_id
|
group by user_id
|
||||||
) t on t.user_id = t1.user_id and t.max_gold = t1.gold
|
) t on t.user_id = t1.user_id and t.max_gold = t1.gold
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@driver.on_startup
|
@PriorityLifecycle.on_startup(priority=5)
|
||||||
async def _():
|
async def _():
|
||||||
await ResourceManager.init_resources()
|
if not ZhenxunRepoManager.check_resources_exists():
|
||||||
|
await ZhenxunRepoManager.resources_update()
|
||||||
"""签到与用户的数据迁移"""
|
"""签到与用户的数据迁移"""
|
||||||
if goods_list := await GoodsInfo.filter(uuid__isnull=True).all():
|
if goods_list := await GoodsInfo.filter(uuid__isnull=True).all():
|
||||||
for goods in goods_list:
|
for goods in goods_list:
|
||||||
|
|||||||
@@ -26,6 +26,21 @@ __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__"
|
||||||
@@ -35,25 +50,11 @@ 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):
|
||||||
info: list[str | Path] = [
|
result: list[str | Path] = [QQ_INFO.format(version=version)]
|
||||||
f"""
|
|
||||||
『绪山真寻Bot』
|
|
||||||
版本:{version}
|
|
||||||
简介:基于Nonebot2开发,支持多平台,是一个非常可爱的Bot呀,希望与大家要好好相处
|
|
||||||
""".strip()
|
|
||||||
]
|
|
||||||
path = DATA_PATH / "about.png"
|
path = DATA_PATH / "about.png"
|
||||||
if path.exists():
|
if path.exists():
|
||||||
info.append(path)
|
result.append(path)
|
||||||
|
await MessageUtils.build_message(result).send() # type: ignore
|
||||||
else:
|
else:
|
||||||
info = [
|
await MessageUtils.build_message(INFO.format(version=version)).send()
|
||||||
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)
|
logger.info("查看关于", arparma.header_result, session=session)
|
||||||
|
|||||||
@@ -2,18 +2,14 @@ from nonebot.plugin import PluginMetadata
|
|||||||
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
|
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_session import EventSession
|
||||||
|
|
||||||
from zhenxun.configs.config import Config
|
from zhenxun.configs.utils import PluginExtraData
|
||||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
from zhenxun.services.help_service import create_plugin_help_image
|
||||||
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.exception import EmptyError
|
from zhenxun.utils.exception import EmptyError
|
||||||
from zhenxun.utils.message import MessageUtils
|
from zhenxun.utils.message import MessageUtils
|
||||||
from zhenxun.utils.rules import admin_check, ensure_group
|
from zhenxun.utils.rules import admin_check, ensure_group
|
||||||
|
|
||||||
from .config import ADMIN_HELP_IMAGE
|
|
||||||
from .html_help import build_html_help
|
|
||||||
from .normal_help import build_help
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="群组管理员帮助",
|
name="群组管理员帮助",
|
||||||
description="管理员帮助列表",
|
description="管理员帮助列表",
|
||||||
@@ -25,17 +21,24 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
version="0.1",
|
version="0.1",
|
||||||
plugin_type=PluginType.ADMIN,
|
plugin_type=PluginType.ADMIN,
|
||||||
admin_level=1,
|
admin_level=1,
|
||||||
configs=[
|
introduction="""这是 群主/群管理 的帮助列表,里面记录了群组内开关功能的
|
||||||
RegisterConfig(
|
方法帮助以及群管特权方法,建议首次时在群组中发送 '管理员帮助' 查看""",
|
||||||
key="type",
|
precautions=[
|
||||||
value="zhenxun",
|
"只有群主/群管理 才能使用哦,群主拥有6级权限,管理员拥有5级权限!"
|
||||||
help="管理员帮助样式,normal, zhenxun",
|
|
||||||
default_value="zhenxun",
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
|
configs=[],
|
||||||
).to_dict(),
|
).to_dict(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def build_html_help() -> bytes:
|
||||||
|
"""构建管理员帮助图片"""
|
||||||
|
return await create_plugin_help_image(
|
||||||
|
plugin_types=[PluginType.ADMIN, PluginType.SUPER_AND_ADMIN],
|
||||||
|
page_title="群管理员帮助手册",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
_matcher = on_alconna(
|
_matcher = on_alconna(
|
||||||
Alconna("管理员帮助"),
|
Alconna("管理员帮助"),
|
||||||
rule=admin_check(1) & ensure_group,
|
rule=admin_check(1) & ensure_group,
|
||||||
@@ -49,15 +52,9 @@ async def _(
|
|||||||
session: EventSession,
|
session: EventSession,
|
||||||
arparma: Arparma,
|
arparma: Arparma,
|
||||||
):
|
):
|
||||||
if not ADMIN_HELP_IMAGE.exists():
|
|
||||||
try:
|
try:
|
||||||
if Config.get_config("admin_help", "type") == "zhenxun":
|
image_bytes = await build_html_help()
|
||||||
await build_html_help()
|
await MessageUtils.build_message(image_bytes).send()
|
||||||
else:
|
|
||||||
await build_help()
|
|
||||||
except EmptyError:
|
except EmptyError:
|
||||||
await MessageUtils.build_message("当前管理员帮助为空...").finish(
|
await MessageUtils.build_message("当前管理员帮助为空...").finish(reply_to=True)
|
||||||
reply_to=True
|
|
||||||
)
|
|
||||||
await MessageUtils.build_message(ADMIN_HELP_IMAGE).send()
|
|
||||||
logger.info("查看管理员帮助", arparma.header_result, session=session)
|
logger.info("查看管理员帮助", arparma.header_result, session=session)
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
from zhenxun.configs.path_config import IMAGE_PATH
|
|
||||||
from zhenxun.models.plugin_info import PluginInfo
|
|
||||||
|
|
||||||
ADMIN_HELP_IMAGE = IMAGE_PATH / "ADMIN_HELP.png"
|
|
||||||
if ADMIN_HELP_IMAGE.exists():
|
|
||||||
ADMIN_HELP_IMAGE.unlink()
|
|
||||||
|
|
||||||
|
|
||||||
class PluginData(BaseModel):
|
|
||||||
"""
|
|
||||||
插件信息
|
|
||||||
"""
|
|
||||||
|
|
||||||
plugin: PluginInfo
|
|
||||||
"""插件信息"""
|
|
||||||
metadata: PluginMetadata
|
|
||||||
"""元数据"""
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
arbitrary_types_allowed = True
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
from nonebot_plugin_htmlrender import template_to_pic
|
|
||||||
|
|
||||||
from zhenxun.builtin_plugins.admin.admin_help.config import ADMIN_HELP_IMAGE
|
|
||||||
from zhenxun.configs.config import BotConfig
|
|
||||||
from zhenxun.configs.path_config import TEMPLATE_PATH
|
|
||||||
from zhenxun.models.task_info import TaskInfo
|
|
||||||
from zhenxun.utils._build_image import BuildImage
|
|
||||||
|
|
||||||
from .utils import get_plugins
|
|
||||||
|
|
||||||
|
|
||||||
async def get_task() -> dict[str, str] | None:
|
|
||||||
"""获取被动技能帮助"""
|
|
||||||
if task_list := await TaskInfo.all():
|
|
||||||
return {
|
|
||||||
"name": "被动技能",
|
|
||||||
"description": "控制群组中的被动技能状态",
|
|
||||||
"usage": "通过 开启/关闭群被动 来控制群被动 <br>"
|
|
||||||
+ " 示例:开启/关闭群被动早晚安 <br> ---------- <br> "
|
|
||||||
+ "<br>".join([task.name for task in task_list]),
|
|
||||||
}
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
async def build_html_help():
|
|
||||||
"""构建帮助图片"""
|
|
||||||
plugins = await get_plugins()
|
|
||||||
plugin_list = [
|
|
||||||
{
|
|
||||||
"name": data.plugin.name,
|
|
||||||
"description": data.metadata.description.replace("\n", "<br>"),
|
|
||||||
"usage": data.metadata.usage.replace("\n", "<br>"),
|
|
||||||
}
|
|
||||||
for data in plugins
|
|
||||||
]
|
|
||||||
if task := await get_task():
|
|
||||||
plugin_list.append(task)
|
|
||||||
plugin_list.sort(key=lambda p: len(p["description"]) + len(p["usage"]))
|
|
||||||
pic = await template_to_pic(
|
|
||||||
template_path=str((TEMPLATE_PATH / "help").absolute()),
|
|
||||||
template_name="main.html",
|
|
||||||
templates={
|
|
||||||
"data": {
|
|
||||||
"plugin_list": plugin_list,
|
|
||||||
"nickname": BotConfig.self_nickname,
|
|
||||||
"help_name": "群管理员",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pages={
|
|
||||||
"viewport": {"width": 1024, "height": 1024},
|
|
||||||
"base_url": f"file://{TEMPLATE_PATH}",
|
|
||||||
},
|
|
||||||
wait=2,
|
|
||||||
)
|
|
||||||
result = await BuildImage.open(pic).resize(0.5)
|
|
||||||
await result.save(ADMIN_HELP_IMAGE)
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
from PIL.ImageFont import FreeTypeFont
|
|
||||||
|
|
||||||
from zhenxun.models.plugin_info import PluginInfo
|
|
||||||
from zhenxun.models.task_info import TaskInfo
|
|
||||||
from zhenxun.services.log import logger
|
|
||||||
from zhenxun.utils._build_image import BuildImage
|
|
||||||
from zhenxun.utils.image_utils import build_sort_image, group_image, text2image
|
|
||||||
|
|
||||||
from .config import ADMIN_HELP_IMAGE
|
|
||||||
from .utils import get_plugins
|
|
||||||
|
|
||||||
|
|
||||||
async def build_usage_des_image(
|
|
||||||
metadata: PluginMetadata,
|
|
||||||
) -> tuple[BuildImage | None, BuildImage | None]:
|
|
||||||
"""构建用法和描述图片
|
|
||||||
|
|
||||||
参数:
|
|
||||||
metadata: PluginMetadata
|
|
||||||
|
|
||||||
返回:
|
|
||||||
tuple[BuildImage | None, BuildImage | None]: 用法和描述图片
|
|
||||||
"""
|
|
||||||
usage = None
|
|
||||||
description = None
|
|
||||||
if metadata.usage:
|
|
||||||
usage = await text2image(
|
|
||||||
metadata.usage,
|
|
||||||
padding=5,
|
|
||||||
color=(255, 255, 255),
|
|
||||||
font_color=(0, 0, 0),
|
|
||||||
)
|
|
||||||
if metadata.description:
|
|
||||||
description = await text2image(
|
|
||||||
metadata.description,
|
|
||||||
padding=5,
|
|
||||||
color=(255, 255, 255),
|
|
||||||
font_color=(0, 0, 0),
|
|
||||||
)
|
|
||||||
return usage, description
|
|
||||||
|
|
||||||
|
|
||||||
async def build_image(
|
|
||||||
plugin: PluginInfo, metadata: PluginMetadata, font: FreeTypeFont
|
|
||||||
) -> BuildImage:
|
|
||||||
"""构建帮助图片
|
|
||||||
|
|
||||||
参数:
|
|
||||||
plugin: PluginInfo
|
|
||||||
metadata: PluginMetadata
|
|
||||||
font: FreeTypeFont
|
|
||||||
|
|
||||||
返回:
|
|
||||||
BuildImage: 帮助图片
|
|
||||||
|
|
||||||
"""
|
|
||||||
usage, description = await build_usage_des_image(metadata)
|
|
||||||
width = 0
|
|
||||||
height = 100
|
|
||||||
if usage:
|
|
||||||
width = usage.width
|
|
||||||
height += usage.height
|
|
||||||
if description and description.width > width:
|
|
||||||
width = description.width
|
|
||||||
height += description.height
|
|
||||||
font_width, _ = BuildImage.get_text_size(f"{plugin.name}[{plugin.level}]", font)
|
|
||||||
if font_width > width:
|
|
||||||
width = font_width
|
|
||||||
A = BuildImage(width + 30, height + 120, "#EAEDF2")
|
|
||||||
await A.text((15, 10), f"{plugin.name}[{plugin.level}]")
|
|
||||||
await A.text((15, 70), "简介:")
|
|
||||||
if not description:
|
|
||||||
description = BuildImage(A.width - 30, 30, (255, 255, 255))
|
|
||||||
await description.circle_corner(10)
|
|
||||||
await A.paste(description, (15, 100))
|
|
||||||
if not usage:
|
|
||||||
usage = BuildImage(A.width - 30, 30, (255, 255, 255))
|
|
||||||
await usage.circle_corner(10)
|
|
||||||
await A.text((15, description.height + 115), "用法:")
|
|
||||||
await A.paste(usage, (15, description.height + 145))
|
|
||||||
await A.circle_corner(10)
|
|
||||||
return A
|
|
||||||
|
|
||||||
|
|
||||||
async def build_help():
|
|
||||||
"""构造管理员帮助图片
|
|
||||||
|
|
||||||
返回:
|
|
||||||
BuildImage: 管理员帮助图片
|
|
||||||
"""
|
|
||||||
font = BuildImage.load_font("HYWenHei-85W.ttf", 20)
|
|
||||||
image_list = []
|
|
||||||
for data in await get_plugins():
|
|
||||||
plugin = data.plugin
|
|
||||||
metadata = data.metadata
|
|
||||||
try:
|
|
||||||
A = await build_image(plugin, metadata, font)
|
|
||||||
image_list.append(A)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(
|
|
||||||
f"获取群管理员插件 {plugin.module}: {plugin.name} 设置失败...",
|
|
||||||
"管理员帮助",
|
|
||||||
e=e,
|
|
||||||
)
|
|
||||||
if task_list := await TaskInfo.all():
|
|
||||||
task_str = "\n".join([task.name for task in task_list])
|
|
||||||
task_str = "通过 开启/关闭群被动 来控制群被动\n----------\n" + task_str
|
|
||||||
task_image = await text2image(task_str, padding=5, color=(255, 255, 255))
|
|
||||||
await task_image.circle_corner(10)
|
|
||||||
A = BuildImage(task_image.width + 50, task_image.height + 85, "#EAEDF2")
|
|
||||||
await A.text((25, 10), "被动技能")
|
|
||||||
await A.paste(task_image, (25, 50))
|
|
||||||
await A.circle_corner(10)
|
|
||||||
image_list.append(A)
|
|
||||||
image_group, _ = group_image(image_list)
|
|
||||||
A = await build_sort_image(image_group, color=(255, 255, 255), padding_top=160)
|
|
||||||
text = await BuildImage.build_text_image(
|
|
||||||
"群管理员帮助",
|
|
||||||
size=40,
|
|
||||||
)
|
|
||||||
tip = await BuildImage.build_text_image(
|
|
||||||
"注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数", size=25, font_color="red"
|
|
||||||
)
|
|
||||||
await A.paste(text, (50, 30))
|
|
||||||
await A.paste(tip, (50, 90))
|
|
||||||
await A.save(ADMIN_HELP_IMAGE)
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import nonebot
|
|
||||||
|
|
||||||
from zhenxun.models.plugin_info import PluginInfo
|
|
||||||
from zhenxun.utils.enum import PluginType
|
|
||||||
from zhenxun.utils.exception import EmptyError
|
|
||||||
|
|
||||||
from .config import PluginData
|
|
||||||
|
|
||||||
|
|
||||||
async def get_plugins() -> list[PluginData]:
|
|
||||||
"""获取插件数据"""
|
|
||||||
plugin_list = await PluginInfo.filter(
|
|
||||||
plugin_type__in=[PluginType.ADMIN, PluginType.SUPER_AND_ADMIN]
|
|
||||||
).all()
|
|
||||||
data_list = []
|
|
||||||
for plugin in plugin_list:
|
|
||||||
if _plugin := nonebot.get_plugin_by_module_name(plugin.module_path):
|
|
||||||
if _plugin.metadata:
|
|
||||||
data_list.append(PluginData(plugin=plugin, metadata=_plugin.metadata))
|
|
||||||
if not data_list:
|
|
||||||
raise EmptyError()
|
|
||||||
return data_list
|
|
||||||
@@ -14,13 +14,19 @@ 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 PluginExtraData, RegisterConfig
|
from zhenxun.configs.utils import (
|
||||||
|
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
|
from ._data_source import BanManage, call_ban
|
||||||
|
|
||||||
base_config = Config.get("ban")
|
base_config = Config.get("ban")
|
||||||
|
|
||||||
@@ -30,11 +36,12 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
usage="""
|
usage="""
|
||||||
普通管理员
|
普通管理员
|
||||||
格式:
|
格式:
|
||||||
ban [At用户] ?[-t [时长(分钟)]]
|
ban [At用户] ?[-t [时长(分钟)]] ?[-r [理由]]
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
ban @用户 : 永久拉黑用户
|
ban @用户 : 永久拉黑用户
|
||||||
ban @用户 -t 100 : 拉黑用户100分钟
|
ban @用户 -t 100 : 拉黑用户100分钟
|
||||||
|
ban @用户 -t 10 -r 坏 : 拉黑用户10分钟并携带理由
|
||||||
unban @用户 : 从小黑屋中拉出来
|
unban @用户 : 从小黑屋中拉出来
|
||||||
""".strip(),
|
""".strip(),
|
||||||
extra=PluginExtraData(
|
extra=PluginExtraData(
|
||||||
@@ -44,7 +51,7 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
superuser_help="""
|
superuser_help="""
|
||||||
超级管理员额外命令
|
超级管理员额外命令
|
||||||
格式:
|
格式:
|
||||||
ban [At用户/用户Id] ?[-t [时长]]
|
ban [At用户/用户Id] ?[-t [时长]] ?[-r [理由]]
|
||||||
unban --id [idx] : 通过id来进行unban操作
|
unban --id [idx] : 通过id来进行unban操作
|
||||||
ban列表: 获取所有Ban数据
|
ban列表: 获取所有Ban数据
|
||||||
|
|
||||||
@@ -60,10 +67,13 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
私聊下:
|
私聊下:
|
||||||
示例:
|
示例:
|
||||||
ban 123456789 : 永久拉黑用户123456789
|
ban 123456789 : 永久拉黑用户123456789
|
||||||
|
ban 123456789 -r 坏 : 永久拉黑用户123456789并携带理由
|
||||||
ban 123456789 -t 100 : 拉黑用户123456789 100分钟
|
ban 123456789 -t 100 : 拉黑用户123456789 100分钟
|
||||||
|
|
||||||
ban -g 999999 : 拉黑群组为999999的群组
|
ban -g 999999 : 拉黑群组为999999的群组
|
||||||
ban -g 999999 -t 100 : 拉黑群组为999999的群组 100分钟
|
ban -g 999999 -t 100 : 拉黑群组为999999的群组 100分钟
|
||||||
|
ban -g 999999 -r 坏 : 永久拉黑群组为999999的群组并携带理由
|
||||||
|
|
||||||
|
|
||||||
unban 123456789 : 从小黑屋中拉出来
|
unban 123456789 : 从小黑屋中拉出来
|
||||||
unban -g 999999 : 将群组9999999从小黑屋中拉出来
|
unban -g 999999 : 将群组9999999从小黑屋中拉出来
|
||||||
@@ -78,6 +88,29 @@ __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"
|
||||||
|
),
|
||||||
|
"reason": AICallableProperties(
|
||||||
|
type="string", description="封禁理由"
|
||||||
|
),
|
||||||
|
"duration": AICallableProperties(
|
||||||
|
type="integer",
|
||||||
|
description="封禁时长(选择的值只能是1-360),单位为分钟,如果频繁触发,按情况增加",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
required=["user_id"],
|
||||||
|
),
|
||||||
|
func=call_ban,
|
||||||
|
)
|
||||||
|
],
|
||||||
).to_dict(),
|
).to_dict(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,6 +119,7 @@ _ban_matcher = on_alconna(
|
|||||||
Alconna(
|
Alconna(
|
||||||
"ban",
|
"ban",
|
||||||
Args["user?", [str, At]],
|
Args["user?", [str, At]],
|
||||||
|
Option("-r|--reason", Args["reason", str]),
|
||||||
Option("-g|--group", Args["group_id", str]),
|
Option("-g|--group", Args["group_id", str]),
|
||||||
Option("-t|--time", Args["duration", int]),
|
Option("-t|--time", Args["duration", int]),
|
||||||
),
|
),
|
||||||
@@ -159,6 +193,7 @@ async def _(
|
|||||||
session: EventSession,
|
session: EventSession,
|
||||||
arparma: Arparma,
|
arparma: Arparma,
|
||||||
user: Match[str | At],
|
user: Match[str | At],
|
||||||
|
reason: Match[str],
|
||||||
duration: Match[int],
|
duration: Match[int],
|
||||||
group_id: Match[str],
|
group_id: Match[str],
|
||||||
):
|
):
|
||||||
@@ -174,13 +209,14 @@ async def _(
|
|||||||
user_id = user.result
|
user_id = user.result
|
||||||
_duration = duration.result * 60 if duration.available else -1
|
_duration = duration.result * 60 if duration.available else -1
|
||||||
_duration_text = f"{duration.result} 分钟" if duration.available else " 到世界湮灭"
|
_duration_text = f"{duration.result} 分钟" if duration.available else " 到世界湮灭"
|
||||||
|
ban_reason = reason.result if reason.available else None
|
||||||
if (gid := session.id3 or session.id2) and not group_id.available:
|
if (gid := session.id3 or session.id2) and not group_id.available:
|
||||||
if not user_id or (
|
if not user_id or (
|
||||||
user_id == bot.self_id and session.id1 not in bot.config.superusers
|
user_id == bot.self_id and session.id1 not in bot.config.superusers
|
||||||
):
|
):
|
||||||
_duration = 0.5
|
_duration = 0.5
|
||||||
await MessageUtils.build_message("倒反天罡,小小管理速速退下!").send()
|
await MessageUtils.build_message("倒反天罡,小小管理速速退下!").send()
|
||||||
await BanManage.ban(session.id1, gid, 30, session, True)
|
await BanManage.ban(session.id1, gid, ban_reason, 30, session, True)
|
||||||
_duration_text = "半 分钟"
|
_duration_text = "半 分钟"
|
||||||
logger.info(
|
logger.info(
|
||||||
f"尝试ban {BotConfig.self_nickname} 反被拿下",
|
f"尝试ban {BotConfig.self_nickname} 反被拿下",
|
||||||
@@ -196,7 +232,12 @@ async def _(
|
|||||||
]
|
]
|
||||||
).finish(reply_to=True)
|
).finish(reply_to=True)
|
||||||
await BanManage.ban(
|
await BanManage.ban(
|
||||||
user_id, gid, _duration, session, session.id1 in bot.config.superusers
|
user_id,
|
||||||
|
gid,
|
||||||
|
ban_reason,
|
||||||
|
_duration,
|
||||||
|
session,
|
||||||
|
session.id1 in bot.config.superusers,
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
"管理员Ban",
|
"管理员Ban",
|
||||||
@@ -218,7 +259,7 @@ async def _(
|
|||||||
).finish(reply_to=True)
|
).finish(reply_to=True)
|
||||||
elif session.id1 in bot.config.superusers:
|
elif session.id1 in bot.config.superusers:
|
||||||
_group_id = group_id.result if group_id.available else None
|
_group_id = group_id.result if group_id.available else None
|
||||||
await BanManage.ban(user_id, _group_id, _duration, session, True)
|
await BanManage.ban(user_id, _group_id, ban_reason, _duration, session, True)
|
||||||
logger.info(
|
logger.info(
|
||||||
"超级用户Ban",
|
"超级用户Ban",
|
||||||
arparma.header_result,
|
arparma.header_result,
|
||||||
@@ -270,7 +311,7 @@ async def _(
|
|||||||
At(flag="user", target=user_id)
|
At(flag="user", target=user_id)
|
||||||
if isinstance(user.result, At)
|
if isinstance(user.result, At)
|
||||||
else result
|
else result
|
||||||
), # type: ignore
|
),
|
||||||
" 从黑屋中拉了出来并急救了一下!",
|
" 从黑屋中拉了出来并急救了一下!",
|
||||||
]
|
]
|
||||||
).finish(reply_to=True)
|
).finish(reply_to=True)
|
||||||
|
|||||||
@@ -5,9 +5,20 @@ 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, reason: str | None = None, duration: int = 1):
|
||||||
|
"""调用ban
|
||||||
|
|
||||||
|
参数:
|
||||||
|
user_id: 用户id
|
||||||
|
"""
|
||||||
|
await BanConsole.ban(user_id, None, 9, reason, duration * 60)
|
||||||
|
logger.info("被讨厌了,已将用户加入黑名单...", "ban", session=user_id)
|
||||||
|
|
||||||
|
|
||||||
class BanManage:
|
class BanManage:
|
||||||
@classmethod
|
@classmethod
|
||||||
async def build_ban_image(
|
async def build_ban_image(
|
||||||
@@ -44,20 +55,26 @@ class BanManage:
|
|||||||
"用户ID",
|
"用户ID",
|
||||||
"群组ID",
|
"群组ID",
|
||||||
"BAN LEVEL",
|
"BAN LEVEL",
|
||||||
|
"封禁原因",
|
||||||
"剩余时长(分钟)",
|
"剩余时长(分钟)",
|
||||||
"操作员ID",
|
"操作员ID",
|
||||||
]
|
]
|
||||||
row_data = []
|
row_data = []
|
||||||
for data in data_list:
|
for data in data_list:
|
||||||
duration = int((data.ban_time + data.duration - time.time()) / 60)
|
if data.duration == -1:
|
||||||
if data.duration < 0:
|
|
||||||
duration = "∞"
|
duration = "∞"
|
||||||
|
else:
|
||||||
|
duration = int((data.ban_time + data.duration - time.time()) / 60)
|
||||||
|
if isinstance(duration, int) and duration < 0:
|
||||||
|
await data.delete()
|
||||||
|
continue
|
||||||
row_data.append(
|
row_data.append(
|
||||||
[
|
[
|
||||||
data.id,
|
data.id,
|
||||||
data.user_id,
|
data.user_id,
|
||||||
data.group_id,
|
data.group_id,
|
||||||
data.ban_level,
|
data.ban_level,
|
||||||
|
data.ban_reason,
|
||||||
duration,
|
duration,
|
||||||
data.operator,
|
data.operator,
|
||||||
]
|
]
|
||||||
@@ -97,22 +114,22 @@ class BanManage:
|
|||||||
is_superuser: 是否为超级用户操作
|
is_superuser: 是否为超级用户操作
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
tuple[bool, str]: 是否unban成功, 群组/用户id或提示
|
tuple[bool, str | Non]: 是否unban成功, 群组/用户id或提示
|
||||||
"""
|
"""
|
||||||
user_level = 9999
|
user_level = 9999
|
||||||
if not is_superuser and user_id and session.id1:
|
if not is_superuser and user_id and session.id1:
|
||||||
user_level = await LevelUser.get_user_level(session.id1, group_id)
|
user_level = await LevelUser.get_user_level(session.id1, group_id)
|
||||||
if idx:
|
if idx:
|
||||||
ban_data = await BanConsole.get_or_none(id=idx)
|
ban_data = await BanConsole.get_ban(id=idx)
|
||||||
if not ban_data:
|
if not ban_data:
|
||||||
return False, "该用户/群组不在黑名单中捏..."
|
return False, "该用户/群组不在黑名单中捏..."
|
||||||
if ban_data.ban_level > user_level:
|
if ban_data.ban_level > user_level:
|
||||||
return False, "unBan权限等级不足捏..."
|
return False, "unBan权限等级不足捏..."
|
||||||
await ban_data.delete()
|
await ban_data.delete()
|
||||||
return True, str(ban_data.user_id or ban_data.group_id)
|
return True, ban_data.user_id or ban_data.group_id
|
||||||
elif await BanConsole.check_ban_level(user_id, group_id, user_level):
|
elif await BanConsole.check_ban_level(user_id, group_id, user_level):
|
||||||
await BanConsole.unban(user_id, group_id)
|
await BanConsole.unban(user_id, group_id)
|
||||||
return True, str(group_id)
|
return True, group_id or ""
|
||||||
return False, "该用户/群组不在黑名单中不足捏..."
|
return False, "该用户/群组不在黑名单中不足捏..."
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -120,6 +137,7 @@ class BanManage:
|
|||||||
cls,
|
cls,
|
||||||
user_id: str | None,
|
user_id: str | None,
|
||||||
group_id: str | None,
|
group_id: str | None,
|
||||||
|
reason: str | None,
|
||||||
duration: int,
|
duration: int,
|
||||||
session: EventSession,
|
session: EventSession,
|
||||||
is_superuser: bool,
|
is_superuser: bool,
|
||||||
@@ -129,6 +147,7 @@ class BanManage:
|
|||||||
参数:
|
参数:
|
||||||
user_id: 用户id
|
user_id: 用户id
|
||||||
group_id: 群组id
|
group_id: 群组id
|
||||||
|
reason: 理由
|
||||||
duration: 时长,秒
|
duration: 时长,秒
|
||||||
session: Session
|
session: Session
|
||||||
is_superuser: 是否为超级用户操作
|
is_superuser: 是否为超级用户操作
|
||||||
@@ -136,4 +155,4 @@ class BanManage:
|
|||||||
level = 9999
|
level = 9999
|
||||||
if not is_superuser and user_id and session.id1:
|
if not is_superuser and user_id and session.id1:
|
||||||
level = await LevelUser.get_user_level(session.id1, group_id)
|
level = await LevelUser.get_user_level(session.id1, group_id)
|
||||||
await BanConsole.ban(user_id, group_id, level, duration, session.id1)
|
await BanConsole.ban(user_id, group_id, level, reason, duration, session.id1)
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
|
import asyncio
|
||||||
|
import random
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
from nonebot import on_notice
|
from nonebot import on_notice
|
||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
from nonebot.adapters.onebot.v11 import GroupIncreaseNoticeEvent
|
from nonebot.adapters.onebot.v11 import GroupIncreaseNoticeEvent
|
||||||
|
from nonebot.permission import SUPERUSER
|
||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
|
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
|
||||||
from nonebot_plugin_apscheduler import scheduler
|
from nonebot_plugin_apscheduler import scheduler
|
||||||
@@ -10,6 +14,7 @@ from nonebot_plugin_session import EventSession
|
|||||||
from zhenxun.configs.config import BotConfig
|
from zhenxun.configs.config import BotConfig
|
||||||
from zhenxun.configs.utils import PluginExtraData
|
from zhenxun.configs.utils import PluginExtraData
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
|
from zhenxun.services.tags import tag_manager
|
||||||
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.platform import PlatformUtils
|
from zhenxun.utils.platform import PlatformUtils
|
||||||
@@ -45,12 +50,79 @@ _matcher = on_alconna(
|
|||||||
_notice = on_notice(priority=1, block=False, rule=notice_rule(GroupIncreaseNoticeEvent))
|
_notice = on_notice(priority=1, block=False, rule=notice_rule(GroupIncreaseNoticeEvent))
|
||||||
|
|
||||||
|
|
||||||
|
_update_all_matcher = on_alconna(
|
||||||
|
Alconna("更新所有群组信息"),
|
||||||
|
permission=SUPERUSER,
|
||||||
|
priority=1,
|
||||||
|
block=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _update_all_groups_task(bot: Bot, session: EventSession):
|
||||||
|
"""
|
||||||
|
在后台执行所有群组的更新任务,并向超级用户发送最终报告。
|
||||||
|
"""
|
||||||
|
success_count = 0
|
||||||
|
fail_count = 0
|
||||||
|
total_count = 0
|
||||||
|
bot_id = bot.self_id
|
||||||
|
|
||||||
|
logger.info(f"Bot {bot_id}: 开始执行所有群组信息更新任务...", "更新所有群组")
|
||||||
|
try:
|
||||||
|
group_list, _ = await PlatformUtils.get_group_list(bot)
|
||||||
|
total_count = len(group_list)
|
||||||
|
for i, group in enumerate(group_list):
|
||||||
|
try:
|
||||||
|
logger.debug(
|
||||||
|
f"Bot {bot_id}: 正在更新第 {i + 1}/{total_count} 个群组: "
|
||||||
|
f"{group.group_id}",
|
||||||
|
"更新所有群组",
|
||||||
|
)
|
||||||
|
await MemberUpdateManage.update_group_member(bot, group.group_id)
|
||||||
|
success_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
fail_count += 1
|
||||||
|
logger.error(
|
||||||
|
f"Bot {bot_id}: 更新群组 {group.group_id} 信息失败",
|
||||||
|
"更新所有群组",
|
||||||
|
e=e,
|
||||||
|
)
|
||||||
|
await asyncio.sleep(random.uniform(1.5, 3.0))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Bot {bot_id}: 获取群组列表失败,任务中断", "更新所有群组", e=e)
|
||||||
|
await PlatformUtils.send_superuser(
|
||||||
|
bot,
|
||||||
|
f"Bot {bot_id} 更新所有群组信息任务失败:无法获取群组列表。",
|
||||||
|
session.id1,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await tag_manager._invalidate_cache()
|
||||||
|
summary_message = (
|
||||||
|
f"🤖 Bot {bot_id} 所有群组信息更新任务完成!\n"
|
||||||
|
f"总计群组: {total_count}\n"
|
||||||
|
f"✅ 成功: {success_count}\n"
|
||||||
|
f"❌ 失败: {fail_count}"
|
||||||
|
)
|
||||||
|
logger.info(summary_message.replace("\n", " | "), "更新所有群组")
|
||||||
|
await PlatformUtils.send_superuser(bot, summary_message, session.id1)
|
||||||
|
|
||||||
|
|
||||||
|
@_update_all_matcher.handle()
|
||||||
|
async def _(bot: Bot, session: EventSession):
|
||||||
|
await MessageUtils.build_message(
|
||||||
|
"已开始在后台更新所有群组信息,过程可能需要几分钟到几十分钟,完成后将私聊通知您。"
|
||||||
|
).send(reply_to=True)
|
||||||
|
asyncio.create_task(_update_all_groups_task(bot, session)) # noqa: RUF006
|
||||||
|
|
||||||
|
|
||||||
@_matcher.handle()
|
@_matcher.handle()
|
||||||
async def _(bot: Bot, session: EventSession, arparma: Arparma):
|
async def _(bot: Bot, session: EventSession, arparma: Arparma):
|
||||||
if gid := session.id3 or session.id2:
|
if gid := session.id3 or session.id2:
|
||||||
logger.info("更新群组成员信息", arparma.header_result, session=session)
|
logger.info("更新群组成员信息", arparma.header_result, session=session)
|
||||||
result = await MemberUpdateManage.update_group_member(bot, gid)
|
result = await MemberUpdateManage.update_group_member(bot, gid)
|
||||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||||
|
await tag_manager._invalidate_cache()
|
||||||
await MessageUtils.build_message("群组id为空...").send()
|
await MessageUtils.build_message("群组id为空...").send()
|
||||||
|
|
||||||
|
|
||||||
@@ -64,6 +136,7 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent):
|
|||||||
session=event.user_id,
|
session=event.user_id,
|
||||||
group_id=event.group_id,
|
group_id=event.group_id,
|
||||||
)
|
)
|
||||||
|
await tag_manager._invalidate_cache()
|
||||||
|
|
||||||
|
|
||||||
@scheduler.scheduled_job(
|
@scheduler.scheduled_job(
|
||||||
@@ -91,3 +164,5 @@ async def _():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Bot: {bot.self_id} 自动更新群组信息", e=e)
|
logger.error(f"Bot: {bot.self_id} 自动更新群组信息", e=e)
|
||||||
logger.debug(f"自动 Bot: {bot.self_id} 更新群组成员信息成功...")
|
logger.debug(f"自动 Bot: {bot.self_id} 更新群组成员信息成功...")
|
||||||
|
|
||||||
|
await tag_manager._invalidate_cache()
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import re
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
from nonebot_plugin_uninfo import Member, SceneType, get_interface
|
from nonebot_plugin_uninfo import Member, SceneType, get_interface
|
||||||
|
|
||||||
from zhenxun.configs.config import Config
|
from zhenxun.configs.config import Config
|
||||||
|
from zhenxun.models.group_console import GroupConsole
|
||||||
from zhenxun.models.group_member_info import GroupInfoUser
|
from zhenxun.models.group_member_info import GroupInfoUser
|
||||||
from zhenxun.models.level_user import LevelUser
|
from zhenxun.models.level_user import LevelUser
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
@@ -32,7 +34,9 @@ class MemberUpdateManage:
|
|||||||
"""
|
"""
|
||||||
driver = nonebot.get_driver()
|
driver = nonebot.get_driver()
|
||||||
default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH")
|
default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH")
|
||||||
nickname = member.nick or member.user.name or ""
|
nickname = re.sub(
|
||||||
|
r"[\x00-\x09\x0b-\x1f\x7f-\x9f]", "", member.nick or member.user.name or ""
|
||||||
|
)
|
||||||
role = member.role
|
role = member.role
|
||||||
db_user_uid = [u.user_id for u in db_user]
|
db_user_uid = [u.user_id for u in db_user]
|
||||||
uid2name = {u.user_id: u.user_name for u in db_user}
|
uid2name = {u.user_id: u.user_name for u in db_user}
|
||||||
@@ -91,6 +95,25 @@ class MemberUpdateManage:
|
|||||||
)
|
)
|
||||||
return "更新群组失败,群组不存在..."
|
return "更新群组失败,群组不存在..."
|
||||||
members = await interface.get_members(SceneType.GROUP, group_list[0].id)
|
members = await interface.get_members(SceneType.GROUP, group_list[0].id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
group_console, _ = await GroupConsole.get_or_create(
|
||||||
|
group_id=group_id, defaults={"platform": platform}
|
||||||
|
)
|
||||||
|
group_console.member_count = len(members)
|
||||||
|
group_console.group_name = group_list[0].name or ""
|
||||||
|
await group_console.save(update_fields=["member_count", "group_name"])
|
||||||
|
logger.debug(
|
||||||
|
f"已更新群组 {group_id} 的成员总数为 {len(members)}",
|
||||||
|
"更新群组成员信息",
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"更新群组 {group_id} 的 GroupConsole 信息失败",
|
||||||
|
"更新群组成员信息",
|
||||||
|
e=e,
|
||||||
|
)
|
||||||
|
|
||||||
db_user = await GroupInfoUser.filter(group_id=group_id).all()
|
db_user = await GroupInfoUser.filter(group_id=group_id).all()
|
||||||
db_user_uid = [u.user_id for u in db_user]
|
db_user_uid = [u.user_id for u in db_user]
|
||||||
data_list = ([], [], [])
|
data_list = ([], [], [])
|
||||||
@@ -101,25 +124,16 @@ class MemberUpdateManage:
|
|||||||
exist_member_list.append(member.id)
|
exist_member_list.append(member.id)
|
||||||
if data_list[0]:
|
if data_list[0]:
|
||||||
try:
|
try:
|
||||||
await GroupInfoUser.bulk_create(data_list[0], 30)
|
await GroupInfoUser.bulk_create(
|
||||||
|
data_list[0], 30, ignore_conflicts=True
|
||||||
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"创建用户数据 {len(data_list[0])} 条",
|
f"创建用户数据 {len(data_list[0])} 条",
|
||||||
"更新群组成员信息",
|
"更新群组成员信息",
|
||||||
target=group_id,
|
target=group_id,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error("批量创建用户数据失败", "更新群组成员信息", e=e)
|
||||||
f"批量创建用户数据失败: {e},开始进行逐个存储",
|
|
||||||
"更新群组成员信息",
|
|
||||||
)
|
|
||||||
for u in data_list[0]:
|
|
||||||
try:
|
|
||||||
await u.save()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"创建用户 {u.user_name}({u.user_id}) 数据失败: {e}",
|
|
||||||
"更新群组成员信息",
|
|
||||||
)
|
|
||||||
if data_list[1]:
|
if data_list[1]:
|
||||||
await GroupInfoUser.bulk_update(data_list[1], ["user_name"], 30)
|
await GroupInfoUser.bulk_update(data_list[1], ["user_name"], 30)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot_plugin_alconna import AlconnaQuery, Arparma, Match, Query
|
from nonebot_plugin_alconna import AlconnaQuery, Arparma, Match, Query
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
|
|
||||||
from zhenxun.configs.config import Config
|
from zhenxun.configs.config import Config
|
||||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||||
@@ -9,7 +9,7 @@ from zhenxun.services.log import logger
|
|||||||
from zhenxun.utils.enum import BlockType, PluginType
|
from zhenxun.utils.enum import BlockType, PluginType
|
||||||
from zhenxun.utils.message import MessageUtils
|
from zhenxun.utils.message import MessageUtils
|
||||||
|
|
||||||
from ._data_source import PluginManage, build_plugin, build_task, delete_help_image
|
from ._data_source import PluginManager, build_plugin, build_task
|
||||||
from .command import _group_status_matcher, _status_matcher
|
from .command import _group_status_matcher, _status_matcher
|
||||||
|
|
||||||
base_config = Config.get("plugin_switch")
|
base_config = Config.get("plugin_switch")
|
||||||
@@ -57,6 +57,11 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
关闭群被动早晚安
|
关闭群被动早晚安
|
||||||
关闭群被动早晚安 -g 12355555
|
关闭群被动早晚安 -g 12355555
|
||||||
|
|
||||||
|
开启/关闭默认群被动 [被动名称]
|
||||||
|
私聊下: 开启/关闭群被动默认状态
|
||||||
|
示例:
|
||||||
|
关闭默认群被动 早晚安
|
||||||
|
|
||||||
开启/关闭所有群被动 ?[-g [group_id]]
|
开启/关闭所有群被动 ?[-g [group_id]]
|
||||||
私聊中: 开启/关闭全局或指定群组被动状态
|
私聊中: 开启/关闭全局或指定群组被动状态
|
||||||
示例:
|
示例:
|
||||||
@@ -87,10 +92,10 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
@_status_matcher.assign("$main")
|
@_status_matcher.assign("$main")
|
||||||
async def _(
|
async def _(
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
session: EventSession,
|
session: Uninfo,
|
||||||
arparma: Arparma,
|
arparma: Arparma,
|
||||||
):
|
):
|
||||||
if session.id1 in bot.config.superusers:
|
if session.user.id in bot.config.superusers:
|
||||||
image = await build_plugin()
|
image = await build_plugin()
|
||||||
logger.info(
|
logger.info(
|
||||||
"查看功能列表",
|
"查看功能列表",
|
||||||
@@ -105,7 +110,7 @@ async def _(
|
|||||||
@_status_matcher.assign("open")
|
@_status_matcher.assign("open")
|
||||||
async def _(
|
async def _(
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
session: EventSession,
|
session: Uninfo,
|
||||||
arparma: Arparma,
|
arparma: Arparma,
|
||||||
plugin_name: Match[str],
|
plugin_name: Match[str],
|
||||||
group: Match[str],
|
group: Match[str],
|
||||||
@@ -114,22 +119,23 @@ async def _(
|
|||||||
all: Query[bool] = AlconnaQuery("all.value", False),
|
all: Query[bool] = AlconnaQuery("all.value", False),
|
||||||
):
|
):
|
||||||
if not all.result and not plugin_name.available:
|
if not all.result and not plugin_name.available:
|
||||||
await MessageUtils.build_message("请输入功能名称").finish(reply_to=True)
|
await MessageUtils.build_message("请输入功能/被动名称").finish(reply_to=True)
|
||||||
name = plugin_name.result
|
name = plugin_name.result
|
||||||
if gid := session.id3 or session.id2:
|
if session.group:
|
||||||
|
group_id = session.group.id
|
||||||
"""修改当前群组的数据"""
|
"""修改当前群组的数据"""
|
||||||
if task.result:
|
if task.result:
|
||||||
if all.result:
|
if all.result:
|
||||||
result = await PluginManage.unblock_group_all_task(gid)
|
result = await PluginManager.unblock_group_all_task(group_id)
|
||||||
logger.info("开启所有群组被动", arparma.header_result, session=session)
|
logger.info("开启所有群组被动", arparma.header_result, session=session)
|
||||||
else:
|
else:
|
||||||
result = await PluginManage.unblock_group_task(name, gid)
|
result = await PluginManager.unblock_group_task(name, group_id)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"开启群组被动 {name}", arparma.header_result, session=session
|
f"开启群组被动 {name}", arparma.header_result, session=session
|
||||||
)
|
)
|
||||||
elif session.id1 in bot.config.superusers and default_status.result:
|
elif session.user.id in bot.config.superusers and default_status.result:
|
||||||
"""单个插件的进群默认修改"""
|
"""单个插件的进群默认修改"""
|
||||||
result = await PluginManage.set_default_status(name, True)
|
result = await PluginManager.set_default_status(name, True)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"超级用户开启 {name} 功能进群默认开关",
|
f"超级用户开启 {name} 功能进群默认开关",
|
||||||
arparma.header_result,
|
arparma.header_result,
|
||||||
@@ -137,8 +143,8 @@ async def _(
|
|||||||
)
|
)
|
||||||
elif all.result:
|
elif all.result:
|
||||||
"""所有插件"""
|
"""所有插件"""
|
||||||
result = await PluginManage.set_all_plugin_status(
|
result = await PluginManager.set_all_plugin_status(
|
||||||
True, default_status.result, gid
|
True, default_status.result, group_id
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
"开启群组中全部功能",
|
"开启群组中全部功能",
|
||||||
@@ -146,22 +152,23 @@ async def _(
|
|||||||
session=session,
|
session=session,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result = await PluginManage.unblock_group_plugin(name, gid)
|
result = await PluginManager.unblock_group_plugin(name, group_id)
|
||||||
logger.info(f"开启功能 {name}", arparma.header_result, session=session)
|
logger.info(f"开启功能 {name}", arparma.header_result, session=session)
|
||||||
delete_help_image(gid)
|
|
||||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||||
elif session.id1 in bot.config.superusers:
|
elif session.user.id in bot.config.superusers:
|
||||||
"""私聊"""
|
"""私聊"""
|
||||||
group_id = group.result if group.available else None
|
group_id = group.result if group.available else None
|
||||||
if all.result:
|
if all.result:
|
||||||
if task.result:
|
if task.result:
|
||||||
"""关闭全局或指定群全部被动"""
|
"""关闭全局或指定群全部被动"""
|
||||||
if group_id:
|
if group_id:
|
||||||
result = await PluginManage.unblock_group_all_task(group_id)
|
result = await PluginManager.unblock_group_all_task(group_id)
|
||||||
else:
|
else:
|
||||||
result = await PluginManage.unblock_global_all_task()
|
result = await PluginManager.unblock_global_all_task(
|
||||||
|
default_status.result
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
result = await PluginManage.set_all_plugin_status(
|
result = await PluginManager.set_all_plugin_status(
|
||||||
True, default_status.result, group_id
|
True, default_status.result, group_id
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -171,8 +178,8 @@ async def _(
|
|||||||
session=session,
|
session=session,
|
||||||
)
|
)
|
||||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||||
if default_status.result:
|
if default_status.result and not task.result:
|
||||||
result = await PluginManage.set_default_status(name, True)
|
result = await PluginManager.set_default_status(name, True)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"超级用户开启 {name} 功能进群默认开关",
|
f"超级用户开启 {name} 功能进群默认开关",
|
||||||
arparma.header_result,
|
arparma.header_result,
|
||||||
@@ -186,7 +193,7 @@ async def _(
|
|||||||
name = split_list[0]
|
name = split_list[0]
|
||||||
group_id = split_list[1]
|
group_id = split_list[1]
|
||||||
if group_id:
|
if group_id:
|
||||||
result = await PluginManage.superuser_task_handle(name, group_id, True)
|
result = await PluginManager.superuser_task_handle(name, group_id, True)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"超级用户开启被动技能 {name}",
|
f"超级用户开启被动技能 {name}",
|
||||||
arparma.header_result,
|
arparma.header_result,
|
||||||
@@ -194,28 +201,29 @@ async def _(
|
|||||||
target=group_id,
|
target=group_id,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result = await PluginManage.unblock_global_task(name)
|
result = await PluginManager.unblock_global_task(
|
||||||
|
name, default_status.result
|
||||||
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"超级用户开启全局被动技能 {name}",
|
f"超级用户开启全局被动技能 {name}",
|
||||||
arparma.header_result,
|
arparma.header_result,
|
||||||
session=session,
|
session=session,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result = await PluginManage.superuser_unblock(name, None, group_id)
|
result = await PluginManager.superuser_unblock(name, None, group_id)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"超级用户开启功能 {name}",
|
f"超级用户开启功能 {name}",
|
||||||
arparma.header_result,
|
arparma.header_result,
|
||||||
session=session,
|
session=session,
|
||||||
target=group_id,
|
target=group_id,
|
||||||
)
|
)
|
||||||
delete_help_image()
|
|
||||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||||
|
|
||||||
|
|
||||||
@_status_matcher.assign("close")
|
@_status_matcher.assign("close")
|
||||||
async def _(
|
async def _(
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
session: EventSession,
|
session: Uninfo,
|
||||||
arparma: Arparma,
|
arparma: Arparma,
|
||||||
plugin_name: Match[str],
|
plugin_name: Match[str],
|
||||||
block_type: Match[str],
|
block_type: Match[str],
|
||||||
@@ -225,22 +233,23 @@ async def _(
|
|||||||
all: Query[bool] = AlconnaQuery("all.value", False),
|
all: Query[bool] = AlconnaQuery("all.value", False),
|
||||||
):
|
):
|
||||||
if not all.result and not plugin_name.available:
|
if not all.result and not plugin_name.available:
|
||||||
await MessageUtils.build_message("请输入功能名称").finish(reply_to=True)
|
await MessageUtils.build_message("请输入功能/被动名称").finish(reply_to=True)
|
||||||
name = plugin_name.result
|
name = plugin_name.result
|
||||||
if gid := session.id3 or session.id2:
|
if session.group:
|
||||||
|
group_id = session.group.id
|
||||||
"""修改当前群组的数据"""
|
"""修改当前群组的数据"""
|
||||||
if task.result:
|
if task.result:
|
||||||
if all.result:
|
if all.result:
|
||||||
result = await PluginManage.block_group_all_task(gid)
|
result = await PluginManager.block_group_all_task(group_id)
|
||||||
logger.info("开启所有群组被动", arparma.header_result, session=session)
|
logger.info("开启所有群组被动", arparma.header_result, session=session)
|
||||||
else:
|
else:
|
||||||
result = await PluginManage.block_group_task(name, gid)
|
result = await PluginManager.block_group_task(name, group_id)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"关闭群组被动 {name}", arparma.header_result, session=session
|
f"关闭群组被动 {name}", arparma.header_result, session=session
|
||||||
)
|
)
|
||||||
elif session.id1 in bot.config.superusers and default_status.result:
|
elif session.user.id in bot.config.superusers and default_status.result:
|
||||||
"""单个插件的进群默认修改"""
|
"""单个插件的进群默认修改"""
|
||||||
result = await PluginManage.set_default_status(name, False)
|
result = await PluginManager.set_default_status(name, False)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"超级用户开启 {name} 功能进群默认开关",
|
f"超级用户开启 {name} 功能进群默认开关",
|
||||||
arparma.header_result,
|
arparma.header_result,
|
||||||
@@ -248,26 +257,27 @@ async def _(
|
|||||||
)
|
)
|
||||||
elif all.result:
|
elif all.result:
|
||||||
"""所有插件"""
|
"""所有插件"""
|
||||||
result = await PluginManage.set_all_plugin_status(
|
result = await PluginManager.set_all_plugin_status(
|
||||||
False, default_status.result, gid
|
False, default_status.result, group_id
|
||||||
)
|
)
|
||||||
logger.info("关闭群组中全部功能", arparma.header_result, session=session)
|
logger.info("关闭群组中全部功能", arparma.header_result, session=session)
|
||||||
else:
|
else:
|
||||||
result = await PluginManage.block_group_plugin(name, gid)
|
result = await PluginManager.block_group_plugin(name, group_id)
|
||||||
logger.info(f"关闭功能 {name}", arparma.header_result, session=session)
|
logger.info(f"关闭功能 {name}", arparma.header_result, session=session)
|
||||||
delete_help_image(gid)
|
|
||||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||||
elif session.id1 in bot.config.superusers:
|
elif session.user.id in bot.config.superusers:
|
||||||
group_id = group.result if group.available else None
|
group_id = group.result if group.available else None
|
||||||
if all.result:
|
if all.result:
|
||||||
if task.result:
|
if task.result:
|
||||||
"""关闭全局或指定群全部被动"""
|
"""关闭全局或指定群全部被动"""
|
||||||
if group_id:
|
if group_id:
|
||||||
result = await PluginManage.block_group_all_task(group_id)
|
result = await PluginManager.block_group_all_task(group_id)
|
||||||
else:
|
else:
|
||||||
result = await PluginManage.block_global_all_task()
|
result = await PluginManager.block_global_all_task(
|
||||||
|
default_status.result
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
result = await PluginManage.set_all_plugin_status(
|
result = await PluginManager.set_all_plugin_status(
|
||||||
False, default_status.result, group_id
|
False, default_status.result, group_id
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -277,8 +287,8 @@ async def _(
|
|||||||
session=session,
|
session=session,
|
||||||
)
|
)
|
||||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||||
if default_status.result:
|
if default_status.result and not task.result:
|
||||||
result = await PluginManage.set_default_status(name, False)
|
result = await PluginManager.set_default_status(name, False)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"超级用户关闭 {name} 功能进群默认开关",
|
f"超级用户关闭 {name} 功能进群默认开关",
|
||||||
arparma.header_result,
|
arparma.header_result,
|
||||||
@@ -292,7 +302,9 @@ async def _(
|
|||||||
name = split_list[0]
|
name = split_list[0]
|
||||||
group_id = split_list[1]
|
group_id = split_list[1]
|
||||||
if group_id:
|
if group_id:
|
||||||
result = await PluginManage.superuser_task_handle(name, group_id, False)
|
result = await PluginManager.superuser_task_handle(
|
||||||
|
name, group_id, False
|
||||||
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"超级用户关闭被动技能 {name}",
|
f"超级用户关闭被动技能 {name}",
|
||||||
arparma.header_result,
|
arparma.header_result,
|
||||||
@@ -300,7 +312,9 @@ async def _(
|
|||||||
target=group_id,
|
target=group_id,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result = await PluginManage.block_global_task(name)
|
result = await PluginManager.block_global_task(
|
||||||
|
name, default_status.result
|
||||||
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"超级用户关闭全局被动技能 {name}",
|
f"超级用户关闭全局被动技能 {name}",
|
||||||
arparma.header_result,
|
arparma.header_result,
|
||||||
@@ -314,32 +328,32 @@ async def _(
|
|||||||
elif block_type.result in ["g", "group"]:
|
elif block_type.result in ["g", "group"]:
|
||||||
if block_type.available:
|
if block_type.available:
|
||||||
_type = BlockType.GROUP
|
_type = BlockType.GROUP
|
||||||
result = await PluginManage.superuser_block(name, _type, group_id)
|
result = await PluginManager.superuser_block(name, _type, group_id)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"超级用户关闭功能 {name}, 禁用类型: {_type}",
|
f"超级用户关闭功能 {name}, 禁用类型: {_type}",
|
||||||
arparma.header_result,
|
arparma.header_result,
|
||||||
session=session,
|
session=session,
|
||||||
target=group_id,
|
target=group_id,
|
||||||
)
|
)
|
||||||
delete_help_image()
|
|
||||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||||
|
|
||||||
|
|
||||||
@_group_status_matcher.handle()
|
@_group_status_matcher.handle()
|
||||||
async def _(
|
async def _(
|
||||||
session: EventSession,
|
session: Uninfo,
|
||||||
arparma: Arparma,
|
arparma: Arparma,
|
||||||
status: str,
|
status: str,
|
||||||
):
|
):
|
||||||
if gid := session.id3 or session.id2:
|
if session.group:
|
||||||
|
group_id = session.group.id
|
||||||
if status == "sleep":
|
if status == "sleep":
|
||||||
await PluginManage.sleep(gid)
|
await PluginManager.sleep(group_id)
|
||||||
logger.info("进行休眠", arparma.header_result, session=session)
|
logger.info("进行休眠", arparma.header_result, session=session)
|
||||||
await MessageUtils.build_message("那我先睡觉了...").finish()
|
await MessageUtils.build_message("那我先睡觉了...").finish()
|
||||||
else:
|
else:
|
||||||
if await PluginManage.is_wake(gid):
|
if await PluginManager.is_wake(group_id):
|
||||||
await MessageUtils.build_message("我还醒着呢!").finish()
|
await MessageUtils.build_message("我还醒着呢!").finish()
|
||||||
await PluginManage.wake(gid)
|
await PluginManager.wake(group_id)
|
||||||
logger.info("醒来", arparma.header_result, session=session)
|
logger.info("醒来", arparma.header_result, session=session)
|
||||||
await MessageUtils.build_message("呜..醒来了...").finish()
|
await MessageUtils.build_message("呜..醒来了...").finish()
|
||||||
return MessageUtils.build_message("群组id为空...").send()
|
return MessageUtils.build_message("群组id为空...").send()
|
||||||
@@ -347,10 +361,10 @@ async def _(
|
|||||||
|
|
||||||
@_status_matcher.assign("task")
|
@_status_matcher.assign("task")
|
||||||
async def _(
|
async def _(
|
||||||
session: EventSession,
|
session: Uninfo,
|
||||||
arparma: Arparma,
|
arparma: Arparma,
|
||||||
):
|
):
|
||||||
image = await build_task(session.id3 or session.id2)
|
image = await build_task(session.group.id if session.group else None)
|
||||||
if image:
|
if image:
|
||||||
logger.info("查看群被动列表", arparma.header_result, session=session)
|
logger.info("查看群被动列表", arparma.header_result, session=session)
|
||||||
await MessageUtils.build_message(image).finish(reply_to=True)
|
await MessageUtils.build_message(image).finish(reply_to=True)
|
||||||
|
|||||||
@@ -1,30 +1,14 @@
|
|||||||
import os
|
from typing import cast
|
||||||
|
|
||||||
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
|
|
||||||
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.models.task_info import TaskInfo
|
from zhenxun.models.task_info import TaskInfo
|
||||||
from zhenxun.utils.enum import BlockType, PluginType
|
from zhenxun.services.cache import CacheRoot
|
||||||
|
from zhenxun.utils.common_utils import CommonUtils
|
||||||
|
from zhenxun.utils.enum import BlockType, CacheType, PluginType
|
||||||
from zhenxun.utils.exception import GroupInfoNotFound
|
from zhenxun.utils.exception import GroupInfoNotFound
|
||||||
from zhenxun.utils.image_utils import BuildImage, ImageTemplate, RowStyle
|
from zhenxun.utils.image_utils import BuildImage, ImageTemplate, RowStyle
|
||||||
|
|
||||||
HELP_FILE = IMAGE_PATH / "SIMPLE_HELP.png"
|
|
||||||
|
|
||||||
GROUP_HELP_PATH = DATA_PATH / "group_help"
|
|
||||||
|
|
||||||
|
|
||||||
def delete_help_image(gid: str | None = None):
|
|
||||||
"""删除帮助图片"""
|
|
||||||
if gid:
|
|
||||||
for file in os.listdir(GROUP_HELP_PATH):
|
|
||||||
if file.startswith(f"{gid}"):
|
|
||||||
os.remove(GROUP_HELP_PATH / file)
|
|
||||||
else:
|
|
||||||
if HELP_FILE.exists():
|
|
||||||
HELP_FILE.unlink()
|
|
||||||
for file in GROUP_HELP_PATH.iterdir():
|
|
||||||
file.unlink()
|
|
||||||
|
|
||||||
|
|
||||||
def plugin_row_style(column: str, text: str) -> RowStyle:
|
def plugin_row_style(column: str, text: str) -> RowStyle:
|
||||||
"""被动技能文本风格
|
"""被动技能文本风格
|
||||||
@@ -116,9 +100,7 @@ async def build_task(group_id: str | None) -> BuildImage:
|
|||||||
column_name = ["ID", "模块", "名称", "群组状态", "全局状态", "运行时间"]
|
column_name = ["ID", "模块", "名称", "群组状态", "全局状态", "运行时间"]
|
||||||
group = None
|
group = None
|
||||||
if group_id:
|
if group_id:
|
||||||
group = await GroupConsole.get_or_none(
|
group = await GroupConsole.get_group(group_id=group_id)
|
||||||
group_id=group_id, channel_id__isnull=True
|
|
||||||
)
|
|
||||||
if not group:
|
if not group:
|
||||||
raise GroupInfoNotFound()
|
raise GroupInfoNotFound()
|
||||||
else:
|
else:
|
||||||
@@ -155,7 +137,7 @@ async def build_task(group_id: str | None) -> BuildImage:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PluginManage:
|
class PluginManager:
|
||||||
@classmethod
|
@classmethod
|
||||||
async def set_default_status(cls, plugin_name: str, status: bool) -> str:
|
async def set_default_status(cls, plugin_name: str, status: bool) -> str:
|
||||||
"""设置插件进群默认状态
|
"""设置插件进群默认状态
|
||||||
@@ -200,26 +182,26 @@ class PluginManage:
|
|||||||
)
|
)
|
||||||
return f"成功将所有功能进群默认状态修改为: {'开启' if status else '关闭'}"
|
return f"成功将所有功能进群默认状态修改为: {'开启' if status else '关闭'}"
|
||||||
if group_id:
|
if group_id:
|
||||||
if group := await GroupConsole.get_or_none(
|
if group := await GroupConsole.get_group(group_id=group_id):
|
||||||
group_id=group_id, channel_id__isnull=True
|
module_list = cast(
|
||||||
):
|
list[str],
|
||||||
module_list = await PluginInfo.filter(
|
await PluginInfo.filter(plugin_type=PluginType.NORMAL).values_list(
|
||||||
plugin_type=PluginType.NORMAL
|
"module", flat=True
|
||||||
).values_list("module", flat=True)
|
),
|
||||||
if status:
|
|
||||||
for module in module_list:
|
|
||||||
group.block_plugin = group.block_plugin.replace(
|
|
||||||
f"<{module},", ""
|
|
||||||
)
|
)
|
||||||
|
if status:
|
||||||
|
# 开启所有功能 - 清空禁用列表
|
||||||
|
group.block_plugin = ""
|
||||||
else:
|
else:
|
||||||
module_list = [f"<{module}" for module in module_list]
|
# 关闭所有功能 - 将模块列表转换为禁用格式
|
||||||
group.block_plugin = ",".join(module_list) + "," # type: ignore
|
group.block_plugin = CommonUtils.convert_module_format(module_list)
|
||||||
await group.save(update_fields=["block_plugin"])
|
await group.save(update_fields=["block_plugin"])
|
||||||
return f"成功将此群组所有功能状态修改为: {'开启' if status else '关闭'}"
|
return f"成功将此群组所有功能状态修改为: {'开启' if status else '关闭'}"
|
||||||
return "获取群组失败..."
|
return "获取群组失败..."
|
||||||
await PluginInfo.filter(plugin_type=PluginType.NORMAL).update(
|
await PluginInfo.filter(plugin_type=PluginType.NORMAL).update(
|
||||||
status=status, block_type=None if status else BlockType.ALL
|
status=status, block_type=None if status else BlockType.ALL
|
||||||
)
|
)
|
||||||
|
await CacheRoot.invalidate_cache(CacheType.PLUGINS)
|
||||||
return f"成功将所有功能全局状态修改为: {'开启' if status else '关闭'}"
|
return f"成功将所有功能全局状态修改为: {'开启' if status else '关闭'}"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -232,9 +214,7 @@ class PluginManage:
|
|||||||
返回:
|
返回:
|
||||||
bool: 是否醒来
|
bool: 是否醒来
|
||||||
"""
|
"""
|
||||||
if c := await GroupConsole.get_or_none(
|
if c := await GroupConsole.get_group(group_id=group_id):
|
||||||
group_id=group_id, channel_id__isnull=True
|
|
||||||
):
|
|
||||||
return c.status
|
return c.status
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -245,9 +225,11 @@ class PluginManage:
|
|||||||
参数:
|
参数:
|
||||||
group_id: 群组id
|
group_id: 群组id
|
||||||
"""
|
"""
|
||||||
await GroupConsole.filter(group_id=group_id, channel_id__isnull=True).update(
|
group, _ = await GroupConsole.get_or_create(
|
||||||
status=False
|
group_id=group_id, channel_id__isnull=True
|
||||||
)
|
)
|
||||||
|
group.status = False
|
||||||
|
await group.save(update_fields=["status"])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def wake(cls, group_id: str):
|
async def wake(cls, group_id: str):
|
||||||
@@ -256,9 +238,11 @@ class PluginManage:
|
|||||||
参数:
|
参数:
|
||||||
group_id: 群组id
|
group_id: 群组id
|
||||||
"""
|
"""
|
||||||
await GroupConsole.filter(group_id=group_id, channel_id__isnull=True).update(
|
group, _ = await GroupConsole.get_or_create(
|
||||||
status=True
|
group_id=group_id, channel_id__isnull=True
|
||||||
)
|
)
|
||||||
|
group.status = True
|
||||||
|
await group.save(update_fields=["status"])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def block(cls, module: str):
|
async def block(cls, module: str):
|
||||||
@@ -267,7 +251,9 @@ class PluginManage:
|
|||||||
参数:
|
参数:
|
||||||
module: 模块名
|
module: 模块名
|
||||||
"""
|
"""
|
||||||
await PluginInfo.filter(module=module).update(status=False)
|
if plugin := await PluginInfo.get_plugin(module=module):
|
||||||
|
plugin.status = False
|
||||||
|
await plugin.save(update_fields=["status"])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def unblock(cls, module: str):
|
async def unblock(cls, module: str):
|
||||||
@@ -276,7 +262,9 @@ class PluginManage:
|
|||||||
参数:
|
参数:
|
||||||
module: 模块名
|
module: 模块名
|
||||||
"""
|
"""
|
||||||
await PluginInfo.filter(module=module).update(status=True)
|
if plugin := await PluginInfo.get_plugin(module=module):
|
||||||
|
plugin.status = True
|
||||||
|
await plugin.save(update_fields=["status"])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def block_group_plugin(cls, plugin_name: str, group_id: str) -> str:
|
async def block_group_plugin(cls, plugin_name: str, group_id: str) -> str:
|
||||||
@@ -342,17 +330,21 @@ class PluginManage:
|
|||||||
return await cls._change_group_task("", group_id, True, True)
|
return await cls._change_group_task("", group_id, True, True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def block_global_all_task(cls) -> str:
|
async def block_global_all_task(cls, is_default: bool) -> str:
|
||||||
"""禁用全局被动技能
|
"""禁用全局被动技能
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
str: 返回信息
|
str: 返回信息
|
||||||
"""
|
"""
|
||||||
|
if is_default:
|
||||||
|
await TaskInfo.all().update(default_status=False)
|
||||||
|
return "已禁用所有被动进群默认状态"
|
||||||
|
else:
|
||||||
await TaskInfo.all().update(status=False)
|
await TaskInfo.all().update(status=False)
|
||||||
return "已全局禁用所有被动状态"
|
return "已全局禁用所有被动状态"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def block_global_task(cls, name: str) -> str:
|
async def block_global_task(cls, name: str, is_default: bool = False) -> str:
|
||||||
"""禁用全局被动技能
|
"""禁用全局被动技能
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
@@ -361,29 +353,45 @@ class PluginManage:
|
|||||||
返回:
|
返回:
|
||||||
str: 返回信息
|
str: 返回信息
|
||||||
"""
|
"""
|
||||||
|
if is_default:
|
||||||
|
await TaskInfo.filter(name=name).update(default_status=False)
|
||||||
|
return f"已禁用被动进群默认状态 {name}"
|
||||||
|
else:
|
||||||
await TaskInfo.filter(name=name).update(status=False)
|
await TaskInfo.filter(name=name).update(status=False)
|
||||||
return f"已全局禁用被动状态 {name}"
|
return f"已全局禁用被动状态 {name}"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def unblock_global_all_task(cls) -> str:
|
async def unblock_global_all_task(cls, is_default: bool) -> str:
|
||||||
"""开启全局被动技能
|
"""开启全局被动技能
|
||||||
|
|
||||||
|
参数:
|
||||||
|
is_default: 是否为默认状态
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
str: 返回信息
|
str: 返回信息
|
||||||
"""
|
"""
|
||||||
|
if is_default:
|
||||||
|
await TaskInfo.all().update(default_status=True)
|
||||||
|
return "已开启所有被动进群默认状态"
|
||||||
|
else:
|
||||||
await TaskInfo.all().update(status=True)
|
await TaskInfo.all().update(status=True)
|
||||||
return "已全局开启所有被动状态"
|
return "已全局开启所有被动状态"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def unblock_global_task(cls, name: str) -> str:
|
async def unblock_global_task(cls, name: str, is_default: bool = False) -> str:
|
||||||
"""开启全局被动技能
|
"""开启全局被动技能
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
name: 被动技能名称
|
name: 被动技能名称
|
||||||
|
is_default: 是否为默认状态
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
str: 返回信息
|
str: 返回信息
|
||||||
"""
|
"""
|
||||||
|
if is_default:
|
||||||
|
await TaskInfo.filter(name=name).update(default_status=True)
|
||||||
|
return f"已开启被动进群默认状态 {name}"
|
||||||
|
else:
|
||||||
await TaskInfo.filter(name=name).update(status=True)
|
await TaskInfo.filter(name=name).update(status=True)
|
||||||
return f"已全局开启被动状态 {name}"
|
return f"已全局开启被动状态 {name}"
|
||||||
|
|
||||||
@@ -417,17 +425,18 @@ class PluginManage:
|
|||||||
"""
|
"""
|
||||||
status_str = "关闭" if status else "开启"
|
status_str = "关闭" if status else "开启"
|
||||||
if is_all:
|
if is_all:
|
||||||
modules = await TaskInfo.annotate().values_list("module", flat=True)
|
module_list = cast(
|
||||||
if modules:
|
list[str], await TaskInfo.annotate().values_list("module", flat=True)
|
||||||
|
)
|
||||||
|
if module_list:
|
||||||
group, _ = await GroupConsole.get_or_create(
|
group, _ = await GroupConsole.get_or_create(
|
||||||
group_id=group_id, channel_id__isnull=True
|
group_id=group_id, channel_id__isnull=True
|
||||||
)
|
)
|
||||||
modules = [f"<{module}" for module in modules]
|
|
||||||
if status:
|
if status:
|
||||||
group.block_task = ",".join(modules) + "," # type: ignore
|
group.block_task = CommonUtils.convert_module_format(module_list)
|
||||||
else:
|
else:
|
||||||
for module in modules:
|
# 开启所有模块 - 清空禁用列表
|
||||||
group.block_task = group.block_task.replace(f"{module},", "")
|
group.block_task = ""
|
||||||
await group.save(update_fields=["block_task"])
|
await group.save(update_fields=["block_task"])
|
||||||
return f"已成功{status_str}全部被动技能!"
|
return f"已成功{status_str}全部被动技能!"
|
||||||
elif task := await TaskInfo.get_or_none(name=task_name):
|
elif task := await TaskInfo.get_or_none(name=task_name):
|
||||||
|
|||||||
@@ -58,6 +58,19 @@ _status_matcher.shortcut(
|
|||||||
prefix=True,
|
prefix=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_status_matcher.shortcut(
|
||||||
|
r"开启(所有|全部)默认群被动",
|
||||||
|
command="switch",
|
||||||
|
arguments=["open", "--task", "--all", "-df"],
|
||||||
|
prefix=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
_status_matcher.shortcut(
|
||||||
|
r"关闭(所有|全部)默认群被动",
|
||||||
|
command="switch",
|
||||||
|
arguments=["close", "--task", "--all", "-df"],
|
||||||
|
prefix=True,
|
||||||
|
)
|
||||||
|
|
||||||
_status_matcher.shortcut(
|
_status_matcher.shortcut(
|
||||||
r"开启群被动\s*(?P<name>.+)",
|
r"开启群被动\s*(?P<name>.+)",
|
||||||
@@ -73,6 +86,20 @@ _status_matcher.shortcut(
|
|||||||
prefix=True,
|
prefix=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_status_matcher.shortcut(
|
||||||
|
r"开启默认群被动\s*(?P<name>.+)",
|
||||||
|
command="switch",
|
||||||
|
arguments=["open", "{name}", "--task", "-df"],
|
||||||
|
prefix=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
_status_matcher.shortcut(
|
||||||
|
r"关闭默认群被动\s*(?P<name>.+)",
|
||||||
|
command="switch",
|
||||||
|
arguments=["close", "{name}", "--task", "-df"],
|
||||||
|
prefix=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
_status_matcher.shortcut(
|
_status_matcher.shortcut(
|
||||||
r"开启(所有|全部)群被动",
|
r"开启(所有|全部)群被动",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ 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"
|
||||||
@@ -91,7 +92,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)
|
||||||
|
|
||||||
|
|
||||||
@driver.on_startup
|
@PriorityLifecycle.on_startup(priority=5)
|
||||||
def _():
|
def _():
|
||||||
"""数据迁移
|
"""数据迁移
|
||||||
|
|
||||||
|
|||||||
@@ -11,18 +11,14 @@ from nonebot_plugin_alconna import (
|
|||||||
on_alconna,
|
on_alconna,
|
||||||
store_true,
|
store_true,
|
||||||
)
|
)
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
|
|
||||||
from zhenxun.configs.utils import PluginExtraData
|
from zhenxun.configs.utils import PluginExtraData
|
||||||
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.manager.resource_manager import (
|
|
||||||
DownloadResourceException,
|
|
||||||
ResourceManager,
|
|
||||||
)
|
|
||||||
from zhenxun.utils.message import MessageUtils
|
from zhenxun.utils.message import MessageUtils
|
||||||
|
|
||||||
from ._data_source import UpdateManage
|
from ._data_source import UpdateManager
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="自动更新",
|
name="自动更新",
|
||||||
@@ -32,16 +28,26 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
检查更新真寻最新版本,包括了自动更新
|
检查更新真寻最新版本,包括了自动更新
|
||||||
资源文件大小一般在130mb左右,除非必须更新一般仅更新代码文件
|
资源文件大小一般在130mb左右,除非必须更新一般仅更新代码文件
|
||||||
指令:
|
指令:
|
||||||
检查更新 [main|release|resource] ?[-r]
|
检查更新 [main|release|resource|webui] ?[-r] ?[-f] ?[-z] ?[-t]
|
||||||
main: main分支
|
main: main分支
|
||||||
release: 最新release
|
release: 最新release
|
||||||
resource: 资源文件
|
resource: 资源文件
|
||||||
|
webui: webui文件
|
||||||
-r: 下载资源文件,一般在更新main或release时使用
|
-r: 下载资源文件,一般在更新main或release时使用
|
||||||
|
-f: 强制更新,一般用于更新main时使用(仅git更新时有效)
|
||||||
|
-s: 更新源,为 git 或 ali(默认使用ali)
|
||||||
|
-z: 下载zip文件进行更新(仅git有效)
|
||||||
|
-t: 更新方式,git或download(默认使用git)
|
||||||
|
git: 使用git pull(推荐)
|
||||||
|
download: 通过commit hash比较文件后下载更新(仅git有效)
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
检查更新 main
|
检查更新 main
|
||||||
检查更新 main -r
|
检查更新 main -r
|
||||||
|
检查更新 main -f
|
||||||
检查更新 release -r
|
检查更新 release -r
|
||||||
检查更新 resource
|
检查更新 resource
|
||||||
|
检查更新 webui
|
||||||
""".strip(),
|
""".strip(),
|
||||||
extra=PluginExtraData(
|
extra=PluginExtraData(
|
||||||
author="HibiKier",
|
author="HibiKier",
|
||||||
@@ -53,8 +59,11 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
_matcher = on_alconna(
|
_matcher = on_alconna(
|
||||||
Alconna(
|
Alconna(
|
||||||
"检查更新",
|
"检查更新",
|
||||||
Args["ver_type?", ["main", "release", "resource"]],
|
Args["ver_type?", ["main", "release", "resource", "webui"]],
|
||||||
Option("-r|--resource", action=store_true, help_text="下载资源文件"),
|
Option("-r|--resource", action=store_true, help_text="下载资源文件"),
|
||||||
|
Option("-f|--force", action=store_true, help_text="强制更新"),
|
||||||
|
Option("-s", Args["source?", ["git", "ali"]], help_text="更新源"),
|
||||||
|
Option("-z|--zip", action=store_true, help_text="下载zip文件"),
|
||||||
),
|
),
|
||||||
priority=1,
|
priority=1,
|
||||||
block=True,
|
block=True,
|
||||||
@@ -66,32 +75,61 @@ _matcher = on_alconna(
|
|||||||
@_matcher.handle()
|
@_matcher.handle()
|
||||||
async def _(
|
async def _(
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
session: EventSession,
|
session: Uninfo,
|
||||||
ver_type: Match[str],
|
ver_type: Match[str],
|
||||||
resource: Query[bool] = Query("resource", False),
|
resource: Query[bool] = Query("resource", False),
|
||||||
|
force: Query[bool] = Query("force", False),
|
||||||
|
source: Query[str] = Query("source", "ali"),
|
||||||
|
zip: Query[bool] = Query("zip", False),
|
||||||
):
|
):
|
||||||
if not session.id1:
|
|
||||||
await MessageUtils.build_message("用户id为空...").finish()
|
|
||||||
result = ""
|
result = ""
|
||||||
if ver_type.result in {"main", "release"}:
|
await MessageUtils.build_message("正在进行检查更新...").send(reply_to=True)
|
||||||
|
|
||||||
if not ver_type.available:
|
if not ver_type.available:
|
||||||
result = await UpdateManage.check_version()
|
result += await UpdateManager.check_version()
|
||||||
logger.info("查看当前版本...", "检查更新", session=session)
|
logger.info("查看当前版本...", "检查更新", session=session)
|
||||||
await MessageUtils.build_message(result).finish()
|
await MessageUtils.build_message(result).finish()
|
||||||
|
return
|
||||||
|
|
||||||
|
ver_type_str = ver_type.result
|
||||||
|
source_str = source.result
|
||||||
|
if ver_type_str in {"main", "release"}:
|
||||||
try:
|
try:
|
||||||
result = await UpdateManage.update(bot, session.id1, ver_type.result)
|
result += await UpdateManager.update_zhenxun(
|
||||||
|
bot,
|
||||||
|
session.user.id,
|
||||||
|
ver_type_str, # type: ignore
|
||||||
|
force.result,
|
||||||
|
source_str, # type: ignore
|
||||||
|
zip.result,
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("版本更新失败...", "检查更新", session=session, e=e)
|
logger.error("版本更新失败...", "检查更新", session=session, e=e)
|
||||||
await MessageUtils.build_message(f"更新版本失败...e: {e}").finish()
|
await MessageUtils.build_message(f"更新版本失败...e: {e}").finish()
|
||||||
|
elif ver_type.result == "webui":
|
||||||
|
if zip.result:
|
||||||
|
source_str = None
|
||||||
|
try:
|
||||||
|
result += await UpdateManager.update_webui(
|
||||||
|
source_str, # type: ignore
|
||||||
|
"test",
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("WebUI更新失败...", "检查更新", session=session, e=e)
|
||||||
|
result += "\nWebUI更新错误..."
|
||||||
if resource.result or ver_type.result == "resource":
|
if resource.result or ver_type.result == "resource":
|
||||||
try:
|
try:
|
||||||
await ResourceManager.init_resources(True)
|
if zip.result:
|
||||||
result += "\n资源文件更新成功!"
|
source_str = None
|
||||||
except DownloadResourceException:
|
result += await UpdateManager.update_resources(
|
||||||
result += "\n资源更新下载失败..."
|
source_str, # type: ignore
|
||||||
|
"main",
|
||||||
|
force.result,
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("资源更新下载失败...", "检查更新", session=session, e=e)
|
logger.error("资源更新下载失败...", "检查更新", session=session, e=e)
|
||||||
result += "\n资源更新未知错误..."
|
result += "\n资源更新错误..."
|
||||||
if result:
|
if result:
|
||||||
await MessageUtils.build_message(result.strip()).finish()
|
await MessageUtils.build_message(result.strip()).finish()
|
||||||
await MessageUtils.build_message("更新版本失败...").finish()
|
await MessageUtils.build_message("更新版本失败...").finish()
|
||||||
|
|||||||
@@ -1,217 +1,307 @@
|
|||||||
import os
|
import asyncio
|
||||||
import shutil
|
from typing import Literal
|
||||||
import subprocess
|
|
||||||
import tarfile
|
|
||||||
import zipfile
|
|
||||||
|
|
||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
from nonebot.utils import run_sync
|
from packaging.specifiers import SpecifierSet
|
||||||
|
from packaging.version import InvalidVersion, Version
|
||||||
|
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
from zhenxun.utils.github_utils import GithubUtils
|
|
||||||
from zhenxun.utils.github_utils.models import RepoInfo
|
|
||||||
from zhenxun.utils.http_utils import AsyncHttpx
|
from zhenxun.utils.http_utils import AsyncHttpx
|
||||||
|
from zhenxun.utils.manager.virtual_env_package_manager import VirtualEnvPackageManager
|
||||||
|
from zhenxun.utils.manager.zhenxun_repo_manager import (
|
||||||
|
ZhenxunRepoConfig,
|
||||||
|
ZhenxunRepoManager,
|
||||||
|
)
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
from zhenxun.utils.platform import PlatformUtils
|
||||||
|
from zhenxun.utils.repo_utils import RepoFileManager
|
||||||
|
|
||||||
from .config import (
|
LOG_COMMAND = "AutoUpdate"
|
||||||
BACKUP_PATH,
|
|
||||||
BASE_PATH,
|
|
||||||
BASE_PATH_STRING,
|
|
||||||
DEFAULT_GITHUB_URL,
|
|
||||||
DOWNLOAD_GZ_FILE,
|
|
||||||
DOWNLOAD_ZIP_FILE,
|
|
||||||
PYPROJECT_FILE,
|
|
||||||
PYPROJECT_FILE_STRING,
|
|
||||||
PYPROJECT_LOCK_FILE,
|
|
||||||
PYPROJECT_LOCK_FILE_STRING,
|
|
||||||
RELEASE_URL,
|
|
||||||
REPLACE_FOLDERS,
|
|
||||||
REQ_TXT_FILE,
|
|
||||||
REQ_TXT_FILE_STRING,
|
|
||||||
TMP_PATH,
|
|
||||||
VERSION_FILE,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def install_requirement():
|
class UpdateManager:
|
||||||
requirement_path = (REQ_TXT_FILE).absolute()
|
@staticmethod
|
||||||
|
async def _get_latest_commit_date(owner: str, repo: str, path: str) -> str:
|
||||||
if not requirement_path.exists():
|
"""获取文件最新 commit 日期"""
|
||||||
logger.debug(
|
api_url = f"https://api.github.com/repos/{owner}/{repo}/commits"
|
||||||
f"没有找到zhenxun的requirement.txt,目标路径为{requirement_path}", "插件管理"
|
params = {"path": path, "page": 1, "per_page": 1}
|
||||||
)
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
data = await AsyncHttpx.get_json(api_url, params=params)
|
||||||
["pip", "install", "-r", str(requirement_path)],
|
if data and isinstance(data, list) and data[0]:
|
||||||
check=True,
|
date_str = data[0]["commit"]["committer"]["date"]
|
||||||
capture_output=True,
|
return date_str.split("T")[0]
|
||||||
text=True,
|
except Exception as e:
|
||||||
)
|
logger.warning(f"获取 {owner}/{repo}/{path} 的 commit 日期失败", e=e)
|
||||||
logger.debug(f"成功安装真寻依赖,日志:\n{result.stdout}", "插件管理")
|
return "获取失败"
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
logger.error(f"安装真寻依赖失败,错误:\n{e.stderr}", "插件管理", e=e)
|
|
||||||
|
|
||||||
|
|
||||||
@run_sync
|
|
||||||
def _file_handle(latest_version: str | None):
|
|
||||||
"""文件移动操作
|
|
||||||
|
|
||||||
参数:
|
|
||||||
latest_version: 版本号
|
|
||||||
"""
|
|
||||||
BACKUP_PATH.mkdir(exist_ok=True, parents=True)
|
|
||||||
logger.debug("开始解压文件压缩包...", "检查更新")
|
|
||||||
download_file = DOWNLOAD_GZ_FILE
|
|
||||||
if DOWNLOAD_GZ_FILE.exists():
|
|
||||||
tf = tarfile.open(DOWNLOAD_GZ_FILE)
|
|
||||||
else:
|
|
||||||
download_file = DOWNLOAD_ZIP_FILE
|
|
||||||
tf = zipfile.ZipFile(DOWNLOAD_ZIP_FILE)
|
|
||||||
tf.extractall(TMP_PATH)
|
|
||||||
logger.debug("解压文件压缩包完成...", "检查更新")
|
|
||||||
download_file_path = TMP_PATH / next(
|
|
||||||
x for x in os.listdir(TMP_PATH) if (TMP_PATH / x).is_dir()
|
|
||||||
)
|
|
||||||
_pyproject = download_file_path / PYPROJECT_FILE_STRING
|
|
||||||
_lock_file = download_file_path / PYPROJECT_LOCK_FILE_STRING
|
|
||||||
_req_file = download_file_path / REQ_TXT_FILE_STRING
|
|
||||||
extract_path = download_file_path / BASE_PATH_STRING
|
|
||||||
target_path = BASE_PATH
|
|
||||||
if PYPROJECT_FILE.exists():
|
|
||||||
logger.debug(f"移除备份文件: {PYPROJECT_FILE}", "检查更新")
|
|
||||||
shutil.move(PYPROJECT_FILE, BACKUP_PATH / PYPROJECT_FILE_STRING)
|
|
||||||
if PYPROJECT_LOCK_FILE.exists():
|
|
||||||
logger.debug(f"移除备份文件: {PYPROJECT_LOCK_FILE}", "检查更新")
|
|
||||||
shutil.move(PYPROJECT_LOCK_FILE, BACKUP_PATH / PYPROJECT_LOCK_FILE_STRING)
|
|
||||||
if REQ_TXT_FILE.exists():
|
|
||||||
logger.debug(f"移除备份文件: {REQ_TXT_FILE}", "检查更新")
|
|
||||||
shutil.move(REQ_TXT_FILE, BACKUP_PATH / REQ_TXT_FILE_STRING)
|
|
||||||
if _pyproject.exists():
|
|
||||||
logger.debug("移动文件: pyproject.toml", "检查更新")
|
|
||||||
shutil.move(_pyproject, PYPROJECT_FILE)
|
|
||||||
if _lock_file.exists():
|
|
||||||
logger.debug("移动文件: poetry.lock", "检查更新")
|
|
||||||
shutil.move(_lock_file, PYPROJECT_LOCK_FILE)
|
|
||||||
if _req_file.exists():
|
|
||||||
logger.debug("移动文件: requirements.txt", "检查更新")
|
|
||||||
shutil.move(_req_file, REQ_TXT_FILE)
|
|
||||||
for folder in REPLACE_FOLDERS:
|
|
||||||
"""移动指定文件夹"""
|
|
||||||
_dir = BASE_PATH / folder
|
|
||||||
_backup_dir = BACKUP_PATH / folder
|
|
||||||
if _backup_dir.exists():
|
|
||||||
logger.debug(f"删除备份文件夹 {_backup_dir}", "检查更新")
|
|
||||||
shutil.rmtree(_backup_dir)
|
|
||||||
if _dir.exists():
|
|
||||||
logger.debug(f"移动旧文件夹 {_dir}", "检查更新")
|
|
||||||
shutil.move(_dir, _backup_dir)
|
|
||||||
else:
|
|
||||||
logger.warning(f"文件夹 {_dir} 不存在,跳过删除", "检查更新")
|
|
||||||
for folder in REPLACE_FOLDERS:
|
|
||||||
src_folder_path = extract_path / folder
|
|
||||||
dest_folder_path = target_path / folder
|
|
||||||
if src_folder_path.exists():
|
|
||||||
logger.debug(
|
|
||||||
f"移动文件夹: {src_folder_path} -> {dest_folder_path}", "检查更新"
|
|
||||||
)
|
|
||||||
shutil.move(src_folder_path, dest_folder_path)
|
|
||||||
else:
|
|
||||||
logger.debug(f"源文件夹不存在: {src_folder_path}", "检查更新")
|
|
||||||
if tf:
|
|
||||||
tf.close()
|
|
||||||
if download_file.exists():
|
|
||||||
logger.debug(f"删除下载文件: {download_file}", "检查更新")
|
|
||||||
download_file.unlink()
|
|
||||||
if extract_path.exists():
|
|
||||||
logger.debug(f"删除解压文件夹: {extract_path}", "检查更新")
|
|
||||||
shutil.rmtree(extract_path)
|
|
||||||
if TMP_PATH.exists():
|
|
||||||
shutil.rmtree(TMP_PATH)
|
|
||||||
if latest_version:
|
|
||||||
with open(VERSION_FILE, "w", encoding="utf8") as f:
|
|
||||||
f.write(f"__version__: {latest_version}")
|
|
||||||
install_requirement()
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateManage:
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def check_version(cls) -> str:
|
async def check_version(cls) -> str:
|
||||||
"""检查更新版本
|
"""检查真寻和资源的版本"""
|
||||||
|
bot_cur_version = cls.__get_version()
|
||||||
|
|
||||||
返回:
|
release_task = ZhenxunRepoManager.zhenxun_get_latest_releases_data()
|
||||||
str: 更新信息
|
dev_version_task = RepoFileManager.get_file_content(
|
||||||
"""
|
ZhenxunRepoConfig.ZHENXUN_BOT_GITHUB_URL, "__version__"
|
||||||
cur_version = cls.__get_version()
|
)
|
||||||
data = await cls.__get_latest_data()
|
bot_commit_date_task = cls._get_latest_commit_date(
|
||||||
if not data:
|
"HibiKier", "zhenxun_bot", "__version__"
|
||||||
return "检查更新获取版本失败..."
|
)
|
||||||
return (
|
res_commit_date_task = cls._get_latest_commit_date(
|
||||||
"检测到当前版本更新\n"
|
"zhenxun-org", "zhenxun-bot-resources", "__version__"
|
||||||
f"当前版本:{cur_version}\n"
|
|
||||||
f"最新版本:{data.get('name')}\n"
|
|
||||||
f"创建日期:{data.get('created_at')}\n"
|
|
||||||
f"更新内容:\n{data.get('body')}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
release_data,
|
||||||
|
dev_version_text,
|
||||||
|
bot_commit_date,
|
||||||
|
res_commit_date,
|
||||||
|
) = await asyncio.gather(
|
||||||
|
release_task,
|
||||||
|
dev_version_task,
|
||||||
|
bot_commit_date_task,
|
||||||
|
res_commit_date_task,
|
||||||
|
return_exceptions=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(release_data, dict):
|
||||||
|
bot_release_version = release_data.get("name", "获取失败")
|
||||||
|
bot_release_date = release_data.get("created_at", "").split("T")[0]
|
||||||
|
else:
|
||||||
|
bot_release_version = "获取失败"
|
||||||
|
bot_release_date = "获取失败"
|
||||||
|
logger.warning(f"获取 Bot release 信息失败: {release_data}")
|
||||||
|
|
||||||
|
if isinstance(dev_version_text, str):
|
||||||
|
bot_dev_version = dev_version_text.split(":")[-1].strip()
|
||||||
|
else:
|
||||||
|
bot_dev_version = "获取失败"
|
||||||
|
bot_commit_date = "获取失败"
|
||||||
|
logger.warning(f"获取 Bot dev 版本信息失败: {dev_version_text}")
|
||||||
|
|
||||||
|
bot_update_hint = ""
|
||||||
|
try:
|
||||||
|
cur_base_v = bot_cur_version.split("-")[0].lstrip("v")
|
||||||
|
dev_base_v = bot_dev_version.split("-")[0].lstrip("v")
|
||||||
|
|
||||||
|
if Version(cur_base_v) < Version(dev_base_v):
|
||||||
|
bot_update_hint = "\n-> 发现新开发版本, 可用 `检查更新 main` 更新"
|
||||||
|
elif (
|
||||||
|
Version(cur_base_v) == Version(dev_base_v)
|
||||||
|
and bot_cur_version != bot_dev_version
|
||||||
|
):
|
||||||
|
bot_update_hint = "\n-> 发现新开发版本, 可用 `检查更新 main` 更新"
|
||||||
|
except (InvalidVersion, TypeError, IndexError):
|
||||||
|
if bot_cur_version != bot_dev_version and bot_dev_version != "获取失败":
|
||||||
|
bot_update_hint = "\n-> 发现新开发版本, 可用 `检查更新 main` 更新"
|
||||||
|
|
||||||
|
bot_update_info = (
|
||||||
|
f"当前版本: {bot_cur_version}\n"
|
||||||
|
f"最新开发版: {bot_dev_version} (更新于: {bot_commit_date})\n"
|
||||||
|
f"最新正式版: {bot_release_version} (发布于: {bot_release_date})"
|
||||||
|
f"{bot_update_hint}"
|
||||||
|
)
|
||||||
|
|
||||||
|
res_version_file = ZhenxunRepoConfig.RESOURCE_PATH / "__version__"
|
||||||
|
res_cur_version = "未找到"
|
||||||
|
if res_version_file.exists():
|
||||||
|
if text := res_version_file.open(encoding="utf8").readline():
|
||||||
|
res_cur_version = text.split(":")[-1].strip()
|
||||||
|
|
||||||
|
res_latest_version = "获取失败"
|
||||||
|
try:
|
||||||
|
res_latest_version_text = await RepoFileManager.get_file_content(
|
||||||
|
ZhenxunRepoConfig.RESOURCE_GITHUB_URL, "__version__"
|
||||||
|
)
|
||||||
|
res_latest_version = res_latest_version_text.split(":")[-1].strip()
|
||||||
|
except Exception as e:
|
||||||
|
res_commit_date = "获取失败"
|
||||||
|
logger.warning(f"获取资源版本信息失败: {e}")
|
||||||
|
|
||||||
|
res_update_hint = ""
|
||||||
|
try:
|
||||||
|
if Version(res_cur_version) < Version(res_latest_version):
|
||||||
|
res_update_hint = "\n-> 发现新资源版本, 可用 `检查更新 resource` 更新"
|
||||||
|
except (InvalidVersion, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
res_update_info = (
|
||||||
|
f"当前版本: {res_cur_version}\n"
|
||||||
|
f"最新版本: {res_latest_version} (更新于: {res_commit_date})"
|
||||||
|
f"{res_update_hint}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return f"『绪山真寻 Bot』\n{bot_update_info}\n\n『真寻资源』\n{res_update_info}"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def update(cls, bot: Bot, user_id: str, version_type: str) -> str:
|
async def update_webui(
|
||||||
|
cls,
|
||||||
|
source: Literal["git", "ali"] | None,
|
||||||
|
branch: str = "dist",
|
||||||
|
force: bool = False,
|
||||||
|
):
|
||||||
|
"""更新WebUI
|
||||||
|
|
||||||
|
参数:
|
||||||
|
source: 更新源
|
||||||
|
branch: 分支
|
||||||
|
force: 是否强制更新
|
||||||
|
|
||||||
|
返回:
|
||||||
|
str: 返回消息
|
||||||
|
"""
|
||||||
|
if not source:
|
||||||
|
await ZhenxunRepoManager.webui_zip_update()
|
||||||
|
return "WebUI更新完成!"
|
||||||
|
result = await ZhenxunRepoManager.webui_git_update(
|
||||||
|
source,
|
||||||
|
branch=branch,
|
||||||
|
force=force,
|
||||||
|
)
|
||||||
|
if not result.success:
|
||||||
|
logger.error(f"WebUI更新失败...错误: {result.error_message}", LOG_COMMAND)
|
||||||
|
return f"WebUI更新失败...错误: {result.error_message}"
|
||||||
|
return "WebUI更新完成!"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def update_resources(
|
||||||
|
cls,
|
||||||
|
source: Literal["git", "ali"] | None,
|
||||||
|
branch: str = "main",
|
||||||
|
force: bool = False,
|
||||||
|
) -> str:
|
||||||
|
"""更新资源
|
||||||
|
|
||||||
|
参数:
|
||||||
|
source: 更新源
|
||||||
|
branch: 分支
|
||||||
|
force: 是否强制更新
|
||||||
|
|
||||||
|
返回:
|
||||||
|
str: 返回消息
|
||||||
|
"""
|
||||||
|
if not source:
|
||||||
|
await ZhenxunRepoManager.resources_zip_update()
|
||||||
|
return "真寻资源更新完成!"
|
||||||
|
result = await ZhenxunRepoManager.resources_git_update(
|
||||||
|
source,
|
||||||
|
branch=branch,
|
||||||
|
force=force,
|
||||||
|
)
|
||||||
|
if not result.success:
|
||||||
|
logger.error(
|
||||||
|
f"真寻资源更新失败...错误: {result.error_message}", LOG_COMMAND
|
||||||
|
)
|
||||||
|
return f"真寻资源更新失败...错误: {result.error_message}"
|
||||||
|
return "真寻资源更新完成!"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def update_zhenxun(
|
||||||
|
cls,
|
||||||
|
bot: Bot,
|
||||||
|
user_id: str,
|
||||||
|
version_type: Literal["main", "release"],
|
||||||
|
force: bool,
|
||||||
|
source: Literal["git", "ali"],
|
||||||
|
zip: bool,
|
||||||
|
) -> str:
|
||||||
"""更新操作
|
"""更新操作
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
bot: Bot
|
bot: Bot
|
||||||
user_id: 用户id
|
user_id: 用户id
|
||||||
version_type: 更新版本类型
|
version_type: 更新版本类型
|
||||||
|
force: 是否强制更新
|
||||||
|
source: 更新源
|
||||||
|
zip: 是否下载zip文件
|
||||||
|
update_type: 更新方式
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
str | None: 返回消息
|
str | None: 返回消息
|
||||||
"""
|
"""
|
||||||
logger.info("开始下载真寻最新版文件....", "检查更新")
|
|
||||||
cur_version = cls.__get_version()
|
cur_version = cls.__get_version()
|
||||||
url = None
|
|
||||||
new_version = None
|
|
||||||
repo_info = GithubUtils.parse_github_url(DEFAULT_GITHUB_URL)
|
|
||||||
if version_type in {"main"}:
|
|
||||||
repo_info.branch = version_type
|
|
||||||
new_version = await cls.__get_version_from_repo(repo_info)
|
|
||||||
if new_version:
|
|
||||||
new_version = new_version.split(":")[-1].strip()
|
|
||||||
url = await repo_info.get_archive_download_urls()
|
|
||||||
elif version_type == "release":
|
|
||||||
data = await cls.__get_latest_data()
|
|
||||||
if not data:
|
|
||||||
return "获取更新版本失败..."
|
|
||||||
new_version = data.get("name", "")
|
|
||||||
url = await repo_info.get_release_source_download_urls_tgz(new_version)
|
|
||||||
if not url:
|
|
||||||
return "获取版本下载链接失败..."
|
|
||||||
if TMP_PATH.exists():
|
|
||||||
logger.debug(f"删除临时文件夹 {TMP_PATH}", "检查更新")
|
|
||||||
shutil.rmtree(TMP_PATH)
|
|
||||||
logger.debug(
|
|
||||||
f"开始更新版本:{cur_version} -> {new_version} | 下载链接:{url}",
|
|
||||||
"检查更新",
|
|
||||||
)
|
|
||||||
await PlatformUtils.send_superuser(
|
await PlatformUtils.send_superuser(
|
||||||
bot,
|
bot,
|
||||||
f"检测真寻已更新,版本更新:{cur_version} -> {new_version}\n开始更新...",
|
f"检测真寻已更新,当前版本:{cur_version}\n开始更新...",
|
||||||
user_id,
|
user_id,
|
||||||
)
|
)
|
||||||
download_file = (
|
result_message = ""
|
||||||
DOWNLOAD_GZ_FILE if version_type == "release" else DOWNLOAD_ZIP_FILE
|
if zip:
|
||||||
|
new_version = await ZhenxunRepoManager.zhenxun_zip_update(version_type)
|
||||||
|
await PlatformUtils.send_superuser(
|
||||||
|
bot, "真寻更新完成,开始安装依赖...", user_id
|
||||||
)
|
)
|
||||||
if await AsyncHttpx.download_file(url, download_file, stream=True):
|
await VirtualEnvPackageManager.install_requirement(
|
||||||
logger.debug("下载真寻最新版文件完成...", "检查更新")
|
ZhenxunRepoConfig.REQUIREMENTS_FILE
|
||||||
await _file_handle(new_version)
|
)
|
||||||
result = "版本更新完成"
|
result_message = (
|
||||||
return (
|
f"版本更新完成!\n版本: {cur_version} -> {new_version}\n"
|
||||||
f"{result}\n"
|
|
||||||
f"版本: {cur_version} -> {new_version}\n"
|
|
||||||
"请重新启动真寻以完成更新!"
|
"请重新启动真寻以完成更新!"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.debug("下载真寻最新版文件失败...", "检查更新")
|
result = await ZhenxunRepoManager.zhenxun_git_update(
|
||||||
return ""
|
source,
|
||||||
|
branch=version_type,
|
||||||
|
force=force,
|
||||||
|
)
|
||||||
|
if not result.success:
|
||||||
|
logger.error(
|
||||||
|
f"真寻版本更新失败...错误: {result.error_message}",
|
||||||
|
LOG_COMMAND,
|
||||||
|
)
|
||||||
|
return f"版本更新失败...错误: {result.error_message}"
|
||||||
|
await PlatformUtils.send_superuser(
|
||||||
|
bot, "真寻更新完成,开始安装依赖...", user_id
|
||||||
|
)
|
||||||
|
await VirtualEnvPackageManager.install_requirement(
|
||||||
|
ZhenxunRepoConfig.REQUIREMENTS_FILE
|
||||||
|
)
|
||||||
|
result_message = (
|
||||||
|
f"版本更新完成!\n"
|
||||||
|
f"版本: {cur_version} -> {result.new_version}\n"
|
||||||
|
f"变更文件个数: {len(result.changed_files)}"
|
||||||
|
f"{'' if source == 'git' else '(阿里云更新不支持查看变更文件)'}\n"
|
||||||
|
"请重新启动真寻以完成更新!"
|
||||||
|
)
|
||||||
|
resource_warning = ""
|
||||||
|
if version_type == "main":
|
||||||
|
try:
|
||||||
|
spec_content = await RepoFileManager.get_file_content(
|
||||||
|
ZhenxunRepoConfig.ZHENXUN_BOT_GITHUB_URL, "resources.spec"
|
||||||
|
)
|
||||||
|
required_spec_str = None
|
||||||
|
for line in spec_content.splitlines():
|
||||||
|
if line.startswith("require_resources_version:"):
|
||||||
|
required_spec_str = line.split(":", 1)[1].strip().strip("\"'")
|
||||||
|
break
|
||||||
|
if required_spec_str:
|
||||||
|
res_version_file = ZhenxunRepoConfig.RESOURCE_PATH / "__version__"
|
||||||
|
local_res_version_str = "0.0.0"
|
||||||
|
if res_version_file.exists():
|
||||||
|
if text := res_version_file.open(encoding="utf8").readline():
|
||||||
|
local_res_version_str = text.split(":")[-1].strip()
|
||||||
|
|
||||||
|
spec = SpecifierSet(required_spec_str)
|
||||||
|
local_ver = Version(local_res_version_str)
|
||||||
|
if not spec.contains(local_ver):
|
||||||
|
warning_header = (
|
||||||
|
f"⚠️ **资源版本不兼容!**\n"
|
||||||
|
f"当前代码需要资源版本: `{required_spec_str}`\n"
|
||||||
|
f"您当前的资源版本是: `{local_res_version_str}`\n"
|
||||||
|
"**将自动为您更新资源文件...**"
|
||||||
|
)
|
||||||
|
await PlatformUtils.send_superuser(bot, warning_header, user_id)
|
||||||
|
resource_update_source = None if zip else source
|
||||||
|
resource_update_result = await cls.update_resources(
|
||||||
|
source=resource_update_source, force=force
|
||||||
|
)
|
||||||
|
resource_warning = (
|
||||||
|
f"\n\n{warning_header}\n{resource_update_result}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"检查资源版本兼容性时出错: {e}", LOG_COMMAND, e=e)
|
||||||
|
resource_warning = (
|
||||||
|
"\n\n⚠️ 检查资源版本兼容性时出错,建议手动运行 `检查更新 resource`"
|
||||||
|
)
|
||||||
|
return result_message + resource_warning
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __get_version(cls) -> str:
|
def __get_version(cls) -> str:
|
||||||
@@ -221,44 +311,9 @@ class UpdateManage:
|
|||||||
str: 当前版本号
|
str: 当前版本号
|
||||||
"""
|
"""
|
||||||
_version = "v0.0.0"
|
_version = "v0.0.0"
|
||||||
if VERSION_FILE.exists():
|
if ZhenxunRepoConfig.ZHENXUN_BOT_VERSION_FILE.exists():
|
||||||
if text := VERSION_FILE.open(encoding="utf8").readline():
|
if text := ZhenxunRepoConfig.ZHENXUN_BOT_VERSION_FILE.open(
|
||||||
|
encoding="utf8"
|
||||||
|
).readline():
|
||||||
_version = text.split(":")[-1].strip()
|
_version = text.split(":")[-1].strip()
|
||||||
return _version
|
return _version
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def __get_latest_data(cls) -> dict:
|
|
||||||
"""获取最新版本信息
|
|
||||||
|
|
||||||
返回:
|
|
||||||
dict: 最新版本数据
|
|
||||||
"""
|
|
||||||
for _ in range(3):
|
|
||||||
try:
|
|
||||||
res = await AsyncHttpx.get(RELEASE_URL)
|
|
||||||
if res.status_code == 200:
|
|
||||||
return res.json()
|
|
||||||
except TimeoutError:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("检查更新真寻获取版本失败", e=e)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def __get_version_from_repo(cls, repo_info: RepoInfo) -> str:
|
|
||||||
"""从指定分支获取版本号
|
|
||||||
|
|
||||||
参数:
|
|
||||||
branch: 分支名称
|
|
||||||
|
|
||||||
返回:
|
|
||||||
str: 版本号
|
|
||||||
"""
|
|
||||||
version_url = await repo_info.get_raw_download_urls(path="__version__")
|
|
||||||
try:
|
|
||||||
res = await AsyncHttpx.get(version_url)
|
|
||||||
if res.status_code == 200:
|
|
||||||
return res.text.strip()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"获取 {repo_info.branch} 分支版本失败", e=e)
|
|
||||||
return "未知版本"
|
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from zhenxun.configs.path_config import TEMP_PATH
|
|
||||||
|
|
||||||
DEFAULT_GITHUB_URL = "https://github.com/HibiKier/zhenxun_bot/tree/main"
|
|
||||||
RELEASE_URL = "https://api.github.com/repos/HibiKier/zhenxun_bot/releases/latest"
|
|
||||||
|
|
||||||
VERSION_FILE_STRING = "__version__"
|
|
||||||
VERSION_FILE = Path() / VERSION_FILE_STRING
|
|
||||||
|
|
||||||
PYPROJECT_FILE_STRING = "pyproject.toml"
|
|
||||||
PYPROJECT_FILE = Path() / PYPROJECT_FILE_STRING
|
|
||||||
PYPROJECT_LOCK_FILE_STRING = "poetry.lock"
|
|
||||||
PYPROJECT_LOCK_FILE = Path() / PYPROJECT_LOCK_FILE_STRING
|
|
||||||
REQ_TXT_FILE_STRING = "requirements.txt"
|
|
||||||
REQ_TXT_FILE = Path() / REQ_TXT_FILE_STRING
|
|
||||||
|
|
||||||
BASE_PATH_STRING = "zhenxun"
|
|
||||||
BASE_PATH = Path() / BASE_PATH_STRING
|
|
||||||
|
|
||||||
TMP_PATH = TEMP_PATH / "auto_update"
|
|
||||||
|
|
||||||
BACKUP_PATH = Path() / "backup"
|
|
||||||
|
|
||||||
DOWNLOAD_GZ_FILE_STRING = "download_latest_file.tar.gz"
|
|
||||||
DOWNLOAD_ZIP_FILE_STRING = "download_latest_file.zip"
|
|
||||||
DOWNLOAD_GZ_FILE = TMP_PATH / DOWNLOAD_GZ_FILE_STRING
|
|
||||||
DOWNLOAD_ZIP_FILE = TMP_PATH / DOWNLOAD_ZIP_FILE_STRING
|
|
||||||
|
|
||||||
REPLACE_FOLDERS = [
|
|
||||||
"builtin_plugins",
|
|
||||||
"services",
|
|
||||||
"utils",
|
|
||||||
"models",
|
|
||||||
"configs",
|
|
||||||
]
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
from nonebot.permission import SUPERUSER
|
||||||
|
from nonebot.plugin import PluginMetadata
|
||||||
|
from nonebot.rule import to_me
|
||||||
|
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
|
||||||
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
|
|
||||||
|
from zhenxun.configs.config import BotConfig
|
||||||
|
from zhenxun.configs.utils import PluginExtraData
|
||||||
|
from zhenxun.services.log import logger
|
||||||
|
from zhenxun.utils.manager.bot_profile_manager import BotProfileManager
|
||||||
|
from zhenxun.utils.message import MessageUtils
|
||||||
|
|
||||||
|
__plugin_meta__ = PluginMetadata(
|
||||||
|
name="自我介绍",
|
||||||
|
description=f"这是{BotConfig.self_nickname}的深情告白",
|
||||||
|
usage="""
|
||||||
|
指令:
|
||||||
|
自我介绍
|
||||||
|
""".strip(),
|
||||||
|
extra=PluginExtraData(
|
||||||
|
author="HibiKier",
|
||||||
|
version="0.1",
|
||||||
|
menu_type="其他",
|
||||||
|
superuser_help="""
|
||||||
|
在data/bot_profile/bot_id/profile.txt 中编辑BOT自我介绍
|
||||||
|
在data/bot_profile/bot_id/bot_id.png 中编辑BOT头像
|
||||||
|
指令:
|
||||||
|
重载自我介绍
|
||||||
|
""".strip(),
|
||||||
|
).to_dict(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_matcher = on_alconna(Alconna("自我介绍"), priority=5, block=True, rule=to_me())
|
||||||
|
|
||||||
|
_reload_matcher = on_alconna(
|
||||||
|
Alconna("重载自我介绍"), priority=1, block=True, permission=SUPERUSER
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@_matcher.handle()
|
||||||
|
async def _(session: Uninfo, arparma: Arparma):
|
||||||
|
file_path = await BotProfileManager.build_bot_profile_image(session.self_id)
|
||||||
|
if not file_path:
|
||||||
|
await MessageUtils.build_message(
|
||||||
|
f"{BotConfig.self_nickname}当前没有自我简介哦"
|
||||||
|
).finish(reply_to=True)
|
||||||
|
await MessageUtils.build_message(file_path).send()
|
||||||
|
logger.info("BOT自我介绍", arparma.header_result, session=session)
|
||||||
|
|
||||||
|
|
||||||
|
@_reload_matcher.handle()
|
||||||
|
async def _(session: Uninfo, arparma: Arparma):
|
||||||
|
BotProfileManager.clear_profile_image(session.self_id)
|
||||||
|
await MessageUtils.build_message(f"重载{BotConfig.self_nickname}自我介绍成功").send(
|
||||||
|
reply_to=True
|
||||||
|
)
|
||||||
|
logger.info("重载BOT自我介绍", arparma.header_result, session=session)
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
from nonebot import on_message
|
from nonebot import on_message
|
||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot_plugin_alconna import UniMsg
|
from nonebot_plugin_alconna import UniMsg
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_apscheduler import scheduler
|
||||||
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
|
|
||||||
from zhenxun.configs.config import Config
|
from zhenxun.configs.config import Config
|
||||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||||
from zhenxun.models.chat_history import ChatHistory
|
from zhenxun.models.chat_history import ChatHistory
|
||||||
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.utils import get_entity_ids
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="消息存储",
|
name="消息存储",
|
||||||
@@ -37,18 +39,34 @@ def rule(message: UniMsg) -> bool:
|
|||||||
|
|
||||||
chat_history = on_message(rule=rule, priority=1, block=False)
|
chat_history = on_message(rule=rule, priority=1, block=False)
|
||||||
|
|
||||||
|
TEMP_LIST = []
|
||||||
|
|
||||||
|
|
||||||
@chat_history.handle()
|
@chat_history.handle()
|
||||||
async def handle_message(message: UniMsg, session: EventSession):
|
async def _(message: UniMsg, session: Uninfo):
|
||||||
"""处理消息存储"""
|
entity = get_entity_ids(session)
|
||||||
try:
|
TEMP_LIST.append(
|
||||||
await ChatHistory.create(
|
ChatHistory(
|
||||||
user_id=session.id1,
|
user_id=entity.user_id,
|
||||||
group_id=session.id2,
|
group_id=entity.group_id,
|
||||||
text=str(message),
|
text=str(message),
|
||||||
plain_text=message.extract_plain_text(),
|
plain_text=message.extract_plain_text(),
|
||||||
bot_id=session.bot_id,
|
bot_id=session.self_id,
|
||||||
platform=session.platform,
|
platform=session.platform,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@scheduler.scheduled_job(
|
||||||
|
"interval",
|
||||||
|
minutes=1,
|
||||||
|
)
|
||||||
|
async def _():
|
||||||
|
try:
|
||||||
|
message_list = TEMP_LIST.copy()
|
||||||
|
TEMP_LIST.clear()
|
||||||
|
if message_list:
|
||||||
|
await ChatHistory.bulk_create(message_list)
|
||||||
|
logger.debug(f"批量添加聊天记录 {len(message_list)} 条", "定时任务")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("存储聊天记录失败", "chat_history", e=e)
|
logger.warning("存储聊天记录失败", "chat_history", e=e)
|
||||||
|
|||||||
@@ -14,12 +14,16 @@ from nonebot_plugin_alconna import (
|
|||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_session import EventSession
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from zhenxun.configs.utils import Command, PluginExtraData
|
from zhenxun import ui
|
||||||
|
from zhenxun.configs.config import Config
|
||||||
|
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 import avatar_service
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
|
from zhenxun.ui.builders import TableBuilder
|
||||||
|
from zhenxun.ui.models import ImageCell, TextCell
|
||||||
from zhenxun.utils.enum import PluginType
|
from zhenxun.utils.enum import PluginType
|
||||||
from zhenxun.utils.image_utils import ImageTemplate
|
|
||||||
from zhenxun.utils.message import MessageUtils
|
from zhenxun.utils.message import MessageUtils
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
@@ -27,22 +31,23 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
description="消息统计查询",
|
description="消息统计查询",
|
||||||
usage="""
|
usage="""
|
||||||
格式:
|
格式:
|
||||||
消息排行 ?[type [日,周,月,年]] ?[--des]
|
消息排行 ?[type [日,周,月,季,年]] ?[--des]
|
||||||
|
|
||||||
快捷:
|
快捷:
|
||||||
[日,周,月,年]消息排行 ?[数量]
|
[日,周,月,季,年]消息排行 ?[数量]
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
消息排行 : 所有记录排行
|
消息排行 : 所有记录排行
|
||||||
日消息排行 : 今日记录排行
|
日消息排行 : 今日记录排行
|
||||||
周消息排行 : 今日记录排行
|
周消息排行 : 本周记录排行
|
||||||
月消息排行 : 今日记录排行
|
月消息排行 : 本月记录排行
|
||||||
年消息排行 : 今日记录排行
|
季消息排行 : 本季度记录排行
|
||||||
|
年消息排行 : 本年记录排行
|
||||||
消息排行 周 --des : 逆序周记录排行
|
消息排行 周 --des : 逆序周记录排行
|
||||||
""".strip(),
|
""".strip(),
|
||||||
extra=PluginExtraData(
|
extra=PluginExtraData(
|
||||||
author="HibiKier",
|
author="HibiKier",
|
||||||
version="0.1",
|
version="0.2",
|
||||||
plugin_type=PluginType.NORMAL,
|
plugin_type=PluginType.NORMAL,
|
||||||
menu_type="数据统计",
|
menu_type="数据统计",
|
||||||
commands=[
|
commands=[
|
||||||
@@ -50,8 +55,19 @@ __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(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,7 +76,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,
|
||||||
@@ -68,7 +84,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,
|
||||||
@@ -96,49 +112,75 @@ 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)
|
||||||
column_name = ["名次", "昵称", "发言次数"]
|
elif date in ["季"]:
|
||||||
|
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, count.result, "DES" if arparma.find("des") else "DESC", date_scope
|
group_id, fetch_count, "DES" if arparma.find("des") else "DESC", date_scope
|
||||||
):
|
):
|
||||||
idx = 1
|
rows_data = []
|
||||||
data_list = []
|
platform = "qq"
|
||||||
for uid, num in rank_data:
|
|
||||||
if user := await GroupInfoUser.filter(
|
user_ids_in_rank = [str(uid) for uid, _ in rank_data]
|
||||||
user_id=uid, group_id=group_id
|
users_in_group_query = GroupInfoUser.filter(
|
||||||
).first():
|
user_id__in=user_ids_in_rank, group_id=group_id
|
||||||
user_name = user.user_name
|
)
|
||||||
else:
|
users_in_group = {u.user_id: u for u in await users_in_group_query}
|
||||||
user_name = uid
|
|
||||||
data_list.append([idx, user_name, num])
|
for idx, (uid, num) in enumerate(rank_data):
|
||||||
idx += 1
|
if len(rows_data) >= count.result:
|
||||||
|
break
|
||||||
|
|
||||||
|
uid_str = str(uid)
|
||||||
|
user_in_group = users_in_group.get(uid_str)
|
||||||
|
|
||||||
|
if not user_in_group and not show_quit_member:
|
||||||
|
continue
|
||||||
|
|
||||||
|
user_name = (
|
||||||
|
user_in_group.user_name if user_in_group else f"{uid_str}(已退群)"
|
||||||
|
)
|
||||||
|
|
||||||
|
avatar_path = await avatar_service.get_avatar_path(platform, uid_str)
|
||||||
|
|
||||||
|
rows_data.append(
|
||||||
|
[
|
||||||
|
TextCell(content=str(len(rows_data) + 1)),
|
||||||
|
ImageCell(
|
||||||
|
src=avatar_path.as_uri() if avatar_path else "", shape="circle"
|
||||||
|
),
|
||||||
|
TextCell(content=user_name),
|
||||||
|
TextCell(content=str(num), bold=True),
|
||||||
|
]
|
||||||
|
)
|
||||||
if not date_scope:
|
if not date_scope:
|
||||||
if date_scope := await ChatHistory.get_group_first_msg_datetime(group_id):
|
first_msg_time = await ChatHistory.get_group_first_msg_datetime(group_id)
|
||||||
date_scope = date_scope.astimezone(
|
if first_msg_time:
|
||||||
|
date_scope_start = first_msg_time.astimezone(
|
||||||
pytz.timezone("Asia/Shanghai")
|
pytz.timezone("Asia/Shanghai")
|
||||||
).replace(microsecond=0)
|
).replace(microsecond=0)
|
||||||
|
date_str = f"{str(date_scope_start).split('+')[0]} - 至今"
|
||||||
else:
|
else:
|
||||||
date_scope = time_now.replace(microsecond=0)
|
date_str = f"{time_now.replace(microsecond=0)} - 至今"
|
||||||
date_str = f"{str(date_scope).split('+')[0]} - 至今"
|
|
||||||
else:
|
else:
|
||||||
date_str = (
|
date_str = (
|
||||||
f"{date_scope[0].replace(microsecond=0)} - "
|
f"{date_scope[0].replace(microsecond=0)} - "
|
||||||
f"{date_scope[1].replace(microsecond=0)}"
|
f"{date_scope[1].replace(microsecond=0)}"
|
||||||
)
|
)
|
||||||
A = await ImageTemplate.table_page(
|
|
||||||
f"消息排行({count.result})", date_str, column_name, data_list
|
builder = TableBuilder(f"消息排行({count.result})", date_str)
|
||||||
)
|
builder.set_headers(column_name).add_rows(rows_data)
|
||||||
|
|
||||||
|
image_bytes = await ui.render(builder.build())
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"查看消息排行 数量={count.result}", arparma.header_result, session=session
|
f"查看消息排行 数量={count.result}", arparma.header_result, session=session
|
||||||
)
|
)
|
||||||
await MessageUtils.build_message(A).finish(reply_to=True)
|
await MessageUtils.build_message(image_bytes).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))
|
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ from nonebot.permission import SUPERUSER
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot.rule import Rule, to_me
|
from nonebot.rule import Rule, to_me
|
||||||
from nonebot_plugin_alconna import Alconna, on_alconna
|
from nonebot_plugin_alconna import Alconna, on_alconna
|
||||||
from nonebot_plugin_htmlrender import template_to_pic
|
|
||||||
|
|
||||||
|
from zhenxun import ui
|
||||||
from zhenxun.configs.config import Config
|
from zhenxun.configs.config import Config
|
||||||
from zhenxun.configs.path_config import TEMPLATE_PATH
|
|
||||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
from zhenxun.configs.utils import 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
|
||||||
@@ -27,7 +26,7 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
""".strip(),
|
""".strip(),
|
||||||
extra=PluginExtraData(
|
extra=PluginExtraData(
|
||||||
author="HibiKier",
|
author="HibiKier",
|
||||||
version="0.1",
|
version="0.2",
|
||||||
plugin_type=PluginType.SUPERUSER,
|
plugin_type=PluginType.SUPERUSER,
|
||||||
configs=[
|
configs=[
|
||||||
RegisterConfig(
|
RegisterConfig(
|
||||||
@@ -67,18 +66,14 @@ _self_check_poke_matcher = on_notice(
|
|||||||
|
|
||||||
async def handle_self_check():
|
async def handle_self_check():
|
||||||
try:
|
try:
|
||||||
data = await get_status_info()
|
data_dict = await get_status_info()
|
||||||
image = await template_to_pic(
|
|
||||||
template_path=str((TEMPLATE_PATH / "check").absolute()),
|
image_bytes = await ui.render_template(
|
||||||
template_name="main.html",
|
"pages/builtin/check",
|
||||||
templates={"data": data},
|
data=data_dict,
|
||||||
pages={
|
|
||||||
"viewport": {"width": 195, "height": 750},
|
|
||||||
"base_url": f"file://{TEMPLATE_PATH}",
|
|
||||||
},
|
|
||||||
wait=2,
|
|
||||||
)
|
)
|
||||||
await MessageUtils.build_message(image).send()
|
|
||||||
|
await MessageUtils.build_message(image_bytes).send()
|
||||||
logger.info("自检成功", "自检")
|
logger.info("自检成功", "自检")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await MessageUtils.build_message(f"自检失败: {e}").send()
|
await MessageUtils.build_message(f"自检失败: {e}").send()
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import contextlib
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -18,7 +19,47 @@ BAIDU_URL = "https://www.baidu.com/"
|
|||||||
GOOGLE_URL = "https://www.google.com/"
|
GOOGLE_URL = "https://www.google.com/"
|
||||||
|
|
||||||
VERSION_FILE = Path() / "__version__"
|
VERSION_FILE = Path() / "__version__"
|
||||||
ARM_KEY = "aarch64"
|
|
||||||
|
|
||||||
|
def get_arm_cpu_freq_safe():
|
||||||
|
"""获取ARM设备CPU频率"""
|
||||||
|
# 方法1: 优先从系统频率文件读取
|
||||||
|
freq_files = [
|
||||||
|
"/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq",
|
||||||
|
"/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq",
|
||||||
|
"/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq",
|
||||||
|
"/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq",
|
||||||
|
]
|
||||||
|
|
||||||
|
for freq_file in freq_files:
|
||||||
|
try:
|
||||||
|
with open(freq_file) as f:
|
||||||
|
frequency = int(f.read().strip())
|
||||||
|
return round(frequency / 1000000, 2) # 转换为GHz
|
||||||
|
except (OSError, ValueError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 方法2: 解析/proc/cpuinfo
|
||||||
|
with contextlib.suppress(OSError, FileNotFoundError, ValueError, PermissionError):
|
||||||
|
with open("/proc/cpuinfo") as f:
|
||||||
|
for line in f:
|
||||||
|
if "CPU MHz" in line:
|
||||||
|
freq = float(line.split(":")[1].strip())
|
||||||
|
return round(freq / 1000, 2) # 转换为GHz
|
||||||
|
# 方法3: 使用lscpu命令
|
||||||
|
with contextlib.suppress(OSError, subprocess.SubprocessError, ValueError):
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["LC_ALL"] = "C"
|
||||||
|
result = subprocess.run(
|
||||||
|
["lscpu"], capture_output=True, text=True, env=env, timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
for line in result.stdout.split("\n"):
|
||||||
|
if "CPU max MHz" in line or "CPU MHz" in line:
|
||||||
|
freq = float(line.split(":")[1].strip())
|
||||||
|
return round(freq / 1000, 2) # 转换为GHz
|
||||||
|
return 0 # 如果所有方法都失败,返回0
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -37,7 +78,7 @@ class CPUInfo:
|
|||||||
if _cpu_freq := psutil.cpu_freq():
|
if _cpu_freq := psutil.cpu_freq():
|
||||||
cpu_freq = round(_cpu_freq.current / 1000, 2)
|
cpu_freq = round(_cpu_freq.current / 1000, 2)
|
||||||
else:
|
else:
|
||||||
cpu_freq = 0
|
cpu_freq = get_arm_cpu_freq_safe()
|
||||||
return CPUInfo(core=cpu_core, usage=cpu_usage, freq=cpu_freq)
|
return CPUInfo(core=cpu_core, usage=cpu_usage, freq=cpu_freq)
|
||||||
|
|
||||||
|
|
||||||
@@ -160,44 +201,13 @@ def __get_version() -> str | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def __get_arm_cpu():
|
|
||||||
env = os.environ.copy()
|
|
||||||
env["LC_ALL"] = "en_US.UTF-8"
|
|
||||||
cpu_info = subprocess.check_output(["lscpu"], env=env).decode()
|
|
||||||
model_name = ""
|
|
||||||
cpu_freq = 0
|
|
||||||
for line in cpu_info.splitlines():
|
|
||||||
if "Model name" in line:
|
|
||||||
model_name = line.split(":")[1].strip()
|
|
||||||
if "CPU MHz" in line:
|
|
||||||
cpu_freq = float(line.split(":")[1].strip())
|
|
||||||
return model_name, cpu_freq
|
|
||||||
|
|
||||||
|
|
||||||
def __get_arm_oracle_cpu_freq():
|
|
||||||
cpu_freq = subprocess.check_output(
|
|
||||||
["dmidecode", "-s", "processor-frequency"]
|
|
||||||
).decode()
|
|
||||||
return round(float(cpu_freq.split()[0]) / 1000, 2)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_status_info() -> dict:
|
async def get_status_info() -> dict:
|
||||||
"""获取信息"""
|
"""获取信息"""
|
||||||
data = await __build_status()
|
data = await __build_status()
|
||||||
|
|
||||||
system = platform.uname()
|
system = platform.uname()
|
||||||
if system.machine == ARM_KEY and not (
|
|
||||||
cpuinfo.get_cpu_info().get("brand_raw") and data.cpu.freq
|
|
||||||
):
|
|
||||||
model_name, cpu_freq = __get_arm_cpu()
|
|
||||||
if not data.cpu.freq:
|
|
||||||
data.cpu.freq = cpu_freq or __get_arm_oracle_cpu_freq()
|
|
||||||
data = data.get_system_info()
|
|
||||||
data["brand_raw"] = model_name
|
|
||||||
else:
|
|
||||||
data = data.get_system_info()
|
data = data.get_system_info()
|
||||||
data["brand_raw"] = cpuinfo.get_cpu_info().get("brand_raw", "Unknown")
|
data["brand_raw"] = cpuinfo.get_cpu_info().get("brand_raw", "Unknown")
|
||||||
|
|
||||||
baidu, google = await __get_network_info()
|
baidu, google = await __get_network_info()
|
||||||
data["baidu"] = "#8CC265" if baidu else "red"
|
data["baidu"] = "#8CC265" if baidu else "red"
|
||||||
data["google"] = "#8CC265" if google else "red"
|
data["google"] = "#8CC265" if google else "red"
|
||||||
|
|||||||
@@ -13,17 +13,13 @@ from nonebot_plugin_alconna import (
|
|||||||
)
|
)
|
||||||
from nonebot_plugin_uninfo import Uninfo
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
|
|
||||||
from zhenxun.builtin_plugins.help._config import (
|
from zhenxun.configs.config import Config
|
||||||
GROUP_HELP_PATH,
|
|
||||||
SIMPLE_DETAIL_HELP_IMAGE,
|
|
||||||
SIMPLE_HELP_IMAGE,
|
|
||||||
)
|
|
||||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
from zhenxun.configs.utils import 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 ._data_source import create_help_img, get_plugin_help
|
from ._data_source import create_help_img, get_llm_help, get_plugin_help
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="帮助",
|
name="帮助",
|
||||||
@@ -36,11 +32,40 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
is_show=False,
|
is_show=False,
|
||||||
configs=[
|
configs=[
|
||||||
RegisterConfig(
|
RegisterConfig(
|
||||||
key="type",
|
key="ENABLE_LLM_HELPER",
|
||||||
value="normal",
|
value=False,
|
||||||
help="帮助图片样式 ['normal', 'HTML', 'zhenxun']",
|
help="是否开启LLM智能帮助功能",
|
||||||
default_value="zhenxun",
|
default_value=False,
|
||||||
)
|
type=bool,
|
||||||
|
),
|
||||||
|
RegisterConfig(
|
||||||
|
key="DEFAULT_LLM_MODEL",
|
||||||
|
value="Gemini/gemini-2.5-flash-lite-preview-06-17",
|
||||||
|
help="智能帮助功能使用的默认LLM模型",
|
||||||
|
default_value="Gemini/gemini-2.5-flash-lite-preview-06-17",
|
||||||
|
type=str,
|
||||||
|
),
|
||||||
|
RegisterConfig(
|
||||||
|
key="LLM_HELPER_STYLE",
|
||||||
|
value="绪山真寻",
|
||||||
|
help="设置智能帮助功能的回复口吻或风格",
|
||||||
|
default_value="绪山真寻",
|
||||||
|
type=str,
|
||||||
|
),
|
||||||
|
RegisterConfig(
|
||||||
|
key="LLM_HELPER_REPLY_AS_IMAGE_THRESHOLD",
|
||||||
|
value=100,
|
||||||
|
help="AI帮助回复超过多少字时转为图片发送",
|
||||||
|
default_value=100,
|
||||||
|
type=int,
|
||||||
|
),
|
||||||
|
RegisterConfig(
|
||||||
|
key="HELP_STYLE",
|
||||||
|
value="default",
|
||||||
|
help="帮助页面的显示样式 (可选值: 'default', 'simple')",
|
||||||
|
default_value="default",
|
||||||
|
type=str,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).to_dict(),
|
).to_dict(),
|
||||||
)
|
)
|
||||||
@@ -77,26 +102,43 @@ async def _(
|
|||||||
is_detail: Query[bool] = AlconnaQuery("detail.value", False),
|
is_detail: Query[bool] = AlconnaQuery("detail.value", False),
|
||||||
):
|
):
|
||||||
_is_superuser = is_superuser.result if is_superuser.available else False
|
_is_superuser = is_superuser.result if is_superuser.available else False
|
||||||
if name.available:
|
|
||||||
if _is_superuser and session.user.id not in bot.config.superusers:
|
if _is_superuser and session.user.id not in bot.config.superusers:
|
||||||
_is_superuser = False
|
await MessageUtils.build_message("权限不足,无法查看超级用户帮助").finish(
|
||||||
if result := await get_plugin_help(session.user.id, name.result, _is_superuser):
|
reply_to=True
|
||||||
await MessageUtils.build_message(result).send(reply_to=True)
|
)
|
||||||
else:
|
|
||||||
await MessageUtils.build_message("没有此功能的帮助信息...").send(
|
if name.available:
|
||||||
|
help_style = Config.get_config("help", "HELP_STYLE")
|
||||||
|
variant = help_style if help_style != "default" else None
|
||||||
|
|
||||||
|
traditional_help_result = await get_plugin_help(
|
||||||
|
session.user.id, name.result, _is_superuser, variant=variant
|
||||||
|
)
|
||||||
|
|
||||||
|
is_plugin_found = not (
|
||||||
|
isinstance(traditional_help_result, str)
|
||||||
|
and "没有查找到这个功能噢..." in traditional_help_result
|
||||||
|
)
|
||||||
|
if is_plugin_found:
|
||||||
|
await MessageUtils.build_message(traditional_help_result).send(
|
||||||
reply_to=True
|
reply_to=True
|
||||||
)
|
)
|
||||||
logger.info(f"查看帮助详情: {name.result}", "帮助", session=session)
|
logger.info(f"查看帮助详情: {name.result}", "帮助", session=session)
|
||||||
|
elif Config.get_config("help", "ENABLE_LLM_HELPER"):
|
||||||
|
logger.info(f"智能帮助处理问题: {name.result}", "帮助", session=session)
|
||||||
|
llm_answer = await get_llm_help(name.result, session.user.id)
|
||||||
|
await MessageUtils.build_message(llm_answer).send(reply_to=True)
|
||||||
|
else:
|
||||||
|
await MessageUtils.build_message(traditional_help_result).send(
|
||||||
|
reply_to=True
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"查看帮助详情失败,未找到: {name.result}", "帮助", session=session
|
||||||
|
)
|
||||||
elif session.group and (gid := session.group.id):
|
elif session.group and (gid := session.group.id):
|
||||||
_image_path = GROUP_HELP_PATH / f"{gid}_{is_detail.result}.png"
|
image_bytes = await create_help_img(session, gid, is_detail.result)
|
||||||
if not _image_path.exists():
|
await MessageUtils.build_message(image_bytes).finish()
|
||||||
result = await create_help_img(session, gid, is_detail.result)
|
|
||||||
await MessageUtils.build_message(_image_path).finish()
|
|
||||||
else:
|
else:
|
||||||
if is_detail.result:
|
image_bytes = await create_help_img(session, None, is_detail.result)
|
||||||
_image_path = SIMPLE_DETAIL_HELP_IMAGE
|
await MessageUtils.build_message(image_bytes).finish()
|
||||||
else:
|
|
||||||
_image_path = SIMPLE_HELP_IMAGE
|
|
||||||
if not _image_path.exists():
|
|
||||||
result = await create_help_img(session, None, is_detail.result)
|
|
||||||
await MessageUtils.build_message(_image_path).finish()
|
|
||||||
|
|||||||
@@ -1,60 +1,126 @@
|
|||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
from nonebot_plugin_uninfo import Uninfo
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
|
|
||||||
|
from zhenxun import ui
|
||||||
|
from zhenxun.configs.config import BotConfig, Config
|
||||||
from zhenxun.configs.path_config import IMAGE_PATH
|
from zhenxun.configs.path_config import IMAGE_PATH
|
||||||
|
from zhenxun.configs.utils import PluginExtraData
|
||||||
|
from zhenxun.models.bot_console import BotConsole
|
||||||
|
from zhenxun.models.group_console import GroupConsole
|
||||||
from zhenxun.models.level_user import LevelUser
|
from zhenxun.models.level_user import LevelUser
|
||||||
from zhenxun.models.plugin_info import PluginInfo
|
from zhenxun.models.plugin_info import PluginInfo
|
||||||
from zhenxun.utils.enum import PluginType
|
from zhenxun.models.statistics import Statistics
|
||||||
from zhenxun.utils.image_utils import BuildImage, ImageTemplate
|
from zhenxun.services import (
|
||||||
|
LLMException,
|
||||||
from ._config import (
|
LLMMessage,
|
||||||
GROUP_HELP_PATH,
|
avatar_service,
|
||||||
SIMPLE_DETAIL_HELP_IMAGE,
|
generate,
|
||||||
SIMPLE_HELP_IMAGE,
|
|
||||||
base_config,
|
|
||||||
)
|
)
|
||||||
from .html_help import build_html_image
|
from zhenxun.services.log import logger
|
||||||
from .normal_help import build_normal_image
|
from zhenxun.ui.builders import (
|
||||||
from .zhenxun_help import build_zhenxun_image
|
NotebookBuilder,
|
||||||
|
PluginMenuBuilder,
|
||||||
|
)
|
||||||
|
from zhenxun.ui.models import PluginMenuCategory
|
||||||
|
from zhenxun.utils.common_utils import format_usage_for_markdown
|
||||||
|
from zhenxun.utils.enum import BlockType, PluginType
|
||||||
|
from zhenxun.utils.platform import PlatformUtils
|
||||||
|
|
||||||
|
from ._utils import classify_plugin
|
||||||
|
|
||||||
random_bk_path = IMAGE_PATH / "background" / "help" / "simple_help"
|
random_bk_path = IMAGE_PATH / "background" / "help" / "simple_help"
|
||||||
|
|
||||||
background = IMAGE_PATH / "background" / "0.png"
|
background = IMAGE_PATH / "background" / "0.png"
|
||||||
|
|
||||||
|
|
||||||
driver = nonebot.get_driver()
|
driver = nonebot.get_driver()
|
||||||
|
|
||||||
|
|
||||||
|
def _create_plugin_menu_item(
|
||||||
|
bot: BotConsole | None,
|
||||||
|
plugin: PluginInfo,
|
||||||
|
group: GroupConsole | None,
|
||||||
|
is_detail: bool,
|
||||||
|
) -> dict:
|
||||||
|
"""为插件菜单构造一个插件菜单项数据字典"""
|
||||||
|
status = True
|
||||||
|
has_superuser_help = False
|
||||||
|
nb_plugin = nonebot.get_plugin_by_module_name(plugin.module_path)
|
||||||
|
if nb_plugin and nb_plugin.metadata and nb_plugin.metadata.extra:
|
||||||
|
extra_data = PluginExtraData(**nb_plugin.metadata.extra)
|
||||||
|
if extra_data.superuser_help:
|
||||||
|
has_superuser_help = True
|
||||||
|
|
||||||
|
if not plugin.status:
|
||||||
|
if plugin.block_type == BlockType.ALL:
|
||||||
|
status = False
|
||||||
|
elif group and plugin.block_type == BlockType.GROUP:
|
||||||
|
status = False
|
||||||
|
elif not group and plugin.block_type == BlockType.PRIVATE:
|
||||||
|
status = False
|
||||||
|
elif group and f"{plugin.module}," in group.block_plugin:
|
||||||
|
status = False
|
||||||
|
elif bot and f"{plugin.module}," in bot.block_plugins:
|
||||||
|
status = False
|
||||||
|
|
||||||
|
commands = []
|
||||||
|
if is_detail and nb_plugin and nb_plugin.metadata and nb_plugin.metadata.extra:
|
||||||
|
extra_data = PluginExtraData(**nb_plugin.metadata.extra)
|
||||||
|
commands = [cmd.command for cmd in extra_data.commands]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": str(plugin.id),
|
||||||
|
"name": plugin.name,
|
||||||
|
"status": status,
|
||||||
|
"has_superuser_help": has_superuser_help,
|
||||||
|
"commands": commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def create_help_img(
|
async def create_help_img(
|
||||||
session: Uninfo, group_id: str | None, is_detail: bool
|
session: Uninfo, group_id: str | None, is_detail: bool
|
||||||
) -> Path:
|
) -> bytes:
|
||||||
"""生成帮助图片
|
"""使用渲染服务生成帮助图片"""
|
||||||
|
classified_data = await classify_plugin(
|
||||||
参数:
|
session, group_id, is_detail, _create_plugin_menu_item
|
||||||
session: Uninfo
|
|
||||||
group_id: 群号
|
|
||||||
"""
|
|
||||||
help_type = base_config.get("type", "").strip().lower()
|
|
||||||
|
|
||||||
match help_type:
|
|
||||||
case "html":
|
|
||||||
result = BuildImage.open(await build_html_image(group_id, is_detail))
|
|
||||||
case "zhenxun":
|
|
||||||
result = BuildImage.open(
|
|
||||||
await build_zhenxun_image(session, group_id, is_detail)
|
|
||||||
)
|
)
|
||||||
case _:
|
|
||||||
result = await build_normal_image(group_id, is_detail)
|
sorted_categories = dict(
|
||||||
if group_id:
|
sorted(classified_data.items(), key=lambda x: len(x[1]), reverse=True)
|
||||||
save_path = GROUP_HELP_PATH / f"{group_id}_{is_detail}.png"
|
)
|
||||||
elif is_detail:
|
categories_for_model = []
|
||||||
save_path = SIMPLE_DETAIL_HELP_IMAGE
|
plugin_count = 0
|
||||||
else:
|
active_count = 0
|
||||||
save_path = SIMPLE_HELP_IMAGE
|
|
||||||
await result.save(save_path)
|
if sorted_categories:
|
||||||
return save_path
|
menu_key = next(iter(sorted_categories.keys()))
|
||||||
|
max_data = sorted_categories.pop(menu_key)
|
||||||
|
main_category_name = "主要功能" if menu_key in ["normal", "功能"] else menu_key
|
||||||
|
categories_for_model.append({"name": main_category_name, "items": max_data})
|
||||||
|
plugin_count += len(max_data)
|
||||||
|
active_count += sum(1 for item in max_data if item["status"])
|
||||||
|
|
||||||
|
for menu, value in sorted_categories.items():
|
||||||
|
category_name = "主要功能" if menu in ["normal", "功能"] else menu
|
||||||
|
categories_for_model.append({"name": category_name, "items": value})
|
||||||
|
plugin_count += len(value)
|
||||||
|
active_count += sum(1 for item in value if item["status"])
|
||||||
|
|
||||||
|
platform = PlatformUtils.get_platform(session)
|
||||||
|
bot_id = BotConfig.get_qbot_uid(session.self_id) or session.self_id
|
||||||
|
bot_avatar_path = await avatar_service.get_avatar_path(platform, bot_id)
|
||||||
|
bot_avatar_url = bot_avatar_path.as_uri() if bot_avatar_path else ""
|
||||||
|
|
||||||
|
builder = PluginMenuBuilder(
|
||||||
|
bot_name=BotConfig.self_nickname,
|
||||||
|
bot_avatar_url=bot_avatar_url,
|
||||||
|
is_detail=is_detail,
|
||||||
|
)
|
||||||
|
|
||||||
|
for category in categories_for_model:
|
||||||
|
builder.add_category(
|
||||||
|
PluginMenuCategory(name=category["name"], items=category["items"])
|
||||||
|
)
|
||||||
|
|
||||||
|
return await ui.render(builder.build())
|
||||||
|
|
||||||
|
|
||||||
async def get_user_allow_help(user_id: str) -> list[PluginType]:
|
async def get_user_allow_help(user_id: str) -> list[PluginType]:
|
||||||
@@ -78,15 +144,36 @@ async def get_user_allow_help(user_id: str) -> list[PluginType]:
|
|||||||
return type_list
|
return type_list
|
||||||
|
|
||||||
|
|
||||||
|
def min_leading_spaces(str_list: list[str]) -> int:
|
||||||
|
min_spaces = 9999
|
||||||
|
|
||||||
|
for s in str_list:
|
||||||
|
leading_spaces = len(s) - len(s.lstrip(" "))
|
||||||
|
|
||||||
|
if leading_spaces < min_spaces:
|
||||||
|
min_spaces = leading_spaces
|
||||||
|
|
||||||
|
return min_spaces if min_spaces != 9999 else 0
|
||||||
|
|
||||||
|
|
||||||
|
def split_text(text: str):
|
||||||
|
split_text = text.split("\n")
|
||||||
|
min_spaces = min_leading_spaces(split_text)
|
||||||
|
if min_spaces > 0:
|
||||||
|
split_text = [s[min_spaces:] for s in split_text]
|
||||||
|
return [s.replace(" ", " ") for s in split_text]
|
||||||
|
|
||||||
|
|
||||||
async def get_plugin_help(
|
async def get_plugin_help(
|
||||||
user_id: str, name: str, is_superuser: bool
|
user_id: str, name: str, is_superuser: bool, variant: str | None = None
|
||||||
) -> str | BuildImage:
|
) -> str | bytes:
|
||||||
"""获取功能的帮助信息
|
"""获取功能的帮助信息
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
user_id: 用户id
|
user_id: 用户id
|
||||||
name: 插件名称或id
|
name: 插件名称或id
|
||||||
is_superuser: 是否为超级用户
|
is_superuser: 是否为超级用户
|
||||||
|
variant: 使用的皮肤/变体名称
|
||||||
"""
|
"""
|
||||||
type_list = await get_user_allow_help(user_id)
|
type_list = await get_user_allow_help(user_id)
|
||||||
if name.isdigit():
|
if name.isdigit():
|
||||||
@@ -95,23 +182,132 @@ async def get_plugin_help(
|
|||||||
plugin = await PluginInfo.get_or_none(
|
plugin = await PluginInfo.get_or_none(
|
||||||
name__iexact=name, load_status=True, plugin_type__in=type_list
|
name__iexact=name, load_status=True, plugin_type__in=type_list
|
||||||
)
|
)
|
||||||
|
|
||||||
if plugin:
|
if plugin:
|
||||||
_plugin = nonebot.get_plugin_by_module_name(plugin.module_path)
|
_plugin = nonebot.get_plugin_by_module_name(plugin.module_path)
|
||||||
if _plugin and _plugin.metadata:
|
if _plugin and _plugin.metadata:
|
||||||
items = None
|
extra_data = PluginExtraData(**_plugin.metadata.extra)
|
||||||
|
|
||||||
|
call_count = await Statistics.filter(plugin_name=plugin.module).count()
|
||||||
|
usage = _plugin.metadata.usage
|
||||||
if is_superuser:
|
if is_superuser:
|
||||||
extra = _plugin.metadata.extra
|
if not extra_data.superuser_help:
|
||||||
if usage := extra.get("superuser_help"):
|
return "该功能没有超级用户帮助信息"
|
||||||
items = {
|
usage = extra_data.superuser_help
|
||||||
"简介": _plugin.metadata.description,
|
|
||||||
"用法": usage,
|
metadata_items = [
|
||||||
|
{"label": "作者", "value": extra_data.author or "未知"},
|
||||||
|
{"label": "版本", "value": extra_data.version or "未知"},
|
||||||
|
{"label": "调用次数", "value": call_count},
|
||||||
|
]
|
||||||
|
|
||||||
|
processed_description = format_usage_for_markdown(
|
||||||
|
_plugin.metadata.description.strip()
|
||||||
|
)
|
||||||
|
processed_usage = format_usage_for_markdown(usage.strip())
|
||||||
|
|
||||||
|
sections = [
|
||||||
|
{"title": "简介", "content": [processed_description]},
|
||||||
|
{"title": "使用方法", "content": [processed_usage]},
|
||||||
|
]
|
||||||
|
|
||||||
|
page_data = {
|
||||||
|
"title": _plugin.metadata.name,
|
||||||
|
"metadata": metadata_items,
|
||||||
|
"sections": sections,
|
||||||
}
|
}
|
||||||
else:
|
|
||||||
items = {
|
component = ui.template("pages/builtin/help", data=page_data)
|
||||||
"简介": _plugin.metadata.description,
|
if variant:
|
||||||
"用法": _plugin.metadata.usage,
|
component.variant = variant
|
||||||
}
|
return await ui.render(component, use_cache=True, device_scale_factor=2)
|
||||||
if items:
|
|
||||||
return await ImageTemplate.hl_page(plugin.name, items)
|
|
||||||
return "糟糕! 该功能没有帮助喔..."
|
return "糟糕! 该功能没有帮助喔..."
|
||||||
return "没有查找到这个功能噢..."
|
return "没有查找到这个功能噢..."
|
||||||
|
|
||||||
|
|
||||||
|
async def get_llm_help(question: str, user_id: str) -> str | bytes:
|
||||||
|
"""
|
||||||
|
使用LLM来回答用户的自然语言求助。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
question: 用户的问题。
|
||||||
|
user_id: 提问用户的ID。
|
||||||
|
|
||||||
|
返回:
|
||||||
|
str | bytes: LLM生成的回答或错误提示。
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
allowed_types = await get_user_allow_help(user_id)
|
||||||
|
|
||||||
|
plugins = await PluginInfo.filter(
|
||||||
|
is_show=True, plugin_type__in=allowed_types
|
||||||
|
).all()
|
||||||
|
|
||||||
|
knowledge_base_parts = []
|
||||||
|
for p in plugins:
|
||||||
|
meta = nonebot.get_plugin_by_module_name(p.module_path)
|
||||||
|
if not meta or not meta.metadata:
|
||||||
|
continue
|
||||||
|
usage = meta.metadata.usage.strip() or "无"
|
||||||
|
desc = meta.metadata.description.strip() or "无"
|
||||||
|
part = f"功能名称: {p.name}\n功能描述: {desc}\n用法示例:\n{usage}"
|
||||||
|
knowledge_base_parts.append(part)
|
||||||
|
|
||||||
|
if not knowledge_base_parts:
|
||||||
|
return "抱歉,根据您的权限,当前没有可供查询的功能信息。"
|
||||||
|
|
||||||
|
knowledge_base = "\n\n---\n\n".join(knowledge_base_parts)
|
||||||
|
|
||||||
|
user_role = "普通用户"
|
||||||
|
if PluginType.SUPERUSER in allowed_types:
|
||||||
|
user_role = "超级管理员"
|
||||||
|
elif PluginType.ADMIN in allowed_types:
|
||||||
|
user_role = "管理员"
|
||||||
|
|
||||||
|
base_system_prompt = (
|
||||||
|
f"你是一个精通机器人功能的AI助手。当前向你提问的用户是一位「{user_role}」。\n"
|
||||||
|
"你的任务是根据下面提供的功能列表和详细说明,来回答用户关于如何使用机器人的问题。\n"
|
||||||
|
"请仔细阅读每个功能的描述和用法,然后用简洁、清晰的语言告诉用户应该使用哪个或哪些命令来解决他们的问题。\n"
|
||||||
|
"如果找不到完全匹配的功能,可以推荐最相关的一个或几个。直接给出操作指令和简要解释即可。"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
Config.get_config("help", "LLM_HELPER_STYLE")
|
||||||
|
and Config.get_config("help", "LLM_HELPER_STYLE").strip()
|
||||||
|
):
|
||||||
|
style = Config.get_config("help", "LLM_HELPER_STYLE")
|
||||||
|
style_instruction = f"请务必使用「{style}」的风格和口吻来回答。"
|
||||||
|
system_prompt = f"{base_system_prompt}\n{style_instruction}"
|
||||||
|
else:
|
||||||
|
system_prompt = base_system_prompt
|
||||||
|
|
||||||
|
full_instruction = (
|
||||||
|
f"{system_prompt}\n\n=== 功能列表和说明 ===\n{knowledge_base}"
|
||||||
|
)
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
LLMMessage.system(full_instruction),
|
||||||
|
LLMMessage.user(question),
|
||||||
|
]
|
||||||
|
response = await generate(
|
||||||
|
messages=messages,
|
||||||
|
model=Config.get_config("help", "DEFAULT_LLM_MODEL"),
|
||||||
|
)
|
||||||
|
|
||||||
|
reply_text = response.text if response else "抱歉,我暂时无法回答这个问题。"
|
||||||
|
threshold = Config.get_config("help", "LLM_HELPER_REPLY_AS_IMAGE_THRESHOLD", 50)
|
||||||
|
|
||||||
|
if len(reply_text) > threshold:
|
||||||
|
builder = NotebookBuilder()
|
||||||
|
builder.text(reply_text)
|
||||||
|
return await ui.render(builder.build())
|
||||||
|
|
||||||
|
return reply_text
|
||||||
|
|
||||||
|
except LLMException as e:
|
||||||
|
logger.error(f"LLM智能帮助出错: {e}", "帮助", e=e)
|
||||||
|
return "抱歉,智能帮助功能当前不可用,请稍后再试或联系管理员。"
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"构建LLM帮助时发生未知错误: {e}", "帮助", e=e)
|
||||||
|
return "抱歉,智能帮助功能遇到了一点小问题,正在紧急处理中!"
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
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
|
||||||
@@ -27,23 +30,28 @@ async def sort_type() -> dict[str, list[PluginInfo]]:
|
|||||||
|
|
||||||
|
|
||||||
async def classify_plugin(
|
async def classify_plugin(
|
||||||
group_id: str | None, is_detail: bool, handle: Callable
|
session: Uninfo, 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]]: 分类插件数据
|
||||||
"""
|
"""
|
||||||
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_group(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(plugin, group, is_detail))
|
classify[menu].append(handle(bot, plugin, group, is_detail))
|
||||||
|
for value in classify.values():
|
||||||
|
value.sort(key=lambda x: int(x["id"]))
|
||||||
return classify
|
return classify
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
import os
|
|
||||||
import random
|
|
||||||
|
|
||||||
from nonebot_plugin_htmlrender import template_to_pic
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
from zhenxun.configs.path_config import TEMPLATE_PATH
|
|
||||||
from zhenxun.models.group_console import GroupConsole
|
|
||||||
from zhenxun.models.plugin_info import PluginInfo
|
|
||||||
from zhenxun.utils.enum import BlockType
|
|
||||||
|
|
||||||
from ._utils import classify_plugin
|
|
||||||
|
|
||||||
LOGO_PATH = TEMPLATE_PATH / "menu" / "res" / "logo"
|
|
||||||
|
|
||||||
|
|
||||||
class Item(BaseModel):
|
|
||||||
plugin_name: str
|
|
||||||
"""插件名称"""
|
|
||||||
sta: int
|
|
||||||
"""插件状态"""
|
|
||||||
|
|
||||||
|
|
||||||
class PluginList(BaseModel):
|
|
||||||
plugin_type: str
|
|
||||||
"""菜单名称"""
|
|
||||||
icon: str
|
|
||||||
"""图标"""
|
|
||||||
logo: str
|
|
||||||
"""logo"""
|
|
||||||
items: list[Item]
|
|
||||||
"""插件列表"""
|
|
||||||
|
|
||||||
|
|
||||||
ICON2STR = {
|
|
||||||
"normal": "fa fa-cog",
|
|
||||||
"原神相关": "fa fa-circle-o",
|
|
||||||
"常规插件": "fa fa-cubes",
|
|
||||||
"联系管理员": "fa fa-envelope-o",
|
|
||||||
"抽卡相关": "fa fa-credit-card-alt",
|
|
||||||
"来点好康的": "fa fa-picture-o",
|
|
||||||
"数据统计": "fa fa-bar-chart",
|
|
||||||
"一些工具": "fa fa-shopping-cart",
|
|
||||||
"商店": "fa fa-shopping-cart",
|
|
||||||
"其它": "fa fa-tags",
|
|
||||||
"群内小游戏": "fa fa-gamepad",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def __handle_item(
|
|
||||||
plugin: PluginInfo, group: GroupConsole | None, is_detail: bool
|
|
||||||
) -> Item:
|
|
||||||
"""构造Item
|
|
||||||
|
|
||||||
参数:
|
|
||||||
plugin: PluginInfo
|
|
||||||
group: 群组
|
|
||||||
is_detail: 是否详细
|
|
||||||
|
|
||||||
返回:
|
|
||||||
Item: Item
|
|
||||||
"""
|
|
||||||
sta = 0
|
|
||||||
if not plugin.status:
|
|
||||||
if group and plugin.block_type in [
|
|
||||||
BlockType.ALL,
|
|
||||||
BlockType.GROUP,
|
|
||||||
]:
|
|
||||||
sta = 2
|
|
||||||
if not group and plugin.block_type in [
|
|
||||||
BlockType.ALL,
|
|
||||||
BlockType.PRIVATE,
|
|
||||||
]:
|
|
||||||
sta = 2
|
|
||||||
if group:
|
|
||||||
if f"{plugin.module}:super," in group.block_plugin:
|
|
||||||
sta = 2
|
|
||||||
if f"{plugin.module}," in group.block_plugin:
|
|
||||||
sta = 1
|
|
||||||
return Item(plugin_name=plugin.name, sta=sta)
|
|
||||||
|
|
||||||
|
|
||||||
def build_plugin_data(classify: dict[str, list[Item]]) -> list[dict[str, str]]:
|
|
||||||
"""构建前端插件数据
|
|
||||||
|
|
||||||
参数:
|
|
||||||
classify: 插件数据
|
|
||||||
|
|
||||||
返回:
|
|
||||||
list[dict[str, str]]: 前端插件数据
|
|
||||||
"""
|
|
||||||
lengths = [len(classify[c]) for c in classify]
|
|
||||||
index = lengths.index(max(lengths))
|
|
||||||
menu_key = list(classify.keys())[index]
|
|
||||||
max_data = classify[menu_key]
|
|
||||||
del classify[menu_key]
|
|
||||||
plugin_list = []
|
|
||||||
for menu_type in classify:
|
|
||||||
icon = "fa fa-pencil-square-o"
|
|
||||||
if menu_type in ICON2STR.keys():
|
|
||||||
icon = ICON2STR[menu_type]
|
|
||||||
logo = LOGO_PATH / random.choice(os.listdir(LOGO_PATH))
|
|
||||||
data = {
|
|
||||||
"name": menu_type if menu_type != "normal" else "功能",
|
|
||||||
"items": classify[menu_type],
|
|
||||||
"icon": icon,
|
|
||||||
"logo": str(logo.absolute()),
|
|
||||||
}
|
|
||||||
plugin_list.append(data)
|
|
||||||
plugin_list.insert(
|
|
||||||
0,
|
|
||||||
{
|
|
||||||
"name": menu_key if menu_key != "normal" else "功能",
|
|
||||||
"items": max_data,
|
|
||||||
"icon": "fa fa-pencil-square-o",
|
|
||||||
"logo": str((LOGO_PATH / random.choice(os.listdir(LOGO_PATH))).absolute()),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return plugin_list
|
|
||||||
|
|
||||||
|
|
||||||
async def build_html_image(group_id: str | None, is_detail: bool) -> bytes:
|
|
||||||
"""构造HTML帮助图片
|
|
||||||
|
|
||||||
参数:
|
|
||||||
group_id: 群号
|
|
||||||
is_detail: 是否详细帮助
|
|
||||||
"""
|
|
||||||
classify = await classify_plugin(group_id, is_detail, __handle_item)
|
|
||||||
plugin_list = build_plugin_data(classify)
|
|
||||||
return await template_to_pic(
|
|
||||||
template_path=str((TEMPLATE_PATH / "menu").absolute()),
|
|
||||||
template_name="zhenxun_menu.html",
|
|
||||||
templates={"plugin_list": plugin_list},
|
|
||||||
pages={
|
|
||||||
"viewport": {"width": 1903, "height": 975},
|
|
||||||
"base_url": f"file://{TEMPLATE_PATH}",
|
|
||||||
},
|
|
||||||
wait=2,
|
|
||||||
)
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
from zhenxun.configs.path_config import IMAGE_PATH
|
|
||||||
from zhenxun.models.group_console import GroupConsole
|
|
||||||
from zhenxun.utils._build_image import BuildImage
|
|
||||||
from zhenxun.utils.enum import BlockType
|
|
||||||
from zhenxun.utils.image_utils import build_sort_image, group_image
|
|
||||||
|
|
||||||
from ._utils import sort_type
|
|
||||||
|
|
||||||
BACKGROUND_PATH = IMAGE_PATH / "background" / "help" / "simple_help"
|
|
||||||
|
|
||||||
|
|
||||||
async def build_normal_image(group_id: str | None, is_detail: bool) -> BuildImage:
|
|
||||||
"""构造PIL帮助图片
|
|
||||||
|
|
||||||
参数:
|
|
||||||
group_id: 群号
|
|
||||||
is_detail: 详细帮助
|
|
||||||
"""
|
|
||||||
image_list = []
|
|
||||||
font_size = 24
|
|
||||||
font = BuildImage.load_font("HYWenHei-85W.ttf", 20)
|
|
||||||
sort_data = await sort_type()
|
|
||||||
for idx, menu_type in enumerate(sort_data):
|
|
||||||
plugin_list = sort_data[menu_type]
|
|
||||||
"""拿到最大宽度和结算高度"""
|
|
||||||
wh_list = [
|
|
||||||
BuildImage.get_text_size(f"{x.id}.{x.name}", font) for x in plugin_list
|
|
||||||
]
|
|
||||||
wh_list.append(BuildImage.get_text_size(menu_type, font))
|
|
||||||
sum_height = (font_size + 6) * len(plugin_list) + 10
|
|
||||||
max_width = max(x[0] for x in wh_list) + 30
|
|
||||||
bk = BuildImage(
|
|
||||||
max_width + 40,
|
|
||||||
sum_height + 50,
|
|
||||||
font_size=30,
|
|
||||||
color="#a7d1fc",
|
|
||||||
font="CJGaoDeGuo.otf",
|
|
||||||
)
|
|
||||||
title_size = bk.getsize(menu_type)
|
|
||||||
max_width = max_width if max_width > title_size[0] else title_size[0]
|
|
||||||
row = BuildImage(
|
|
||||||
max_width + 40,
|
|
||||||
sum_height,
|
|
||||||
font_size=font_size,
|
|
||||||
color="black" if idx % 2 else "white",
|
|
||||||
)
|
|
||||||
curr_h = 10
|
|
||||||
group = await GroupConsole.get_or_none(group_id=group_id)
|
|
||||||
for _, plugin in enumerate(plugin_list):
|
|
||||||
text_color = (255, 255, 255) if idx % 2 else (0, 0, 0)
|
|
||||||
if group and f"{plugin.module}," in group.block_plugin:
|
|
||||||
text_color = (252, 75, 13)
|
|
||||||
pos = None
|
|
||||||
# 禁用状态划线
|
|
||||||
if plugin.block_type in [BlockType.ALL, BlockType.GROUP] or (
|
|
||||||
group and f"super:{plugin.module}," in group.block_plugin
|
|
||||||
):
|
|
||||||
w = curr_h + int(row.getsize(plugin.name)[1] / 2) + 2
|
|
||||||
line_width = row.getsize(plugin.name)[0] + 35
|
|
||||||
pos = (7, w, line_width, w)
|
|
||||||
await row.text((10, curr_h), f"{plugin.id}.{plugin.name}", text_color)
|
|
||||||
if pos:
|
|
||||||
await row.line(pos, (236, 66, 7), 3)
|
|
||||||
curr_h += font_size + 5
|
|
||||||
await bk.text((0, 14), menu_type, center_type="width")
|
|
||||||
await bk.paste(row, (0, 50))
|
|
||||||
await bk.transparent(2)
|
|
||||||
image_list.append(bk)
|
|
||||||
image_group, h = group_image(image_list)
|
|
||||||
|
|
||||||
async def _a(image: BuildImage):
|
|
||||||
await image.filter("GaussianBlur", 5)
|
|
||||||
|
|
||||||
result = await build_sort_image(
|
|
||||||
image_group,
|
|
||||||
h,
|
|
||||||
background_path=BACKGROUND_PATH,
|
|
||||||
background_handle=_a,
|
|
||||||
)
|
|
||||||
width, height = 10, 10
|
|
||||||
for s in [
|
|
||||||
"目前支持的功能列表:",
|
|
||||||
"可以通过 ‘帮助 [功能名称或功能Id]’ 来获取对应功能的使用方法",
|
|
||||||
]:
|
|
||||||
text = await BuildImage.build_text_image(s, "HYWenHei-85W.ttf", 24)
|
|
||||||
await result.paste(text, (width, height))
|
|
||||||
height += 50
|
|
||||||
if s == "目前支持的功能列表:":
|
|
||||||
width += 50
|
|
||||||
text = await BuildImage.build_text_image(
|
|
||||||
"注: 红字代表功能被群管理员禁用,红线代表功能正在维护",
|
|
||||||
"HYWenHei-85W.ttf",
|
|
||||||
24,
|
|
||||||
(231, 74, 57),
|
|
||||||
)
|
|
||||||
await result.paste(
|
|
||||||
text,
|
|
||||||
(300, 10),
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
import nonebot
|
|
||||||
from nonebot_plugin_htmlrender import template_to_pic
|
|
||||||
from nonebot_plugin_uninfo import Uninfo
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
from zhenxun.configs.config import BotConfig
|
|
||||||
from zhenxun.configs.path_config import TEMPLATE_PATH
|
|
||||||
from zhenxun.configs.utils import PluginExtraData
|
|
||||||
from zhenxun.models.group_console import GroupConsole
|
|
||||||
from zhenxun.models.plugin_info import PluginInfo
|
|
||||||
from zhenxun.utils.enum import BlockType
|
|
||||||
from zhenxun.utils.platform import PlatformUtils
|
|
||||||
|
|
||||||
from ._utils import classify_plugin
|
|
||||||
|
|
||||||
|
|
||||||
class Item(BaseModel):
|
|
||||||
plugin_name: str
|
|
||||||
"""插件名称"""
|
|
||||||
commands: list[str]
|
|
||||||
"""插件命令"""
|
|
||||||
|
|
||||||
|
|
||||||
def __handle_item(plugin: PluginInfo, group: GroupConsole | None, is_detail: bool):
|
|
||||||
"""构造Item
|
|
||||||
|
|
||||||
参数:
|
|
||||||
plugin: PluginInfo
|
|
||||||
group: 群组
|
|
||||||
|
|
||||||
返回:
|
|
||||||
Item: Item
|
|
||||||
"""
|
|
||||||
if not plugin.status:
|
|
||||||
if plugin.block_type == BlockType.ALL:
|
|
||||||
plugin.name = f"{plugin.name}(不可用)"
|
|
||||||
elif group and plugin.block_type == BlockType.GROUP:
|
|
||||||
plugin.name = f"{plugin.name}(不可用)"
|
|
||||||
elif not group and plugin.block_type == BlockType.PRIVATE:
|
|
||||||
plugin.name = f"{plugin.name}(不可用)"
|
|
||||||
elif group and f"{plugin.module}," in group.block_plugin:
|
|
||||||
plugin.name = f"{plugin.name}(不可用)"
|
|
||||||
commands = []
|
|
||||||
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:
|
|
||||||
extra_data = PluginExtraData(**nb_plugin.metadata.extra)
|
|
||||||
commands = [cmd.command for cmd in extra_data.commands]
|
|
||||||
return Item(plugin_name=f"{plugin.id}-{plugin.name}", commands=commands)
|
|
||||||
|
|
||||||
|
|
||||||
def build_plugin_data(classify: dict[str, list[Item]]) -> list[dict[str, str]]:
|
|
||||||
"""构建前端插件数据
|
|
||||||
|
|
||||||
参数:
|
|
||||||
classify: 插件数据
|
|
||||||
|
|
||||||
返回:
|
|
||||||
list[dict[str, str]]: 前端插件数据
|
|
||||||
"""
|
|
||||||
classify = dict(sorted(classify.items(), key=lambda x: len(x[1]), reverse=True))
|
|
||||||
menu_key = next(iter(classify.keys()))
|
|
||||||
max_data = classify[menu_key]
|
|
||||||
del classify[menu_key]
|
|
||||||
plugin_list = [
|
|
||||||
{
|
|
||||||
"name": "主要功能" if menu in ["normal", "功能"] else menu,
|
|
||||||
"items": value,
|
|
||||||
}
|
|
||||||
for menu, value in classify.items()
|
|
||||||
]
|
|
||||||
plugin_list = build_line_data(plugin_list)
|
|
||||||
plugin_list.insert(
|
|
||||||
0,
|
|
||||||
build_plugin_line(
|
|
||||||
menu_key if menu_key not in ["normal", "功能"] else "主要功能",
|
|
||||||
max_data,
|
|
||||||
30,
|
|
||||||
100,
|
|
||||||
True,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return plugin_list
|
|
||||||
|
|
||||||
|
|
||||||
def build_plugin_line(
|
|
||||||
name: str, items: list, left: int, width: int | None = None, is_max: bool = False
|
|
||||||
) -> dict:
|
|
||||||
"""构造插件行数据
|
|
||||||
|
|
||||||
参数:
|
|
||||||
name: 菜单名称
|
|
||||||
items: 插件名称列表
|
|
||||||
left: 左边距
|
|
||||||
width: 总插件长度.
|
|
||||||
is_max: 是否为最大长度的插件菜单
|
|
||||||
|
|
||||||
返回:
|
|
||||||
dict: 插件数据
|
|
||||||
"""
|
|
||||||
_plugins = []
|
|
||||||
width = width or 50
|
|
||||||
if len(items) // 2 > 6 or is_max:
|
|
||||||
width = 100
|
|
||||||
plugin_list1 = []
|
|
||||||
plugin_list2 = []
|
|
||||||
for i in range(len(items)):
|
|
||||||
if i % 2:
|
|
||||||
plugin_list1.append(items[i])
|
|
||||||
else:
|
|
||||||
plugin_list2.append(items[i])
|
|
||||||
_plugins = [(30, 50, plugin_list1), (0, 50, plugin_list2)]
|
|
||||||
else:
|
|
||||||
_plugins = [(left, 100, items)]
|
|
||||||
return {"name": name, "items": _plugins, "width": width}
|
|
||||||
|
|
||||||
|
|
||||||
def build_line_data(plugin_list: list[dict]) -> list[dict]:
|
|
||||||
"""构造插件数据
|
|
||||||
|
|
||||||
参数:
|
|
||||||
plugin_list: 插件列表
|
|
||||||
|
|
||||||
返回:
|
|
||||||
list[dict]: 插件数据
|
|
||||||
"""
|
|
||||||
left = 30
|
|
||||||
data = []
|
|
||||||
for plugin in plugin_list:
|
|
||||||
data.append(build_plugin_line(plugin["name"], plugin["items"], left))
|
|
||||||
if len(plugin["items"]) // 2 <= 6:
|
|
||||||
left = 15 if left == 30 else 30
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
async def build_zhenxun_image(
|
|
||||||
session: Uninfo, group_id: str | None, is_detail: bool
|
|
||||||
) -> bytes:
|
|
||||||
"""构造真寻帮助图片
|
|
||||||
|
|
||||||
参数:
|
|
||||||
bot_id: bot_id
|
|
||||||
group_id: 群号
|
|
||||||
is_detail: 是否详细帮助
|
|
||||||
"""
|
|
||||||
classify = await classify_plugin(group_id, is_detail, __handle_item)
|
|
||||||
plugin_list = build_plugin_data(classify)
|
|
||||||
platform = PlatformUtils.get_platform(session)
|
|
||||||
bot_id = BotConfig.get_qbot_uid(session.self_id) or session.self_id
|
|
||||||
bot_ava = PlatformUtils.get_user_avatar_url(bot_id, platform)
|
|
||||||
width = int(637 * 1.5) if is_detail else 637
|
|
||||||
title_font = int(53 * 1.5) if is_detail else 53
|
|
||||||
tip_font = int(19 * 1.5) if is_detail else 19
|
|
||||||
return await template_to_pic(
|
|
||||||
template_path=str((TEMPLATE_PATH / "ss_menu").absolute()),
|
|
||||||
template_name="main.html",
|
|
||||||
templates={
|
|
||||||
"data": {
|
|
||||||
"plugin_list": plugin_list,
|
|
||||||
"ava": bot_ava,
|
|
||||||
"width": width,
|
|
||||||
"font_size": (title_font, tip_font),
|
|
||||||
"is_detail": is_detail,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pages={
|
|
||||||
"viewport": {"width": width, "height": 453},
|
|
||||||
"base_url": f"file://{TEMPLATE_PATH}",
|
|
||||||
},
|
|
||||||
wait=2,
|
|
||||||
)
|
|
||||||
@@ -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,4 +49,23 @@ 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,
|
||||||
|
)
|
||||||
|
|
||||||
|
Config.add_plugin_config(
|
||||||
|
"hook",
|
||||||
|
"AUTH_HOOKS_CONCURRENCY_LIMIT",
|
||||||
|
5,
|
||||||
|
help="同步进入权限钩子最大并发数",
|
||||||
|
default_value=5,
|
||||||
|
type=int,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
|
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
|
||||||
|
|||||||