Merge pull request #1588 from HibiKier/dev

Dev
This commit is contained in:
HibiKier
2024-08-29 22:41:58 +08:00
committed by GitHub
265 changed files with 3448 additions and 18734 deletions
+17 -10
View File
@@ -1,5 +1,3 @@
SUPERUSERS=[""]
COMMAND_START=[""]
@@ -13,6 +11,16 @@ SESSION_EXPIRE_TIMEOUT=30
# 全局图片统一使用bytes发送,当真寻与协议端不在同一服务器上时为True
IMAGE_TO_BYTES = False
# 回复消息时自称
SELF_NICKNAME="小真寻"
# 数据库配置
# 示例: "postgres://user:password@127.0.0.1:5432/database"
DB_URL = ""
# 系统代理
# SYSTEM_PROXY = "http://127.0.0.1:7890"
PLATFORM_SUPERUSERS = '
{
"qq": [""],
@@ -22,6 +30,12 @@ PLATFORM_SUPERUSERS = '
DRIVER=~fastapi+~httpx+~websockets
# LOG_LEVEL=DEBUG
# 服务器和端口
HOST = 127.0.0.1
PORT = 8080
# kook adapter toekn
# kaiheila_bots =[{"token": ""}]
@@ -51,11 +65,4 @@ DRIVER=~fastapi+~httpx+~websockets
# '
# application_commands的{"*": ["*"]}代表将全部应用命令注册为全局应用命令
# {"admin": ["123", "456"]}则代表将admin命令注册为id是123、456服务器的局部命令,其余命令不注册
# LOG_LEVEL=DEBUG
# 服务器和端口
HOST = 127.0.0.1
PORT = 8080
# {"admin": ["123", "456"]}则代表将admin命令注册为id是123、456服务器的局部命令,其余命令不注册
+56
View File
@@ -0,0 +1,56 @@
name: 检查bot是否运行正常
on:
push:
branches: ["dev", "main"]
pull_request:
branches: ["dev", "main"]
jobs:
bot-check:
runs-on: ubuntu-latest
name: bot check
steps:
- uses: actions/checkout@v4
- name: Setup Python
id: setup_python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install Poetry
run: pip install poetry
# Poetry cache depends on OS, Python version and Poetry version.
- name: Cache Poetry cache
id: cache-poetry
uses: actions/cache@v3
with:
path: ~/.cache/pypoetry
key: poetry-cache-${{ runner.os }}-${{ steps.setup_python.outputs.python-version }}
- name: Cache Data cache
uses: actions/cache@v3
with:
path: data
key: data-cache-${{ runner.os }}-${{ steps.setup_python.outputs.python-version }}
- name: Install dependencies
if: steps.cache-poetry.outputs.cache-hit != 'true'
run: |
rm -rf poetry.lock
poetry source remove ali
poetry install --no-root
poetry run pip install pydantic==1.10
- name: Check bot run
id: bot_check_run
run: |
mv scripts/bot_check.py bot_check.py
sed -i "s|^.*\?DB_URL.*|DB_URL=\"${{ env.DB_URL }}\"|g" .env.dev
sed -i "s/^.*\?LOG_LEVEL.*/LOG_LEVEL=${{ env.LOG_LEVEL }}/g" .env.dev
poetry run python3 bot_check.py
env:
DB_URL: "sqlite://:memory:"
LOG_LEVEL: DEBUG
+33
View File
@@ -0,0 +1,33 @@
default_install_hook_types: [pre-commit, prepare-commit-msg]
ci:
autofix_commit_msg: ":rotating_light: auto fix by pre-commit hooks"
autofix_prs: true
autoupdate_branch: dev
autoupdate_schedule: weekly
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.6
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
stages: [commit]
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
stages: [commit]
- repo: https://github.com/psf/black
rev: 24.8.0
hooks:
- id: black
stages: [commit]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
hooks:
- id: prettier
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
stages: [commit]
+151 -159
View File
@@ -14,12 +14,10 @@
<div align=center>
[![license](https://img.shields.io/badge/license-AGPL3.0-FE7D37)](https://github.com/HibiKier/zhenxun_bot/blob/main/LICENSE)
[![tencent-qq](https://img.shields.io/badge/%E7%BE%A4-是真寻酱哒-red?style=logo=tencent-qq)](https://jq.qq.com/?_wv=1027&k=u8PgBkMZ)
[![tencent-qq](https://img.shields.io/badge/%E7%BE%A4-真寻的技术群-c73e7e?style=logo=tencent-qq)](https://qm.qq.com/q/YYYt5rkMYc)
</div>
<div align=center>
@@ -34,7 +32,6 @@
</div>
<div align=center>
“真寻是<strong>[椛椛](https://github.com/FloatTech/ZeroBot-Plugin)</strong>的好朋友!”
@@ -46,7 +43,7 @@
| :---: | :---: | :---: | :---: |
| [LLOneBot](https://github.com/LLOneBot/LLOneBot) | NTQQ | linyuchen | 可用 |
| [Napcat](https://github.com/NapNeko/NapCatQQ) | NTQQ | NapNeko | 可用 |
| [Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core) | ? | LagrangeDev/Linwenxuan04 | 可用
| [Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core) | | LagrangeDev/Linwenxuan04 | 可用
</div>
@@ -71,6 +68,7 @@
这是一个免费的,版本为dev的zhenxun,你可以通过napcat或拉格朗日等直接连接用于体验与测试
(球球了测试君!)
```
Url: 43.143.112.57:11451/onebot/v11/ws
AccessToken: PUBLIC_ZHENXUN_TEST
@@ -82,14 +80,16 @@ AccessToken: PUBLIC_ZHENXUN_TEST
<div align=center>
“不要害怕,你的背后还有千千万万的 <strong>伙伴</strong> 啊!”
“不要害怕,你的背后还有千千万万的 <strong>伙伴</strong> 啊!”
| 项目名称 | 主要用途 | 仓库作者 | 备注 |
| :---: | :---: | :---: | :---: |
| [WebUi](https://github.com/HibiKier/zhenxun_bot_webui) | 管理 | [hibikier](https://github.com/HibiKier) | 基于真寻WebApi的webui实现
| [一键安装](https://github.com/zhenxun-org/zhenxun_bot-deploy) | 安装 | [AkashiCoin](https://github.com/AkashiCoin) | 新版本未测试
| [Docker单机版](https://github.com/Sakuracio/zhenxun_bot_docker) | 安装 | [zhenxun-org](https://github.com/zhenxun-org) | 新版本未测试
| [Docker全量版](https://shields.io/badge/GITHUB-SinKy--Yan-4476AF?logo=github&style=for-the-badge) | 安装 | [zhenxun-org](https://github.com/zhenxun-org) | 包含 真寻Bot PostgreSQL数据库 go-cqhttp webui等(新版本未测试)
| 项目名称 | 主要用途 | 仓库作者 | 备注 |
| :-----------------------------------------------------------------------------------------------: | :------: | :-------------------------------------------: | :-------------------------------------------------------------: |
| [插件库](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) | 扩展插件索引库 |
| [WebUi](https://github.com/HibiKier/zhenxun_bot_webui) | 管理 | [hibikier](https://github.com/HibiKier) | 基于真寻WebApi的webui实现 |
| [一键安装](https://github.com/zhenxun-org/zhenxun_bot-deploy) | 安装 | [AkashiCoin](https://github.com/AkashiCoin) | 新版本未测试 |
| [Docker单机版](https://github.com/Sakuracio/zhenxun_bot_docker) | 安装 | [zhenxun-org](https://github.com/zhenxun-org) | 新版本未测试 |
| [Docker全量版](https://shields.io/badge/GITHUB-SinKy--Yan-4476AF?logo=github&style=for-the-badge) | 安装 | [zhenxun-org](https://github.com/zhenxun-org) | 包含 真寻Bot PostgreSQL数据库 go-cqhttp webui等(新版本未测试) |
PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可能无法正常启动全量版容器**
@@ -112,11 +112,11 @@ PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可
## ~~来点优点?~~ 可爱难道还不够吗
* 实现了许多功能,且提供了大量功能管理命令
* 通过Config配置项将所有插件配置统计保存至config.yaml,利于统一用户修改
* 方便增删插件,原生nonebot2 matcher,不需要额外修改,仅仅通过简单的配置属性就可以生成`帮助图片`和`帮助信息`
* 提供了cd,阻塞,每日次数等限制,仅仅通过简单的属性就可以生成一个限制,例如:`PluginCdBlock` 等
* **..... 更多详细请通过`传送门`查看文档!**
- 实现了许多功能,且提供了大量功能管理命令
- 通过Config配置项将所有插件配置统计保存至config.yaml,利于统一用户修改
- 方便增删插件,原生nonebot2 matcher,不需要额外修改,仅仅通过简单的配置属性就可以生成`帮助图片`和`帮助信息`
- 提供了cd,阻塞,每日次数等限制,仅仅通过简单的属性就可以生成一个限制,例如:`PluginCdBlock` 等
- **..... 更多详细请通过`传送门`查看文档!**
## 简单部署
@@ -138,8 +138,7 @@ python bot.py
# 在Linux系统,你可能还需要运行此命令安装playwright依赖
playwright install-deps
# 首次后会在data目录下生成database.json和config.yaml文件
# database.json用户配置数据库信息
# 首次后会在data目录下生成config.yaml文件
# config.yaml用户配置插件
```
@@ -157,20 +156,13 @@ playwright install-deps
"kaiheila": [],
"discord": []
}
'
'
# 此处填写你的数据库地址
# 示例: "postgres://user:password@127.0.0.1:5432/database"
DB_URL = "" # 数据库地址
2.在data/database.json文件中修改数据库配置
{
"bind": "",
"sql_name": "postgres",
"user": "", # 用户们
"password": "", # 密码
"address": "", # 数据库地址ip
"port": "", # 数据库端口
"database": "" # 数据库名称
}
3.在configs/config.yaml文件中 # 该文件需要启动一次后生成
2.在configs/config.yaml文件中 # 该文件需要启动一次后生成
* 修改插件配置项
```
@@ -182,146 +174,146 @@ playwright install-deps
### 已实现的常用功能
* [x] 昵称系统(群与群与私聊分开.)
- [x] 昵称系统(群与群与私聊分开.)
* [x] 图灵AI(会把'你'等关键字替换为你的昵称),且带有 [AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus),够味
* [x] 签到/我的签到/好感度排行/好感度总排行(影响色图概率和开箱次数,支持配置)
* [x] 发送某文件夹下的随机图片(支持自定义,默认:美图,萝莉,壁纸)
* [x] 色图(这不是基础功能嘛喂)
* [x] coser
* [x] 黑白草图生成器
* [x] 鸡汤/语录
* [x] 骂我(钉宫语音)
* [x] 戳一戳(概率发送美图,钉宫语音或者戳回去)
* [x] 模拟开箱/我的开箱/群开箱统计/我的金色/设置cookie(csgo,内置爬虫脚本,需要提前抓取数据和图片,需要session,可能需要代理,阿里云服务器等ip也许已经被ban了(我无代理访问失败),如果访问太多账号API调用可能被禁止访问api!)
* [x] 鲁迅说过
* [x] 构造假消息(自定义的分享链接)
* [x] 商店/我的金币/购买道具/使用道具
* [x] 8种手游抽卡 (查看 [nonebot_plugin_gamedraw](https://github.com/HibiKier/nonebot_plugin_gamedraw))
* [x] 我有一个朋友想问问..(借鉴pcrbot插件)
* [x] 原神黄历
* [x] 原神今日素材
* [x] 原神资源查询 (借鉴[Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot)插件)
* [x] 原神便笺查询
* [x] 原神玩家查询
* [x] 原神树脂提醒
* [x] 原神签到/自动签到
* [x] 金币红包
* [x] 微博热搜
* [x] B站主播/UP/番剧订阅
- [x] 图灵AI(会把'你'等关键字替换为你的昵称),且带有 [AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus),够味
- [x] 签到/我的签到/好感度排行/好感度总排行(影响色图概率和开箱次数,支持配置)
- [x] 发送某文件夹下的随机图片(支持自定义,默认:美图,萝莉,壁纸)
- [x] 色图(这不是基础功能嘛喂)
- [x] coser
- [x] 黑白草图生成器
- [x] 鸡汤/语录
- [x] 骂我(钉宫语音)
- [x] 戳一戳(概率发送美图,钉宫语音或者戳回去)
- [x] 模拟开箱/我的开箱/群开箱统计/我的金色/设置cookie(csgo,内置爬虫脚本,需要提前抓取数据和图片,需要session,可能需要代理,阿里云服务器等ip也许已经被ban了(我无代理访问失败),如果访问太多账号API调用可能被禁止访问api!)
- [x] 鲁迅说过
- [x] 构造假消息(自定义的分享链接)
- [x] 商店/我的金币/购买道具/使用道具
- [x] 8种手游抽卡 (查看 [nonebot_plugin_gamedraw](https://github.com/HibiKier/nonebot_plugin_gamedraw))
- [x] 我有一个朋友想问问..(借鉴pcrbot插件)
- [x] 原神黄历
- [x] 原神今日素材
- [x] 原神资源查询 (借鉴[Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot)插件)
- [x] 原神便笺查询
- [x] 原神玩家查询
- [x] 原神树脂提醒
- [x] 原神签到/自动签到
- [x] 金币红包
- [x] 微博热搜
- [x] B站主播/UP/番剧订阅
* [x] pil对图片的一些操作
* [x] BUFF饰品底价查询(需要session)
* [x] 天气查询
* [x] 疫情查询
* [x] bt磁力搜索(咳咳,这功能我想dddd)
* [x] reimu搜索(上车) (使用[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)的插件)
* [x] 靠图识番 (使用[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)的插件)
* [x] 以图搜图 (使用[nonebot_plugin_picsearcher](https://github.com/synodriver/nonebot_plugin_picsearcher)插件)
* [x] 搜番
* [x] 点歌 [nonebot_plugin_songpicker2](https://github.com/maxesisn/nonebot_plugin_songpicker2)插件(删除了选歌和评论)
* [x] epic免费游戏
* [x] p站排行榜
* [x] p站搜图
* [x] 翻译(日英韩)
* [x] pix图库(一个自己的图库,含有增删查改,黑名单等命令)
- [x] pil对图片的一些操作
- [x] BUFF饰品底价查询(需要session)
- [x] 天气查询
- [x] 疫情查询
- [x] bt磁力搜索(咳咳,这功能我想dddd)
- [x] reimu搜索(上车) (使用[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)的插件)
- [x] 靠图识番 (使用[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)的插件)
- [x] 以图搜图 (使用[nonebot_plugin_picsearcher](https://github.com/synodriver/nonebot_plugin_picsearcher)插件)
- [x] 搜番
- [x] 点歌 [nonebot_plugin_songpicker2](https://github.com/maxesisn/nonebot_plugin_songpicker2)插件(删除了选歌和评论)
- [x] epic免费游戏
- [x] p站排行榜
- [x] p站搜图
- [x] 翻译(日英韩)
- [x] pix图库(一个自己的图库,含有增删查改,黑名单等命令)
* [x] 查看当前群欢迎消息
* [x] 查看该群自己的权限
* [x] 我的信息(只是为了看看什么时候入群)
* [x] 更新信息(如果继续更新的话)
* [x] go-cqhttp最新版下载和上传(不需要请删除)
* [x] 撤回
* [x] 滴滴滴-(用户对超级用户发送消息)
* [x] 金币红包/金币排行
* [x] 俄罗斯轮盘/胜场排行/败场排行/欧洲人排行/慈善家排行
* [x] 网易云热评
* [x] 念首古诗
* [x] 获取b站视频封面
* [x] 通过PID获取图片
* [x] 功能统计可视化
* [x] 词云
* [x] 关于
- [x] 查看当前群欢迎消息
- [x] 查看该群自己的权限
- [x] 我的信息(只是为了看看什么时候入群)
- [x] 更新信息(如果继续更新的话)
- [x] go-cqhttp最新版下载和上传(不需要请删除)
- [x] 撤回
- [x] 滴滴滴-(用户对超级用户发送消息)
- [x] 金币红包/金币排行
- [x] 俄罗斯轮盘/胜场排行/败场排行/欧洲人排行/慈善家排行
- [x] 网易云热评
- [x] 念首古诗
- [x] 获取b站视频封面
- [x] 通过PID获取图片
- [x] 功能统计可视化
- [x] 词云
- [x] 关于
### 已实现的管理员功能
* [x] 更新群组成员信息
- [x] 更新群组成员信息
* [x] 95%的群功能开关
* [x] 查看群内被动技能状态
* [x] 自定义群欢迎消息(是真寻的不是管家的!)
* [x] .ban/.unban(支持设置ban时长)= 黑白名单
* [x] 刷屏禁言相关:刷屏检测设置/设置禁言时长/设置检测次数
* [x] 上传图片/连续上传图片 (上传图片至指定图库)
* [x] 移动图片 (同上)
* [x] 删除图片 (同上)
* [x] 群内B站订阅
* [x] 词条设置
* [x] 休息吧/醒来
- [x] 95%的群功能开关
- [x] 查看群内被动技能状态
- [x] 自定义群欢迎消息(是真寻的不是管家的!)
- [x] .ban/.unban(支持设置ban时长)= 黑白名单
- [x] 刷屏禁言相关:刷屏检测设置/设置禁言时长/设置检测次数
- [x] 上传图片/连续上传图片 (上传图片至指定图库)
- [x] 移动图片 (同上)
- [x] 删除图片 (同上)
- [x] 群内B站订阅
- [x] 词条设置
- [x] 休息吧/醒来
### 已实现的超级用户功能
* [x] 添加/删除权限(是真寻的管理员权限,不是群管理员)
- [x] 添加/删除权限(是真寻的管理员权限,不是群管理员)
* [x] 开启/关闭指定群的广播通知
* [x] 广播
* [x] 自检(检查系统状态)
* [x] 所有群组/所有好友
* [x] 退出指定群
* [x] 更新好友信息/更新群信息
* [x] /t(对用户进行回复或发送消息)
* [x] 上传/删除/修改商品(需要编写对应的商品功能)
* [x] 节日红包发送
* [x] 修改群权限
* [x] ban
* [x] 更新色图
* [x] 更新价格/更加图片(csgo开箱)
* [x] 重载原神/方舟/赛马娘/坎公骑冠剑卡池
* [x] 更新原神今日素材/更新原神资源信息
* [x] PIX相关操作
* [x] 检查更新真寻
* [x] 重启
* [x] 添加/删除/查看群白名单
* [x] 功能开关(更多设置)
* [x] 功能状态
* [x] b了
* [x] 执行sql
* [x] 重载配置
* [x] 清理临时数据
* [x] 增删群认证
* [x] 同意/拒绝好友/群聊请求
* [x] 配置重载
- [x] 开启/关闭指定群的广播通知
- [x] 广播
- [x] 自检(检查系统状态)
- [x] 所有群组/所有好友
- [x] 退出指定群
- [x] 更新好友信息/更新群信息
- [x] /t(对用户进行回复或发送消息)
- [x] 上传/删除/修改商品(需要编写对应的商品功能)
- [x] 节日红包发送
- [x] 修改群权限
- [x] ban
- [x] 更新色图
- [x] 更新价格/更加图片(csgo开箱)
- [x] 重载原神/方舟/赛马娘/坎公骑冠剑卡池
- [x] 更新原神今日素材/更新原神资源信息
- [x] PIX相关操作
- [x] 检查更新真寻
- [x] 重启
- [x] 添加/删除/查看群白名单
- [x] 功能开关(更多设置)
- [x] 功能状态
- [x] b了
- [x] 执行sql
- [x] 重载配置
- [x] 清理临时数据
- [x] 增删群认证
- [x] 同意/拒绝好友/群聊请求
- [x] 配置重载
#### 超级用户的被动技能
* [x] 邀请入群提醒(别人邀请真寻入群)
- [x] 邀请入群提醒(别人邀请真寻入群)
* [x] 添加好友提醒(别人添加真寻好友)
- [x] 添加好友提醒(别人添加真寻好友)
### 已实现的被动技能
* [x] 进群欢迎消息
- [x] 进群欢迎消息
* [x] 群早晚安
* [x] 每日开箱重置提醒
* [x] b站转发解析(解析b站分享信息,支持bv,bilibili链接,b站手机端转发卡片,cv,b23.tv),且5分钟内不解析相同url
* [x] 丢人爬(爬表情包)
* [x] epic通知(每日发送epic免费游戏链接)
* [x] 原神黄历提醒
* [x] 复读
- [x] 群早晚安
- [x] 每日开箱重置提醒
- [x] b站转发解析(解析b站分享信息,支持bv,bilibili链接,b站手机端转发卡片,cv,b23.tv),且5分钟内不解析相同url
- [x] 丢人爬(爬表情包)
- [x] epic通知(每日发送epic免费游戏链接)
- [x] 原神黄历提醒
- [x] 复读
### 已实现的看不见的技能
* [x] 刷屏禁言检测
- [x] 刷屏禁言检测
* [x] 功能调用统计
* [x] 检测恶意触发命令(将被最高权限ban掉30分钟,只有最高权限(9级)可以进行unban)
* [x] 自动同意好友请求,加群请求将会提醒管理员,退群提示,加群欢迎等等
* [x] 群聊时间检测(当群聊最后一人发言时间大于当前36小时后将关闭该群所有通知(即被动技能))
* [x] 群管理员监控,自动为新晋管理员增加权限,为失去群管理员的用户删除权限
* [x] 群权限系统
* [x] 定时更新权限
* [x] 自动配置重载
- [x] 功能调用统计
- [x] 检测恶意触发命令(将被最高权限ban掉30分钟,只有最高权限(9级)可以进行unban)
- [x] 自动同意好友请求,加群请求将会提醒管理员,退群提示,加群欢迎等等
- [x] 群聊时间检测(当群聊最后一人发言时间大于当前36小时后将关闭该群所有通知(即被动技能))
- [x] 群管理员监控,自动为新晋管理员增加权限,为失去群管理员的用户删除权限
- [x] 群权限系统
- [x] 定时更新权限
- [x] 自动配置重载
</details>
@@ -333,7 +325,7 @@ playwright install-deps
### 感谢名单
(可以告诉我你的 **github** 地址,我偷偷换掉0v|)
(可以告诉我你的 **github** 地址,我偷偷换掉0v|)
[shenqi](https://afdian.net/u/fa923a8cfe3d11eba61752540025c377)
[A_Kyuu](https://afdian.net/u/b83954fc2c1211eba9eb52540025c377)
@@ -341,7 +333,7 @@ playwright install-deps
[投冥](https://afdian.net/a/144514mm)
[茶喵](https://afdian.net/u/fd22382eac4d11ecbfc652540025c377)
[AemokpaTNR](https://afdian.net/u/1169bb8c8a9611edb0c152540025c377)
[爱发电用户_wrxn](https://afdian.net/u/4aa03d20db4311ecb1e752540025c377)
[爱发电用户\_wrxn](https://afdian.net/u/4aa03d20db4311ecb1e752540025c377)
[qqw](https://afdian.net/u/b71db4e2cc3e11ebb76652540025c377)
[溫一壺月光下酒](https://afdian.net/u/ad667a5c650c11ed89bf52540025c377)
[伝木](https://afdian.net/u/246b80683f9511edba7552540025c377)
@@ -356,21 +348,21 @@ playwright install-deps
[咸鱼鱼鱼鱼](https://afdian.net/u/8e39b9a400e011ed9f4a52540025c377)
[Kafka](https://afdian.net/u/41d66798ef6911ecbc5952540025c377)
[墨然](https://afdian.net/u/8aa5874a644d11eb8a6752540025c377)
[爱发电用户_T9e4](https://afdian.net/u/2ad1bb82f3a711eca22852540025c377)
[爱发电用户\_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)
[爱发电用户\_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)
[爱发电用户\_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)
[爱发电用户\_Bc6j](https://afdian.net/u/8546be24f44111eca64052540025c377)
[大魔王](https://github.com/xipesoy)
[CopilotLaLaLa](https://github.com/CopilotLaLaLa)
[嘿小欧](https://afdian.net/u/daa4bec4f24911ec82e552540025c377)
@@ -378,7 +370,7 @@ playwright install-deps
[十年くん](https://github.com/shinianj)
[哇](https://afdian.net/u/9b266244f23911eca19052540025c377)
[yajiwa](https://github.com/yajiwa)
[爆金币](https://afdian.net/u/0d78879ef23711ecb22452540025c377)
[爆金币](https://afdian.net/u/0d78879ef23711ecb22452540025c377)
</details>
@@ -386,7 +378,7 @@ playwright install-deps
### 2024/8/11
* 更新dev
- 更新dev
<!-- ### 2024/1/25
@@ -828,7 +820,7 @@ playwright install-deps
## Todo
* [x] web管理
- [x] web管理
## 感谢
+3 -2
View File
@@ -5,9 +5,10 @@ from nonebot.adapters.dodo import Adapter as DoDoAdapter
from nonebot.adapters.kaiheila import Adapter as KaiheilaAdapter
from nonebot.adapters.onebot.v11 import Adapter as OneBotV11Adapter
nonebot.init()
from zhenxun.services.db_context import disconnect, init
nonebot.init()
driver = nonebot.get_driver()
driver.register_adapter(OneBotV11Adapter)
driver.register_adapter(KaiheilaAdapter)
@@ -18,7 +19,7 @@ driver.register_adapter(DoDoAdapter)
driver.on_startup(init)
driver.on_shutdown(disconnect)
nonebot.load_builtin_plugins("echo") # 内置插件
# nonebot.load_builtin_plugins("echo")
nonebot.load_plugins("zhenxun/builtin_plugins")
nonebot.load_plugins("zhenxun/plugins")
+86 -2
View File
@@ -4,6 +4,7 @@ version = "0.1.1"
description = "基于 Nonebot2 和 go-cqhttp 开发,以 postgresql 作为数据库,非常可爱的绪山真寻bot"
authors = ["HibiKier <775757368@qq.com>"]
license = "AGPL"
package-mode = false
[[tool.poetry.source]]
name = "ali"
@@ -15,7 +16,7 @@ python = "^3.10"
playwright = "^1.41.1"
nonebot-adapter-onebot = "^2.3.1"
nonebot-plugin-apscheduler = "^0.3.0"
tortoise-orm = {extras = ["asyncpg"], version = "^0.20.0"}
tortoise-orm = { extras = ["asyncpg"], version = "^0.20.0" }
cattrs = "^23.2.3"
ruamel-yaml = "^0.18.5"
strenum = "^0.4.15"
@@ -43,11 +44,94 @@ cn2an = "^0.5.22"
aiohttp = "^3.9.5"
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"
nonebot-plugin-alconna = "^0.51.1"
[tool.poetry.dev-dependencies]
nonebug = "^0.3.2"
pytest-cov = "^5.0.0"
pytest-mock = "^3.6.1"
pytest-asyncio = "^0.23.5"
pytest-xdist = "^3.3.1"
respx = "^0.21.1"
[tool.nonebot]
plugins = [
"nonebot_plugin_apscheduler",
"nonebot_plugin_session",
"nonebot_plugin_htmlrender",
"nonebot_plugin_userinfo",
"nonebot_plugin_alconna",
]
plugin_dirs = ["zhenxun/services", "zhenxun/builtin_plugins", "zhenxun/plugins"]
adapters = [
{ name = "OneBot V11", module_name = "nonebot.adapters.onebot.v11" },
{ name = "DoDo", module_name = "nonebot.adapters.dodo" },
{ name = "开黑啦", module_name = "nonebot.adapters.kaiheila" },
]
[tool.black]
line-length = 88
target-version = ["py39", "py310", "py311", "py312"]
include = '\.pyi?$'
extend-exclude = '''
'''
[tool.isort]
profile = "black"
line_length = 88
length_sort = true
skip_gitignore = true
force_sort_within_sections = true
src_paths = ["zhenxun", "tests"]
extra_standard_library = ["typing_extensions"]
[tool.ruff]
line-length = 88
target-version = "py310"
[tool.ruff.lint]
select = [
"F", # Pyflakes
"W", # pycodestyle warnings
"E", # pycodestyle errors
"UP", # pyupgrade
"ASYNC", # flake8-async
"C4", # flake8-comprehensions
"T10", # flake8-debugger
"T20", # flake8-print
"PYI", # flake8-pyi
"PT", # flake8-pytest-style
"Q", # flake8-quotes
"RUF", # Ruff-specific rules
]
ignore = [
"E402", # module-import-not-at-top-of-file
"UP037", # quoted-annotation
"RUF001", # ambiguous-unicode-character-string
"RUF002", # ambiguous-unicode-character-docstring
"RUF003", # ambiguous-unicode-character-comment
]
[tool.ruff.lint.flake8-pytest-style]
fixture-parentheses = false
mark-parentheses = false
[tool.pyright]
pythonVersion = "3.10"
pythonPlatform = "All"
defineConstant = { PYDANTIC_V2 = true }
executionEnvironments = [
{ root = "./tests", extraPaths = [
"./",
] },
{ root = "./" },
]
typeCheckingMode = "standard"
reportShadowedImports = false
disableBytesTypePromotions = true
[build-system]
requires = ["poetry-core>=1.0.0"]
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 728 KiB

After

Width:  |  Height:  |  Size: 2.0 MiB

+7
View File
@@ -0,0 +1,7 @@
body {
position: absolute;
left: -8px;
top: -8px;
}
+50
View File
@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ECharts Example</title>
<!-- 引入ECharts -->
<script src="https://cdn.bootcss.com/echarts/4.2.1/echarts.min.js"></script>
<link rel="stylesheet" href="main.css">
</head>
<body>
<!-- 图表容器 -->
<div id="main" style="width: 1000px;height:500px;"></div>
<script type="text/javascript">
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('main'));
// 指定图表的配置项和数据
var option = {
xAxis: {
type: 'value',
boundaryGap: [0, 0.01]
},
yAxis: {
type: 'category',
data: {{data.category_data|tojson}}
},
series: [
{
data: {{data.data|tojson}},
type: 'bar',
itemStyle: {
// 为每个柱子设置随机颜色
color: function () {
// 生成一个随机颜色
return 'rgb(' +
Math.round(Math.random() * 255) + ',' +
Math.round(Math.random() * 255) + ',' +
Math.round(Math.random() * 255) + ')';
}
}
}
]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
</script>
</body>
</html>
+285
View File
@@ -0,0 +1,285 @@
@font-face {
font-family: cr105Font;
/* 导入的字体文件 */
src: url("./res/font/ChillReunion_105S.otf");
}
@font-face {
font-family: cr95Font;
/* 导入的字体文件 */
src: url("./res/font/ChillReunion_95.otf");
}
@font-face {
font-family: cr65sFont;
/* 导入的字体文件 */
src: url("./res/font/ChillReunion_65S.otf");
}
@font-face {
font-family: shFont;
/* 导入的字体文件 */
src: url("./res/font/SourceHanSansSC-Bold.otf");
}
@font-face {
font-family: rxxxtFont;
/* 导入的字体文件 */
src: url("./res/font/rxxxkat.ttf");
}
@font-face {
font-family: kcytFont;
/* 导入的字体文件 */
src: url("./res/font/jcyt.ttf");
}
@font-face {
font-family: yshsFont;
/* 导入的字体文件 */
src: url("/resources/font/YSHaoShenTi-2.ttf");
}
body {
position: absolute;
left: -8px;
top: -8px;
}
.wrapper{
height: 926px;
width: 465px;
background-color: #FBE4E4;
position: relative;
font-family: 'cr105Font'
}
.avatar {
height: 120px;
width: 120px;
border-radius: 50%;
margin-top: 38px;
margin-left: 40px;
box-shadow: 0px 0 10px 3px #D6A7A7;
}
.avatar-img {
height: 120px;
width: 120px;
border-radius: 50%;
}
.top-head {
width: 100%;
height: 179px;
color: #D37B8D;
}
.nickname {
margin-top: 43px;
margin-left: 30px;
font-size: 47px;
height: 57px;
align-items: center;
justify-content: center;
display: flex
}
.uid {
font-size: 21px;
position: absolute;
width: 255px;
top: 84px;
left: 30px;
font-family: 'cr95Font'
}
.rl-img {
height: 90px;
width: 90px;
position: absolute;
top: 27px;
left: 30px;
}
.zx-img {
height:238px;
width: 120px;
left: 306px;
position: absolute;
z-index: 999;
}
.text-day {
font-size: 25px;
margin-left: 130px;
margin-top: 46px;
font-family: 'shFont';
}
.text-zx {
font-family: 'cr65sFont';
position: absolute;
top: 72px;
left: 130px;
}
.sign-content{
width: 100%;
height: 160px;
position: relative;
}
.sign-top {
background-color: #D47E8F;
width:445px;
height: 150px;
position: absolute;
left: 9px;
color: #FBE4E4;
}
.sign-bottom {
background-color: #953B50;
width: 445px;
height: 150px;
position: absolute;
top: 10px;
left: 15px
}
.qian {
position: absolute;
left: 15px;
top: 16px;
height: 132px;
}
.sign-data{
width: 100%;
height: 207px;
font-family: 'kcytFont';
position: relative;
color: #D47E8F;
}
.today-text {
font-size: 50px;
position: absolute;
top: -40px;
left: 130px;
}
.abs-text {
position: absolute;
font-size: 30px;
z-index: 2;
width: 465px;
}
.gift {
text-align: center;
/* float: left; */
/* margin-top: 150px; */
}
.sign-text {
font-size: 30px;
position: absolute;
top: 60px;
left: 140px;
}
.sign-num {
font-size: 30px;
position: absolute;
top: 60px;
}
.line {
background-color: #D1778A;
width: 435px;
height: 3px;
margin-left: 15px;
}
.bottom-foot{
width: 100%;
height: 376px;
position: relative;
font-family: 'rxxxtFont';
color: #D47E8F;
}
.heart-list {
position: absolute;
top: 76px;
left: 15px;
width: 435px;
}
.heart-img {
height: 30px;
width: 30px;
}
.cur-text {
font-size: 32px;
left: 10px;
top: -21px;
position: absolute;
}
.bot-text {
font-size: 30px;
position: absolute;
top: 85px;
left: 15px;
width: 435px;
}
.progress-border {
position: absolute;
top: 273px;
left: 19px;
width: 313px;
height: 30px;
border: #DF9DA8 1px solid;
}
.progress-bar {
height: 30px;
background-color: #D47E8F;
}
.weather-img {
width: 70px;
height: 70px;
position: absolute;
top: 37px;
left: 371px;
}
.mbl-img {
width: 132px;
height: 164px;
position: absolute;
top: 145px;
left: 333px;
}
.wd{
position: absolute;
font-size: 35px;
top: 60px;
left: 377px;
}
.date {
position: absolute;
font-size: 20px;
top: 350px;
left: 250px;
font-family: 'rxxxtFont';
width: 210px;
}
+78
View File
@@ -0,0 +1,78 @@
</html>
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>test</title>
<!-- <link rel="stylesheet" href="./res/font-awesome/css/font-awesome.min.css"> -->
<link rel="stylesheet" href="main.css">
</head>
<body>
<div class="wrapper">
<div class="top-head">
<div style="display: flex;">
<div class="avatar">
<img class="avatar-img" src="{{data.ava}}" alt="">
</div>
<div style="position: relative;">
<p class="nickname" style="font-size: {{ data.font_size }}px;">{{data.name}}</p>
<p class="uid">UID:{{data.uid}}</p>
</div>
</div>
</div>
<div class="sign-content">
<div class="sign-bottom">
</div>
<div class="sign-top">
<img class="rl-img" class="avatar-img" src="res/img/rl.png" alt="">
<img class="zx-img" class="avatar-img" src="res/img/1.png" alt="">
<p class="text-day">累计签到{{data.sign_count}}天</p>
<p class="text-zx">{{data.message}}</p>
</div>
</div>
<div class="sign-data">
<img class="qian" src="res/img/3.png" alt="">
<p class="today-text">今日签到</p>
<div class="abs-text" style="top: 42px;">
<p class="gift">{{data.impression}}</p>
</div>
<div class="abs-text" style="top: 83px;">
<p class="gift">{{data.gold}}</p></div>
<div class="abs-text" style="top: 123px;">
<p class="gift">{{data.gift}}</p>
</div>
</div>
<div class="line"></div>
<div class="bottom-foot">
<p class="cur-text">当前好感度: {{data.cur_impression}}</p>
<div class="heart-list">
{% for i in data.heart2 %}
<img class="heart-img" src="res/img/h2.png" alt=""/>
{% endfor %}
{% for i in data.heart1 %}
<img class="heart-img" src="res/img/h1.png" alt=""/>
{% endfor %}
</div>
<div class="bot-text">
<p style="position: absolute; top: 9px">好感度等级: {{data.level}}</p>
<p style="position: absolute; top: 58px">{{data.attitude}}</p>
<p style="position: absolute; top: 111px"">距离升级还差{{data.interpolation}}好感度</p>
</div>
<div class="progress-border">
<div class="progress-bar" style="width: {{ data.process }}%;"></div>
</div>
<img class="weather-img" src="res/img/weather/0.png" alt="">
<p class="wd" >28℃</p>
<img class="mbl-img" src="res/img/2.png" alt="">
<div class="date">
{{data.date}}
</div>
</div>
</div>
</body>
<script type="text/javascript" src="main.js">
</script>
</html>
+17
View File
@@ -0,0 +1,17 @@
let wdDom = document.querySelector(".wd")
let wd = Math.floor(Math.random() * 40) + 1
wdDom.innerHTML = wd + "℃"
let weatherDom = document.querySelector(".weather-img")
let r = Math.floor(Math.random() * 12)
weatherDom.src = "res/img/weather/" + r + ".png"
let qianDom = document.querySelector(".qian")
let r1 = Math.floor(Math.random() * 6)
qianDom.src = "res/img/tag/" + r1 + ".png"
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+142
View File
@@ -0,0 +1,142 @@
@font-face {
font-family: fzrzFont;
/* 导入的字体文件 */
src: url("./res/font/fzrzExtraBold.ttf");
}
@font-face {
font-family: syhtFont;
/* 导入的字体文件 */
src: url("./res/font/syht.otf");
}
@font-face {
font-family: systFont;
/* 导入的字体文件 */
src: url("./res/font/syst.otf");
}
body {
position: absolute;
left: -8px;
top: -8px;
}
.wrapper{
/* height: 1321px; */
width: 637px;
background-image: url('res/img/bk.jpg');
background-size: cover;
position: relative;
font-family: 'cr105Font';
display: flex;
justify-content: center;
align-items: center;
padding: 25px 0;
}
.zx-img {
position: absolute;
width: 222px;
bottom: 0;
right: 3px;
z-index: 0;
}
.avatar {
height: 73px;
width: 73px;
border-radius: 50%;
position: absolute;
top: 153px;
left: 40px;
border: #F3868C 1px solid;
}
.avatar-img {
height: 73px;
width: 73px;
border-radius: 50%;
}
.main {
background-image: url('res/img/main.png');
background-size: 100% 100%;
/* background-size: cover; */
height: auto;
width: 595px;
position: relative;
}
.head-text {
font-size: 53px;
font-family: 'fzrzFont';
margin-left: 40px;
/* color: #F67186; */
background: linear-gradient(to right, #F67186, #F7889C);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.help-text {
font-size: 19px;
font-family: 'systFont';
margin-left: 40px;
/* color: #F67186; */
background: linear-gradient(to right, #F67186, #F7889C);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
position: absolute;
top: 118px;
left: -5px;
}
.menu-list {
width: 593px;
height: calc(100% - 230px);
/* background-color: blanchedalmond; */
/* margin-left: 35px; */
margin-top: 35px;
/* float: left; */
display: flex; /* 使用flex布局 */
flex-wrap: wrap;
}
.main-menu {
/* width: 100%; */
z-index: 1;
}
.title {
height: 45px;
width: 225px;
background-image: url('res/img/title.png');
background-size: cover;
background-repeat: no-repeat;
/* background: linear-gradient(to right, #F67186, #F7889C); */
font-family: 'fzrzFont';
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 30px;
border-radius: 16px;
letter-spacing:4px;
margin: 0 auto;
}
.plugin-list {
display: flex;
/* justify-content: center; */
/* align-items: center; */
font-family: 'syhtFont';
font-size: 25px;
z-index: 1;
}
+179
View File
@@ -0,0 +1,179 @@
</html>
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>test</title>
<!-- <link rel="stylesheet" href="./res/font-awesome/css/font-awesome.min.css"> -->
<link rel="stylesheet" href="main.css">
</head>
<body>
<div class="wrapper">
<div class="main">
<div style="margin-top: 30px;"></div>
<div class="head-text">真寻BOT的使用方法</div>
<div class="help-text">可以通过'帮助[功能名称或功能ID]'来获取对应功能的使用方法</div>
<div class="avatar">
<img class="avatar-img" src="{{data.ava}}" alt="">
</div>
<div class="menu-list">
{% for plugin in data['plugin_list'] %}
<div class='main-menu' style="width: {{ plugin.width }}%;">
<div class="title">{{plugin.name}}</div>
<div class=plugin-list>
{% for item_list in plugin['items'] %}
<ul style="margin-left: {{ item_list[0] }}px; margin-top: 23px; width: {{item_list[1]}}%;">
{% for item in item_list[2] %}
<li>{{item.plugin_name}}</li>
{% endfor %}
</ul>
{% endfor %}
</div>
</div>
{% endfor %}
<img src="./res/img/3.png" class="zx-img" />
</div>
<!-- <div class="menu-list">
<div class='main-menu' style="width: 100%;">
<div class="title">帮助</div>
<div class=plugin-list style="width: 100%;">
<ul style="margin-left: 30px;margin-top: 10px; width: 50%;">
<li>47-签到</li>
<li>43-鸡汤</li>
<li>81鲁迅说</li>
<li>69-磁力搜索</li>
<li>36-roll</li>
<li>77-钉宫骂我</li>
<li>68-网易云热评</li>
<li>44-pid搜索</li>
<li>56-我有一个朋友</li>
<li>42-敏感词文本监听</li>
<li>63-能不能好好说话</li>
</ul>
<ul style="margin-left: 0px;margin-top: 10px; width: 50%;">
<li>62-AI</li>
<li>5-古诗</li>
<li>10-词云</li>
<li>45-词库问答</li>
<li>12-微博热搜</li>
<li>45-金币红包</li>
<li>64-PIX添加</li>
<li>23-coser</li>
<li>42-B站视频解析</li>
<li>43-查看pix图库</li>
<li>55-一言二次元语录</li>
<li>55-epic免费游戏</li>
</ul>
</div>
</div>
<div class='main-menu' style="width: 50%;">
<div class="title">抽卡相关</div>
<div class=plugin-list>
<ul style="margin-left: 30px;margin-top: 10px; width: 50%;">
<li>111-游戏抽卡</li>
<li>44-CSGO开箱</li>
</ul>
</div>
</div>
<div class='main-menu' style="width: 50%;">
<div class="title">帮助2</div>
<div class=plugin-list>
<ul style="margin-left: 15px;margin-top: 10px; width: 50%;">
<li>53-识图</li>
<li>55-识番</li>
<li>13-翻译</li>
<li>55-搜番</li>
<li>22-b封面</li>
<li>41-BUFF皮肤查询</li>
</ul>
</div>
</div>
<div class='main-menu'>
<div class="title">帮助</div>
<div class=plugin-list>
<ul>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
</ul>
</div>
</div>
<div class='main-menu'>
<div class="title">帮助</div>
<div class=plugin-list>
<ul>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
</ul>
</div>
</div>
<div class='main-menu'>
<div class="title">帮助</div>
<div class=plugin-list>
<ul>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
</ul>
</div>
</div>
<div class='main-menu'>
<div class="title">帮助</div>
<div class=plugin-list>
<ul>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
</ul>
</div>
</div>
<div class='main-menu'>
<div class="title">帮助</div>
<div class=plugin-list>
<ul>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
</ul>
</div>
</div>
<div class='main-menu'>
<div class="title">帮助</div>
<div class=plugin-list>
<ul>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
</ul>
</div>
</div>
</div> -->
<img src="./res/img/3.png" class="zx-img" />
</div>
</div>
</body>
<script type="text/javascript" src="main.js">
</script>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

+63
View File
@@ -0,0 +1,63 @@
import os
import re
import asyncio
import nonebot
from nonebot.log import logger
# from nonebot.adapters.discord import Adapter as DiscordAdapter
from nonebot.adapters.dodo import Adapter as DoDoAdapter
from nonebot.adapters.kaiheila import Adapter as KaiheilaAdapter
from nonebot.adapters.onebot.v11 import Adapter as OneBotV11Adapter
nonebot.init()
from zhenxun.services.db_context import init, disconnect
driver = nonebot.get_driver()
driver.register_adapter(OneBotV11Adapter)
driver.register_adapter(KaiheilaAdapter)
driver.register_adapter(DoDoAdapter)
driver.on_startup(init)
driver.on_shutdown(disconnect)
# nonebot.load_builtin_plugins("echo")
nonebot.load_plugins("zhenxun/builtin_plugins")
nonebot.load_plugins("zhenxun/plugins")
all_plugins = [name.replace(":", ".") for name in nonebot.get_available_plugin_names()]
logger.info(f"所有插件:{all_plugins}")
loaded_plugins = tuple(
re.sub(r"^zhenxun\.(plugins|builtin_plugins)\.", "", plugin.module_name)
for plugin in nonebot.get_loaded_plugins()
)
logger.info(f"已加载插件:{loaded_plugins}")
for plugin in all_plugins.copy():
if plugin.startswith(("platform",)):
logger.info(f"平台插件:{plugin}")
elif plugin.endswith(loaded_plugins):
logger.info(f"已加载插件:{plugin}")
else:
logger.info(f"未加载插件:{plugin}")
continue
all_plugins.remove(plugin)
if all_plugins:
logger.info(f"出现未加载的插件:{all_plugins}")
exit(1)
else:
logger.info("所有插件均已加载")
@driver.on_startup
async def _():
task = asyncio.create_task(asyncio.sleep(1))
task.add_done_callback(lambda _: driver._lifespan.on_ready(os._exit(0)))
nonebot.run()
+37
View File
@@ -0,0 +1,37 @@
from pathlib import Path
import nonebot
import pytest
from nonebot.plugin import Plugin
from nonebug import NONEBOT_INIT_KWARGS
from nonebug.app import App
from pytest_mock import MockerFixture
from respx import MockRouter
def pytest_configure(config: pytest.Config) -> None:
config.stash[NONEBOT_INIT_KWARGS] = {
"driver": "~fastapi+~httpx+~websockets",
"superusers": ["AkashiCoin"],
"command_start": "",
"session_running_expression": "别急呀,小真寻要宕机了!QAQ",
"image_to_bytes": False,
"nickname": ["真寻", "小真寻", "绪山真寻", "小寻子"],
"session_expire_timeout": 30,
"self_nickname": "小真寻",
"db_url": "sqlite://:memory:",
"platform_superusers": {"qq": ["qq_su"], "dodo": ["dodo_su"]},
"host": "127.0.0.1",
"port": 8080,
}
@pytest.fixture(scope="session", autouse=True)
def load_plugin(nonebug_init: None) -> set[Plugin]:
return nonebot.load_plugins("zhenxun.plugins")
@pytest.fixture
async def app(app: App, tmp_path: Path, mocker: MockerFixture):
mocker.patch("nonebot.drivers.websockets.connect", return_value=MockRouter())
return app
+2 -2
View File
@@ -62,8 +62,8 @@ async def _():
group_user = []
try:
group_user = await GroupInfoUser.filter(uid__isnull=False).all()
except Exception:
logger.warning("获取GroupInfoUser数据uid失败...")
except Exception as e:
logger.warning("获取GroupInfoUser数据uid失败...", e=e)
user2uid = {u.user_id: u.uid for u in group_user}
db = Tortoise.get_connection("default")
old_sign_list = await db.execute_query_dict(SIGN_SQL)
@@ -7,7 +7,7 @@ from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
from nonebot_plugin_apscheduler import scheduler
from nonebot_plugin_session import EventSession
from zhenxun.configs.config import NICKNAME
from zhenxun.configs.config import BotConfig
from zhenxun.configs.utils import PluginExtraData
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
@@ -62,7 +62,7 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent):
if str(event.user_id) == bot.self_id:
await MemberUpdateManage.update(bot, str(event.group_id))
logger.info(
f"{NICKNAME}加入群聊更新群组信息",
f"{BotConfig.self_nickname}加入群聊更新群组信息",
"更新群组成员列表",
session=event.user_id,
group_id=event.group_id,
@@ -1,20 +1,18 @@
import time
from datetime import datetime, timedelta, timezone
from datetime import datetime, timezone, timedelta
from nonebot.adapters import Bot
# from nonebot.adapters.discord import Bot as DiscordBot
# from nonebot.adapters.dodo import Bot as DodoBot
from nonebot.adapters.dodo.models import MemberInfo
# from nonebot.adapters.kaiheila import Bot as KaiheilaBot
from nonebot.adapters.onebot.v11 import Bot as v11Bot
from nonebot.adapters.onebot.v12 import Bot as v12Bot
from zhenxun.configs.config import Config
from zhenxun.models.group_member_info import GroupInfoUser
from zhenxun.models.level_user import LevelUser
from zhenxun.services.log import logger
from zhenxun.configs.config import Config
from zhenxun.models.level_user import LevelUser
from zhenxun.models.group_member_info import GroupInfoUser
# from nonebot.adapters.discord import Bot as DiscordBot
# from nonebot.adapters.dodo import Bot as DodoBot
class MemberUpdateManage:
@@ -98,26 +96,27 @@ class MemberUpdateManage:
user_id = str(user_info["user_id"])
nickname = user_info["card"] or user_info["nickname"]
role = user_info["role"]
if default_auth:
if role in ["owner", "admin"] and not await LevelUser.is_group_flag(
user_id, group_id
):
if (
default_auth
and role in ["owner", "admin"]
and not await LevelUser.is_group_flag(user_id, group_id)
):
if role == "owner":
await LevelUser.set_level(user_id, group_id, default_auth + 1)
else:
await LevelUser.set_level(user_id, group_id, default_auth)
if user_id in bot.config.superusers:
await LevelUser.set_level(user_id, group_id, 9)
join_time = datetime.strptime(
time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(user_info["join_time"])
),
"%Y-%m-%d %H:%M:%S",
).replace(tzinfo=timezone(timedelta(hours=8)))
join_time = datetime.fromtimestamp(
user_info["join_time"], timezone(timedelta(hours=8))
)
if cnt := db_user_uid.count(user_id):
users = [u for u in db_user if u.user_id == user_id]
user = users[0]
if cnt > 1:
for u in users[1:]:
delete_list.append(u.id)
if nickname != uid2name.get(user_id):
user = users[0]
user.user_name = nickname
update_list.append(user)
else:
@@ -153,7 +152,7 @@ class MemberUpdateManage:
user_id__in=delete_member_list, group_id=group_id
).delete()
logger.info(
f"删除已退群用户", "更新群组成员信息", group_id=group_id, platform="qq"
"删除已退群用户", "更新群组成员信息", group_id=group_id, platform="qq"
)
@classmethod
@@ -162,7 +161,8 @@ class MemberUpdateManage:
pass
# exist_member_list = []
# default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH")
# group_member_list: list[GetGroupMemberInfoResp] = await bot.get_group_member_list(
# group_member_list: list[GetGroupMemberInfoResp] =
# await bot.get_group_member_list(
# group_id=group_id
# )
# for user_info in group_member_list:
@@ -177,7 +177,8 @@ class MemberUpdateManage:
# if str(user_id) in bot.config.superusers:
# await LevelUser.set_level(str(user_id), group_id, 9)
# join_time = datetime.strptime(
# time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(user_info["join_time"])),
# time.strftime("%Y-%m-%d %H:%M:%S",
# time.localtime(user_info["join_time"])),
# "%Y-%m-%d %H:%M:%S",
# )
# await GroupInfoUser.update_or_create(
@@ -191,7 +192,8 @@ class MemberUpdateManage:
# },
# )
# exist_member_list.append(str(user_id))
# logger.debug("更新成功", "更新群组成员信息", session=user_id, group_id=group_id)
# logger.debug("更新成功", "更新群组成员信息",
# session=user_id, group_id=group_id)
# if delete_member_list := list(
# set(exist_member_list).difference(
# set(await GroupInfoUser.get_group_member_id_list(group_id))
@@ -0,0 +1,45 @@
from nonebot.adapters import Bot
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
from nonebot_plugin_session import EventSession
from zhenxun.configs.utils import PluginExtraData
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.message import MessageUtils
from zhenxun.utils.platform import PlatformUtils
from zhenxun.utils.rules import admin_check, ensure_group
__plugin_meta__ = PluginMetadata(
name="更新群组列表",
description="更新群组列表",
usage="""
更新群组的基本信息
指令:
更新群组信息
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.SUPER_AND_ADMIN,
admin_level=1,
).dict(),
)
_matcher = on_alconna(
Alconna("更新群组信息"),
rule=admin_check(1) & ensure_group,
priority=5,
block=True,
)
@_matcher.handle()
async def _(bot: Bot, session: EventSession, arparma: Arparma):
logger.info("更新群组信息", arparma.header_result, session=session)
try:
await PlatformUtils.update_group(bot)
await MessageUtils.build_message("已经成功更新了群组信息!").send(reply_to=True)
except Exception as e:
await MessageUtils.build_message("更新群组信息失败!").finish(reply_to=True)
@@ -58,7 +58,7 @@ __plugin_meta__ = PluginMetadata(
关闭群被动早晚安
关闭群被动早晚安 -g 12355555
开启/关闭所有群被动 -[g ?[group_id]]
开启/关闭所有群被动 ?[-g [group_id]]
私聊中: 开启/关闭全局或指定群组被动状态
示例:
开启所有群被动: 开启全局所有被动
@@ -0,0 +1,63 @@
from nonebot.rule import to_me
from nonebot.adapters import Bot
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata
from nonebot_plugin_session import EventSession
from nonebot_plugin_alconna import Args, Match, Alconna, on_alconna
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.message import MessageUtils
from zhenxun.configs.utils import RegisterConfig, PluginExtraData
from ._data_source import UpdateManage
__plugin_meta__ = PluginMetadata(
name="自动更新",
description="就算是真寻也会成长的",
usage="""
usage:
检查更新真寻最新版本,包括了自动更新
指令:
检查更新真寻
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.SUPERUSER,
configs=[
RegisterConfig(
key="UPDATE_REMIND",
value=True,
help="是否检测更新版本",
default_value=True,
),
],
).dict(),
)
_matcher = on_alconna(
Alconna("检查更新", Args["ver_type?", ["main", "dev", "release"]]),
priority=1,
block=True,
permission=SUPERUSER,
rule=to_me(),
)
@_matcher.handle()
async def _(bot: Bot, session: EventSession, ver_type: Match[str]):
if not session.id1:
await MessageUtils.build_message("用户id为空...").finish()
if not ver_type.available:
result = await UpdateManage.check_version()
logger.info("查看当前版本...", "检查更新", session=session)
await MessageUtils.build_message(result).finish()
try:
result = await UpdateManage.update(bot, session.id1, ver_type.result)
except Exception as e:
logger.error("版本更新失败...", "检查更新", session=session, e=e)
await MessageUtils.build_message(f"更新版本失败...e: {e}").finish()
if result:
await MessageUtils.build_message(result).finish()
await MessageUtils.build_message("更新版本失败...").finish()
@@ -0,0 +1,266 @@
import os
import shutil
import tarfile
import zipfile
import subprocess
from nonebot.adapters import Bot
from nonebot.utils import run_sync
from zhenxun.services.log import logger
from zhenxun.utils.http_utils import AsyncHttpx
from zhenxun.utils.platform import PlatformUtils
from .config import (
DEV_URL,
MAIN_URL,
TMP_PATH,
BASE_PATH,
BACKUP_PATH,
RELEASE_URL,
REQ_TXT_FILE,
VERSION_FILE,
PYPROJECT_FILE,
REPLACE_FOLDERS,
BASE_PATH_STRING,
DOWNLOAD_GZ_FILE,
DOWNLOAD_ZIP_FILE,
PYPROJECT_LOCK_FILE,
REQ_TXT_FILE_STRING,
PYPROJECT_FILE_STRING,
PYPROJECT_LOCK_FILE_STRING,
)
def install_requirement():
requirement_path = (REQ_TXT_FILE).absolute()
if not requirement_path.exists():
logger.debug(
f"没有找到zhenxun的requirement.txt,目标路径为{requirement_path}", "插件管理"
)
return
try:
result = subprocess.run(
["pip", "install", "-r", str(requirement_path)],
check=True,
capture_output=True,
text=True,
)
logger.debug(f"成功安装真寻依赖,日志:\n{result.stdout}", "插件管理")
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 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 tf:
tf.close()
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
async def check_version(cls) -> str:
"""检查更新版本
返回:
str: 更新信息
"""
cur_version = cls.__get_version()
data = await cls.__get_latest_data()
if not data:
return "检查更新获取版本失败..."
return (
"检测到当前版本更新\n"
f"当前版本:{cur_version}\n"
f"最新版本:{data.get('name')}\n"
f"创建日期:{data.get('created_at')}\n"
f"更新内容:\n{data.get('body')}"
)
@classmethod
async def update(cls, bot: Bot, user_id: str, version_type: str) -> str | None:
"""更新操作
参数:
bot: Bot
user_id: 用户id
version_type: 更新版本类型
返回:
str | None: 返回消息
"""
logger.info("开始下载真寻最新版文件....", "检查更新")
cur_version = cls.__get_version()
url = None
new_version = None
if version_type == "dev":
url = DEV_URL
new_version = await cls.__get_version_from_branch("dev")
if new_version:
new_version = new_version.split(":")[-1].strip()
elif version_type == "main":
url = MAIN_URL
new_version = await cls.__get_version_from_branch("main")
if new_version:
new_version = new_version.split(":")[-1].strip()
elif version_type == "release":
data = await cls.__get_latest_data()
if not data:
return "获取更新版本失败..."
url = data.get("tarball_url")
new_version = data.get("name")
url = (await AsyncHttpx.get(url)).headers.get("Location") # type: ignore
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(
bot,
f"检测真寻已更新,版本更新:{cur_version} -> {new_version}\n开始更新...",
user_id,
)
download_file = (
DOWNLOAD_GZ_FILE if version_type == "release" else DOWNLOAD_ZIP_FILE
)
if await AsyncHttpx.download_file(url, download_file):
logger.debug("下载真寻最新版文件完成...", "检查更新")
await _file_handle(new_version)
return (
f"版本更新完成\n"
f"版本: {cur_version} -> {new_version}\n"
"请重新启动真寻以完成更新!"
)
else:
logger.debug("下载真寻最新版文件失败...", "检查更新")
return None
@classmethod
def __get_version(cls) -> str:
"""获取当前版本
返回:
str: 当前版本号
"""
_version = "v0.0.0"
if VERSION_FILE.exists():
if text := VERSION_FILE.open(encoding="utf8").readline():
_version = text.split(":")[-1].strip()
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_branch(cls, branch: str) -> str:
"""从指定分支获取版本号
参数:
branch: 分支名称
返回:
str: 版本号
"""
version_url = f"https://raw.githubusercontent.com/HibiKier/zhenxun_bot/{branch}/__version__"
try:
res = await AsyncHttpx.get(version_url)
if res.status_code == 200:
return res.text.strip()
except Exception as e:
logger.error(f"获取 {branch} 分支版本失败", e=e)
return "未知版本"
@@ -0,0 +1,35 @@
from pathlib import Path
from zhenxun.configs.path_config import TEMP_PATH
DEV_URL = "https://ghproxy.cc/https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/dev.zip"
MAIN_URL = "https://ghproxy.cc/https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/main.zip"
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 = TMP_PATH / "download_latest_file.tar.gz"
DOWNLOAD_ZIP_FILE = TMP_PATH / "download_latest_file.zip"
REPLACE_FOLDERS = [
"builtin_plugins",
"services",
"utils",
"models",
"configs",
]
+23 -34
View File
@@ -1,27 +1,26 @@
from nonebot.rule import to_me
from nonebot.adapters import Bot
from nonebot.plugin import PluginMetadata
from nonebot.rule import to_me
from nonebot_plugin_session import EventSession
from nonebot_plugin_alconna import (
Alconna,
AlconnaQuery,
Args,
Match,
Option,
Query,
Option,
Alconna,
AlconnaQuery,
on_alconna,
store_true,
)
from nonebot_plugin_session import EventSession
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.image_utils import BuildImage
from zhenxun.utils.message import MessageUtils
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.configs.utils import RegisterConfig, PluginExtraData
from ._data_source import create_help_img, get_plugin_help
from ._utils import GROUP_HELP_PATH
from ._data_source import create_help_img, get_plugin_help
__plugin_meta__ = PluginMetadata(
name="帮助",
@@ -35,8 +34,8 @@ __plugin_meta__ = PluginMetadata(
RegisterConfig(
key="type",
value="normal",
help="帮助图片样式 ['normal', 'HTML']",
default_value="normal",
help="帮助图片样式 ['normal', 'HTML', 'zhenxun']",
default_value="zhenxun",
)
],
).dict(),
@@ -67,35 +66,25 @@ async def _(
session: EventSession,
is_superuser: Query[bool] = AlconnaQuery("superuser.value", False),
):
_is_superuser = False
if is_superuser.available:
_is_superuser = is_superuser.result
_is_superuser = is_superuser.result if is_superuser.available else False
if name.available:
if _is_superuser and session.id1 not in bot.config.superusers:
_is_superuser = False
if result := await get_plugin_help(name.result, _is_superuser):
if isinstance(result, BuildImage):
await MessageUtils.build_message(result).send(reply_to=True)
else:
await MessageUtils.build_message(result).send(reply_to=True)
await MessageUtils.build_message(result).send(reply_to=True)
else:
await MessageUtils.build_message("没有此功能的帮助信息...").send(
reply_to=True
)
logger.info(
f"查看帮助详情: {name.result}",
"帮助",
session=session,
)
logger.info(f"查看帮助详情: {name.result}", "帮助", session=session)
elif gid := session.id3 or session.id2:
_image_path = GROUP_HELP_PATH / f"{gid}.png"
if not _image_path.exists():
await create_help_img(bot.self_id, gid)
await MessageUtils.build_message(_image_path).finish()
else:
if gid := session.id3 or session.id2:
_image_path = GROUP_HELP_PATH / f"{gid}.png"
if not _image_path.exists():
await create_help_img(gid)
await MessageUtils.build_message(_image_path).finish()
else:
if not SIMPLE_HELP_IMAGE.exists():
if SIMPLE_HELP_IMAGE.exists():
SIMPLE_HELP_IMAGE.unlink()
await create_help_img(None)
await MessageUtils.build_message(SIMPLE_HELP_IMAGE).finish()
if not SIMPLE_HELP_IMAGE.exists():
if SIMPLE_HELP_IMAGE.exists():
SIMPLE_HELP_IMAGE.unlink()
await create_help_img(bot.self_id, None)
await MessageUtils.build_message(SIMPLE_HELP_IMAGE).finish()
+7 -7
View File
@@ -1,7 +1,7 @@
import nonebot
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.utils.image_utils import BuildImage, ImageTemplate
from ._utils import HelpImageBuild
@@ -11,14 +11,14 @@ random_bk_path = IMAGE_PATH / "background" / "help" / "simple_help"
background = IMAGE_PATH / "background" / "0.png"
async def create_help_img(group_id: str | None):
"""
说明:
生成帮助图片
async def create_help_img(bot_id: str, group_id: str | None):
"""生成帮助图片
参数:
:param group_id: 群号
bot_id: bot id
group_id: 群号
"""
await HelpImageBuild().build_image(group_id)
await HelpImageBuild().build_image(bot_id, group_id)
async def get_plugin_help(name: str, is_superuser: bool) -> str | BuildImage:
+100 -18
View File
@@ -1,13 +1,16 @@
import os
import random
from typing import Dict
import aiofiles
from nonebot_plugin_htmlrender import template_to_pic
from zhenxun.configs.config import Config
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH, TEMPLATE_PATH
from zhenxun.models.group_console import GroupConsole
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.utils.enum import BlockType, PluginType
from zhenxun.utils.image_utils import BuildImage, build_sort_image, group_image
from zhenxun.models.group_console import GroupConsole
from zhenxun.builtin_plugins.sign_in.utils import AVA_URL
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH, TEMPLATE_PATH
from zhenxun.utils.image_utils import BuildImage, group_image, build_sort_image
from ._config import Item
@@ -25,7 +28,7 @@ LOGO_PATH = TEMPLATE_PATH / "menu" / "res" / "logo"
class HelpImageBuild:
def __init__(self):
self._data: list[PluginInfo] = []
self._sort_data: Dict[str, list[PluginInfo]] = {}
self._sort_data: dict[str, list[PluginInfo]] = {}
self._image_list = []
self.icon2str = {
"normal": "fa fa-cog",
@@ -60,23 +63,104 @@ class HelpImageBuild:
self._sort_data[menu_type] = []
self._sort_data[menu_type].append(plugin)
async def build_image(self, group_id: str | None):
async def build_image(self, bot_id: str, group_id: str | None):
if group_id:
help_image = GROUP_HELP_PATH / f"{group_id}.png"
else:
help_image = IMAGE_PATH / f"SIMPLE_HELP.png"
help_image = IMAGE_PATH / "SIMPLE_HELP.png"
build_type = Config.get_config("help", "TYPE")
if build_type == "HTML":
byt = await self.build_html_image(group_id)
with open(help_image, "wb") as f:
f.write(byt)
async with aiofiles.open(help_image, "wb") as f:
await f.write(byt)
elif build_type == "zhenxun":
byt = await self.build_ss_image(bot_id, group_id)
async with aiofiles.open(help_image, "wb") as f:
await f.write(byt)
else:
img = await self.build_pil_image(group_id)
await img.save(help_image)
async def build_html_image(self, group_id: str | None) -> bytes:
from nonebot_plugin_htmlrender import template_to_pic
async def build_ss_image(self, bot_id: str, group_id: str | None) -> bytes:
"""构造ss帮助图片
参数:
group_id: 群号
"""
await self.sort_type()
classify = {}
for menu in self._sort_data:
self._sort_data[menu].sort(key=lambda k: len(k.name))
for menu in self._sort_data:
for plugin in self._sort_data[menu]:
if not plugin.status:
if group_id and plugin.block_type in [
BlockType.ALL,
BlockType.GROUP,
]:
plugin.name = f"{plugin.name}(不可用)"
if not group_id and plugin.block_type in [
BlockType.ALL,
BlockType.PRIVATE,
]:
plugin.name = f"{plugin.name}(不可用)"
if not classify.get(menu):
classify[menu] = []
classify[menu].append(
Item(plugin_name=f"{plugin.id}-{plugin.name}", sta=0)
)
max_len = 0
flag_index = -1
max_data = None
plugin_list = []
for index, plu in enumerate(classify.keys()):
data = {
"name": "主要功能" if plu in ["normal", "功能"] else plu,
"items": classify[plu],
}
if len(classify[plu]) > max_len:
max_len = len(classify[plu])
flag_index = index
max_data = data
plugin_list.append(data)
del plugin_list[flag_index]
plugin_list.insert(0, max_data)
_data = []
_left = 30
for plugin in plugin_list:
_plugins = []
width = 50
if len(plugin["items"]) // 2 > 6:
width = 100
_pu1 = []
_pu2 = []
for i in range(len(plugin["items"])):
if i % 2:
_pu1.append(plugin["items"][i])
else:
_pu2.append(plugin["items"][i])
_plugins = [(30, 50, _pu1), (0, 50, _pu2)]
else:
_plugins = [(_left, 100, plugin["items"])]
_left = 15 if _left == 30 else 30
_data.append({"name": plugin["name"], "items": _plugins, "width": width})
return await template_to_pic(
template_path=str((TEMPLATE_PATH / "ss_menu").absolute()),
template_name="main.html",
templates={"data": {"plugin_list": _data, "ava": AVA_URL.format(bot_id)}},
pages={
"viewport": {"width": 637, "height": 975},
"base_url": f"file://{TEMPLATE_PATH}",
},
wait=2,
)
async def build_html_image(self, group_id: str | None) -> bytes:
"""构造HTML帮助图片
参数:
group_id: 群号
"""
await self.sort_type()
classify = {}
for menu in self._sort_data:
@@ -127,7 +211,7 @@ class HelpImageBuild:
plugin_list.append(data)
del plugin_list[flag_index]
plugin_list.insert(0, max_data)
pic = await template_to_pic(
return await template_to_pic(
template_path=str((TEMPLATE_PATH / "menu").absolute()),
template_name="zhenxun_menu.html",
templates={"plugin_list": plugin_list},
@@ -137,10 +221,9 @@ class HelpImageBuild:
},
wait=2,
)
return pic
async def build_pil_image(self, group_id: str | None) -> BuildImage:
"""构造帮助图片
"""构造PIL帮助图片
参数:
group_id: 群号
@@ -161,7 +244,7 @@ class HelpImageBuild:
sum_height = 50 * len(plugin_list) + 10
else:
sum_height = (font_size + 6) * len(plugin_list) + 10
max_width = max([x[0] for x in wh_list]) + 30
max_width = max(x[0] for x in wh_list) + 30
bk = BuildImage(
max_width + 40,
sum_height + 50,
@@ -175,7 +258,7 @@ class HelpImageBuild:
max_width + 40,
sum_height,
font_size=font_size,
color="white" if not idx % 2 else "black",
color="black" if idx % 2 else "white",
)
curr_h = 10
group = await GroupConsole.get_or_none(group_id=group_id)
@@ -199,7 +282,7 @@ class HelpImageBuild:
name_image = await self.build_name_image( # type: ignore
max_width,
plugin.name,
"black" if not idx % 2 else "white",
"white" if idx % 2 else "black",
text_color,
pos,
)
@@ -213,7 +296,6 @@ class HelpImageBuild:
await bk.text((0, 14), menu_type, center_type="width")
await bk.paste(B, (0, 50))
await bk.transparent(2)
# await bk.acircle_corner(point_list=['lt', 'rt'])
self._image_list.append(bk)
image_group, h = group_image(self._image_list)
@@ -198,6 +198,8 @@ class AuthChecker:
return False
if not self._flmt_s.check(sid):
return False
if plugin.module == "ai":
return False
return True
async def auth(
@@ -392,7 +394,7 @@ class AuthChecker:
if not plugin.status and plugin.block_type == BlockType.ALL:
"""全局状态"""
if group_id:
if await GroupConsole.is_super_group(group_id, channel_id):
if await GroupConsole.is_super_group(group_id):
raise IsSuperuserException()
logger.debug(
f"{plugin.name}({plugin.module}) 全局未开启此功能...",
@@ -482,11 +484,11 @@ class AuthChecker:
"""群休眠"""
if text.strip() != "醒来":
logger.debug(
f"功能总开关关闭状态...",
f"群休眠状态...",
"HOOK",
session=session,
)
raise IgnoredException("功能总开关关闭状态")
raise IgnoredException("群休眠状态")
async def auth_cost(
self, user: UserConsole, plugin: PluginInfo, session: EventSession
+1 -1
View File
@@ -1,6 +1,6 @@
from typing import Optional
from nonebot.adapters.onebot.v11 import Bot, Event, MessageEvent
from nonebot.adapters.onebot.v11 import Bot, Event
from nonebot.matcher import Matcher
from nonebot.message import run_postprocessor, run_preprocessor
from nonebot_plugin_alconna import UniMsg
+11 -2
View File
@@ -1,3 +1,5 @@
import logging
from nonebot.adapters import Bot, Event
from nonebot.exception import IgnoredException
from nonebot.matcher import Matcher
@@ -8,6 +10,7 @@ from nonebot_plugin_session import EventSession
from zhenxun.configs.config import Config
from zhenxun.models.ban_console import BanConsole
from zhenxun.models.group_console import GroupConsole
from zhenxun.utils.enum import PluginType
from zhenxun.utils.message import MessageUtils
from zhenxun.utils.utils import FreqLimiter
@@ -30,7 +33,7 @@ async def _(
if plugin := matcher.plugin:
if metadata := plugin.metadata:
extra = metadata.extra
if extra.get("plugin_type") == PluginType.HIDDEN:
if extra.get("plugin_type") in [PluginType.HIDDEN, PluginType.DEPENDANT]:
return
user_id = session.id1
group_id = session.id3 or session.id2
@@ -38,7 +41,12 @@ async def _(
if user_id in bot.config.superusers:
return
if await BanConsole.is_ban(None, group_id):
logging.debug("群组处于黑名单中...", "ban_hook")
raise IgnoredException("群组处于黑名单中...")
if g := await GroupConsole.get_group(group_id):
if g.level < 0:
logging.debug("群黑名单, 群权限-1...", "ban_hook")
raise IgnoredException("群黑名单, 群权限-1..")
if user_id:
ban_result = Config.get_config("hook", "BAN_RESULT")
if user_id in bot.config.superusers:
@@ -59,7 +67,7 @@ async def _(
time_str = f"{hours} 小时 {minute}分钟"
else:
time_str = f"{minute} 分钟"
if ban_result and _flmt.check(user_id):
if time != -1 and ban_result and _flmt.check(user_id):
_flmt.start_cd(user_id)
await MessageUtils.build_message(
[
@@ -67,4 +75,5 @@ async def _(
f"{ban_result}\n在..在 {time_str} 后才会理你喔",
]
).send()
logging.debug("用户处于黑名单中...", "ban_hook")
raise IgnoredException("用户处于黑名单中...")
+11 -2
View File
@@ -1,6 +1,7 @@
import time
from collections import defaultdict
from nonebot.adapters import Event
from nonebot.adapters.onebot.v11 import Bot
from nonebot.exception import IgnoredException
from nonebot.matcher import Matcher
@@ -63,16 +64,24 @@ _blmt = BanCheckLimiter(
# 恶意触发命令检测
@run_preprocessor
async def _(matcher: Matcher, bot: Bot, session: EventSession, state: T_State):
async def _(
matcher: Matcher, bot: Bot, session: EventSession, state: T_State, event: Event
):
module = None
if plugin := matcher.plugin:
module = plugin.module_name
if metadata := plugin.metadata:
extra = metadata.extra
if extra.get("plugin_type") in [PluginType.HIDDEN, PluginType.DEPENDANT]:
if extra.get("plugin_type") in [
PluginType.HIDDEN,
PluginType.DEPENDANT,
PluginType.ADMIN,
]:
return
else:
return
if matcher.type == "notice":
return
user_id = session.id1
group_id = session.id3 or session.id2
malicious_ban_time = Config.get_config("hook", "MALICIOUS_BAN_TIME")
+21 -21
View File
@@ -3,9 +3,9 @@ from pathlib import Path
import nonebot
from nonebot.adapters import Bot
from zhenxun.models.group_console import GroupConsole
from zhenxun.services.log import logger
from zhenxun.utils.platform import PlatformUtils
from zhenxun.models.group_console import GroupConsole
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
@@ -20,24 +20,24 @@ async def _(bot: Bot):
参数:
bot: Bot
"""
if PlatformUtils.get_platform(bot) == "qq":
logger.debug(f"更新Bot: {bot.self_id} 的群认证...")
group_list, _ = await PlatformUtils.get_group_list(bot)
gid_list = [(g.group_id, g.group_name) for g in group_list]
db_group_list = await GroupConsole.all().values_list("group_id", flat=True)
create_list = []
update_id = []
for gid, name in gid_list:
if gid not in db_group_list:
create_list.append(
GroupConsole(group_id=gid, group_name=name, group_flag=1)
)
else:
update_id.append(gid)
if create_list:
await GroupConsole.bulk_create(create_list, 10)
if PlatformUtils.get_platform(bot) != "qq":
return
logger.debug(f"更新Bot: {bot.self_id} 的群认证...")
group_list, _ = await PlatformUtils.get_group_list(bot)
db_group_list = await GroupConsole.all().values_list("group_id", flat=True)
create_list = []
update_id = []
for group in group_list:
if group.group_id not in db_group_list:
group.group_flag = 1
create_list.append(group)
else:
await GroupConsole.filter(group_id__in=update_id).update(group_flag=1)
logger.debug(
f"更新Bot: {bot.self_id} 的群认证完成,共创建 {len(create_list)} 条数据,共修改 {len(update_id)} 条数据..."
)
update_id.append(group.group_id)
if create_list:
await GroupConsole.bulk_create(create_list, 10)
else:
await GroupConsole.filter(group_id__in=update_id).update(group_flag=1)
logger.debug(
f"更新Bot: {bot.self_id} 的群认证完成,共创建 {len(create_list)} 条数据,"
"共修改 {len(update_id)} 条数据..."
)
+72 -60
View File
@@ -1,23 +1,24 @@
import nonebot
import aiofiles
import ujson as json
from nonebot import get_loaded_plugins
from nonebot.drivers import Driver
from nonebot.plugin import Plugin
from ruamel.yaml import YAML
from nonebot.plugin import Plugin
from nonebot.drivers import Driver
from nonebot import get_loaded_plugins
from zhenxun.services.log import logger
from zhenxun.models.task_info import TaskInfo
from zhenxun.configs.path_config import DATA_PATH
from zhenxun.configs.utils import PluginExtraData, PluginSetting
from zhenxun.models.group_console import GroupConsole
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.models.plugin_limit import PluginLimit
from zhenxun.models.task_info import TaskInfo
from zhenxun.services.log import logger
from zhenxun.models.group_console import GroupConsole
from zhenxun.configs.utils import PluginSetting, PluginExtraData
from zhenxun.utils.enum import (
BlockType,
PluginType,
LimitCheckType,
LimitWatchType,
PluginLimitType,
PluginType,
)
_yaml = YAML(pure=True)
@@ -31,7 +32,7 @@ async def _handle_setting(
plugin: Plugin,
plugin_list: list[PluginInfo],
limit_list: list[PluginLimit],
task_list: list[TaskInfo],
task_list: list[tuple[bool, TaskInfo]],
):
"""处理插件设置
@@ -40,8 +41,7 @@ async def _handle_setting(
plugin_list: 插件列表
limit_list: 插件限制列表
"""
metadata = plugin.metadata
if metadata:
if metadata := plugin.metadata:
extra = metadata.extra
extra_data = PluginExtraData(**extra)
logger.debug(f"{metadata.name}:{plugin.name} -> {extra}", "初始化插件数据")
@@ -71,30 +71,34 @@ async def _handle_setting(
)
)
if extra_data.limits:
for limit in extra_data.limits:
limit_list.append(
PluginLimit(
module=plugin.name,
module_path=plugin.module_name,
limit_type=limit._type,
watch_type=limit.watch_type,
status=limit.status,
check_type=limit.check_type,
result=limit.result,
cd=getattr(limit, "cd", None),
max_count=getattr(limit, "max_count", None),
)
limit_list.extend(
PluginLimit(
module=plugin.name,
module_path=plugin.module_name,
limit_type=limit._type,
watch_type=limit.watch_type,
status=limit.status,
check_type=limit.check_type,
result=limit.result,
cd=getattr(limit, "cd", None),
max_count=getattr(limit, "max_count", None),
)
for limit in extra_data.limits
)
if extra_data.tasks:
for task in extra_data.tasks:
task_list.append(
task_list.extend(
(
task.create_status,
TaskInfo(
module=task.module,
name=task.name,
status=task.status,
run_time=task.run_time,
)
default_status=task.default_status,
),
)
for task in extra_data.tasks
)
@driver.on_startup
@@ -104,7 +108,7 @@ async def _():
"""
plugin_list: list[PluginInfo] = []
limit_list: list[PluginLimit] = []
task_list: list[TaskInfo] = []
task_list = []
module2id = {}
load_plugin = []
if module_list := await PluginInfo.all().values("id", "module_path"):
@@ -132,14 +136,15 @@ async def _():
update_list.append(plugin)
if create_list:
await PluginInfo.bulk_create(create_list, 10)
if update_list:
# TODO: 批量更新无法更新plugin_type: tortoise.exceptions.OperationalError: column "superuser" does not exist
pass
# await PluginInfo.bulk_update(
# update_list,
# ["name", "author", "version", "admin_level", "plugin_type"],
# 10,
# )
# if update_list:
# # TODO: 批量更新无法更新plugin_type: tortoise.exceptions.OperationalError:
# column "superuser" does not exist
# pass
# await PluginInfo.bulk_update(
# update_list,
# ["name", "author", "version", "admin_level", "plugin_type"],
# 10,
# )
if limit_list:
limit_create = []
plugins = []
@@ -147,8 +152,8 @@ async def _():
plugins = await PluginInfo.filter(module_path__in=module_path_list).all()
if plugins:
for limit in limit_list:
if l := [p for p in plugins if p.module_path == limit.module_path]:
plugin = l[0]
if lmt := [p for p in plugins if p.module_path == limit.module_path]:
plugin = lmt[0]
limit_type_list = [
_limit.limit_type for _limit in await plugin.plugin_limit.all() # type: ignore
]
@@ -163,18 +168,25 @@ async def _():
}
create_list = []
update_list = []
for task in task_list:
for status, task in task_list:
if task.module not in module_dict:
create_list.append(task)
create_list.append((status, task))
else:
task.id = module_dict[task.module]
update_list.append(task)
if create_list:
await TaskInfo.bulk_create(create_list, 10)
_create_list = [t[1] for t in create_list]
await TaskInfo.bulk_create(_create_list, 10)
if block := [t[1].module for t in create_list if not t[0]]:
block_task = ",".join(block) + ","
if group_list := await GroupConsole.all():
for group in group_list:
group.block_task += block_task
await GroupConsole.bulk_update(group_list, ["block_task"], 10)
if update_list:
await TaskInfo.bulk_update(
update_list,
["run_time", "status", "name"],
["run_time", "name"],
10,
)
await data_migration()
@@ -195,14 +207,14 @@ async def limit_migration():
count_file = DATA_PATH / "configs" / "plugins2count.yaml"
limit_data: dict[str, list[tuple[str, dict]]] = {}
if cd_file.exists():
with open(cd_file, encoding="utf8") as f:
if data := _yaml.load(f):
async with aiofiles.open(cd_file, encoding="utf8") as f:
if data := _yaml.load(await f.read()):
for k in data["PluginCdLimit"]:
limit_data[k] = [("CD", data["PluginCdLimit"][k])]
cd_file.unlink()
if block_file.exists():
with open(block_file, encoding="utf8") as f:
if data := _yaml.load(f):
async with aiofiles.open(block_file, encoding="utf8") as f:
if data := _yaml.load(await f.read()):
for k in data["PluginBlockLimit"]:
if k in limit_data:
limit_data[k].append(("BLOCK", data["PluginBlockLimit"][k]))
@@ -210,8 +222,8 @@ async def limit_migration():
limit_data[k] = [("BLOCK", data["PluginBlockLimit"][k])]
block_file.unlink()
if count_file.exists():
with open(count_file, encoding="utf8") as f:
if data := _yaml.load(f):
async with aiofiles.open(count_file, encoding="utf8") as f:
if data := _yaml.load(await f.read()):
for k in data["PluginCountLimit"]:
if k in limit_data:
limit_data[k].append(("COUNT", data["PluginCountLimit"][k]))
@@ -287,7 +299,8 @@ async def limit_migration():
max_count=_limit.get("max_count"),
)
)
# TODO: 批量错误 tortoise.exceptions.OperationalError: syntax error at or near "ALL"
# TODO: 批量错误 tortoise.exceptions.OperationalError:
# syntax error at or near "ALL"
# if update_list:
# await PluginLimit.bulk_update(
# update_list,
@@ -311,8 +324,8 @@ async def plugin_migration():
setting_file = DATA_PATH / "configs" / "plugins2settings.yaml"
plugin_file = DATA_PATH / "manager" / "plugins_manager.json"
if setting_file.exists():
with open(setting_file, encoding="utf8") as f:
if data := _yaml.load(f):
async with aiofiles.open(setting_file, encoding="utf8") as f:
if data := _yaml.load(await f.read()):
logger.info("开始迁移插件setting数据...")
data = data["PluginSettings"]
plugins = await PluginInfo.filter(module__in=data.keys())
@@ -342,8 +355,8 @@ async def plugin_migration():
setting_file.unlink()
logger.info("迁移插件setting数据完成!")
if plugin_file.exists():
with open(plugin_file, encoding="utf8") as f:
if data := json.load(f):
async with aiofiles.open(plugin_file, encoding="utf8") as f:
if data := json.loads(await f.read()):
logger.info("开始迁移插件数据...")
plugins = await PluginInfo.filter(module__in=data.keys())
for plugin in plugins:
@@ -359,7 +372,8 @@ async def plugin_migration():
block_type = BlockType.GROUP
plugin.block_type = block_type
await plugin.save(update_fields=["status", "block_type"])
# TODO: tortoise.exceptions.OperationalError: syntax error at or near "ALL"
# TODO: tortoise.exceptions.OperationalError: syntax error at
# or near "ALL"
# await PluginInfo.bulk_update(plugins, ["status", "block_type"], 10)
plugin_file.unlink()
logger.info("迁移插件数据完成!")
@@ -371,22 +385,20 @@ async def group_migration():
"""
group_file = DATA_PATH / "manager" / "group_manager.json"
if group_file.exists():
with open(group_file, encoding="utf8") as f:
if data := json.load(f):
async with aiofiles.open(group_file, encoding="utf8") as f:
if data := json.loads(await f.read()):
logger.info("开始迁移群组数据...")
update_list = []
create_list = []
white_group = data["white_group"]
close_task = data["close_task"]
old_group_list: dict = data["group_manager"]
if close_task:
if close_task := data["close_task"]:
"""全局被动关闭"""
await TaskInfo.filter(module__in=close_task).update(status=False)
group_list = await GroupConsole.filter(
group_id__in=old_group_list.keys()
)
for old_group_id in old_group_list:
old_group = old_group_list[old_group_id]
for old_group_id, old_group in old_group_list.items():
block_plugin = ""
block_task = ""
status = old_group.get("status", True)
+12 -12
View File
@@ -10,7 +10,7 @@ from nonebot_plugin_alconna import Alconna, Option, on_alconna, store_true
from nonebot_plugin_session import EventSession
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
from zhenxun.configs.config import NICKNAME, Config
from zhenxun.configs.config import BotConfig, Config
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.models.ban_console import BanConsole
from zhenxun.models.friend_user import FriendUser
@@ -24,11 +24,11 @@ __plugin_meta__ = PluginMetadata(
name="昵称系统",
description="区区昵称,才不想叫呢!",
usage=f"""
个人昵称,将替换{NICKNAME}称呼你的名称,群聊 与 私聊 昵称相互独立,全局昵称设置将更改您目前所有群聊中及私聊的昵称
个人昵称,将替换{BotConfig.self_nickname}称呼你的名称,群聊 与 私聊 昵称相互独立,全局昵称设置将更改您目前所有群聊中及私聊的昵称
指令:
以后叫我 [昵称]: 设置当前群聊/私聊的昵称
全局昵称设置 [昵称]: 设置当前所有群聊和私聊的昵称
{NICKNAME}我是谁
{BotConfig.self_nickname}我是谁
""".strip(),
extra=PluginExtraData(
author="HibiKier",
@@ -86,25 +86,25 @@ _matcher.shortcut(
CALL_NAME = [
"好啦好啦,我知道啦,{},以后就这么叫你吧",
f"嗯嗯,{NICKNAME}" + "记住你的昵称了哦,{}",
f"嗯嗯,{BotConfig.self_nickname}" + "记住你的昵称了哦,{}",
"好突然,突然要叫你昵称什么的...{}..",
f"{NICKNAME}" + "会好好记住{}的,放心吧",
f"{BotConfig.self_nickname}" + "会好好记住{}的,放心吧",
"好..好.,那窝以后就叫你{}了.",
]
REMIND = [
"我肯定记得你啊,你是{}啊",
"我不会忘记你的,你也不要忘记我!{}",
f"哼哼,{NICKNAME}" + "记忆力可是很好的,{}",
f"哼哼,{BotConfig.self_nickname}" + "记忆力可是很好的,{}",
"嗯?你是失忆了嘛...{}..",
f"不要小看{NICKNAME}" + "的记忆力啊!笨蛋{}!QAQ",
f"不要小看{BotConfig.self_nickname}" + "的记忆力啊!笨蛋{}!QAQ",
"哎?{}..怎么了吗..突然这样问..",
]
CANCEL = [
f"呜..{NICKNAME}" + "睡一觉就会忘记的..和梦一样..{}",
f"呜..{BotConfig.self_nickname}" + "睡一觉就会忘记的..和梦一样..{}",
"窝知道了..{}..",
f"是{NICKNAME}" + "哪里做的不好嘛..好吧..晚安{}",
f"是{BotConfig.self_nickname}" + "哪里做的不好嘛..好吧..晚安{}",
"呃,{},下次我绝对绝对绝对不会再忘记你!",
"可..可恶!{}!太可恶了!呜",
]
@@ -152,9 +152,9 @@ def CheckNickname():
logger.debug(
"昵称设置禁止字符: [{word}]", "昵称设置", session=session
)
await MessageUtils.build_message(f"字符 [{x}] 为禁止字符!").finish(
at_sender=True
)
await MessageUtils.build_message(
f"字符 [{word}] 为禁止字符!"
).finish(at_sender=True)
return Depends(dependency)
@@ -1,12 +1,13 @@
import os
import random
import re
import random
from datetime import datetime
import nonebot
import ujson as json
from nonebot import on_notice, on_request
from nonebot.adapters import Bot
from nonebot_plugin_alconna import At
from nonebot import on_notice, on_request
from nonebot.plugin import PluginMetadata
from nonebot.adapters.onebot.v11 import (
GroupDecreaseNoticeEvent,
GroupIncreaseNoticeEvent,
@@ -15,22 +16,20 @@ from nonebot.adapters.onebot.v12 import (
GroupMemberDecreaseEvent,
GroupMemberIncreaseEvent,
)
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import At
from zhenxun.configs.config import NICKNAME, Config
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task
from zhenxun.services.log import logger
from zhenxun.utils.utils import FreqLimiter
from zhenxun.utils.message import MessageUtils
from zhenxun.models.fg_request import FgRequest
from zhenxun.models.group_console import GroupConsole
from zhenxun.models.group_member_info import GroupInfoUser
from zhenxun.models.level_user import LevelUser
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.models.task_info import TaskInfo
from zhenxun.services.log import logger
from zhenxun.utils.common_utils import CommonUtils
from zhenxun.configs.config import Config, BotConfig
from zhenxun.models.group_console import GroupConsole
from zhenxun.models.group_member_info import GroupInfoUser
from zhenxun.utils.enum import PluginType, RequestHandleType
from zhenxun.utils.message import MessageUtils
from zhenxun.utils.utils import FreqLimiter
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
from zhenxun.configs.utils import Task, RegisterConfig, PluginExtraData
__plugin_meta__ = PluginMetadata(
name="QQ群事件处理",
@@ -44,7 +43,7 @@ __plugin_meta__ = PluginMetadata(
RegisterConfig(
module="invite_manager",
key="message",
value=f"请不要未经同意就拉{NICKNAME}入群!告辞!",
value=f"请不要未经同意就拉{BotConfig.self_nickname}入群!告辞!",
help="强制拉群后进群回复的内容",
),
RegisterConfig(
@@ -88,8 +87,6 @@ __plugin_meta__ = PluginMetadata(
)
superuser = nonebot.get_driver().config.platform_superusers["qq"][0]
base_config = Config.get("invite_manager")
@@ -108,6 +105,7 @@ add_group = on_request(priority=1, block=False)
@group_increase_handle.handle()
async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent):
superuser = BotConfig.get_superuser("qq")
user_id = str(event.user_id)
group_id = str(event.group_id)
if user_id == bot.self_id:
@@ -130,7 +128,7 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent
message=f"触发强制入群保护,已成功退出群聊 {group_id}...",
)
logger.info(
f"强制拉群或未有群信息,退出群聊成功",
"强制拉群或未有群信息,退出群聊成功",
"入群检测",
group_id=event.group_id,
)
@@ -141,7 +139,7 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent
await req.save(update_fields=["handle_type"])
except Exception as e:
logger.error(
f"强制拉群或未有群信息,退出群聊失败",
"强制拉群或未有群信息,退出群聊失败",
"入群检测",
group_id=event.group_id,
e=e,
@@ -210,7 +208,7 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent
user_info["user_id"], user_info["group_id"], 9
)
logger.debug(
f"添加超级用户权限: 9",
"添加超级用户权限: 9",
"入群检测",
session=user_info["user_id"],
group_id=user_info["group_id"],
@@ -231,19 +229,21 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent
"""群欢迎消息"""
_flmt.start_cd(group_id)
path = DATA_PATH / "welcome_message" / "qq" / f"{group_id}"
data = json.load((path / "text.json").open(encoding="utf-8"))
message = data["message"]
msg_split = re.split(r"\[image:\d+\]", message)
file = path / "text.json"
msg_list = []
if data["at"]:
msg_list.append(At(flag="user", target=user_id))
for i, text in enumerate(msg_split):
msg_list.append(text)
img_file = path / f"{i}.png"
if img_file.exists():
msg_list.append(img_file)
if not TaskInfo.is_block("group_welcome", group_id):
logger.info(f"发送群欢迎消息...", "入群检测", group_id=group_id)
if file.exists():
data = json.load((path / "text.json").open(encoding="utf-8"))
message = data["message"]
msg_split = re.split(r"\[image:\d+\]", message)
if data["at"]:
msg_list.append(At(flag="user", target=user_id))
for i, text in enumerate(msg_split):
msg_list.append(text)
img_file = path / f"{i}.png"
if img_file.exists():
msg_list.append(img_file)
if not await CommonUtils.is_block("group_welcome", group_id):
logger.info("发送群欢迎消息...", "入群检测", group_id=group_id)
if msg_list:
await MessageUtils.build_message(msg_list).send()
else:
@@ -274,17 +274,18 @@ async def _(bot: Bot, event: GroupDecreaseNoticeEvent | GroupMemberDecreaseEvent
operator_name = "None"
group = await GroupConsole.filter(group_id=str(group_id)).first()
group_name = group.group_name if group else ""
coffee = int(superuser)
await bot.send_private_msg(
user_id=coffee,
message=f"****呜..一份踢出报告****\n"
f"我被 {operator_name}({operator_id})\n"
f"踢出了 {group_name}({group_id})\n"
f"日期:{str(datetime.now()).split('.')[0]}",
)
if group:
await group.delete()
return
if superuser := BotConfig.get_superuser("qq"):
coffee = int(superuser)
await bot.send_private_msg(
user_id=coffee,
message=f"****呜..一份踢出报告****\n"
f"我被 {operator_name}({operator_id})\n"
f"踢出了 {group_name}({group_id})\n"
f"日期:{str(datetime.now()).split('.')[0]}",
)
if group:
await group.delete()
return
if str(event.user_id) == bot.self_id:
"""踢出Bot"""
await GroupConsole.filter(group_id=str(event.group_id)).delete()
@@ -311,7 +312,7 @@ async def _(bot: Bot, event: GroupDecreaseNoticeEvent | GroupMemberDecreaseEvent
operator = await bot.get_group_member_info(
user_id=event.operator_id, group_id=event.group_id
)
operator_name = operator["card"] if operator["card"] else operator["nickname"]
operator_name = operator["card"] or operator["nickname"]
result = f"{user_name} 被 {operator_name} 送走了."
if not TaskInfo.is_block("refund_group_remind", str(event.group_id)):
if not await CommonUtils.is_block("refund_group_remind", str(event.group_id)):
await group_decrease_handle.send(f"{result}")
@@ -0,0 +1,135 @@
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import Alconna, Args, Subcommand, on_alconna
from nonebot_plugin_session import EventSession
from zhenxun.configs.utils import PluginExtraData
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.message import MessageUtils
from .data_source import ShopManage
__plugin_meta__ = PluginMetadata(
name="插件商店",
description="插件商店",
usage="""
插件商店 : 查看当前的插件商店
添加插件 id : 添加插件
移除插件 id : 移除插件
搜索插件 name or author : 搜索插件
更新插件 id : 更新插件
""".strip(),
extra=PluginExtraData(
author="HibiKier",
version="0.1",
plugin_type=PluginType.SUPERUSER,
).dict(),
)
_matcher = on_alconna(
Alconna(
"插件商店",
Subcommand("add", Args["plugin_id", int]),
Subcommand("remove", Args["plugin_id", int]),
Subcommand("search", Args["plugin_name_or_author", str]),
Subcommand("update", Args["plugin_id", int]),
),
permission=SUPERUSER,
priority=1,
block=True,
)
_matcher.shortcut(
r"添加插件",
command="插件商店",
arguments=["add", "{%0}"],
prefix=True,
)
_matcher.shortcut(
r"移除插件",
command="插件商店",
arguments=["remove", "{%0}"],
prefix=True,
)
_matcher.shortcut(
r"搜索插件",
command="插件商店",
arguments=["search", "{%0}"],
prefix=True,
)
_matcher.shortcut(
r"更新插件",
command="插件商店",
arguments=["update", "{%0}"],
prefix=True,
)
@_matcher.assign("$main")
async def _(session: EventSession):
try:
result = await ShopManage.get_plugins_info()
logger.info("查看插件列表", "插件商店", session=session)
await MessageUtils.build_message(result).send()
except Exception as e:
logger.error(f"查看插件列表失败 e: {e}", "插件商店", session=session, e=e)
await MessageUtils.build_message("获取插件列表失败...").send()
@_matcher.assign("add")
async def _(session: EventSession, plugin_id: int):
try:
await MessageUtils.build_message(
f"正在添加插件 Id: {plugin_id}"
).send()
result = await ShopManage.add_plugin(plugin_id)
except Exception as e:
logger.error(f"添加插件 Id: {plugin_id}失败", "插件商店", session=session, e=e)
await MessageUtils.build_message(
f"添加插件 Id: {plugin_id} 失败 e: {e}"
).finish()
logger.info(f"添加插件 Id: {plugin_id}", "插件商店", session=session)
await MessageUtils.build_message(result).send()
@_matcher.assign("remove")
async def _(session: EventSession, plugin_id: int):
try:
result = await ShopManage.remove_plugin(plugin_id)
except Exception as e:
logger.error(f"移除插件 Id: {plugin_id}失败", "插件商店", session=session, e=e)
await MessageUtils.build_message(
f"移除插件 Id: {plugin_id} 失败 e: {e}"
).finish()
logger.info(f"移除插件 Id: {plugin_id}", "插件商店", session=session)
await MessageUtils.build_message(result).send()
@_matcher.assign("search")
async def _(session: EventSession, plugin_name_or_author: str):
try:
result = await ShopManage.search_plugin(plugin_name_or_author)
except Exception as e:
logger.error(f"搜索插件 name: {plugin_name_or_author}失败", "插件商店", session=session, e=e)
await MessageUtils.build_message(
f"搜索插件 name: {plugin_name_or_author} 失败 e: {e}"
).finish()
logger.info(f"搜索插件 name: {plugin_name_or_author}", "插件商店", session=session)
await MessageUtils.build_message(result).send()
@_matcher.assign("update")
async def _(session: EventSession, plugin_id: int):
try:
await MessageUtils.build_message(
f"正在更新插件 Id: {plugin_id}"
).send()
result = await ShopManage.update_plugin(plugin_id)
except Exception as e:
logger.error(f"更新插件 Id: {plugin_id}失败", "插件商店", session=session, e=e)
await MessageUtils.build_message(
f"更新插件 Id: {plugin_id} 失败 e: {e}"
).finish()
logger.info(f"更新插件 Id: {plugin_id}", "插件商店", session=session)
await MessageUtils.build_message(result).send()
@@ -0,0 +1,19 @@
from pathlib import Path
BASE_PATH = Path() / "zhenxun"
BASE_PATH.mkdir(parents=True, exist_ok=True)
CONFIG_URL = "https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins/plugins.json"
"""插件信息文件"""
CONFIG_INDEX_URL = "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins_index/index/plugins.json"
"""插件索引库信息文件"""
CONFIG_INDEX_CDN_URL = "https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins_index@index/plugins.json"
"""插件索引库信息文件cdn"""
DOWNLOAD_URL = (
"https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/contents/{}?ref=main"
)
"""插件下载地址"""
@@ -0,0 +1,434 @@
import re
import shutil
import subprocess
from pathlib import Path
import aiofiles
import ujson as json
from zhenxun.services.log import logger
from zhenxun.utils.http_utils import AsyncHttpx
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.utils.image_utils import RowStyle, BuildImage, ImageTemplate
from .config import (
BASE_PATH,
CONFIG_URL,
DOWNLOAD_URL,
CONFIG_INDEX_URL,
CONFIG_INDEX_CDN_URL,
)
def row_style(column: str, text: str) -> RowStyle:
"""被动技能文本风格
参数:
column: 表头
text: 文本内容
返回:
RowStyle: RowStyle
"""
style = RowStyle()
if column == "-" and text == "已安装":
style.font_color = "#67C23A"
return style
async def recurrence_get_url(
url: str,
data_list: list[tuple[str, str]],
ignore_list: list[str] | None = None,
api_url: str | None = None,
):
"""递归获取目录下所有文件
参数:
url: 信息url
data_list: 数据列表
异常:
ValueError: 访问错误
"""
if ignore_list is None:
ignore_list = []
logger.debug(f"访问插件下载信息 URL: {url}", "插件管理")
res = await AsyncHttpx.get(url)
if res.status_code != 200:
raise ValueError(f"访问错误, code: {res.status_code}")
json_data = res.json()
if isinstance(json_data, list):
data_list.extend((v.get("download_url"), v["path"]) for v in json_data)
else:
data_list.append((json_data.get("download_url"), json_data["path"]))
for download_url, path in data_list:
if not download_url:
_url = api_url + path if api_url else DOWNLOAD_URL.format(path)
if _url not in ignore_list:
ignore_list.append(_url)
await recurrence_get_url(_url, data_list, ignore_list, api_url)
async def download_file(url: str, _is: bool = False, api_url: str | None = None):
"""下载文件
参数:
url: 插件详情url
_is: 是否为第三方插件
url_start : 第三方插件url
异常:
ValueError: 下载失败
"""
data_list = []
await recurrence_get_url(url, data_list, api_url=api_url)
for download_url, path in data_list:
if download_url and "." in path:
logger.debug(f"下载文件: {path}", "插件管理")
base_path = "zhenxun/plugins/" if _is else "zhenxun/"
file = Path(f"{base_path}{path}")
file.parent.mkdir(parents=True, exist_ok=True)
r = await AsyncHttpx.get(download_url)
if r.status_code != 200:
raise ValueError(f"文件下载错误, code: {r.status_code}")
content = r.text.replace("\r\n", "\n") # 统一换行符为 UNIX 风格
async with aiofiles.open(file, "w", encoding="utf8") as f:
logger.debug(f"写入文件: {file}", "插件管理")
await f.write(content)
def install_requirement(plugin_path: Path):
requirement_files = ["requirement.txt", "requirements.txt"]
requirement_paths = [plugin_path / file for file in requirement_files]
existing_requirements = next(
(path for path in requirement_paths if path.exists()), None
)
if not existing_requirements:
logger.debug(
f"No requirement.txt found for plugin: {plugin_path.name}", "插件管理"
)
return
try:
result = subprocess.run(
["pip", "install", "-r", str(existing_requirements)],
check=True,
capture_output=True,
text=True,
)
logger.debug(
"Successfully installed dependencies for"
f" plugin: {plugin_path.name}. Output:\n{result.stdout}",
"插件管理",
)
except subprocess.CalledProcessError:
logger.error(
f"Failed to install dependencies for plugin: {plugin_path.name}. "
" Error:\n{e.stderr}"
)
class ShopManage:
type2name = { # noqa: RUF012
"NORMAL": "普通插件",
"ADMIN": "管理员插件",
"SUPERUSER": "超级用户插件",
"ADMIN_SUPERUSER": "管理员/超级用户插件",
"DEPENDANT": "依赖插件",
"HIDDEN": "其他插件",
}
@classmethod
async def __get_data(cls) -> dict:
"""获取插件信息数据
异常:
ValueError: 访问请求失败
返回:
dict: 插件信息数据
"""
res = await AsyncHttpx.get(CONFIG_URL)
res2 = await AsyncHttpx.get(CONFIG_INDEX_URL)
if res2.status_code != 200:
logger.info("访问第三方插件信息文件失败,改为进行cdn访问")
res2 = await AsyncHttpx.get(CONFIG_INDEX_CDN_URL)
# 检查请求结果
if res.status_code != 200 or res2.status_code != 200:
raise ValueError(f"下载错误, code: {res.status_code}, {res2.status_code}")
# 解析并合并返回的 JSON 数据
data1 = json.loads(res.text)
data2 = json.loads(res2.text)
return {**data1, **data2}
@classmethod
def version_check(cls, plugin_info: dict, suc_plugin: dict[str, str]):
module = plugin_info["module"]
if module in suc_plugin and plugin_info["version"] != suc_plugin[module]:
return f"{suc_plugin[module]} (有更新->{plugin_info['version']})"
return plugin_info["version"]
@classmethod
def get_url_path(cls, module_path: str, is_dir: bool) -> str:
url_path = None
path = BASE_PATH
module_path_split = module_path.split(".")
if len(module_path_split) == 2:
"""单个文件或文件夹"""
if is_dir:
url_path = "/".join(module_path_split)
else:
url_path = "/".join(module_path_split) + ".py"
else:
"""嵌套文件或文件夹"""
for p in module_path_split[:-1]:
path = path / p
path.mkdir(parents=True, exist_ok=True)
if is_dir:
url_path = f"{'/'.join(module_path_split)}"
else:
url_path = f"{'/'.join(module_path_split)}.py"
return url_path
@classmethod
async def get_plugins_info(cls) -> BuildImage | str:
"""插件列表
返回:
BuildImage | str: 返回消息
"""
data: dict = await cls.__get_data()
column_name = ["-", "ID", "名称", "简介", "作者", "版本", "类型"]
for k in data.copy():
if data[k]["plugin_type"]:
data[k]["plugin_type"] = cls.type2name[data[k]["plugin_type"]]
plugin_list = await PluginInfo.filter(load_status=True).values_list(
"module", "version"
)
suc_plugin = {p[0]: p[1] for p in plugin_list if p[1]}
data_list = [
[
"已安装" if plugin_info[1]["module"] in suc_plugin else "",
id,
plugin_info[0],
plugin_info[1]["description"],
plugin_info[1]["author"],
cls.version_check(plugin_info[1], suc_plugin),
plugin_info[1]["plugin_type"],
]
for id, plugin_info in enumerate(data.items())
]
return await ImageTemplate.table_page(
"插件列表",
"通过安装/卸载插件 ID 来管理插件",
column_name,
data_list,
text_style=row_style,
)
@classmethod
async def add_plugin(cls, plugin_id: int) -> str:
"""添加插件
参数:
plugin_id: 插件id
返回:
str: 返回消息
"""
data: dict = await cls.__get_data()
if plugin_id < 0 or plugin_id >= len(data):
return "插件ID不存在..."
plugin_key = list(data.keys())[plugin_id]
plugin_info = data[plugin_key]
module_path_split = plugin_info["module_path"].split(".")
url_path = cls.get_url_path(plugin_info["module_path"], plugin_info["is_dir"])
if not url_path and plugin_info["module_path"]:
return "插件下载地址构建失败..."
logger.debug(f"尝试下载插件 URL: {url_path}", "插件管理")
github_url = plugin_info.get("github_url")
if github_url:
if not (r := re.search(r"github\.com/([^/]+/[^/]+)", github_url)):
return "github地址格式错误"
github_path = r[1]
api_url = f"https://api.github.com/repos/{github_path}/contents/"
download_url = f"{api_url}{url_path}?ref=main"
else:
download_url = DOWNLOAD_URL.format(url_path)
api_url = None
await download_file(download_url, bool(github_url), api_url)
# 安装依赖
plugin_path = BASE_PATH / "/".join(module_path_split)
if url_path and github_url and api_url:
plugin_path = BASE_PATH / "plugins" / "/".join(module_path_split)
res = await AsyncHttpx.get(api_url)
if res.status_code != 200:
return f"访问错误, code: {res.status_code}"
json_data = res.json()
if requirement_file := next(
(
v
for v in json_data
if v["name"] in ["requirements.txt", "requirement.txt"]
),
None,
):
r = await AsyncHttpx.get(requirement_file.get("download_url"))
if r.status_code != 200:
raise ValueError(f"文件下载错误, code: {r.status_code}")
requirement_path = plugin_path / requirement_file["name"]
async with aiofiles.open(requirement_path, "w", encoding="utf8") as f:
logger.debug(f"写入文件: {requirement_path}", "插件管理")
await f.write(r.text)
install_requirement(plugin_path)
return f"插件 {plugin_key} 安装成功! 重启后生效"
@classmethod
async def remove_plugin(cls, plugin_id: int) -> str:
"""移除插件
参数:
plugin_id: 插件id
返回:
str: 返回消息
"""
data: dict = await cls.__get_data()
if plugin_id < 0 or plugin_id >= len(data):
return "插件ID不存在..."
plugin_key = list(data.keys())[plugin_id]
plugin_info = data[plugin_key]
path = BASE_PATH
if plugin_info.get("github_url"):
path = BASE_PATH / "plugins"
for p in plugin_info["module_path"].split("."):
path = path / p
if not plugin_info["is_dir"]:
path = Path(f"{path}.py")
if not path.exists():
return f"插件 {plugin_key} 不存在..."
logger.debug(f"尝试移除插件 {plugin_key} 文件: {path}", "插件管理")
if plugin_info["is_dir"]:
shutil.rmtree(path)
else:
path.unlink()
return f"插件 {plugin_key} 移除成功! 重启后生效"
@classmethod
async def search_plugin(cls, plugin_name_or_author: str) -> BuildImage | str:
"""搜索插件
参数:
plugin_name_or_author: 插件名称或作者
返回:
BuildImage | str: 返回消息
"""
data: dict = await cls.__get_data()
for k in data.copy():
if data[k]["plugin_type"]:
data[k]["plugin_type"] = cls.type2name[data[k]["plugin_type"]]
plugin_list = await PluginInfo.filter(load_status=True).values_list(
"module", "version"
)
suc_plugin = {p[0]: p[1] for p in plugin_list if p[1]}
filtered_data = [
(id, plugin_info)
for id, plugin_info in enumerate(data.items())
if plugin_name_or_author.lower() in plugin_info[0].lower()
or plugin_name_or_author.lower() in plugin_info[1]["author"].lower()
]
data_list = [
[
"已安装" if plugin_info[1]["module"] in suc_plugin else "",
id,
plugin_info[0],
plugin_info[1]["description"],
plugin_info[1]["author"],
cls.version_check(plugin_info[1], suc_plugin),
plugin_info[1]["plugin_type"],
]
for id, plugin_info in filtered_data
]
if not data_list:
return "未找到相关插件..."
column_name = ["-", "ID", "名称", "简介", "作者", "版本", "类型"]
return await ImageTemplate.table_page(
"插件列表",
"通过添加/移除插件 ID 来管理插件",
column_name,
data_list,
text_style=row_style,
)
@classmethod
async def update_plugin(cls, plugin_id: int) -> str:
"""更新插件
参数:
plugin_id: 插件id
返回:
str: 返回消息
"""
data: dict = await cls.__get_data()
if plugin_id < 0 or plugin_id >= len(data):
return "插件ID不存在..."
plugin_key = list(data.keys())[plugin_id]
plugin_info = data[plugin_key]
module_path_split = plugin_info["module_path"].split(".")
url_path = cls.get_url_path(plugin_info["module_path"], plugin_info["is_dir"])
if not url_path and plugin_info["module_path"]:
return "插件下载地址构建失败..."
logger.debug(f"尝试下载插件 URL: {url_path}", "插件管理")
github_url = plugin_info.get("github_url")
if github_url:
if not (r := re.search(r"github\.com/([^/]+/[^/]+)", github_url)):
return "github地址格式错误..."
github_path = r[1]
api_url = f"https://api.github.com/repos/{github_path}/contents/"
download_url = f"{api_url}{url_path}?ref=main"
else:
download_url = DOWNLOAD_URL.format(url_path)
api_url = None
await download_file(download_url, bool(github_url), api_url)
# 安装依赖
plugin_path = BASE_PATH / "/".join(module_path_split)
if url_path and github_url and api_url:
plugin_path = BASE_PATH / "plugins" / "/".join(module_path_split)
res = await AsyncHttpx.get(api_url)
if res.status_code != 200:
return f"访问错误, code: {res.status_code}"
json_data = res.json()
if requirement_file := next(
(
v
for v in json_data
if v["name"] in ["requirements.txt", "requirement.txt"]
),
None,
):
r = await AsyncHttpx.get(requirement_file.get("download_url"))
if r.status_code != 200:
raise ValueError(f"文件下载错误, code: {r.status_code}")
requirement_path = plugin_path / requirement_file["name"]
async with aiofiles.open(requirement_path, "w", encoding="utf8") as f:
logger.debug(f"写入文件: {requirement_path}", "插件管理")
await f.write(r.text)
install_requirement(plugin_path)
return f"插件 {plugin_key} 更新成功! 重启后生效"
+84 -77
View File
@@ -1,25 +1,27 @@
import time
from datetime import datetime
import nonebot
from nonebot import on_message, on_request
from nonebot.adapters.onebot.v11 import ActionFailed
from nonebot.adapters.onebot.v11 import Bot as v11Bot
from nonebot.adapters.onebot.v11 import FriendRequestEvent, GroupRequestEvent
from nonebot.adapters.onebot.v12 import Bot as v12Bot
from nonebot.plugin import PluginMetadata
from nonebot_plugin_apscheduler import scheduler
from nonebot import on_message, on_request
from nonebot_plugin_session import EventSession
from nonebot_plugin_apscheduler import scheduler
from nonebot.adapters.onebot.v11 import Bot as v11Bot
from nonebot.adapters.onebot.v12 import Bot as v12Bot
from nonebot.adapters.onebot.v11 import (
ActionFailed,
GroupRequestEvent,
FriendRequestEvent,
)
from zhenxun.configs.config import NICKNAME, Config
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
from zhenxun.models.fg_request import FgRequest
from zhenxun.models.friend_user import FriendUser
from zhenxun.models.group_console import GroupConsole
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType, RequestHandleType, RequestType
from zhenxun.utils.message import MessageUtils
from zhenxun.models.fg_request import FgRequest
from zhenxun.utils.platform import PlatformUtils
from zhenxun.models.friend_user import FriendUser
from zhenxun.configs.config import Config, BotConfig
from zhenxun.models.group_console import GroupConsole
from zhenxun.configs.utils import RegisterConfig, PluginExtraData
from zhenxun.utils.enum import PluginType, RequestType, RequestHandleType
base_config = Config.get("invite_manager")
@@ -46,13 +48,11 @@ __plugin_meta__ = PluginMetadata(
class Timer:
data: dict[str, float] = {}
data: dict[str, float] = {} # noqa: RUF012
@classmethod
def check(cls, uid: int | str):
if uid not in cls.data:
return True
return time.time() - cls.data[uid] > 5 * 60
return True if uid not in cls.data else time.time() - cls.data[uid] > 5 * 60
@classmethod
def clear(cls):
@@ -69,16 +69,15 @@ _t = on_message(priority=999, block=False, rule=lambda: False)
@friend_req.handle()
async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSession):
superuser = nonebot.get_driver().config.platform_superusers["qq"][0]
superuser = BotConfig.get_superuser("qq")
if event.user_id and Timer.check(event.user_id):
logger.debug(f"收录好友请求...", "好友请求", target=event.user_id)
logger.debug("收录好友请求...", "好友请求", target=event.user_id)
user = await bot.get_stranger_info(user_id=event.user_id)
nickname = user["nickname"]
# sex = user["sex"]
# age = str(user["age"])
comment = event.comment
if superuser:
superuser = int(superuser)
await MessageUtils.build_message(
f"*****一份好友申请*****\n"
f"昵称:{nickname}({event.user_id})\n"
@@ -88,7 +87,7 @@ async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSessi
).send(target=PlatformUtils.get_target(bot, superuser))
if base_config.get("AUTO_ADD_FRIEND"):
logger.debug(
f"已开启好友请求自动同意,成功通过该请求",
"已开启好友请求自动同意,成功通过该请求",
"好友请求",
target=event.user_id,
)
@@ -113,22 +112,33 @@ async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSessi
comment=comment,
)
else:
logger.debug(f"好友请求五分钟内重复, 已忽略", "好友请求", target=event.user_id)
logger.debug("好友请求五分钟内重复, 已忽略", "好友请求", target=event.user_id)
@group_req.handle()
async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSession):
superuser = nonebot.get_driver().config.platform_superusers["qq"][0]
# 邀请
superuser = BotConfig.get_superuser("qq")
if event.sub_type == "invite":
if str(event.user_id) in bot.config.superusers:
try:
logger.debug(
f"超级用户自动同意加入群聊",
"超级用户自动同意加入群聊",
"群聊请求",
session=event.user_id,
target=event.group_id,
)
group, _ = await GroupConsole.update_or_create(
group_id=str(event.group_id),
defaults={
"group_name": "",
"max_member_count": 0,
"member_count": 0,
"group_flag": 1,
},
)
await bot.set_group_add_request(
flag=event.flag, sub_type="invite", approve=True
)
if isinstance(bot, v11Bot):
group_info = await bot.get_group_info(group_id=event.group_id)
max_member_count = group_info["max_member_count"]
@@ -137,17 +147,11 @@ async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSessio
group_info = await bot.get_group_info(group_id=str(event.group_id))
max_member_count = 0
member_count = 0
await GroupConsole.update_or_create(
group_id=str(event.group_id),
defaults={
"group_name": group_info["group_name"],
"max_member_count": max_member_count,
"member_count": member_count,
"group_flag": 1,
},
)
await bot.set_group_add_request(
flag=event.flag, sub_type="invite", approve=True
group.max_member_count = max_member_count
group.member_count = member_count
group.group_name = group_info["group_name"]
await group.save(
update_fields=["group_name", "max_member_count", "member_count"]
)
except ActionFailed as e:
logger.error(
@@ -157,48 +161,47 @@ async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSessio
target=event.group_id,
e=e,
)
elif Timer.check(f"{event.user_id}:{event.group_id}"):
logger.debug(
f"收录 用户[{event.user_id}] 群聊[{event.group_id}] 群聊请求",
"群聊请求",
target=event.group_id,
)
nickname = await FriendUser.get_user_name(str(event.user_id))
await PlatformUtils.send_superuser(
bot,
f"*****一份入群申请*****\n申请人:{nickname}({event.user_id})\n群聊:"
f"{event.group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}",
superuser,
)
await bot.send_private_msg(
user_id=event.user_id,
message=f"想要邀请我偷偷入群嘛~已经提醒{BotConfig.self_nickname}的管理员大人了\n"
"请确保已经群主或群管理沟通过!\n"
"等待管理员处理吧!",
)
# 旧请求全部设置为过期
await FgRequest.filter(
request_type=RequestType.GROUP,
user_id=str(event.user_id),
group_id=str(event.group_id),
handle_type__isnull=True,
).update(handle_type=RequestHandleType.EXPIRE)
await FgRequest.create(
request_type=RequestType.GROUP,
platform=session.platform,
bot_id=bot.self_id,
flag=event.flag,
user_id=str(event.user_id),
nickname=nickname,
group_id=str(event.group_id),
)
else:
if Timer.check(f"{event.user_id}:{event.group_id}"):
logger.debug(
f"收录 用户[{event.user_id}] 群聊[{event.group_id}] 群聊请求",
"群聊请求",
target=event.group_id,
)
nickname = await FriendUser.get_user_name(str(event.user_id))
await Text(
f"*****一份入群申请*****\n"
f"申请人:{nickname}({event.user_id})\n"
f"群聊:{event.group_id}\n"
f"邀请日期:{datetime.now().replace(microsecond=0)}"
).send_to(target=TargetQQPrivate(user_id=superuser), bot=bot)
await bot.send_private_msg(
user_id=event.user_id,
message=f"想要邀请我偷偷入群嘛~已经提醒{NICKNAME}的管理员大人了\n"
"请确保已经群主或群管理沟通过!\n"
"等待管理员处理吧!",
)
# 旧请求全部设置为过期
await FgRequest.filter(
request_type=RequestType.GROUP,
user_id=str(event.user_id),
group_id=str(event.group_id),
handle_type__isnull=True,
).update(handle_type=RequestHandleType.EXPIRE)
await FgRequest.create(
request_type=RequestType.GROUP,
platform=session.platform,
bot_id=bot.self_id,
flag=event.flag,
user_id=str(event.user_id),
nickname=nickname,
group_id=str(event.group_id),
)
else:
logger.debug(
f"群聊请求五分钟内重复, 已忽略",
"群聊请求",
target=f"{event.user_id}:{event.group_id}",
)
logger.debug(
"群聊请求五分钟内重复, 已忽略",
"群聊请求",
target=f"{event.user_id}:{event.group_id}",
)
@scheduler.scheduled_job(
@@ -207,3 +210,7 @@ async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSessio
)
async def _():
Timer.clear()
async def _():
Timer.clear()
@@ -11,7 +11,7 @@ from nonebot.plugin import PluginMetadata
from nonebot.rule import to_me
from nonebot_plugin_session import EventSession
from zhenxun.configs.config import NICKNAME
from zhenxun.configs.config import BotConfig
from zhenxun.configs.utils import PluginExtraData
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
@@ -48,11 +48,13 @@ RESTART_FILE = Path() / "restart.sh"
@_matcher.got(
"flag",
prompt=f"确定是否重启{NICKNAME}?确定请回复[是|好|确定](重启失败咱们将失去联系,请谨慎!)",
prompt=f"确定是否重启{BotConfig.self_nickname}?确定请回复[是|好|确定](重启失败咱们将失去联系,请谨慎!)",
)
async def _(bot: Bot, session: EventSession, flag: str = ArgStr("flag")):
if flag.lower() in ["true", "是", "好", "确定", "确定是"]:
await MessageUtils.build_message(f"开始重启{NICKNAME}..请稍等...").send()
await MessageUtils.build_message(
f"开始重启{BotConfig.self_nickname}..请稍等..."
).send()
with open(RESTART_MARK, "w", encoding="utf8") as f:
f.write(f"{bot.self_id} {session.id1}")
logger.info("开始重启真寻...", "重启", session=session)
@@ -90,7 +92,7 @@ async def _(bot: Bot):
bot_id, session_id = f.read().split()
if bot := nonebot.get_bot(bot_id):
if target := PlatformUtils.get_target(bot, session_id):
await MessageUtils.build_message(f"{NICKNAME}已成功重启!").send(
target, bot=bot
)
await MessageUtils.build_message(
f"{BotConfig.self_nickname}已成功重启!"
).send(target, bot=bot)
RESTART_MARK.unlink()
@@ -3,8 +3,8 @@ from pathlib import Path
from nonebot_plugin_apscheduler import scheduler
from zhenxun.configs.config import Config
from zhenxun.services.log import logger
from zhenxun.configs.config import Config
Config.add_plugin_config(
"_backup",
@@ -18,14 +18,7 @@ Config.add_plugin_config(
Config.add_plugin_config(
"_backup",
"BACKUP_DIR_OR_FILE",
[
"data/black_word",
"data/configs",
"data/statistics",
"data/word_bank",
"data/manager",
"configs",
],
["data"],
help="备份的文件夹或文件",
default_value=[],
type=list[str],
@@ -39,24 +32,25 @@ Config.add_plugin_config(
minute=25,
)
async def _():
if Config.get_config("_backup", "BACKUP_FLAG"):
_backup_path = Path() / "backup"
_backup_path.mkdir(exist_ok=True, parents=True)
if backup_dir_or_file := Config.get_config("_backup", "BACKUP_DIR_OR_FILE"):
for path_file in backup_dir_or_file:
try:
path = Path(path_file)
_p = _backup_path / path_file
if path.exists():
if path.is_dir():
if _p.exists():
shutil.rmtree(_p, ignore_errors=True)
shutil.copytree(path_file, _p)
else:
if _p.exists():
_p.unlink()
shutil.copy(path_file, _p)
logger.debug(f"已完成自动备份:{path_file}", "自动备份")
except Exception as e:
logger.error(f"自动备份文件 {path_file} 发生错误", "自动备份", e=e)
logger.info("自动备份成功...", "自动备份")
if not Config.get_config("_backup", "BACKUP_FLAG"):
return
_backup_path = Path() / "backup"
_backup_path.mkdir(exist_ok=True, parents=True)
if backup_dir_or_file := Config.get_config("_backup", "BACKUP_DIR_OR_FILE"):
for path_file in backup_dir_or_file:
try:
path = Path(path_file)
_p = _backup_path / path_file
if path.exists():
if path.is_dir():
if _p.exists():
shutil.rmtree(_p, ignore_errors=True)
shutil.copytree(path_file, _p)
else:
if _p.exists():
_p.unlink()
shutil.copy(path_file, _p)
logger.debug(f"已完成自动备份:{path_file}", "自动备份")
except Exception as e:
logger.error(f"自动备份文件 {path_file} 发生错误", "自动备份", e=e)
logger.info("自动备份成功...", "自动备份")
+21 -20
View File
@@ -19,22 +19,23 @@ from zhenxun.utils.platform import PlatformUtils
async def _():
"""检测群组发言时间并禁用全部被动"""
update_list = []
for bot in nonebot.get_bots().values():
group_list, _ = await PlatformUtils.get_group_list(bot)
group_list = [g for g in group_list if g.channel_id == None]
for group in group_list:
try:
last_message = (
await ChatHistory.filter(group_id=group.group_id)
.annotate()
.order_by("-create_time")
.first()
)
if last_message:
now = datetime.now(pytz.timezone("Asia/Shanghai"))
if modules := await TaskInfo.annotate().values_list(
"module", flat=True
):
if modules := await TaskInfo.annotate().values_list(
"module", flat=True
):
for bot in nonebot.get_bots().values():
group_list, _ = await PlatformUtils.get_group_list(bot)
group_list = [g for g in group_list if g.channel_id == None]
for group in group_list:
try:
last_message = (
await ChatHistory.filter(group_id=group.group_id)
.annotate()
.order_by("-create_time")
.first()
)
if last_message:
now = datetime.now(pytz.timezone("Asia/Shanghai"))
if now - timedelta(days=2) > last_message.create_time:
_group, _ = await GroupConsole.get_or_create(
group_id=group.group_id, channel_id__isnull=True
@@ -46,9 +47,9 @@ async def _():
"Chat检测",
target=_group.group_id,
)
except Exception as e:
logger.error(
"检测群组发言时间失败...", "Chat检测", target=group.group_id
)
except Exception as e:
logger.error(
"检测群组发言时间失败...", "Chat检测", target=group.group_id
)
if update_list:
await GroupConsole.bulk_update(update_list, ["block_task"], 10)
+11 -10
View File
@@ -2,14 +2,15 @@ import nonebot
from nonebot.plugin import PluginMetadata
from nonebot_plugin_apscheduler import scheduler
from zhenxun.configs.config import NICKNAME
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.configs.utils import PluginExtraData, Task
from zhenxun.models.task_info import TaskInfo
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.configs.config import BotConfig
from zhenxun.models.task_info import TaskInfo
from zhenxun.utils.message import MessageUtils
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.utils.common_utils import CommonUtils
from zhenxun.utils.platform import broadcast_group
from zhenxun.configs.utils import Task, PluginExtraData
__plugin_meta__ = PluginMetadata(
name="早晚安被动技能",
@@ -19,10 +20,7 @@ __plugin_meta__ = PluginMetadata(
author="HibiKier",
version="0.1",
plugin_type=PluginType.HIDDEN,
tasks=[
Task(module="group_welcome", name="进群欢迎"),
Task(module="refund_group_remind", name="退群提醒"),
],
tasks=[Task(module="morning_goodnight", name="早晚安")],
).dict(),
)
@@ -40,7 +38,7 @@ async def _():
async def check(group_id: str) -> bool:
return not await TaskInfo.is_block("morning_goodnight", group_id)
return not await CommonUtils.is_block("morning_goodnight", group_id)
# 早上好
@@ -63,7 +61,10 @@ async def _():
)
async def _():
message = MessageUtils.build_message(
[f"{NICKNAME}要睡觉了,你们也要早点睡呀", IMAGE_PATH / "zhenxun" / "sleep.jpg"]
[
f"{BotConfig.self_nickname}要睡觉了,你们也要早点睡呀",
IMAGE_PATH / "zhenxun" / "sleep.jpg",
]
)
await broadcast_group(
message,
@@ -72,6 +72,12 @@ __plugin_meta__ = PluginMetadata(
default_value=0.05,
type=float,
),
RegisterConfig(
key="IMAGE_STYLE",
value="zhenxun",
help="签到图片样式, [normal, zhenxun]",
default_value="zhenxun",
),
],
limits=[PluginCdBlock()],
).dict(),
+141 -12
View File
@@ -7,9 +7,10 @@ from pathlib import Path
import nonebot
import pytz
from nonebot.drivers import Driver
from nonebot_plugin_htmlrender import template_to_pic
from zhenxun.configs.config import NICKNAME, Config
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.configs.config import BotConfig, Config
from zhenxun.configs.path_config import IMAGE_PATH, TEMPLATE_PATH
from zhenxun.models.sign_log import SignLog
from zhenxun.models.sign_user import SignUser
from zhenxun.utils.image_utils import BuildImage
@@ -25,8 +26,27 @@ from .config import (
lik2relation,
)
AVA_URL = "http://q1.qlogo.cn/g?b=qq&nk={}&s=160"
driver: Driver = nonebot.get_driver()
base_config = Config.get("sign_in")
MORNING_MESSAGE = [
"早上好,希望今天是美好的一天!",
"醒了吗,今天也要元气满满哦!",
"早上好呀,今天也要开心哦!",
"早安,愿你拥有美好的一天!",
]
LG_MESSAGE = [
"今天要早点休息哦~",
"可不要熬夜到太晚呀",
"请尽早休息吧!",
"不要熬夜啦!",
]
@driver.on_startup
async def init_image():
@@ -73,9 +93,14 @@ async def get_card(
if card_file.exists():
return card_file
is_card_view = True
return await _generate_card(
user, nickname, add_impression, gold, gift, is_double, is_card_view
)
if base_config.get("IMAGE_STYLE") == "zhenxun":
return await _generate_html_card(
user, nickname, add_impression, gold, gift, is_double, is_card_view
)
else:
return await _generate_card(
user, nickname, add_impression, gold, gift, is_double, is_card_view
)
async def _generate_card(
@@ -125,7 +150,9 @@ async def _generate_card(
level = "8"
interpolation = 0
await info_img.text((0, 0), f"· 好感度等级:{level} [{lik2relation[level]}]")
await info_img.text((0, 20), f"· {NICKNAME}对你的态度:{level2attitude[level]}")
await info_img.text(
(0, 20), f"· {BotConfig.self_nickname}对你的态度:{level2attitude[level]}"
)
await info_img.text((0, 40), f"· 距离升级还差 {interpolation:.2f} 好感度")
bar_bk = BuildImage(220, 20, background=SIGN_RESOURCE_PATH / "bar_white.png")
@@ -189,7 +216,9 @@ async def _generate_card(
f"好感度:{user.impression:.2f}", size=30
)
watermark = await BuildImage.build_text_image(
f"{NICKNAME}@{datetime.now().year}", size=15, font_color=(155, 155, 155)
f"{BotConfig.self_nickname}@{datetime.now().year}",
size=15,
font_color=(155, 155, 155),
)
today_data = BuildImage(300, 300, color=(255, 255, 255, 0), font_size=20)
if is_card_view:
@@ -237,8 +266,8 @@ async def _generate_card(
_type = "sign"
current_date = datetime.now()
current_datetime_str = current_date.strftime("%Y-%m-%d %a %H:%M:%S")
data = current_date.date()
data_img = await BuildImage.build_text_image(
date = current_date.date()
date_img = await BuildImage.build_text_image(
f"时间:{current_datetime_str}", size=20
)
await bk.paste(nickname_img, (30, 15))
@@ -249,7 +278,7 @@ async def _generate_card(
# await bk.paste(sign_day_img, (398, 158))
# await bk.text((_x, 167), "days")
await bk.paste(tip_image, (10, 167))
await bk.paste(data_img, (220, 370))
await bk.paste(date_img, (220, 370))
await bk.paste(lik_text1_img, (220, 240))
await bk.paste(lik_text2_img, (262, 234))
await bk.paste(bar_bk, (225, 275))
@@ -257,8 +286,8 @@ async def _generate_card(
await bk.paste(today_sign_text_img, (550, 180))
await bk.paste(today_data, (580, 220))
await bk.paste(watermark, (15, 400))
await bk.save(SIGN_TODAY_CARD_PATH / f"{user.user_id}_{_type}_{data}.png")
return IMAGE_PATH / "sign" / "today_card" / f"{user.user_id}_{_type}_{data}.png"
await bk.save(SIGN_TODAY_CARD_PATH / f"{user.user_id}_{_type}_{date}.png")
return IMAGE_PATH / "sign" / "today_card" / f"{user.user_id}_{_type}_{date}.png"
async def generate_progress_bar_pic():
@@ -333,3 +362,103 @@ def clear_sign_data_pic():
for file in os.listdir(SIGN_TODAY_CARD_PATH):
if str(date) not in file:
os.remove(SIGN_TODAY_CARD_PATH / file)
async def _generate_html_card(
user: SignUser,
nickname: str,
impression: float,
gold: int | None,
gift: str,
is_double: bool = False,
is_card_view: bool = False,
) -> Path:
"""生成签到卡片
参数:
user: SignUser
nickname: 用户昵称
impression: 新增的好感度
gold: 金币
gift: 礼物
is_double: 是否触发双倍.
is_card_view: 是否展示好感度卡片.
返回:
Path: 卡片路径
"""
user_console = await user.user_console
if user_console and user_console.uid:
uid = f"{user_console.uid}".rjust(12, "0")
uid = uid[:4] + " " + uid[4:8] + " " + uid[8:]
else:
uid = "XXXX XXXX XXXX"
level, next_impression, previous_impression = get_level_and_next_impression(
float(user.impression)
)
interpolation = next_impression - impression
if level == "9":
level = "8"
interpolation = 0
message = f"{BotConfig.self_nickname}希望你开心!"
hour = datetime.now().hour
if hour > 6 and hour < 10:
message = random.choice(MORNING_MESSAGE)
elif hour >= 0 and hour < 6:
message = random.choice(LG_MESSAGE)
_impression = impression
if is_double:
_impression = f"{impression}(×2)"
process = 1 - (next_impression - user.impression) / (
next_impression - previous_impression
)
if next_impression == 0:
process = 0
now = datetime.now()
data = {
"ava": AVA_URL.format(user.user_id),
"name": nickname,
"uid": uid,
"sign_count": f"{user.sign_count}",
"message": f"{BotConfig.self_nickname}说: {message}",
"cur_impression": f"{user.impression:.2f}",
"impression": f"好感度+{_impression}",
"gold": f"金币+{gold}",
"gift": gift,
"level": f"{level} [{lik2relation[level]}]",
"attitude": f"对你的态度: {level2attitude[level]}",
"interpolation": f"{interpolation:.2f}",
"heart2": [1 for _ in range(int(level))],
"heart1": [1 for _ in range(9 - int(level))],
"process": process * 100,
"date": str(now.replace(microsecond=0)),
"font_size": 45,
}
if len(nickname) > 6:
data["font_size"] = 27
_type = "sign"
if is_card_view:
_type = "view"
value_list = (
await SignUser.annotate()
.order_by("-impression")
.values_list("user_id", flat=True)
)
index = value_list.index(user.user_id) + 1 # type: ignore
data["impression"] = f"好感度排名第 {index} 位"
data["gold"] = f"总金币:{gold}"
data["gift"] = ""
pic = await template_to_pic(
template_path=str((TEMPLATE_PATH / "sign").absolute()),
template_name="main.html",
templates={"data": data},
pages={
"viewport": {"width": 465, "height": 926},
"base_url": f"file://{TEMPLATE_PATH}",
},
wait=2,
)
image = BuildImage.open(pic)
date = now.date()
await image.save(SIGN_TODAY_CARD_PATH / f"{user.user_id}_{_type}_{date}.png")
return IMAGE_PATH / "sign" / "today_card" / f"{user.user_id}_{_type}_{date}.png"
@@ -6,6 +6,8 @@ from zhenxun.models.group_console import GroupConsole
from zhenxun.models.group_member_info import GroupInfoUser
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.models.statistics import Statistics
from zhenxun.utils.echart_utils import ChartUtils
from zhenxun.utils.echart_utils.models import Barh
from zhenxun.utils.enum import PluginType
from zhenxun.utils.image_utils import BuildImage, BuildMat, MatType
@@ -114,17 +116,15 @@ class StatisticsManage:
@classmethod
async def __build_image(cls, data_list: list[tuple[str, int]], title: str):
mat = BuildMat(MatType.BARH)
module2count = {x[0]: x[1] for x in data_list}
plugin_info = await PluginInfo.filter(
module__in=module2count.keys(), plugin_type=PluginType.NORMAL
module__in=module2count.keys(),
plugin_type=PluginType.NORMAL,
).all()
x_index = []
data = []
for plugin in plugin_info:
x_index.append(plugin.name)
data.append(module2count.get(plugin.module, 0))
mat.x_index = x_index
mat.data = data
mat.title = title
return await mat.build()
barh = Barh(data=data, category_data=x_index)
return await ChartUtils.barh(barh)
@@ -1,5 +1,6 @@
import nonebot_plugin_alconna as alc
from nonebot.adapters import Bot
import nonebot_plugin_alconna as alc
from nonebot_plugin_session import EventSession
# from nonebot.adapters.discord import Bot as DiscordBot
# from nonebot.adapters.dodo import Bot as DodoBot
@@ -7,12 +8,11 @@ from nonebot.adapters import Bot
# from nonebot.adapters.onebot.v11 import Bot as v11Bot
# from nonebot.adapters.onebot.v12 import Bot as v12Bot
from nonebot_plugin_alconna import Image, UniMsg
from nonebot_plugin_session import EventSession
from zhenxun.models.task_info import TaskInfo
from zhenxun.services.log import logger
from zhenxun.utils.message import MessageUtils
from zhenxun.utils.platform import PlatformUtils
from zhenxun.utils.common_utils import CommonUtils
class BroadcastManage:
@@ -42,7 +42,7 @@ class BroadcastManage:
error_count = 0
for group in group_list:
try:
if not await TaskInfo.is_block(
if not await CommonUtils.is_block(
group.group_id,
"broadcast", # group.channel_id
):
@@ -7,7 +7,7 @@ from nonebot_plugin_session import EventSession
from tortoise import Tortoise
from zhenxun.configs.utils import PluginExtraData
from zhenxun.services.db_context import TestSQL
from zhenxun.models.ban_console import BanConsole
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.image_utils import ImageTemplate
@@ -60,7 +60,7 @@ async def _(session: EventSession, message: UniMsg):
logger.info(f"执行SQL语句: {sql_text}", "exec", session=session)
try:
if not sql_text.lower().startswith("select"):
await TestSQL.raw(sql_text)
await BanConsole.raw(sql_text)
else:
db = Tortoise.get_connection("default")
res = await db.execute_query_dict(sql_text)
@@ -74,10 +74,12 @@ async def _(session: EventSession, message: UniMsg):
for c in _column:
data.append(r.get(c))
data_list.append(data)
if not data_list:
return await MessageUtils.build_message("查询结果为空!").send()
table = await ImageTemplate.table_page(
"EXEC", f"总共有 {len(data_list)} 条数据捏", list(_column), data_list
)
await MessageUtils.build_message(table).send()
return await MessageUtils.build_message(table).send()
except Exception as e:
logger.error("执行 SQL 语句失败...", session=session, e=e)
await MessageUtils.build_message(f"执行 SQL 语句失败... {type(e)}").finish()
@@ -16,7 +16,7 @@ from nonebot_plugin_alconna import (
)
from nonebot_plugin_session import EventSession
from zhenxun.configs.config import NICKNAME
from zhenxun.configs.config import BotConfig
from zhenxun.configs.utils import PluginExtraData
from zhenxun.models.group_console import GroupConsole
from zhenxun.services.log import logger
@@ -60,18 +60,17 @@ __plugin_meta__ = PluginMetadata(
_matcher = on_alconna(
Alconna(
"group-manage",
Option("--delete", action=store_true, help_text="删除"),
Subcommand(
"modify-level", Args["level", int]["group_id?", int], help_text="修改群权限"
),
Subcommand(
"super-handle",
Option("--del", action=store_true, help_text="删除"),
Args["group_id", int],
help_text="添加/删除群白名单",
),
Subcommand(
"auth-handle",
Option("--del", action=store_true, help_text="删除"),
Args["group_id", int],
help_text="添加/删除群认证",
),
@@ -99,7 +98,7 @@ _matcher.shortcut(
_matcher.shortcut(
"删除群白名单",
command="group-manage",
arguments=["super-handle", "{%0}", "--del"],
arguments=["super-handle", "{%0}", "--delete"],
prefix=True,
)
@@ -113,7 +112,7 @@ _matcher.shortcut(
_matcher.shortcut(
"删除群认证",
command="group-manage",
arguments=["auth-handle", "{%0}", "--del"],
arguments=["auth-handle", "{%0}", "--delete"],
prefix=True,
)
@@ -167,8 +166,8 @@ async def _(session: EventSession, arparma: Arparma, state: T_State):
group = await GroupConsole.get_or_none(group_id=gid)
if not group:
await MessageUtils.build_message("群组信息不存在, 请更新群组信息...").finish()
s = "删除" if arparma.find("del") else "添加"
group.is_super = not arparma.find("del")
s = "删除" if arparma.find("delete") else "添加"
group.is_super = not arparma.find("delete")
await group.save(update_fields=["is_super"])
await MessageUtils.build_message(f"{s}群白名单成功!").send(reply_to=True)
logger.info(f"{s}群白名单", arparma.header_result, session=session, target=gid)
@@ -178,9 +177,9 @@ async def _(session: EventSession, arparma: Arparma, state: T_State):
async def _(session: EventSession, arparma: Arparma, state: T_State):
gid = state["group_id"]
await GroupConsole.update_or_create(
group_id=gid, defaults={"group_flag": 0 if arparma.find("del") else 1}
group_id=gid, defaults={"group_flag": 0 if arparma.find("delete") else 1}
)
s = "删除" if arparma.find("del") else "添加"
s = "删除" if arparma.find("delete") else "添加"
await MessageUtils.build_message(f"{s}群认证成功!").send(reply_to=True)
logger.info(f"{s}群白名单", arparma.header_result, session=session, target=gid)
@@ -191,11 +190,16 @@ async def _(bot: Bot, session: EventSession, arparma: Arparma, group_id: int):
group_list = [g["group_id"] for g in await bot.get_group_list()]
if group_id not in group_list:
logger.debug("群组不存在", "退群", session=session, target=group_id)
await MessageUtils.build_message(f"{NICKNAME}未在该群组中...").finish()
await MessageUtils.build_message(
f"{BotConfig.self_nickname}未在该群组中..."
).finish()
try:
await bot.set_group_leave(group_id=group_id)
logger.info(
f"{NICKNAME}退出群组成功", "退群", session=session, target=group_id
f"{BotConfig.self_nickname}退出群组成功",
"退群",
session=session,
target=group_id,
)
await MessageUtils.build_message(f"退出群组 {group_id} 成功!").send()
await GroupConsole.filter(group_id=group_id).delete()
@@ -1,25 +1,27 @@
import asyncio
import secrets
import nonebot
from fastapi import APIRouter, FastAPI
from nonebot.log import default_filter, default_format
from fastapi import FastAPI, APIRouter
from nonebot.plugin import PluginMetadata
from nonebot.log import default_filter, default_format
from zhenxun.configs.config import Config as gConfig
from zhenxun.configs.utils import PluginExtraData
from zhenxun.services.log import logger, logger_
from zhenxun.utils.enum import PluginType
from zhenxun.services.log import logger, logger_
from zhenxun.configs.config import Config as gConfig
from zhenxun.configs.utils import RegisterConfig, PluginExtraData
from .public import init_public
from .auth import router as auth_router
from .api.logs import router as ws_log_routes
from .api.logs.log_manager import LOG_STORAGE
from .api.tabs.database import router as database_router
from .api.tabs.main import router as main_router
from .api.tabs.main import ws_router as status_routes
from .api.tabs.manage import router as manage_router
from .api.tabs.system import router as system_router
from .api.tabs.main import ws_router as status_routes
from .api.tabs.database import router as database_router
from .api.tabs.manage.chat import ws_router as chat_routes
from .api.tabs.plugin_manage import router as plugin_router
from .api.tabs.system import router as system_router
from .auth import router as auth_router
__plugin_meta__ = PluginMetadata(
name="WebUi",
@@ -27,15 +29,40 @@ __plugin_meta__ = PluginMetadata(
usage="""
""".strip(),
extra=PluginExtraData(
author="HibiKier", version="0.1", plugin_type=PluginType.HIDDEN
author="HibiKier",
version="0.1",
plugin_type=PluginType.HIDDEN,
configs=[
RegisterConfig(
module="web-ui",
key="username",
value="admin",
help="前端管理用户名",
type=str,
default_value="admin",
),
RegisterConfig(
module="web-ui",
key="password",
value=None,
help="前端管理密码",
type=str,
default_value=None,
),
RegisterConfig(
module="web-ui",
key="secret",
value=secrets.token_urlsafe(32),
help="JWT密钥",
type=str,
default_value=None,
),
],
).dict(),
)
driver = nonebot.get_driver()
gConfig.add_plugin_config("web-ui", "username", "admin", help="前端管理用户名")
gConfig.add_plugin_config("web-ui", "password", None, help="前端管理密码")
gConfig.set_name("web-ui", "web-ui")
@@ -59,7 +86,7 @@ WsApiRouter.include_router(chat_routes)
@driver.on_startup
def _():
async def _():
try:
async def log_sink(message: str):
@@ -71,7 +98,7 @@ def _():
logger.warning("Web Ui log_sink", e=e)
if not loop:
loop = asyncio.new_event_loop()
loop.create_task(LOG_STORAGE.add(message.rstrip("\n")))
loop.create_task(LOG_STORAGE.add(message.rstrip("\n"))) # noqa: RUF006
logger_.add(
log_sink, colorize=True, filter=default_filter, format=default_format
@@ -80,6 +107,7 @@ def _():
app: FastAPI = nonebot.get_app()
app.include_router(BaseApiRouter)
app.include_router(WsApiRouter)
await init_public(app)
logger.info("<g>API启动成功</g>", "Web UI")
except Exception as e:
logger.error("<g>API启动失败</g>", "Web UI", e=e)

Some files were not shown because too many files have changed in this diff Show More