From 2653f6719382937647f9e4eb29d4f608d8fb61ce Mon Sep 17 00:00:00 2001
From: HibiKier <775757368@qq.com>
Date: Thu, 16 Dec 2021 11:16:28 +0800
Subject: [PATCH] update 0.0.7.0
---
README.md | 158 ++----
basic_plugins/admin_bot_manage/data_source.py | 53 +-
basic_plugins/admin_bot_manage/rule.py | 4 +-
basic_plugins/admin_bot_manage/switch_rule.py | 20 +-
basic_plugins/admin_help/data_source.py | 6 +-
basic_plugins/apscheduler/__init__.py | 60 +-
basic_plugins/broadcast/__init__.py | 8 +
basic_plugins/group_handle/__init__.py | 16 +
basic_plugins/help/data_source.py | 28 +-
basic_plugins/hooks/__init__.py | 2 -
basic_plugins/hooks/auth_hook.py | 42 +-
basic_plugins/hooks/ban_hook.py | 7 +-
basic_plugins/nickname.py | 2 +-
basic_plugins/super_cmd/exec_sql.py | 38 ++
.../super_cmd/set_admin_permissions.py | 8 +-
basic_plugins/super_help/data_source.py | 8 +-
bot.py | 4 +-
configs/config.py | 4 +-
configs/utils/__init__.py | 6 +-
models/friend_user.py | 5 +-
models/group_member_info.py | 5 +-
models/sign_group_user.py | 30 +-
plugins/about.py | 45 ++
plugins/ai/utils.py | 5 +-
plugins/alapi/data_source.py | 10 +-
plugins/bilibili_sub/utils.py | 14 +-
plugins/c_song/music_163.py | 5 +-
plugins/check/data_source.py | 8 +-
plugins/check_zhenxun_update/data_source.py | 6 +-
plugins/dialogue/__init__.py | 2 +-
plugins/draw_card/util.py | 20 +-
plugins/epic/__init__.py | 13 +-
plugins/fudu.py | 9 +-
plugins/genshin/almanac/__init__.py | 9 +
plugins/genshin/material_remind/__init__.py | 10 +-
plugins/genshin/query_resource_points/map.py | 10 +-
.../query_resource_points/query_resource.py | 14 +-
plugins/genshin/query_user/__init__.py | 26 +
plugins/genshin/query_user/bind/__init__.py | 97 ++++
plugins/genshin/query_user/models/__init__.py | 210 +++++++
.../genshin/query_user/query_memo/__init__.py | 53 ++
.../query_user/query_memo/data_source.py | 237 ++++++++
.../genshin/query_user/query_role/__init__.py | 61 ++
.../query_user/query_role/data_source.py | 249 ++++++++
.../query_user/query_role/draw_image.py | 530 ++++++++++++++++++
.../reset_today_query_user_data/__init__.py | 17 +
plugins/genshin/query_user/utils/__init__.py | 35 ++
plugins/gold_redbag/__init__.py | 5 +-
plugins/gold_redbag/data_source.py | 18 +-
plugins/image_management/__init__.py | 13 +
.../image_management/delete_image/__init__.py | 27 +-
.../image_management/move_image/__init__.py | 19 +-
.../image_management/send_image/__init__.py | 46 +-
.../upload_image/data_source.py | 17 +-
plugins/luxun/__init__.py | 6 +-
plugins/one_friend/__init__.py | 10 +-
plugins/open_cases/__init__.py | 9 +
plugins/open_cases/open_cases_c.py | 12 +-
plugins/open_cases/utils.py | 2 +-
plugins/parse_bilibili_json.py | 15 +-
plugins/pid_search.py | 2 +
plugins/pix_gallery/__init__.py | 8 +
plugins/pix_gallery/data_source.py | 12 +-
plugins/pix_gallery/pix.py | 17 +-
plugins/poke/__init__.py | 2 +-
plugins/russian/data_source.py | 4 +-
plugins/send_setu_/send_setu/__init__.py | 5 +
plugins/send_setu_/send_setu/data_source.py | 16 +-
plugins/shop/shop_handle/data_source.py | 8 +-
plugins/sign_in/__init__.py | 28 +-
plugins/sign_in/group_user_checkin.py | 36 +-
plugins/sign_in/utils.py | 60 +-
plugins/statistics/statistics_handle.py | 12 +-
plugins/update_picture.py | 4 +-
plugins/white2black_image.py | 8 +-
plugins/word_bank/word_hanlde.py | 4 +-
plugins/yiqing/__init__.py | 2 +-
.../genshin_card/chars_ava/10000002.png | Bin 0 -> 38185 bytes
.../genshin_card/chars_ava/10000003.png | Bin 0 -> 34708 bytes
.../genshin_card/chars_ava/10000005.png | Bin 0 -> 31636 bytes
.../genshin_card/chars_ava/10000006.png | Bin 0 -> 41978 bytes
.../genshin_card/chars_ava/10000007.png | Bin 0 -> 33067 bytes
.../genshin_card/chars_ava/10000014.png | Bin 0 -> 36185 bytes
.../genshin_card/chars_ava/10000015.png | Bin 0 -> 39627 bytes
.../genshin_card/chars_ava/10000016.png | Bin 0 -> 37769 bytes
.../genshin_card/chars_ava/10000020.png | Bin 0 -> 42718 bytes
.../genshin_card/chars_ava/10000021.png | Bin 0 -> 37053 bytes
.../genshin_card/chars_ava/10000022.png | Bin 0 -> 35020 bytes
.../genshin_card/chars_ava/10000023.png | Bin 0 -> 32522 bytes
.../genshin_card/chars_ava/10000024.png | Bin 0 -> 38384 bytes
.../genshin_card/chars_ava/10000025.png | Bin 0 -> 30620 bytes
.../genshin_card/chars_ava/10000026.png | Bin 0 -> 37637 bytes
.../genshin_card/chars_ava/10000027.png | Bin 0 -> 34969 bytes
.../genshin_card/chars_ava/10000029.png | Bin 0 -> 39578 bytes
.../genshin_card/chars_ava/10000030.png | Bin 0 -> 32021 bytes
.../genshin_card/chars_ava/10000031.png | Bin 0 -> 39915 bytes
.../genshin_card/chars_ava/10000032.png | Bin 0 -> 35110 bytes
.../genshin_card/chars_ava/10000033.png | Bin 0 -> 36657 bytes
.../genshin_card/chars_ava/10000034.png | Bin 0 -> 36321 bytes
.../genshin_card/chars_ava/10000035.png | Bin 0 -> 39540 bytes
.../genshin_card/chars_ava/10000036.png | Bin 0 -> 32290 bytes
.../genshin_card/chars_ava/10000037.png | Bin 0 -> 33883 bytes
.../genshin_card/chars_ava/10000038.png | Bin 0 -> 33979 bytes
.../genshin_card/chars_ava/10000039.png | Bin 0 -> 36587 bytes
.../genshin_card/chars_ava/10000041.png | Bin 0 -> 40943 bytes
.../genshin_card/chars_ava/10000042.png | Bin 0 -> 36944 bytes
.../genshin_card/chars_ava/10000043.png | Bin 0 -> 40441 bytes
.../genshin_card/chars_ava/10000044.png | Bin 0 -> 42036 bytes
.../genshin_card/chars_ava/10000045.png | Bin 0 -> 38541 bytes
.../genshin_card/chars_ava/10000046.png | Bin 0 -> 40449 bytes
.../genshin_card/chars_ava/10000047.png | Bin 0 -> 38879 bytes
.../genshin_card/chars_ava/10000048.png | Bin 0 -> 41430 bytes
.../genshin_card/chars_ava/10000049.png | Bin 0 -> 40789 bytes
.../genshin_card/chars_ava/10000050.png | Bin 0 -> 254544 bytes
.../genshin_card/chars_ava/10000051.png | Bin 0 -> 45899 bytes
.../genshin_card/chars_ava/10000052.png | Bin 0 -> 288666 bytes
.../genshin_card/chars_ava/10000053.png | Bin 0 -> 37819 bytes
.../genshin_card/chars_ava/10000054.png | Bin 0 -> 210180 bytes
.../genshin_card/chars_ava/10000056.png | Bin 0 -> 225288 bytes
.../genshin_card/chars_ava/10000062.png | Bin 0 -> 254544 bytes
resources/img/genshin/genshin_card/cover.png | Bin 0 -> 32883 bytes
.../img/genshin/genshin_card/element.png | Bin 0 -> 2732 bytes
resources/img/genshin/genshin_card/head.png | Bin 0 -> 6186 bytes
.../img/genshin/genshin_card/homes/lock.png | Bin 0 -> 2385 bytes
.../img/genshin/genshin_card/logo/璃月.png | Bin 0 -> 14852 bytes
.../img/genshin/genshin_card/logo/稻妻.png | Bin 0 -> 13983 bytes
.../img/genshin/genshin_card/logo/蒙德.png | Bin 0 -> 18069 bytes
.../genshin/genshin_card/logo/龙脊雪山.png | Bin 0 -> 12548 bytes
resources/img/genshin/genshin_card/middle.png | Bin 0 -> 3915 bytes
resources/img/genshin/genshin_memo/resin.png | Bin 0 -> 43259 bytes
.../genshin/genshin_memo/resin_discount.png | Bin 0 -> 3550 bytes
resources/img/genshin/genshin_memo/task.png | Bin 0 -> 7122 bytes
update_info.json | 5 +-
utils/browser.py | 2 +-
utils/data_utils.py | 8 +-
utils/http_utils.py | 155 ++---
utils/image_utils.py | 277 ++++++++-
utils/manager/data_class.py | 6 +-
utils/manager/group_manager.py | 121 +++-
utils/manager/requests_manager.py | 22 +-
utils/manager/resources_manager.py | 8 +-
141 files changed, 2693 insertions(+), 547 deletions(-)
create mode 100644 basic_plugins/super_cmd/exec_sql.py
create mode 100644 plugins/about.py
create mode 100644 plugins/genshin/query_user/__init__.py
create mode 100644 plugins/genshin/query_user/bind/__init__.py
create mode 100644 plugins/genshin/query_user/models/__init__.py
create mode 100644 plugins/genshin/query_user/query_memo/__init__.py
create mode 100644 plugins/genshin/query_user/query_memo/data_source.py
create mode 100644 plugins/genshin/query_user/query_role/__init__.py
create mode 100644 plugins/genshin/query_user/query_role/data_source.py
create mode 100644 plugins/genshin/query_user/query_role/draw_image.py
create mode 100644 plugins/genshin/query_user/reset_today_query_user_data/__init__.py
create mode 100644 plugins/genshin/query_user/utils/__init__.py
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000002.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000003.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000005.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000006.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000007.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000014.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000015.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000016.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000020.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000021.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000022.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000023.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000024.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000025.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000026.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000027.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000029.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000030.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000031.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000032.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000033.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000034.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000035.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000036.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000037.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000038.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000039.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000041.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000042.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000043.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000044.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000045.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000046.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000047.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000048.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000049.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000050.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000051.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000052.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000053.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000054.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000056.png
create mode 100644 resources/img/genshin/genshin_card/chars_ava/10000062.png
create mode 100644 resources/img/genshin/genshin_card/cover.png
create mode 100644 resources/img/genshin/genshin_card/element.png
create mode 100644 resources/img/genshin/genshin_card/head.png
create mode 100644 resources/img/genshin/genshin_card/homes/lock.png
create mode 100644 resources/img/genshin/genshin_card/logo/璃月.png
create mode 100644 resources/img/genshin/genshin_card/logo/稻妻.png
create mode 100644 resources/img/genshin/genshin_card/logo/蒙德.png
create mode 100644 resources/img/genshin/genshin_card/logo/龙脊雪山.png
create mode 100644 resources/img/genshin/genshin_card/middle.png
create mode 100644 resources/img/genshin/genshin_memo/resin.png
create mode 100644 resources/img/genshin/genshin_memo/resin_discount.png
create mode 100644 resources/img/genshin/genshin_memo/task.png
diff --git a/README.md b/README.md
index 25a86aa6..d4e3154d 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@
## 未完成的文档
-[传送门](https://hibikier.github.io/zhenxun_bot/)
+
[传送门](https://hibikier.github.io/zhenxun_bot/)
## 真寻的帮助
请对真寻说: '真寻帮助' or '管理员帮助' or '超级用户帮助' or '真寻帮助 指令'
@@ -51,9 +51,11 @@
- [x] 商店/我的金币/购买道具/使用道具
- [x] 8种手游抽卡 (查看 [nonebot_plugin_gamedraw](https://github.com/HibiKier/nonebot_plugin_gamedraw))
- [x] 我有一个朋友想问问..(借鉴pcrbot插件)
-- [x] 原神黄历 (使用[Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot)插件)
+- [x] 原神黄历
- [x] 原神今日素材
- [x] 原神资源查询 (借鉴[Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot)插件)
+- [x] 原神便笺查询
+- [x] 原神玩家查询
- [x] 金币红包
- [x] 微博热搜
- [x] B站主播/UP/番剧订阅
@@ -89,6 +91,8 @@
- [x] 通过PID获取图片
- [x] 功能统计可视化
+- [x] 关于
+
### 已实现的管理员功能
- [x] 更新群组成员信息
- [x] 95%的群功能开关
@@ -101,6 +105,7 @@
- [x] 删除图片 (同上)
- [x] 群内B站订阅
- [x] 群词条
+- [x] 休息吧/醒来
### 已实现的超级用户功能
- [x] 添加/删除权限(是真寻的管理员权限,不是群管理员)
@@ -126,6 +131,11 @@
- [x] 功能开关(更多设置)
- [x] 功能状态
- [x] b了
+- [x] 执行sql
+- [x] 重载配置
+- [x] 清理临时数据
+- [x] 增删群认证
+- [x] 同意/拒绝好友/群聊请求
#### 超级用户的被动技能
- [x] 邀请入群提醒(别人邀请真寻入群)
@@ -152,127 +162,6 @@
- [x] 定时更新权限
-## 功能具体指令
-
-功能具体指令说明
-
-### 常用功能
-
-** [*]:表示该参数可有多个 [?]:表示参数可选 **
-
-| 功能 | 指令 | 说明
-| ----------------------| :--------------------------------------:| :------------------------:
-| 签到 | 签到/我的签到/好感度排行/好感度总榜/好感度总榜\[显示我/屏蔽我] | 普通的签到插件,可以获得好感度和金币
好感度影响开箱次数和涩图触发概率,金币用于购买道具,俄罗斯轮盘赌注以及金币红包
好感度总榜,显示所有群的群员好感度排行,可通过命令好感度总榜\[显示我/屏蔽我] 来设置是否隐藏
-| 发送图片 | 美图/壁纸/萝莉 \[id]?(默认随机)/\[num]张图\[keyword] | 发送指定文件夹下的图片
示例:萝莉->发送img文件夹下luoli文件夹下的图片
在线搜索一些不色的图,示例:3张图米浴
-| 色图 | 色图/色图xx/n张色图/n张xx的色图/查色图(查询本地色图信息)/色图r【n<10】 | 色图r返回10张r18色图(仅私聊),并限制每日次数(默认5次)
其他示例:色图 真寻
5张真寻的色图
-| 黑白草图 | 黑白草图/黑白图 \[文字] \[图片] | 整活生成器,示例:黑白图 我喜欢真寻 \[图片]
-| coser | coser/cos/括丝 | coser图片,说实话挺失望的,太色了
-| 骂我 | 骂我 | 就是发送钉宫的语音罢了
-| 戳一戳 | 戳一戳 | 随机发送钉宫语音 or 美图 or 萝莉图 or 文本
-| 模拟开箱 | 开箱 \[武器箱名称](默认随机)/N连开箱 \[武器箱名称](默认随机)/我的开箱/群开箱统计/我的金色 | 当不指定武器箱时默认随机,此功能需要先在/open_cases/config.py中编写指定武器箱数据,然后提前爬取价格,使用超级用户命令更新cookie后,再使用命令更新xx武器箱【注:未设置爬取频率,可能会被禁用api(请谨慎!!用小号!!)】
-| 鲁迅说过 | 鲁迅说过 \[文本] | 示例:鲁迅说过 真寻世界第一可爱
-| 假消息 | 假消息 \[网址] \[标题] \[内容]? \[图片]? | 构造虚假的分享消息
-| 商店系统 | 商店/我的金币/购买道具 \[名称或序号] \[数量](默认1)/使用道具 \[名称或序号] | 示例:
购买道具 1 3
购买道具 好感度双倍加持卡Ⅰ 3
使用道具 1
使用道具 好感度双倍加持卡Ⅰ
-| 抽卡系统 | 原神/明日方舟/赛马娘/坎公骑冠剑/碧蓝航线/阴阳师/公主连结(pcr)/FGO N抽/一井 | 详细帮助请查看: [nonebot_plugin_gamedraw](https://github.com/HibiKier/nonebot_plugin_gamedraw)
示例:原神90抽
-| 我有一个朋友 | 我有一个朋友他说/想问问/\[文本] | 会将文本中的(他,她,它)替换成 '我'
示例:我有一个朋友想问问他喜不喜欢真寻
-| 昵称系统 | 以后叫我\[昵称]/以后请叫我\[昵称]/我是谁/我叫什么 | 此昵称会替换与真寻聊天中 '你' 的名称(群名片),群与群与私聊的昵称相互独立
-| 原神黄历 | 原神黄历 | 查看今日原神黄历,含有每日10:25的定时任务
-| 原神材料 | 今日素材 | 发送可莉特调的截图
-| 丘丘语翻译 | 丘丘翻译/丘丘一下/丘丘语翻译 | 示例:丘丘一下 mimi
-| 原神资源查询 | 原神资源查询 \[资源名称] \[路线]?/\[资源名称]在哪/哪里有\[资源名称]/原神资源列表 | 如果资源名称末尾添加‘路线’的话将生成残缺缺缺版的优先路径
示例:嘟嘟莲在哪
原神资源查询嘟嘟莲路线
-| 俄罗斯轮盘 | 装弹\[子弹数] \[金额](默认200)/开枪/结算/我的战绩/胜场排行/败场排行/欧洲人排行/慈善家排行 | 紧张刺激的群内小游戏,使用每日签到的金币作为赌注,具体玩法请发送 真寻帮助 俄罗斯轮盘
-| 红包系统 | 塞红包\[金额] \[数量](默认5)/抢/开/戳一戳/退回 | 仿微信明日方舟红包的样式(pil拼图大师!),每个红包金额随机生成,最多会是红包总金额的1/3,退回用于退回一分钟后还未开完的红包
-| 金币排行 | 金币排行 | 字面意思
-|网易云热评 | 到点了/12点了/网易云热评/网易云评论 | 防下塔
-| 古诗 | 念诗/念首诗/来首诗 | 突然文艺起来了
-|微博热搜 | 微博热搜/微博热搜[序号]? | 快捷热搜查询方式
-| pil对图片的操作 | 修改尺寸/等比压缩/旋转图片/水平翻转/铅笔滤镜/模糊效果/锐化效果/高斯模糊/边缘检测/底色替换 | 选项较多,请直接发送 真寻图片帮助
-| BUFF皮肤底价查询 | 查询皮肤 \[武器名称] \[皮肤名称] | 网络不友好的话会经常超时
示例:查询皮肤 沙漠之鹰 印花集
-| 天气查询 | \[城市]天气 | 非常常见的插件,第一个入门插件
-| 疫情查询 | 疫情/查询疫情 \[城市名或省份名] | 示例:疫情杭州
-| bt磁力搜索 | bt \[关键词] \[页数]?(默认第1页) | 该功能仅仅提供给私聊,因为可以搜到一些色色的东西,示例:bt钢铁侠 5
-| 上车 | 略 | 直接查看真寻帮助 上车,每日限制次数(默认5)
-| 以图识番 | 识番 \[图片] | 以图搜翻,图片越清晰越完整正确率越高
-| 以图搜图 | 识图 (asc)? \[图片] | 参数asc更换搜索引擎为ascii2d,默认为saucenao
-| 点歌 | 点歌 \[歌名] | 网易云点歌小助手
-| 搜番 | 搜番 \[关键字] | 群聊只返回5个结果,私聊返回20个结果
-| epic白嫖游戏通知 | epic | 通知你又到了白嫖游戏的时候,可以不玩,不能没有
-| P站排行榜 | p站排行 \[排行类型参数]? \[数量]? \[日期]?| 9种不同排行榜,r18类型仅可私聊,通过参数选择,查看真寻帮助p站排行
示例:p站排行榜 1 9 2018-4-25
-| 搜图 | 搜图 \[关键词] \[数量]? \[排序方式]? \[r18]?| r18仅可私聊,查看真寻帮助搜图
-| 通过PID搜索图片 | p搜 [pid] | 在群内使用此功能会在30秒内撤回
-| 翻译 | 英翻/日翻/韩翻/翻韩/翻日/翻英 \[文本] | 三种语言互相翻译
-| 获取b站视频封面 | b封面 [链接/av/bv/cv/直播id] | 快捷的封面获取方式
-| 群欢迎消息 | 群欢迎消息/查看群欢迎消息/查看当前群欢迎消息 | 查看给真寻设置的群欢迎消息
-| 自我介绍 | 自我介绍 | 没错,一份正经的真寻自我介绍
-| 我的权限 | 我的权限 | 真寻内部定义的一套权限系统
-| 我的信息 | 我的信息 | 唯一的作用就是看看什么时候加入群
-| 撤回 | 撤回 \[消息位置]?(默认为最新一条消息) | 按顺序撤回发送的消息,示例:撤回 1
-| 滴滴滴- | 滴滴滴- \[文本] | 用于用户联系真寻的超级用户
-|功能调用统计可视化 | 功能调用统计(自记录以来的功能调用统计)
周功能调用统计 [plugin_name]
月功能调用统计 [plugin_name]| 当plugin_name为空时为7天或30内的所有功能统计
-| pix | pix/PIX [tags/uid/pid:pid] [num] | 无参数时随机查看pix图库的图片(无r18),num数量默认=1,tags:查看相关tags图片,uid:查找相关画师图片,pid:pid:指定查看pid图片
示例:pix原神 3
pix23493844
pixpid:29429933
-| 添加pix关键词/uid/pid | 添加pix关键词/uid/pid *[关键词/uid/pid]| 添加关键词或uid或pid用于下次搜索,关键词搜索相关tag,uid会收录作者下收藏符合标准的作品,pid收录单张作品
示例:添加pix关键词 原神
添加pixuid 123441
添加pixpid 2748937|
-| 查看pix图库 | 查看pix图库 [tags] | 查看已收录的tag相关图片数量
示例:查看pix图库 原神 莫娜
-|显示pix关键词 | 显示pix关键词 | 查看已收录的所有关键词/UID/PID
-| b了 | b了 [at] | 使真寻完全忽略一个用户的所有信息
-| B站订阅 | 添加订阅 [主播/UP/番剧] [id/链接/番名] / 删除订阅 [id] / 查看订阅 | 可以通过直接间链接或主播间id添加订阅主播开播提醒,动态和投稿,可以通过番名,番剧的id,或者番剧链接添加番剧订阅(是番剧id,md开头,不是集数id,ep开头的)更新, 可以通过UP个人id或UP主页链接订阅UP动态和投稿
-
-### 管理员功能
-
-**群主与群管理员默认5级权限**
-
-| 功能 | 权限等级 | 指令 | 说明
-| -------------| --------------| :--------------------------------------:| :------------------------:
-| 更新群组成员信息 | 1 | 更新群组成员信息/更新群组成员列表 | 存储群员的基本信息,虽然有自动更新,但备个命令以防万一
-| 群功能开关 | 2 | 开启/关闭\[指令名]功能 | 群帮助中左边带有√的功能都可以通过此命令开启或关闭,示例:开启色图
-| 查看群被动技能 | 2 | 群通知状态 | 详细请查看被动技能列表
-| 被动技能开关 | 2 | 开启/关闭被动技能 | 有时候花里胡哨通知也会很烦人
-| 自定义群欢迎消息 | 2 | 自定义群欢迎消息 \[文本] \[图片] | 文本和图片至少需要一个,在文本内添加"\[at]"字符串可以用来设置艾特进群的新群员
-| 黑名单 | 5 | .ban/.unban \[at] \[小时]? \[分钟]?| 不提供具体时间的话则ban掉永久,且权限低的用户无法unban高权限用户的ban,同级权限也无法进行ban/unban
示例:.ban@笨蛋 1 50
-| 刷屏检测相关 | 5 | 刷屏检测设置/设置检测时间 \[文本]/设置检测次数 \[文本]/设置禁言时长 \[分钟]| 非常讨厌刷屏的人,打算给他们一点教训
-| 上传图片 | 6 | 上传图片 \[图库] \[图片]... | 上传图片至指定图库,虽然并不打算开放给群员,但还是写了,支持批量图片
示例:上传图片 美图 \[图片]..
-| 删除图片 | 6 | 删除图片 [图库] \[图片id] | 通过指定本地图片id来删除指定图库的图片
示例:删除图片 美图 1
-| 移动图片 | 6 | 移动图片 \[移出的图库] \[移入的图库] \[图片id] | 移动指定图库中的图片到指定的新图库中,移入的图片id更改为移入图库的最后一位,移除的图库中原本图片的id又最后一位图片替代
示例:移动图片 美图 萝莉 22
-|B站订阅 | 5 | 功能同上,就是在群中有权限限制 | 略
-
-### 超级用户功能
-
-| 功能 | 指令 | 说明
-| ----------------------| :--------------------------------------:| :------------------------:
-| 权限增删 | 添加/删除权限 \[at] \[level]
添加/删除权限 [qq] [group] [level] | 用于添加或修改权限等级,且该权限不会被自动更新取消
-| 所有群组/好友 | 所有群组/好友 | 查看真寻添加的群组与好友
-| 广播 | 广播- [文本] | 广播所有群组
-| 更新色图 | 更新色图 | 更新群友搜索色图时保存的url
-| 回复 | /t | 命令较多,请查看/t帮助,省略群号则私聊用户(必须要有用户的好友)
-| 更新cookie | 更新cookie \[cookie] | 用于更新开箱数据和查询buff皮肤
-| 开启广播通知 | 开启/关闭广播通知 \[群号] | 用于开启/关闭是否对某些群进行广播(上边的广播方法)
-| 退群 | 退群 \[群号] | 用于退出某一些群
-| 检查系统状态 | 自检 | 略
-| 更新好友/群组信息| 更新好友/群组信息 | 包含自动更新,被t出群等等有更好的可视信息
-| 重载卡池 | 略 | 重载抽卡的游戏卡池,请查看 \[nonebot_plugin_gamedraw](https://github.com/HibiKier/nonebot_plugin_gamedraw)
-| 添加商品 | 添加商品 \[名称]-\[价格]-\[描述]-\[折扣](小数)?-\[限时时间](分钟)? | 为真寻的商店添加一点点道具
示例:添加商品-昏睡红茶-300-一杯上好的奇怪红茶-0.9-60
-| 删除商品 | 删除商品 \[名称或序号] | 在真寻的商店中删除一点东西
-| 修改商品 | 修改商品 -name \[名称或序号] -price \[价格] -des \[描述] -discount \[折扣] -time \[限时]| 注意空格,不需要的参数可以不加
示例:修改商品 -name 1 -price 900 【修改序号为1的商品的价格为900】
-| 节日红包 | 节日红包 \[金额] \[数量] \[祝福语]? \[群号]?... | 群号支持批量,使用空格隔开,不使用群号则对所有群发送节日红包,节日红包有效时间为24小时,祝福语默认为“恭喜发财 大吉大利”
示例:节日红包 10000 15 真寻真可爱 123324423 23423423
-| 修改群权限 | 修改群权限 \[group] \[level] | 所以说这功能是对内鬼的无奈,默认群权限为5,默认无法使用 色图/coser/p站排行/搜图(这些功能都要9级权限)
-|更新原神今日素材| 更新原神今日素材 | 自动更新原神每日素材失败时可以手动触发
-|更新原神资源信息| 更新原神资源信息 | 除了每日自动更新的资源外,额外更新大地图
-| 清理数据 | 清理数据 | 清理 temp,rar,r18_rar 文件夹的文件数据
-|添加pix关键词/uid/pid | 添加pix关键词/uid/pid [keyword/pid:pid/uid:uid] [-f]? | 与普通功能相同,额外可以通过参数 -f 强制通过检测
-|删除pix关键词 | 删除pix关键词 *[keyword/uid/pid:pid] | 删除已收录的keyword/uid/pid
-|更新pix关键词 | 更新pix关键词 [keyword/pid:pid/uid:uid] [num]?| 更新keyword,uid,pid或指定uid,pid,未指定时,则更新全部,当num未指定时为keyword/uid/pid更新全部
示例:更新pix关键词
更新pix关键词uid 8
更新pix关键词pid:83457477
num:倒叙更新数量
-|删除pix图片 | 删除pix图片 [*pid] [-b]? | [-b]参数为删除的同时加入黑名单(不再更新),虽然是pid,但是_p也可以
示例:删除pix图片3458344 8235234_p1 -b
-|显示pix关键词 | 显示pix关键词 | 与普通功能相同,额外显示待收录和黑名单
-|pix检测更新 | pix检测更新 [update]? | 检测从未更新过的pid或uid,-update参数将在检测后直接更新未更新过的pid或uid
示例:pix检测更新 update
-| 检查更新真寻 | 检查更新真寻 | 不再需要麻烦的clone第一步
-| 关闭功能 | 关闭[功能] [group/private]/*[群号]? | 关闭色图:维护功能(关闭总开关)
关闭色图 g:在群内限制功能
关闭色图 p:在私聊限制功能
关闭色图 1234678:禁用12345678的色图功能
-| 群白名单 | 添加/删除群白名单 *[群号] / 查看群白名单 | 白名单内的群不会受到 维护 限制
-| 功能状态 | 功能状态 | 查看功能开关状态
-| pix | pix [-s/-r] [keyword] | 可以通过pix -s 查看图库的涩图,pix -r查看图库的r18图,支持搜索,当然,pix图库只区分了r18和非r18,如果-s查询到不色的图也问题不大
-| 重载插件配置 |重载插件配置 |用于生效手动修改配文件
-|帮助 -super | 帮助[功能] -super | 显示该插件的超级用户帮助
-
-
-
## 详细配置请前往文档,以下为最简部署和配置,如果你有基础并学习过nonebot2的话
@@ -321,6 +210,29 @@ python bot.py
## 更新
+### 2021/12/16 \[v0.0.7.0]
+
+* 提供了真寻群聊功能总开关和对应默认配置项,命令:休息吧 醒来
+* 新增原神玩家查询,原神便笺查询
+* 群功能管理提供全部开启/关闭命令:开启/关闭全部功能
+* 提供主要数据自动备份,且提供自定义配置项
+* 提供命令:关于,用于介绍Bot之类的
+* 新增命令exec,用于执行sql语句
+* 签到提供参数 "all",用于签到所有群聊
+* Ban提醒提供cd
+* 本地图库提供配置项SHOW_ID,用于设置发送图片时是否显示id
+* 色图和PIX提供配置项SHOW_INFO,用于设置发送图片时是否显示图片信息
+* 所有被动技能提供了进群默认状态配置项
+
+* 修复添加权限第二种添加形式无法正确添加正确的权限
+* 修复签到获取好感度卡时金币不会增加
+* 修复当红包数量不合法时依旧扣除金币
+* 修复金币红包再次使用塞红包时无法正确退回上次未开完的金币
+* 修复 滴滴滴- 只包含图片时不会发送至管理员
+
+* 修改了权限插件加载顺序防止小概率优先加载权限插件引起报错
+* 本地图库新图库会统一建立在resource/img/image_management文件夹下,如果该文件夹内未找到图库,会从上级目录查找(即:resource/img/)
+
### 2021/12/1 \[v0.0.6.5/6]
* 群权限-1时超级用户命令依旧生效
diff --git a/basic_plugins/admin_bot_manage/data_source.py b/basic_plugins/admin_bot_manage/data_source.py
index 345ad630..6d32cebc 100755
--- a/basic_plugins/admin_bot_manage/data_source.py
+++ b/basic_plugins/admin_bot_manage/data_source.py
@@ -11,7 +11,7 @@ from services.db_context import db
from models.level_user import LevelUser
from configs.config import Config
from utils.manager import group_manager, plugins2settings_manager, plugins_manager
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from utils.http_utils import AsyncHttpx
import asyncio
import time
@@ -72,7 +72,9 @@ async def custom_group_welcome(
logger.info(f"USER {user_id} GROUP {group_id} 更换群欢迎消息 {msg}")
result += msg
if img:
- await AsyncHttpx.download_file(img, DATA_PATH + f"custom_welcome_msg/{group_id}.jpg")
+ await AsyncHttpx.download_file(
+ img, DATA_PATH + f"custom_welcome_msg/{group_id}.jpg"
+ )
img_result = image(abspath=DATA_PATH + f"custom_welcome_msg/{group_id}.jpg")
logger.info(f"USER {user_id} GROUP {group_id} 更换群欢迎消息图片")
except Exception as e:
@@ -107,7 +109,16 @@ async def change_group_switch(cmd: str, group_id: int, is_super: bool = False):
else:
if await group_manager.check_group_task_status(group_id, task):
await group_manager.close_group_task(group_id, task)
+ if group_help_file.exists():
+ group_help_file.unlink()
return f"已 {status} 全部被动技能!"
+ if cmd == "全部功能":
+ for f in plugins2settings_manager.get_data():
+ if status == "开启":
+ group_manager.unblock_plugin(f, group_id)
+ else:
+ group_manager.block_plugin(f, group_id)
+ return f"已 {status} 全部功能!"
if cmd in [task_data[x] for x in task_data.keys()]:
type_ = "task"
modules = [x for x in task_data.keys() if task_data[x] == cmd]
@@ -132,14 +143,14 @@ async def change_group_switch(cmd: str, group_id: int, is_super: bool = False):
if not group_manager.get_plugin_status(module, group_id):
return f"功能 {cmd} 正处于关闭状态!不要重复关闭."
group_manager.block_plugin(module, group_id)
- if group_help_file.exists():
- group_help_file.unlink()
+ if group_help_file.exists():
+ group_help_file.unlink()
if is_super:
- for file in os.listdir(Path(DATA_PATH) / 'group_help'):
- file = Path(DATA_PATH) / 'group_help' / file
+ for file in os.listdir(Path(DATA_PATH) / "group_help"):
+ file = Path(DATA_PATH) / "group_help" / file
file.unlink()
else:
- _help_image = Path(DATA_PATH) / 'group_help' / f"{group_id}.png"
+ _help_image = Path(DATA_PATH) / "group_help" / f"{group_id}.png"
if _help_image.exists():
_help_image.unlink()
return f"{status} {cmd} 功能!"
@@ -158,8 +169,8 @@ def set_plugin_status(cmd: str, block_type: str = "all"):
plugins_manager.unblock_plugin(module)
else:
plugins_manager.block_plugin(module, block_type=block_type)
- for file in os.listdir(Path(DATA_PATH) / 'group_help'):
- file = Path(DATA_PATH) / 'group_help' / file
+ for file in os.listdir(Path(DATA_PATH) / "group_help"):
+ file = Path(DATA_PATH) / "group_help" / file
file.unlink()
@@ -195,11 +206,11 @@ def _get_plugin_status() -> MessageSegment:
rst += "\n"
flag_str += f"{flag}\n"
height = len(rst.split("\n")) * 24
- a = CreateImg(250, height, font_size=20)
+ a = BuildImage(250, height, font_size=20)
a.text((10, 10), rst)
- b = CreateImg(200, height, font_size=20)
+ b = BuildImage(200, height, font_size=20)
b.text((10, 10), flag_str)
- A = CreateImg(500, height)
+ A = BuildImage(500, height)
A.paste(a)
A.paste(b, (270, 0))
return image(b64=A.pic2bs4())
@@ -288,3 +299,21 @@ async def update_member_info(group_id: int, remind_superuser: bool = False) -> b
user_id=int(list(bot.config.superusers)[0]), message=result[:-1]
)
return True
+
+
+def set_group_bot_status(group_id: int, status: bool) -> str:
+ """
+ 设置群聊bot开关状态
+ :param group_id: 群号
+ :param status: 状态
+ """
+ if status:
+ if group_manager.check_group_bot_status(group_id):
+ return "我还醒着呢!"
+ group_manager.turn_on_group_bot_status(group_id)
+ return "呜..醒来了..."
+ else:
+ group_manager.shutdown_group_bot_status(group_id)
+ # for x in group_manager.get_task_data():
+ # group_manager.close_group_task(group_id, x)
+ return "那我先睡觉了..."
diff --git a/basic_plugins/admin_bot_manage/rule.py b/basic_plugins/admin_bot_manage/rule.py
index 397b9c8d..2139213d 100755
--- a/basic_plugins/admin_bot_manage/rule.py
+++ b/basic_plugins/admin_bot_manage/rule.py
@@ -14,10 +14,10 @@ def switch_rule(bot: Bot, event: Event, state: T_State) -> bool:
:param event: pass
:param state: pass
"""
+ global cmd
try:
if not cmd:
- cmd.append('关闭全部被动')
- cmd.append('开启全部被动')
+ cmd = ["关闭全部被动", "开启全部被动", "开启全部功能", "关闭全部功能"]
_data = group_manager.get_task_data()
for key in _data:
cmd.append(f"开启{_data[key]}")
diff --git a/basic_plugins/admin_bot_manage/switch_rule.py b/basic_plugins/admin_bot_manage/switch_rule.py
index 7d580610..7c3bf3a7 100755
--- a/basic_plugins/admin_bot_manage/switch_rule.py
+++ b/basic_plugins/admin_bot_manage/switch_rule.py
@@ -1,4 +1,4 @@
-from nonebot import on_command, on_message
+from nonebot import on_command, on_message, on_regex
from nonebot.typing import T_State
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent, MessageEvent, GROUP
from .data_source import (
@@ -6,12 +6,14 @@ from .data_source import (
set_plugin_status,
get_plugin_status,
group_current_status,
+ set_group_bot_status
)
from services.log import logger
from configs.config import NICKNAME, Config
from utils.utils import get_message_text, is_number
from nonebot.permission import SUPERUSER
from .rule import switch_rule
+import re
__zx_plugin_name__ = "群功能开关 [Admin]"
@@ -24,6 +26,7 @@ usage:
群被动状态
开启全部被动
关闭全部被动
+ 醒来/休息吧
示例:开启/关闭色图
""".strip()
__plugin_superuser_usage__ = """
@@ -40,6 +43,7 @@ __plugin_cmd__ = [
"群被动状态",
"开启全部被动",
"关闭全部被动",
+ "醒来/休息吧",
"功能状态 [_superuser]",
"开启/关闭[功能] [group] [_superuser]",
"开启/关闭[功能] ['private'/'group'] [_superuser]",
@@ -57,6 +61,8 @@ plugins_status = on_command("功能状态", permission=SUPERUSER, priority=5, bl
group_task_status = on_command("群被动状态", permission=GROUP, priority=5, block=True)
+group_status = on_regex("^(休息吧|醒来)$", permission=GROUP, priority=5, block=True)
+
@switch_rule_matcher.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
@@ -105,3 +111,15 @@ async def _(bot: Bot, event: MessageEvent, state: T_State):
@group_task_status.handle()
async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
await group_task_status.send(await group_current_status(event.group_id))
+
+
+@group_status.handle()
+async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
+ r = re.search("^(休息吧|醒来)$", get_message_text(event.json()))
+ if r:
+ if r.group(1) == "休息吧":
+ msg = set_group_bot_status(event.group_id, False)
+ else:
+ msg = set_group_bot_status(event.group_id, True)
+ await group_status.send(msg)
+ logger.info(f"USER {event.user_id} GROUP {event.group_id} 使用总开关命令:{r.group(1)}")
diff --git a/basic_plugins/admin_help/data_source.py b/basic_plugins/admin_help/data_source.py
index 65862fdb..5535ae98 100755
--- a/basic_plugins/admin_help/data_source.py
+++ b/basic_plugins/admin_help/data_source.py
@@ -1,4 +1,4 @@
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from configs.path_config import IMAGE_PATH
from services.log import logger
from utils.utils import get_matchers
@@ -84,8 +84,8 @@ def _create_help_image():
for i, x in enumerate(task_data.keys()):
help_str += f'{i+1}.开启/关闭{task_data[x]}\n\n'
height = len(help_str.split("\n")) * 33
- A = CreateImg(width, height, font_size=24)
- _background = CreateImg(width, height, background=background)
+ A = BuildImage(width, height, font_size=24)
+ _background = BuildImage(width, height, background=background)
A.text((150, 110), help_str)
A.paste(_background, alpha=True)
A.save(admin_help_image)
diff --git a/basic_plugins/apscheduler/__init__.py b/basic_plugins/apscheduler/__init__.py
index a12aa80e..487025b6 100755
--- a/basic_plugins/apscheduler/__init__.py
+++ b/basic_plugins/apscheduler/__init__.py
@@ -5,8 +5,10 @@ from services.log import logger
from models.group_info import GroupInfo
from models.friend_user import FriendUser
from nonebot.adapters.cqhttp.exception import ActionFailed
-from configs.config import NICKNAME
+from configs.config import NICKNAME, Config
from utils.manager import group_manager
+from pathlib import Path
+import shutil
__zx_plugin_name__ = "定时任务相关 [Hidden]"
__plugin_version__ = 0.1
@@ -14,7 +16,33 @@ __plugin_author__ = "HibiKier"
__plugin_task__ = {'zwa': '早晚安'}
-x = on_message(priority=9, block=False)
+Config.add_plugin_config(
+ "_task",
+ "DEFAULT_ZWA",
+ True,
+ help_="被动 早晚安 进群默认开关状态",
+ default_value=True,
+)
+
+Config.add_plugin_config(
+ "_backup",
+ "BACKUP_FLAG",
+ True,
+ help_="是否开启文件备份",
+ default_value=True
+)
+
+Config.add_plugin_config(
+ "_backup",
+ "BACKUP_DIR_OR_FILE",
+ ['data/black_word', 'data/configs', 'data/statistics', 'data/word_bank', 'data/manager', 'configs'],
+ name="文件备份",
+ help_="备份的文件夹或文件",
+ default_value=[]
+)
+
+
+cx = on_message(priority=9, block=False)
# 早上好
@@ -106,6 +134,34 @@ async def _():
logger.error(f"自动更新群组信息错误 e:{e}")
+# 自动备份
+@scheduler.scheduled_job(
+ "cron",
+ hour=3,
+ minute=25,
+)
+async def _():
+ if Config.get_config("_backup", "BACKUP_FLAG"):
+ _backup_path = Path() / 'backup'
+ _backup_path.mkdir(exist_ok=True, parents=True)
+ for x in Config.get_config("_backup", "BACKUP_DIR_OR_FILE"):
+ try:
+ path = Path(x)
+ _p = _backup_path / x
+ if path.exists():
+ if path.is_dir():
+ if _p.exists():
+ shutil.rmtree(_p, ignore_errors=True)
+ shutil.copytree(x, _p)
+ else:
+ if _p.exists():
+ _p.unlink()
+ shutil.copy(x, _p)
+ logger.info(f'已完成自动备份:{x}')
+ except Exception as e:
+ logger.error(f"自动备份文件 {x} 发生错误 {type(e)}:{e}")
+
+
# 一次性任务
# 固定时间触发,仅触发一次:
#
diff --git a/basic_plugins/broadcast/__init__.py b/basic_plugins/broadcast/__init__.py
index 328d73c7..97f129b4 100755
--- a/basic_plugins/broadcast/__init__.py
+++ b/basic_plugins/broadcast/__init__.py
@@ -7,6 +7,7 @@ from utils.utils import get_message_text, get_message_imgs
from services.log import logger
from utils.message_builder import image
from utils.manager import group_manager
+from configs.config import Config
__zx_plugin_name__ = "广播 [Superuser]"
__plugin_usage__ = """
@@ -20,6 +21,13 @@ __plugin_cmd__ = ["广播-"]
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_task__ = {"broadcast": "广播"}
+Config.add_plugin_config(
+ "_task",
+ "DEFAULT_BROADCAST",
+ True,
+ help_="被动 广播 进群默认开关状态",
+ default_value=True,
+)
broadcast = on_command("广播-", priority=1, permission=SUPERUSER, block=True)
diff --git a/basic_plugins/group_handle/__init__.py b/basic_plugins/group_handle/__init__.py
index ff390d09..59abf8cb 100755
--- a/basic_plugins/group_handle/__init__.py
+++ b/basic_plugins/group_handle/__init__.py
@@ -38,6 +38,22 @@ Config.add_plugin_config(
Config.add_plugin_config(
"invite_manager", "welcome_msg_cd", 5, help_="群欢迎消息cd", default_value=5
)
+Config.add_plugin_config(
+ "_task",
+ "DEFAULT_GROUP_WELCOME",
+ True,
+ help_="被动 进群欢迎 进群默认开关状态",
+ default_value=True,
+)
+Config.add_plugin_config(
+ "_task",
+ "DEFAULT_REFUND_GROUP_REMIND",
+ True,
+ help_="被动 退群提醒 进群默认开关状态",
+ default_value=True,
+)
+
+
_flmt = FreqLimiter(Config.get_config("invite_manager", "welcome_msg_cd"))
diff --git a/basic_plugins/help/data_source.py b/basic_plugins/help/data_source.py
index 6168180d..e10fc521 100755
--- a/basic_plugins/help/data_source.py
+++ b/basic_plugins/help/data_source.py
@@ -1,4 +1,4 @@
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from configs.path_config import IMAGE_PATH
from utils.manager import (
plugins2settings_manager,
@@ -50,7 +50,7 @@ def _create_help_img(
_des_tmp = {}
_plugin_name_tmp = []
_tmp = []
- tmp_img = CreateImg(0, 0, plain_text="1", font_size=24)
+ tmp_img = BuildImage(0, 0, plain_text="1", font_size=24)
font_height = tmp_img.h
# 插件分类
for matcher in _matchers:
@@ -181,7 +181,7 @@ def _create_help_img(
)
height = len(help_str.split("\n")) * (font_height + 5)
simple_height = len(simple_help_str.split("\n")) * (font_height + 5)
- A = CreateImg(
+ A = BuildImage(
width + 150, height, font_size=24, color="white" if not ix % 2 else "black"
)
A.text((10, 10), help_str, (255, 255, 255) if ix % 2 else (0, 0, 0))
@@ -192,8 +192,8 @@ def _create_help_img(
for x in simple_help_str.split("\n")
]:
simple_width = simple_width if simple_width > x else x
- bk = CreateImg(simple_width + 20, simple_height, font_size=24, color="#6495ED")
- B = CreateImg(
+ bk = BuildImage(simple_width + 20, simple_height, font_size=24, color="#6495ED")
+ B = BuildImage(
simple_width + 20,
simple_height,
font_size=24,
@@ -236,7 +236,7 @@ def _create_help_img(
for img in help_img_list:
height += img.h
if not group_id:
- A = CreateImg(width + 150, height + 50, font_size=24)
+ A = BuildImage(width + 150, height + 50, font_size=24)
A.text(
(10, 10), '* 注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数 *\n\n" "功能名: 功能简介 -> 指令\n\n'
)
@@ -253,14 +253,14 @@ def _create_help_img(
if img.h > height:
height = img.h
width += img.w + 10
- B = CreateImg(width + 100, height + 250, font_size=24)
+ B = BuildImage(width + 100, height + 250, font_size=24)
width, _ = get_max_width_or_paste(simple_help_img_list, B)
bk = None
random_bk = os.listdir(random_bk_path)
if random_bk:
bk = random.choice(random_bk)
x = max(width + 50, height + 250)
- B = CreateImg(
+ B = BuildImage(
x,
x,
font_size=24,
@@ -272,7 +272,7 @@ def _create_help_img(
w = 10
h = 10
for msg in ["目前支持的功能列表:", "可以通过 ‘帮助[功能名称]’ 来获取对应功能的使用方法", "或者使用 ‘详细帮助’ 来获取所有功能方法"]:
- text = CreateImg(
+ text = BuildImage(
0,
0,
plain_text=msg,
@@ -284,7 +284,7 @@ def _create_help_img(
if msg == "目前支持的功能列表:":
w += 50
B.paste(
- CreateImg(
+ BuildImage(
0,
0,
plain_text="注: 红字代表功能被群管理员禁用,红线代表功能正在维护",
@@ -299,8 +299,8 @@ def _create_help_img(
def get_max_width_or_paste(
- simple_help_img_list: list, B: CreateImg = None, is_paste: bool = False
-) -> "int, CreateImg":
+ simple_help_img_list: list, B: BuildImage = None, is_paste: bool = False
+) -> "int, BuildImage":
"""
获取最大宽度,或直接贴图
:param simple_help_img_list: 简单帮助图片列表
@@ -353,8 +353,8 @@ def get_plugin_help(msg: str, is_super: bool = False) -> Optional[str]:
_width = len(x) * 24
width = width if width > _width else _width
height = len(result.split("\n")) * 45
- A = CreateImg(width, height, font_size=24)
- bk = CreateImg(
+ A = BuildImage(width, height, font_size=24)
+ bk = BuildImage(
width,
height,
background=Path(IMAGE_PATH) / "background" / "1.png",
diff --git a/basic_plugins/hooks/__init__.py b/basic_plugins/hooks/__init__.py
index f33bd813..9ca94805 100755
--- a/basic_plugins/hooks/__init__.py
+++ b/basic_plugins/hooks/__init__.py
@@ -1,5 +1,4 @@
from configs.config import Config
-import nonebot
Config.add_plugin_config(
@@ -35,4 +34,3 @@ Config.add_plugin_config(
default_value=6
)
-nonebot.load_plugins("basic_plugins/hooks")
diff --git a/basic_plugins/hooks/auth_hook.py b/basic_plugins/hooks/auth_hook.py
index aaa173da..7e9403ce 100755
--- a/basic_plugins/hooks/auth_hook.py
+++ b/basic_plugins/hooks/auth_hook.py
@@ -13,7 +13,7 @@ from nonebot.adapters.cqhttp import (
Bot,
MessageEvent,
GroupMessageEvent,
- PokeNotifyEvent,
+ PokeNotifyEvent
)
from configs.config import Config
from models.ban_user import BanUser
@@ -37,16 +37,19 @@ ignore_rst_module = ["ai", "poke", "dialogue"]
async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
module = matcher.module
plugins2info_dict = plugins2settings_manager.get_data()
- if (
- (not isinstance(event, MessageEvent) and module != "poke")
- or await BanUser.is_ban(event.user_id)
- and str(event.user_id) not in bot.config.superusers
- ) or (
- str(event.user_id) in bot.config.superusers
- and plugins2info_dict.get(module)
- and not plugins2info_dict[module]["limit_superuser"]
- ):
- return
+ try:
+ if (
+ (not isinstance(event, MessageEvent) and module != "poke")
+ or await BanUser.is_ban(event.user_id)
+ and str(event.user_id) not in bot.config.superusers
+ ) or (
+ str(event.user_id) in bot.config.superusers
+ and plugins2info_dict.get(module)
+ and not plugins2info_dict[module]["limit_superuser"]
+ ):
+ return
+ except AttributeError:
+ pass
# 超级用户命令
try:
_plugin = nonebot.plugin.get_plugin(module)
@@ -59,10 +62,19 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
return
except AttributeError:
pass
- # 群黑名单检测
- if isinstance(event, GroupMessageEvent):
- if group_manager.get_group_level(event.group_id) < 0:
- raise IgnoredException("群黑名单")
+ # 群黑名单检测 群总开关检测
+ if isinstance(event, GroupMessageEvent) or matcher.module == "poke":
+ try:
+ if group_manager.get_group_level(event.group_id) < 0:
+ raise IgnoredException("群黑名单")
+ if not group_manager.check_group_bot_status(event.group_id):
+ try:
+ if str(event.get_message()) != "醒来":
+ raise IgnoredException("功能总开关关闭状态")
+ except ValueError:
+ raise IgnoredException("功能总开关关闭状态")
+ except AttributeError:
+ pass
if module in admin_manager.keys() and matcher.priority not in [1, 9]:
if isinstance(event, GroupMessageEvent):
# 个人权限
diff --git a/basic_plugins/hooks/ban_hook.py b/basic_plugins/hooks/ban_hook.py
index ea59ef0a..b3f2ac7e 100755
--- a/basic_plugins/hooks/ban_hook.py
+++ b/basic_plugins/hooks/ban_hook.py
@@ -9,7 +9,7 @@ from nonebot.adapters.cqhttp import (
)
from configs.config import Config
from models.ban_user import BanUser
-from utils.utils import is_number, static_flmt
+from utils.utils import is_number, static_flmt, FreqLimiter
from utils.message_builder import at
@@ -20,6 +20,8 @@ Config.add_plugin_config(
help_="对被ban用户发送的消息",
)
+_flmt = FreqLimiter(300)
+
# 检查是否被ban
@run_preprocessor
@@ -55,7 +57,8 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
if matcher.priority != 9:
try:
ban_result = Config.get_config("hook", "BAN_RESULT")
- if ban_result:
+ if ban_result and _flmt.check(event.user_id):
+ _flmt.start_cd(event.user_id)
await bot.send_group_msg(
group_id=event.group_id,
message=at(event.user_id)
diff --git a/basic_plugins/nickname.py b/basic_plugins/nickname.py
index c4ff30db..bb8ef1e0 100755
--- a/basic_plugins/nickname.py
+++ b/basic_plugins/nickname.py
@@ -25,7 +25,7 @@ __plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_configs__ = {
"BLACK_WORD": {
- "value": ["爸", "妈", "爹", "爷"],
+ "value": ["爸", "爹", "爷"],
"help": "昵称所屏蔽的关键词,会被替换为 *",
"default_value": None
}
diff --git a/basic_plugins/super_cmd/exec_sql.py b/basic_plugins/super_cmd/exec_sql.py
new file mode 100644
index 00000000..4b44f251
--- /dev/null
+++ b/basic_plugins/super_cmd/exec_sql.py
@@ -0,0 +1,38 @@
+from nonebot import on_command
+from nonebot.permission import SUPERUSER
+from nonebot.typing import T_State
+from nonebot.adapters.cqhttp import Bot, MessageEvent
+from nonebot.rule import to_me
+from services.db_context import db
+from utils.utils import get_message_text
+from services.log import logger
+
+__zx_plugin_name__ = "执行sql [Superuser]"
+__plugin_usage__ = """
+usage:
+ 执行一段sql语句
+ 指令:
+ exec [sql语句]
+""".strip()
+__plugin_des__ = "执行一段sql语句"
+__plugin_cmd__ = [
+ "exec [sql语句]",
+]
+__plugin_version__ = 0.1
+__plugin_author__ = "HibiKier"
+
+
+exec_ = on_command("exec", rule=to_me(), permission=SUPERUSER, priority=1, block=True)
+
+
+@exec_.handle()
+async def _(bot: Bot, event: MessageEvent, state: T_State):
+ sql = get_message_text(event.json())
+ async with db.transaction():
+ try:
+ query = db.text(sql)
+ await db.first(query)
+ await exec_.send("执行 sql 语句成功.")
+ except Exception as e:
+ await exec_.send(f"执行 sql 语句失败 {type(e)}:{e}")
+ logger.error(f"执行 sql 语句失败 {type(e)}:{e}")
diff --git a/basic_plugins/super_cmd/set_admin_permissions.py b/basic_plugins/super_cmd/set_admin_permissions.py
index a375af6e..f488e3fa 100755
--- a/basic_plugins/super_cmd/set_admin_permissions.py
+++ b/basic_plugins/super_cmd/set_admin_permissions.py
@@ -40,7 +40,7 @@ async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
group_id = -1
level = 0
try:
- args = get_message_text(event.json()).strip().split()
+ args = get_message_text(event.json()).split()
qq = get_message_at(event.json())
flag = -1
if not qq:
@@ -55,7 +55,7 @@ async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
else:
await super_cmd.finish(
"权限参数不完全\n\t格式:添加/删除权限 [at] [level]"
- "\n\t格式:添加/删除权限 [qq] [group] [level]",
+ "\n\t格式:添加/删除权限 [qq] [group_id] [level]",
at_sender=True,
)
else:
@@ -63,10 +63,6 @@ async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
group_id = event.group_id
flag = 2
if state["_prefix"]["raw_command"][:2] == "添加":
- if is_number(args[0]):
- level = int(args[0])
- else:
- await super_cmd.finish("权限等级必须是数字!", at_sender=True)
if await LevelUser.set_level(qq, group_id, level, 1):
result = "添加管理成功, 权限: " + str(level)
else:
diff --git a/basic_plugins/super_help/data_source.py b/basic_plugins/super_help/data_source.py
index 730ff152..4138bf5c 100755
--- a/basic_plugins/super_help/data_source.py
+++ b/basic_plugins/super_help/data_source.py
@@ -1,4 +1,4 @@
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from configs.path_config import IMAGE_PATH
from services.log import logger
from utils.utils import get_matchers
@@ -32,7 +32,7 @@ def _create_help_image():
_plugin_name_list = []
width = 0
help_str = "超级用户帮助\n\n* 注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数 *\n\n"
- tmp_img = CreateImg(0, 0, plain_text='1', font_size=24)
+ tmp_img = BuildImage(0, 0, plain_text='1', font_size=24)
for matcher in _matchers:
plugin_name = ""
try:
@@ -72,8 +72,8 @@ def _create_help_image():
)
height = len(help_str.split("\n")) * 33
width += 500
- A = CreateImg(width, height, font_size=24)
- _background = CreateImg(width, height, background=background)
+ A = BuildImage(width, height, font_size=24)
+ _background = BuildImage(width, height, background=background)
A.text((300, 140), help_str)
A.paste(_background, alpha=True)
A.save(superuser_help_image)
diff --git a/bot.py b/bot.py
index c8f1a34b..ca579075 100644
--- a/bot.py
+++ b/bot.py
@@ -9,10 +9,12 @@ driver.register_adapter("cqhttp", CQHTTPBot)
config = driver.config
driver.on_startup(init)
driver.on_shutdown(disconnect)
-# 优先加载定时任务插件
+# 优先加载定时任务
nonebot.load_plugin("nonebot_plugin_apscheduler")
nonebot.load_plugins("basic_plugins")
nonebot.load_plugins("plugins")
+# 最后加载权限控制
+nonebot.load_plugins("basic_plugins/hooks")
if __name__ == "__main__":
diff --git a/configs/config.py b/configs/config.py
index ead748f6..70ff7a1e 100644
--- a/configs/config.py
+++ b/configs/config.py
@@ -17,8 +17,8 @@ address: str = "" # 数据库地址
port: str = "" # 数据库端口
database: str = "" # 数据库名称
-# 全局代理,例如 "http://127.0.0.1:7890"
-SYSTEM_PROXY: Optional[str] = None
+# 代理,例如 "http://127.0.0.1:7890"
+SYSTEM_PROXY: Optional[str] = None # 全局代理
Config = ConfigsManager(Path() / "data" / "configs" / "plugins2config.yaml")
diff --git a/configs/utils/__init__.py b/configs/utils/__init__.py
index 7bf96d5c..739d2118 100644
--- a/configs/utils/__init__.py
+++ b/configs/utils/__init__.py
@@ -105,11 +105,12 @@ class ConfigsManager:
if self._data[module].get(key) is not None:
self._data[module][key]["default_value"] = value
- def get_config(self, module: str, key: str) -> Optional[Any]:
+ def get_config(self, module: str, key: str, default: Optional[Any] = None) -> Optional[Any]:
"""
获取指定配置值
:param module: 模块名
:param key: 配置名称
+ :param default: 没有key值内容的默认返回值
"""
key = key.upper()
if module in self._data.keys():
@@ -118,6 +119,8 @@ class ConfigsManager:
if self._data[module][key]["value"] is None:
return self._data[module][key]["default_value"]
return self._data[module][key]["value"]
+ if default is not None:
+ return default
return None
def get_level2module(self, module: str, key: str) -> Optional[str]:
@@ -170,4 +173,3 @@ class ConfigsManager:
def __getitem__(self, key):
return self._data[key]
-
diff --git a/models/friend_user.py b/models/friend_user.py
index 69d31ef5..ef7742f7 100755
--- a/models/friend_user.py
+++ b/models/friend_user.py
@@ -83,8 +83,9 @@ class FriendUser(db.Model):
if user.nickname:
_tmp = ""
black_word = Config.get_config("nickname", "BLACK_WORD")
- for x in user.nickname:
- _tmp += "*" if x in black_word else x
+ if black_word:
+ for x in user.nickname:
+ _tmp += "*" if x in black_word else x
return _tmp
return ""
diff --git a/models/group_member_info.py b/models/group_member_info.py
index df032d8b..6db45bab 100755
--- a/models/group_member_info.py
+++ b/models/group_member_info.py
@@ -156,8 +156,9 @@ class GroupInfoUser(db.Model):
if user.nickname:
_tmp = ""
black_word = Config.get_config("nickname", "BLACK_WORD")
- for x in user.nickname:
- _tmp += "*" if x in black_word else x
+ if black_word:
+ for x in user.nickname:
+ _tmp += "*" if x in black_word else x
return _tmp
return ""
diff --git a/models/sign_group_user.py b/models/sign_group_user.py
index d605c164..1c86677c 100755
--- a/models/sign_group_user.py
+++ b/models/sign_group_user.py
@@ -1,5 +1,5 @@
from datetime import datetime
-
+from typing import List
from services.db_context import db
@@ -26,6 +26,14 @@ class SignGroupUser(db.Model):
async def ensure(
cls, user_qq: int, belonging_group: int, for_update: bool = False
) -> "SignGroupUser":
+ """
+ 说明:
+ 获取签到用户
+ 参数:
+ :param user_qq: 用户qq
+ :param belonging_group: 所在群聊
+ :param for_update: 是否存在修改数据
+ """
query = cls.query.where(
(cls.user_qq == user_qq) & (cls.belonging_group == belonging_group)
)
@@ -40,8 +48,28 @@ class SignGroupUser(db.Model):
impression=0,
)
+ @classmethod
+ async def get_user_all_data(cls, user_qq: int) -> List["SignGroupUser"]:
+ """
+ 说明:
+ 获取某用户所有数据
+ 参数:
+ :param user_qq: 用户qq
+ """
+ query = cls.query.where(cls.user_qq == user_qq)
+ query = query.with_for_update()
+ return await query.gino.all()
+
@classmethod
async def sign(cls, user: "SignGroupUser", impression: float, checkin_time_last: datetime):
+ """
+ 说明:
+ 签到
+ 说明:
+ :param user: 用户
+ :param impression: 增加的好感度
+ :param checkin_time_last: 签到时间
+ """
await user.update(
checkin_count=user.checkin_count + 1,
checkin_time_last=checkin_time_last,
diff --git a/plugins/about.py b/plugins/about.py
new file mode 100644
index 00000000..d6833922
--- /dev/null
+++ b/plugins/about.py
@@ -0,0 +1,45 @@
+from nonebot import on_regex
+from nonebot.adapters.cqhttp import Bot, MessageEvent
+from nonebot.typing import T_State
+from nonebot.rule import to_me
+from pathlib import Path
+
+
+__zx_plugin_name__ = "关于"
+__plugin_usage__ = """
+usage:
+ 想要更加了解真寻吗
+ 指令:
+ 关于
+""".strip()
+__plugin_des__ = "想要更加了解真寻吗"
+__plugin_cmd__ = ["关于"]
+__plugin_version__ = 0.1
+__plugin_type__ = ("其他",)
+__plugin_author__ = "HibiKier"
+__plugin_settings__ = {
+ "level": 1,
+ "default_status": True,
+ "limit_superuser": False,
+ "cmd": ["关于"],
+}
+
+
+about = on_regex("^关于$", priority=5, block=True, rule=to_me())
+
+
+@about.handle()
+async def _(bot: Bot, event: MessageEvent, state: T_State):
+ ver_file = Path() / '__version__'
+ version = None
+ if ver_file.exists():
+ with open(ver_file, 'r', encoding='utf8') as f:
+ version = f.read().split(':')[-1].strip()
+ msg = f"""
+『绪山真寻Bot』
+版本:{version}
+简介:基于Nonebot2与go-cqhttp开发,是一个非常可爱的Bot呀,希望与大家要好好相处
+项目地址:https://github.com/HibiKier/zhenxun_bot
+文档地址:https://hibikier.github.io/zhenxun_bot/
+ """.strip()
+ await about.send(msg)
diff --git a/plugins/ai/utils.py b/plugins/ai/utils.py
index ebd4f6d8..3b00941b 100755
--- a/plugins/ai/utils.py
+++ b/plugins/ai/utils.py
@@ -75,7 +75,10 @@ class AiMessageManager(StaticData):
:param user_id: 用户id
:param nickname: 用户昵称
"""
- if len(self._data[user_id]["message"]) < 2:
+ try:
+ if len(self._data[user_id]["message"]) < 2:
+ return None
+ except KeyError:
return None
msg = await self._get_user_repeat_message_result(user_id)
if not msg:
diff --git a/plugins/alapi/data_source.py b/plugins/alapi/data_source.py
index ad853dec..26296c65 100755
--- a/plugins/alapi/data_source.py
+++ b/plugins/alapi/data_source.py
@@ -1,5 +1,5 @@
from nonebot.adapters.cqhttp import MessageSegment
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from utils.message_builder import image
from configs.path_config import IMAGE_PATH
from typing import Optional
@@ -33,14 +33,14 @@ def gen_wbtop_pic(data: dict) -> MessageSegment:
生成微博热搜图片
:param data: 微博热搜数据
"""
- bk = CreateImg(700, 32 * 50 + 280, 700, 32, color="#797979")
- wbtop_bk = CreateImg(700, 280, background=f"{IMAGE_PATH}/other/webtop.png")
+ bk = BuildImage(700, 32 * 50 + 280, 700, 32, color="#797979")
+ wbtop_bk = BuildImage(700, 280, background=f"{IMAGE_PATH}/other/webtop.png")
bk.paste(wbtop_bk)
- text_bk = CreateImg(700, 32 * 50, 700, 32, color="#797979")
+ text_bk = BuildImage(700, 32 * 50, 700, 32, color="#797979")
for i, data in enumerate(data):
title = f"{i+1}. {data['hot_word']}"
hot = data["hot_word_num"]
- img = CreateImg(700, 30, font_size=20)
+ img = BuildImage(700, 30, font_size=20)
w, h = img.getsize(title)
img.text((10, int((30 - h) / 2)), title)
img.text((580, int((30 - h) / 2)), hot)
diff --git a/plugins/bilibili_sub/utils.py b/plugins/bilibili_sub/utils.py
index 869944f2..42699de1 100755
--- a/plugins/bilibili_sub/utils.py
+++ b/plugins/bilibili_sub/utils.py
@@ -1,4 +1,4 @@
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from configs.path_config import IMAGE_PATH
from utils.http_utils import AsyncHttpx
from pathlib import Path
@@ -35,21 +35,21 @@ async def create_live_des_image(uid: int, title: str, cover: str, tags: str, des
sex = user_info["sex"]
face = user_info["face"]
sign = user_info["sign"]
- ava = CreateImg(100, 100, background=BytesIO(await get_pic(face)))
+ ava = BuildImage(100, 100, background=BytesIO(await get_pic(face)))
ava.circle()
- cover = CreateImg(470, 265, background=BytesIO(await get_pic(cover)))
+ cover = BuildImage(470, 265, background=BytesIO(await get_pic(cover)))
print()
def _create_live_des_image(
title: str,
- cover: CreateImg,
+ cover: BuildImage,
tags: str,
des: str,
user_name: str,
sex: str,
sign: str,
- ava: CreateImg,
+ ava: BuildImage,
):
"""
生成主播简介图片
@@ -66,6 +66,6 @@ def _create_live_des_image(
border = BORDER_PATH / "0.png"
border_img = None
if border.exists():
- border_img = CreateImg(1772, 2657, background=border)
- bk = CreateImg(1772, 2657, font_size=30)
+ border_img = BuildImage(1772, 2657, background=border)
+ bk = BuildImage(1772, 2657, font_size=30)
bk.paste(cover, (0, 100), center_type="by_width")
diff --git a/plugins/c_song/music_163.py b/plugins/c_song/music_163.py
index 8552d06f..91fe774b 100755
--- a/plugins/c_song/music_163.py
+++ b/plugins/c_song/music_163.py
@@ -23,7 +23,10 @@ async def search_song(song_name: str):
async def get_song_id(song_name: str) -> int:
""" """
r = await search_song(song_name)
- return r["result"]["songs"][0]["id"]
+ try:
+ return r["result"]["songs"][0]["id"]
+ except KeyError:
+ return 0
async def get_song_info(songId: int):
diff --git a/plugins/check/data_source.py b/plugins/check/data_source.py
index c77865cf..b843b8c8 100755
--- a/plugins/check/data_source.py
+++ b/plugins/check/data_source.py
@@ -2,7 +2,7 @@ import psutil
import time
from datetime import datetime
from utils.http_utils import AsyncHttpx
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from configs.path_config import IMAGE_PATH
from pathlib import Path
import asyncio
@@ -49,7 +49,7 @@ class Check:
async def show(self):
await self.check_all()
- A = CreateImg(0, 0, font_size=24)
+ A = BuildImage(0, 0, font_size=24)
rst = (
f'[Time] {str(datetime.now()).split(".")[0]}\n'
f"-----System-----\n"
@@ -69,10 +69,10 @@ class Check:
if w > width:
width = w
height += 30
- A = CreateImg(width + 50, height + 10, font_size=24, font="HWZhongSong.ttf")
+ A = BuildImage(width + 50, height + 10, font_size=24, font="HWZhongSong.ttf")
A.transparent(1)
A.text((10, 10), rst)
_x = max(width, height)
- bk = CreateImg(_x + 100, _x + 100, background=Path(IMAGE_PATH) / "background" / "check" / "0.jpg")
+ bk = BuildImage(_x + 100, _x + 100, background=Path(IMAGE_PATH) / "background" / "check" / "0.jpg")
bk.paste(A, alpha=True, center_type='center')
return bk.pic2bs4()
diff --git a/plugins/check_zhenxun_update/data_source.py b/plugins/check_zhenxun_update/data_source.py
index 795b6a75..77cb0cca 100755
--- a/plugins/check_zhenxun_update/data_source.py
+++ b/plugins/check_zhenxun_update/data_source.py
@@ -1,5 +1,5 @@
from nonebot.adapters.cqhttp import Bot, Message
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from configs.path_config import IMAGE_PATH
from utils.message_builder import image
from utils.http_utils import AsyncHttpx
@@ -85,12 +85,12 @@ async def check_update(bot: Bot) -> 'int, str':
update_info = data["body"]
width = 0
height = len(update_info.split('\n')) * 24
- A = CreateImg(width, height, font_size=20)
+ A = BuildImage(width, height, font_size=20)
for m in update_info.split('\n'):
w, h = A.getsize(m)
if w > width:
width = w
- A = CreateImg(width + 50, height, font_size=20)
+ A = BuildImage(width + 50, height, font_size=20)
A.text((10, 10), update_info)
A.save(f'{IMAGE_PATH}/update_info.png')
await bot.send_private_msg(
diff --git a/plugins/dialogue/__init__.py b/plugins/dialogue/__init__.py
index e8ad4c1e..2d003ab2 100755
--- a/plugins/dialogue/__init__.py
+++ b/plugins/dialogue/__init__.py
@@ -65,7 +65,7 @@ async def _(bot: Bot, event: MessageEvent, state: T_State):
img_msg = _text("")
for img in get_message_imgs(event.json()):
img_msg += image(img)
- if not text or text in ["帮助"]:
+ if not text and not img_msg:
await dialogue.send("请发送[滴滴滴]+您要说的内容~", at_sender=True)
else:
group_id = 0
diff --git a/plugins/draw_card/util.py b/plugins/draw_card/util.py
index 0f8bf8cf..759771d2 100755
--- a/plugins/draw_card/util.py
+++ b/plugins/draw_card/util.py
@@ -6,7 +6,7 @@ from configs.path_config import IMAGE_PATH
from utils.http_utils import AsyncHttpx
import nonebot
import pypinyin
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
import platform
from services.log import logger
import random
@@ -98,37 +98,37 @@ async def generate_img(card_set: Union[Set[BaseData], List[BaseData]], game_name
num = 0
for n in star_list:
num += n
- A = CreateImg(w, h)
+ A = BuildImage(w, h)
A.paste(card_img)
return A.pic2bs4()
def _pst(h: int, img_list: list, game_name: str, background_list: list):
- card_img = CreateImg(100 * 10, h, 100, 100)
+ card_img = BuildImage(100 * 10, h, 100, 100)
idx = 0
for img in img_list:
try:
if game_name == 'prts':
- bk = CreateImg(100, 100, color=background_list[idx])
- b = CreateImg(94, 94, background=img)
+ bk = BuildImage(100, 100, color=background_list[idx])
+ b = BuildImage(94, 94, background=img)
bk.paste(b, (3, 3))
b = bk
elif game_name == 'azur' and background_list:
- bk = CreateImg(100, 100, background=background_list[idx])
- b = CreateImg(98, 90, background=img)
+ bk = BuildImage(100, 100, background=background_list[idx])
+ b = BuildImage(98, 90, background=img)
bk.paste(b, (1, 5))
b = bk
else:
try:
- b = CreateImg(100, 100, background=img)
+ b = BuildImage(100, 100, background=img)
except UnidentifiedImageError as e:
logger.warning(f'无法识别图片 已删除图片,下次更新重新下载... e:{e}')
if os.path.exists(img):
os.remove(img)
- b = CreateImg(100, 100, color='black')
+ b = BuildImage(100, 100, color='black')
except FileNotFoundError:
logger.warning(f'{img} not exists')
- b = CreateImg(100, 100, color='black')
+ b = BuildImage(100, 100, color='black')
card_img.paste(b)
idx += 1
return card_img
diff --git a/plugins/epic/__init__.py b/plugins/epic/__init__.py
index a2fa2c2a..c54e5c82 100755
--- a/plugins/epic/__init__.py
+++ b/plugins/epic/__init__.py
@@ -5,6 +5,7 @@ from nonebot.typing import T_State
from utils.utils import scheduler, get_bot
from .data_source import get_epic_free
from utils.manager import group_manager
+from configs.config import Config
__zx_plugin_name__ = "epic免费游戏"
__plugin_usage__ = """
@@ -24,6 +25,14 @@ __plugin_settings__ = {
"cmd": ["epic"],
}
__plugin_task__ = {"epic_free_game": "epic免费游戏"}
+Config.add_plugin_config(
+ "_task",
+ "DEFAULT_EPIC_FREE_GAME",
+ True,
+ help_="被动 epic免费游戏 进群默认开关状态",
+ default_value=True,
+)
+
epic = on_command("epic", priority=5, block=True)
@@ -58,9 +67,7 @@ async def _():
if await group_manager.check_group_task_status(g, "epic_free_game"):
try:
msg_list, code = await get_epic_free(bot, GroupMessageEvent)
- if code == 200:
+ if msg_list and code == 200:
await bot.send_group_forward_msg(group_id=g, messages=msg_list)
- else:
- await bot.send_group_msg(group_id=g, message=msg_list)
except Exception as e:
logger.error(f"GROUP {g} epic免费游戏推送错误 {type(e)}: {e}")
diff --git a/plugins/fudu.py b/plugins/fudu.py
index 8e483e9e..475e3ab7 100755
--- a/plugins/fudu.py
+++ b/plugins/fudu.py
@@ -19,13 +19,20 @@ usage:
重复3次相同的消息时会复读
""".strip()
__plugin_des__ = "群友的本质是什么?是复读机哒!"
-__plugin_type__ = ("被动相关",)
+__plugin_type__ = ("其他",)
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_task__ = {"fudu": "复读"}
__plugin_configs__ = {
"FUDU_PROBABILITY": {"value": 0.7, "help": "复读概率", "default_value": 0.7}
}
+Config.add_plugin_config(
+ "_task",
+ "DEFAULT_FUDU",
+ True,
+ help_="被动 复读 进群默认开关状态",
+ default_value=True,
+)
class Fudu:
diff --git a/plugins/genshin/almanac/__init__.py b/plugins/genshin/almanac/__init__.py
index 894f28e3..743a1d00 100755
--- a/plugins/genshin/almanac/__init__.py
+++ b/plugins/genshin/almanac/__init__.py
@@ -6,6 +6,7 @@ from services.log import logger
from configs.path_config import IMAGE_PATH
from .data_source import get_alc_image
from utils.manager import group_manager
+from configs.config import Config
from pathlib import Path
__zx_plugin_name__ = "原神老黄历"
@@ -28,6 +29,14 @@ __plugin_settings__ = {
}
__plugin_task__ = {"genshin_alc": "原神黄历提醒"}
+Config.add_plugin_config(
+ "_task",
+ "DEFAULT_GENSHIN_ALC",
+ True,
+ help_="被动 原神黄历提醒 进群默认开关状态",
+ default_value=True,
+)
+
almanac = on_command("原神黄历", priority=5, block=True)
diff --git a/plugins/genshin/material_remind/__init__.py b/plugins/genshin/material_remind/__init__.py
index 54925dde..223428d7 100755
--- a/plugins/genshin/material_remind/__init__.py
+++ b/plugins/genshin/material_remind/__init__.py
@@ -2,7 +2,7 @@ from nonebot import on_command, Driver
from nonebot.typing import T_State
from nonebot.adapters.cqhttp import Bot, MessageEvent, Message, GroupMessageEvent
from utils.message_builder import image
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from utils.browser import get_browser
from configs.path_config import IMAGE_PATH
import nonebot
@@ -131,12 +131,12 @@ async def update_image():
height = await asyncio.get_event_loop().run_in_executor(
None, get_background_height, weapons_imgs
)
- background_img = CreateImg(1200, height + 100, color="#f6f2ee")
+ background_img = BuildImage(1200, height + 100, color="#f6f2ee")
current_width = 50
for imgs in [char_imgs, weapons_imgs]:
current_height = 20
for img in imgs:
- x = CreateImg(0, 0, background=img)
+ x = BuildImage(0, 0, background=img)
background_img.paste(x, (current_width, current_height))
current_height += x.size[1]
current_width += 600
@@ -154,8 +154,8 @@ async def update_image():
def get_background_height(weapons_imgs: List[str]) -> int:
height = 0
for weapons in weapons_imgs:
- height += CreateImg(0, 0, background=weapons).size[1]
- last_weapon = CreateImg(0, 0, background=weapons_imgs[-1])
+ height += BuildImage(0, 0, background=weapons).size[1]
+ last_weapon = BuildImage(0, 0, background=weapons_imgs[-1])
w, h = last_weapon.size
last_weapon.crop((0, 0, w, h - 10))
last_weapon.save(weapons_imgs[-1])
diff --git a/plugins/genshin/query_resource_points/map.py b/plugins/genshin/query_resource_points/map.py
index 449dbfc4..59c837e5 100755
--- a/plugins/genshin/query_resource_points/map.py
+++ b/plugins/genshin/query_resource_points/map.py
@@ -1,6 +1,6 @@
from pathlib import Path
from configs.path_config import IMAGE_PATH, TEXT_PATH
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from typing import Tuple, List
from math import sqrt, pow
import random
@@ -39,7 +39,7 @@ class Map:
:param planning_route: 是否规划最佳线路
:param ratio: 压缩比率
"""
- self.map = CreateImg(0, 0, background=map_path)
+ self.map = BuildImage(0, 0, background=map_path)
self.resource_name = resource_name
self.center_x = center_point[0]
self.center_y = center_point[1]
@@ -225,13 +225,13 @@ class Map:
# self.map.line(xy, (255, 0, 0), width=3)
# 获取资源图标
- def _get_icon_image(self, id_: int) -> "CreateImg":
+ def _get_icon_image(self, id_: int) -> "BuildImage":
icon = icon_path / f"{id_}.png"
if icon.exists():
- return CreateImg(
+ return BuildImage(
int(50 * self.ratio), int(50 * self.ratio), background=icon
)
- return CreateImg(
+ return BuildImage(
int(50 * self.ratio),
int(50 * self.ratio),
background=f"{icon_path}/box.png",
diff --git a/plugins/genshin/query_resource_points/query_resource.py b/plugins/genshin/query_resource_points/query_resource.py
index 8c131832..7f06a903 100755
--- a/plugins/genshin/query_resource_points/query_resource.py
+++ b/plugins/genshin/query_resource_points/query_resource.py
@@ -3,7 +3,7 @@ from configs.path_config import IMAGE_PATH, TEXT_PATH
from PIL.Image import UnidentifiedImageError
from utils.message_builder import image
from services.log import logger
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from asyncio.exceptions import TimeoutError
from asyncio import Semaphore
from utils.image_utils import is_valid
@@ -186,11 +186,11 @@ async def download_map_init(
force_flag=flag,
)
idx += 1
- _w, h = CreateImg(0, 0, background=f"{map_path}/0.png", ratio=MAP_RATIO).size
+ _w, h = BuildImage(0, 0, background=f"{map_path}/0.png", ratio=MAP_RATIO).size
w = _w * len(os.listdir(map_path))
- map_file = CreateImg(w, h, _w, h, ratio=MAP_RATIO)
+ map_file = BuildImage(w, h, _w, h, ratio=MAP_RATIO)
for i in range(idx):
- map_file.paste(CreateImg(0, 0, background=f"{map_path}/{i}.png", ratio=MAP_RATIO))
+ map_file.paste(BuildImage(0, 0, background=f"{map_path}/{i}.png", ratio=MAP_RATIO))
map_file.save(f"{map_path}/map.png")
else:
logger.warning(f'获取原神地图失败 msg: {data["message"]}')
@@ -230,10 +230,10 @@ async def download_resource_type():
# 初始化资源图标
def gen_icon(icon: str):
- A = CreateImg(0, 0, background=f"{icon_path}/box.png")
- B = CreateImg(0, 0, background=f"{icon_path}/box_alpha.png")
+ A = BuildImage(0, 0, background=f"{icon_path}/box.png")
+ B = BuildImage(0, 0, background=f"{icon_path}/box_alpha.png")
icon_ = icon_path / f"{icon}"
- icon_img = CreateImg(115, 115, background=icon_)
+ icon_img = BuildImage(115, 115, background=icon_)
icon_img.circle()
B.paste(icon_img, (17, 10), True)
B.paste(A, alpha=True)
diff --git a/plugins/genshin/query_user/__init__.py b/plugins/genshin/query_user/__init__.py
new file mode 100644
index 00000000..0ac73fe1
--- /dev/null
+++ b/plugins/genshin/query_user/__init__.py
@@ -0,0 +1,26 @@
+from configs.config import Config
+import nonebot
+
+
+Config.add_plugin_config(
+ "genshin",
+ "mhyVersion",
+ "2.11.1"
+)
+
+Config.add_plugin_config(
+ "genshin",
+ "salt",
+ "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs"
+)
+
+Config.add_plugin_config(
+ "genshin",
+ "client_type",
+ "5"
+)
+
+nonebot.load_plugins("plugins/genshin/query_user")
+
+
+
diff --git a/plugins/genshin/query_user/bind/__init__.py b/plugins/genshin/query_user/bind/__init__.py
new file mode 100644
index 00000000..ebf75495
--- /dev/null
+++ b/plugins/genshin/query_user/bind/__init__.py
@@ -0,0 +1,97 @@
+from nonebot import on_command
+from nonebot.typing import T_State
+from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent
+from utils.utils import get_message_text, is_number
+from ..models import Genshin
+from services.log import logger
+
+
+__zx_plugin_name__ = "原神绑定"
+__plugin_usage__ = """
+usage:
+ 绑定原神uid等数据,cookie极为重要,请谨慎绑定
+ ** 如果对拥有者不熟悉,并不建议添加cookie **
+ 该项目只会对cookie用于”米游社签到“,“原神玩家查询”,“原神便笺查询”
+ 指令:
+ 原神绑定uid [uid]
+ 原神绑定米游社id [mys_id]
+ 原神绑定cookie [cookie] # 该绑定请私聊
+ 原神解绑 [uid]
+ 示例:原神绑定uid 92342233
+ 如果不明白怎么获取cookie请输入“原神绑定cookie”。
+""".strip()
+__plugin_des__ = "绑定自己的原神uid等"
+__plugin_cmd__ = ["原神绑定uid [uid]", "原神绑定米游社id [mys_id]", "原神绑定cookie [cookie]", "原神解绑"]
+__plugin_type__ = ("原神相关",)
+__plugin_version__ = 0.1
+__plugin_author__ = "HibiKier"
+__plugin_settings__ = {
+ "level": 5,
+ "default_status": True,
+ "limit_superuser": False,
+ "cmd": ["原神绑定"],
+}
+
+bind = on_command(
+ "原神绑定uid", aliases={"原神绑定米游社id", "原神绑定cookie"}, priority=5, block=True
+)
+
+unbind = on_command("原神解绑", priority=5, block=True)
+
+
+@bind.handle()
+async def _(bot: Bot, event: MessageEvent, state: T_State):
+ msg = get_message_text(event.json())
+ if state["_prefix"]["raw_command"] in ["原神绑定uid", "原神绑定米游社id"]:
+ if not is_number(msg):
+ await bind.finish("uid/id必须为纯数字!", at_senders=True)
+ msg = int(msg)
+ if state["_prefix"]["raw_command"] == "原神绑定uid":
+ uid = await Genshin.get_user_uid(event.user_id)
+ if uid:
+ await bind.finish(f"您已绑定过uid:{uid},如果希望更换uid,请先发送原神解绑")
+ flag = await Genshin.add_uid(event.user_id, msg)
+ if not flag:
+ await bind.finish("添加失败,该uid可能已存在...")
+ _x = f"已成功添加原神uid:{msg}"
+ elif state["_prefix"]["raw_command"] == "原神绑定米游社id":
+ uid = await Genshin.get_user_uid(event.user_id)
+ if not uid:
+ await bind.finish("请先绑定原神uid..")
+ await Genshin.set_mys_id(uid, msg)
+ _x = f"已成功为uid:{uid} 设置米游社id:{msg}"
+ else:
+ if not msg:
+ await bind.finish(
+ "私聊发送!!\n打开https://bbs.mihoyo.com/ys/登录后按F12点击控制台输入document.cookie复制输出的内容即可"
+ )
+ if isinstance(event, GroupMessageEvent):
+ await bind.finish("请立即撤回你的消息并私聊发送!")
+ uid = await Genshin.get_user_uid(event.user_id)
+ if not uid:
+ await bind.finish("请先绑定原神uid..")
+ if msg.startswith('"'):
+ msg = msg[1:]
+ if msg.endswith('"'):
+ msg = msg[:-1]
+ await Genshin.set_cookie(uid, msg)
+ _x = f"已成功为uid:{uid} 设置cookie"
+ await bind.send(_x)
+ logger.info(
+ f"(USER {event.user_id}, "
+ f"GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
+ f" {state['_prefix']['raw_command']}:{msg}"
+ )
+
+
+@unbind.handle()
+async def _(bot: Bot, event: MessageEvent, state: T_State):
+ if await Genshin.delete_user(event.user_id):
+ await unbind.send("用户数据删除成功...")
+ logger.info(
+ f"(USER {event.user_id}, GROUP "
+ f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
+ f"原神解绑"
+ )
+ else:
+ await unbind.send("该用户数据不存在..")
diff --git a/plugins/genshin/query_user/models/__init__.py b/plugins/genshin/query_user/models/__init__.py
new file mode 100644
index 00000000..fc5820ae
--- /dev/null
+++ b/plugins/genshin/query_user/models/__init__.py
@@ -0,0 +1,210 @@
+from services.db_context import db
+from typing import Optional, Union
+
+
+class Genshin(db.Model):
+ __tablename__ = "genshin"
+
+ id = db.Column(db.Integer(), primary_key=True)
+ user_qq = db.Column(db.BigInteger(), nullable=False)
+ uid = db.Column(db.BigInteger())
+ mys_id = db.Column(db.BigInteger())
+ cookie = db.Column(db.String(), default="")
+ today_query_uid = db.Column(db.String(), default="") # 该cookie今日查询的uid
+ auto_sign = db.Column(db.Boolean(), default=False) # 自动签到
+
+ _idx1 = db.Index("genshin_uid_idx1", "user_qq", "uid", unique=True)
+
+ @classmethod
+ async def add_uid(cls, user_qq: int, uid: int):
+ """
+ 说明:
+ 添加一个uid
+ 参数:
+ :param user_qq: 用户qq
+ :param uid: 原神uid
+ """
+ query = cls.query.where((cls.user_qq == user_qq) & (cls.uid == uid))
+ user = await query.gino.first()
+ if not user:
+ await cls.create(
+ user_qq=user_qq,
+ uid=uid,
+ )
+ return True
+ return False
+
+ @classmethod
+ async def set_mys_id(cls, uid: int, mys_id: int) -> bool:
+ """
+ 说明:
+ 设置米游社id
+ 参数:
+ :param uid: 原神uid
+ :param mys_id: 米游社id
+ """
+ query = cls.query.where(cls.uid == uid).with_for_update()
+ user = await query.gino.first()
+ if user:
+ await user.update(mys_id=mys_id).apply()
+ return True
+ return False
+
+ @classmethod
+ async def set_cookie(cls, uid: int, cookie: str) -> bool:
+ """
+ 说明:
+ 设置cookie
+ 参数:
+ :param uid: 原神uid
+ :param cookie: 米游社id
+ """
+ query = cls.query.where(cls.uid == uid).with_for_update()
+ user = await query.gino.first()
+ if user:
+ await user.update(cookie=cookie).apply()
+ return True
+ return False
+
+ @classmethod
+ async def set_auto_sign(cls, uid: int, flag: bool) -> bool:
+ """
+ 说明:
+ 设置米游社自动签到
+ 参数:
+ :param uid: 原神uid
+ :param flag: 开关状态
+ """
+ query = cls.query.where(cls.uid == uid).with_for_update()
+ user = await query.gino.first()
+ if user:
+ await user.update(auto_sign=flag).apply()
+ return True
+ return False
+
+ @classmethod
+ async def get_query_cookie(cls, uid: int) -> Optional[str]:
+ """
+ 说明:
+ 获取查询角色信息cookie
+ 参数:
+ :param uid: 原神uid
+ """
+ # 查找用户今日是否已经查找过,防止重复
+ query = cls.query.where(cls.today_query_uid.contains(str(uid)))
+ x = await query.gino.first()
+ if x:
+ return x.cookie
+ for u in [x for x in await cls.query.order_by(db.func.random()).gino.all() if x.cookie]:
+ if not u.today_query_uid or len(u.today_query_uid[:-1].split()) < 30:
+ await cls._add_query_uid(uid, u.uid)
+ return u.cookie
+ return None
+
+ @classmethod
+ async def get_user_cookie(cls, uid: int, flag: bool = False) -> Optional[str]:
+ """
+ 说明:
+ 获取用户cookie
+ 参数:
+ :param uid:原神uid
+ :param flag:必须使用自己的cookie
+ """
+ cookie = await cls._get_user_data(None, uid, "cookie")
+ print(uid, cookie)
+ if not cookie and not flag:
+ cookie = await cls.get_query_cookie(uid)
+ print(uid, cookie)
+ return cookie
+
+ @classmethod
+ async def get_user_uid(cls, user_qq: int) -> Optional[int]:
+ """
+ 说明:
+ 获取用户uid
+ 参数:
+ :param user_qq:用户qq
+ """
+ return await cls._get_user_data(user_qq, None, "uid")
+
+ @classmethod
+ async def get_user_mys_id(cls, uid: int) -> Optional[int]:
+ """
+ 说嘛:
+ 获取用户米游社id
+ 参数:
+ :param uid:原神id
+ """
+ return await cls._get_user_data(None, uid, "mys_id")
+
+ @classmethod
+ async def delete_user_cookie(cls, uid: int):
+ """
+ 说明:
+ 删除用户cookie
+ 参数:
+ :param uid: 原神uid
+ """
+ query = cls.query.where(cls.uid == uid).with_for_update()
+ user = await query.gino.first()
+ if user:
+ await user.update(cookie="").apply()
+
+ @classmethod
+ async def delete_user(cls, user_qq: int):
+ """
+ 说明:
+ 删除用户数据
+ 参数:
+ :param user_qq: 用户qq
+ """
+ query = cls.query.where(cls.user_qq == user_qq).with_for_update()
+ user = await query.gino.first()
+ if not user:
+ return False
+ await user.delete()
+ return True
+
+ @classmethod
+ async def _add_query_uid(cls, uid: int, cookie_uid: int):
+ """
+ 说明:
+ 添加每日查询重复uid的cookie
+ 参数:
+ :param uid: 原神uid
+ :param cookie_uid: cookie的uid
+ """
+ query = cls.query.where(cls.uid == cookie_uid).with_for_update()
+ user = await query.gino.first()
+ await user.update(today_query_uid=cls.today_query_uid + f"{uid} ").apply()
+
+ @classmethod
+ async def _get_user_data(
+ cls, user_qq: Optional[int], uid: Optional[int], type_: str
+ ) -> Optional[Union[int, str]]:
+ """
+ 说明:
+ 获取用户数据
+ 参数:
+ :param user_qq: 用户qq
+ :param uid: uid
+ :param type_: 数据类型
+ """
+ if type_ == "uid":
+ user = await cls.query.where(cls.user_qq == user_qq).gino.first()
+ return user.uid if user else None
+ user = await cls.query.where(cls.uid == uid).gino.first()
+ if not user:
+ return None
+ if type_ == "mys_id":
+ return user.mys_id
+ elif type_ == "cookie":
+ return user.cookie
+
+ @classmethod
+ async def reset_today_query_uid(cls):
+ for u in await cls.query.with_for_update().gino.all():
+ if u.today_query_uid:
+ await u.update(
+ today_query_uid=""
+ ).apply()
diff --git a/plugins/genshin/query_user/query_memo/__init__.py b/plugins/genshin/query_user/query_memo/__init__.py
new file mode 100644
index 00000000..13d1fad0
--- /dev/null
+++ b/plugins/genshin/query_user/query_memo/__init__.py
@@ -0,0 +1,53 @@
+from nonebot import on_command
+from nonebot.typing import T_State
+from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent
+from services.log import logger
+from .data_source import get_user_memo
+from ..models import Genshin
+
+
+__zx_plugin_name__ = "原神便笺查询"
+__plugin_usage__ = """
+usage:
+ 通过指定cookie和uid查询事实数据
+ 指令:
+ 原神便笺查询/yss
+ 示例:原神便笺查询 92342233
+""".strip()
+__plugin_des__ = "不能浪费丝毫体力"
+__plugin_cmd__ = ["原神便笺查询/yss"]
+__plugin_type__ = ("原神相关",)
+__plugin_version__ = 0.1
+__plugin_author__ = "HibiKier"
+__plugin_settings__ = {
+ "level": 5,
+ "default_status": True,
+ "limit_superuser": False,
+ "cmd": ["原神便笺查询"],
+}
+
+query_memo_matcher = on_command("原神便签查询", aliases={"原神便笺查询", "yss"}, priority=5, block=True)
+
+
+@query_memo_matcher.handle()
+async def _(bot: Bot, event: MessageEvent, state: T_State):
+ uid = await Genshin.get_user_uid(event.user_id)
+ if not uid or not await Genshin.get_user_cookie(uid, True):
+ await query_memo_matcher.finish("请先绑定uid和cookie!")
+ if isinstance(event, GroupMessageEvent):
+ uname = event.sender.card or event.sender.nickname
+ else:
+ uname = event.sender.nickname
+ data = await get_user_memo(event.user_id, uid, uname)
+ if data:
+ await query_memo_matcher.send(data)
+ logger.info(
+ f"(USER {event.user_id}, "
+ f"GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) 使用原神便笺查询 uid:{uid}"
+ )
+ else:
+ await query_memo_matcher.send("未查询到数据...")
+
+
+
+
diff --git a/plugins/genshin/query_user/query_memo/data_source.py b/plugins/genshin/query_user/query_memo/data_source.py
new file mode 100644
index 00000000..686da4c8
--- /dev/null
+++ b/plugins/genshin/query_user/query_memo/data_source.py
@@ -0,0 +1,237 @@
+from typing import Optional, Union
+from nonebot.adapters.cqhttp import MessageSegment
+from configs.config import Config
+from asyncio.exceptions import TimeoutError
+from services.log import logger
+from configs.path_config import IMAGE_PATH
+from utils.image_utils import BuildImage
+from utils.http_utils import AsyncHttpx
+from utils.utils import get_user_avatar
+from utils.message_builder import image
+from ..utils import get_ds
+from ..models import Genshin
+from io import BytesIO
+from pathlib import Path
+from nonebot import Driver
+import asyncio
+import nonebot
+
+
+driver: Driver = nonebot.get_driver()
+
+
+memo_path = Path(IMAGE_PATH) / "genshin" / "genshin_memo"
+memo_path.mkdir(exist_ok=True, parents=True)
+
+
+@driver.on_startup
+async def _():
+ for name, url in zip(
+ ["resin.png", "task.png", "resin_discount.png"],
+ [
+ "https://upload-bbs.mihoyo.com/upload/2021/09/29/8819732/54266243c7d15ba31690c8f5d63cc3c6_71491376413333325"
+ "20.png?x-oss-process=image//resize,s_600/quality,q_80/auto-orient,0/interlace,1/format,png",
+ "https://patchwiki.biligame.com/images/ys/thumb/c/cc/6k6kuj1kte6m1n7hexqfrn92z6h4yhh.png/60px-委托任务logo.png",
+ "https://patchwiki.biligame.com/images/ys/d/d9/t1hv6wpucbwucgkhjntmzroh90nmcdv.png",
+ ],
+ ):
+ file = memo_path / name
+ if not file.exists():
+ await AsyncHttpx.download_file(url, file)
+ logger.info(f"已下载原神便签资源 -> {file}...")
+
+
+async def get_user_memo(user_id: int, uid: int, uname: str) -> Optional[Union[str, MessageSegment]]:
+ uid = str(uid)
+ if uid[0] == "1" or uid[0] == "2":
+ server_id = "cn_gf01"
+ elif uid[0] == "5":
+ server_id = "cn_qd01"
+ else:
+ return None
+ return await parse_data_and_draw(user_id, uid, server_id, uname)
+
+
+async def get_memo(uid: str, server_id: str) -> "Union[str, dict], int":
+ try:
+ req = await AsyncHttpx.get(
+ url=f"https://api-takumi.mihoyo.com/game_record/app/genshin/api/dailyNote?server={server_id}&role_id={uid}",
+ headers={
+ "DS": get_ds(f"role_id={uid}&server={server_id}"),
+ "x-rpc-app_version": Config.get_config("genshin", "mhyVersion"),
+ "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1",
+ "x-rpc-client_type": Config.get_config("genshin", "client_type"),
+ "Referer": "https://webstatic.mihoyo.com/",
+ "Cookie": await Genshin.get_user_cookie(int(uid))
+ },
+ )
+ data = req.json()
+ if data["message"] == "OK":
+ return data["data"], 200
+ return data["message"], 999
+ except TimeoutError:
+ return "访问超时,请稍后再试", 997
+ except Exception as e:
+ logger.info(f"便签查询获取失败未知错误 {e}:{e}")
+ return "发生了一些错误,请稍后再试", 998
+
+
+def create_border(
+ image_name: str, content: str, notice_text: str, value: str
+) -> BuildImage:
+ border = BuildImage(500, 100, color="#E0D9D1", font="HYWenHei-85W.ttf", font_size=20)
+ text_bk = BuildImage(350, 96, color="#F5F1EB", font_size=23, font="HYWenHei-85W.ttf")
+ _x = 70 if image_name == "resin.png" else 50
+ _px = 10 if image_name == "resin.png" else 20
+ text_bk.paste(
+ BuildImage(_x, _x, background=memo_path / image_name),
+ (_px, 0),
+ True,
+ center_type="by_height",
+ )
+ text_bk.text((87, 20), content)
+ text_bk.paste(
+ BuildImage(
+ 0,
+ 0,
+ plain_text=notice_text,
+ font_color=(203, 189, 175),
+ font="HYWenHei-85W.ttf",
+ font_size=17,
+ ),
+ (87, 50),
+ True,
+ )
+ font_width, _ = border.getsize(value)
+ border.text((350 + 76 - int(font_width / 2), 0), value, center_type="by_height")
+ border.paste(text_bk, (2, 0), center_type="by_height")
+ return border
+
+
+async def parse_data_and_draw(
+ user_id: int, uid: str, server_id: str, uname: str
+) -> Union[str, MessageSegment]:
+ data, code = await get_memo(uid, server_id)
+ if code != 200:
+ return data
+ user_avatar = BytesIO(await get_user_avatar(user_id))
+ for x in data["expeditions"]:
+ file_name = x["avatar_side_icon"].split("_")[-1]
+ role_avatar = memo_path / "role_avatar" / file_name
+ if not role_avatar.exists():
+ await AsyncHttpx.download_file(x["avatar_side_icon"], role_avatar)
+ return await asyncio.get_event_loop().run_in_executor(
+ None, _parse_data_and_draw, data, user_avatar, uid, uname
+ )
+
+
+def _parse_data_and_draw(
+ data: dict, user_avatar: BytesIO, uid: int, uname: str
+) -> Union[str, MessageSegment]:
+ current_resin = data["current_resin"] # 当前树脂
+ max_resin = data["max_resin"] # 最大树脂
+ resin_recovery_time = data["resin_recovery_time"] # 树脂全部回复时间
+ finished_task_num = data["finished_task_num"] # 完成的每日任务
+ total_task_num = data["total_task_num"] # 每日任务总数
+ remain_resin_discount_num = data["remain_resin_discount_num"] # 值得铭记的强敌总数
+ resin_discount_num_limit = data["resin_discount_num_limit"] # 剩余值得铭记的强敌
+ current_expedition_num = data["current_expedition_num"] # 当前挖矿人数
+ max_expedition_num = data["max_expedition_num"] # 每日挖矿最大人数
+ expeditions = data["expeditions"] # 挖矿详情
+
+ minute, second = divmod(int(resin_recovery_time), 60)
+ hour, minute = divmod(minute, 60)
+
+ A = BuildImage(1030, 520, color="#f1e9e1", font_size=15, font="HYWenHei-85W.ttf")
+ A.text((10, 15), "原神便笺 | Create By ZhenXun", (198, 186, 177))
+ ava = BuildImage(100, 100, background=user_avatar)
+ ava.circle()
+ A.paste(ava, (40, 40), True)
+ A.paste(
+ BuildImage(0, 0, plain_text=uname, font_size=20, font="HYWenHei-85W.ttf"),
+ (160, 62),
+ True,
+ )
+ A.paste(
+ BuildImage(
+ 0,
+ 0,
+ plain_text=f"UID:{uid}",
+ font_size=15,
+ font="HYWenHei-85W.ttf",
+ font_color=(21, 167, 89),
+ ),
+ (160, 92),
+ True,
+ )
+ border = create_border(
+ "resin.png",
+ "原粹树脂",
+ "将在{:0>2d}:{:0>2d}:{:0>2d}秒后全部恢复".format(hour, minute, second),
+ f"{current_resin}/{max_resin}",
+ )
+
+ A.paste(border, (10, 155))
+ border = create_border(
+ "task.png",
+ "每日委托",
+ "今日委托已全部完成" if finished_task_num == total_task_num else "今日委托完成数量不足",
+ f"{finished_task_num}/{total_task_num}",
+ )
+ A.paste(border, (10, 265))
+ border = create_border(
+ "resin_discount.png",
+ "值得铭记的强敌",
+ "本周剩余消耗减半次数",
+ f"{remain_resin_discount_num}/{resin_discount_num_limit}",
+ )
+ A.paste(border, (10, 375))
+ expeditions_border = BuildImage(
+ 470, 430, color="#E0D9D1", font="HYWenHei-85W.ttf", font_size=20
+ )
+ expeditions_text = BuildImage(
+ 466, 426, color="#F5F1EB", font_size=23, font="HYWenHei-85W.ttf"
+ )
+ expeditions_text.text(
+ (5, 5), f"探索派遣限制{current_expedition_num}/{max_expedition_num}", (100, 100, 98)
+ )
+ h = 45
+ for x in expeditions:
+ _bk = BuildImage(400, 66, color="#ECE3D8", font="HYWenHei-85W.ttf", font_size=21)
+ file_name = x["avatar_side_icon"].split("_")[-1]
+ role_avatar = memo_path / "role_avatar" / file_name
+ _ava_img = BuildImage(75, 75, background=role_avatar)
+ _ava_img.circle()
+ if x["status"] == "Finished":
+ msg = "探索完成"
+ font_color = (146, 188, 63)
+ _circle_color = (146, 188, 63)
+ else:
+ minute, second = divmod(int(x["remained_time"]), 60)
+ hour, minute = divmod(minute, 60)
+ font_color = (193, 180, 167)
+ msg = "还剩{:0>2d}小时{:0>2d}分钟{:0>2d}秒".format(hour, minute, second)
+ _circle_color = "#DE9C58"
+
+ _circle_bk = BuildImage(60, 60)
+ _circle_bk.circle()
+ a_circle = BuildImage(55, 55, color=_circle_color)
+ a_circle.circle()
+ b_circle = BuildImage(47, 47)
+ b_circle.circle()
+ a_circle.paste(b_circle, (4, 4), alpha=True)
+ _circle_bk.paste(a_circle, (4, 4), alpha=True)
+
+ _bk.paste(_circle_bk, (25, 0), True, center_type="by_height")
+ _bk.paste(_ava_img, (19, -13), True)
+ _bk.text((100, 0), msg, font_color, "by_height")
+ _bk.circle_corner(20)
+
+ expeditions_text.paste(_bk, (25, h), True)
+ h += 75
+
+ expeditions_border.paste(expeditions_text, center_type="center")
+
+ A.paste(expeditions_border, (550, 45))
+
+ return image(b64=A.pic2bs4())
diff --git a/plugins/genshin/query_user/query_role/__init__.py b/plugins/genshin/query_user/query_role/__init__.py
new file mode 100644
index 00000000..d28775b0
--- /dev/null
+++ b/plugins/genshin/query_user/query_role/__init__.py
@@ -0,0 +1,61 @@
+from nonebot import on_command
+from nonebot.typing import T_State
+from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent
+from services.log import logger
+from .data_source import query_role_data
+from ..models import Genshin
+from utils.utils import get_message_text, is_number
+
+
+__zx_plugin_name__ = "原神玩家查询"
+__plugin_usage__ = """
+usage:
+ 通过uid查询原神玩家信息
+ 指令:
+ 原神玩家查询/ys ?[uid]
+ 示例:原神玩家查询 92342233
+""".strip()
+__plugin_des__ = "请问你们有几个肝?"
+__plugin_cmd__ = ["原神玩家查询/ys"]
+__plugin_type__ = ("原神相关",)
+__plugin_version__ = 0.1
+__plugin_author__ = "HibiKier"
+__plugin_settings__ = {
+ "level": 5,
+ "default_status": True,
+ "limit_superuser": False,
+ "cmd": ["原神玩家查询"],
+}
+
+
+query_role_info_matcher = on_command("原神玩家查询", aliases={"原神玩家查找", "ys"}, priority=5, block=True)
+
+
+@query_role_info_matcher.handle()
+async def _(bot: Bot, event: MessageEvent, state: T_State):
+ msg = get_message_text(event.json())
+ if msg:
+ if not is_number(msg):
+ await query_role_info_matcher.finish("查询uid必须为数字!")
+ msg = int(msg)
+ if not msg:
+ uid = await Genshin.get_user_uid(event.user_id)
+ else:
+ uid = msg
+ if not uid: # or not await Genshin.get_user_cookie(uid):
+ await query_role_info_matcher.finish("请先绑定uid和cookie!")
+ nickname = event.sender.card if event.sender.card else event.sender.nickname
+ mys_id = await Genshin.get_user_mys_id(uid)
+ data = await query_role_data(event.user_id, uid, mys_id, nickname)
+ if data:
+ await query_role_info_matcher.send(data)
+ logger.info(
+ f"(USER {event.user_id}, "
+ f"GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) 使用原神玩家查询 uid:{uid}"
+ )
+ else:
+ await query_role_info_matcher.send("查询失败..")
+
+
+
+
diff --git a/plugins/genshin/query_user/query_role/data_source.py b/plugins/genshin/query_user/query_role/data_source.py
new file mode 100644
index 00000000..76b11265
--- /dev/null
+++ b/plugins/genshin/query_user/query_role/data_source.py
@@ -0,0 +1,249 @@
+from typing import Optional, List, Dict, Union
+from .draw_image import init_image, get_genshin_image
+from nonebot.adapters.cqhttp import MessageSegment
+from ..utils import get_ds, element_mastery
+from services.log import logger
+from utils.http_utils import AsyncHttpx
+from configs.config import Config
+from ..models import Genshin
+
+try:
+ import ujson as json
+except ModuleNotFoundError:
+ import json
+
+
+async def query_role_data(
+ user_id: int, uid: int, mys_id: Optional[str] = None, nickname: Optional[str] = None
+) -> Optional[Union[MessageSegment, str]]:
+ uid = str(uid)
+ if uid[0] == "1" or uid[0] == "2":
+ server_id = "cn_gf01"
+ elif uid[0] == "5":
+ server_id = "cn_qd01"
+ else:
+ return None
+ return await get_image(user_id, uid, server_id, mys_id, nickname)
+
+
+async def get_image(
+ user_id: int,
+ uid: str,
+ server_id: str,
+ mys_id: Optional[str] = None,
+ nickname: Optional[str] = None,
+) -> Optional[Union[MessageSegment, str]]:
+ """
+ 生成图片
+ :param user_id:用户qq
+ :param uid: 用户uid
+ :param server_id: 服务器
+ :param mys_id: 米游社id
+ :param nickname: QQ昵称
+ :return:
+ """
+ data, code = await get_info(uid, server_id)
+ if code != 200:
+ return data
+ if data:
+ char_data_list, role_data, world_data_dict, home_data_list = parsed_data(data)
+ mys_data = await get_mys_data(uid, mys_id)
+ if mys_data:
+ nickname = None
+ if char_data_list:
+ char_detailed_data = await get_character(
+ uid, [x["id"] for x in char_data_list], server_id
+ )
+ _x = {}
+ if char_detailed_data:
+ for char in char_detailed_data["avatars"]:
+ _x[char["name"]] = {
+ "weapon": char["weapon"]["name"],
+ "weapon_image": char["weapon"]["icon"],
+ "level": char["weapon"]["level"],
+ "affix_level": char["weapon"]["affix_level"],
+ }
+
+ await init_image(char_data_list, _x)
+ return await get_genshin_image(
+ user_id,
+ uid,
+ char_data_list,
+ role_data,
+ world_data_dict,
+ home_data_list,
+ _x,
+ mys_data,
+ nickname,
+ )
+ return "未找到用户数据..."
+
+
+# Github-@lulu666lulu https://github.com/Azure99/GenshinPlayerQuery/issues/20
+"""
+{body:"",query:{"action_ticket": undefined, "game_biz": "hk4e_cn”}}
+对应 https://api-takumi.mihoyo.com/binding/api/getUserGameRolesByCookie?game_biz=hk4e_cn //查询米哈游账号下绑定的游戏(game_biz可留空)
+{body:"",query:{"uid": 12345(被查询账号米哈游uid)}}
+对应 https://api-takumi.mihoyo.com/game_record/app/card/wapi/getGameRecordCard?uid=
+{body:"",query:{'role_id': '查询账号的uid(游戏里的)' ,'server': '游戏服务器'}}
+对应 https://api-takumi.mihoyo.com/game_record/app/genshin/api/index?server= server信息 &role_id= 游戏uid
+{body:"",query:{'role_id': '查询账号的uid(游戏里的)' , 'schedule_type': 1(我这边只看到出现过1和2), 'server': 'cn_gf01'}}
+对应 https://api-takumi.mihoyo.com/game_record/app/genshin/api/spiralAbyss?schedule_type=1&server= server信息 &role_id= 游戏uid
+{body:"",query:{game_id: 2(目前我知道有崩坏3是1原神是2)}}
+对应 https://api-takumi.mihoyo.com/game_record/app/card/wapi/getAnnouncement?game_id= 这个是公告api
+b=body q=query
+其中b只在post的时候有内容,q只在get的时候有内容
+"""
+
+
+async def get_info(uid_: str, server_id: str) -> "Optional[Union[dict, str]], int":
+ try:
+ req = await AsyncHttpx.get(
+ url=f"https://api-takumi.mihoyo.com/game_record/app/genshin/api/index?server={server_id}&role_id={uid_}",
+ headers={
+ "Accept": "application/json, text/plain, */*",
+ "DS": get_ds(f"role_id={uid_}&server={server_id}"),
+ "Origin": "https://webstatic.mihoyo.com",
+ "x-rpc-app_version": Config.get_config("genshin", "mhyVersion"),
+ "User-Agent": "Mozilla/5.0 (Linux; Android 9; Unspecified Device) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36 miHoYoBBS/2.2.0",
+ "x-rpc-client_type": Config.get_config("genshin", "client_type"),
+ "Referer": "https://webstatic.mihoyo.com/app/community-game-records/index.html?v=6",
+ "Accept-Encoding": "gzip, deflate",
+ "Accept-Language": "zh-CN,en-US;q=0.8",
+ "X-Requested-With": "com.mihoyo.hyperion",
+ "Cookie": await Genshin.get_user_cookie(int(uid_))
+ },
+ )
+ data = req.json()
+ if data["message"] == "OK":
+ return data["data"], 200
+ return data["message"], 999
+ except Exception as e:
+ logger.error(f"访问失败,请重试! {type(e)}: {e}")
+ return None, -1
+
+
+async def get_character(
+ uid: str, character_ids: List[str], server_id="cn_gf01"
+) -> Optional[dict]:
+ try:
+ req = await AsyncHttpx.post(
+ url="https://api-takumi.mihoyo.com/game_record/app/genshin/api/character",
+ headers={
+ "Accept": "application/json, text/plain, */*",
+ "DS": get_ds(
+ "",
+ {
+ "character_ids": character_ids,
+ "role_id": uid,
+ "server": server_id,
+ },
+ ),
+ "Origin": "https://webstatic.mihoyo.com",
+ "Cookie": await Genshin.get_user_cookie(int(uid)),
+ "x-rpc-app_version": Config.get_config("genshin", "mhyVersion"),
+ "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1",
+ "x-rpc-client_type": "5",
+ "Referer": "https://webstatic.mihoyo.com/",
+ "Accept-Encoding": "gzip, deflate",
+ "Accept-Language": "zh-CN,en-US;q=0.8",
+ "X-Requested-With": "com.mihoyo.hyperion",
+ },
+ json={"character_ids": character_ids, "role_id": uid, "server": server_id},
+ )
+ data = req.json()
+ if data["message"] == "OK":
+ return data["data"]
+ except Exception as e:
+ logger.error(f"访问失败,请重试! {type(e)}: {e}")
+ return None
+
+
+def parsed_data(
+ data: dict,
+) -> "Optional[List[Dict[str, str]]], Dict[str, str], Optional[List[Dict[str, str]]], Optional[List[Dict[str, str]]]":
+ """
+ 解析数据
+ :param data: 数据
+ """
+ char_data_list = []
+ for char in data["avatars"]:
+ _x = {
+ "id": char["id"],
+ "image": char["image"],
+ "name": char["name"],
+ "element": element_mastery[char["element"].lower()],
+ "fetter": char["fetter"],
+ "level": char["level"],
+ "rarity": char["rarity"],
+ "actived_constellation_num": char["actived_constellation_num"],
+ }
+ char_data_list.append(_x)
+ role_data = {
+ "active_day_number": data["stats"]["active_day_number"], # 活跃天数
+ "achievement_number": data["stats"]["achievement_number"], # 达成成就数量
+ "win_rate": data["stats"]["win_rate"],
+ "anemoculus_number": data["stats"]["anemoculus_number"], # 风神瞳已收集
+ "geoculus_number": data["stats"]["geoculus_number"], # 岩神瞳已收集
+ "avatar_number": data["stats"]["avatar_number"], # 获得角色数量
+ "way_point_number": data["stats"]["way_point_number"], # 传送点已解锁
+ "domain_number": data["stats"]["domain_number"], # 秘境解锁数量
+ "spiral_abyss": data["stats"]["spiral_abyss"], # 深渊当期进度
+ "precious_chest_number": data["stats"]["precious_chest_number"], # 珍贵宝箱
+ "luxurious_chest_number": data["stats"]["luxurious_chest_number"], # 华丽宝箱
+ "exquisite_chest_number": data["stats"]["exquisite_chest_number"], # 精致宝箱
+ "magic_chest_number": data["stats"]["magic_chest_number"], # 奇馈宝箱
+ "common_chest_number": data["stats"]["common_chest_number"], # 普通宝箱
+ "electroculus_number": data["stats"]["electroculus_number"], # 雷神瞳已收集
+ }
+ world_data_dict = {}
+ for world in data["world_explorations"]:
+ _x = {
+ "level": world["level"], # 声望等级
+ "exploration_percentage": world["exploration_percentage"], # 探索进度
+ "image": world["icon"],
+ "name": world["name"],
+ "offerings": world["offerings"],
+ }
+ world_data_dict[world["name"]] = _x
+ home_data_list = []
+ for home in data["homes"]:
+ _x = {
+ "level": home["level"], # 最大信任等级
+ "visit_num": home["visit_num"], # 最高历史访客数
+ "comfort_num": home["comfort_num"], # 最高洞天仙力
+ "item_num": home["item_num"], # 已获得摆件数量
+ "name": home["name"],
+ "icon": home["icon"],
+ "comfort_level_name": home["comfort_level_name"],
+ "comfort_level_icon": home["comfort_level_icon"],
+ }
+ home_data_list.append(_x)
+ return char_data_list, role_data, world_data_dict, home_data_list
+
+
+async def get_mys_data(uid: str, mys_id: Optional[str]) -> Optional[List[Dict]]:
+ """
+ 获取用户米游社数据
+ :param uid: 原神uid
+ :param mys_id: 米游社id
+ """
+ if mys_id:
+ try:
+ req = await AsyncHttpx.get(
+ url=f"https://api-takumi.mihoyo.com/game_record/card/wapi/getGameRecordCard?uid={mys_id}",
+ headers={
+ "DS": get_ds(f"uid={mys_id}"),
+ "x-rpc-app_version": Config.get_config("genshin", "mhyVersion"),
+ "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1",
+ "x-rpc-client_type": "5",
+ "Referer": "https://webstatic.mihoyo.com/",
+ "Cookie": await Genshin.get_user_cookie(int(uid))
+ },
+ )
+ data = req.json()
+ if data["message"] == "OK":
+ return data["data"]["list"]
+ except Exception as e:
+ logger.error(f"访问失败,请重试! {type(e)}: {e}")
+ return None
diff --git a/plugins/genshin/query_user/query_role/draw_image.py b/plugins/genshin/query_user/query_role/draw_image.py
new file mode 100644
index 00000000..556e1cae
--- /dev/null
+++ b/plugins/genshin/query_user/query_role/draw_image.py
@@ -0,0 +1,530 @@
+from configs.path_config import IMAGE_PATH
+from pathlib import Path
+from utils.image_utils import BuildImage
+from typing import List, Dict, Optional
+from utils.message_builder import image
+from nonebot.adapters.cqhttp import MessageSegment
+from utils.http_utils import AsyncHttpx
+from utils.utils import get_user_avatar
+from io import BytesIO
+import random
+import asyncio
+import os
+
+
+image_path = Path(IMAGE_PATH) / "genshin" / "genshin_card"
+
+
+async def get_genshin_image(
+ user_id: int,
+ uid: str,
+ char_data_list: List[Dict],
+ role_data: Dict,
+ world_data_dict: Dict,
+ home_data_list: List[Dict],
+ char_detailed_dict: dict = None,
+ mys_data: Optional[List[Dict]] = None,
+ nickname: Optional[str] = None,
+) -> MessageSegment:
+ """
+ 生成图片数据
+ :param user_id:用户qq
+ :param uid: 原神uid
+ :param char_data_list: 角色列表
+ :param role_data: 玩家数据
+ :param world_data_dict: 国家数据字典
+ :param home_data_list: 家园列表
+ :param char_detailed_dict: 角色武器字典
+ :param mys_data: 用户米游社数据
+ :param nickname: 用户昵称
+ """
+ user_ava = BytesIO(await get_user_avatar(user_id))
+ return await asyncio.get_event_loop().run_in_executor(
+ None,
+ _get_genshin_image,
+ uid,
+ char_data_list,
+ role_data,
+ world_data_dict,
+ home_data_list,
+ char_detailed_dict,
+ mys_data,
+ nickname,
+ user_ava,
+ )
+
+
+def _get_genshin_image(
+ uid: str,
+ char_data_list: List[Dict],
+ role_data: Dict,
+ world_data_dict: Dict,
+ home_data_list: List[Dict],
+ char_detailed_dict: dict = None,
+ mys_data: Optional[Dict] = None,
+ nickname: Optional[str] = None,
+ user_ava: Optional[BytesIO] = None,
+) -> MessageSegment:
+ """
+ 生成图片数据
+ :param uid: 原神uid
+ :param char_data_list: 角色列表
+ :param role_data: 玩家数据
+ :param world_data_dict: 国家数据字典
+ :param home_data_list: 家园列表
+ :param char_detailed_dict: 角色武器字典
+ :param mys_data: 用户米游社数据
+ :param nickname: 用户昵称
+ :param user_ava:用户头像
+ """
+ x = 450 if char_detailed_dict else 330
+ char_height = (
+ len(char_data_list) / 7
+ if len(char_data_list) % 7 == 0
+ else len(char_data_list) / 7 + 1
+ )
+ foot = BuildImage(1700, 87, background=image_path / "head.png")
+ head = BuildImage(1700, 87, background=image_path / "head.png")
+ head.rotate(180)
+ middle = BuildImage(
+ 1700, int(1600 + 200 + x * char_height), background=image_path / "middle.png"
+ )
+ A = BuildImage(middle.w, middle.h + foot.h + head.h)
+ A.paste(head, (-5, 0), True)
+ A.paste(middle, (0, head.h), True)
+ A.paste(foot, (0, head.h + middle.h), True)
+ A.crop((0, 0, A.w - 5, A.h))
+ user_image = get_user_data_image(uid, role_data, mys_data, nickname, user_ava)
+ home_image = get_home_data_image(home_data_list)
+ country_image = get_country_data_image(world_data_dict)
+ char_image, _h = get_char_data_image(char_data_list, char_detailed_dict)
+ top_bk = BuildImage(user_image.w, user_image.h + home_image.h + 100, color="#F9F6F2")
+ top_bk.paste(user_image, alpha=True)
+ top_bk.paste(home_image, (0, user_image.h + 50), alpha=True)
+ top_bk.paste(country_image, (home_image.w + 100, user_image.h + 50), alpha=True)
+ bar = BuildImage(1600, 200, font_size=50, color="#F9F6F2", font="HYWenHei-85W.ttf")
+ bar.text((50, 10), "角色背包", (104, 103, 101))
+ bar.line((50, 90, 1550, 90), (227, 219, 209), width=10)
+ if A.h - top_bk.h - bar.h - _h > 200:
+ _h = A.h - top_bk.h - bar.h - _h - 200
+ A.crop((0, 0, A.w, A.h - _h))
+ A.paste(foot, (0, A.h - 87))
+ A.paste(top_bk, (0, 100), center_type="by_width")
+ A.paste(bar, (50, top_bk.h + 80))
+ A.paste(char_image, (0, top_bk.h + bar.h + 10), center_type="by_width")
+ rand = random.randint(1, 10000)
+ A.save(Path(IMAGE_PATH) / "temp" / f"genshin_user_card_{rand}.png")
+ return image(f"genshin_user_card_{rand}.png", "temp")
+
+
+def get_user_data_image(
+ uid: str,
+ role_data: Dict,
+ mys_data: Optional[Dict] = None,
+ nickname: Optional[str] = None,
+ user_ava: Optional[BytesIO] = None,
+) -> BuildImage:
+ """
+ 画出玩家基本数据
+ :param uid: 原神uid
+ :param role_data: 玩家数据
+ :param mys_data: 玩家米游社数据
+ :param nickname: 用户昵称
+ :param user_ava:用户头像
+ """
+ if mys_data:
+ nickname = [x["nickname"] for x in mys_data if x["game_id"] == 2][0]
+ region = BuildImage(1440, 450, color="#E3DBD1", font="HYWenHei-85W.ttf")
+ region.circle_corner(30)
+ uname_img = BuildImage(
+ 0,
+ 0,
+ plain_text=nickname,
+ font_size=40,
+ color=(255, 255, 255, 0),
+ font="HYWenHei-85W.ttf",
+ )
+ uid_img = BuildImage(
+ 0,
+ 0,
+ plain_text=f"UID: {uid}",
+ font_size=25,
+ color=(255, 255, 255, 0),
+ font="HYWenHei-85W.ttf",
+ font_color=(21, 167, 89),
+ )
+ ava_bk = BuildImage(270, 270, background=image_path / "cover.png")
+ # 用户头像
+ if user_ava:
+ ava_img = BuildImage(200, 200, background=user_ava)
+ ava_img.circle()
+ ava_bk.paste(ava_img, alpha=True, center_type="center")
+ else:
+ ava_img = BuildImage(
+ 245,
+ 245,
+ background=image_path
+ / "chars_ava"
+ / random.choice(os.listdir(image_path / "chars_ava")),
+ )
+ ava_bk.paste(ava_img, (12, 16), alpha=True)
+ region.paste(uname_img, (int(170 + uid_img.w / 2 - uname_img.w / 2), 305), True)
+ region.paste(uid_img, (170, 355), True)
+ region.paste(ava_bk, (int(550 / 2 - ava_bk.w / 2), 40), True)
+ data_img = BuildImage(
+ 800, 400, color="#E3DBD1", font="HYWenHei-85W.ttf", font_size=40
+ )
+ _height = 0
+ keys = [
+ ["活跃天数", "成就达成", "获得角色", "深境螺旋"],
+ ["华丽宝箱", "珍贵宝箱", "精致宝箱", "普通宝箱"],
+ ["奇馈宝箱", "风神瞳", "岩神瞳", "雷神瞳"],
+ ]
+ values = [
+ [
+ role_data["active_day_number"],
+ role_data["achievement_number"],
+ role_data["avatar_number"],
+ role_data["spiral_abyss"],
+ ],
+ [
+ role_data["luxurious_chest_number"],
+ role_data["precious_chest_number"],
+ role_data["exquisite_chest_number"],
+ role_data["common_chest_number"],
+ ],
+ [
+ role_data["magic_chest_number"],
+ role_data["anemoculus_number"],
+ role_data["geoculus_number"],
+ role_data["electroculus_number"],
+ ],
+ ]
+ for key, value in zip(keys, values):
+ _tmp_data_img = BuildImage(
+ 800, 200, color="#E3DBD1", font="HYWenHei-85W.ttf", font_size=40
+ )
+ _width = 10
+ for k, v in zip(key, value):
+ t_ = BuildImage(
+ 0,
+ 0,
+ plain_text=k,
+ color=(255, 255, 255, 0),
+ font_color=(138, 143, 143),
+ font="HYWenHei-85W.ttf",
+ font_size=30,
+ )
+ tmp_ = BuildImage(
+ t_.w, t_.h + 70, color="#E3DBD1", font="HYWenHei-85W.ttf", font_size=40
+ )
+ tmp_.text((0, 0), str(v), center_type="by_width")
+ tmp_.paste(t_, (0, 50), True, "by_width")
+ _tmp_data_img.paste(tmp_, (_width if len(key) > 3 else _width + 15, 0))
+ _width += 200
+ data_img.paste(_tmp_data_img, (0, _height))
+ _height += _tmp_data_img.h - 70
+ region.paste(data_img, (510, 50))
+ return region
+
+
+def get_home_data_image(home_data_list: List[Dict]) -> BuildImage:
+ """
+ 画出家园数据
+ :param home_data_list: 家园列表
+ """
+ region = BuildImage(
+ 550, 1050, color="#E3DBD1", font="HYWenHei-85W.ttf", font_size=40
+ )
+ try:
+ region.text(
+ (0, 30), f'尘歌壶 Lv.{home_data_list[0]["level"]}', center_type="by_width"
+ )
+ region.text(
+ (0, 980), f'仙力: {home_data_list[0]["comfort_num"]}', center_type="by_width"
+ )
+ except (IndexError, KeyError):
+ region.text((0, 30), f"尘歌壶 Lv.0", center_type="by_width")
+ region.text((0, 980), f"仙力: 0", center_type="by_width")
+ region.circle_corner(30)
+ height = 100
+ homes = os.listdir(image_path / "homes")
+ homes.remove("lock.png")
+ homes.sort()
+ unlock_home = [x["name"] for x in home_data_list]
+ for i, file in enumerate(homes):
+ home_img = image_path / "homes" / file
+ x = BuildImage(500, 250, background=home_img)
+ if file.split(".")[0] not in unlock_home:
+ black_img = BuildImage(500, 250, color="black")
+ lock_img = BuildImage(0, 0, background=image_path / "homes" / "lock.png")
+ black_img.circle_corner(50)
+ black_img.transparent(1)
+ black_img.paste(lock_img, alpha=True, center_type="center")
+ x.paste(black_img, alpha=True)
+ else:
+ black_img = BuildImage(
+ 500, 150, color="black", font="HYWenHei-85W.ttf", font_size=40
+ )
+ black_img.text((55, 55), file.split(".")[0], fill=(226, 211, 146))
+ black_img.transparent(1)
+ text_img = BuildImage(
+ 0,
+ 0,
+ plain_text="洞天等级",
+ font="HYWenHei-85W.ttf",
+ font_color=(203, 200, 184),
+ font_size=35,
+ color=(255, 255, 255, 0),
+ )
+ level_img = BuildImage(
+ 0,
+ 0,
+ plain_text=f'{home_data_list[0]["comfort_level_name"]}',
+ font="HYWenHei-85W.ttf",
+ font_color=(211, 213, 207),
+ font_size=30,
+ color=(255, 255, 255, 0),
+ )
+ black_img.paste(text_img, (270, 25), True)
+ black_img.paste(level_img, (278, 85), True)
+ x.paste(black_img, alpha=True, center_type="center")
+ x.circle_corner(50)
+ region.paste(x, (0, height), True, "by_width")
+ height += 300
+ return region
+
+
+def get_country_data_image(world_data_dict: Dict) -> BuildImage:
+ """
+ 画出国家探索供奉等图像
+ :param world_data_dict: 国家数据字典
+ """
+ region = BuildImage(790, 1050, color="#F9F6F2")
+ height = 0
+ for country in ["蒙德", "龙脊雪山", "璃月", "稻妻"]:
+ x = BuildImage(790, 250, color="#3A4467")
+ logo = BuildImage(180, 180, background=image_path / "logo" / f"{country}.png")
+ tmp_bk = BuildImage(770, 230, color="#606779")
+ tmp_bk.circle_corner(10)
+ content_bk = BuildImage(
+ 755, 215, color="#3A4467", font_size=40, font="HYWenHei-85W.ttf"
+ )
+ content_bk.paste(logo, (50, 0), True, "by_height")
+ if country in ["蒙德", "璃月"]:
+ content_bk.text((300, 40), "探索", fill=(239, 211, 114))
+ content_bk.text(
+ (450, 40),
+ f"{world_data_dict[country]['exploration_percentage'] / 10}%",
+ fill=(255, 255, 255),
+ )
+ content_bk.text((300, 120), "声望", fill=(239, 211, 114))
+ content_bk.text(
+ (450, 120),
+ f"Lv.{world_data_dict[country]['level']}",
+ fill=(255, 255, 255),
+ )
+ elif country in ["龙脊雪山"]:
+ content_bk.text((300, 40), "探索", fill=(239, 211, 114))
+ content_bk.text(
+ (450, 40),
+ f"{world_data_dict[country]['exploration_percentage'] / 10}%",
+ fill=(255, 255, 255),
+ )
+ content_bk.text((300, 120), "供奉", fill=(239, 211, 114))
+ content_bk.text(
+ (450, 120),
+ f"Lv.{world_data_dict[country]['offerings'][0]['level']}",
+ fill=(255, 255, 255),
+ )
+ elif country in ["稻妻"]:
+ content_bk.text((300, 20), "探索", fill=(239, 211, 114))
+ content_bk.text(
+ (450, 20),
+ f"{world_data_dict[country]['exploration_percentage'] / 10}%",
+ fill=(255, 255, 255),
+ )
+ content_bk.text((300, 85), "声望", fill=(239, 211, 114))
+ content_bk.text(
+ (450, 85),
+ f"Lv.{world_data_dict[country]['level']}",
+ fill=(255, 255, 255),
+ )
+ content_bk.text((300, 150), "神樱", fill=(239, 211, 114))
+ content_bk.text(
+ (450, 150),
+ f"Lv.{world_data_dict[country]['offerings'][0]['level']}",
+ fill=(255, 255, 255),
+ )
+ x.paste(tmp_bk, alpha=True, center_type="center")
+ x.paste(content_bk, alpha=True, center_type="center")
+ x.circle_corner(20)
+ region.paste(x, (0, height), center_type="by_width")
+ height += 267
+ return region
+
+
+def get_char_data_image(
+ char_data_list: List[Dict], char_detailed_dict: dict
+) -> "BuildImage, int":
+ """
+ 画出角色列表
+ :param char_data_list: 角色列表
+ :param char_detailed_dict: 角色武器
+ """
+ x = 420 if char_detailed_dict else 350
+ _h = x * int(
+ len(char_data_list) / 7
+ if len(char_data_list) % 7 == 0
+ else len(char_data_list) / 7 + 1
+ )
+ region = BuildImage(
+ 1600,
+ _h,
+ color="#F9F6F2",
+ )
+ width = 120
+ height = 0
+ idx = 0
+ for char in char_data_list:
+ if width + 230 > 1550:
+ width = 120
+ height += x
+ idx += 1
+ char_img = image_path / "chars" / f'{char["name"]}.png'
+ char_bk = BuildImage(
+ 270,
+ 500 if char_detailed_dict else 400,
+ background=image_path / "element.png",
+ font="HYWenHei-85W.ttf",
+ font_size=35,
+ )
+ char_img = BuildImage(0, 0, background=char_img)
+ actived_constellation_num = BuildImage(
+ 0,
+ 0,
+ plain_text=f"命之座: {char['actived_constellation_num']}层",
+ font="HYWenHei-85W.ttf",
+ font_size=25,
+ color=(255, 255, 255, 0),
+ )
+ level = BuildImage(
+ 0,
+ 0,
+ plain_text=f"Lv.{char['level']}",
+ font="HYWenHei-85W.ttf",
+ font_size=30,
+ color=(255, 255, 255, 0),
+ font_color=(21, 167, 89),
+ )
+ love_log = BuildImage(
+ 0,
+ 0,
+ plain_text="♥",
+ font="HWZhongSong.ttf",
+ font_size=40,
+ color=(255, 255, 255, 0),
+ font_color=(232, 31, 168),
+ )
+ fetter = BuildImage(
+ 0,
+ 0,
+ plain_text=f'{char["fetter"]}',
+ font="HYWenHei-85W.ttf",
+ font_size=30,
+ color=(255, 255, 255, 0),
+ font_color=(232, 31, 168),
+ )
+ if char_detailed_dict.get(char["name"]):
+ weapon = BuildImage(
+ 100,
+ 100,
+ background=image_path
+ / "weapons"
+ / f'{char_detailed_dict[char["name"]]["weapon"]}.png',
+ )
+ weapon_name = BuildImage(
+ 0,
+ 0,
+ plain_text=f"{char_detailed_dict[char['name']]['weapon']}",
+ font="HYWenHei-85W.ttf",
+ font_size=25,
+ color=(255, 255, 255, 0),
+ )
+ weapon_affix_level = BuildImage(
+ 0,
+ 0,
+ plain_text=f"精炼: {char_detailed_dict[char['name']]['affix_level']}",
+ font="HYWenHei-85W.ttf",
+ font_size=20,
+ color=(255, 255, 255, 0),
+ )
+ weapon_level = BuildImage(
+ 0,
+ 0,
+ plain_text=f"Lv.{char_detailed_dict[char['name']]['level']}",
+ font="HYWenHei-85W.ttf",
+ font_size=25,
+ color=(255, 255, 255, 0),
+ font_color=(21, 167, 89),
+ )
+ char_bk.paste(weapon, (20, 380), True)
+ char_bk.paste(
+ weapon_name,
+ (100 + int((char_bk.w - 22 - weapon.w - weapon_name.w) / 2 - 10), 390),
+ True,
+ )
+ char_bk.paste(
+ weapon_affix_level,
+ (
+ (
+ 100
+ + int(
+ (char_bk.w - 10 - weapon.w - weapon_affix_level.w) / 2 - 10
+ ),
+ 420,
+ )
+ ),
+ True,
+ )
+ char_bk.paste(
+ weapon_level,
+ (
+ (
+ 100
+ + int((char_bk.w - 10 - weapon.w - weapon_level.w) / 2 - 10),
+ 450,
+ )
+ ),
+ True,
+ )
+ char_bk.paste(char_img, (0, 5), alpha=True, center_type="by_width")
+ char_bk.text((0, 270), char["name"], center_type="by_width")
+ char_bk.paste(actived_constellation_num, (0, 310), True, "by_width")
+ char_bk.paste(level, (60, 340), True)
+ char_bk.paste(love_log, (155, 330), True)
+ char_bk.paste(fetter, (180, 340), True)
+ char_bk.resize(0.8)
+ region.paste(char_bk, (width, height), True)
+ width += 230
+ return region, _h
+
+
+async def init_image(char_data_list: List[Dict], char_detailed_dict: dict):
+ """
+ 下载头像
+ :param char_data_list: 角色列表
+ :param char_detailed_dict: 角色武器
+ """
+ for char in char_data_list:
+ file = image_path / "chars" / f'{char["name"]}.png'
+ file.parent.mkdir(parents=True, exist_ok=True)
+ if not file.exists():
+ await AsyncHttpx.download_file(char["image"], file)
+ for char in char_detailed_dict.keys():
+ file = image_path / "weapons" / f'{char_detailed_dict[char]["weapon"]}.png'
+ file.parent.mkdir(parents=True, exist_ok=True)
+ if not file.exists():
+ await AsyncHttpx.download_file(
+ char_detailed_dict[char]["weapon_image"], file
+ )
diff --git a/plugins/genshin/query_user/reset_today_query_user_data/__init__.py b/plugins/genshin/query_user/reset_today_query_user_data/__init__.py
new file mode 100644
index 00000000..0c2c24d0
--- /dev/null
+++ b/plugins/genshin/query_user/reset_today_query_user_data/__init__.py
@@ -0,0 +1,17 @@
+from utils.utils import scheduler
+from ..models import Genshin
+from services.log import logger
+
+
+@scheduler.scheduled_job(
+ "cron",
+ hour=0,
+ minute=1,
+)
+async def _():
+ try:
+ await Genshin.reset_today_query_uid()
+ logger.warning(f"重置原神查询记录成功..")
+ except Exception as e:
+ logger.error(f"重置原神查询记录失败. {type(e)}:{e}")
+
diff --git a/plugins/genshin/query_user/utils/__init__.py b/plugins/genshin/query_user/utils/__init__.py
new file mode 100644
index 00000000..7cbd9ded
--- /dev/null
+++ b/plugins/genshin/query_user/utils/__init__.py
@@ -0,0 +1,35 @@
+from configs.config import Config
+import json
+import time
+import random
+import hashlib
+
+
+def _md5(text):
+ md5 = hashlib.md5()
+ md5.update(text.encode())
+ return md5.hexdigest()
+
+
+def get_ds(q: str = "", b: dict = None):
+ if b:
+ br = json.dumps(b)
+ else:
+ br = ""
+ s = Config.get_config("genshin", "salt")
+ t = str(int(time.time()))
+ r = str(random.randint(100000, 200000))
+ c = _md5("salt=" + s + "&t=" + t + "&r=" + r + "&b=" + br + "&q=" + q)
+ return t + "," + r + "," + c
+
+
+element_mastery = {
+ "anemo": "风",
+ "pyro": "火",
+ "geo": "岩",
+ "electro": "雷",
+ "cryo": "冰",
+ "hydro": "水",
+ "dendro": "草",
+ "none": "无",
+}
diff --git a/plugins/gold_redbag/__init__.py b/plugins/gold_redbag/__init__.py
index 2ffae551..c11b7437 100755
--- a/plugins/gold_redbag/__init__.py
+++ b/plugins/gold_redbag/__init__.py
@@ -115,6 +115,7 @@ async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
redbag_data[event.group_id]["amount"]
- redbag_data[event.group_id]["open_amount"]
)
+ await return_gold(event.user_id, event.group_id, amount)
await gold_redbag.send(
f'{redbag_data[event.group_id]["nickname"]}的红包过时未开完,退还{amount}金币...'
)
@@ -137,11 +138,11 @@ async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
else:
amount = msg[0]
num = msg[1]
+ if not is_number(num) or int(num) < 1:
+ await gold_redbag.finish("红包个数给我输正确啊!", at_sender=True)
flag, amount = await check_gold(event.user_id, event.group_id, amount)
if not flag:
await gold_redbag.finish(amount, at_sender=True)
- if not is_number(num) or int(num) < 1:
- await gold_redbag.finish("红包个数给我输正确啊!", at_sender=True)
group_member_num = (await bot.get_group_info(group_id=event.group_id))['member_count']
num = int(num)
if num > group_member_num:
diff --git a/plugins/gold_redbag/data_source.py b/plugins/gold_redbag/data_source.py
index 419cfe3b..06f96980 100755
--- a/plugins/gold_redbag/data_source.py
+++ b/plugins/gold_redbag/data_source.py
@@ -1,6 +1,6 @@
from models.bag_user import BagUser
from utils.utils import is_number, get_user_avatar
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from configs.path_config import IMAGE_PATH
from .model import RedbagUser
import random
@@ -44,8 +44,8 @@ async def open_redbag(user_id: int, group_id: int, redbag_data: dict):
# 随机红包图片
async def generate_send_redbag_pic(user_id: int, msg: str = '恭喜发财 大吉大利'):
random_redbag = random.choice(os.listdir(f"{IMAGE_PATH}/prts/redbag_2"))
- redbag = CreateImg(0, 0, font_size=38, background=f'{IMAGE_PATH}/prts/redbag_2/{random_redbag}')
- ava = CreateImg(65, 65, background=BytesIO(await get_user_avatar(user_id)))
+ redbag = BuildImage(0, 0, font_size=38, background=f'{IMAGE_PATH}/prts/redbag_2/{random_redbag}')
+ ava = BuildImage(65, 65, background=BytesIO(await get_user_avatar(user_id)))
await asyncio.get_event_loop().run_in_executor(None, ava.circle)
redbag.text((int((redbag.size[0] - redbag.getsize(msg)[0]) / 2), 210), msg, (240, 218, 164))
redbag.paste(ava, (int((redbag.size[0] - ava.size[0])/2), 130), True)
@@ -61,19 +61,19 @@ async def generate_open_redbag_pic(user_id: int, send_user_nickname: str, amount
async def _generate_open_redbag_pic(user_id: int, send_user_nickname: str, amount: int, text: str):
send_user_nickname += '的红包'
amount = str(amount)
- head = CreateImg(1000, 980, font_size=30, background=f'{IMAGE_PATH}/prts/redbag_12.png')
- size = CreateImg(0, 0, font_size=50).getsize(send_user_nickname)
+ head = BuildImage(1000, 980, font_size=30, background=f'{IMAGE_PATH}/prts/redbag_12.png')
+ size = BuildImage(0, 0, font_size=50).getsize(send_user_nickname)
# QQ头像
- ava_bk = CreateImg(100 + size[0], 66, color='white', font_size=50)
- ava = CreateImg(66, 66, background=BytesIO(await get_user_avatar(user_id)))
+ ava_bk = BuildImage(100 + size[0], 66, color='white', font_size=50)
+ ava = BuildImage(66, 66, background=BytesIO(await get_user_avatar(user_id)))
ava_bk.paste(ava)
ava_bk.text((100, 7), send_user_nickname)
# ava_bk.show()
ava_bk_w, ava_bk_h = ava_bk.size
head.paste(ava_bk, (int((1000 - ava_bk_w) / 2), 300))
# 金额
- size = CreateImg(0, 0, font_size=150).getsize(amount)
- price = CreateImg(size[0], size[1], font_size=150)
+ size = BuildImage(0, 0, font_size=150).getsize(amount)
+ price = BuildImage(size[0], size[1], font_size=150)
price.text((0, 0), amount, fill=(209, 171, 108))
# 金币中文
head.paste(price, (int((1000 - size[0]) / 2) - 50, 460))
diff --git a/plugins/image_management/__init__.py b/plugins/image_management/__init__.py
index 81121c6e..9a4464db 100755
--- a/plugins/image_management/__init__.py
+++ b/plugins/image_management/__init__.py
@@ -1,4 +1,6 @@
from configs.config import Config
+from configs.path_config import IMAGE_PATH
+from pathlib import Path
import nonebot
@@ -44,5 +46,16 @@ Config.add_plugin_config(
default_value=6,
)
+Config.add_plugin_config(
+ "image_management",
+ "SHOW_ID",
+ True,
+ help_="是否消息显示图片下标id",
+ default_value=True
+)
+
+
+(Path(IMAGE_PATH) / "image_management").mkdir(parents=True, exist_ok=True)
+
nonebot.load_plugins("plugins/image_management")
diff --git a/plugins/image_management/delete_image/__init__.py b/plugins/image_management/delete_image/__init__.py
index 45b08da3..c4e314fa 100755
--- a/plugins/image_management/delete_image/__init__.py
+++ b/plugins/image_management/delete_image/__init__.py
@@ -31,12 +31,17 @@ __plugin_settings__ = {
delete_img = on_command("删除图片", priority=5, rule=to_me(), block=True)
+_path = Path(IMAGE_PATH) / "image_management"
+
+
@delete_img.args_parser
async def parse(bot: Bot, event: MessageEvent, state: T_State):
if get_message_text(event.json()) in ["取消", "算了"]:
await delete_img.finish("已取消操作..", at_sender=True)
if state["_current_key"] in ["path"]:
- if get_message_text(event.json()) not in Config.get_config("image_management", "IMAGE_DIR_LIST"):
+ if get_message_text(event.json()) not in Config.get_config(
+ "image_management", "IMAGE_DIR_LIST"
+ ):
await delete_img.reject("此目录不正确,请重新输入目录!")
state[state["_current_key"]] = get_message_text(event.json())
if state["_current_key"] == "id":
@@ -52,7 +57,11 @@ async def _(bot: Bot, event: MessageEvent, state: T_State):
args = raw_arg.split(" ")
if args[0] in ["帮助"]:
await delete_img.finish(__plugin_usage__)
- if len(args) >= 2 and args[0] in Config.get_config("image_management", "IMAGE_DIR_LIST") and is_number(args[1]):
+ if (
+ len(args) >= 2
+ and args[0] in Config.get_config("image_management", "IMAGE_DIR_LIST")
+ and is_number(args[1])
+ ):
state["path"] = args[0]
state["id"] = args[1]
@@ -60,18 +69,18 @@ async def _(bot: Bot, event: MessageEvent, state: T_State):
@delete_img.got("path", prompt="请输入要删除的目标图库?")
@delete_img.got("id", prompt="请输入要删除的图片id?")
async def arg_handle(bot: Bot, event: MessageEvent, state: T_State):
- path = cn2py(state["path"])
img_id = state["id"]
- # path = IMAGE_PATH + path
- path = Path(IMAGE_PATH) / path
- temp = Path(IMAGE_PATH) / "temp"
+ path = _path / cn2py(state["path"])
+ if not path.exists() and (path.parent.parent / cn2py(state["path"])).exists():
+ path = path.parent.parent / cn2py(state["path"])
+ temp = Path(TEMP_PATH)
max_id = len(os.listdir(path)) - 1
if int(img_id) > max_id or int(img_id) < 0:
await delete_img.finish(f"Id超过上下限,上限:{max_id}", at_sender=True)
try:
- if os.path.exists(temp / "delete.jpg"):
- os.remove(temp / "delete.jpg")
- logger.info("删除图片 delete.jpg 成功")
+ if (temp / "delete.jpg").exists():
+ (temp / "delete.jpg").unlink()
+ logger.info(f"删除{cn2py(state['path'])}图片 {img_id}.jpg 成功")
except Exception as e:
logger.warning(f"删除图片 delete.jpg 失败 e{e}")
try:
diff --git a/plugins/image_management/move_image/__init__.py b/plugins/image_management/move_image/__init__.py
index 8522f213..1ae25012 100755
--- a/plugins/image_management/move_image/__init__.py
+++ b/plugins/image_management/move_image/__init__.py
@@ -31,6 +31,9 @@ __plugin_settings__ = {
move_img = on_command("移动图片", priority=5, rule=to_me(), block=True)
+_path = Path(IMAGE_PATH) / "image_management"
+
+
@move_img.args_parser
async def parse(bot: Bot, event: MessageEvent, state: T_State):
if str(event.get_message()) in ["取消", "算了"]:
@@ -72,8 +75,20 @@ async def _(bot: Bot, event: MessageEvent, state: T_State):
@move_img.got("id", prompt="要移动的图片id是?")
async def _(bot: Bot, event: MessageEvent, state: T_State):
img_id = state["id"]
- source_path = Path(IMAGE_PATH) / cn2py(state["source_path"])
- destination_path = Path(IMAGE_PATH) / cn2py(state["destination_path"])
+ source_path = _path / cn2py(state["source_path"])
+ destination_path = _path / cn2py(state["destination_path"])
+ if (
+ not source_path.exists()
+ and (source_path.parent.parent / cn2py(state["source_path"])).exists()
+ ):
+ source_path = source_path.parent.parent / cn2py(state["source_path"])
+ if (
+ not destination_path.exists()
+ and (destination_path.parent.parent / cn2py(state["destination_path"])).exists()
+ ):
+ destination_path = destination_path.parent.parent / cn2py(
+ state["destination_path"]
+ )
destination_path.mkdir(parents=True, exist_ok=True)
max_id = len(os.listdir(source_path)) - 1
des_max_id = len(os.listdir(destination_path))
diff --git a/plugins/image_management/send_image/__init__.py b/plugins/image_management/send_image/__init__.py
index 04a9fa90..e352a717 100755
--- a/plugins/image_management/send_image/__init__.py
+++ b/plugins/image_management/send_image/__init__.py
@@ -6,6 +6,7 @@ from services.log import logger
from nonebot.typing import T_State
from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent
from utils.utils import FreqLimiter, cn2py
+from pathlib import Path
from configs.config import Config
from utils.manager import group_manager, withdraw_message_manager
import random
@@ -16,7 +17,7 @@ try:
except ModuleNotFoundError:
import json
-__zx_plugin_name__ = "发送本地图库图片"
+__zx_plugin_name__ = "本地图库"
__plugin_usage__ = f"""
usage:
发送指定图库下的随机或指定id图片
@@ -37,49 +38,61 @@ __plugin_settings__ = {
"cmd": ["发送图片"] + Config.get_config("image_management", "IMAGE_DIR_LIST"),
}
__plugin_task__ = {"pa": "丢人爬"}
-__plugin_resources__ = {
- "pa": IMAGE_PATH
-}
+__plugin_resources__ = {"pa": IMAGE_PATH}
+
+Config.add_plugin_config(
+ "_task",
+ "DEFAULT_PA",
+ True,
+ help_="被动 爬 进群默认开关状态",
+ default_value=True,
+)
_flmt = FreqLimiter(1)
cmd = set(Config.get_config("image_management", "IMAGE_DIR_LIST"))
-# print(cmd)
-
send_img = on_command("img", aliases=cmd, priority=5, block=True)
pa = on_keyword({"丢人爬", "爪巴"}, priority=5, block=True)
pa_reg = on_regex("^爬$", priority=5, block=True)
-search_url = "https://api.fantasyzone.cc/tu/search.php"
+
+_path = Path(IMAGE_PATH) / "image_management"
@send_img.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
img_id = get_message_text(event.json())
- path = cn2py(state["_prefix"]["raw_command"]) + "/"
+ path = _path / cn2py(state["_prefix"]["raw_command"])
if state["_prefix"]["raw_command"] in Config.get_config(
"image_management", "IMAGE_DIR_LIST"
):
- if not os.path.exists(f"{IMAGE_PATH}/{path}/"):
- os.mkdir(f"{IMAGE_PATH}/{path}/")
- length = len(os.listdir(IMAGE_PATH + path))
+ if not path.exists() and (path.parent.parent / cn2py(state["_prefix"]["raw_command"])).exists():
+ path = Path(IMAGE_PATH) / cn2py(state["_prefix"]["raw_command"])
+ else:
+ path.mkdir(parents=True, exist_ok=True)
+ length = len(os.listdir(path))
if length == 0:
- logger.warning(f"图库 {path} 为空,调用取消!")
+ logger.warning(f'图库 {cn2py(state["_prefix"]["raw_command"])} 为空,调用取消!')
await send_img.finish("该图库中没有图片噢")
index = img_id if img_id else str(random.randint(0, length - 1))
if not is_number(index):
return
if int(index) > length - 1 or int(index) < 0:
await send_img.finish(f"超过当前上下限!({length - 1})")
- result = image(f"{index}.jpg", path)
+ result = image(path / f"{index}.jpg")
if result:
logger.info(
f"(USER {event.user_id}, GROUP "
- f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) 发送{path}:"
+ f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) "
+ f"发送{cn2py(state['_prefix']['raw_command'])}:"
+ result
)
- msg_id = await send_img.send(f"id:{index}" + result)
+ msg_id = await send_img.send(
+ f"id:{index}" + result
+ if Config.get_config("image_management", "SHOW_ID")
+ else "" + result
+ )
withdraw_message_manager.withdraw_message(
event,
msg_id,
@@ -88,7 +101,8 @@ async def _(bot: Bot, event: MessageEvent, state: T_State):
else:
logger.info(
f"(USER {event.user_id}, GROUP "
- f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) 发送 {path} 失败"
+ f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) "
+ f"发送 {cn2py(state['_prefix']['raw_command'])} 失败"
)
await send_img.finish(f"不想给你看Ov|")
diff --git a/plugins/image_management/upload_image/data_source.py b/plugins/image_management/upload_image/data_source.py
index 3ce0a399..1ac655ab 100755
--- a/plugins/image_management/upload_image/data_source.py
+++ b/plugins/image_management/upload_image/data_source.py
@@ -8,11 +8,16 @@ from utils.http_utils import AsyncHttpx
import os
+_path = Path(IMAGE_PATH) / "image_management"
+
+
async def upload_image_to_local(
- img_list: List[str], path: str, user_id: int, group_id: int = 0
+ img_list: List[str], path_: str, user_id: int, group_id: int = 0
) -> str:
- _path = path
- path = Path(IMAGE_PATH) / cn2py(path)
+ _path_name = path_
+ path = _path / cn2py(path_)
+ if not path.exists() and (path.parent.parent / cn2py(path_)).exists():
+ path = path.parent.parent / cn2py(path_)
path.mkdir(parents=True, exist_ok=True)
img_id = len(os.listdir(path))
failed_list = []
@@ -28,15 +33,15 @@ async def upload_image_to_local(
failed_result += str(img) + "\n"
logger.info(
f"USER {user_id} GROUP {group_id}"
- f" 上传图片至 {_path} 共 {len(img_list)} 张,失败 {len(failed_list)} 张,id={success_id[:-1]}"
+ f" 上传图片至 {_path_name} 共 {len(img_list)} 张,失败 {len(failed_list)} 张,id={success_id[:-1]}"
)
if failed_list:
return (
- f"这次一共为 {_path}库 添加了 {len(img_list) - len(failed_list)} 张图片\n"
+ f"这次一共为 {_path_name}库 添加了 {len(img_list) - len(failed_list)} 张图片\n"
f"依次的Id为:{success_id[:-1]}\n上传失败:{failed_result[:-1]}\n{NICKNAME}感谢您对图库的扩充!WW"
)
else:
return (
- f"这次一共为 {_path}库 添加了 {len(img_list)} 张图片\n依次的Id为:"
+ f"这次一共为 {_path_name}库 添加了 {len(img_list)} 张图片\n依次的Id为:"
f"{success_id[:-1]}\n{NICKNAME}感谢您对图库的扩充!WW"
)
diff --git a/plugins/luxun/__init__.py b/plugins/luxun/__init__.py
index d3e58e23..f988901b 100755
--- a/plugins/luxun/__init__.py
+++ b/plugins/luxun/__init__.py
@@ -5,7 +5,7 @@ from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent
from utils.message_builder import image
from services.log import logger
from utils.utils import get_message_text
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
__zx_plugin_name__ = "鲁迅说"
__plugin_usage__ = """
@@ -31,7 +31,7 @@ __plugin_block_limit__ = {
luxun = on_command("鲁迅说过", aliases={"鲁迅说"}, priority=5, block=True)
-luxun_author = CreateImg(0, 0, plain_text="--鲁迅", font_size=30, font='msyh.ttf', font_color=(255, 255, 255))
+luxun_author = BuildImage(0, 0, plain_text="--鲁迅", font_size=30, font='msyh.ttf', font_color=(255, 255, 255))
@luxun.handle()
@@ -46,7 +46,7 @@ async def handle_event(bot: Bot, event: MessageEvent, state: T_State):
content = state["content"].strip()
if content.startswith(",") or content.startswith(","):
content = content[1:]
- A = CreateImg(0, 0, font_size=37, background=f'{IMAGE_PATH}/other/luxun.jpg', font='msyh.ttf')
+ A = BuildImage(0, 0, font_size=37, background=f'{IMAGE_PATH}/other/luxun.jpg', font='msyh.ttf')
x = ""
if len(content) > 40:
await luxun.finish('太长了,鲁迅说不完...')
diff --git a/plugins/one_friend/__init__.py b/plugins/one_friend/__init__.py
index f1526bef..67dd0937 100755
--- a/plugins/one_friend/__init__.py
+++ b/plugins/one_friend/__init__.py
@@ -6,7 +6,7 @@ from nonebot.adapters.cqhttp import Bot, GroupMessageEvent
from utils.utils import get_message_text, get_message_at, get_user_avatar
from utils.message_builder import image
import re
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
__zx_plugin_name__ = "我有一个朋友"
__plugin_usage__ = """
@@ -60,13 +60,13 @@ async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
msg = msg.replace("他", "我").replace("她", "我").replace("它", "我")
x = await get_user_avatar(qq)
if x:
- ava = CreateImg(200, 100, background=BytesIO(x))
+ ava = BuildImage(200, 100, background=BytesIO(x))
else:
- ava = CreateImg(200, 100, color=(0, 0, 0))
+ ava = BuildImage(200, 100, color=(0, 0, 0))
ava.circle()
- text = CreateImg(300, 30, font_size=30)
+ text = BuildImage(300, 30, font_size=30)
text.text((0, 0), user_name)
- A = CreateImg(700, 150, font_size=25, color="white")
+ A = BuildImage(700, 150, font_size=25, color="white")
A.paste(ava, (30, 25), True)
A.paste(text, (150, 38))
A.text((150, 85), msg, (125, 125, 125))
diff --git a/plugins/open_cases/__init__.py b/plugins/open_cases/__init__.py
index 155881d4..6639fe6e 100755
--- a/plugins/open_cases/__init__.py
+++ b/plugins/open_cases/__init__.py
@@ -18,6 +18,7 @@ from .open_cases_c import (
open_shilian_case,
)
from .utils import util_get_buff_price, util_get_buff_img, update_count_daily
+from configs.config import Config
__zx_plugin_name__ = "开箱"
__plugin_usage__ = """
@@ -84,6 +85,14 @@ __plugin_configs__ = {
"BUFF_PROXY": {"value": None, "help": "使用代理访问BUFF"},
}
+Config.add_plugin_config(
+ "_task",
+ "DEFAULT_OPEN_CASE_RESET_REMIND",
+ True,
+ help_="被动 每日开箱重置提醒 进群默认开关状态",
+ default_value=True,
+)
+
cases_name = ["狂牙大行动", "突围大行动", "命悬一线", "裂空", "光谱"]
cases_matcher_group = MatcherGroup(priority=5, permission=GROUP, block=True)
diff --git a/plugins/open_cases/open_cases_c.py b/plugins/open_cases/open_cases_c.py
index b8ff2fe6..0322dc23 100755
--- a/plugins/open_cases/open_cases_c.py
+++ b/plugins/open_cases/open_cases_c.py
@@ -10,7 +10,7 @@ import random
from .utils import get_price
from .models.buff_prices import BuffPrice
from PIL import Image
-from utils.image_utils import alpha2white_pil, CreateImg
+from utils.image_utils import alpha2white_pil, BuildImage
from configs.path_config import IMAGE_PATH
import asyncio
from utils.utils import cn2py
@@ -239,7 +239,7 @@ async def open_shilian_case(user_qq: int, group: int, case_name: str, num: int =
):
skin_name += "".join(i)
# img = image(skin_name, "cases/" + case, "png")
- wImg = CreateImg(200, 270, 200, 200)
+ wImg = BuildImage(200, 270, 200, 200)
wImg.paste(
alpha2white_pil(
Image.open(IMAGE_PATH + f"cases/{case}/{skin_name}.png").resize(
@@ -263,7 +263,7 @@ async def open_shilian_case(user_qq: int, group: int, case_name: str, num: int =
logger.warning(
f"USER {user_qq} GROUP {group} 开启{case_name}武器箱 {num} 次, 价格:{uplist[10]}, 数据更新失败"
)
- # markImg = CreateImg(1000, h, 200, 270)
+ # markImg = BuildImage(1000, h, 200, 270)
# for img in img_list:
# markImg.paste(img)
markImg = await asyncio.get_event_loop().run_in_executor(
@@ -284,7 +284,7 @@ async def open_shilian_case(user_qq: int, group: int, case_name: str, num: int =
def paste_markImg(h: int, img_list: list):
- markImg = CreateImg(1000, h, 200, 270)
+ markImg = BuildImage(1000, h, 200, 270)
for img in img_list:
markImg.paste(img)
return markImg
@@ -399,7 +399,7 @@ async def my_knifes_name(user_id: int, group: int):
def _pst_my_knife(w, h, knifes_list):
- A = CreateImg(w, h, 540, 600)
+ A = BuildImage(w, h, 540, 600)
for knife in knifes_list:
case = knife.split("||")[0]
knife = knife.split("||")[1]
@@ -415,7 +415,7 @@ def _pst_my_knife(w, h, knifes_list):
style=pypinyin.NORMAL,
):
skin_name += "".join(i)
- knife_img = CreateImg(470, 600, 470, 470, font_size=20)
+ knife_img = BuildImage(470, 600, 470, 470, font_size=20)
knife_img.paste(
alpha2white_pil(
Image.open(IMAGE_PATH + f"cases/{case}/{skin_name}.png").resize(
diff --git a/plugins/open_cases/utils.py b/plugins/open_cases/utils.py
index cb5b4d71..77313d51 100755
--- a/plugins/open_cases/utils.py
+++ b/plugins/open_cases/utils.py
@@ -8,10 +8,10 @@ import os
from services.log import logger
from utils.utils import get_bot
from asyncio.exceptions import TimeoutError
-import pypinyin
from nonebot.adapters.cqhttp.exception import ActionFailed
from configs.config import Config
from utils.manager import group_manager
+from .config import *
url = "https://buff.163.com/api/market/goods"
# proxies = 'http://49.75.59.242:3128'
diff --git a/plugins/parse_bilibili_json.py b/plugins/parse_bilibili_json.py
index 82172717..4811f63a 100755
--- a/plugins/parse_bilibili_json.py
+++ b/plugins/parse_bilibili_json.py
@@ -8,10 +8,11 @@ from nonebot.adapters.cqhttp.permission import GROUP
from bilibili_api import video
from utils.message_builder import image
from nonebot.adapters.cqhttp.exception import ActionFailed
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from utils.browser import get_browser
from configs.path_config import IMAGE_PATH
from utils.http_utils import AsyncHttpx
+from configs.config import Config
import asyncio
import time
from bilibili_api import settings
@@ -25,10 +26,18 @@ usage:
B站转发解析,解析b站分享信息,支持bv,bilibili链接,b站手机端转发卡片,cv,b23.tv,且5分钟内不解析相同url
""".strip()
__plugin_des__ = "B站转发解析"
-__plugin_type__ = ("被动相关",)
+__plugin_type__ = ("其他",)
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_task__ = {"bilibili_parse": "b站转发解析"}
+Config.add_plugin_config(
+ "_task",
+ "DEFAULT_BILIBILI_PARSE",
+ True,
+ help_="被动 B站转发解析 进群默认开关状态",
+ default_value=True,
+)
+
if get_local_proxy():
settings.proxy = get_local_proxy()
@@ -154,5 +163,5 @@ async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
def resize(path: str):
- A = CreateImg(0, 0, background=path, ratio=0.5)
+ A = BuildImage(0, 0, background=path, ratio=0.5)
A.save(path)
diff --git a/plugins/pid_search.py b/plugins/pid_search.py
index 43bf19b8..251a4f53 100755
--- a/plugins/pid_search.py
+++ b/plugins/pid_search.py
@@ -69,6 +69,8 @@ async def _(bot: Bot, event: MessageEvent, state: T_State):
data = (await AsyncHttpx.get(url, params=params, timeout=5)).json()
except TimeoutError:
pass
+ except Exception as e:
+ await pid_search.finish(f"发生了一些错误..{type(e)}:{e}")
else:
if not data["width"] and not data["height"]:
await pid_search.finish(f"没有搜索到 PID:{pid} 的图片", at_sender=True)
diff --git a/plugins/pix_gallery/__init__.py b/plugins/pix_gallery/__init__.py
index 8e71f3b2..a846c5f0 100755
--- a/plugins/pix_gallery/__init__.py
+++ b/plugins/pix_gallery/__init__.py
@@ -57,6 +57,14 @@ Config.add_plugin_config(
default_value=10
)
+Config.add_plugin_config(
+ "pix",
+ "SHOW_INFO",
+ True,
+ help_="是否显示图片的基本信息,如PID等",
+ default_value=True
+)
+
nonebot.load_plugins("plugins/pix_gallery")
diff --git a/plugins/pix_gallery/data_source.py b/plugins/pix_gallery/data_source.py
index 37bb3fb8..d6116799 100755
--- a/plugins/pix_gallery/data_source.py
+++ b/plugins/pix_gallery/data_source.py
@@ -5,7 +5,7 @@ from asyncio.exceptions import TimeoutError
from .model.pixiv import Pixiv
from typing import List, Optional
from utils.utils import change_pixiv_image_links
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from utils.http_utils import AsyncHttpx
from services.log import logger
from configs.config import Config
@@ -347,16 +347,16 @@ def gen_keyword_pic(
del img_data["_n_pid"]
del img_data["_n_uid"]
current_width = 0
- A = CreateImg(img_width, 1100)
+ A = BuildImage(img_width, 1100)
for x in list(img_data.keys()):
if img_data[x]["data"]:
- img = CreateImg(img_data[x]["width"] * 200, 1100, 200, 1100, font_size=40)
+ img = BuildImage(img_data[x]["width"] * 200, 1100, 200, 1100, font_size=40)
start_index = 0
end_index = 40
total_index = img_data[x]["width"] * 40
for _ in range(img_data[x]["width"]):
- tmp = CreateImg(198, 1100, font_size=20)
- text_img = CreateImg(198, 100, font_size=50)
+ tmp = BuildImage(198, 1100, font_size=20)
+ text_img = BuildImage(198, 100, font_size=50)
key_str = "\n".join(
[key for key in img_data[x]["data"][start_index:end_index]]
)
@@ -370,7 +370,7 @@ def gen_keyword_pic(
end_index = (
end_index + 40 if end_index + 40 <= total_index else total_index
)
- background_img = CreateImg(200, 1100, color="#FFE4C4")
+ background_img = BuildImage(200, 1100, color="#FFE4C4")
background_img.paste(tmp, (1, 1))
img.paste(background_img)
A.paste(img, (current_width, 0))
diff --git a/plugins/pix_gallery/pix.py b/plugins/pix_gallery/pix.py
index 04c1bfc0..ea283959 100755
--- a/plugins/pix_gallery/pix.py
+++ b/plugins/pix_gallery/pix.py
@@ -143,14 +143,17 @@ async def _(bot: Bot, event: MessageEvent, state: T_State):
uid = img.uid
_img = await get_image(img_url, event.user_id)
if _img:
- msg_id = await pix.send(
- Message(
- f"title:{title}\n"
- f"author:{author}\n"
- f"PID:{pid}\nUID:{uid}\n"
- f"{image(_img, 'temp')}"
+ if Config.get_config("pix", "SHOW_INFO"):
+ msg_id = await pix.send(
+ Message(
+ f"title:{title}\n"
+ f"author:{author}\n"
+ f"PID:{pid}\nUID:{uid}\n"
+ f"{image(_img, 'temp')}"
+ )
)
- )
+ else:
+ msg_id = await pix.send(image(_img, 'temp'))
logger.info(
f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
f" 查看PIX图库PID: {pid}"
diff --git a/plugins/poke/__init__.py b/plugins/poke/__init__.py
index 393981eb..b75606a9 100755
--- a/plugins/poke/__init__.py
+++ b/plugins/poke/__init__.py
@@ -16,7 +16,7 @@ usage:
戳一戳随机掉落语音或美图萝莉图
""".strip()
__plugin_des__ = "戳一戳发送语音美图萝莉图不美哉?"
-__plugin_type__ = ("被动相关",)
+__plugin_type__ = ("其他",)
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
diff --git a/plugins/russian/data_source.py b/plugins/russian/data_source.py
index 35d12623..765cbaca 100755
--- a/plugins/russian/data_source.py
+++ b/plugins/russian/data_source.py
@@ -1,10 +1,10 @@
from .model import RussianUser
from typing import Optional
from utils.data_utils import init_rank
-from utils.image_utils import CreateMat
+from utils.image_utils import BuildMat
-async def rank(group_id: int, itype: str, num: int) -> Optional[CreateMat]:
+async def rank(group_id: int, itype: str, num: int) -> Optional[BuildMat]:
all_users = await RussianUser.get_all_user(group_id)
all_user_id = [user.user_qq for user in all_users]
if itype == 'win_rank':
diff --git a/plugins/send_setu_/send_setu/__init__.py b/plugins/send_setu_/send_setu/__init__.py
index 16b439b4..464626ab 100755
--- a/plugins/send_setu_/send_setu/__init__.py
+++ b/plugins/send_setu_/send_setu/__init__.py
@@ -98,6 +98,11 @@ __plugin_configs__ = {
"value": 10,
"help": "色图下载超时限制(秒)",
"default_value": 10
+ },
+ "SHOW_INFO": {
+ "value": True,
+ "help": "是否显示色图的基本信息,如PID等",
+ "default_value": True
}
}
Config.add_plugin_config(
diff --git a/plugins/send_setu_/send_setu/data_source.py b/plugins/send_setu_/send_setu/data_source.py
index cdc590ae..418813ed 100755
--- a/plugins/send_setu_/send_setu/data_source.py
+++ b/plugins/send_setu_/send_setu/data_source.py
@@ -183,13 +183,15 @@ def gen_message(setu_image: Setu, img_msg: bool = False) -> str:
title = setu_image.title
author = setu_image.author
pid = setu_image.pid
- return (
- f"id:{local_id}\n"
- f"title:{title}\n"
- f"author:{author}\n"
- f"PID:{pid}\n"
- f"{image(f'{local_id}', f'{r18_path if setu_image.is_r18 else path}') if img_msg else ''}"
- )
+ if Config.get_config("send_setu", "SHOW_INFO"):
+ return (
+ f"id:{local_id}\n"
+ f"title:{title}\n"
+ f"author:{author}\n"
+ f"PID:{pid}\n"
+ f"{image(f'{local_id}', f'{r18_path if setu_image.is_r18 else path}') if img_msg else ''}"
+ )
+ return f"{image(f'{local_id}', f'{r18_path if setu_image.is_r18 else path}') if img_msg else ''}"
# 罗翔老师!
diff --git a/plugins/shop/shop_handle/data_source.py b/plugins/shop/shop_handle/data_source.py
index 915839d4..0c4f4ac6 100755
--- a/plugins/shop/shop_handle/data_source.py
+++ b/plugins/shop/shop_handle/data_source.py
@@ -1,5 +1,5 @@
from ..models.goods_info import GoodsInfo
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from utils.utils import is_number
from configs.path_config import IMAGE_PATH
from configs.config import Config
@@ -37,9 +37,9 @@ async def create_shop_help():
w = 1000
h = 400 + len(goods_lst) * 40
h = 1000 if h < 1000 else h
- shop_logo = CreateImg(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png")
- shop = CreateImg(w, h, font_size=20)
- zhenxun_img = CreateImg(525, 581, background=f"{IMAGE_PATH}/zhenxun/toukan_2.png")
+ shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png")
+ shop = BuildImage(w, h, font_size=20)
+ zhenxun_img = BuildImage(525, 581, background=f"{IMAGE_PATH}/zhenxun/toukan_2.png")
shop.paste(zhenxun_img, (780, 100))
shop.paste(shop_logo, (450, 30), True)
shop.text(
diff --git a/plugins/sign_in/__init__.py b/plugins/sign_in/__init__.py
index cece39ab..5b578616 100755
--- a/plugins/sign_in/__init__.py
+++ b/plugins/sign_in/__init__.py
@@ -3,6 +3,7 @@ from .group_user_checkin import (
group_user_check,
group_impression_rank,
impression_rank,
+ check_in_all
)
from nonebot.typing import T_State
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent
@@ -45,26 +46,18 @@ __plugin_settings__ = {
}
__plugin_cd_limit__ = {}
__plugin_configs__ = {
- "MAX_SIGN_GOLD": {
- "value": 200,
- "help": "签到好感度加成额外获得的最大金币数",
- "default_value": 200
- },
- "SIGN_CARD1_PROB": {
- "value": 0.2,
- "help": "签到好感度双倍加持卡Ⅰ掉落概率",
- "default_value": 0.2
- },
+ "MAX_SIGN_GOLD": {"value": 200, "help": "签到好感度加成额外获得的最大金币数", "default_value": 200},
+ "SIGN_CARD1_PROB": {"value": 0.2, "help": "签到好感度双倍加持卡Ⅰ掉落概率", "default_value": 0.2},
"SIGN_CARD2_PROB": {
"value": 0.09,
"help": "签到好感度双倍加持卡Ⅱ掉落概率",
- "default_value": 0.09
+ "default_value": 0.09,
},
"SIGN_CARD3_PROB": {
"value": 0.05,
"help": "签到好感度双倍加持卡Ⅲ掉落概率",
- "default_value": 0.05
- }
+ "default_value": 0.05,
+ },
}
@@ -98,6 +91,9 @@ async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
await group_user_check_in(nickname, event.user_id, event.group_id),
at_sender=True,
)
+ if get_message_text(event.json()) == "all":
+ await check_in_all(nickname, event.user_id)
+
@my_sign.handle()
@@ -142,12 +138,12 @@ async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
@scheduler.scheduled_job(
- 'interval',
+ "interval",
hours=1,
)
async def _():
try:
clear_sign_data_pic()
- logger.info('清理日常签到图片数据数据完成....')
+ logger.info("清理日常签到图片数据数据完成....")
except Exception as e:
- logger.error(f'清理日常签到图片数据数据失败..{type(e)}: {e}')
+ logger.error(f"清理日常签到图片数据数据失败..{type(e)}: {e}")
diff --git a/plugins/sign_in/group_user_checkin.py b/plugins/sign_in/group_user_checkin.py
index a811843f..12ec7aa0 100755
--- a/plugins/sign_in/group_user_checkin.py
+++ b/plugins/sign_in/group_user_checkin.py
@@ -4,7 +4,7 @@ from models.group_member_info import GroupInfoUser
from models.bag_user import BagUser
from configs.config import NICKNAME
from nonebot.adapters.cqhttp import MessageSegment
-from utils.image_utils import CreateImg, CreateMat
+from utils.image_utils import BuildImage, BuildMat
from services.db_context import db
from .utils import get_card, SIGN_TODAY_CARD_PATH
from typing import Optional
@@ -39,6 +39,26 @@ async def group_user_check_in(
return await _handle_check_in(nickname, user_qq, group, present) # ok
+async def check_in_all(nickname: str, user_qq: int):
+ """
+ 说明:
+ 签到所有群
+ 参数:
+ :param nickname: 昵称
+ :param user_qq: 用户qq
+ """
+ async with db.transaction():
+ present = datetime.now()
+ for u in await SignGroupUser.get_user_all_data(user_qq):
+ group = u.belonging_group
+ if not ((
+ u.checkin_time_last + timedelta(hours=8)
+ ).date() >= present.date() or f"{u}_{group}_sign_{datetime.now().date()}" in os.listdir(
+ SIGN_TODAY_CARD_PATH
+ )):
+ await _handle_check_in(nickname, user_qq, group, present)
+
+
async def _handle_check_in(
nickname: str, user_qq: int, group: int, present: datetime
) -> MessageSegment:
@@ -84,7 +104,7 @@ async def group_user_check(nickname: str, user_qq: int, group: int) -> MessageSe
return await get_card(user, nickname, None, gold, "", is_card_view=True)
-async def group_impression_rank(group: int, num: int) -> Optional[CreateMat]:
+async def group_impression_rank(group: int, num: int) -> Optional[BuildMat]:
user_qq_list, impression_list, _ = await SignGroupUser.get_all_impression(group)
return await init_rank("好感度排行榜", user_qq_list, impression_list, group, num)
@@ -141,9 +161,9 @@ async def _pst(users: list, impressions: list, groups: list):
count = math.ceil(lens / 33)
width = 10
idx = 0
- A = CreateImg(1740, 3300, color="#FFE4C4")
+ A = BuildImage(1740, 3300, color="#FFE4C4")
for _ in range(count):
- col_img = CreateImg(550, 3300, 550, 100, color="#FFE4C4")
+ col_img = BuildImage(550, 3300, 550, 100, color="#FFE4C4")
for _ in range(33 if int(lens / 33) >= 1 else lens % 33 - 1):
idx += 1
if idx > 100:
@@ -164,13 +184,13 @@ async def _pst(users: list, impressions: list, groups: list):
user_name = user_name if len(user_name) < 11 else user_name[:10] + "..."
ava = await get_user_avatar(user)
if ava:
- ava = CreateImg(
+ ava = BuildImage(
50, 50, background=BytesIO(ava)
)
else:
- ava = CreateImg(50, 50, color="white")
+ ava = BuildImage(50, 50, color="white")
ava.circle()
- bk = CreateImg(550, 100, color="#FFE4C4", font_size=30)
+ bk = BuildImage(550, 100, color="#FFE4C4", font_size=30)
font_w, font_h = bk.getsize(f"{idx}")
bk.text((5, int((100 - font_h) / 2)), f"{idx}.")
bk.paste(ava, (55, int((100 - 50) / 2)), True)
@@ -180,7 +200,7 @@ async def _pst(users: list, impressions: list, groups: list):
A.paste(col_img, (width, 0))
lens -= 33
width += 580
- W = CreateImg(1740, 3700, color="#FFE4C4", font_size=130)
+ W = BuildImage(1740, 3700, color="#FFE4C4", font_size=130)
W.paste(A, (0, 260))
font_w, font_h = W.getsize(f"{NICKNAME}的好感度总榜")
W.text((int((1740 - font_w) / 2), int((260 - font_h) / 2)), f"{NICKNAME}的好感度总榜")
diff --git a/plugins/sign_in/utils.py b/plugins/sign_in/utils.py
index 4f5a8fb0..25975713 100755
--- a/plugins/sign_in/utils.py
+++ b/plugins/sign_in/utils.py
@@ -12,7 +12,7 @@ from models.sign_group_user import SignGroupUser
from models.group_member_info import GroupInfoUser
from nonebot.adapters.cqhttp import MessageSegment
from utils.utils import get_user_avatar
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from utils.message_builder import image
from configs.config import NICKNAME
from pathlib import Path
@@ -112,18 +112,18 @@ def _generate_card(
is_double: bool = False,
is_card_view: bool = False,
) -> MessageSegment:
- ava_bk = CreateImg(140, 140, is_alpha=True)
- ava_border = CreateImg(
+ ava_bk = BuildImage(140, 140, is_alpha=True)
+ ava_border = BuildImage(
140,
140,
background=SIGN_BORDER_PATH / "ava_border_01.png",
)
- ava = CreateImg(102, 102, background=ava_bytes)
+ ava = BuildImage(102, 102, background=ava_bytes)
ava.circle()
ava_bk.paste(ava, center_type="center")
ava_bk.paste(ava_border, alpha=True, center_type="center")
- info_img = CreateImg(250, 150, color=(255, 255, 255, 0), font_size=15)
+ info_img = BuildImage(250, 150, color=(255, 255, 255, 0), font_size=15)
level, next_impression, previous_impression = get_level_and_next_impression(
user.impression
)
@@ -131,8 +131,8 @@ def _generate_card(
info_img.text((0, 20), f"· {NICKNAME}对你的态度:{level2attitude[level]}")
info_img.text((0, 40), f"· 距离升级还差 {next_impression - user.impression:.2f} 好感度")
- bar_bk = CreateImg(220, 20, background=SIGN_RESOURCE_PATH / "bar_white.png")
- bar = CreateImg(220, 20, background=SIGN_RESOURCE_PATH / "bar.png")
+ bar_bk = BuildImage(220, 20, background=SIGN_RESOURCE_PATH / "bar_white.png")
+ bar = BuildImage(220, 20, background=SIGN_RESOURCE_PATH / "bar.png")
bar_bk.paste(
bar,
(
@@ -150,7 +150,7 @@ def _generate_card(
font_size = 30
if "好感度双倍加持卡" in gift:
font_size = 20
- gift_border = CreateImg(
+ gift_border = BuildImage(
270,
100,
background=SIGN_BORDER_PATH / "gift_border_02.png",
@@ -158,20 +158,20 @@ def _generate_card(
)
gift_border.text((0, 0), gift, center_type="center")
- bk = CreateImg(
+ bk = BuildImage(
876,
424,
background=SIGN_BACKGROUND_PATH
/ random.choice(os.listdir(SIGN_BACKGROUND_PATH)),
font_size=25,
)
- A = CreateImg(876, 274, background=SIGN_RESOURCE_PATH / "white.png")
- line = CreateImg(2, 180, color="black")
+ A = BuildImage(876, 274, background=SIGN_RESOURCE_PATH / "white.png")
+ line = BuildImage(2, 180, color="black")
A.transparent(2)
A.paste(ava_bk, (25, 80), True)
A.paste(line, (200, 70))
- nickname_img = CreateImg(
+ nickname_img = BuildImage(
0,
0,
plain_text=nickname,
@@ -184,7 +184,7 @@ def _generate_card(
uid = uid[:4] + " " + uid[4:8] + " " + uid[8:]
else:
uid = "XXXX XXXX XXXX"
- uid_img = CreateImg(
+ uid_img = BuildImage(
0,
0,
plain_text=f"UID: {uid}",
@@ -192,7 +192,7 @@ def _generate_card(
font_size=30,
font_color=(255, 255, 255),
)
- sign_day_img = CreateImg(
+ sign_day_img = BuildImage(
0,
0,
plain_text=f"{user.checkin_count}",
@@ -200,17 +200,17 @@ def _generate_card(
font_size=40,
font_color=(211, 64, 33),
)
- lik_text1_img = CreateImg(
+ lik_text1_img = BuildImage(
0, 0, plain_text="当前", color=(255, 255, 255, 0), font_size=20
)
- lik_text2_img = CreateImg(
+ lik_text2_img = BuildImage(
0,
0,
plain_text=f"好感度:{user.impression:.2f}",
color=(255, 255, 255, 0),
font_size=30,
)
- watermark = CreateImg(
+ watermark = BuildImage(
0,
0,
plain_text=f"{NICKNAME}@{datetime.now().year}",
@@ -218,15 +218,15 @@ def _generate_card(
font_size=15,
font_color=(155, 155, 155),
)
- today_data = CreateImg(300, 300, color=(255, 255, 255, 0), font_size=20)
+ today_data = BuildImage(300, 300, color=(255, 255, 255, 0), font_size=20)
if is_card_view:
- today_sign_text_img = CreateImg(
+ today_sign_text_img = BuildImage(
0, 0, plain_text="", color=(255, 255, 255, 0), font_size=30
)
if impression_list:
impression_list.sort(reverse=True)
index = impression_list.index(user.impression)
- rank_img = CreateImg(
+ rank_img = BuildImage(
0,
0,
plain_text=f"* 此群好感排名第 {index + 1} 位",
@@ -247,7 +247,7 @@ def _generate_card(
_type = "view"
else:
A.paste(gift_border, (570, 140), True)
- today_sign_text_img = CreateImg(
+ today_sign_text_img = BuildImage(
0, 0, plain_text="今日签到", color=(255, 255, 255, 0), font_size=30
)
if is_double:
@@ -262,7 +262,7 @@ def _generate_card(
hour = current_date.hour
minute = current_date.minute
second = current_date.second
- data_img = CreateImg(
+ data_img = BuildImage(
0,
0,
plain_text=f"时间:{data} {weekdays[week]} {hour}:{minute}:{second}",
@@ -296,14 +296,14 @@ def generate_progress_bar_pic():
bg_2 = (254, 1, 254)
bg_1 = (0, 245, 246)
- bk = CreateImg(1000, 50, is_alpha=True)
- img_x = CreateImg(50, 50, color=bg_2)
+ bk = BuildImage(1000, 50, is_alpha=True)
+ img_x = BuildImage(50, 50, color=bg_2)
img_x.circle()
img_x.crop((25, 0, 50, 50))
- img_y = CreateImg(50, 50, color=bg_1)
+ img_y = BuildImage(50, 50, color=bg_1)
img_y.circle()
img_y.crop((0, 0, 25, 50))
- A = CreateImg(950, 50)
+ A = BuildImage(950, 50)
width, height = A.size
step_r = (bg_2[0] - bg_1[0]) / width
@@ -321,12 +321,12 @@ def generate_progress_bar_pic():
bk.paste(img_x, (975, 0), True)
bk.save(SIGN_RESOURCE_PATH / "bar.png")
- A = CreateImg(950, 50)
- bk = CreateImg(1000, 50, is_alpha=True)
- img_x = CreateImg(50, 50)
+ A = BuildImage(950, 50)
+ bk = BuildImage(1000, 50, is_alpha=True)
+ img_x = BuildImage(50, 50)
img_x.circle()
img_x.crop((25, 0, 50, 50))
- img_y = CreateImg(50, 50)
+ img_y = BuildImage(50, 50)
img_y.circle()
img_y.crop((0, 0, 25, 50))
bk.paste(img_y, (0, 0), True)
diff --git a/plugins/statistics/statistics_handle.py b/plugins/statistics/statistics_handle.py
index f44308c8..e54e8a8f 100755
--- a/plugins/statistics/statistics_handle.py
+++ b/plugins/statistics/statistics_handle.py
@@ -5,7 +5,7 @@ from nonebot.typing import T_State
from pathlib import Path
from configs.path_config import DATA_PATH, IMAGE_PATH
from utils.utils import get_message_text
-from utils.image_utils import CreateMat
+from utils.image_utils import BuildMat
from utils.message_builder import image
from utils.manager import plugins2settings_manager
import asyncio
@@ -202,7 +202,7 @@ async def generate_statistics_img(
except KeyError:
count.append(0)
week_lst = ["7" if i == "0" else i for i in week_lst]
- bar_graph = CreateMat(
+ bar_graph = BuildMat(
y=count,
mat_type="line",
title=f"{name} 周 {plugin} 功能调用统计【为7天统计】",
@@ -226,7 +226,7 @@ async def generate_statistics_img(
day_lst.append(i)
count = [data[str(day_lst[i])][plugin] for i in range(30)]
day_lst = [str(x + 1) for x in day_lst]
- bar_graph = CreateMat(
+ bar_graph = BuildMat(
y=count,
mat_type="line",
title=f"{name} 月 {plugin} 功能调用统计【为30天统计】",
@@ -246,12 +246,12 @@ async def generate_statistics_img(
return bar_graph.pic2bs4()
-async def init_bar_graph(data: dict, title: str) -> CreateMat:
+async def init_bar_graph(data: dict, title: str) -> BuildMat:
return await asyncio.get_event_loop().run_in_executor(None, _init_bar_graph, data, title)
-def _init_bar_graph(data: dict, title: str) -> CreateMat:
- bar_graph = CreateMat(
+def _init_bar_graph(data: dict, title: str) -> BuildMat:
+ bar_graph = BuildMat(
y=[data[x] for x in data.keys() if data[x] != 0],
mat_type="barh",
title=title,
diff --git a/plugins/update_picture.py b/plugins/update_picture.py
index f197a5a3..13b16ad5 100755
--- a/plugins/update_picture.py
+++ b/plugins/update_picture.py
@@ -9,7 +9,7 @@ from nonebot.typing import T_State
from utils.utils import get_message_imgs
from pathlib import Path
from utils.utils import is_number, get_message_text
-from utils.image_utils import CreateImg, pic2b64
+from utils.image_utils import BuildImage, pic2b64
from configs.config import NICKNAME
from utils.http_utils import AsyncHttpx
import cv2
@@ -83,7 +83,7 @@ for i in range(len(method_list)):
method_oper.append(method_list[i])
method_oper.append(str(i + 1))
-update_img_help = CreateImg(960, 700, font_size=24)
+update_img_help = BuildImage(960, 700, font_size=24)
update_img_help.text((10, 10), __plugin_usage__)
update_img_help.save(IMAGE_PATH + "update_img_help.png")
diff --git a/plugins/white2black_image.py b/plugins/white2black_image.py
index 9a5bf7f2..40637ba5 100755
--- a/plugins/white2black_image.py
+++ b/plugins/white2black_image.py
@@ -4,7 +4,7 @@ from nonebot import on_command
from utils.utils import get_message_imgs, get_message_text, is_chinese
from utils.message_builder import image
from configs.path_config import TEMP_PATH
-from utils.image_utils import CreateImg
+from utils.image_utils import BuildImage
from services.log import logger
from utils.http_utils import AsyncHttpx
from pathlib import Path
@@ -57,12 +57,12 @@ async def _(bot: Bot, event: MessageEvent, state: T_State):
):
await w2b_img.finish("下载图片失败...请稍后再试...")
msg = await get_translate(msg)
- w2b = CreateImg(0, 0, background=Path(TEMP_PATH) / f"{event.user_id}_w2b.png")
+ w2b = BuildImage(0, 0, background=Path(TEMP_PATH) / f"{event.user_id}_w2b.png")
w2b.convert("L")
msg_sp = msg.split("<|>")
w, h = w2b.size
add_h, font_size = init_h_font_size(h)
- bg = CreateImg(w, h + add_h, color="black", font_size=font_size)
+ bg = BuildImage(w, h + add_h, color="black", font_size=font_size)
bg.paste(w2b)
chinese_msg = formalization_msg(msg)
if not bg.check_font_size(chinese_msg):
@@ -84,7 +84,7 @@ async def _(bot: Bot, event: MessageEvent, state: T_State):
)
-def centered_text(img: CreateImg, text: str, add_h: int):
+def centered_text(img: BuildImage, text: str, add_h: int):
top_h = img.h - add_h + (img.h / 100)
bottom_h = img.h - (img.h / 100)
text_sp = text.split("<|>")
diff --git a/plugins/word_bank/word_hanlde.py b/plugins/word_bank/word_hanlde.py
index 881209fa..5bbcb77c 100644
--- a/plugins/word_bank/word_hanlde.py
+++ b/plugins/word_bank/word_hanlde.py
@@ -59,11 +59,11 @@ show_word = on_command("显示词条", aliases={"查看词条"}, priority=5, blo
@add_word.handle()
async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
msg = str(event.get_message()).strip()
- r = re.search(r"^问(.+)\s?答(.*)", msg)
+ r = re.search(r"^问(.+)\s?答([\s\S]*)", msg)
if not r:
await add_word.finish("未检测到词条问题...")
problem = r.group(1).strip()
- answer = r.group(2).strip()
+ answer = msg.split('答', maxsplit=1)[-1]
if not answer:
await add_word.finish("未检测到词条回答...")
idx = 0
diff --git a/plugins/yiqing/__init__.py b/plugins/yiqing/__init__.py
index 8c8926fe..29674c2e 100755
--- a/plugins/yiqing/__init__.py
+++ b/plugins/yiqing/__init__.py
@@ -54,4 +54,4 @@ async def _(bot: Bot, event: MessageEvent, state: T_State):
f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) 查询疫情失败"
)
else:
- await yiqing.send(f"{NICKNAME}只支持国内的疫情查询喔...")
+ await yiqing.send(f"{NICKNAME}没有查到{msg}的疫情查询...")
diff --git a/resources/img/genshin/genshin_card/chars_ava/10000002.png b/resources/img/genshin/genshin_card/chars_ava/10000002.png
new file mode 100644
index 0000000000000000000000000000000000000000..97caa00a2cef22c9c0cd04df8b6e7b175a3bcee9
GIT binary patch
literal 38185
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
z