@@ -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服务器的局部命令,其余命令不注册
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -14,12 +14,10 @@
|
||||
|
||||
<div align=center>
|
||||
|
||||
|
||||
[](https://github.com/HibiKier/zhenxun_bot/blob/main/LICENSE)
|
||||
[](https://jq.qq.com/?_wv=1027&k=u8PgBkMZ)
|
||||
[](https://qm.qq.com/q/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管理
|
||||
|
||||
## 感谢
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
Before Width: | Height: | Size: 728 KiB After Width: | Height: | Size: 2.0 MiB |
@@ -0,0 +1,7 @@
|
||||
|
||||
body {
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
top: -8px;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 513 KiB |
|
After Width: | Height: | Size: 706 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
@@ -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()
|
||||
@@ -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
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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("用户处于黑名单中...")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)} 条数据..."
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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} 更新成功! 重启后生效"
|
||||
@@ -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("自动备份成功...", "自动备份")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||