mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-09-29 00:32:06 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
216d32cf21 | ||
|
|
248fae4fe6 | ||
|
|
b479b6839d | ||
|
|
1a4e1e9515 | ||
|
|
e5160a4ee8 | ||
|
|
56ea3964a7 | ||
|
|
ad73778286 | ||
|
|
3e548f48c8 | ||
|
|
ae466931fa | ||
|
|
68052b526f | ||
|
|
903b5b88a7 | ||
|
|
007823d6b2 | ||
|
|
db5c4fb349 | ||
|
|
c37901290a | ||
|
|
da67f1f6f8 | ||
|
|
5172fa69e5 | ||
|
|
dd7280cd0d | ||
|
|
0d0f522c88 | ||
|
|
5429e6ed5c | ||
|
|
9db69fa05b | ||
|
|
3b8d52bcb5 | ||
|
|
807f39a336 | ||
|
|
fea2f30461 | ||
|
|
5d97e3f95f | ||
|
|
63e6ca3e99 | ||
|
|
b11590ec3c | ||
|
|
3a43ed6919 | ||
|
|
15ce9c91fb | ||
|
|
0e8d7537d4 | ||
|
|
1fca0787be | ||
|
|
af30285c00 | ||
|
|
a84cab7afd | ||
|
|
593573ddfb | ||
|
|
e9e9f81bdb | ||
|
|
61f624841b | ||
|
|
4bb38a2730 | ||
|
|
d4ec0df00c | ||
|
|
c8a7d855f8 | ||
|
|
73a126dcd5 | ||
|
|
0736533389 | ||
|
|
2695d658ab | ||
|
|
947166e793 | ||
|
|
80c49a7cf5 | ||
|
|
55bfa8f669 | ||
|
|
74ade14f5e | ||
|
|
c9b5218ebc | ||
|
|
b324b2bd65 | ||
|
|
35c038efa2 | ||
|
|
caa404d58d | ||
|
|
830103b9d5 | ||
|
|
6e18379597 | ||
|
|
6effbacb28 | ||
|
|
a20a1a0234 | ||
|
|
3667724061 | ||
|
|
785179e4a7 | ||
|
|
715338f821 | ||
|
|
7d5a82ab6d | ||
|
|
e764cfcc1c | ||
|
|
363acf37bc | ||
|
|
5fd687c1e4 | ||
|
|
dbedcbb93d | ||
|
|
9e9611f11f | ||
|
|
44002838da | ||
|
|
4daab8459f | ||
|
|
5a002f75cc | ||
|
|
a2180a1365 | ||
|
|
7b836acf36 | ||
|
|
a112a561cc | ||
|
|
6dcc2a69f4 | ||
|
|
74e9c55a09 | ||
|
|
d4d6e736a4 | ||
|
|
5cf9938fa2 | ||
|
|
72ad0ad983 | ||
|
|
690750d91e | ||
|
|
9f1dfc4b9c | ||
|
|
05eab5307f | ||
|
|
b0fe70c960 | ||
|
|
09d7992bc4 | ||
|
|
c832a432c1 | ||
|
|
41c22f4314 | ||
|
|
252d45c79b | ||
|
|
ae8a214a3a | ||
|
|
86ce6d7664 | ||
|
|
67e96d6106 | ||
|
|
24d9ec2b75 | ||
|
|
501e47c6ec | ||
|
|
5534a5a5c2 | ||
|
|
7246aabf40 | ||
|
|
604ffae87e | ||
|
|
2d5c16456b | ||
|
|
d44f7633dc | ||
|
|
469ac4035c | ||
|
|
dab218a6d7 | ||
|
|
d6fc675d57 | ||
|
|
04148f7365 | ||
|
|
4c34ac570f | ||
|
|
fbdb8758a3 | ||
|
|
2d08747d70 | ||
|
|
0b4bacbbd1 | ||
|
|
e0412c7edb | ||
|
|
38501cebf1 | ||
|
|
ee45d0217e | ||
|
|
558f362919 | ||
|
|
fb9db2f50c | ||
|
|
c1862e9c68 | ||
|
|
731bde8e28 | ||
|
|
47c87f8694 | ||
|
|
5558cdacc3 | ||
|
|
4650d244a5 | ||
|
|
c65b8aae07 | ||
|
|
7ba2fb9584 | ||
|
|
9a2cfd0c9b | ||
|
|
a7d61b1f2b | ||
|
|
7ab185f7d7 | ||
|
|
724541d691 | ||
|
|
baa6cda9c2 | ||
|
|
9c987e9aad | ||
|
|
134974ea9c | ||
|
|
2d946958e3 | ||
|
|
ffe010e9ce | ||
|
|
1c67252b0c | ||
|
|
60c1be25d4 | ||
|
|
18323a0916 | ||
|
|
9d98abded9 | ||
|
|
b06a742b8c | ||
|
|
87fe44f188 | ||
|
|
96f6ef1693 | ||
|
|
374b7b7261 | ||
|
|
23c4141e55 | ||
|
|
c21200c2a9 | ||
|
|
4a8b94259d | ||
|
|
28a58df27b | ||
|
|
caaf486bbe | ||
|
|
0a972a399e | ||
|
|
245dad1239 | ||
|
|
a6b6a2fbff | ||
|
|
53000a949b | ||
|
|
eae1dd59a7 | ||
|
|
e6347a76a8 | ||
|
|
13e0fb3df7 | ||
|
|
3f1b23ef8a | ||
|
|
acae7f0658 | ||
|
|
a9456b1bd6 | ||
|
|
e4d5a230d2 | ||
|
|
fedf44b9fa | ||
|
|
451db104e8 | ||
|
|
9ac4c7f4e3 | ||
|
|
7cf1c6f016 | ||
|
|
6dd87ee301 | ||
|
|
96837fa8db | ||
|
|
d816e76962 | ||
|
|
310945d9cd | ||
|
|
1aba480495 | ||
|
|
07407749fe | ||
|
|
dfab70dd90 | ||
|
|
3b15469c98 | ||
|
|
e4590800c2 | ||
|
|
c63d4b8ad0 | ||
|
|
1fb913e2ed | ||
|
|
72ace2dab7 | ||
|
|
3bcef18629 | ||
|
|
c91758519c | ||
|
|
9c8f2e7aa0 | ||
|
|
5f377af6d5 | ||
|
|
570b5b1766 | ||
|
|
c4d53d58eb | ||
|
|
ed65033cf1 | ||
|
|
41836bde26 | ||
|
|
8381858742 | ||
|
|
1040bd0d58 | ||
|
|
404e47332e | ||
|
|
3db128785e | ||
|
|
2189ea11df | ||
|
|
49b9bd65b0 | ||
|
|
b3fdb1ff58 | ||
|
|
c79e3fdcf0 | ||
|
|
d7509f7030 | ||
|
|
5c99b55502 | ||
|
|
fd255f022b | ||
|
|
41642589ae | ||
|
|
619fef3bff | ||
|
|
38c0da0cf8 | ||
|
|
b241a58e84 | ||
|
|
35c9585b22 | ||
|
|
d2fe95cc05 | ||
|
|
92e60ba141 | ||
|
|
2356527791 | ||
|
|
cab0f7b522 | ||
|
|
8d3f101a68 | ||
|
|
c8ac2b6281 | ||
|
|
d7d8efd305 | ||
|
|
a4bc04c958 | ||
|
|
5fc208c95a | ||
|
|
02743d5955 | ||
|
|
a7ffebea35 | ||
|
|
c511ef50a2 | ||
|
|
82902e0cde | ||
|
|
1de5ae09bc | ||
|
|
8b735f1cb5 | ||
|
|
33d1334174 | ||
|
|
324b58c21b | ||
|
|
b7b1651709 | ||
|
|
b5a4a6a9ff | ||
|
|
fe2c82b03c | ||
|
|
81e4bca9ef | ||
|
|
c0d055b825 | ||
|
|
188ae38525 | ||
|
|
86a8fe7096 | ||
|
|
0bb2b45f5c | ||
|
|
a17d8731c8 | ||
|
|
9cdd2326c8 | ||
|
|
d75653631d | ||
|
|
e234c8906e | ||
|
|
57baea18b5 | ||
|
|
0f3efe29c4 | ||
|
|
89b87a38b3 | ||
|
|
c52b7ca2e9 | ||
|
|
371b55dc9e | ||
|
|
53e572353c | ||
|
|
3ae71c74e9 | ||
|
|
cb643c04f7 | ||
|
|
faf9ee159b | ||
|
|
ffb3b2d73e | ||
|
|
ffcc327089 | ||
|
|
dfd5a6a86a | ||
|
|
d43f89b067 | ||
|
|
f15c3ab6a2 | ||
|
|
e27809298e | ||
|
|
4b3e28e14b | ||
|
|
71ce9153ed | ||
|
|
25d63966de | ||
|
|
98afbb54b6 | ||
|
|
03a51ca5dd | ||
|
|
428481b2d5 | ||
|
|
f87c60e6db | ||
|
|
6d824e38bc | ||
|
|
f0fd5bfb9e | ||
|
|
33374df671 | ||
|
|
c035b1b19c | ||
|
|
b3b09ed666 | ||
|
|
8029c00a02 | ||
|
|
a2c0921013 | ||
|
|
31cdabc229 | ||
|
|
1059a0aae0 | ||
|
|
e6576e7f40 | ||
|
|
5837144095 | ||
|
|
d30a5c4207 | ||
|
|
7d169b0e5e | ||
|
|
1f54226673 | ||
|
|
901a90ff13 | ||
|
|
56573d1d34 | ||
|
|
b0ab78340a | ||
|
|
93539be492 | ||
|
|
c551e21766 | ||
|
|
ed342af97d | ||
|
|
17b87a2cb1 | ||
|
|
69ca02834d | ||
|
|
6cb4099a2e | ||
|
|
e75a4966b2 | ||
|
|
d55cba49bd | ||
|
|
0215abac30 | ||
|
|
2dcac5f009 | ||
|
|
0d8c64f7d1 | ||
|
|
55626645fb | ||
|
|
f7925fa259 | ||
|
|
c814ae08b1 | ||
|
|
24666368f7 | ||
|
|
a77e85b62d | ||
|
|
6319c7fce1 | ||
|
|
a45b95d5d4 | ||
|
|
188f6f0f99 | ||
|
|
a72f126d8f | ||
|
|
33eb1b83a8 | ||
|
|
2d9ebe2e6d | ||
|
|
262b098dec | ||
|
|
79d88065aa | ||
|
|
0f59895c3b | ||
|
|
610d588e34 | ||
|
|
f99abb2f98 | ||
|
|
b2b7560d05 | ||
|
|
07c16e5238 | ||
|
|
1741ea7d26 | ||
|
|
b5ce4fab31 | ||
|
|
062e4c2062 | ||
|
|
0adb66a8bf | ||
|
|
2aad517112 | ||
|
|
14d1512b1e | ||
|
|
3f533516af | ||
|
|
f1354308fd | ||
|
|
c06248e46b | ||
|
|
9c8eaed938 | ||
|
|
2081302e32 | ||
|
|
678fc19f2d | ||
|
|
adc2757e8e | ||
|
|
82a1261371 | ||
|
|
067da9dc57 | ||
|
|
0eb9f9fc6f | ||
|
|
aee1340714 | ||
|
|
a72a9bd8cb | ||
|
|
7250410653 | ||
|
|
12f55800b0 | ||
|
|
53b375b4b2 | ||
|
|
fba27abe29 | ||
|
|
4caa3dec04 | ||
|
|
516b0f5ccb | ||
|
|
b5098be08e | ||
|
|
5b08a4650c | ||
|
|
eb02157b94 | ||
|
|
222e1719a4 |
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Issue template
|
||||
about: " issue template's purpose here."
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### 系统版本:Ubuntu 20.04
|
||||
### 真寻版本:0.1.5.3
|
||||
|
||||
### 错误截图
|
||||
|
||||
[img]
|
||||
|
||||
### 日志截图
|
||||
|
||||
[img]
|
||||
|
||||
### 错误说明
|
||||
|
||||
发生了xx错误...
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Generate poetry.lock
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- 'pyproject.toml'
|
||||
|
||||
jobs:
|
||||
poetry_lock:
|
||||
name: Generate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: he0119/setup-python@main
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: poetry lock
|
||||
run: |
|
||||
rm -f poetry.lock
|
||||
sed -i.bak "8,11d" pyproject.toml
|
||||
poetry lock
|
||||
mv pyproject.toml.bak pyproject.toml
|
||||
# commit
|
||||
- name: commit file
|
||||
run: |
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git add poetry.lock
|
||||
git commit -m "Auto generate poetry.lock" -a
|
||||
# push
|
||||
- name: push changes
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,9 @@
|
||||
### 系统版本:Ubuntu 20.04
|
||||
### 真寻版本:0.1.5.3
|
||||
|
||||
### 错误截图
|
||||
|
||||
[img]
|
||||
|
||||
### 错误说明
|
||||
发生了xx错误...
|
||||
+1
-2
@@ -140,9 +140,8 @@ cython_debug/
|
||||
|
||||
test.py
|
||||
server_ip.py
|
||||
game_utils.py
|
||||
member_activity_handle.py
|
||||
Yu-Gi-Oh/
|
||||
search_image/
|
||||
black_word/
|
||||
csgo/
|
||||
fantasy_card/
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
## 声明
|
||||
此项目仅用于学习交流,请勿用于非法用途
|
||||
|
||||
## Gitee
|
||||
# Nonebot2
|
||||
<img style="height: 200px;width: 200px;" src="https://camo.githubusercontent.com/0ef71e86056da694c540790aa4a4e314396884d6c4fdb95362a7538b27a1b034/68747470733a2f2f76322e6e6f6e65626f742e6465762f6c6f676f2e706e67">
|
||||
|
||||
# [Gitee](https://gitee.com/two_Dimension/zhenxun_bot)
|
||||
非常 [ **[NICE](https://github.com/nonebot/nonebot2)** ] 的OneBot框架
|
||||
|
||||
## 未完成的文档
|
||||
|
||||
@@ -32,6 +33,12 @@
|
||||
## 普通帮助图片
|
||||

|
||||
|
||||
## Web UI
|
||||
[zhenxun_bot_webui](https://github.com/HibiKier/zhenxun_bot_webui)
|
||||
|
||||
## 一键安装脚本
|
||||
[zhenxun_bot-deploy](https://github.com/AkashiCoin/zhenxun_bot-deploy)
|
||||
|
||||
## 提供符合真寻标准的插件仓库
|
||||
|
||||
[AkashiCoin/nonebot_plugins_zhenxun_bot](https://github.com/AkashiCoin/nonebot_plugins_zhenxun_bot)
|
||||
@@ -107,7 +114,7 @@
|
||||
- [x] 获取b站视频封面
|
||||
- [x] 通过PID获取图片
|
||||
- [x] 功能统计可视化
|
||||
|
||||
- [x] 词云
|
||||
- [x] 关于
|
||||
|
||||
### 已实现的管理员功能
|
||||
@@ -229,17 +236,284 @@ python bot.py
|
||||
|
||||
## 使用Docker
|
||||
__Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__
|
||||
#### GitHub:[Sakuracio/zxenv](https://github.com/Sakuracio/zxenv)
|
||||
#### GitHub:[Sakuracio/zhenxun_bot_docker](https://github.com/Sakuracio/zhenxun_bot_docker)
|
||||
#### DockerHub:[hibikier/zhenxun_bot](https://hub.docker.com/r/hibikier/zhenxun_bot)
|
||||
|
||||
|
||||
|
||||
## 更新
|
||||
|
||||
### 2022/6/19 \[v0.1.6.0]
|
||||
|
||||
* 暂时使用hook修复webui中plugins2setting修改时会改变plugins2setting.cmd为字符串
|
||||
* 修复原神树脂重复提醒的bug [@pull/828](https://github.com/HibiKier/zhenxun_bot/pull/828)
|
||||
* 词条优化问题展示,修改含机器人昵称和@逻辑 [@pull/835](https://github.com/HibiKier/zhenxun_bot/pull/835)
|
||||
|
||||
### 2022/6/18
|
||||
|
||||
* 修复昵称系统`BLACK_WORD`为空时造成报错
|
||||
* 修复特殊头像时背景透明化出错
|
||||
* 修复text2image纯文本时换行时颜色不统一
|
||||
* 优化webUI,当有插件出错时不会影响其他插件显示
|
||||
* 优化Config类型注释 [@pull/830](https://github.com/HibiKier/zhenxun_bot/pull/830)
|
||||
* 优化browser
|
||||
|
||||
### 2022/6/17
|
||||
|
||||
* 修复wordbank的一些小问题 @pull/820
|
||||
|
||||
### 2022/6/14
|
||||
|
||||
* AsyncHttpx提供可关闭 ssl 验证参数 [@pull/812](https://github.com/HibiKier/zhenxun_bot/pull/812)
|
||||
* fix: 自动更新群组 [@pull/810](https://github.com/HibiKier/zhenxun_bot/pull/810)
|
||||
* fix: 修复添加词条时对answer中图片的处理 [@pull/806](https://github.com/HibiKier/zhenxun_bot/pull/806)
|
||||
* 修复pix中指定num无法生效
|
||||
* 替换了bt URL
|
||||
|
||||
### 2022/6/13
|
||||
|
||||
* Windows 重启适配 [@pull/804](https://github.com/HibiKier/zhenxun_bot/pull/804)
|
||||
* 色图等提供修改md5方法 [@pull/800](https://github.com/HibiKier/zhenxun_bot/pull/800)
|
||||
* webui中token.json文件写入补充 [@pull/798](https://github.com/HibiKier/zhenxun_bot/pull/798)
|
||||
* 修复识图某些图片可能会出错
|
||||
|
||||
### 2022/6/11
|
||||
|
||||
* pix当只有一个tag时且为数字时可以通过添加'#'防止判定为数量
|
||||
|
||||
### 2022/6/9
|
||||
|
||||
* 修复b站订阅同群二人以上相同订阅时发送多次信息
|
||||
* 修复超级用户帮助中缺少了 ‘插件商店’ 相关帮助
|
||||
* 昵称系统提供了详细帮助
|
||||
|
||||
### 2022/6/5 \[v0.1.5.9]
|
||||
|
||||
* webui修复plugin2setting中cmd从list转变为str
|
||||
* 当命令`我的金币`被风控时将以图片形式发送
|
||||
* fix gold_redbag [@pull/763](https://github.com/HibiKier/zhenxun_bot/pull/763)
|
||||
* 金币红包功能增加更多封面 [@pull/764](https://github.com/HibiKier/zhenxun_bot/pull/764)
|
||||
|
||||
### 2022/6/3
|
||||
|
||||
* 插件仓库在已安装插件边上会提示\[已安装]
|
||||
* 修复ShopRegister kwargs某些字段无效
|
||||
* 调整了一下查看所有请求中的年龄绘制 [@pull/745](https://github.com/HibiKier/zhenxun_bot/pull/745)
|
||||
* 修复原神树脂提醒bug [@pull/756](https://github.com/HibiKier/zhenxun_bot/pull/756)
|
||||
|
||||
### 2022/5/31
|
||||
|
||||
* 修复开启/关闭全部功能时帮助图片未重绘 [@pull/721](https://github.com/HibiKier/zhenxun_bot/pull/721)
|
||||
* bot_friend_group将group_handle.修改为friend_handle. [@pull/711](https://github.com/HibiKier/zhenxun_bot/pull/711)
|
||||
* 修复发红包图片背景未透明化;修复原神树脂提醒参数错误 [@pull/712](https://github.com/HibiKier/zhenxun_bot/pull/712)
|
||||
* 修复抽卡插件:方舟更新UP池信息时,若公告的第一个池子过期会导致无法更新UP池信息 [@pull/707](https://github.com/HibiKier/zhenxun_bot/pull/707)
|
||||
* 商店插件判断是否有requirements.txt文件 [@pull/705](https://github.com/HibiKier/zhenxun_bot/pull/705)
|
||||
* 删除原神玩家查询api返回变更的多余键值
|
||||
* 优化了text2image方法
|
||||
|
||||
### 2022/5/29 \[v0.1.5.8]
|
||||
|
||||
* 提供了真寻适配仓库的插件 安装/卸载 操作
|
||||
* 暂时关闭了插件资源清空
|
||||
* 通过指令安装插件时会在插件目录下生成plugin_info.json记录当前插件信息
|
||||
|
||||
### 2022/5/28
|
||||
|
||||
* 修复私聊无法添加昵称
|
||||
* 修复原神玩家查询层岩巨渊地下矿区没开时报错
|
||||
* 修复 ```休息吧``` 无法阻断戳一戳
|
||||
* 当图库无图片时,戳一戳将略过发送图片
|
||||
* 新增搜图提供配置项```ALLOW_GROUP_R18```:允许在群聊中使用r18参数
|
||||
* 新增自动更新插件```UPDATE_REMIND```:新版本提醒,原配置项```AUTO_UPDATE_ZHENXUN```改为自动更新升级
|
||||
* black_word新增当群权限为-1时不再检测该群
|
||||
* 修复非超级用户绑定原神cookie会被black_word阻拦
|
||||
* 修复微博热搜报错,发红包小bug [@pull/688](https://github.com/HibiKier/zhenxun_bot/pull/688)
|
||||
* 更多的中文提示
|
||||
|
||||
### 2022/5/26
|
||||
|
||||
* 修复\[滴滴滴]会被转义成[滴滴滴]导致无法触发的问题
|
||||
* 将一些错误以中文提示输出
|
||||
* 更新BT搜索源地址 [@pull/668](https://github.com/HibiKier/zhenxun_bot/pull/668)
|
||||
* 更新抽卡插件 [@pull/667](https://github.com/HibiKier/zhenxun_bot/pull/667)
|
||||
|
||||
### 2022/5/25
|
||||
|
||||
* 修复webui中CountLimit字段limit_type类型错误
|
||||
* 修改nickname插件:一处文案错误,添加敏感词 [@pull/624](https://github.com/HibiKier/zhenxun_bot/pull/624)
|
||||
* gamedraw的ba卡池搬运了过来并且进行了真寻的适配 [@pull/617](https://github.com/HibiKier/zhenxun_bot/pull/617)
|
||||
* feat: stream downloading and progress [@pull/607](https://github.com/HibiKier/zhenxun_bot/pull/607)
|
||||
* 修改翻译插件,添加缺失的冒号 [@pull/602](https://github.com/HibiKier/zhenxun_bot/pull/602)
|
||||
* 修复碧蓝航线/明日方舟up池解析出错的问题 [@pull/610](https://github.com/HibiKier/zhenxun_bot/pull/602)
|
||||
|
||||
### 2022/5/24
|
||||
|
||||
* fix: 修正了签到时日期时间的显示不补齐零的bug (符合日期时间表示法 ISO 8601)[@pull/600](https://github.com/HibiKier/zhenxun_bot/pull/600)
|
||||
* 更新"微博热搜"接口 [@pull/579](https://github.com/HibiKier/zhenxun_bot/pull/579)
|
||||
* refactor&fix(manager): modify argument [@pull/576](https://github.com/HibiKier/zhenxun_bot/pull/576)
|
||||
* 修复复读不能复读图片的问题 [@pull/573](https://github.com/HibiKier/zhenxun_bot/pull/573)
|
||||
* 修复抽卡插件:方舟抽卡的抽取和显示问题 [@pull/581](https://github.com/HibiKier/zhenxun_bot/pull/581)
|
||||
|
||||
### 2022/5/23 \[v0.1.5.6]
|
||||
|
||||
* 修复"清除已删除插件数据" [@pull/545](https://github.com/HibiKier/zhenxun_bot/pull/545)
|
||||
* 修复有置顶的up主B站动态获取失败 [@pull/552](https://github.com/HibiKier/zhenxun_bot/pull/552)
|
||||
* 添加pixiv搜图多关键词支持;修复p站搜图数量参数问题 [@pull/441](https://github.com/HibiKier/zhenxun_bot/pull/441)
|
||||
* 修复开箱更新价格错误传参
|
||||
* 修复pix无法正确查询uid
|
||||
* 新增色图插件添加配置项```ALLOW_GROUP_R18```:允许群聊中使用色图r
|
||||
* 新增PIX插件添加配置项```ALLOW_GROUP_SETU```:允许非超级用户使用-s参数
|
||||
* 新增PIX插件添加配置项```ALLOW_GROUP_R18```:允许非超级用户使用-r参数
|
||||
|
||||
### 2022/5/22 \[v0.1.5.4]
|
||||
|
||||
* 使用action自动更新poetry.lock [@pull/515](https://github.com/HibiKier/zhenxun_bot/pull/515)
|
||||
* fix(bilibili_sub): card is None and timeout [@pull/516](https://github.com/HibiKier/zhenxun_bot/pull/516)
|
||||
* 修复了epic有时获取新免费游戏消息时获取不到图片
|
||||
* 修复好感度满时签到出错(虽然是不可能满的
|
||||
* 修复原神资源图标下载路径错误
|
||||
* 修复自动更新群组可能失败
|
||||
|
||||
### 2022/5/21
|
||||
|
||||
* 修复搜番无结果时报错无正确反馈
|
||||
* 解锁了windows上无法使用playwright的限制
|
||||
* 修复p搜对应pid有多张图时出错,改为连续发送图片
|
||||
* 修复p搜对数字的错误判断
|
||||
* 修复添加商品折扣无法正确添加
|
||||
* 修复了bilibili订阅直播间订阅up名称不一致的问题
|
||||
* 修复原神玩家查询没开地图时报错
|
||||
* 最低priority修改为 999
|
||||
* 修复刷屏检测失效
|
||||
* 修复刷屏检测设置命令无法生效
|
||||
* 优化刷屏显示设置禁言时长显示,并改为分钟
|
||||
* 修复了多连开箱无法指定武器箱
|
||||
* 修复识番链接无法正确获取
|
||||
* 新增真寻入群时即刻刷新权限
|
||||
* 提高了微博热搜截图的等待时间
|
||||
|
||||
### 2022/5/19
|
||||
|
||||
* fix: mihoyo bbs api changed [@pull/357](https://github.com/HibiKier/zhenxun_bot/pull/357)
|
||||
* Add word_clouds [@pull/265](https://github.com/HibiKier/zhenxun_bot/pull/265)
|
||||
* Fix wrong live streamer name [@pull/284](https://github.com/HibiKier/zhenxun_bot/pull/284)
|
||||
|
||||
### 2022/5/16
|
||||
|
||||
* 词条支持图片和@问题 [@pull/160](https://github.com/HibiKier/zhenxun_bot/pull/160)
|
||||
|
||||
### 2022/5/15
|
||||
|
||||
* 修复了商店商品无法正确添加
|
||||
* 修复了多张色图无法正确发送
|
||||
|
||||
### 2022/5/14
|
||||
|
||||
* 修复B站动态生成失败的问题 [@pull/159](https://github.com/HibiKier/zhenxun_bot/pull/159)
|
||||
|
||||
### 2022/5/11
|
||||
|
||||
* fix: 更改p搜api,解决p搜无法使用的问题 [@pull/155](https://github.com/HibiKier/zhenxun_bot/pull/155)
|
||||
|
||||
### 2022/5/9 \[v0.1.5.3]
|
||||
|
||||
* 替换了疫情API
|
||||
* 修复了私聊.ban/.unban出错
|
||||
|
||||
### 2022/5/5
|
||||
|
||||
* 修改bilibili_sub插件在windows平台下报错 [@pull/153](https://github.com/HibiKier/zhenxun_bot/pull/153)
|
||||
|
||||
### 2022/5/3 \[v0.1.5.2]
|
||||
|
||||
* 商品使用函数可以添加特定参数,例如:user_id, group_id, ShopParam等以及自己提供的参数
|
||||
* 添加商品注册装饰器shop_register
|
||||
* 修复商品函数kwargs无法获取参数值
|
||||
|
||||
|
||||
### 2022/5/1
|
||||
|
||||
* 删除了`group_last_chat`插件(该功能可由`chat_history`替代
|
||||
* 新增敏感词检测(全新反击系统,是时候重拳出击了
|
||||
|
||||
### 2022/4/26
|
||||
|
||||
* 修复了群白名单无法正确添加
|
||||
* 优化了管理员帮助图片,背景图层将位于最下层
|
||||
* 修复了树脂140时不断提醒(未测试
|
||||
* 新增了消息记录的消息排行
|
||||
* WebUI新增CPU,内存,磁盘监控
|
||||
* WebUI新增资源文件夹统计可视化
|
||||
|
||||
### 2022/4/12
|
||||
|
||||
* 修复b了命令私聊出错
|
||||
|
||||
### 2022/4/10 \[v0.1.4.7]
|
||||
|
||||
* 新增消息记录模块
|
||||
* 丰富处理请求操作提示
|
||||
* web ui新增配置项修改
|
||||
|
||||
### 2022/4/9
|
||||
|
||||
* fix: 更新问题,戳一戳图片路径问题 [@pull/144](https://github.com/HibiKier/zhenxun_bot/pull/144)
|
||||
|
||||
### 2022/4/8
|
||||
|
||||
* 修复原神玩家查询
|
||||
|
||||
### 2022/4/6
|
||||
|
||||
* update search_type [@pull/143](https://github.com/HibiKier/zhenxun_bot/pull/143)
|
||||
|
||||
### 2022/4/5 \[v0.1.4.6]
|
||||
|
||||
* 修复web修改插件后帮助图片生成错误
|
||||
|
||||
### 2022/4/4 \[v0.1.4.5]
|
||||
|
||||
* 替换了bt搜索URL
|
||||
* 优化使用playwright的相关代码
|
||||
* 原神玩家查询新增层岩巨渊探索
|
||||
* 修复原神便笺角色头像黑框
|
||||
* 修复同意群聊请求错误
|
||||
* 提供webui方面的api
|
||||
* 新增web-ui(前端简易管理页面插件)插件
|
||||
|
||||
### 2022/3/21
|
||||
|
||||
* 修复statistics_handle.py乱码
|
||||
|
||||
### 2022/3/18 \[v0.1.4.4]
|
||||
|
||||
* 修复戳一戳无法功能关闭与ban禁用
|
||||
* 新增图片搜索 search_image
|
||||
|
||||
### 2022/3/7
|
||||
|
||||
* 优化增删权限插件
|
||||
|
||||
### 2022/3/6
|
||||
|
||||
* 修复树脂提醒无法开启
|
||||
* 修复p搜图片路径错误
|
||||
|
||||
### 2022/3/3 \[v0.1.4.3]
|
||||
|
||||
* 修复手动同意群聊请求依旧退出
|
||||
|
||||
### 2022/3/1 \[v0.1.4.2]
|
||||
|
||||
* 0.1.4内容
|
||||
|
||||
### 2022/2/27 \[v0.1.4.1]
|
||||
|
||||
* 优化抽卡
|
||||
|
||||
### 2022/2/25 \[v0.1.4]
|
||||
|
||||
* PIX提供配置MAX_ONCE_NUM2FORWARD:当单次发送图片超过指定张数且在群聊时,将转为合并消息
|
||||
* 优化抽卡
|
||||
* 修复点歌无法正确发送
|
||||
* 修复我有一个朋友有时文本会包含CQ码
|
||||
* 修复群欢消息被动控制文本未删除 [@pull/124](https://github.com/HibiKier/zhenxun_bot/pull/124)
|
||||
@@ -281,7 +555,6 @@ __Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__
|
||||
|
||||
* 适配nonebot.beta2
|
||||
* 删除图片搜索 nonebot_plugin_picsearcher
|
||||
* 新增图片搜索 search_image
|
||||
* 替换cos api
|
||||
* 原神签到树脂提醒新增绑定群里,在某群绑定uid就会在某群发送提醒信息(有好友则私聊,需要重新绑定uid
|
||||
* 修改update_info.json
|
||||
@@ -290,15 +563,15 @@ __Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__
|
||||
* epic restful 替换 [@pull/119](https://github.com/HibiKier/zhenxun_bot/pull/119)
|
||||
* fix: 修复远古时期残留的epic推送问题 [@pull/122](https://github.com/HibiKier/zhenxun_bot/pull/122)
|
||||
|
||||
### 2021/2/11
|
||||
### 2022/2/11
|
||||
|
||||
* 修复pix不使用反代无法下载图片
|
||||
|
||||
### 2021/2/10 \[v0.1.1]
|
||||
### 2022/2/10 \[v0.1.1]
|
||||
|
||||
* 修复购买道具出错
|
||||
|
||||
### 2021/2/9 \[v0.1]
|
||||
### 2022/2/9 \[v0.1]
|
||||
|
||||
* 新增原神自动签到和手动签到
|
||||
* 新增原神树脂提醒
|
||||
@@ -308,7 +581,7 @@ __Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__
|
||||
* 修复修改商品时限制时间出错
|
||||
* 修复超时商品依旧可以被购买
|
||||
|
||||
### 2021/1/16 \[v0.0.9.0]
|
||||
### 2022/1/16 \[v0.0.9.0]
|
||||
|
||||
* Ai提供文本敏感词过滤器
|
||||
* 疫情插件适配新版腾讯API
|
||||
@@ -327,7 +600,7 @@ __Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__
|
||||
* “send_success_msg”(发送成功的交互信息->即:使用道具 {name} {num} 次成功)
|
||||
* “_max_num_limit”(该道具单次使用的最多个数,默认1)
|
||||
|
||||
### 2021/1/5 \[v0.0.8.2]
|
||||
### 2022/1/5 \[v0.0.8.2]
|
||||
|
||||
* 提供金币消费hook,可在plugins2settings.yaml中配置该功能需要消费的金币
|
||||
* 商店插件将作为内置插件移动至basic_plugins
|
||||
@@ -398,4 +671,4 @@ __..... 更多更新信息请查看文档__
|
||||
[NothAmor / nonebot2_luxun_says](https://github.com/NothAmor/nonebot2_luxun_says) :基于nonebot2机器人框架的鲁迅说插件
|
||||
[Kyomotoi / AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus) :一个~~特二刺螈~~(文爱)的适用于任何bot的词库
|
||||
[Ailitonia / omega-miya](https://github.com/Ailitonia/omega-miya) :基于nonebot2的qq机器人
|
||||
[KimigaiiWuyi / GenshinUID]("https://github.com/KimigaiiWuyi/GenshinUID") :一个基于HoshinoBot/NoneBot2的原神UID查询插件
|
||||
[KimigaiiWuyi / GenshinUID](https://github.com/KimigaiiWuyi/GenshinUID) :一个基于HoshinoBot/NoneBot2的原神UID查询插件
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__: v0.1.4
|
||||
__version__: v0.1.6.0
|
||||
|
||||
@@ -118,6 +118,8 @@ async def change_group_switch(cmd: str, group_id: int, is_super: bool = False):
|
||||
group_manager.unblock_plugin(f, group_id)
|
||||
else:
|
||||
group_manager.block_plugin(f, group_id)
|
||||
if group_help_file.exists():
|
||||
group_help_file.unlink()
|
||||
return f"已 {status} 全部功能!"
|
||||
if cmd in [task_data[x] for x in task_data.keys()]:
|
||||
type_ = "task"
|
||||
@@ -232,10 +234,7 @@ async def update_member_info(group_id: int, remind_superuser: bool = False) -> b
|
||||
_exist_member_list = []
|
||||
# try:
|
||||
for user_info in _group_user_list:
|
||||
if user_info["card"] == "":
|
||||
nickname = user_info["nickname"]
|
||||
else:
|
||||
nickname = user_info["card"]
|
||||
nickname = user_info["card"] or user_info["nickname"]
|
||||
async with db.transaction():
|
||||
# 更新权限
|
||||
if (
|
||||
@@ -321,3 +320,4 @@ def set_group_bot_status(group_id: int, status: bool) -> str:
|
||||
# for x in group_manager.get_task_data():
|
||||
# group_manager.close_group_task(group_id, x)
|
||||
return "那我先睡觉了..."
|
||||
|
||||
|
||||
@@ -24,11 +24,17 @@ def switch_rule(event: Event) -> bool:
|
||||
_data = plugins2settings_manager.get_data()
|
||||
for key in _data:
|
||||
try:
|
||||
for x in _data[key]["cmd"]:
|
||||
cmd.append(f"开启{x}")
|
||||
cmd.append(f"关闭{x}")
|
||||
cmd.append(f"开启 {x}")
|
||||
cmd.append(f"关闭 {x}")
|
||||
if isinstance(_data[key]["cmd"], list):
|
||||
for x in _data[key]["cmd"]:
|
||||
cmd.append(f"开启{x}")
|
||||
cmd.append(f"关闭{x}")
|
||||
cmd.append(f"开启 {x}")
|
||||
cmd.append(f"关闭 {x}")
|
||||
else:
|
||||
cmd.append(f"开启{key}")
|
||||
cmd.append(f"关闭{key}")
|
||||
cmd.append(f"开启 {key}")
|
||||
cmd.append(f"关闭 {key}")
|
||||
except KeyError:
|
||||
pass
|
||||
msg = get_message_text(event.json()).split()
|
||||
|
||||
@@ -2,6 +2,7 @@ from nonebot import on_command, on_notice
|
||||
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, GROUP, GroupIncreaseNoticeEvent
|
||||
from ._data_source import update_member_info
|
||||
|
||||
|
||||
__zx_plugin_name__ = "更新群组成员列表 [Admin]"
|
||||
__plugin_usage__ = """
|
||||
usage:
|
||||
|
||||
@@ -5,7 +5,6 @@ from utils.utils import get_matchers
|
||||
from utils.manager import group_manager
|
||||
from nonebot.adapters.onebot.v11 import Bot
|
||||
from nonebot import Driver
|
||||
import asyncio
|
||||
import nonebot
|
||||
|
||||
|
||||
@@ -27,12 +26,10 @@ async def create_help_image():
|
||||
"""
|
||||
创建管理员帮助图片
|
||||
"""
|
||||
await asyncio.get_event_loop().run_in_executor(
|
||||
None, _create_help_image
|
||||
)
|
||||
await _create_help_image()
|
||||
|
||||
|
||||
def _create_help_image():
|
||||
async def _create_help_image():
|
||||
"""
|
||||
创建管理员帮助图片
|
||||
"""
|
||||
@@ -85,9 +82,9 @@ def _create_help_image():
|
||||
height = len(help_str.split("\n")) * 33
|
||||
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)
|
||||
await A.apaste(_background, alpha=True)
|
||||
await A.atext((150, 110), help_str)
|
||||
await A.asave(admin_help_image)
|
||||
logger.info(f'已成功加载 {len(_plugin_name_list)} 条管理员命令')
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from nonebot import on_message
|
||||
from services.log import logger
|
||||
from models.group_info import GroupInfo
|
||||
from models.friend_user import FriendUser
|
||||
from nonebot.adapters.onebot.v11.exception import ActionFailed
|
||||
from nonebot.adapters.onebot.v11 import ActionFailed
|
||||
from configs.config import NICKNAME, Config
|
||||
from utils.manager import group_manager
|
||||
from pathlib import Path
|
||||
@@ -107,6 +107,7 @@ async def _():
|
||||
group_info["group_name"],
|
||||
group_info["max_member_count"],
|
||||
group_info["member_count"],
|
||||
1
|
||||
)
|
||||
logger.info(f"自动更新群组 {g} 信息成功")
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent, PrivateMessageEvent, MessageEvent, Message, Bot
|
||||
from nonebot.params import CommandArg, Command
|
||||
from nonebot import on_command
|
||||
from typing import Tuple
|
||||
|
||||
from configs.config import NICKNAME, Config
|
||||
from models.ban_user import BanUser
|
||||
from models.level_user import LevelUser
|
||||
from typing import Tuple
|
||||
from utils.utils import get_message_at, is_number
|
||||
from configs.config import NICKNAME, Config
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.onebot.v11 import (Bot, GroupMessageEvent, Message,
|
||||
MessageEvent, PrivateMessageEvent)
|
||||
from nonebot.params import Command, CommandArg
|
||||
from nonebot.permission import SUPERUSER
|
||||
from .data_source import parse_ban_time, a_ban
|
||||
from services.log import logger
|
||||
from utils.utils import get_message_at, is_number
|
||||
|
||||
from .data_source import a_ban, parse_ban_time
|
||||
|
||||
__zx_plugin_name__ = "封禁Ban用户 [Admin]"
|
||||
__plugin_usage__ = """
|
||||
@@ -34,19 +36,19 @@ usage:
|
||||
示例:b了 1234567
|
||||
示例:.ban 12345567
|
||||
""".strip()
|
||||
__plugin_des__ = '你被逮捕了!丢进小黑屋!'
|
||||
__plugin_cmd__ = ['.ban [at] ?[小时] ?[分钟]', '.unban [at]', 'b了 [at] [_superuser]']
|
||||
__plugin_des__ = "你被逮捕了!丢进小黑屋!"
|
||||
__plugin_cmd__ = [".ban [at] ?[小时] ?[分钟]", ".unban [at]", "b了 [at] [_superuser]"]
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = 'HibiKier'
|
||||
__plugin_author__ = "HibiKier"
|
||||
__plugin_settings__ = {
|
||||
"admin_level": Config.get_config("ban", "BAN_LEVEL"),
|
||||
"cmd": ['.ban', '.unban', 'ban', 'unban']
|
||||
"cmd": [".ban", ".unban", "ban", "unban"],
|
||||
}
|
||||
__plugin_configs__ = {
|
||||
"BAN_LEVEL [LEVEL]": {
|
||||
"value": 5,
|
||||
"help": "ban/unban所需要的管理员权限等级",
|
||||
"default_value": 5
|
||||
"default_value": 5,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,18 +60,23 @@ ban = on_command(
|
||||
block=True,
|
||||
)
|
||||
|
||||
super_ban = on_command('b了', permission=SUPERUSER, priority=5, block=True)
|
||||
super_ban = on_command("b了", permission=SUPERUSER, priority=5, block=True)
|
||||
|
||||
|
||||
@ban.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()):
|
||||
async def _(
|
||||
bot: Bot,
|
||||
event: GroupMessageEvent,
|
||||
cmd: Tuple[str, ...] = Command(),
|
||||
arg: Message = CommandArg(),
|
||||
):
|
||||
cmd = cmd[0]
|
||||
result = ""
|
||||
qq = get_message_at(event.json())
|
||||
if qq:
|
||||
qq = qq[0]
|
||||
user_name = await bot.get_group_member_info(group_id=event.group_id, user_id=qq)
|
||||
user_name = user_name['card'] or user_name['nickname']
|
||||
user_name = user_name["card"] or user_name["nickname"]
|
||||
msg = arg.extract_plain_text().strip()
|
||||
time = parse_ban_time(msg)
|
||||
if isinstance(time, str):
|
||||
@@ -108,49 +115,58 @@ async def _(bot: Bot, event: GroupMessageEvent, cmd: Tuple[str, ...] = Command()
|
||||
|
||||
|
||||
@ban.handle()
|
||||
async def _(bot: Bot, event: PrivateMessageEvent, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()):
|
||||
async def _(
|
||||
bot: Bot,
|
||||
event: PrivateMessageEvent,
|
||||
cmd: Tuple[str, ...] = Command(),
|
||||
arg: Message = CommandArg(),
|
||||
):
|
||||
cmd = cmd[0]
|
||||
msg = arg.extract_plain_text().strip()
|
||||
if msg:
|
||||
if str(event.user_id) in bot.config.superusers:
|
||||
if is_number(arg.extract_plain_text().strip().split()[0]):
|
||||
qq = int(msg[0])
|
||||
msg = msg[1:]
|
||||
msg_splt = msg.split()
|
||||
if is_number(msg_splt[0]):
|
||||
|
||||
qq = int(msg_splt[0])
|
||||
msg = msg_splt[1:]
|
||||
if cmd in [".ban", "/ban"]:
|
||||
time = parse_ban_time(msg)
|
||||
time = parse_ban_time(" ".join(msg))
|
||||
if isinstance(time, str):
|
||||
await ban.finish(time)
|
||||
result = await a_ban(qq, time, str(qq), event, 9)
|
||||
else:
|
||||
if await BanUser.unban(qq):
|
||||
logger.info(
|
||||
f"USER {event.user_id} 将 USER {qq} 解禁"
|
||||
)
|
||||
logger.info(f"USER {event.user_id} 将 USER {qq} 解禁")
|
||||
result = f"已经把 {qq} 从黑名单中删除了!"
|
||||
else:
|
||||
result = f"{qq} 不在黑名单!"
|
||||
await ban.send(result)
|
||||
else:
|
||||
await ban.finish('qq号必须是数字!\n格式:.ban [qq] [hour]? [minute]?', at_sender=True)
|
||||
await ban.finish(
|
||||
"qq号必须是数字!\n格式:.ban [qq] [hour]? [minute]?", at_sender=True
|
||||
)
|
||||
|
||||
|
||||
@super_ban.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
|
||||
user_name = ""
|
||||
if isinstance(event, GroupMessageEvent):
|
||||
qq = get_message_at(event.json())
|
||||
if qq:
|
||||
qq = qq[0]
|
||||
user = await bot.get_group_member_info(group_id=event.group_id, user_id=qq)
|
||||
user_name = user["card"] or user["nickname"]
|
||||
else:
|
||||
qq = arg.extract_plain_text().strip()
|
||||
if not is_number(qq):
|
||||
await super_ban.finish("对象qq必须为纯数字...")
|
||||
qq = [qq]
|
||||
qq = int(qq)
|
||||
user_name = qq
|
||||
if qq:
|
||||
qq = qq[0]
|
||||
user = await bot.get_group_member_info(group_id=event.group_id, user_id=qq)
|
||||
user_name = user['card'] or user['nickname']
|
||||
if not await BanUser.ban(qq, 10, 99999999):
|
||||
await BanUser.unban(qq)
|
||||
await BanUser.ban(qq, 10, 99999999)
|
||||
await ban.send(f"已将 {user_name} 拉入黑名单!")
|
||||
else:
|
||||
await super_ban.send('需要添加被super ban的对象,可以使用at或者指定qq..')
|
||||
|
||||
await super_ban.send("需要添加被super ban的对象,可以使用at或者指定qq..")
|
||||
|
||||
@@ -25,7 +25,9 @@ def parse_ban_time(msg: str) -> Union[int, str]:
|
||||
return int(msg[0]) * 60 * 60 + int(msg[1]) * 60
|
||||
|
||||
|
||||
async def a_ban(qq: int, time: int, user_name: str, event: MessageEvent, ban_level: int = None) -> str:
|
||||
async def a_ban(
|
||||
qq: int, time: int, user_name: str, event: MessageEvent, ban_level: int = None
|
||||
) -> str:
|
||||
"""
|
||||
ban
|
||||
:param qq: qq
|
||||
@@ -38,7 +40,8 @@ async def a_ban(qq: int, time: int, user_name: str, event: MessageEvent, ban_lev
|
||||
ban_level = await LevelUser.get_user_level(event.user_id, event.group_id)
|
||||
if await BanUser.ban(qq, ban_level, time):
|
||||
logger.info(
|
||||
f"USER {event.user_id} GROUP {event.group_id} 将 USER {qq} 封禁 时长 {time / 60} 分钟"
|
||||
f"USER {event.user_id} GROUP"
|
||||
f" {event.group_id if isinstance(event, GroupMessageEvent) else ''} 将 USER {qq} 封禁 时长 {time / 60} 分钟"
|
||||
)
|
||||
result = f"已经将 {user_name} 加入{NICKNAME}的黑名单了!"
|
||||
if time != -1:
|
||||
@@ -57,13 +60,3 @@ async def a_ban(qq: int, time: int, user_name: str, event: MessageEvent, ban_lev
|
||||
time += " 分钟"
|
||||
result = f"{user_name} 已在黑名单!预计 {time}后解封"
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import nonebot
|
||||
|
||||
nonebot.load_plugins("basic_plugins/chat_history")
|
||||
@@ -0,0 +1,6 @@
|
||||
from nonebot.adapters.onebot.v11 import Event, MessageEvent
|
||||
from configs.config import Config
|
||||
|
||||
|
||||
def rule(event: Event) -> bool:
|
||||
return Config.get_config("chat_history", "FLAG") and isinstance(event, MessageEvent)
|
||||
@@ -0,0 +1,39 @@
|
||||
from configs.config import Config
|
||||
from models.chat_history import ChatHistory
|
||||
from nonebot import on_message
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent
|
||||
from utils.utils import get_message_text
|
||||
|
||||
from ._rule import rule
|
||||
|
||||
__zx_plugin_name__ = "消息存储 [Hidden]"
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
|
||||
|
||||
Config.add_plugin_config(
|
||||
"chat_history", "FLAG", True, help_="是否开启消息自从存储", name="消息存储", default_value=True
|
||||
)
|
||||
|
||||
|
||||
chat_history = on_message(rule=rule, priority=1, block=False)
|
||||
|
||||
|
||||
@chat_history.handle()
|
||||
async def _(event: MessageEvent):
|
||||
if isinstance(event, GroupMessageEvent):
|
||||
await ChatHistory.add_chat_msg(
|
||||
event.user_id, event.group_id, str(event.get_message()), get_message_text(event.json())
|
||||
)
|
||||
else:
|
||||
await ChatHistory.add_chat_msg(event.user_id, None, str(event.get_message()), get_message_text(event.json()))
|
||||
|
||||
|
||||
# @test.handle()
|
||||
# async def _(event: MessageEvent):
|
||||
# print(await ChatHistory.get_user_msg(event.user_id, "private"))
|
||||
# print(await ChatHistory.get_user_msg_count(event.user_id, "private"))
|
||||
# print(await ChatHistory.get_user_msg(event.user_id, "group"))
|
||||
# print(await ChatHistory.get_user_msg_count(event.user_id, "group"))
|
||||
# print(await ChatHistory.get_group_msg(event.group_id))
|
||||
# print(await ChatHistory.get_group_msg_count(event.group_id))
|
||||
@@ -0,0 +1,108 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytz
|
||||
from models.chat_history import ChatHistory
|
||||
from models.group_member_info import GroupInfoUser
|
||||
from nonebot import on_regex
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent
|
||||
from nonebot.params import RegexGroup
|
||||
from utils.image_utils import BuildImage, text2image
|
||||
from utils.utils import is_number
|
||||
from utils.message_builder import image
|
||||
from typing import Tuple, Any
|
||||
|
||||
|
||||
__zx_plugin_name__ = "消息统计"
|
||||
__plugin_usage__ = """
|
||||
usage:
|
||||
发言记录统计
|
||||
regex:(周|月)?消息排行(des|DES)?(n=[0-9]{1,2})?
|
||||
指令:
|
||||
消息统计?(des)?(n=?)
|
||||
周消息统计?(des)?(n=?)
|
||||
月消息统计?(des)?(n=?)
|
||||
示例:
|
||||
消息统计
|
||||
消息统计des
|
||||
消息统计DESn=15
|
||||
消息统计n=15
|
||||
""".strip()
|
||||
__plugin_des__ = "发言消息排行"
|
||||
__plugin_cmd__ = [
|
||||
"消息统计",
|
||||
"周消息统计",
|
||||
"月消息统计"
|
||||
]
|
||||
__plugin_type__ = ("数据统计", 1)
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
__plugin_settings__ = {
|
||||
"level": 5,
|
||||
"cmd": ["消息统计"],
|
||||
}
|
||||
|
||||
|
||||
msg_handler = on_regex(r"^(周|月)?消息统计(des|DES)?(n=[0-9]{1,2})?$", priority=5, block=True)
|
||||
|
||||
|
||||
@msg_handler.handle()
|
||||
async def _(event: GroupMessageEvent, reg_group: Tuple[Any, ...] = RegexGroup()):
|
||||
gid = event.group_id
|
||||
date_scope = None
|
||||
date, order, num = reg_group
|
||||
num = num.split("=")[-1] if num else 10
|
||||
if num and is_number(num) and 10 < int(num) < 50:
|
||||
num = int(num)
|
||||
if date in ["周"]:
|
||||
date_scope = (datetime.now() - timedelta(days=7), datetime.now())
|
||||
elif date in ["月"]:
|
||||
date_scope = (datetime.now() - timedelta(days=30), datetime.now())
|
||||
if rank_data := await ChatHistory.get_group_msg_rank(
|
||||
gid, num, order or "DESC", date_scope
|
||||
):
|
||||
name = "昵称:\n\n"
|
||||
num_str = "发言次数:\n\n"
|
||||
idx = 1
|
||||
for uid, num in rank_data:
|
||||
try:
|
||||
user_name = (await GroupInfoUser.get_member_info(uid, gid)).user_name
|
||||
except AttributeError:
|
||||
user_name = uid
|
||||
name += f"\t{idx}.{user_name} \n\n"
|
||||
num_str += f"\t{num}\n\n"
|
||||
idx += 1
|
||||
name_img = await text2image(name.strip(), padding=10, color="#f9f6f2")
|
||||
num_img = await text2image(num_str.strip(), padding=10, color="#f9f6f2")
|
||||
if not date_scope:
|
||||
if date_scope := await ChatHistory.get_group_first_msg_datetime(gid):
|
||||
date_scope = date_scope.astimezone(
|
||||
pytz.timezone("Asia/Shanghai")
|
||||
).replace(microsecond=0)
|
||||
else:
|
||||
date_scope = datetime.now().replace(microsecond=0)
|
||||
date_str = f"日期:{date_scope} - 至今"
|
||||
else:
|
||||
date_str = f"日期:{date_scope[0].replace(microsecond=0)} - {date_scope[1].replace(microsecond=0)}"
|
||||
date_w = BuildImage(0, 0, font_size=15).getsize(date_str)[0]
|
||||
img_w = date_w if date_w > name_img.w + num_img.w else name_img.w + num_img.w
|
||||
A = BuildImage(
|
||||
img_w + 15,
|
||||
num_img.h + 30,
|
||||
color="#f9f6f2",
|
||||
font="CJGaoDeGuo.otf",
|
||||
font_size=15,
|
||||
)
|
||||
await A.atext((10, 10), date_str)
|
||||
await A.apaste(name_img, (0, 30))
|
||||
await A.apaste(num_img, (name_img.w, 30))
|
||||
await msg_handler.send(image(b64=A.pic2bs4()))
|
||||
|
||||
|
||||
# @test.handle()
|
||||
# async def _(event: MessageEvent):
|
||||
# print(await ChatHistory.get_user_msg(event.user_id, "private"))
|
||||
# print(await ChatHistory.get_user_msg_count(event.user_id, "private"))
|
||||
# print(await ChatHistory.get_user_msg(event.user_id, "group"))
|
||||
# print(await ChatHistory.get_user_msg_count(event.user_id, "group"))
|
||||
# print(await ChatHistory.get_group_msg(event.group_id))
|
||||
# print(await ChatHistory.get_group_msg_count(event.group_id))
|
||||
@@ -1,15 +1,16 @@
|
||||
from nonebot import on_notice, on_request
|
||||
from configs.path_config import IMAGE_PATH, DATA_PATH
|
||||
from models.level_user import LevelUser
|
||||
from utils.message_builder import image
|
||||
from models.group_member_info import GroupInfoUser
|
||||
from datetime import datetime
|
||||
from services.log import logger
|
||||
from nonebot.adapters.onebot.v11 import (
|
||||
Bot,
|
||||
ActionFailed,
|
||||
GroupIncreaseNoticeEvent,
|
||||
GroupDecreaseNoticeEvent,
|
||||
)
|
||||
from nonebot.adapters.onebot.v11.exception import ActionFailed
|
||||
from utils.manager import group_manager, plugins2settings_manager, requests_manager
|
||||
from configs.config import NICKNAME
|
||||
from models.group_info import GroupInfo
|
||||
@@ -96,6 +97,23 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent):
|
||||
for plugin in data.keys():
|
||||
if not data[plugin]["default_status"]:
|
||||
group_manager.block_plugin(plugin, event.group_id)
|
||||
# 即刻刷新权限
|
||||
for user_info in await bot.get_group_member_list(group_id=event.group_id):
|
||||
if user_info["role"] in [
|
||||
"owner",
|
||||
"admin",
|
||||
] and not await LevelUser.is_group_flag(
|
||||
user_info["user_id"], event.group_id
|
||||
):
|
||||
await LevelUser.set_level(
|
||||
user_info["user_id"],
|
||||
user_info["group_id"],
|
||||
Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH"),
|
||||
)
|
||||
if str(user_info["user_id"]) in bot.config.superusers:
|
||||
await LevelUser.set_level(
|
||||
user_info["user_id"], user_info["group_id"], 9
|
||||
)
|
||||
else:
|
||||
join_time = datetime.now()
|
||||
user_info = await bot.get_group_member_info(
|
||||
@@ -128,9 +146,7 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent):
|
||||
msg = msg.replace("[at]", "")
|
||||
at_flag = True
|
||||
if (DATA_PATH / "custom_welcome_msg" / f"{event.group_id}.jpg").exists():
|
||||
img = image(
|
||||
DATA_PATH / "custom_welcome_msg" / f"{event.group_id}.jpg"
|
||||
)
|
||||
img = image(DATA_PATH / "custom_welcome_msg" / f"{event.group_id}.jpg")
|
||||
if msg or img:
|
||||
msg = msg.strip() + img
|
||||
msg = "\n" + msg if at_flag else msg
|
||||
|
||||
@@ -56,6 +56,9 @@ def _create_help_img(
|
||||
for matcher in _matchers:
|
||||
plugin_name = None
|
||||
_plugin = nonebot.plugin.get_plugin(matcher.plugin_name)
|
||||
if not _plugin:
|
||||
logger.warning(f"获取 功能:{matcher.plugin_name} 失败...")
|
||||
continue
|
||||
_module = _plugin.module
|
||||
try:
|
||||
plugin_name = _module.__getattribute__("__zx_plugin_name__")
|
||||
@@ -277,7 +280,7 @@ def _create_help_img(
|
||||
0,
|
||||
plain_text=msg,
|
||||
font_size=24,
|
||||
font="yuanshen.ttf",
|
||||
font="HYWenHei-85W.ttf",
|
||||
)
|
||||
B.paste(text, (w, h), True)
|
||||
h += 50
|
||||
@@ -289,7 +292,7 @@ def _create_help_img(
|
||||
0,
|
||||
plain_text="注: 红字代表功能被群管理员禁用,红线代表功能正在维护",
|
||||
font_size=24,
|
||||
font="yuanshen.ttf",
|
||||
font="HYWenHei-85W.ttf",
|
||||
font_color=(231, 74, 57)
|
||||
),
|
||||
(300, 10),
|
||||
|
||||
@@ -2,6 +2,10 @@ from nonebot.adapters.onebot.v11 import GroupMessageEvent, PrivateMessageEvent
|
||||
from utils.manager import plugins2block_manager, StaticData
|
||||
import time
|
||||
|
||||
ignore_rst_module = ["ai", "poke", "dialogue"]
|
||||
|
||||
other_limit_plugins = ["poke"]
|
||||
|
||||
|
||||
class StatusMessageManager(StaticData):
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.message import run_preprocessor, run_postprocessor, IgnoredException
|
||||
from nonebot.adapters.onebot.v11.exception import ActionFailed
|
||||
from models.friend_user import FriendUser
|
||||
from models.group_member_info import GroupInfoUser
|
||||
from models.bag_user import BagUser
|
||||
@@ -13,11 +12,17 @@ from utils.manager import (
|
||||
plugins2block_manager,
|
||||
plugins2count_manager,
|
||||
)
|
||||
from ._utils import set_block_limit_false, status_message_manager
|
||||
from ._utils import (
|
||||
set_block_limit_false,
|
||||
status_message_manager,
|
||||
ignore_rst_module,
|
||||
other_limit_plugins,
|
||||
)
|
||||
from nonebot.typing import T_State
|
||||
from typing import Optional
|
||||
from nonebot.adapters.onebot.v11 import (
|
||||
Bot,
|
||||
ActionFailed,
|
||||
MessageEvent,
|
||||
GroupMessageEvent,
|
||||
PokeNotifyEvent,
|
||||
@@ -37,12 +42,10 @@ _flmt_g = FreqLimiter(Config.get_config("hook", "CHECK_NOTICE_INFO_CD"))
|
||||
_flmt_s = FreqLimiter(Config.get_config("hook", "CHECK_NOTICE_INFO_CD"))
|
||||
_flmt_c = FreqLimiter(Config.get_config("hook", "CHECK_NOTICE_INFO_CD"))
|
||||
|
||||
ignore_rst_module = ["ai", "poke", "dialogue"]
|
||||
|
||||
|
||||
# 权限检测
|
||||
@run_preprocessor
|
||||
async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
|
||||
async def _(matcher: Matcher, bot: Bot, event: Event, state: T_State):
|
||||
module = matcher.plugin_name
|
||||
plugins2info_dict = plugins2settings_manager.get_data()
|
||||
# 功能的金币检测 #######################################
|
||||
@@ -64,7 +67,7 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
|
||||
await BagUser.spend_gold(event.user_id, event.group_id, cost_gold)
|
||||
try:
|
||||
if (
|
||||
(not isinstance(event, MessageEvent) and module != "poke")
|
||||
(not isinstance(event, MessageEvent) and module not in other_limit_plugins)
|
||||
or await BanUser.is_ban(event.user_id)
|
||||
and str(event.user_id) not in bot.config.superusers
|
||||
) or (
|
||||
@@ -88,7 +91,10 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
|
||||
except AttributeError:
|
||||
pass
|
||||
# 群黑名单检测 群总开关检测
|
||||
if isinstance(event, GroupMessageEvent) or matcher.plugin_name == "poke":
|
||||
if (
|
||||
isinstance(event, GroupMessageEvent)
|
||||
or matcher.plugin_name in other_limit_plugins
|
||||
):
|
||||
try:
|
||||
if (
|
||||
group_manager.get_group_level(event.group_id) < 0
|
||||
@@ -103,7 +109,7 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
|
||||
raise IgnoredException("功能总开关关闭状态")
|
||||
except AttributeError:
|
||||
pass
|
||||
if module in admin_manager.keys() and matcher.priority not in [1, 9]:
|
||||
if module in admin_manager.keys() and matcher.priority not in [1, 999]:
|
||||
if isinstance(event, GroupMessageEvent):
|
||||
# 个人权限
|
||||
if (
|
||||
@@ -143,10 +149,12 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
|
||||
if event.is_tome():
|
||||
status_message_manager.add(event.user_id)
|
||||
raise IgnoredException("权限不足")
|
||||
if module in plugins2info_dict.keys() and matcher.priority not in [1, 9]:
|
||||
if module in plugins2info_dict.keys() and matcher.priority not in [1, 999]:
|
||||
# 戳一戳单独判断
|
||||
if isinstance(event, GroupMessageEvent) or (
|
||||
isinstance(event, PokeNotifyEvent) and event.group_id
|
||||
if (
|
||||
isinstance(event, GroupMessageEvent)
|
||||
or isinstance(event, PokeNotifyEvent)
|
||||
or matcher.plugin_name in other_limit_plugins
|
||||
):
|
||||
if status_message_manager.get(event.group_id) is None:
|
||||
status_message_manager.delete(event.group_id)
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.message import run_preprocessor, IgnoredException
|
||||
from nonebot.adapters.onebot.v11.exception import ActionFailed
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters.onebot.v11 import (
|
||||
Bot,
|
||||
Event,
|
||||
MessageEvent,
|
||||
ActionFailed,
|
||||
PokeNotifyEvent,
|
||||
GroupMessageEvent,
|
||||
)
|
||||
from configs.config import Config
|
||||
from models.ban_user import BanUser
|
||||
from utils.utils import is_number, static_flmt, FreqLimiter
|
||||
from utils.message_builder import at
|
||||
from ._utils import ignore_rst_module, other_limit_plugins
|
||||
|
||||
|
||||
Config.add_plugin_config(
|
||||
@@ -25,18 +28,19 @@ _flmt = FreqLimiter(300)
|
||||
|
||||
# 检查是否被ban
|
||||
@run_preprocessor
|
||||
async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
|
||||
try:
|
||||
if (
|
||||
await BanUser.is_super_ban(event.user_id)
|
||||
and str(event.user_id) not in bot.config.superusers
|
||||
):
|
||||
raise IgnoredException("用户处于超级黑名单中")
|
||||
except AttributeError:
|
||||
pass
|
||||
if not isinstance(event, MessageEvent):
|
||||
return
|
||||
if matcher.type == "message" and matcher.priority not in [1, 9]:
|
||||
async def _(matcher: Matcher, bot: Bot, event: Event, state: T_State):
|
||||
if (
|
||||
(isinstance(event, MessageEvent) or isinstance(event, PokeNotifyEvent))
|
||||
and matcher.priority not in [1, 999]
|
||||
) or matcher.plugin_name in other_limit_plugins:
|
||||
try:
|
||||
if (
|
||||
await BanUser.is_super_ban(event.user_id)
|
||||
and str(event.user_id) not in bot.config.superusers
|
||||
):
|
||||
raise IgnoredException("用户处于超级黑名单中")
|
||||
except AttributeError:
|
||||
pass
|
||||
if (
|
||||
await BanUser.is_ban(event.user_id)
|
||||
and str(event.user_id) not in bot.config.superusers
|
||||
@@ -54,10 +58,14 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
|
||||
if not static_flmt.check(event.user_id):
|
||||
raise IgnoredException("用户处于黑名单中")
|
||||
static_flmt.start_cd(event.user_id)
|
||||
if matcher.priority != 9:
|
||||
if matcher.priority != 999:
|
||||
try:
|
||||
ban_result = Config.get_config("hook", "BAN_RESULT")
|
||||
if ban_result and _flmt.check(event.user_id):
|
||||
if (
|
||||
ban_result
|
||||
and _flmt.check(event.user_id)
|
||||
and matcher.plugin_name not in ignore_rst_module
|
||||
):
|
||||
_flmt.start_cd(event.user_id)
|
||||
await bot.send_group_msg(
|
||||
group_id=event.group_id,
|
||||
@@ -71,10 +79,10 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
|
||||
if not static_flmt.check(event.user_id):
|
||||
raise IgnoredException("用户处于黑名单中")
|
||||
static_flmt.start_cd(event.user_id)
|
||||
if matcher.priority != 9:
|
||||
if matcher.priority != 999:
|
||||
try:
|
||||
ban_result = Config.get_config("hook", "BAN_RESULT")
|
||||
if ban_result:
|
||||
if ban_result and matcher.plugin_name not in ignore_rst_module:
|
||||
await bot.send_private_msg(
|
||||
user_id=event.user_id,
|
||||
message=at(event.user_id)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.message import run_preprocessor, IgnoredException
|
||||
from nonebot.adapters.onebot.v11.exception import ActionFailed
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters.onebot.v11 import (
|
||||
Bot,
|
||||
ActionFailed,
|
||||
MessageEvent,
|
||||
GroupMessageEvent,
|
||||
)
|
||||
@@ -25,7 +25,7 @@ _blmt = BanCheckLimiter(
|
||||
async def _(matcher: Matcher, bot: Bot, event: GroupMessageEvent, state: T_State):
|
||||
if not isinstance(event, MessageEvent):
|
||||
return
|
||||
if matcher.type == "message" and matcher.priority not in [1, 9]:
|
||||
if matcher.type == "message" and matcher.priority not in [1, 999]:
|
||||
if state["_prefix"]["raw_command"]:
|
||||
if _blmt.check(f'{event.user_id}{state["_prefix"]["raw_command"]}'):
|
||||
if await BanUser.ban(
|
||||
|
||||
@@ -2,12 +2,15 @@ from nonebot.matcher import Matcher
|
||||
from nonebot.message import run_preprocessor, IgnoredException
|
||||
from nonebot.typing import T_State
|
||||
from ._utils import status_message_manager
|
||||
from utils.image_utils import text2image
|
||||
from typing import Dict, Any
|
||||
from nonebot.adapters.onebot.v11 import (
|
||||
Bot,
|
||||
MessageEvent,
|
||||
PrivateMessageEvent,
|
||||
GroupMessageEvent,
|
||||
)
|
||||
import re
|
||||
|
||||
|
||||
# 为什么AI会自己和自己聊天
|
||||
@@ -38,3 +41,11 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
|
||||
status_message_manager.delete(event.user_id)
|
||||
raise IgnoredException("有命令就别说话了")
|
||||
|
||||
# @Bot.on_calling_api
|
||||
# async def handle_api_call(bot: Bot, api: str, data: Dict[str, Any]):
|
||||
# if api in ["send_msg", "send_group_msg", "send_private_msg"]:
|
||||
# msg = str(data["message"])
|
||||
# if (r := re.search("\[\[To_Img\|?(.*?)]]", msg)) or (r := re.search("[[To_Img\|?(.*?)[[")):
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ async def handle_api_call(bot: Bot, api: str, data: Dict[str, Any]):
|
||||
r = None
|
||||
if (
|
||||
(
|
||||
(api == "send_msg" and data["message_type"] == "group")
|
||||
(api == "send_msg" and data.get("message_type") == "group")
|
||||
or api == "send_group_msg"
|
||||
)
|
||||
and (
|
||||
|
||||
@@ -20,26 +20,36 @@ def init_none_plugin_count_manager():
|
||||
"""
|
||||
清除已删除插件数据
|
||||
"""
|
||||
modules = [x.module for x in get_matchers()]
|
||||
for module in none_plugin_count_manager.keys():
|
||||
if module not in modules:
|
||||
none_plugin_count_manager.add_count(module)
|
||||
else:
|
||||
none_plugin_count_manager.reset(module)
|
||||
if none_plugin_count_manager.check(module):
|
||||
modules = [x.plugin_name for x in get_matchers()]
|
||||
plugins_manager_list = list(plugins_manager.keys())
|
||||
for module in plugins_manager_list:
|
||||
if module not in modules or none_plugin_count_manager.check(module):
|
||||
try:
|
||||
plugin_name = plugins_manager.get(module)["plugin_name"]
|
||||
except (AttributeError, KeyError):
|
||||
plugin_name = ""
|
||||
try:
|
||||
plugins2settings_manager.delete(module)
|
||||
plugins2count_manager.delete(module)
|
||||
plugins2cd_manager.delete(module)
|
||||
plugins2block_manager.delete(module)
|
||||
plugins_manager.delete(module)
|
||||
resources_manager.remove_resource(module)
|
||||
logger.info(f"{module}:{plugin_name} 插件疑似已删除,清除对应插件数据...")
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"{module}:{plugin_name} 插件疑似已删除,清除对应插件数据失败...{type(e)}:{e}"
|
||||
)
|
||||
if none_plugin_count_manager.check(module):
|
||||
try:
|
||||
plugins2settings_manager.delete(module)
|
||||
plugins2settings_manager.save()
|
||||
plugins2count_manager.delete(module)
|
||||
plugins2count_manager.save()
|
||||
plugins2cd_manager.delete(module)
|
||||
plugins2cd_manager.save()
|
||||
plugins2block_manager.delete(module)
|
||||
plugins2block_manager.save()
|
||||
plugins_manager.delete(module)
|
||||
plugins_manager.save()
|
||||
# resources_manager.remove_resource(module)
|
||||
none_plugin_count_manager.delete(module)
|
||||
logger.info(f"{module}:{plugin_name} 插件疑似已删除,清除对应插件数据...")
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f"{module}:{plugin_name} 插件疑似已删除,清除对应插件数据失败...{type(e)}:{e}")
|
||||
else:
|
||||
none_plugin_count_manager.add_count(module)
|
||||
logger.info(
|
||||
f"{module}:{plugin_name} 插件疑似已删除,加载{none_plugin_count_manager._max_count}次失败后将清除对应插件数据,当前次数:{none_plugin_count_manager._data[module]}")
|
||||
else:
|
||||
none_plugin_count_manager.reset(module)
|
||||
none_plugin_count_manager.save()
|
||||
|
||||
@@ -3,6 +3,7 @@ from ruamel.yaml import round_trip_load, round_trip_dump, YAML
|
||||
from utils.manager import admin_manager, plugins_manager
|
||||
from configs.config import Config
|
||||
from services.log import logger
|
||||
from utils.text_utils import prompt2cn
|
||||
from utils.utils import get_matchers
|
||||
from ruamel import yaml
|
||||
import nonebot
|
||||
@@ -56,7 +57,9 @@ def init_plugins_config(data_path):
|
||||
_override=True,
|
||||
)
|
||||
else:
|
||||
Config.add_plugin_config(matcher.plugin_name, key, plugin_configs[key])
|
||||
Config.add_plugin_config(
|
||||
matcher.plugin_name, key, plugin_configs[key]
|
||||
)
|
||||
else:
|
||||
plugin_configs = _data[matcher.plugin_name]
|
||||
for key in plugin_configs:
|
||||
@@ -79,7 +82,10 @@ def init_plugins_config(data_path):
|
||||
_data[plugin].yaml_set_start_comment(plugin_name, indent=2)
|
||||
# 初始化未设置的管理员权限等级
|
||||
for k, v in Config.get_admin_level_data():
|
||||
admin_manager.set_admin_level(k, v)
|
||||
try:
|
||||
admin_manager.set_admin_level(k, v)
|
||||
except KeyError as e:
|
||||
raise KeyError(f"{e} ****** 请检查是否有插件加载失败 ******")
|
||||
# 存完插件基本设置
|
||||
with open(plugins2config_file, "w", encoding="utf8") as wf:
|
||||
round_trip_dump(
|
||||
@@ -96,14 +102,23 @@ def init_plugins_config(data_path):
|
||||
for plugin in Config.keys():
|
||||
_tmp_data[plugin] = {}
|
||||
for k in Config[plugin].keys():
|
||||
if _data.get(plugin) and k in _data[plugin].keys():
|
||||
Config.set_config(plugin, k, _data[plugin][k])
|
||||
if level2module := Config.get_level2module(plugin, k):
|
||||
try:
|
||||
admin_manager.set_admin_level(level2module, _data[plugin][k])
|
||||
except KeyError:
|
||||
logger.warning(f"{level2module} 设置权限等级失败:{_data[plugin][k]}")
|
||||
_tmp_data[plugin][k] = Config.get_config(plugin, k)
|
||||
try:
|
||||
if _data.get(plugin) and k in _data[plugin].keys():
|
||||
Config.set_config(plugin, k, _data[plugin][k])
|
||||
if level2module := Config.get_level2module(plugin, k):
|
||||
try:
|
||||
admin_manager.set_admin_level(
|
||||
level2module, _data[plugin][k]
|
||||
)
|
||||
except KeyError:
|
||||
logger.warning(
|
||||
f"{level2module} 设置权限等级失败:{_data[plugin][k]}"
|
||||
)
|
||||
_tmp_data[plugin][k] = Config.get_config(plugin, k)
|
||||
except AttributeError as e:
|
||||
raise AttributeError(
|
||||
f"{e}\n" + prompt2cn("可能为config.yaml配置文件填写不规范", 46)
|
||||
)
|
||||
Config.save()
|
||||
temp_file = Path() / "configs" / "temp_config.yaml"
|
||||
try:
|
||||
|
||||
@@ -28,7 +28,7 @@ def init_plugins_cd_limit(data_path):
|
||||
_module = _plugin.module
|
||||
plugin_cd_limit = _module.__getattribute__("__plugin_cd_limit__")
|
||||
plugins2cd_manager.add_cd_limit(
|
||||
matcher.plugin_name, data_dict=plugin_cd_limit
|
||||
matcher.plugin_name, **plugin_cd_limit
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
@@ -76,7 +76,7 @@ def init_plugins_block_limit(data_path):
|
||||
_module = _plugin.module
|
||||
plugin_block_limit = _module.__getattribute__("__plugin_block_limit__")
|
||||
plugins2block_manager.add_block_limit(
|
||||
matcher.plugin_name, data_dict=plugin_block_limit
|
||||
matcher.plugin_name, **plugin_block_limit
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
@@ -124,7 +124,7 @@ def init_plugins_count_limit(data_path):
|
||||
_module = _plugin.module
|
||||
plugin_count_limit = _module.__getattribute__("__plugin_count_limit__")
|
||||
plugins2count_manager.add_count_limit(
|
||||
matcher.plugin_name, data_dict=plugin_count_limit
|
||||
matcher.plugin_name, **plugin_count_limit
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
@@ -74,7 +74,7 @@ def init_plugins_settings(data_path: str):
|
||||
if plugin_settings.get('cost_gold') is None:
|
||||
plugin_settings['cost_gold'] = 0
|
||||
if (
|
||||
plugin_settings["cmd"] is not None
|
||||
plugin_settings.get("cmd") is not None
|
||||
and plugin_name not in plugin_settings["cmd"]
|
||||
):
|
||||
plugin_settings["cmd"].append(plugin_name)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from nonebot import on_request, on_message
|
||||
from nonebot.adapters.onebot.v11 import (
|
||||
Bot,
|
||||
ActionFailed,
|
||||
FriendRequestEvent,
|
||||
GroupRequestEvent,
|
||||
MessageEvent,
|
||||
@@ -8,7 +9,6 @@ from nonebot.adapters.onebot.v11 import (
|
||||
from models.friend_user import FriendUser
|
||||
from datetime import datetime
|
||||
from configs.config import NICKNAME, Config
|
||||
from nonebot.adapters.onebot.v11.exception import ActionFailed
|
||||
from utils.manager import requests_manager
|
||||
from models.group_info import GroupInfo
|
||||
from utils.utils import scheduler
|
||||
@@ -25,7 +25,7 @@ __plugin_configs__ = {
|
||||
|
||||
friend_req = on_request(priority=5, block=True)
|
||||
group_req = on_request(priority=5, block=True)
|
||||
x = on_message(priority=9, block=False)
|
||||
x = on_message(priority=999, block=False)
|
||||
|
||||
exists_data = {"private": {}, "group": {}}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, PrivateMessageEvent, Message
|
||||
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, PrivateMessageEvent, Message, MessageEvent
|
||||
from nonebot import on_command
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.rule import to_me
|
||||
@@ -22,9 +22,15 @@ __plugin_des__ = "区区昵称,才不想叫呢!"
|
||||
__plugin_cmd__ = ["以后叫我 [昵称]", f"{NICKNAME}我是谁"]
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
__plugin_settings__ = {
|
||||
"level": 5,
|
||||
"default_status": True,
|
||||
"limit_superuser": False,
|
||||
"cmd": ["昵称", "昵称系统"],
|
||||
}
|
||||
__plugin_configs__ = {
|
||||
"BLACK_WORD": {
|
||||
"value": ["爸", "爹", "爷"],
|
||||
"value": ["爸", "爹", "爷", "父亲"],
|
||||
"help": "昵称所屏蔽的关键词,会被替换为 *",
|
||||
"default_value": None
|
||||
}
|
||||
@@ -47,7 +53,7 @@ cancel_nickname = on_command("取消昵称", rule=to_me(), priority=5, block=Tru
|
||||
|
||||
|
||||
@nickname.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent, arg: Message = CommandArg()):
|
||||
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
|
||||
msg = arg.extract_plain_text().strip()
|
||||
if not msg:
|
||||
await nickname.finish("叫你空白?叫你虚空?叫你无名??", at_sender=True)
|
||||
@@ -57,8 +63,9 @@ async def _(bot: Bot, event: GroupMessageEvent, arg: Message = CommandArg()):
|
||||
await nickname.finish("笨蛋!休想占用我的名字!#", at_sender=True)
|
||||
_tmp = ""
|
||||
black_word = Config.get_config("nickname", "BLACK_WORD")
|
||||
for x in msg:
|
||||
_tmp += "*" if x in black_word else x
|
||||
if black_word:
|
||||
for x in msg:
|
||||
_tmp += "*" if x in black_word else x
|
||||
msg = _tmp
|
||||
if isinstance(event, GroupMessageEvent):
|
||||
if await GroupInfoUser.set_group_member_nickname(
|
||||
@@ -73,7 +80,7 @@ async def _(bot: Bot, event: GroupMessageEvent, arg: Message = CommandArg()):
|
||||
f"好啦好啦,我知道啦,{msg},以后就这么叫你吧",
|
||||
f"嗯嗯,{NICKNAME}记住你的昵称了哦,{msg}",
|
||||
f"好突然,突然要叫你昵称什么的...{msg}..",
|
||||
f"{NICKNAME}会好好记住的{msg}的,放心吧",
|
||||
f"{NICKNAME}会好好记住{msg}的,放心吧",
|
||||
f"好..好.,那窝以后就叫你{msg}了.",
|
||||
]
|
||||
)
|
||||
@@ -90,7 +97,7 @@ async def _(bot: Bot, event: GroupMessageEvent, arg: Message = CommandArg()):
|
||||
f"好啦好啦,我知道啦,{msg},以后就这么叫你吧",
|
||||
f"嗯嗯,{NICKNAME}记住你的昵称了哦,{msg}",
|
||||
f"好突然,突然要叫你昵称什么的...{msg}..",
|
||||
f"{NICKNAME}会好好记住的{msg}的,放心吧",
|
||||
f"{NICKNAME}会好好记住{msg}的,放心吧",
|
||||
f"好..好.,那窝以后就叫你{msg}了.",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
from nonebot import on_command, on_regex
|
||||
|
||||
from .data_source import show_plugin_repo, install_plugin, uninstall_plugin, download_json
|
||||
from services.log import logger
|
||||
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent
|
||||
from nonebot.params import CommandArg
|
||||
|
||||
from utils.message_builder import image
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
|
||||
__zx_plugin_name__ = "插件商店 [Superuser]"
|
||||
__plugin_usage__ = """
|
||||
usage:
|
||||
下载安装插件
|
||||
指令:
|
||||
查看插件仓库
|
||||
更新插件仓库
|
||||
安装插件 [name/id] (重新安装等同于更新)
|
||||
卸载插件 [name/id]
|
||||
""".strip()
|
||||
__plugin_des__ = "从真寻适配仓库中下载插件"
|
||||
__plugin_cmd__ = ["查看插件仓库", "更新插件仓库", "安装插件 [name/id]", "卸载插件 [name/id]"]
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
|
||||
show_repo = on_regex("^查看插件仓库$", priority=1, block=True, permission=SUPERUSER)
|
||||
|
||||
update_repo = on_regex("^更新插件仓库$", priority=1, block=True, permission=SUPERUSER)
|
||||
|
||||
install_plugin_matcher = on_command("安装插件", priority=1, block=True, permission=SUPERUSER)
|
||||
|
||||
uninstall_plugin_matcher = on_command("卸载插件", priority=1, block=True, permission=SUPERUSER)
|
||||
|
||||
|
||||
@install_plugin_matcher.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
|
||||
arg = arg.extract_plain_text().strip()
|
||||
msg = await install_plugin(arg)
|
||||
await install_plugin_matcher.send(msg)
|
||||
|
||||
|
||||
@uninstall_plugin_matcher.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
|
||||
arg = arg.extract_plain_text().strip()
|
||||
msg = await uninstall_plugin(arg)
|
||||
await install_plugin_matcher.send(msg)
|
||||
|
||||
|
||||
@update_repo.handle()
|
||||
async def _(bot: Bot, event: MessageEvent):
|
||||
code = await download_json()
|
||||
if code == 200:
|
||||
await update_repo.finish("更新插件仓库信息成功!")
|
||||
await update_repo.send("更新插件仓库信息失败!")
|
||||
|
||||
|
||||
@show_repo.handle()
|
||||
async def _(bot: Bot, event: MessageEvent):
|
||||
msg = await show_plugin_repo()
|
||||
if isinstance(msg, int):
|
||||
await show_repo.finish("文件下载失败或解压失败..")
|
||||
await show_repo.send(image(b64=msg))
|
||||
logger.info(
|
||||
f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
|
||||
f" 查看插件仓库"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Union, Tuple
|
||||
from utils.manager import plugins_manager
|
||||
|
||||
import ujson as json
|
||||
|
||||
from services import logger
|
||||
from utils.http_utils import AsyncHttpx
|
||||
from configs.path_config import TEMP_PATH, DATA_PATH
|
||||
from utils.image_utils import text2image, BuildImage
|
||||
from utils.utils import is_number
|
||||
|
||||
path = DATA_PATH / "plugin_shop"
|
||||
if not path.exists():
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
repo_json_url = "https://github.com/zhenxun-org/nonebot_plugins_zhenxun_bot/archive/refs/heads/index.zip"
|
||||
repo_zip_path = TEMP_PATH / "plugin_repo_json.zip"
|
||||
plugin_json = path / "zhenxun_plugins.json"
|
||||
|
||||
extensive_plugin_path = Path() / "extensive_plugin"
|
||||
|
||||
path = DATA_PATH / "plugin_shop"
|
||||
if not path.exists():
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
if not extensive_plugin_path.exists():
|
||||
extensive_plugin_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
data = {}
|
||||
|
||||
|
||||
async def install_plugin(name: str) -> str:
|
||||
"""
|
||||
安装插件
|
||||
:param name: 插件名或下标
|
||||
"""
|
||||
try:
|
||||
if is_number(name):
|
||||
name, code = await get_plugin_name(int(name))
|
||||
if code != 200:
|
||||
return name
|
||||
plugin_url = data[name]["download_url"]
|
||||
url = (await AsyncHttpx.get(plugin_url)).headers.get("Location")
|
||||
zip_file = TEMP_PATH / f"{name}.zip"
|
||||
if zip_file.exists():
|
||||
zip_file.unlink()
|
||||
if await AsyncHttpx.download_file(url, zip_file):
|
||||
logger.debug("开始解压插件压缩包...")
|
||||
# 解压
|
||||
zf = zipfile.ZipFile(zip_file, "r")
|
||||
extract_path = TEMP_PATH / f"{name}"
|
||||
if extract_path.exists():
|
||||
shutil.rmtree(extract_path.absolute(), ignore_errors=True)
|
||||
extract_path.mkdir(exist_ok=True, parents=True)
|
||||
for file in zf.namelist():
|
||||
zf.extract(file, extract_path)
|
||||
zf.close()
|
||||
logger.debug("解压插件压缩包完成...")
|
||||
logger.debug("开始移动插件文件夹...")
|
||||
if (extensive_plugin_path / f"{name}").exists():
|
||||
logger.debug("extensive_plugin目录下文件夹已存在,删除该目录插件文件夹...")
|
||||
shutil.rmtree(
|
||||
(extensive_plugin_path / f"{name}").absolute(), ignore_errors=True
|
||||
)
|
||||
extract_path.rename(extensive_plugin_path / f"{name}")
|
||||
tmp = ""
|
||||
if "pyproject.toml" in os.listdir(extensive_plugin_path / f"{name}"):
|
||||
tmp = "检测到该插件含有额外依赖,当前安装无法保证依赖完全安装成功。"
|
||||
os.system(
|
||||
f"poetry run pip install -r {(extensive_plugin_path / f'{name}' / 'pyproject.toml').absolute()}"
|
||||
)
|
||||
elif "requirements.txt" in os.listdir(extensive_plugin_path / f"{name}"):
|
||||
tmp = "检测到该插件含有额外依赖,当前安装无法保证依赖完全安装成功。"
|
||||
os.system(
|
||||
f"poetry run pip install -r {(extensive_plugin_path / f'{name}' / 'requirements.txt').absolute()}"
|
||||
)
|
||||
with open(extensive_plugin_path / f"{name}" / "plugin_info.json", "w") as f:
|
||||
json.dump(data[name], f, ensure_ascii=False, indent=4)
|
||||
logger.debug("移动插件文件夹完成...")
|
||||
logger.info(f"成功安装插件 {name} 成功!\n{tmp}")
|
||||
return f"成功安装插件 {name},请重启真寻!"
|
||||
except Exception as e:
|
||||
logger.error(f"安装插件 {name} 失败 {type(e)}:{e}")
|
||||
return f"安装插件 {name} 失败 {type(e)}:{e}"
|
||||
|
||||
|
||||
async def uninstall_plugin(name: str) -> str:
|
||||
"""
|
||||
删除插件
|
||||
:param name: 插件名或下标
|
||||
"""
|
||||
try:
|
||||
if is_number(name):
|
||||
name, code = await get_plugin_name(int(name))
|
||||
if code != 200:
|
||||
return name
|
||||
if name not in os.listdir(extensive_plugin_path):
|
||||
return f"未安装 {name} 插件!"
|
||||
shutil.rmtree((extensive_plugin_path / name).absolute(), ignore_errors=True)
|
||||
logger.info(f"插件 {name} 删除成功!")
|
||||
return f"插件 {name} 删除成功!"
|
||||
except Exception as e:
|
||||
logger.error(f"删除插件 {name} 失败 {type(e)}:{e}")
|
||||
return f"删除插件 {name} 失败 {type(e)}:{e}"
|
||||
|
||||
|
||||
async def show_plugin_repo() -> Union[int, str]:
|
||||
"""
|
||||
获取插件仓库数据并格式化
|
||||
"""
|
||||
if not plugin_json.exists():
|
||||
code = await download_json()
|
||||
if code != 200:
|
||||
return code
|
||||
plugin_info = json.load(open(plugin_json, "r", encoding="utf8"))
|
||||
load_plugin_list = plugins_manager.get_data().keys()
|
||||
image_list = []
|
||||
w, h = 0, 0
|
||||
line_height = 10
|
||||
for i, key in enumerate(plugin_info.keys()):
|
||||
data[key] = {
|
||||
"名称": plugin_info[key]["plugin_name"],
|
||||
"模块": key,
|
||||
"作者": plugin_info[key]["author"],
|
||||
"版本": plugin_info[key]["version"],
|
||||
"简介": plugin_info[key]["introduction"],
|
||||
"download_url": plugin_info[key]["download_url"],
|
||||
"github_url": plugin_info[key]["github_url"],
|
||||
}
|
||||
status = ""
|
||||
version = ""
|
||||
if key in load_plugin_list:
|
||||
status = "<f font_color=#1a7e30>[已安装]</f>"
|
||||
version = f"<f font_color=#1a7e30>[{plugins_manager.get_data()[key]['version']}]</f>"
|
||||
s = (
|
||||
f'id:{i+1}\n名称:{plugin_info[key]["plugin_name"]}'
|
||||
f' \t\t{status}\n'
|
||||
f"模块:{key}\n"
|
||||
f'作者:{plugin_info[key]["author"]}\n'
|
||||
f'版本:{plugin_info[key]["version"]} \t\t{version}\n'
|
||||
f'简介:{plugin_info[key]["introduction"]}\n'
|
||||
f"-------------------"
|
||||
)
|
||||
img = await text2image(s, font_size=20, color="#f9f6f2")
|
||||
w = w if w > img.w else img.w
|
||||
h += img.h + line_height
|
||||
image_list.append(img)
|
||||
A = BuildImage(w + 50, h + 50, color="#f9f6f2")
|
||||
cur_h = 25
|
||||
for img in image_list:
|
||||
await A.apaste(img, (25, cur_h))
|
||||
cur_h += img.h + line_height
|
||||
return A.pic2bs4()
|
||||
|
||||
|
||||
async def download_json() -> int:
|
||||
"""
|
||||
下载插件库json文件
|
||||
"""
|
||||
try:
|
||||
url = (await AsyncHttpx.get(repo_json_url)).headers.get("Location")
|
||||
if repo_zip_path.exists():
|
||||
repo_zip_path.unlink()
|
||||
if await AsyncHttpx.download_file(url, repo_zip_path):
|
||||
zf = zipfile.ZipFile(repo_zip_path, "r")
|
||||
extract_path = path / "temp"
|
||||
for file in zf.namelist():
|
||||
zf.extract(file, extract_path)
|
||||
zf.close()
|
||||
if plugin_json.exists():
|
||||
plugin_json.unlink()
|
||||
(
|
||||
extract_path
|
||||
/ "nonebot_plugins_zhenxun_bot-index"
|
||||
/ "zhenxun_plugins.json"
|
||||
).rename(plugin_json)
|
||||
shutil.rmtree(extract_path.absolute(), ignore_errors=True)
|
||||
return 200
|
||||
except Exception as e:
|
||||
logger.error(f"下载插件库压缩包失败或解压失败 {type(e)}:{e}")
|
||||
return 999
|
||||
|
||||
|
||||
async def get_plugin_name(name: int) -> Tuple[str, int]:
|
||||
"""
|
||||
通过下标获取插件名
|
||||
:param name: 下标
|
||||
"""
|
||||
name = int(name)
|
||||
if not data:
|
||||
await show_plugin_repo()
|
||||
if name < 1 or name > len(data.keys()):
|
||||
return "下标超过上下限!", 999
|
||||
name = list(data.keys())[name - 1]
|
||||
return name, 200
|
||||
@@ -108,6 +108,10 @@ async def _():
|
||||
"ALTER TABLE genshin ADD bind_group Integer;",
|
||||
"genshin"
|
||||
), # 新增原神群号绑定字段
|
||||
(
|
||||
"ALTER TABLE chat_history ADD plain_text Text;",
|
||||
"chat_history"
|
||||
), # 新增纯文本
|
||||
]
|
||||
for sql in sql_str:
|
||||
try:
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message, ActionFailed
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.adapters.onebot.v11.permission import GROUP
|
||||
from utils.data_utils import init_rank
|
||||
from models.bag_user import BagUser
|
||||
from utils.image_utils import text2image
|
||||
from utils.message_builder import image
|
||||
from utils.utils import is_number
|
||||
|
||||
@@ -34,7 +35,11 @@ gold_rank = on_command("金币排行", priority=5, block=True, permission=GROUP)
|
||||
|
||||
@my_gold.handle()
|
||||
async def _(event: GroupMessageEvent):
|
||||
await my_gold.finish(await BagUser.get_user_total_gold(event.user_id, event.group_id))
|
||||
msg = await BagUser.get_user_total_gold(event.user_id, event.group_id)
|
||||
try:
|
||||
await my_gold.send(msg)
|
||||
except ActionFailed:
|
||||
await my_gold.send(image(b64=(await text2image(msg, color="#f9f6f2", padding=10)).pic2bs4()))
|
||||
|
||||
|
||||
@gold_rank.handle()
|
||||
|
||||
@@ -7,11 +7,13 @@ from typing import Optional, Union
|
||||
from configs.config import Config
|
||||
from nonebot import Driver
|
||||
from nonebot.plugin import require
|
||||
from utils.decorator.shop import shop_register
|
||||
import nonebot
|
||||
import time
|
||||
|
||||
driver: Driver = nonebot.get_driver()
|
||||
|
||||
|
||||
use = require("use")
|
||||
|
||||
|
||||
@@ -20,24 +22,26 @@ async def init_default_shop_goods():
|
||||
"""
|
||||
导入内置的三个商品
|
||||
"""
|
||||
async def sign_card(**kwargs):
|
||||
user_id = kwargs['user_id']
|
||||
group_id = kwargs['group_id']
|
||||
prob = kwargs["prob"]
|
||||
|
||||
@shop_register(
|
||||
name=("好感度双倍加持卡Ⅰ", "好感度双倍加持卡Ⅱ", "好感度双倍加持卡Ⅲ"),
|
||||
price=(30, 150, 250),
|
||||
des=(
|
||||
"下次签到双倍好感度概率 + 10%(谁才是真命天子?)(同类商品将覆盖)",
|
||||
"下次签到双倍好感度概率 + 20%(平平庸庸)(同类商品将覆盖)",
|
||||
"下次签到双倍好感度概率 + 30%(金币才是真命天子!)(同类商品将覆盖)",
|
||||
),
|
||||
load_status=Config.get_config("shop", "IMPORT_DEFAULT_SHOP_GOODS"),
|
||||
** {"好感度双倍加持卡Ⅰ_prob": 0.1, "好感度双倍加持卡Ⅱ_prob": 0.2, "好感度双倍加持卡Ⅲ_prob": 0.3},
|
||||
)
|
||||
async def sign_card(user_id: int, group_id: int, prob: float):
|
||||
user = await SignGroupUser.ensure(user_id, group_id)
|
||||
await user.update(add_probability=prob).apply()
|
||||
|
||||
if Config.get_config("shop", "IMPORT_DEFAULT_SHOP_GOODS"):
|
||||
await register_goods(
|
||||
"好感度双倍加持卡Ⅰ", 30, "下次签到双倍好感度概率 + 10%(谁才是真命天子?)(同类商品将覆盖)"
|
||||
)
|
||||
use.register_use("好感度双倍加持卡Ⅰ", sign_card, **{"prob": 0.1})
|
||||
await register_goods("好感度双倍加持卡Ⅱ", 150, "下次签到双倍好感度概率 + 20%(平平庸庸)(同类商品将覆盖)")
|
||||
use.register_use("好感度双倍加持卡Ⅱ", sign_card, **{"prob": 0.2})
|
||||
await register_goods(
|
||||
"好感度双倍加持卡Ⅲ", 250, "下次签到双倍好感度概率 + 30%(金币才是真命天子!)(同类商品将覆盖)"
|
||||
)
|
||||
use.register_use("好感度双倍加持卡Ⅲ", sign_card, **{"prob": 0.3})
|
||||
|
||||
@driver.on_bot_connect
|
||||
async def _():
|
||||
await shop_register.load_register()
|
||||
|
||||
|
||||
# 创建商店界面
|
||||
@@ -59,9 +63,7 @@ async def create_shop_help() -> str:
|
||||
A = BuildImage(1000, h, color="#f9f6f2")
|
||||
current_h = 0
|
||||
for goods in _list:
|
||||
bk = BuildImage(
|
||||
700, 80, font_size=15, color="#f9f6f2", font="CJGaoDeGuo.otf"
|
||||
)
|
||||
bk = BuildImage(700, 80, font_size=15, color="#f9f6f2", font="CJGaoDeGuo.otf")
|
||||
goods_image = BuildImage(
|
||||
600, 80, font_size=20, color="#a29ad6", font="CJGaoDeGuo.otf"
|
||||
)
|
||||
@@ -92,10 +94,18 @@ async def create_shop_help() -> str:
|
||||
await bk.apaste(goods_image, alpha=True)
|
||||
# 添加限时图标和时间
|
||||
if goods.goods_limit_time > 0:
|
||||
_limit_time_logo = BuildImage(40, 40, background=f"{IMAGE_PATH}/other/time.png")
|
||||
_limit_time_logo = BuildImage(
|
||||
40, 40, background=f"{IMAGE_PATH}/other/time.png"
|
||||
)
|
||||
await bk.apaste(_limit_time_logo, (600, 0), True)
|
||||
await bk.apaste(BuildImage(0, 0, plain_text="限时!", font_size=23, font="CJGaoDeGuo.otf"), (640, 10), True)
|
||||
limit_time = time.strftime("%Y-%m-%d %H:%M", time.localtime(goods.goods_limit_time)).split()
|
||||
await bk.apaste(
|
||||
BuildImage(0, 0, plain_text="限时!", font_size=23, font="CJGaoDeGuo.otf"),
|
||||
(640, 10),
|
||||
True,
|
||||
)
|
||||
limit_time = time.strftime(
|
||||
"%Y-%m-%d %H:%M", time.localtime(goods.goods_limit_time)
|
||||
).split()
|
||||
y_m_d = limit_time[0]
|
||||
_h_m = limit_time[1].split(":")
|
||||
h_m = _h_m[0] + "时 " + _h_m[1] + "分"
|
||||
@@ -125,13 +135,12 @@ async def create_shop_help() -> str:
|
||||
|
||||
|
||||
async def register_goods(
|
||||
name: str,
|
||||
price: int,
|
||||
des: str,
|
||||
discount: Optional[float] = 1,
|
||||
limit_time: Optional[int] = 0,
|
||||
**kwargs,
|
||||
):
|
||||
name: str,
|
||||
price: int,
|
||||
des: str,
|
||||
discount: Optional[float] = 1,
|
||||
limit_time: Optional[int] = 0,
|
||||
) -> bool:
|
||||
"""
|
||||
添加商品
|
||||
例如: 折扣:可选参数↓ 限时时间:可选,单位为小时
|
||||
@@ -142,21 +151,20 @@ async def register_goods(
|
||||
:param des: 商品简介
|
||||
:param discount: 商品折扣
|
||||
:param limit_time: 商品限时销售时间,单位为小时
|
||||
:param kwargs: kwargs
|
||||
:return: 是否添加成功
|
||||
"""
|
||||
if kwargs:
|
||||
name = kwargs.get("name")
|
||||
price = kwargs.get("price")
|
||||
des = kwargs.get("des")
|
||||
discount = kwargs.get("discount")
|
||||
limit_time = kwargs.get("time_limit")
|
||||
limit_time = float(limit_time) if limit_time else limit_time
|
||||
discount = discount if discount is None else 1
|
||||
limit_time = int(time.time() + limit_time * 60 * 60) if limit_time is not None and limit_time != 0 else 0
|
||||
return await GoodsInfo.add_goods(
|
||||
name, int(price), des, float(discount), limit_time
|
||||
)
|
||||
if not await GoodsInfo.get_goods_info(name):
|
||||
limit_time = float(limit_time) if limit_time else limit_time
|
||||
discount = discount if discount is not None else 1
|
||||
limit_time = (
|
||||
int(time.time() + limit_time * 60 * 60)
|
||||
if limit_time is not None and limit_time != 0
|
||||
else 0
|
||||
)
|
||||
return await GoodsInfo.add_goods(
|
||||
name, int(price), des, float(discount), limit_time
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
# 删除商品
|
||||
@@ -230,7 +238,11 @@ async def update_goods(**kwargs) -> "str, str, int":
|
||||
int(price),
|
||||
des,
|
||||
float(discount),
|
||||
int(time.time() + limit_time * 60 * 60 if limit_time != 0 and new_time else 0),
|
||||
int(
|
||||
time.time() + limit_time * 60 * 60
|
||||
if limit_time != 0 and new_time
|
||||
else 0
|
||||
),
|
||||
),
|
||||
name,
|
||||
tmp[:-1],
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageSegment
|
||||
from services.log import logger
|
||||
from nonebot.adapters.onebot.v11 import Bot
|
||||
from pydantic import create_model
|
||||
from utils.models import ShopParam
|
||||
from typing import Optional, Union
|
||||
from types import MappingProxyType
|
||||
import inspect
|
||||
import asyncio
|
||||
|
||||
|
||||
@@ -22,7 +26,7 @@ class GoodsUseFuncManager:
|
||||
判断商品使用方法是否被注册
|
||||
:param goods_name: 商品名称
|
||||
"""
|
||||
return bool(self._data.get(goods_name))
|
||||
return bool(self ._data.get(goods_name))
|
||||
|
||||
def get_max_num_limit(self, goods_name: str) -> int:
|
||||
"""
|
||||
@@ -30,24 +34,52 @@ class GoodsUseFuncManager:
|
||||
:param goods_name: 商品名称
|
||||
"""
|
||||
if self.exists(goods_name):
|
||||
return self._data[goods_name]["kwargs"]["_max_num_limit"]
|
||||
return self._data[goods_name]["kwargs"]["max_num_limit"]
|
||||
return 1
|
||||
|
||||
async def use(self, **kwargs) -> Optional[Union[str, MessageSegment]]:
|
||||
async def use(
|
||||
self, param: ShopParam, **kwargs
|
||||
) -> Optional[Union[str, MessageSegment]]:
|
||||
"""
|
||||
使用道具
|
||||
:param param: BaseModel
|
||||
:param kwargs: kwargs
|
||||
"""
|
||||
goods_name = kwargs.get("goods_name")
|
||||
def parse_args(args_: MappingProxyType):
|
||||
param_list_ = []
|
||||
_bot = param.bot
|
||||
param.bot = None
|
||||
param_json = param.dict()
|
||||
param_json["bot"] = _bot
|
||||
for par in args_.keys():
|
||||
if par in ["shop_param"]:
|
||||
param_list_.append(param)
|
||||
elif par not in ["args", "kwargs"]:
|
||||
param_list_.append(param_json.get(par))
|
||||
if kwargs.get(par) is not None:
|
||||
del kwargs[par]
|
||||
return param_list_
|
||||
goods_name = param.goods_name
|
||||
if self.exists(goods_name):
|
||||
if asyncio.iscoroutinefunction(self._data[goods_name]["func"]):
|
||||
return await self._data[goods_name]["func"](
|
||||
**kwargs,
|
||||
)
|
||||
args = inspect.signature(self._data[goods_name]["func"]).parameters
|
||||
if args and list(args.keys())[0] != "kwargs":
|
||||
if asyncio.iscoroutinefunction(self._data[goods_name]["func"]):
|
||||
return await self._data[goods_name]["func"](
|
||||
*parse_args(args)
|
||||
)
|
||||
else:
|
||||
return self._data[goods_name]["func"](
|
||||
*parse_args(args)
|
||||
)
|
||||
else:
|
||||
return self._data[goods_name]["func"](
|
||||
**kwargs,
|
||||
)
|
||||
if asyncio.iscoroutinefunction(self._data[goods_name]["func"]):
|
||||
return await self._data[goods_name]["func"](
|
||||
**kwargs,
|
||||
)
|
||||
else:
|
||||
return self._data[goods_name]["func"](
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def check_send_success_message(self, goods_name: str) -> bool:
|
||||
"""
|
||||
@@ -67,6 +99,18 @@ class GoodsUseFuncManager:
|
||||
return self._data[goods_name]["kwargs"]
|
||||
return {}
|
||||
|
||||
def init_model(self, goods_name: str, bot: Bot, event: GroupMessageEvent, num: int):
|
||||
return self._data[goods_name]["model"](
|
||||
**{
|
||||
"goods_name": goods_name,
|
||||
"bot": bot,
|
||||
"event": event,
|
||||
"user_id": event.user_id,
|
||||
"group_id": event.group_id,
|
||||
"num": num,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
func_manager = GoodsUseFuncManager()
|
||||
|
||||
@@ -83,22 +127,23 @@ async def effect(
|
||||
:return: 使用是否成功
|
||||
"""
|
||||
# 优先使用注册的商品插件
|
||||
try:
|
||||
if func_manager.exists(goods_name):
|
||||
_kwargs = func_manager.get_kwargs(goods_name)
|
||||
return await func_manager.use(
|
||||
**{
|
||||
**_kwargs,
|
||||
"_bot": bot,
|
||||
"event": event,
|
||||
"group_id": event.group_id,
|
||||
"user_id": event.user_id,
|
||||
"num": num,
|
||||
"goods_name": goods_name,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"use 商品生效函数effect 发生错误 {type(e)}:{e}")
|
||||
# try:
|
||||
if func_manager.exists(goods_name):
|
||||
_kwargs = func_manager.get_kwargs(goods_name)
|
||||
return await func_manager.use(
|
||||
func_manager.init_model(goods_name, bot, event, num),
|
||||
**{
|
||||
**_kwargs,
|
||||
"_bot": bot,
|
||||
"event": event,
|
||||
"group_id": event.group_id,
|
||||
"user_id": event.user_id,
|
||||
"num": num,
|
||||
"goods_name": goods_name,
|
||||
},
|
||||
)
|
||||
# except Exception as e:
|
||||
# logger.error(f"use 商品生效函数effect 发生错误 {type(e)}:{e}")
|
||||
return None
|
||||
|
||||
|
||||
@@ -112,10 +157,14 @@ def register_use(goods_name: str, func, **kwargs):
|
||||
if func_manager.exists(goods_name):
|
||||
raise ValueError("该商品使用函数已被注册!")
|
||||
# 发送使用成功信息
|
||||
if kwargs.get("send_success_msg") is None:
|
||||
kwargs["send_success_msg"] = True
|
||||
kwargs["_max_num_limit"] = (
|
||||
kwargs.get("_max_num_limit") if kwargs.get("_max_num_limit") else 1
|
||||
kwargs["send_success_msg"] = kwargs.get("send_success_msg", True)
|
||||
kwargs["max_num_limit"] = kwargs.get("max_num_limit", 1)
|
||||
func_manager.register_use(
|
||||
goods_name,
|
||||
**{
|
||||
"func": func,
|
||||
"model": create_model(f"{goods_name}_model", __base__=ShopParam, **kwargs),
|
||||
"kwargs": kwargs,
|
||||
},
|
||||
)
|
||||
func_manager.register_use(goods_name, **{"func": func, "kwargs": kwargs})
|
||||
logger.info(f"register_use 成功注册商品:{goods_name} 的使用函数")
|
||||
|
||||
@@ -82,15 +82,16 @@ async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandAr
|
||||
if is_number(id_):
|
||||
id_ = int(id_)
|
||||
if cmd[:2] == "同意":
|
||||
if await requests_manager.approve(bot, id_, "private"):
|
||||
await friend_handle.send("同意好友请求成功..")
|
||||
else:
|
||||
await friend_handle.send("同意好友请求失败,可能是未找到此id的请求..")
|
||||
flag = await requests_manager.approve(bot, id_, "private")
|
||||
else:
|
||||
if await requests_manager.refused(bot, id_, "private"):
|
||||
await friend_handle.send("拒绝好友请求成功..")
|
||||
else:
|
||||
await friend_handle.send("拒绝好友请求失败,可能是未找到此id的请求..")
|
||||
flag = await requests_manager.refused(bot, id_, "private")
|
||||
if flag == 1:
|
||||
await friend_handle.send(f"{cmd[:2]}好友请求失败,该请求已失效..")
|
||||
requests_manager.delete_request(id_, "private")
|
||||
elif flag == 2:
|
||||
await friend_handle.send(f"{cmd[:2]}好友请求失败,未找到此id的请求..")
|
||||
else:
|
||||
await friend_handle.send(f"{cmd[:2]}好友请求成功!")
|
||||
else:
|
||||
await friend_handle.send("id必须为纯数字!")
|
||||
|
||||
@@ -99,12 +100,12 @@ async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandAr
|
||||
async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()):
|
||||
cmd = cmd[0]
|
||||
id_ = arg.extract_plain_text().strip()
|
||||
flag = None
|
||||
if is_number(id_):
|
||||
id_ = int(id_)
|
||||
if cmd[:2] == "同意":
|
||||
rid = await requests_manager.approve(bot, id_, "group")
|
||||
rid = requests_manager.get_group_id(id_)
|
||||
if rid:
|
||||
await friend_handle.send("同意群聊请求成功..")
|
||||
if await GroupInfo.get_group_info(rid):
|
||||
await GroupInfo.set_group_flag(rid, 1)
|
||||
else:
|
||||
@@ -116,15 +117,20 @@ async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandAr
|
||||
group_info["member_count"],
|
||||
1
|
||||
)
|
||||
flag = await requests_manager.approve(bot, id_, "group")
|
||||
else:
|
||||
await friend_handle.send("同意群聊请求失败,可能是未找到此id的请求..")
|
||||
await group_handle.send("同意群聊请求失败,未找到此id的请求..")
|
||||
else:
|
||||
if await requests_manager.refused(bot, id_, "group"):
|
||||
await friend_handle.send("拒绝群聊请求成功..")
|
||||
else:
|
||||
await friend_handle.send("拒绝群聊请求失败,可能是未找到此id的请求..")
|
||||
flag = await requests_manager.refused(bot, id_, "group")
|
||||
if flag == 1:
|
||||
await group_handle.send(f"{cmd[:2]}群聊请求失败,该请求已失效..")
|
||||
requests_manager.delete_request(id_, "group")
|
||||
elif flag == 2:
|
||||
await group_handle.send(f"{cmd[:2]}群聊请求失败,未找到此id的请求..")
|
||||
else:
|
||||
await group_handle.send(f"{cmd[:2]}群聊请求成功!")
|
||||
else:
|
||||
await friend_handle.send("id必须为纯数字!")
|
||||
await group_handle.send("id必须为纯数字!")
|
||||
|
||||
|
||||
@cls_request.handle()
|
||||
@@ -144,4 +150,4 @@ async def _():
|
||||
@clear_request.handle()
|
||||
async def _():
|
||||
requests_manager.clear()
|
||||
await cls_request.send("已清空所有好友/群聊请求..")
|
||||
await clear_request.send("已清空所有好友/群聊请求..")
|
||||
@@ -1,4 +1,11 @@
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GROUP, GroupMessageEvent, Message
|
||||
from nonebot.adapters.onebot.v11 import (
|
||||
Bot,
|
||||
MessageEvent,
|
||||
GROUP,
|
||||
GroupMessageEvent,
|
||||
Message,
|
||||
ActionFailed,
|
||||
)
|
||||
from nonebot import on_command, on_regex
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.typing import T_State
|
||||
@@ -8,7 +15,6 @@ from utils.manager import group_manager, plugins2settings_manager
|
||||
from models.group_info import GroupInfo
|
||||
from services.log import logger
|
||||
from configs.config import NICKNAME
|
||||
from nonebot.adapters.onebot.v11.exception import ActionFailed
|
||||
from nonebot.params import Command, CommandArg
|
||||
from typing import Tuple
|
||||
|
||||
@@ -92,7 +98,7 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
|
||||
level = 0
|
||||
if not msg:
|
||||
await add_group_level.finish("用法:修改群权限 [group] [level]")
|
||||
msg = msg.split(" ")
|
||||
msg = msg.split()
|
||||
if len(msg) < 2:
|
||||
await add_group_level.finish("参数不完全..[group] [level]")
|
||||
if is_number(msg[0]) and is_number(msg[1]):
|
||||
@@ -136,10 +142,8 @@ async def _():
|
||||
@manager_group_whitelist.handle()
|
||||
async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()):
|
||||
cmd = cmd[0]
|
||||
msg = arg.extract_plain_text().strip()
|
||||
all_group = [
|
||||
g["group_id"] for g in await bot.get_group_list()
|
||||
]
|
||||
msg = arg.extract_plain_text().strip().split()
|
||||
all_group = [g["group_id"] for g in await bot.get_group_list()]
|
||||
group_list = []
|
||||
for group in msg:
|
||||
if is_number(group) and int(group) in all_group:
|
||||
@@ -151,9 +155,7 @@ async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandAr
|
||||
else:
|
||||
group_manager.delete_group_white_list(group)
|
||||
group_list = [str(x) for x in group_list]
|
||||
await manager_group_whitelist.send(
|
||||
"已成功将 " + "\n".join(group_list) + " " + cmd
|
||||
)
|
||||
await manager_group_whitelist.send("已成功将 " + "\n".join(group_list) + " " + cmd)
|
||||
else:
|
||||
await manager_group_whitelist.send(f"添加失败,请检查{NICKNAME}是否已加入这些群聊或重复添加/删除群白单名")
|
||||
|
||||
@@ -199,6 +201,4 @@ async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandAr
|
||||
else:
|
||||
if await GroupInfo.get_group_info(group_id):
|
||||
await GroupInfo.set_group_flag(group_id, 0)
|
||||
await group_auth.send(
|
||||
f'已为 {group_id} {cmd[:2]}群认证..'
|
||||
)
|
||||
await group_auth.send(f"已为 {group_id} {cmd[:2]}群认证..")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.permission import SUPERUSER
|
||||
from models.level_user import LevelUser
|
||||
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, Message, GroupMessageEvent
|
||||
from utils.utils import get_message_at, is_number
|
||||
from services.log import logger
|
||||
from utils.message_builder import at
|
||||
@@ -37,41 +37,49 @@ super_cmd = on_command(
|
||||
|
||||
|
||||
@super_cmd.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()):
|
||||
cmd = cmd[0]
|
||||
group_id = -1
|
||||
level = 0
|
||||
async def _(
|
||||
bot: Bot,
|
||||
event: MessageEvent,
|
||||
cmd: Tuple[str, ...] = Command(),
|
||||
arg: Message = CommandArg(),
|
||||
):
|
||||
group_id = event.group_id if isinstance(event, GroupMessageEvent) else -1
|
||||
level = None
|
||||
args = arg.extract_plain_text().strip().split()
|
||||
qq = get_message_at(event.json())
|
||||
flag = 2
|
||||
try:
|
||||
args = arg.extract_plain_text().strip().split()
|
||||
qq = get_message_at(event.json())
|
||||
flag = -1
|
||||
if not qq:
|
||||
if len(args) > 2:
|
||||
if is_number(args[0]) and is_number(args[1]) and is_number(args[2]):
|
||||
if qq:
|
||||
qq = qq[0]
|
||||
if cmd[0][:2] == "添加" and args and is_number(args[0]):
|
||||
level = int(args[0])
|
||||
else:
|
||||
if cmd[0][:2] == "添加":
|
||||
if (
|
||||
len(args) > 2
|
||||
and is_number(args[0])
|
||||
and is_number(args[1])
|
||||
and is_number(args[2])
|
||||
):
|
||||
qq = int(args[0])
|
||||
group_id = int(args[1])
|
||||
level = int(args[2])
|
||||
flag = 1
|
||||
else:
|
||||
await super_cmd.finish("所有参数必须是数字!", at_sender=True)
|
||||
else:
|
||||
await super_cmd.finish(
|
||||
"权限参数不完全\n\t格式:添加/删除权限 [at] [level]"
|
||||
"\n\t格式:添加/删除权限 [qq] [group_id] [level]",
|
||||
at_sender=True,
|
||||
)
|
||||
else:
|
||||
if not is_number(args[0]):
|
||||
await super_cmd.finish("所有参数必须是数字!", at_sender=True)
|
||||
level = int(args[0])
|
||||
qq = qq[0]
|
||||
group_id = event.group_id
|
||||
flag = 2
|
||||
if cmd[:2] == "添加":
|
||||
if len(args) > 1 and is_number(args[0]) and is_number(args[1]):
|
||||
qq = int(args[0])
|
||||
group_id = int(args[1])
|
||||
flag = 1
|
||||
level = -1 if cmd[0][:2] == "删除" else level
|
||||
if group_id == -1 or not level or not qq:
|
||||
raise IndexError()
|
||||
except IndexError:
|
||||
await super_cmd.finish(__plugin_usage__)
|
||||
try:
|
||||
if cmd[0][:2] == "添加":
|
||||
if await LevelUser.set_level(qq, group_id, level, 1):
|
||||
result = "添加管理成功, 权限: " + str(level)
|
||||
result = f"添加管理成功, 权限: {level}"
|
||||
else:
|
||||
result = "管理已存在, 更新权限: " + str(level)
|
||||
result = f"管理已存在, 更新权限: {level}"
|
||||
else:
|
||||
if await LevelUser.delete_level(qq, event.group_id):
|
||||
result = "删除管理成功!"
|
||||
@@ -82,7 +90,10 @@ async def _(bot: Bot, event: GroupMessageEvent, cmd: Tuple[str, ...] = Command()
|
||||
elif flag == 1:
|
||||
await bot.send_group_msg(
|
||||
group_id=group_id,
|
||||
message=Message(f"{at(qq)}管理员修改了你的权限" f"\n--------\n你当前的权限等级:{level}"),
|
||||
message=Message(
|
||||
f"{at(qq)}管理员修改了你的权限"
|
||||
f"\n--------\n你当前的权限等级:{level if level != -1 else 0}"
|
||||
),
|
||||
)
|
||||
await super_cmd.send("修改成功")
|
||||
except Exception as e:
|
||||
|
||||
@@ -45,6 +45,7 @@ async def _():
|
||||
group_info["group_name"],
|
||||
group_info["max_member_count"],
|
||||
group_info["member_count"],
|
||||
1
|
||||
):
|
||||
num += 1
|
||||
logger.info(f"自动更新群组 {g} 信息成功")
|
||||
|
||||
@@ -12,6 +12,7 @@ driver.on_shutdown(disconnect)
|
||||
nonebot.load_plugin("nonebot_plugin_apscheduler")
|
||||
nonebot.load_plugins("basic_plugins")
|
||||
nonebot.load_plugins("plugins")
|
||||
nonebot.load_plugins("extensive_plugin")
|
||||
# 最后加载权限控制
|
||||
nonebot.load_plugins("basic_plugins/hooks")
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from typing import Optional, Any, Union
|
||||
from pathlib import Path
|
||||
from ruamel.yaml import YAML
|
||||
from ruamel import yaml
|
||||
from ruamel.yaml.scanner import ScannerError
|
||||
|
||||
|
||||
class ConfigsManager:
|
||||
@@ -11,14 +12,34 @@ class ConfigsManager:
|
||||
|
||||
def __init__(self, file: Path):
|
||||
self._data: dict = {}
|
||||
self._simple_data: dict = {}
|
||||
self._admin_level_data = []
|
||||
self._simple_file = Path() / "configs" / "config.yaml"
|
||||
if file:
|
||||
file.parent.mkdir(exist_ok=True, parents=True)
|
||||
self.file = file
|
||||
_yaml = YAML()
|
||||
if file.exists():
|
||||
_yaml = YAML()
|
||||
with open(file, "r", encoding="utf8") as f:
|
||||
self._data = _yaml.load(f)
|
||||
if not self._data:
|
||||
self.file.unlink()
|
||||
raise ValueError(
|
||||
"配置文件为空!\n"
|
||||
"***********************************************************\n"
|
||||
"****** 配置文件 plugins2config.yaml 为空,已删除,请重启 ******\n"
|
||||
"***********************************************************"
|
||||
)
|
||||
if self._simple_file.exists():
|
||||
try:
|
||||
with open(self._simple_file, "r", encoding="utf8") as f:
|
||||
self._simple_data = _yaml.load(f)
|
||||
except ScannerError as e:
|
||||
raise ScannerError(
|
||||
f"{e}\n**********************************************\n"
|
||||
f"****** 可能为config.yaml配置文件填写不规范 ******\n"
|
||||
f"**********************************************"
|
||||
)
|
||||
|
||||
def add_plugin_config(
|
||||
self,
|
||||
@@ -71,17 +92,24 @@ class ConfigsManager:
|
||||
"""
|
||||
if module in self._data.keys():
|
||||
del self._data[module]
|
||||
self.save()
|
||||
|
||||
def set_config(self, module: str, key: str, value: str):
|
||||
def set_config(self, module: str, key: str, value: Any , save_simple_data: bool = False):
|
||||
"""
|
||||
设置配置值
|
||||
:param module: 模块名
|
||||
:param key: 配置名称
|
||||
:param value: 值
|
||||
:param save_simple_data: 同时保存至config.yaml
|
||||
"""
|
||||
if module in self._data.keys():
|
||||
if self._data[module].get(key) is not None:
|
||||
if (
|
||||
self._data[module].get(key) is not None
|
||||
and self._data[module][key] != value
|
||||
):
|
||||
self._data[module][key]["value"] = value
|
||||
self._simple_data[module][key] = value
|
||||
self.save(save_simple_data = save_simple_data)
|
||||
|
||||
def set_help(self, module: str, key: str, help_: str):
|
||||
"""
|
||||
@@ -93,8 +121,9 @@ class ConfigsManager:
|
||||
if module in self._data.keys():
|
||||
if self._data[module].get(key) is not None:
|
||||
self._data[module][key]["help"] = help_
|
||||
self.save()
|
||||
|
||||
def set_default_value(self, module: str, key: str, value: str):
|
||||
def set_default_value(self, module: str, key: str, value: Any):
|
||||
"""
|
||||
设置配置默认值
|
||||
:param module: 模块名
|
||||
@@ -104,8 +133,11 @@ class ConfigsManager:
|
||||
if module in self._data.keys():
|
||||
if self._data[module].get(key) is not None:
|
||||
self._data[module][key]["default_value"] = value
|
||||
self.save()
|
||||
|
||||
def get_config(self, module: str, key: str, default: Optional[Any] = None) -> Optional[Any]:
|
||||
def get_config(
|
||||
self, module: str, key: str, default: Optional[Any] = None
|
||||
) -> Optional[Any]:
|
||||
"""
|
||||
获取指定配置值
|
||||
:param module: 模块名
|
||||
@@ -142,11 +174,21 @@ class ConfigsManager:
|
||||
if key in self._data.keys():
|
||||
return self._data[key]
|
||||
|
||||
def save(self, path: Union[str, Path] = None):
|
||||
def save(self, path: Union[str, Path] = None, save_simple_data: bool = False):
|
||||
"""
|
||||
保存数据
|
||||
:param path: 路径
|
||||
:param save_simple_data: 同时保存至config.yaml
|
||||
"""
|
||||
if save_simple_data:
|
||||
with open(self._simple_file, "w", encoding="utf8") as f:
|
||||
yaml.dump(
|
||||
self._simple_data,
|
||||
f,
|
||||
indent=2,
|
||||
Dumper=yaml.RoundTripDumper,
|
||||
allow_unicode=True,
|
||||
)
|
||||
path = path if path else self.file
|
||||
with open(path, "w", encoding="utf8") as f:
|
||||
yaml.dump(
|
||||
|
||||
+1450
-155
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,214 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Literal, Optional, Tuple, Union
|
||||
|
||||
from services.db_context import db
|
||||
|
||||
|
||||
class ChatHistory(db.Model):
|
||||
__tablename__ = "chat_history"
|
||||
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
user_qq = db.Column(db.BigInteger(), nullable=False)
|
||||
group_id = db.Column(db.BigInteger())
|
||||
text = db.Column(db.Text())
|
||||
plain_text = db.Column(db.Text())
|
||||
create_time = db.Column(db.DateTime(timezone=True), nullable=False)
|
||||
|
||||
@classmethod
|
||||
async def add_chat_msg(cls, user_qq: int, group_id: Optional[int], text: str, plain_text: str):
|
||||
await cls.create(
|
||||
user_qq=user_qq, group_id=group_id, text=text, plain_text=plain_text, create_time=datetime.now()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_user_msg(
|
||||
cls,
|
||||
uid: int,
|
||||
msg_type: Optional[Literal["private", "group"]],
|
||||
days: Optional[int] = None,
|
||||
) -> List["ChatHistory"]:
|
||||
"""
|
||||
说明:
|
||||
获取用户消息
|
||||
参数:
|
||||
:param uid: 用户qq
|
||||
:param msg_type: 消息类型,私聊或群聊
|
||||
:param days: 限制日期
|
||||
"""
|
||||
return await cls._get_msg(uid, None, "user", msg_type, days).gino.all()
|
||||
|
||||
@classmethod
|
||||
async def get_group_user_msg(
|
||||
cls,
|
||||
uid: int,
|
||||
gid: int,
|
||||
limit: int = 10,
|
||||
date_scope: Tuple[datetime, datetime] = None,
|
||||
) -> List["ChatHistory"]:
|
||||
"""
|
||||
说明:
|
||||
获取群聊指定用户聊天记录
|
||||
参数:
|
||||
:param uid: qq
|
||||
:param gid: 群号
|
||||
:param limit: 获取数量
|
||||
:param date_scope: 日期范围,默认None为全搜索
|
||||
"""
|
||||
return (
|
||||
await cls._get_msg(uid, gid, "group", days=date_scope)
|
||||
.limit(limit)
|
||||
.gino.all()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_group_user_msg_count(cls, uid: int, gid: int) -> Optional[int]:
|
||||
"""
|
||||
说明:
|
||||
查询群聊指定用户的聊天记录数量
|
||||
参数:
|
||||
:param uid: qq
|
||||
:param gid: 群号
|
||||
"""
|
||||
if x := await db.first(
|
||||
db.text(
|
||||
f"SELECT COUNT(id) as sum FROM public.chat_history WHERE user_qq = {uid} AND group_id = {gid}"
|
||||
)
|
||||
):
|
||||
return x[0]
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def get_group_msg_rank(
|
||||
cls,
|
||||
gid: int,
|
||||
limit: int = 10,
|
||||
order: str = "DESC",
|
||||
date_scope: Optional[Tuple[datetime, datetime]] = None,
|
||||
) -> Optional[Tuple[int, int]]:
|
||||
"""
|
||||
说明:
|
||||
获取排行数据
|
||||
参数:
|
||||
:param gid: 群号
|
||||
:param limit: 获取数量
|
||||
:param order: 排序类型,desc,des
|
||||
:param date_scope: 日期范围
|
||||
"""
|
||||
sql = f"SELECT user_qq, COUNT(id) as sum FROM public.chat_history WHERE group_id = {gid} "
|
||||
if date_scope:
|
||||
sql += f"AND create_time BETWEEN '{date_scope[0]}' AND '{date_scope[1]}' "
|
||||
sql += f"GROUP BY user_qq ORDER BY sum {order if order and order.upper() != 'DES' else ''} LIMIT {limit}"
|
||||
return await db.all(db.text(sql))
|
||||
|
||||
@classmethod
|
||||
async def get_group_first_msg_datetime(cls, gid: int) -> Optional[datetime]:
|
||||
"""
|
||||
说明:
|
||||
获取群第一条记录消息时间
|
||||
参数:
|
||||
:param gid:
|
||||
"""
|
||||
if (
|
||||
msg := await cls.query.where(cls.group_id == gid)
|
||||
.order_by(cls.create_time)
|
||||
.gino.first()
|
||||
):
|
||||
return msg.create_time
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def get_user_msg_count(
|
||||
cls,
|
||||
uid: int,
|
||||
msg_type: Optional[Literal["private", "group"]],
|
||||
days: Optional[int] = None,
|
||||
) -> int:
|
||||
"""
|
||||
说明:
|
||||
获取用户消息数量
|
||||
参数:
|
||||
:param uid: 用户qq
|
||||
:param msg_type: 消息类型,私聊或群聊
|
||||
:param days: 限制日期
|
||||
"""
|
||||
return (
|
||||
await cls._get_msg(uid, None, "user", msg_type, days, True).gino.first()
|
||||
)[0]
|
||||
|
||||
@classmethod
|
||||
async def get_group_msg(
|
||||
cls,
|
||||
gid: int,
|
||||
days: Optional[int] = None,
|
||||
) -> List["ChatHistory"]:
|
||||
"""
|
||||
说明:
|
||||
获取群聊消息
|
||||
参数:
|
||||
:param gid: 用户qq
|
||||
:param days: 限制日期
|
||||
"""
|
||||
return await cls._get_msg(None, gid, "group", None, days).gino.all()
|
||||
|
||||
@classmethod
|
||||
async def get_group_msg_count(
|
||||
cls,
|
||||
gid: int,
|
||||
days: Optional[int] = None,
|
||||
) -> List["ChatHistory"]:
|
||||
"""
|
||||
说明:
|
||||
获取群聊消息数量
|
||||
参数:
|
||||
:param gid: 用户qq
|
||||
:param days: 限制日期
|
||||
"""
|
||||
return (await cls._get_msg(None, gid, "group", None, days, True).gino.first())[
|
||||
0
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _get_msg(
|
||||
cls,
|
||||
uid: Optional[int],
|
||||
gid: Optional[int],
|
||||
type_: Literal["user", "group"],
|
||||
msg_type: Optional[Literal["private", "group"]] = None,
|
||||
days: Optional[Union[int, Tuple[datetime, datetime]]] = None,
|
||||
is_select_count: bool = False,
|
||||
):
|
||||
"""
|
||||
说明:
|
||||
获取消息查询query
|
||||
参数:
|
||||
:param uid: 用户qq
|
||||
:param gid: 群号
|
||||
:param type_: 类型,私聊或群聊
|
||||
:param msg_type: 消息类型,用户或群聊
|
||||
:param days: 限制日期
|
||||
"""
|
||||
if is_select_count:
|
||||
setattr(ChatHistory, "count", db.func.count(cls.id).label("count"))
|
||||
query = cls.select("count")
|
||||
else:
|
||||
query = cls.query
|
||||
if type_ == "user":
|
||||
query = query.where(cls.user_qq == uid)
|
||||
if msg_type == "private":
|
||||
query = query.where(cls.group_id == None)
|
||||
elif msg_type == "group":
|
||||
query = query.where(cls.group_id != None)
|
||||
else:
|
||||
query = query.where(cls.group_id == gid)
|
||||
if uid:
|
||||
query = query.where(cls.user_qq == uid)
|
||||
if days:
|
||||
if isinstance(days, int):
|
||||
query = query.where(
|
||||
cls.create_time >= datetime.now() - timedelta(days=days)
|
||||
)
|
||||
elif isinstance(days, tuple):
|
||||
query = query.where(cls.create_time >= days[0]).where(
|
||||
cls.create_time <= days[1]
|
||||
)
|
||||
return query
|
||||
@@ -114,8 +114,7 @@ class GoodsInfo(db.Model):
|
||||
参数:
|
||||
:param goods_name: 商品名称
|
||||
"""
|
||||
query = await cls.query.where(cls.goods_name == goods_name).gino.first()
|
||||
return query
|
||||
return await cls.query.where(cls.goods_name == goods_name).gino.first()
|
||||
|
||||
@classmethod
|
||||
async def get_all_goods(cls) -> List["GoodsInfo"]:
|
||||
|
||||
+15
-10
@@ -1,3 +1,5 @@
|
||||
from asyncpg import UniqueViolationError
|
||||
|
||||
from services.db_context import db
|
||||
|
||||
|
||||
@@ -44,16 +46,19 @@ class LevelUser(db.Model):
|
||||
query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id))
|
||||
query = query.with_for_update()
|
||||
user = await query.gino.first()
|
||||
if user is None:
|
||||
await cls.create(
|
||||
user_qq=user_qq,
|
||||
group_id=group_id,
|
||||
user_level=level,
|
||||
group_flag=group_flag,
|
||||
)
|
||||
return True
|
||||
else:
|
||||
await user.update(user_level=level, group_flag=group_flag).apply()
|
||||
try:
|
||||
if not user:
|
||||
await cls.create(
|
||||
user_qq=user_qq,
|
||||
group_id=group_id,
|
||||
user_level=level,
|
||||
group_flag=group_flag,
|
||||
)
|
||||
return True
|
||||
else:
|
||||
await user.update(user_level=level, group_flag=group_flag).apply()
|
||||
return False
|
||||
except UniqueViolationError:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -37,7 +37,7 @@ Config.add_plugin_config(
|
||||
"alapi", "ALAPI_TOKEN", None, help_="在 https://admin.alapi.cn/user/login 登录后获取token"
|
||||
)
|
||||
|
||||
ai = on_message(rule=to_me(), priority=8)
|
||||
ai = on_message(rule=to_me(), priority=998)
|
||||
|
||||
|
||||
@ai.handle()
|
||||
@@ -76,6 +76,7 @@ async def _(bot: Bot, event: MessageEvent):
|
||||
f"问题:{msg} ---- 回答:{result}"
|
||||
)
|
||||
if result:
|
||||
result = str(result)
|
||||
for t in Config.get_config("ai", "TEXT_FILTER"):
|
||||
result = result.replace(t, "*")
|
||||
await ai.finish(Message(result))
|
||||
|
||||
+34
-30
@@ -57,7 +57,7 @@ async def get_chat_result(text: str, img_url: str, user_id: int, nickname: str)
|
||||
if random.random() < 0.2:
|
||||
if nickname.find("大人") == -1:
|
||||
nickname += "大~人~"
|
||||
rst = rst.replace("小主人", nickname).replace("小朋友", nickname)
|
||||
rst = str(rst).replace("小主人", nickname).replace("小朋友", nickname)
|
||||
ai_message_manager.add_result(user_id, rst)
|
||||
return rst
|
||||
|
||||
@@ -126,35 +126,39 @@ async def xie_ai(text: str) -> str:
|
||||
"""
|
||||
res = await AsyncHttpx.get(f"http://api.qingyunke.com/api.php?key=free&appid=0&msg={text}")
|
||||
content = ""
|
||||
data = json.loads(res.text)
|
||||
if data["result"] == 0:
|
||||
content = data["content"]
|
||||
if "菲菲" in content:
|
||||
content = content.replace("菲菲", NICKNAME)
|
||||
if "艳儿" in content:
|
||||
content = content.replace("艳儿", NICKNAME)
|
||||
if "公众号" in content:
|
||||
content = ""
|
||||
if "{br}" in content:
|
||||
content = content.replace("{br}", "\n")
|
||||
if "提示" in content:
|
||||
content = content[: content.find("提示")]
|
||||
if "淘宝" in content or "taobao.com" in content:
|
||||
return ""
|
||||
while True:
|
||||
r = re.search("{face:(.*)}", content)
|
||||
if r:
|
||||
id_ = r.group(1)
|
||||
content = content.replace(
|
||||
"{" + f"face:{id_}" + "}", str(face(int(id_)))
|
||||
)
|
||||
else:
|
||||
break
|
||||
return (
|
||||
content
|
||||
if not content and not Config.get_config("ai", "ALAPI_AI_CHECK")
|
||||
else await check_text(content)
|
||||
)
|
||||
try:
|
||||
data = json.loads(res.text)
|
||||
if data["result"] == 0:
|
||||
content = data["content"]
|
||||
if "菲菲" in content:
|
||||
content = content.replace("菲菲", NICKNAME)
|
||||
if "艳儿" in content:
|
||||
content = content.replace("艳儿", NICKNAME)
|
||||
if "公众号" in content:
|
||||
content = ""
|
||||
if "{br}" in content:
|
||||
content = content.replace("{br}", "\n")
|
||||
if "提示" in content:
|
||||
content = content[: content.find("提示")]
|
||||
if "淘宝" in content or "taobao.com" in content:
|
||||
return ""
|
||||
while True:
|
||||
r = re.search("{face:(.*)}", content)
|
||||
if r:
|
||||
id_ = r.group(1)
|
||||
content = content.replace(
|
||||
"{" + f"face:{id_}" + "}", str(face(int(id_)))
|
||||
)
|
||||
else:
|
||||
break
|
||||
return (
|
||||
content
|
||||
if not content and not Config.get_config("ai", "ALAPI_AI_CHECK")
|
||||
else await check_text(content)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ai xie_ai 发生错误 {type(e)}:{e}")
|
||||
return ""
|
||||
|
||||
|
||||
def hello() -> str:
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from utils.image_utils import BuildImage
|
||||
from utils.message_builder import image
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from typing import Optional, Tuple, Union
|
||||
from configs.config import Config
|
||||
from utils.http_utils import AsyncHttpx
|
||||
@@ -23,27 +19,8 @@ async def get_data(url: str, params: Optional[dict] = None) -> Tuple[Union[dict,
|
||||
return "没有搜索到...", 997
|
||||
return data, 200
|
||||
else:
|
||||
if data["code"] == 101:
|
||||
return "缺失ALAPI TOKEN,请在配置文件中填写!", 999
|
||||
return f'发生了错误...code:{data["code"]}', 999
|
||||
except TimeoutError:
|
||||
return "超时了....", 998
|
||||
|
||||
|
||||
def gen_wbtop_pic(data: dict) -> MessageSegment:
|
||||
"""
|
||||
生成微博热搜图片
|
||||
:param data: 微博热搜数据
|
||||
"""
|
||||
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 = 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 = 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)
|
||||
text_bk.paste(img)
|
||||
bk.paste(text_bk, (0, 280))
|
||||
return image(b64=bk.pic2bs4())
|
||||
|
||||
@@ -38,7 +38,7 @@ usage:
|
||||
__plugin_des__ = "非常便利的B站订阅通知"
|
||||
__plugin_cmd__ = ["添加订阅 [主播/UP/番剧] [id/链接/番名]", "删除订阅 [id]", "查看订阅"]
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
__plugin_author__ = "HibiKier & NumberSir"
|
||||
__plugin_settings__ = {
|
||||
"level": 5,
|
||||
"default_status": True,
|
||||
@@ -211,7 +211,7 @@ async def _():
|
||||
await sub_manager.reload_sub_data()
|
||||
sub = await sub_manager.random_sub_data()
|
||||
if sub:
|
||||
logger.info(f"Bilibili订阅开始检测:{sub.sub_id}")
|
||||
logger.debug(f"Bilibili订阅开始检测:{sub.sub_id}")
|
||||
rst = await get_sub_status(sub.sub_id, sub.sub_type)
|
||||
await send_sub_msg(rst, sub, bot)
|
||||
if sub.sub_type == "live":
|
||||
@@ -228,12 +228,14 @@ async def send_sub_msg(rst: str, sub: BilibiliSub, bot: Bot):
|
||||
:param sub: BilibiliSub
|
||||
:param bot: Bot
|
||||
"""
|
||||
temp_group = []
|
||||
if rst:
|
||||
for x in sub.sub_users.split(",")[:-1]:
|
||||
try:
|
||||
if ":" in x:
|
||||
if ":" in x and x.split(":")[1] not in temp_group:
|
||||
temp_group.append(x.split(":")[1])
|
||||
await bot.send_group_msg(
|
||||
group_id=int(x.split(":")[1]), message=Message(rst)
|
||||
group_id=int(x.split(":")[1]), message=Message(rst)
|
||||
)
|
||||
else:
|
||||
await bot.send_private_msg(user_id=int(x), message=Message(rst))
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
from bilibili_api.exceptions.ResponseCodeException import ResponseCodeException
|
||||
from bilireq.exceptions import ResponseCodeError
|
||||
from utils.manager import resources_manager
|
||||
from asyncio.exceptions import TimeoutError
|
||||
from .model import BilibiliSub
|
||||
from bilibili_api.live import LiveRoom
|
||||
from bilibili_api import bangumi
|
||||
from bilireq.live import get_room_info_by_id
|
||||
from .utils import get_meta
|
||||
from utils.message_builder import image
|
||||
from bilibili_api.user import User
|
||||
from bilibili_api import user
|
||||
from bilireq.user import get_user_info
|
||||
from bilireq import dynamic
|
||||
from .utils import get_videos
|
||||
from typing import Optional
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from datetime import datetime
|
||||
@@ -36,9 +37,9 @@ async def add_live_sub(live_id: int, sub_user: str) -> str:
|
||||
try:
|
||||
async with db.transaction():
|
||||
try:
|
||||
live = LiveRoom(live_id)
|
||||
live_info = (await live.get_room_info())["room_info"]
|
||||
except ResponseCodeException:
|
||||
"""bilibili_api.live库的LiveRoom类中get_room_info改为bilireq.live库的get_room_info_by_id方法"""
|
||||
live_info = await get_room_info_by_id(live_id)
|
||||
except ResponseCodeError:
|
||||
return f"未找到房间号Id:{live_id} 的信息,请检查Id是否正确"
|
||||
uid = live_info["uid"]
|
||||
room_id = live_info["room_id"]
|
||||
@@ -78,16 +79,18 @@ async def add_up_sub(uid: int, sub_user: str) -> str:
|
||||
try:
|
||||
async with db.transaction():
|
||||
try:
|
||||
u = user.User(uid)
|
||||
user_info = await u.get_user_info()
|
||||
except ResponseCodeException:
|
||||
"""bilibili_api.user库中User类的get_user_info改为bilireq.user库的get_user_info方法"""
|
||||
user_info = await get_user_info(uid)
|
||||
except ResponseCodeError:
|
||||
return f"未找到UpId:{uid} 的信息,请检查Id是否正确"
|
||||
uname = user_info["name"]
|
||||
dynamic_info = await u.get_dynamics(0)
|
||||
"""bilibili_api.user库中User类的get_dynamics改为bilireq.dynamic库的get_user_dynamics方法"""
|
||||
dynamic_info = await dynamic.get_user_dynamics(uid)
|
||||
dynamic_upload_time = 0
|
||||
if dynamic_info.get("cards"):
|
||||
dynamic_upload_time = dynamic_info["cards"][0]["desc"]["timestamp"]
|
||||
video_info = await u.get_videos()
|
||||
"""bilibili_api.user库中User类的get_videos改为bilireq.user库的get_videos方法"""
|
||||
video_info = await get_videos(uid)
|
||||
latest_video_created = 0
|
||||
if video_info["list"].get("vlist"):
|
||||
latest_video_created = video_info["list"]["vlist"][0]["created"]
|
||||
@@ -117,8 +120,9 @@ async def add_season_sub(media_id: int, sub_user: str) -> str:
|
||||
try:
|
||||
async with db.transaction():
|
||||
try:
|
||||
season_info = await bangumi.get_meta(media_id)
|
||||
except ResponseCodeException:
|
||||
"""bilibili_api.bangumi库中get_meta改为bilireq.bangumi库的get_meta方法"""
|
||||
season_info = await get_meta(media_id)
|
||||
except ResponseCodeError:
|
||||
return f"未找到media_id:{media_id} 的信息,请检查Id是否正确"
|
||||
season_id = season_info["media"]["season_id"]
|
||||
season_current_episode = season_info["media"]["new_ep"]["index"]
|
||||
@@ -200,7 +204,7 @@ async def get_sub_status(id_: int, sub_type: str) -> Optional[str]:
|
||||
return await _get_up_status(id_)
|
||||
elif sub_type == "season":
|
||||
return await _get_season_status(id_)
|
||||
except ResponseCodeException:
|
||||
except ResponseCodeError:
|
||||
return f"Id:{id_} 获取信息失败...请检查订阅Id是否存在或稍后再试..."
|
||||
# except Exception as e:
|
||||
# logger.error(f"获取订阅状态发生预料之外的错误 id_:{id_} {type(e)}:{e}")
|
||||
@@ -212,12 +216,12 @@ async def _get_live_status(id_: int) -> Optional[str]:
|
||||
获取直播订阅状态
|
||||
:param id_: 直播间 id
|
||||
"""
|
||||
live = LiveRoom(id_)
|
||||
live_info = (await live.get_room_info())["room_info"]
|
||||
"""bilibili_api.live库的LiveRoom类中get_room_info改为bilireq.live库的get_room_info_by_id方法"""
|
||||
live_info = await get_room_info_by_id(id_)
|
||||
title = live_info["title"]
|
||||
room_id = live_info["room_id"]
|
||||
live_status = live_info["live_status"]
|
||||
cover = live_info["cover"]
|
||||
cover = live_info["user_cover"]
|
||||
sub = await BilibiliSub.get_sub(id_)
|
||||
if sub.live_status != live_status:
|
||||
await BilibiliSub.update_sub_info(id_, live_status=live_status)
|
||||
@@ -238,15 +242,16 @@ async def _get_up_status(id_: int) -> Optional[str]:
|
||||
:return:
|
||||
"""
|
||||
_user = await BilibiliSub.get_sub(id_)
|
||||
u = user.User(_user.uid)
|
||||
user_info = await u.get_user_info()
|
||||
"""bilibili_api.user库中User类的get_user_info改为bilireq.user库的get_user_info方法"""
|
||||
user_info = await get_user_info(_user.uid)
|
||||
uname = user_info["name"]
|
||||
video_info = await u.get_videos()
|
||||
"""bilibili_api.user库中User类的get_videos改为bilireq.user库的get_videos方法"""
|
||||
video_info = await get_videos(id_)
|
||||
latest_video_created = 0
|
||||
video = None
|
||||
if _user.uname != uname:
|
||||
await BilibiliSub.update_sub_info(id_, uname=uname)
|
||||
dynamic_img, dynamic_upload_time = await get_user_dynamic(u, _user)
|
||||
dynamic_img, dynamic_upload_time = await get_user_dynamic(id_, _user)
|
||||
if video_info["list"].get("vlist"):
|
||||
video = video_info["list"]["vlist"][0]
|
||||
latest_video_created = video["created"]
|
||||
@@ -280,7 +285,8 @@ async def _get_season_status(id_) -> Optional[str]:
|
||||
获取 番剧 更新状态
|
||||
:param id_: 番剧 id
|
||||
"""
|
||||
season_info = await bangumi.get_meta(id_)
|
||||
"""bilibili_api.bangumi库中get_meta改为bilireq.bangumi库的get_meta方法"""
|
||||
season_info = await get_meta(id_)
|
||||
title = season_info["media"]["title"]
|
||||
_idx = (await BilibiliSub.get_sub(id_)).season_current_episode
|
||||
new_ep = season_info["media"]["new_ep"]["index"]
|
||||
@@ -297,42 +303,47 @@ async def _get_season_status(id_) -> Optional[str]:
|
||||
|
||||
|
||||
async def get_user_dynamic(
|
||||
u: User, local_user: BilibiliSub
|
||||
uid: int, local_user: BilibiliSub
|
||||
) -> "Optional[MessageSegment], int":
|
||||
"""
|
||||
获取用户动态
|
||||
:param u: 用户类
|
||||
:param uid: 用户uid
|
||||
:param local_user: 数据库存储的用户数据
|
||||
:return: 最新动态截图与时间
|
||||
"""
|
||||
dynamic_info = await u.get_dynamics(0)
|
||||
"""bilibili_api.user库中User类的get_dynamics改为bilireq.dynamic库的get_user_dynamics方法"""
|
||||
dynamic_info = await dynamic.get_user_dynamics(uid)
|
||||
browser = await get_browser()
|
||||
if dynamic_info.get("cards") and browser:
|
||||
dynamic_upload_time = dynamic_info["cards"][0]["desc"]["timestamp"]
|
||||
if local_user.dynamic_upload_time < dynamic_upload_time:
|
||||
page = await browser.new_page()
|
||||
await page.goto(
|
||||
f"https://space.bilibili.com/{local_user.uid}/dynamic",
|
||||
wait_until="networkidle",
|
||||
timeout=10000,
|
||||
)
|
||||
await page.set_viewport_size({"width": 2560, "height": 1080})
|
||||
# 删除置顶
|
||||
await page.evaluate(
|
||||
try:
|
||||
await page.goto(
|
||||
f"https://space.bilibili.com/{local_user.uid}/dynamic",
|
||||
wait_until="networkidle",
|
||||
timeout=10000,
|
||||
)
|
||||
await page.set_viewport_size({"width": 2560, "height": 1080, "timeout": 10000*20}) # timeout: 200s
|
||||
# 删除置顶
|
||||
await page.evaluate(
|
||||
"""
|
||||
xs = document.getElementsByClassName('bili-dyn-item__tag');
|
||||
for (x of xs) {
|
||||
x.parentNode.parentNode.remove();
|
||||
}
|
||||
"""
|
||||
xs = document.getElementsByClassName('first-card-with-title');
|
||||
for (x of xs) {
|
||||
x.remove();
|
||||
}
|
||||
"""
|
||||
)
|
||||
card = await page.query_selector(".card")
|
||||
# 截图并保存
|
||||
await card.screenshot(
|
||||
path=dynamic_path / f"{local_user.sub_id}_{dynamic_upload_time}.jpg",
|
||||
timeout=100000,
|
||||
)
|
||||
await page.close()
|
||||
)
|
||||
card = page.locator(".bili-dyn-list__item").first
|
||||
await card.wait_for()
|
||||
# 截图并保存
|
||||
await card.screenshot(
|
||||
path=dynamic_path / f"{local_user.sub_id}_{dynamic_upload_time}.jpg",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"B站订阅:获取用户动态 发送错误 {type(e)}:{e}")
|
||||
finally:
|
||||
await page.close()
|
||||
return (
|
||||
image(
|
||||
f"{local_user.sub_id}_{dynamic_upload_time}.jpg",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from utils.image_utils import BuildImage
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from utils.http_utils import AsyncHttpx
|
||||
from bilibili_api import user
|
||||
# from bilibili_api import user
|
||||
from bilireq.user import get_user_info
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
@@ -28,8 +29,8 @@ async def create_live_des_image(uid: int, title: str, cover: str, tags: str, des
|
||||
:param des: 直播简介
|
||||
:return:
|
||||
"""
|
||||
u = user.User(uid)
|
||||
user_info = await u.get_user_info()
|
||||
|
||||
user_info = await get_user_info(uid)
|
||||
name = user_info["name"]
|
||||
sex = user_info["sex"]
|
||||
face = user_info["face"]
|
||||
@@ -37,7 +38,6 @@ async def create_live_des_image(uid: int, title: str, cover: str, tags: str, des
|
||||
ava = BuildImage(100, 100, background=BytesIO(await get_pic(face)))
|
||||
ava.circle()
|
||||
cover = BuildImage(470, 265, background=BytesIO(await get_pic(cover)))
|
||||
print()
|
||||
|
||||
|
||||
def _create_live_des_image(
|
||||
@@ -68,3 +68,47 @@ def _create_live_des_image(
|
||||
border_img = BuildImage(1772, 2657, background=border)
|
||||
bk = BuildImage(1772, 2657, font_size=30)
|
||||
bk.paste(cover, (0, 100), center_type="by_width")
|
||||
|
||||
|
||||
async def get_meta(media_id: int, auth=None, reqtype="both", **kwargs):
|
||||
"""
|
||||
根据番剧 ID 获取番剧元数据信息,
|
||||
作为bilibili_api和bilireq的替代品。
|
||||
如果bilireq.bangumi更新了,可以转为调用bilireq.bangumi的get_meta方法,两者完全一致。
|
||||
"""
|
||||
from bilireq.utils import get
|
||||
|
||||
BASE_URL = "https://api.bilibili.com"
|
||||
url = f"{BASE_URL}/pgc/review/user"
|
||||
params = {"media_id": media_id}
|
||||
raw_json = await get(url, raw=True, params=params, auth=auth, reqtype=reqtype, **kwargs)
|
||||
return raw_json['result']
|
||||
|
||||
|
||||
async def get_videos(
|
||||
uid: int, tid: int = 0, pn: int = 1, keyword: str = "", order: str = "pubdate",
|
||||
*, auth=None, reqtype="both", **kwargs
|
||||
):
|
||||
"""
|
||||
获取用户投该视频信息
|
||||
作为bilibili_api和bilireq的替代品。
|
||||
如果bilireq.user更新了,可以转为调用bilireq.user的get_videos方法,两者完全一致。
|
||||
|
||||
:param uid: 用户 UID
|
||||
:param tid: 分区 ID
|
||||
:param pn: 页码
|
||||
:param keyword: 搜索关键词
|
||||
:param order: 排序方式,可以为 “pubdate(上传日期从新到旧), stow(收藏从多到少), click(播放量从多到少)”
|
||||
"""
|
||||
from bilireq.utils import get
|
||||
BASE_URL = "https://api.bilibili.com"
|
||||
url = f"{BASE_URL}/x/space/arc/search"
|
||||
params = {
|
||||
"mid": uid,
|
||||
"ps": 30,
|
||||
"tid": tid,
|
||||
"pn": pn,
|
||||
"keyword": keyword,
|
||||
"order": order
|
||||
}
|
||||
return await get(url, params=params, auth=auth, reqtype=reqtype, **kwargs)
|
||||
@@ -0,0 +1,253 @@
|
||||
from nonebot.adapters.onebot.v11 import (
|
||||
Event,
|
||||
MessageEvent,
|
||||
GroupMessageEvent,
|
||||
Message,
|
||||
Bot,
|
||||
)
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.message import run_preprocessor
|
||||
from utils.image_utils import BuildImage
|
||||
from utils.manager import group_manager
|
||||
from utils.utils import get_message_text, is_number
|
||||
from nonebot.params import RegexGroup, CommandArg
|
||||
from .utils import black_word_manager
|
||||
from nonebot import on_command, on_message, on_regex
|
||||
from configs.config import Config, NICKNAME
|
||||
from nonebot.permission import SUPERUSER
|
||||
from .data_source import show_black_text_image, set_user_punish
|
||||
from services.log import logger
|
||||
from models.ban_user import BanUser
|
||||
from datetime import datetime
|
||||
from utils.message_builder import image
|
||||
from .model import BlackWord
|
||||
from typing import Tuple, Any
|
||||
|
||||
|
||||
__zx_plugin_name__ = "敏感词检测"
|
||||
__plugin_usage__ = """
|
||||
usage:
|
||||
注意你的发言!
|
||||
指令:
|
||||
惩罚机制
|
||||
""".strip()
|
||||
__plugin_superuser_usage__ = """
|
||||
usage:
|
||||
查看和设置惩罚
|
||||
Regex:^记录名单(u:\d*)?(g:\d*)?(d[=><]\d*-\d{1,2}-\d{1,2})?$
|
||||
设置惩罚id需要通过 '记录名单u:xxxxxxxx' 获取
|
||||
指令:
|
||||
记录名单
|
||||
设置惩罚 [user_id] [id] [punish_level]
|
||||
示例:记录名单
|
||||
示例:记录名单u:12345678
|
||||
示例:设置惩罚 12345678 1 4
|
||||
""".strip()
|
||||
__plugin_des__ = "请注意你的发言!!"
|
||||
__plugin_type__ = ("其他",)
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
__plugin_cmd__ = ["惩罚机制", "记录名单 [_superuser]", "设置惩罚 [_superuser]"]
|
||||
__plugin_settings__ = {
|
||||
"cmd": ["敏感词检测"],
|
||||
}
|
||||
|
||||
|
||||
Config.add_plugin_config(
|
||||
"black_word", "CYCLE_DAYS", 30, name="敏感词检测与惩罚", help_="黑名单词汇记录周期", default_value=30
|
||||
)
|
||||
|
||||
Config.add_plugin_config(
|
||||
"black_word",
|
||||
"TOLERATE_COUNT",
|
||||
[5, 1, 1, 1, 1],
|
||||
help_="各个级别惩罚的容忍次数,依次为:1, 2, 3, 4, 5",
|
||||
default_value=[5, 1, 1, 1, 1],
|
||||
)
|
||||
|
||||
Config.add_plugin_config(
|
||||
"black_word", "AUTO_PUNISH", True, help_="是否启动自动惩罚机制", default_value=True
|
||||
)
|
||||
|
||||
# Config.add_plugin_config(
|
||||
# "black_word", "IGNORE_GROUP", [], help_="退出群聊惩罚中忽略的群聊,即不会退出的群聊", default_value=[]
|
||||
# )
|
||||
|
||||
Config.add_plugin_config(
|
||||
"black_word",
|
||||
"BAN_4_DURATION",
|
||||
360,
|
||||
help_="Union[int, List[int, int]]Ban时长(分钟),四级惩罚,可以为指定数字或指定列表区间(随机),例如 [30, 360]",
|
||||
default_value=360,
|
||||
)
|
||||
|
||||
Config.add_plugin_config(
|
||||
"black_word",
|
||||
"BAN_3_DURATION",
|
||||
7,
|
||||
help_="Union[int, List[int, int]]Ban时长(天),三级惩罚,可以为指定数字或指定列表区间(随机),例如 [7, 30]",
|
||||
default_value=360,
|
||||
)
|
||||
|
||||
Config.add_plugin_config(
|
||||
"black_word",
|
||||
"WARNING_RESULT",
|
||||
f"请注意对{NICKNAME}的发言内容",
|
||||
help_="口头警告内容",
|
||||
default_value=f"请注意对{NICKNAME}的发言内容",
|
||||
)
|
||||
|
||||
Config.add_plugin_config(
|
||||
"black_word",
|
||||
"AUTO_ADD_PUNISH_LEVEL",
|
||||
True,
|
||||
help_="自动提级机制,当周期内处罚次数大于某一特定值就提升惩罚等级",
|
||||
default_value=True,
|
||||
)
|
||||
|
||||
Config.add_plugin_config(
|
||||
"black_word",
|
||||
"ADD_PUNISH_LEVEL_TO_COUNT",
|
||||
3,
|
||||
help_="在CYCLE_DAYS周期内触发指定惩罚次数后提升惩罚等级",
|
||||
default_value=3,
|
||||
)
|
||||
|
||||
Config.add_plugin_config(
|
||||
"black_word",
|
||||
"ALAPI_CHECK_FLAG",
|
||||
False,
|
||||
help_="当未检测到已收录的敏感词时,开启ALAPI文本检测并将疑似文本发送给超级用户",
|
||||
default_value=False,
|
||||
)
|
||||
|
||||
Config.add_plugin_config(
|
||||
"black_word",
|
||||
"CONTAIN_BLACK_STOP_PROPAGATION",
|
||||
True,
|
||||
help_="当文本包含任意敏感词时,停止向下级插件传递,即不触发ai",
|
||||
default_value=True,
|
||||
)
|
||||
|
||||
message_matcher = on_message(priority=1, block=False)
|
||||
|
||||
set_punish = on_command("设置惩罚", priority=1, permission=SUPERUSER, block=True)
|
||||
|
||||
show_black = on_regex(
|
||||
r"^记录名单(u:\d*)?(g:\d*)?(d[=><]\d*-\d{1,2}-\d{1,2})?$",
|
||||
priority=1,
|
||||
permission=SUPERUSER,
|
||||
block=True,
|
||||
)
|
||||
|
||||
show_punish = on_command("惩罚机制", aliases={"敏感词检测"}, priority=1, block=True)
|
||||
|
||||
|
||||
# 黑名单词汇检测
|
||||
@run_preprocessor
|
||||
async def _(
|
||||
bot: Bot,
|
||||
matcher: Matcher,
|
||||
event: Event,
|
||||
):
|
||||
if (
|
||||
isinstance(event, MessageEvent)
|
||||
and event.is_tome()
|
||||
and matcher.plugin_name == "black_word"
|
||||
and not await BanUser.is_ban(event.user_id)
|
||||
and str(event.user_id) not in bot.config.superusers
|
||||
and not get_message_text(event.json()).startswith("原神绑定")
|
||||
):
|
||||
# 屏蔽群权限-1的群
|
||||
if isinstance(event, GroupMessageEvent) and group_manager.get_group_level(event.group_id) < 0:
|
||||
return
|
||||
user_id = event.user_id
|
||||
group_id = event.group_id if isinstance(event, GroupMessageEvent) else None
|
||||
msg = get_message_text(event.json())
|
||||
if await black_word_manager.check(user_id, group_id, msg) and Config.get_config(
|
||||
"black_word", "CONTAIN_BLACK_STOP_PROPAGATION"
|
||||
):
|
||||
matcher.stop_propagation()
|
||||
|
||||
|
||||
@show_black.handle()
|
||||
async def _(bot: Bot, reg_group: Tuple[Any, ...] = RegexGroup()):
|
||||
user_id, group_id, date = reg_group
|
||||
date_type = "="
|
||||
if date:
|
||||
date_type = date[1]
|
||||
date = date[2:]
|
||||
try:
|
||||
date = datetime.strptime(date, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
await show_black.finish("日期格式错误,需要:年-月-日")
|
||||
pic = await show_black_text_image(
|
||||
bot,
|
||||
int(user_id.split(":")[1]) if user_id else None,
|
||||
int(group_id.split(":")[1]) if group_id else None,
|
||||
date,
|
||||
date_type,
|
||||
)
|
||||
await show_black.send(image(b64=pic.pic2bs4()))
|
||||
|
||||
|
||||
@show_punish.handle()
|
||||
async def _():
|
||||
text = f"""
|
||||
** 惩罚机制 **
|
||||
|
||||
惩罚前包含容忍机制,在指定周期内会容忍偶尔少次数的敏感词只会进行警告提醒
|
||||
|
||||
多次触发同级惩罚会使惩罚等级提高,即惩罚自动提级机制
|
||||
|
||||
目前公开的惩罚等级:
|
||||
|
||||
1级:永久ban
|
||||
|
||||
2级:删除好友
|
||||
|
||||
3级:ban指定/随机天数
|
||||
|
||||
4级:ban指定/随机时长
|
||||
|
||||
5级:警告
|
||||
|
||||
备注:
|
||||
|
||||
该功能为测试阶段,如果你有被误封情况,请联系管理员,会从数据库中提取出你的数据进行审核后判断
|
||||
|
||||
目前该功能暂不完善,部分情况会由管理员鉴定,请注意对真寻的发言
|
||||
|
||||
关于敏感词:
|
||||
|
||||
记住不要骂{NICKNAME}就对了!
|
||||
""".strip()
|
||||
max_width = 0
|
||||
for m in text.split("\n"):
|
||||
max_width = len(m) * 20 if len(m) * 20 > max_width else max_width
|
||||
max_height = len(text.split("\n")) * 24
|
||||
A = BuildImage(
|
||||
max_width, max_height, font="CJGaoDeGuo.otf", font_size=24, color="#E3DBD1"
|
||||
)
|
||||
A.text((10, 10), text)
|
||||
await show_punish.send(image(b64=A.pic2bs4()))
|
||||
|
||||
|
||||
@set_punish.handle()
|
||||
async def _(event: MessageEvent, arg: Message = CommandArg()):
|
||||
msg = arg.extract_plain_text().strip().split()
|
||||
if (
|
||||
len(msg) < 3
|
||||
or not is_number(msg[0])
|
||||
or not is_number(msg[1])
|
||||
or not is_number(msg[2])
|
||||
):
|
||||
await set_punish.finish("参数错误,请查看帮助...", at_sender=True)
|
||||
uid = int(msg[0])
|
||||
id_ = int(msg[1])
|
||||
punish_level = int(msg[2])
|
||||
rst = await set_user_punish(uid, id_, punish_level)
|
||||
await set_punish.send(rst)
|
||||
logger.info(
|
||||
f"USER {event.user_id} 设置惩罚 uid:{uid} id_:{id_} punish_level:{punish_level} --> {rst}"
|
||||
)
|
||||
@@ -0,0 +1,118 @@
|
||||
from nonebot.adapters.onebot.v11 import Bot
|
||||
from utils.image_utils import BuildImage, text2image
|
||||
from services.log import logger
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from .model import BlackWord
|
||||
from .utils import _get_punish, Config
|
||||
|
||||
|
||||
async def show_black_text_image(
|
||||
bot: Bot,
|
||||
user: Optional[int],
|
||||
group_id: Optional[int],
|
||||
date: Optional[datetime],
|
||||
data_type: str = "=",
|
||||
) -> BuildImage:
|
||||
"""
|
||||
展示记录名单
|
||||
:param bot: bot
|
||||
:param user: 用户qq
|
||||
:param group_id: 群聊
|
||||
:param date: 日期
|
||||
:param data_type: 日期搜索类型
|
||||
:return:
|
||||
"""
|
||||
data = await BlackWord.get_black_data(user, group_id, date, data_type)
|
||||
A = BuildImage(0, 0, color="#f9f6f2", font_size=20)
|
||||
image_list = []
|
||||
friend_str = await bot.get_friend_list()
|
||||
id_str = ""
|
||||
uname_str = ""
|
||||
uid_str = ""
|
||||
gid_str = ""
|
||||
plant_text_str = ""
|
||||
black_word_str = ""
|
||||
punish_str = ""
|
||||
punish_level_str = ""
|
||||
create_time_str = ""
|
||||
for i, x in enumerate(data):
|
||||
try:
|
||||
if x.group_id:
|
||||
user_name = (
|
||||
await bot.get_group_member_info(
|
||||
group_id=x.group_id, user_id=x.user_qq
|
||||
)
|
||||
)["card"]
|
||||
else:
|
||||
user_name = [
|
||||
u["nickname"] for u in friend_str if u["user_id"] == x.user_qq
|
||||
][0]
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"show_black_text_image 获取 USER {x.user_qq} user_name 失败 {type(e)}:{e}"
|
||||
)
|
||||
user_name = x.user_qq
|
||||
id_str += f"{i}\n"
|
||||
uname_str += f"{user_name}\n"
|
||||
uid_str += f"{x.user_qq}\n"
|
||||
gid_str += f"{x.group_id}\n"
|
||||
plant_text = " ".join(x.plant_text.split("\n"))
|
||||
if A.getsize(plant_text)[0] > 200:
|
||||
plant_text = plant_text[:20] + "..."
|
||||
plant_text_str += f"{plant_text}\n"
|
||||
black_word_str += f"{x.black_word}\n"
|
||||
punish_str += f"{x.punish}\n"
|
||||
punish_level_str += f"{x.punish_level}\n"
|
||||
create_time_str += f"{x.create_time.replace(microsecond=0)}\n"
|
||||
_tmp_img = BuildImage(0, 0, font_size=35, font="CJGaoDeGuo.otf")
|
||||
for s, type_ in [
|
||||
(id_str, "Id"),
|
||||
(uname_str, "昵称"),
|
||||
(uid_str, "UID"),
|
||||
(gid_str, "GID"),
|
||||
(plant_text_str, "文本"),
|
||||
(black_word_str, "检测"),
|
||||
(punish_str, "惩罚"),
|
||||
(punish_level_str, "等级"),
|
||||
(create_time_str, "记录日期"),
|
||||
]:
|
||||
img = await text2image(s, color="#f9f6f2", _add_height=2.1)
|
||||
w = _tmp_img.getsize(type_)[0] if _tmp_img.getsize(type_)[0] > img.w else img.w
|
||||
A = BuildImage(w + 11, img.h + 50, color="#f9f6f2", font_size=35, font="CJGaoDeGuo.otf")
|
||||
await A.atext((10, 10), type_)
|
||||
await A.apaste(img, (0, 50))
|
||||
image_list.append(A)
|
||||
horizontal_line = []
|
||||
w, h = 0, 0
|
||||
for img in image_list:
|
||||
w += img.w + 20
|
||||
h = img.h if img.h > h else h
|
||||
horizontal_line.append(img.w)
|
||||
A = BuildImage(w, h, color="#f9f6f2")
|
||||
current_w = 0
|
||||
for img in image_list:
|
||||
await A.apaste(img, (current_w, 0))
|
||||
current_w += img.w + 20
|
||||
return A
|
||||
|
||||
|
||||
async def set_user_punish(user_id: int, id_: int, punish_level: int) -> str:
|
||||
"""
|
||||
设置惩罚
|
||||
:param user_id: 用户id
|
||||
:param id_: 记录下标
|
||||
:param punish_level: 惩罚等级
|
||||
"""
|
||||
result = await _get_punish(punish_level, user_id)
|
||||
punish = {
|
||||
1: "永久ban",
|
||||
2: "删除好友",
|
||||
3: f"ban {result} 天",
|
||||
4: f"ban {result} 分钟",
|
||||
5: "口头警告"
|
||||
}
|
||||
if await BlackWord.set_user_punish(user_id, punish[punish_level], id_=id_):
|
||||
return f"已对 USER {user_id} 进行 {punish[punish_level]} 处罚。"
|
||||
else:
|
||||
return "操作失败,可能未找到用户,id或敏感词"
|
||||
@@ -0,0 +1,148 @@
|
||||
from services.db_context import db
|
||||
from typing import Optional, List
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
class BlackWord(db.Model):
|
||||
__tablename__ = "black_word"
|
||||
|
||||
id = db.Column(db.Integer(), primary_key=True, autoincrement=True)
|
||||
user_qq = db.Column(db.BigInteger(), nullable=False, primary_key=True)
|
||||
group_id = db.Column(db.BigInteger())
|
||||
plant_text = db.Column(db.String())
|
||||
black_word = db.Column(db.String())
|
||||
punish = db.Column(db.String(), default="")
|
||||
punish_level = db.Column(db.Integer())
|
||||
create_time = db.Column(db.DateTime(timezone=True), nullable=False)
|
||||
|
||||
@classmethod
|
||||
async def add_user_black_word(
|
||||
cls,
|
||||
user_qq: int,
|
||||
group_id: Optional[int],
|
||||
black_word: str,
|
||||
plant_text: str,
|
||||
punish_level: int,
|
||||
):
|
||||
"""
|
||||
说明:
|
||||
添加用户发送的敏感词
|
||||
参数:
|
||||
:param user_qq: 用户id
|
||||
:param group_id: 群号
|
||||
:param black_word: 黑名单词汇
|
||||
:param plant_text: 消息文本
|
||||
:param punish_level: 惩罚等级
|
||||
"""
|
||||
await cls.create(
|
||||
user_qq=user_qq,
|
||||
group_id=group_id,
|
||||
plant_text=plant_text,
|
||||
black_word=black_word,
|
||||
punish_level=punish_level,
|
||||
create_time=datetime.now(),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def set_user_punish(
|
||||
cls,
|
||||
user_qq: int,
|
||||
punish: str,
|
||||
black_word: Optional[str] = None,
|
||||
id_: Optional[int] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
设置处罚
|
||||
参数:
|
||||
:param user_qq: 用户id
|
||||
:param punish: 处罚
|
||||
:param black_word: 黑名单词汇
|
||||
:param id_: 记录下标
|
||||
"""
|
||||
user = None
|
||||
if (not black_word and not id_) or not punish:
|
||||
return False
|
||||
query = cls.query.where(cls.user_qq == user_qq).with_for_update()
|
||||
if black_word:
|
||||
user = await query.where(cls.black_word == black_word).order_by(cls.id.desc()).gino.first()
|
||||
elif id_:
|
||||
user_list = await query.gino.all()
|
||||
if len(user_list) == 0 or (id_ < 0 or id_ > len(user_list)):
|
||||
return False
|
||||
user = user_list[id_]
|
||||
if not user:
|
||||
return False
|
||||
await user.update(punish=cls.punish + punish + " ").apply()
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
async def get_user_count(
|
||||
cls, user_qq: int, days: int = 7, punish_level: Optional[int] = None
|
||||
) -> int:
|
||||
"""
|
||||
说明:
|
||||
获取用户规定周期内的犯事次数
|
||||
参数:
|
||||
:param user_qq: 用户qq
|
||||
:param days: 周期天数
|
||||
:param punish_level: 惩罚等级
|
||||
"""
|
||||
setattr(BlackWord, "count", db.func.count(cls.id).label("count"))
|
||||
query = cls.select("count").where(
|
||||
(cls.user_qq == user_qq)
|
||||
& (cls.punish_level != -1)
|
||||
& (cls.create_time > datetime.now() - timedelta(days=days))
|
||||
)
|
||||
if punish_level is not None:
|
||||
query = query.where(cls.punish_level == punish_level)
|
||||
return (await query.gino.first())[0]
|
||||
|
||||
@classmethod
|
||||
async def get_user_punish_level(cls, user_qq: int, days: int = 7) -> Optional[int]:
|
||||
"""
|
||||
说明:
|
||||
获取用户最近一次的惩罚记录等级
|
||||
参数:
|
||||
:param user_qq: 用户qq
|
||||
:param days: 周期天数
|
||||
"""
|
||||
if (
|
||||
query := await cls.query.where(cls.user_qq == user_qq)
|
||||
.where(cls.create_time > datetime.now() - timedelta(days=days))
|
||||
.order_by(cls.id.desc())
|
||||
.gino.first()
|
||||
):
|
||||
return query.punish_level
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def get_black_data(
|
||||
cls,
|
||||
user_qq: Optional[int],
|
||||
group_id: Optional[int],
|
||||
date: Optional[datetime],
|
||||
date_type: str = "=",
|
||||
) -> List["BlackWord"]:
|
||||
"""
|
||||
说明:
|
||||
通过指定条件查询数据
|
||||
参数:
|
||||
:param user_qq: 用户qq
|
||||
:param group_id: 群号
|
||||
:param date: 日期
|
||||
:param date_type: 日期查询类型
|
||||
"""
|
||||
query = cls.query
|
||||
if user_qq:
|
||||
query = query.where(cls.user_qq == user_qq)
|
||||
if group_id:
|
||||
query = query.where(cls.group_id == group_id)
|
||||
if date:
|
||||
if date_type == "=":
|
||||
query = query.where(cls.create_time == date)
|
||||
elif date_type == ">":
|
||||
query = query.where(cls.create_time > date)
|
||||
elif date_type == "<":
|
||||
query = query.where(cls.create_time < date)
|
||||
return await query.gino.all()
|
||||
@@ -0,0 +1,322 @@
|
||||
from utils.utils import cn2py, get_bot
|
||||
from configs.path_config import DATA_PATH
|
||||
from typing import Optional, Union, Tuple
|
||||
from .model import BlackWord
|
||||
from configs.config import Config
|
||||
from pathlib import Path
|
||||
from services.log import logger
|
||||
from models.ban_user import BanUser
|
||||
from nonebot.adapters.onebot.v11 import ActionFailed
|
||||
from models.group_member_info import GroupInfoUser
|
||||
from utils.http_utils import AsyncHttpx
|
||||
import random
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
|
||||
class BlackWordManager:
|
||||
|
||||
"""
|
||||
敏感词管理( 拒绝恶意
|
||||
"""
|
||||
|
||||
def __init__(self, word_file: Path, py_file: Path):
|
||||
self._word_list = {
|
||||
"1": [],
|
||||
"2": [],
|
||||
"3": [],
|
||||
"4": ["sb", "nmsl", "mdzz", "2b", "jb", "操", "废物", "憨憨", "cnm", "rnm"],
|
||||
"5": [],
|
||||
}
|
||||
self._py_list = {
|
||||
"1": [],
|
||||
"2": [],
|
||||
"3": [],
|
||||
"4": [
|
||||
"shabi",
|
||||
"wocaonima",
|
||||
"sima",
|
||||
"sabi",
|
||||
"zhizhang",
|
||||
"naocan",
|
||||
"caonima",
|
||||
"rinima",
|
||||
"simadongxi",
|
||||
"simawanyi",
|
||||
"hanbi",
|
||||
"hanpi",
|
||||
"laji",
|
||||
"fw"
|
||||
],
|
||||
"5": [],
|
||||
}
|
||||
word_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
if word_file.exists():
|
||||
# 清空默认配置
|
||||
with open(word_file, "r", encoding="utf8") as f:
|
||||
self._word_list = json.load(f)
|
||||
else:
|
||||
with open(word_file, "w", encoding="utf8") as f:
|
||||
json.dump(
|
||||
self._word_list,
|
||||
f,
|
||||
ensure_ascii=False,
|
||||
indent=4,
|
||||
)
|
||||
if py_file.exists():
|
||||
# 清空默认配置
|
||||
with open(py_file, "r", encoding="utf8") as f:
|
||||
self._py_list = json.load(f)
|
||||
else:
|
||||
with open(py_file, "w", encoding="utf8") as f:
|
||||
json.dump(
|
||||
self._py_list,
|
||||
f,
|
||||
ensure_ascii=False,
|
||||
indent=4,
|
||||
)
|
||||
|
||||
async def check(
|
||||
self, user_id: int, group_id: Optional[int], message: str
|
||||
) -> Optional[Union[str, bool]]:
|
||||
"""
|
||||
检查是否包含黑名单词汇
|
||||
:param user_id: 用户id
|
||||
:param group_id: 群号
|
||||
:param message: 消息
|
||||
"""
|
||||
if data := self._check(message):
|
||||
if data[0]:
|
||||
await _add_user_black_word(
|
||||
user_id, group_id, data[0], message, int(data[1])
|
||||
)
|
||||
return True
|
||||
if Config.get_config(
|
||||
"black_word", "ALAPI_CHECK_FLAG"
|
||||
) and not await check_text(message):
|
||||
await send_msg(
|
||||
0, None, f"USER {user_id} GROUP {group_id} ALAPI 疑似检测:{message}"
|
||||
)
|
||||
return False
|
||||
|
||||
def _check(self, message: str) -> Tuple[Optional[str], int]:
|
||||
"""
|
||||
检测文本是否违规
|
||||
:param message: 检测消息
|
||||
"""
|
||||
# 移除空格
|
||||
message = message.replace(" ", "")
|
||||
py_msg = cn2py(message).lower()
|
||||
# 完全匹配
|
||||
for x in [self._word_list, self._py_list]:
|
||||
for level in x:
|
||||
if message in x[level] or py_msg in x[level]:
|
||||
return message if message in x[level] else py_msg, level
|
||||
# 模糊匹配
|
||||
for x in [self._word_list, self._py_list]:
|
||||
for level in x:
|
||||
for m in x[level]:
|
||||
if m in message or m in py_msg:
|
||||
return m, -1
|
||||
return None, 0
|
||||
|
||||
|
||||
async def _add_user_black_word(
|
||||
user_id: int,
|
||||
group_id: Optional[int],
|
||||
black_word: str,
|
||||
message: str,
|
||||
punish_level: int,
|
||||
):
|
||||
"""
|
||||
添加敏感词数据
|
||||
:param user_id: 用户id
|
||||
:param group_id: 群号
|
||||
:param black_word: 触发的黑名单词汇
|
||||
:param message: 原始文本
|
||||
:param punish_level: 惩罚等级
|
||||
"""
|
||||
cycle_days = Config.get_config("black_word", "CYCLE_DAYS") or 7
|
||||
user_count = await BlackWord.get_user_count(user_id, cycle_days, punish_level)
|
||||
# 周期内超过次数直接提升惩罚
|
||||
if Config.get_config(
|
||||
"black_word", "AUTO_ADD_PUNISH_LEVEL"
|
||||
) and user_count > Config.get_config("black_word", "ADD_PUNISH_LEVEL_TO_COUNT"):
|
||||
punish_level -= 1
|
||||
await BlackWord.add_user_black_word(
|
||||
user_id, group_id, black_word, message, punish_level
|
||||
)
|
||||
logger.info(
|
||||
f"已将 USER {user_id} GROUP {group_id} 添加至黑名单词汇记录 Black_word:{black_word} Plant_text:{message}"
|
||||
)
|
||||
# 自动惩罚
|
||||
if Config.get_config("black_word", "AUTO_PUNISH") and punish_level != -1:
|
||||
await _punish_handle(user_id, group_id, punish_level, black_word)
|
||||
|
||||
|
||||
async def _punish_handle(
|
||||
user_id: int, group_id: Optional[int], punish_level: int, black_word: str
|
||||
):
|
||||
"""
|
||||
惩罚措施,级别越低惩罚越严
|
||||
:param user_id: 用户id
|
||||
:param group_id: 群号
|
||||
:param black_word: 触发的黑名单词汇
|
||||
"""
|
||||
logger.info(f"BlackWord USER {user_id} 触发 {punish_level} 级惩罚...")
|
||||
# 周期天数
|
||||
cycle_days = Config.get_config("black_word", "CYCLE_DAYS") or 7
|
||||
# 用户周期内触发punish_level级惩罚的次数
|
||||
user_count = await BlackWord.get_user_count(user_id, cycle_days, punish_level)
|
||||
# 获取最近一次的惩罚等级,将在此基础上增加
|
||||
punish_level = await BlackWord.get_user_punish_level(user_id, cycle_days) or punish_level
|
||||
# 容忍次数:List[int]
|
||||
tolerate_count = Config.get_config("black_word", "TOLERATE_COUNT")
|
||||
if not tolerate_count or len(tolerate_count) < 5:
|
||||
tolerate_count = [5, 2, 2, 2, 2]
|
||||
if punish_level == 1 and user_count > tolerate_count[punish_level - 1]:
|
||||
# 永久ban
|
||||
await _get_punish(1, user_id, group_id)
|
||||
await BlackWord.set_user_punish(user_id, "永久ban 删除好友", black_word)
|
||||
elif punish_level == 2 and user_count > tolerate_count[punish_level - 1]:
|
||||
# 删除好友
|
||||
await _get_punish(2, user_id, group_id)
|
||||
await BlackWord.set_user_punish(user_id, "删除好友", black_word)
|
||||
elif punish_level == 3 and user_count > tolerate_count[punish_level - 1]:
|
||||
# 永久ban
|
||||
ban_day = await _get_punish(3, user_id, group_id)
|
||||
await BlackWord.set_user_punish(user_id, f"ban {ban_day} 天", black_word)
|
||||
elif punish_level == 4 and user_count > tolerate_count[punish_level - 1]:
|
||||
# ban指定时长
|
||||
ban_time = await _get_punish(4, user_id, group_id)
|
||||
await BlackWord.set_user_punish(user_id, f"ban {ban_time} 分钟", black_word)
|
||||
elif punish_level == 5 and user_count > tolerate_count[punish_level - 1]:
|
||||
# 口头警告
|
||||
warning_result = await _get_punish(5, user_id, group_id)
|
||||
await BlackWord.set_user_punish(user_id, f"口头警告:{warning_result}", black_word)
|
||||
else:
|
||||
await BlackWord.set_user_punish(user_id, f"提示!", black_word)
|
||||
await send_msg(
|
||||
user_id,
|
||||
group_id,
|
||||
f"BlackWordChecker:该条发言已被记录,目前你在{cycle_days}天内的发表{punish_level}级"
|
||||
f"言论记录次数为:{user_count}次,请注意你的发言\n"
|
||||
f"* 如果你不清楚惩罚机制,请发送“惩罚机制” *",
|
||||
)
|
||||
|
||||
|
||||
async def _get_punish(
|
||||
id_: int, user_id: int, group_id: Optional[int] = None
|
||||
) -> Optional[Union[int, str]]:
|
||||
"""
|
||||
通过id_获取惩罚
|
||||
:param id_: id
|
||||
:param user_id: 用户id
|
||||
:param group_id: 群号
|
||||
"""
|
||||
bot = get_bot()
|
||||
# 忽略的群聊
|
||||
# _ignore_group = Config.get_config("black_word", "IGNORE_GROUP")
|
||||
# 处罚 id 4 ban 时间:int,List[int]
|
||||
ban_3_duration = Config.get_config("black_word", "BAN_3_DURATION")
|
||||
# 处罚 id 4 ban 时间:int,List[int]
|
||||
ban_4_duration = Config.get_config("black_word", "BAN_4_DURATION")
|
||||
# 口头警告内容
|
||||
warning_result = Config.get_config("black_word", "WARNING_RESULT")
|
||||
try:
|
||||
uname = (await GroupInfoUser.get_member_info(user_id, group_id)).user_name
|
||||
except AttributeError:
|
||||
uname = user_id
|
||||
# 永久ban
|
||||
if id_ == 1:
|
||||
if str(user_id) not in bot.config.superusers:
|
||||
await BanUser.ban(user_id, 10, 99999999)
|
||||
await send_msg(user_id, group_id, f"BlackWordChecker 永久ban USER {uname}({user_id})")
|
||||
logger.info(f"BlackWord 永久封禁 USER {user_id}...")
|
||||
# 删除好友(有的话
|
||||
elif id_ == 2:
|
||||
if str(user_id) not in bot.config.superusers:
|
||||
try:
|
||||
await bot.delete_friend(user_id=user_id)
|
||||
await send_msg(
|
||||
user_id, group_id, f"BlackWordChecker 删除好友 USER {uname}({user_id})"
|
||||
)
|
||||
logger.info(f"BlackWord 删除好友 {user_id}...")
|
||||
except ActionFailed:
|
||||
pass
|
||||
# 封禁用户指定时间,默认7天
|
||||
elif id_ == 3:
|
||||
if isinstance(ban_3_duration, list):
|
||||
ban_3_duration = random.randint(ban_3_duration[0], ban_3_duration[1])
|
||||
await BanUser.ban(user_id, 9, ban_4_duration * 60 * 60 * 24)
|
||||
await send_msg(
|
||||
user_id,
|
||||
group_id,
|
||||
f"BlackWordChecker 对用户 USER {uname}({user_id}) 进行封禁 {ban_3_duration} 天处罚。",
|
||||
)
|
||||
logger.info(f"BlackWord 封禁 USER {uname}({user_id}) {ban_3_duration} 天...")
|
||||
return ban_3_duration
|
||||
# 封禁用户指定时间,默认360分钟
|
||||
elif id_ == 4:
|
||||
if isinstance(ban_4_duration, list):
|
||||
ban_4_duration = random.randint(ban_4_duration[0], ban_4_duration[1])
|
||||
await BanUser.ban(user_id, 9, ban_4_duration * 60)
|
||||
await send_msg(
|
||||
user_id,
|
||||
group_id,
|
||||
f"BlackWordChecker 对用户 USER {uname}({user_id}) 进行封禁 {ban_4_duration} 分钟处罚。",
|
||||
)
|
||||
logger.info(f"BlackWord 封禁 USER {uname}({user_id}) {ban_4_duration} 分钟...")
|
||||
return ban_4_duration
|
||||
# 口头警告
|
||||
elif id_ == 5:
|
||||
if group_id:
|
||||
await bot.send_group_msg(group_id=group_id, message=warning_result)
|
||||
else:
|
||||
await bot.send_private_msg(user_id=user_id, message=warning_result)
|
||||
logger.info(f"BlackWord 口头警告 USER {user_id}")
|
||||
return warning_result
|
||||
return None
|
||||
|
||||
|
||||
async def send_msg(user_id: int, group_id: Optional[int], message: str):
|
||||
"""
|
||||
发送消息
|
||||
:param user_id: user_id
|
||||
:param group_id: group_id
|
||||
:param message: message
|
||||
"""
|
||||
bot = get_bot()
|
||||
if not user_id:
|
||||
user_id = int(list(bot.config.superusers)[0])
|
||||
if group_id:
|
||||
await bot.send_group_msg(group_id=group_id, message=message)
|
||||
else:
|
||||
await bot.send_private_msg(user_id=user_id, message=message)
|
||||
|
||||
|
||||
async def check_text(text: str) -> bool:
|
||||
"""
|
||||
ALAPI文本检测,检测输入违规
|
||||
:param text: 回复
|
||||
"""
|
||||
if not Config.get_config("alapi", "ALAPI_TOKEN"):
|
||||
return True
|
||||
params = {"token": Config.get_config("alapi", "ALAPI_TOKEN"), "text": text}
|
||||
try:
|
||||
data = (
|
||||
await AsyncHttpx.get(
|
||||
"https://v2.alapi.cn/api/censor/text", timeout=4, params=params
|
||||
)
|
||||
).json()
|
||||
if data["code"] == 200:
|
||||
return data["data"]["conclusion_type"] == 2
|
||||
except Exception as e:
|
||||
logger.error(f"检测违规文本错误...{type(e)}:{e}")
|
||||
return True
|
||||
|
||||
|
||||
black_word_manager = BlackWordManager(DATA_PATH / "black_word" / "black_word.json", DATA_PATH / "black_word" / "black_py.json")
|
||||
@@ -80,8 +80,8 @@ async def _(
|
||||
except TimeoutError:
|
||||
await bt.finish(f"搜索 {keyword} 超时...")
|
||||
except Exception as e:
|
||||
await bt.finish(f"bt 其他未知错误..")
|
||||
logger.error(f"bt 错误 {type(e)}:{e}")
|
||||
await bt.finish(f"bt 其他未知错误..")
|
||||
if not send_flag:
|
||||
await bt.send(f"{keyword} 未搜索到...")
|
||||
logger.info(f"USER {event.user_id} BT搜索 {keyword} 第 {page} 页")
|
||||
|
||||
@@ -3,13 +3,13 @@ from configs.config import Config
|
||||
from bs4 import BeautifulSoup
|
||||
import platform
|
||||
|
||||
if platform.system() == "Windows":
|
||||
import asyncio
|
||||
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
# if platform.system() == "Windows":
|
||||
# import asyncio
|
||||
#
|
||||
# asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
|
||||
|
||||
url = "http://www.eclzz.world"
|
||||
url = "http://www.eclzz.one/"
|
||||
|
||||
|
||||
async def get_bt_info(keyword: str, page: int):
|
||||
@@ -19,7 +19,7 @@ async def get_bt_info(keyword: str, page: int):
|
||||
:param page: 页数
|
||||
"""
|
||||
text = (await AsyncHttpx.get(f"{url}/s/{keyword}_rel_{page}.html", timeout=5)).text
|
||||
if text.find("大约0条结果") != -1:
|
||||
if "大约0条结果" in text:
|
||||
return
|
||||
soup = BeautifulSoup(text, "lxml")
|
||||
item_lst = soup.find_all("div", {"class": "search-item"})
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
from .music_163 import get_song_id, get_song_info
|
||||
from nonebot.adapters.onebot.v11 import Bot, Event, GroupMessageEvent, Message
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.typing import T_State
|
||||
from services.log import logger
|
||||
from nonebot import on_command
|
||||
|
||||
|
||||
__zx_plugin_name__ = "点歌"
|
||||
__plugin_usage__ = """
|
||||
usage:
|
||||
在线点歌
|
||||
指令:
|
||||
点歌 [歌名]
|
||||
""".strip()
|
||||
__plugin_des__ = "为你点播了一首曾经的歌"
|
||||
__plugin_cmd__ = ["点歌 [歌名]"]
|
||||
__plugin_type__ = ("一些工具",)
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
__plugin_settings__ = {
|
||||
"level": 5,
|
||||
"default_status": True,
|
||||
"limit_superuser": False,
|
||||
"cmd": ["点歌"],
|
||||
}
|
||||
|
||||
|
||||
music = on_command("点歌", priority=5, block=True)
|
||||
|
||||
|
||||
@music.handle()
|
||||
async def handle_first_receive(state: T_State, arg: Message = CommandArg()):
|
||||
args = arg.extract_plain_text().strip()
|
||||
if args:
|
||||
state["song_name"] = args
|
||||
|
||||
|
||||
@music.got("song_name", prompt="歌名是?")
|
||||
async def _(bot: Bot, event: Event, state: T_State):
|
||||
song = state["song_name"]
|
||||
song_id = await get_song_id(song)
|
||||
if not song_id:
|
||||
await music.finish("没有找到这首歌!", at_sender=True)
|
||||
for _ in range(3):
|
||||
song_content = [{"type": "music", "data": {"type": 163, "id": song_id}}]
|
||||
logger.info(
|
||||
f"(USER {event.user_id}, GROUP "
|
||||
f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
|
||||
f" 点歌 :{song}"
|
||||
)
|
||||
await music.finish(song_content)
|
||||
else:
|
||||
await music.finish("网易云繁忙...")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from .data_source import check_update, get_latest_version_data
|
||||
from services.log import logger
|
||||
from utils.utils import scheduler, get_bot
|
||||
from pathlib import Path
|
||||
from configs.config import Config
|
||||
from configs.config import NICKNAME, Config
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.params import ArgStr
|
||||
import platform
|
||||
@@ -25,6 +25,11 @@ __plugin_cmd__ = ["检查更新真寻", "重启"]
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
__plugin_configs__ = {
|
||||
"UPDATE_REMIND": {
|
||||
"value": True,
|
||||
"help": "真寻是否检测版本状态",
|
||||
"default": True,
|
||||
},
|
||||
"AUTO_UPDATE_ZHENXUN": {
|
||||
"value": False,
|
||||
"help": "真寻是否自动检查更新",
|
||||
@@ -66,18 +71,18 @@ async def _(bot: Bot):
|
||||
)
|
||||
|
||||
|
||||
@restart.handle()
|
||||
async def _():
|
||||
if str(platform.system()).lower() == "windows":
|
||||
await restart.finish("暂无windows重启脚本...")
|
||||
|
||||
|
||||
@restart.got("flag", prompt="确定是否重启真寻?(重启失败咱们将失去联系,请谨慎!)")
|
||||
@restart.got("flag", prompt=f"确定是否重启{NICKNAME}?确定请回复[是|好|确定](重启失败咱们将失去联系,请谨慎!)")
|
||||
async def _(flag: str = ArgStr("flag")):
|
||||
if flag.lower() in ["true", "是", "好", "确定", "确定是"]:
|
||||
await restart.send("开始重启真寻..请稍等...")
|
||||
await restart.send(f"开始重启{NICKNAME}..请稍等...")
|
||||
open("is_restart", "w")
|
||||
os.system("./restart.sh")
|
||||
if str(platform.system()).lower() == "windows":
|
||||
import sys
|
||||
python = sys.executable
|
||||
os.execl(python, python, *sys.argv)
|
||||
else:
|
||||
os.system("./restart.sh")
|
||||
else:
|
||||
await restart.send("已取消操作...")
|
||||
|
||||
@@ -88,7 +93,7 @@ async def _(flag: str = ArgStr("flag")):
|
||||
minute=0,
|
||||
)
|
||||
async def _():
|
||||
if Config.get_config("check_zhenxun_update", "AUTO_UPDATE_ZHENXUN"):
|
||||
if Config.get_config("check_zhenxun_update", "UPDATE_REMIND"):
|
||||
_version = "v0.0.0"
|
||||
_version_file = Path() / "__version__"
|
||||
if _version_file.exists():
|
||||
@@ -108,17 +113,18 @@ async def _():
|
||||
message=f"检测到真寻版本更新\n"
|
||||
f"当前版本:{_version},最新版本:{latest_version}",
|
||||
)
|
||||
# try:
|
||||
# code = await check_update(bot)
|
||||
# except Exception as e:
|
||||
# logger.error(f"更新真寻未知错误 {type(e)}:{e}")
|
||||
# await bot.send_private_msg(
|
||||
# user_id=int(list(bot.config.superusers)[0]),
|
||||
# message=f"更新真寻未知错误 {type(e)}:{e}\n",
|
||||
# )
|
||||
# else:
|
||||
# if code == 200:
|
||||
# await bot.send_private_msg(
|
||||
# user_id=int(list(bot.config.superusers)[0]),
|
||||
# message=f"更新完毕,请重启真寻....",
|
||||
# )
|
||||
if Config.get_config("check_zhenxun_update", "AUTO_UPDATE_ZHENXUN"):
|
||||
try:
|
||||
code = await check_update(bot)
|
||||
except Exception as e:
|
||||
logger.error(f"更新真寻未知错误 {type(e)}:{e}")
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f"更新真寻未知错误 {type(e)}:{e}\n",
|
||||
)
|
||||
else:
|
||||
if code == 200:
|
||||
await bot.send_private_msg(
|
||||
user_id=int(list(bot.config.superusers)[0]),
|
||||
message=f"更新完毕,请重启{NICKNAME}....",
|
||||
)
|
||||
|
||||
@@ -14,9 +14,9 @@ import tarfile
|
||||
import shutil
|
||||
import os
|
||||
|
||||
if str(platform.system()).lower() == "windows":
|
||||
policy = asyncio.WindowsSelectorEventLoopPolicy()
|
||||
asyncio.set_event_loop_policy(policy)
|
||||
# if str(platform.system()).lower() == "windows":
|
||||
# policy = asyncio.WindowsSelectorEventLoopPolicy()
|
||||
# asyncio.set_event_loop_policy(policy)
|
||||
|
||||
|
||||
driver = nonebot.get_driver()
|
||||
@@ -73,6 +73,7 @@ async def check_update(bot: Bot) -> 'int, str':
|
||||
message=f"检测真寻已更新,当前版本:{_version},最新版本:{latest_version}\n" f"开始更新.....",
|
||||
)
|
||||
logger.info(f"开始下载真寻最新版文件....")
|
||||
tar_gz_url = (await AsyncHttpx.get(tar_gz_url)).headers.get('Location')
|
||||
if await AsyncHttpx.download_file(tar_gz_url, zhenxun_latest_tar_gz):
|
||||
logger.info("下载真寻最新版文件完成....")
|
||||
error = await asyncio.get_event_loop().run_in_executor(
|
||||
@@ -161,7 +162,7 @@ def _file_handle(latest_version: str) -> str:
|
||||
if backup_file.exists():
|
||||
backup_file.unlink()
|
||||
if file not in [config_file, config_path_file]:
|
||||
os.rename(file.absolute(), backup_file.absolute())
|
||||
shutil.move(file.absolute(), backup_file.absolute())
|
||||
else:
|
||||
with open(file, "r", encoding="utf8") as rf:
|
||||
data = rf.read()
|
||||
@@ -173,7 +174,7 @@ def _file_handle(latest_version: str) -> str:
|
||||
old_file = Path() / file
|
||||
if old_file not in [config_file, config_path_file] and file != "configs":
|
||||
if not old_file.exists() and new_file.exists():
|
||||
os.rename(new_file.absolute(), old_file.absolute())
|
||||
shutil.move(new_file.absolute(), old_file.absolute())
|
||||
logger.info(f"已更新文件:{file}")
|
||||
# except Exception as e:
|
||||
# error = f'{type(e)}:{e}'
|
||||
@@ -188,6 +189,9 @@ def _file_handle(latest_version: str) -> str:
|
||||
local_update_info_file.unlink()
|
||||
with open(_version_file, "w", encoding="utf8") as f:
|
||||
f.write(f"__version__: {latest_version}")
|
||||
os.system(
|
||||
f"poetry run pip install -r {(Path() / 'pyproject.toml').absolute()}"
|
||||
)
|
||||
return error
|
||||
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ __plugin_settings__ = {
|
||||
dialogue_data = {}
|
||||
|
||||
|
||||
dialogue = on_command("[滴滴滴]", aliases={"滴滴滴-"}, priority=5, block=True)
|
||||
dialogue = on_command("[滴滴滴]", aliases={"滴滴滴-"}, priority=5, block=True)
|
||||
reply = on_command("/t", priority=1, permission=SUPERUSER, block=True)
|
||||
|
||||
|
||||
|
||||
Executable → Regular
+182
-330
@@ -1,43 +1,50 @@
|
||||
from nonebot import on_regex, on_keyword
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, Message
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.params import RegexGroup
|
||||
from utils.utils import scheduler
|
||||
from .genshin_handle import genshin_draw, update_genshin_info, reset_count, reload_genshin_pool
|
||||
from .prts_handle import update_prts_info, prts_draw, reload_prts_pool
|
||||
from .pretty_handle import update_pretty_info, pretty_draw, reload_pretty_pool
|
||||
from .guardian_handle import update_guardian_info, guardian_draw, reload_guardian_pool
|
||||
from .pcr_handle import update_pcr_info, pcr_draw
|
||||
from .azur_handle import update_azur_info, azur_draw
|
||||
from .fgo_handle import update_fgo_info, fgo_draw
|
||||
from .onmyoji_handle import update_onmyoji_info, onmyoji_draw
|
||||
from .update_game_info import update_info
|
||||
from .util import is_number, check_num
|
||||
from .rule import is_switch
|
||||
from .config import draw_config
|
||||
from .async_update_game_info import async_update_game
|
||||
from typing import Tuple, Any
|
||||
import re
|
||||
import asyncio
|
||||
import traceback
|
||||
from cn2an import cn2an
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Set, Tuple
|
||||
|
||||
import nonebot
|
||||
from nonebot import on_regex, on_keyword
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent
|
||||
from nonebot.log import logger
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import RegexGroup
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.typing import T_Handler
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
from .handles.base_handle import BaseHandle
|
||||
from .handles.azur_handle import AzurHandle
|
||||
from .handles.fgo_handle import FgoHandle
|
||||
from .handles.genshin_handle import GenshinHandle
|
||||
from .handles.guardian_handle import GuardianHandle
|
||||
from .handles.onmyoji_handle import OnmyojiHandle
|
||||
from .handles.pcr_handle import PcrHandle
|
||||
from .handles.pretty_handle import PrettyHandle
|
||||
from .handles.prts_handle import PrtsHandle
|
||||
from .handles.ba_handle import BaHandle
|
||||
|
||||
from .config import draw_config
|
||||
|
||||
|
||||
__zx_plugin_name__ = "游戏抽卡"
|
||||
__plugin_usage__ = """
|
||||
usage:
|
||||
模拟赛马娘,原神,明日方舟,坎公骑冠剑,公主连结(国/台),碧蓝航线,FGO,阴阳师进行抽卡
|
||||
模拟赛马娘,原神,明日方舟,坎公骑冠剑,公主连结(国/台),碧蓝航线,FGO,阴阳师,碧蓝档案进行抽卡
|
||||
指令:
|
||||
原神[1-300]抽: 原神常驻池
|
||||
原神角色[1-300]抽: 原神角色UP池子
|
||||
原神武器[1-300]抽: 原神武器UP池子
|
||||
原神[1-180]抽: 原神常驻池
|
||||
原神角色[1-180]抽: 原神角色UP池子
|
||||
原神武器[1-180]抽: 原神武器UP池子
|
||||
重置原神抽卡: 清空当前卡池的抽卡次数[即从0开始计算UP概率]
|
||||
方舟[1-300]抽: 方舟卡池,当有当期UP时指向UP池
|
||||
赛马娘[1-200]抽: 赛马娘卡池,当有当期UP时指向UP池
|
||||
坎公骑冠剑[1-300]抽: 坎公骑冠剑卡池,当有当期UP时指向UP池
|
||||
pcr/公主连接[1-300]抽: 公主连接卡池
|
||||
碧蓝航线/碧蓝[重型/轻型/特型][1-300]抽: 碧蓝航线重型/轻型/特型卡池
|
||||
碧蓝航线/碧蓝[重型/轻型/特型/活动][1-300]抽: 碧蓝航线重型/轻型/特型/活动卡池
|
||||
fgo[1-300]抽: fgo卡池
|
||||
阴阳师[1-300]抽: 阴阳师卡池
|
||||
ba/碧蓝档案[1-200]抽:碧蓝档案卡池
|
||||
* 以上指令可以通过 XX一井 来指定最大抽取数量 *
|
||||
* 示例:原神一井 *
|
||||
""".strip()
|
||||
@@ -58,9 +65,9 @@ usage:
|
||||
""".strip()
|
||||
__plugin_des__ = "就算是模拟抽卡也不能改变自己是个非酋"
|
||||
__plugin_cmd__ = [
|
||||
"原神[1-300]抽",
|
||||
"原神角色[1-300]抽",
|
||||
"原神武器[1-300]抽",
|
||||
"原神[1-180]抽",
|
||||
"原神角色[1-180]抽",
|
||||
"原神武器[1-180]抽",
|
||||
"重置原神抽卡",
|
||||
"方舟[1-300]抽",
|
||||
"赛马娘[1-200]抽",
|
||||
@@ -68,6 +75,7 @@ __plugin_cmd__ = [
|
||||
"pcr/公主连接[1-300]抽",
|
||||
"fgo[1-300]抽",
|
||||
"阴阳师[1-300]抽",
|
||||
"碧蓝档案[1-200]抽",
|
||||
"更新方舟信息 [_superuser]",
|
||||
"重载方舟卡池 [_superuser]",
|
||||
"更新原神信息 [_superuser]",
|
||||
@@ -78,6 +86,7 @@ __plugin_cmd__ = [
|
||||
"更新碧蓝航线信息 [_superuser]",
|
||||
"更新fgo信息 [_superuser]",
|
||||
"更新阴阳师信息 [_superuser]",
|
||||
"更新碧蓝档案信息 [_superuser]",
|
||||
]
|
||||
__plugin_type__ = ("抽卡相关", 1)
|
||||
__plugin_version__ = 0.1
|
||||
@@ -89,325 +98,168 @@ __plugin_settings__ = {
|
||||
"cmd": ["游戏抽卡", "抽卡"],
|
||||
}
|
||||
|
||||
prts = on_regex(r'.*?方舟([1-9|一][0-9]{0,2}[抽|井|连])', rule=is_switch('prts'), priority=5, block=True)
|
||||
prts_update = on_keyword({'更新方舟信息', '更新明日方舟信息'}, permission=SUPERUSER, priority=1, block=True)
|
||||
prts_up_reload = on_keyword({'重载方舟卡池'}, priority=1, block=True)
|
||||
|
||||
genshin = on_regex(r'.*?原神(武器|角色)?池?[1-9|一][0-9]{0,2}[抽|井|连]', rule=is_switch('genshin'), priority=5, block=True)
|
||||
genshin_update = on_keyword({'更新原神信息'}, permission=SUPERUSER, priority=1, block=True)
|
||||
genshin_reset = on_keyword({'重置原神抽卡'}, priority=1, block=True)
|
||||
genshin_up_reload = on_keyword({'重载原神卡池'}, priority=1, block=True)
|
||||
|
||||
pretty = on_regex(r'.*?马娘卡?[1-9|一][0-9]{0,2}[抽|井|连]', rule=is_switch('pretty'), priority=5, block=True)
|
||||
pretty_update = on_keyword({'更新马娘信息', '更新赛马娘信息'}, permission=SUPERUSER, priority=1, block=True)
|
||||
pretty_up_reload = on_keyword({'重载赛马娘卡池'}, priority=1, block=True)
|
||||
|
||||
guardian = on_regex(r'.*?坎公骑冠剑武?器?[1-9|一][0-9]{0,2}[抽|井|连]', rule=is_switch('guardian'), priority=5, block=True)
|
||||
guardian_update = on_keyword({'更新坎公骑冠剑信息'}, permission=SUPERUSER, priority=1, block=True)
|
||||
guardian_up_reload = on_keyword({'重载坎公骑冠剑卡池'}, priority=1, block=True)
|
||||
|
||||
pcr = on_regex(r'.*?(pcr|公主连结|公主连接|公主链接|公主焊接)[1-9|一][0-9]{0,2}[抽|井|连]', rule=is_switch('pcr'), priority=5, block=True)
|
||||
pcr_update = on_keyword({'更新pcr信息', '更新公主连结信息'}, permission=SUPERUSER, priority=1, block=True)
|
||||
|
||||
azur = on_regex(r'.*?碧蓝航?线?(轻型|重型|特型)池?[1-9|一][0-9]{0,2}[抽|连]', rule=is_switch('azur'), priority=5, block=True)
|
||||
azur_update = on_keyword({'更新碧蓝信息', '更新碧蓝航线信息'}, permission=SUPERUSER, priority=1, block=True)
|
||||
|
||||
fgo = on_regex(r'.*?fgo[1-9|一][0-9]{0,2}[抽|连]', rule=is_switch('fgo'), priority=5, block=True)
|
||||
fgo_update = on_keyword({'更新fgo信息'}, permission=SUPERUSER, priority=1, block=True)
|
||||
|
||||
onmyoji = on_regex(r'.*?阴阳师[1-9|一][0-9]{0,2}[抽|连]', rule=is_switch('onmyoji'), priority=5, block=True)
|
||||
onmyoji_update = on_keyword({'更新阴阳师信息'}, permission=SUPERUSER, priority=1, block=True)
|
||||
@dataclass
|
||||
class Game:
|
||||
keywords: Set[str]
|
||||
handle: BaseHandle
|
||||
flag: bool
|
||||
max_count: int = 300 # 一次最大抽卡数
|
||||
reload_time: Optional[int] = None # 重载UP池时间(小时)
|
||||
|
||||
|
||||
@prts.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State, reg: Tuple[Any, ...] = RegexGroup()):
|
||||
msg = str(event.get_message()).strip()
|
||||
if msg in ['方舟一井', '方舟1井']:
|
||||
num = 300
|
||||
else:
|
||||
rmsg = re.search(r'.*?方舟(.*)[抽|连]', msg)
|
||||
if rmsg:
|
||||
num, flag = check_num(rmsg.group(1), 300)
|
||||
if not flag:
|
||||
await prts.finish(num, at_sender=True)
|
||||
else:
|
||||
return
|
||||
await prts.send(await prts_draw(int(num)), at_sender=True)
|
||||
games = (
|
||||
Game({"azur", "碧蓝航线"}, AzurHandle(), draw_config.AZUR_FLAG),
|
||||
Game({"fgo", "命运冠位指定"}, FgoHandle(), draw_config.FGO_FLAG),
|
||||
Game(
|
||||
{"genshin", "原神"},
|
||||
GenshinHandle(),
|
||||
draw_config.GENSHIN_FLAG,
|
||||
max_count=180,
|
||||
reload_time=18,
|
||||
),
|
||||
Game(
|
||||
{"guardian", "坎公骑冠剑"},
|
||||
GuardianHandle(),
|
||||
draw_config.GUARDIAN_FLAG,
|
||||
reload_time=4,
|
||||
),
|
||||
Game({"onmyoji", "阴阳师"}, OnmyojiHandle(), draw_config.ONMYOJI_FLAG),
|
||||
Game({"pcr", "公主连结", "公主连接", "公主链接", "公主焊接"}, PcrHandle(), draw_config.PCR_FLAG),
|
||||
Game(
|
||||
{"pretty", "马娘", "赛马娘"},
|
||||
PrettyHandle(),
|
||||
draw_config.PRETTY_FLAG,
|
||||
max_count=200,
|
||||
reload_time=4,
|
||||
),
|
||||
Game({"prts", "方舟", "明日方舟"}, PrtsHandle(), draw_config.PRTS_FLAG, reload_time=4),
|
||||
Game(
|
||||
{"ba","碧蓝档案"},BaHandle(),
|
||||
draw_config.BA_FLAG,
|
||||
max_count=200,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@prts_up_reload.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
text = await reload_prts_pool()
|
||||
await prts_up_reload.finish(Message(f'重载完成!\n{text}'))
|
||||
|
||||
|
||||
@genshin.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
msg = str(event.get_message()).strip()
|
||||
rmsg = re.search(r'.*?原神(武器|角色)?池?(.*)[抽|井|连]', msg)
|
||||
if rmsg:
|
||||
pool_name = rmsg.group(1)
|
||||
if pool_name == '武器':
|
||||
pool_name = 'arms'
|
||||
elif pool_name == '角色':
|
||||
pool_name = 'char'
|
||||
else:
|
||||
pool_name = ''
|
||||
num = rmsg.group(2)
|
||||
if msg.find('一井') != -1 or msg.find('1井') != -1:
|
||||
num = 180
|
||||
else:
|
||||
num, flag = check_num(num, 180)
|
||||
if not flag:
|
||||
await genshin.finish(num, at_sender=True)
|
||||
else:
|
||||
return
|
||||
await genshin.send(await genshin_draw(event.user_id, int(num), pool_name), at_sender=True)
|
||||
|
||||
|
||||
@genshin_up_reload.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
text = await reload_genshin_pool()
|
||||
await genshin_reset.finish(Message(f'重载成功!\n{text}'))
|
||||
|
||||
|
||||
@genshin_reset.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
reset_count(event.user_id)
|
||||
await genshin_reset.send('重置了原神抽卡次数', at_sender=True)
|
||||
|
||||
|
||||
@pretty.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
msg = str(event.get_message()).strip()
|
||||
if msg.find('1井') != -1 or msg.find('一井') != -1:
|
||||
num = 200
|
||||
if msg.find("卡") == -1:
|
||||
pool_name = 'char'
|
||||
else:
|
||||
pool_name = 'card'
|
||||
else:
|
||||
rmsg = re.search(r'.*?马娘(.*)[抽|连]', msg)
|
||||
if rmsg:
|
||||
num = rmsg.group(1)
|
||||
if num[0] == '卡':
|
||||
num = num[1:]
|
||||
pool_name = 'card'
|
||||
def create_matchers():
|
||||
def draw_handler(game: Game) -> T_Handler:
|
||||
async def handler(
|
||||
matcher: Matcher, event: MessageEvent, args: Tuple[str, ...] = RegexGroup()
|
||||
):
|
||||
pool_name, num, unit = args
|
||||
if num == "单":
|
||||
num = 1
|
||||
else:
|
||||
pool_name = 'char'
|
||||
num, flag = check_num(num, 200)
|
||||
if not flag:
|
||||
await pretty.finish(num, at_sender=True)
|
||||
else:
|
||||
return
|
||||
await pretty.send(await pretty_draw(int(num), pool_name), at_sender=True)
|
||||
try:
|
||||
num = int(cn2an(num, mode="smart"))
|
||||
except ValueError:
|
||||
await matcher.finish("必!须!是!数!字!")
|
||||
if unit == "井":
|
||||
num *= game.max_count
|
||||
if num < 1:
|
||||
await matcher.finish("虚空抽卡???")
|
||||
elif num > game.max_count:
|
||||
await matcher.finish("一井都满不足不了你嘛!快爬开!")
|
||||
pool_name = (
|
||||
pool_name.replace("池", "")
|
||||
.replace("武器", "arms")
|
||||
.replace("角色", "char")
|
||||
.replace("卡牌", "card")
|
||||
.replace("卡", "card")
|
||||
)
|
||||
try:
|
||||
res = await game.handle.draw(num, pool_name=pool_name, user_id=event.user_id)
|
||||
except:
|
||||
logger.warning(traceback.format_exc())
|
||||
await matcher.finish("出错了...")
|
||||
await matcher.finish(res, at_sender=True)
|
||||
|
||||
return handler
|
||||
|
||||
def update_handler(game: Game) -> T_Handler:
|
||||
async def handler(matcher: Matcher):
|
||||
await game.handle.update_info()
|
||||
await matcher.finish("更新完成!")
|
||||
|
||||
return handler
|
||||
|
||||
def reload_handler(game: Game) -> T_Handler:
|
||||
async def handler(matcher: Matcher):
|
||||
res = await game.handle.reload_pool()
|
||||
if res:
|
||||
await matcher.finish(res)
|
||||
|
||||
return handler
|
||||
|
||||
def reset_handler(game: Game) -> T_Handler:
|
||||
async def handler(matcher: Matcher, event: MessageEvent):
|
||||
if game.handle.reset_count(event.user_id):
|
||||
await matcher.finish("重置成功!")
|
||||
|
||||
return handler
|
||||
|
||||
def scheduled_job(game: Game) -> T_Handler:
|
||||
async def handler():
|
||||
await game.handle.reload_pool()
|
||||
|
||||
return handler
|
||||
|
||||
for game in games:
|
||||
pool_pattern = r"([^\s单0-9零一二三四五六七八九百十]{0,3})"
|
||||
num_pattern = r"(单|[0-9零一二三四五六七八九百十]{1,3})"
|
||||
unit_pattern = r"([抽|井|连])"
|
||||
draw_regex = r".*?(?:{})\s*{}\s*{}\s*{}".format(
|
||||
"|".join(game.keywords), pool_pattern, num_pattern, unit_pattern
|
||||
)
|
||||
update_keywords = {f"更新{keyword}信息" for keyword in game.keywords}
|
||||
reload_keywords = {f"重载{keyword}卡池" for keyword in game.keywords}
|
||||
reset_keywords = {f"重置{keyword}抽卡" for keyword in game.keywords}
|
||||
if game.flag:
|
||||
on_regex(draw_regex, priority=5, block=True).append_handler(
|
||||
draw_handler(game)
|
||||
)
|
||||
on_keyword(
|
||||
update_keywords, permission=SUPERUSER, priority=1, block=True
|
||||
).append_handler(update_handler(game))
|
||||
on_keyword(reload_keywords, priority=1, block=True).append_handler(
|
||||
reload_handler(game)
|
||||
)
|
||||
on_keyword(reset_keywords, priority=1, block=True).append_handler(
|
||||
reset_handler(game)
|
||||
)
|
||||
if game.reload_time:
|
||||
scheduler.add_job(
|
||||
scheduled_job(game), trigger="cron", hour=game.reload_time, minute=1
|
||||
)
|
||||
|
||||
|
||||
@pretty_up_reload.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
text = await reload_pretty_pool()
|
||||
await genshin_reset.finish(Message(f'重载成功!\n{text}'))
|
||||
|
||||
|
||||
@guardian.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
msg = str(event.get_message()).strip()
|
||||
pool_name = 'char'
|
||||
if msg.find('1井') != -1 or msg.find('一井') != -1:
|
||||
num = 300
|
||||
if msg.find('武器') != -1:
|
||||
pool_name = 'arms'
|
||||
else:
|
||||
rmsg = re.search(r'.*?坎公骑冠剑(.*)[抽|连]', msg)
|
||||
if rmsg:
|
||||
num = rmsg.group(1)
|
||||
if num.find('武器') != -1:
|
||||
pool_name = 'arms'
|
||||
num = num.replace('武器', '')
|
||||
num, flag = check_num(num, 300)
|
||||
if not flag:
|
||||
await guardian.finish(num, at_sender=True)
|
||||
else:
|
||||
return
|
||||
await guardian.send(await guardian_draw(int(num), pool_name), at_sender=True)
|
||||
|
||||
|
||||
@guardian_up_reload.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
text = await reload_guardian_pool()
|
||||
await genshin_reset.finish(Message(f'重载成功!\n{text}'))
|
||||
|
||||
|
||||
@pcr.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
msg = str(event.get_message()).strip()
|
||||
if msg.find('1井') != -1 or msg.find('一井') != -1:
|
||||
num = 300
|
||||
else:
|
||||
rmsg = re.search(r'.*?(pcr|公主连结)(.*)[抽|井|连]', msg)
|
||||
if rmsg:
|
||||
num, flag = check_num(rmsg.group(2), 300)
|
||||
if not flag:
|
||||
await pcr.finish(num, at_sender=True)
|
||||
else:
|
||||
return
|
||||
await pcr.send(await pcr_draw(int(num)), at_sender=True)
|
||||
|
||||
|
||||
@azur.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
msg = str(event.get_message()).strip()
|
||||
rmsg = re.search(r'.*?碧蓝航?线?(轻型|重型|特型)池?(.*)[抽|连]', msg)
|
||||
if rmsg:
|
||||
pool_name = rmsg.group(1)
|
||||
num, flag = check_num(rmsg.group(2), 300)
|
||||
if not flag:
|
||||
await azur.finish(num, at_sender=True)
|
||||
else:
|
||||
return
|
||||
await azur.send(await azur_draw(int(num), pool_name), at_sender=True)
|
||||
|
||||
|
||||
@fgo.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
msg = str(event.get_message()).strip()
|
||||
rmsg = re.search(r'.*?fgo(.*)[抽|连]', msg)
|
||||
if rmsg:
|
||||
num, flag = check_num(rmsg.group(1), 300)
|
||||
if not flag:
|
||||
await fgo.finish(num, at_sender=True)
|
||||
else:
|
||||
return
|
||||
await fgo.send(await fgo_draw(int(num)), at_sender=True)
|
||||
|
||||
|
||||
@onmyoji.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
msg = str(event.get_message()).strip()
|
||||
rmsg = re.search(r'.*?阴阳师(.*)[抽|连]', msg)
|
||||
if rmsg:
|
||||
num, flag = check_num(rmsg.group(1), 300)
|
||||
if not flag:
|
||||
await onmyoji.finish(num, at_sender=True)
|
||||
else:
|
||||
return
|
||||
await onmyoji.send(await onmyoji_draw(int(num)), at_sender=True)
|
||||
|
||||
|
||||
@prts_update.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
await update_prts_info()
|
||||
await prts_update.finish('更新完成!')
|
||||
|
||||
|
||||
@genshin_update.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
await update_genshin_info()
|
||||
await genshin_update.finish('更新完成!')
|
||||
|
||||
|
||||
@pretty_update.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
await update_pretty_info()
|
||||
await genshin_update.finish('更新完成!')
|
||||
|
||||
|
||||
@guardian_update.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
await update_guardian_info()
|
||||
await genshin_update.finish('更新完成!')
|
||||
|
||||
|
||||
@pcr_update.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
await update_pcr_info()
|
||||
await genshin_update.finish('更新完成!')
|
||||
|
||||
|
||||
@azur_update.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
await update_azur_info()
|
||||
await genshin_update.finish('更新完成!')
|
||||
|
||||
|
||||
@fgo_update.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
await update_fgo_info()
|
||||
await genshin_update.finish('更新完成!')
|
||||
|
||||
|
||||
@onmyoji_update.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
await update_onmyoji_info()
|
||||
await genshin_update.finish('更新完成!')
|
||||
create_matchers()
|
||||
|
||||
|
||||
# 更新资源
|
||||
@scheduler.scheduled_job(
|
||||
'cron',
|
||||
"cron",
|
||||
hour=4,
|
||||
minute=1,
|
||||
)
|
||||
async def _():
|
||||
tasks = []
|
||||
if draw_config.PRTS_FLAG:
|
||||
tasks.append(asyncio.ensure_future(update_prts_info()))
|
||||
if draw_config.GENSHIN_FLAG:
|
||||
tasks.append(asyncio.ensure_future(update_genshin_info()))
|
||||
if draw_config.PRETTY_FLAG:
|
||||
tasks.append(asyncio.ensure_future(update_pretty_info()))
|
||||
if draw_config.GUARDIAN_FLAG:
|
||||
tasks.append(asyncio.ensure_future(update_guardian_info()))
|
||||
if draw_config.PCR_FLAG:
|
||||
tasks.append(asyncio.ensure_future(update_pcr_info()))
|
||||
if draw_config.AZUR_FLAG:
|
||||
tasks.append(asyncio.ensure_future(update_azur_info()))
|
||||
if draw_config.FGO_FLAG:
|
||||
tasks.append(asyncio.ensure_future(update_fgo_info()))
|
||||
if draw_config.ONMYOJI_FLAG:
|
||||
tasks.append(asyncio.ensure_future(update_onmyoji_info()))
|
||||
for game in games:
|
||||
if game.flag:
|
||||
tasks.append(asyncio.ensure_future(game.handle.update_info()))
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
|
||||
# 每天四点重载方舟up卡池
|
||||
@scheduler.scheduled_job(
|
||||
'cron',
|
||||
hour=4,
|
||||
minute=1,
|
||||
)
|
||||
driver = nonebot.get_driver()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def _():
|
||||
if draw_config.PRTS_FLAG:
|
||||
await reload_prts_pool()
|
||||
|
||||
|
||||
# 每天四点重载赛马娘up卡池
|
||||
@scheduler.scheduled_job(
|
||||
'cron',
|
||||
hour=4,
|
||||
minute=1,
|
||||
)
|
||||
async def _():
|
||||
if draw_config.PRETTY_FLAG:
|
||||
await reload_pretty_pool()
|
||||
|
||||
|
||||
# 每天下午六点点重载原神up卡池
|
||||
@scheduler.scheduled_job(
|
||||
'cron',
|
||||
hour=18,
|
||||
minute=1,
|
||||
)
|
||||
async def _():
|
||||
if draw_config.PRTS_FLAG:
|
||||
await reload_genshin_pool()
|
||||
|
||||
|
||||
# 重载坎公骑冠剑卡池
|
||||
@scheduler.scheduled_job(
|
||||
'cron',
|
||||
hour=4,
|
||||
minute=1,
|
||||
)
|
||||
async def _():
|
||||
if draw_config.GUARDIAN_FLAG:
|
||||
await reload_guardian_pool()
|
||||
tasks = []
|
||||
for game in games:
|
||||
if game.flag:
|
||||
game.handle.init_data()
|
||||
if not game.handle.data_exists():
|
||||
tasks.append(asyncio.ensure_future(game.handle.update_info()))
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
@@ -1,395 +0,0 @@
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from .config import DRAW_DATA_PATH
|
||||
from asyncio.exceptions import TimeoutError
|
||||
from nonebot.log import logger
|
||||
from utils.http_utils import AsyncHttpx
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
headers = {'User-Agent': '"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)"'}
|
||||
|
||||
prts_up_char = DRAW_DATA_PATH / "draw_card_up" / "prts_up_char.json"
|
||||
genshin_up_char = DRAW_DATA_PATH / "draw_card_up" / "genshin_up_char.json"
|
||||
pretty_up_char = DRAW_DATA_PATH / "draw_card_up" / "pretty_up_char.json"
|
||||
guardian_up_char = DRAW_DATA_PATH / "draw_card_up" / "guardian_up_char.json"
|
||||
|
||||
prts_url = "https://ak.hypergryph.com/news.html"
|
||||
genshin_url = "https://wiki.biligame.com/ys/%E7%A5%88%E6%84%BF"
|
||||
pretty_url = "https://wiki.biligame.com/umamusume/%E5%85%AC%E5%91%8A"
|
||||
guardian_url = "https://wiki.biligame.com/gt/%E9%A6%96%E9%A1%B5"
|
||||
|
||||
|
||||
# 是否过时
|
||||
def is_expired(data: dict):
|
||||
times = data['time'].split('-')
|
||||
for i in range(len(times)):
|
||||
times[i] = str(datetime.now().year) + '-' + times[i].split('日')[0].strip().replace('月', '-')
|
||||
start_date = datetime.strptime(times[0], '%Y-%m-%d').date()
|
||||
end_date = datetime.strptime(times[1], '%Y-%m-%d').date()
|
||||
now = datetime.now().date()
|
||||
return not start_date <= now <= end_date
|
||||
|
||||
|
||||
# 检查写入
|
||||
def check_write(data: dict, up_char_file):
|
||||
try:
|
||||
if is_expired(data['char']):
|
||||
for x in list(data.keys()):
|
||||
data[x]['title'] = ''
|
||||
else:
|
||||
with open(up_char_file, 'w', encoding='utf8') as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
if not up_char_file.exists():
|
||||
with open(up_char_file, 'w', encoding='utf8') as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
else:
|
||||
with open(up_char_file, 'r', encoding='utf8') as f:
|
||||
old_data = json.load(f)
|
||||
if is_expired(old_data['char']):
|
||||
return old_data
|
||||
else:
|
||||
with open(up_char_file, 'w', encoding='utf8') as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
except ValueError:
|
||||
pass
|
||||
return data
|
||||
|
||||
|
||||
class PrtsAnnouncement:
|
||||
|
||||
def __init__(self):
|
||||
self.game_name = '明日方舟'
|
||||
|
||||
async def _get_announcement_text(self):
|
||||
res = await AsyncHttpx.get(prts_url, timeout=7)
|
||||
soup = BeautifulSoup(res.text, 'lxml')
|
||||
ol = soup.find('ol', {'class': 'articleList active', 'data-category-key': 'LATEST'})
|
||||
for li in ol:
|
||||
type_ = li.find('span', {'class': 'articleItemCate'}).text
|
||||
if type_ == '活动':
|
||||
a = li.find('a')['href']
|
||||
return (await AsyncHttpx.get(f'https://ak.hypergryph.com{a}', timeout=7)).text
|
||||
|
||||
async def update_up_char(self):
|
||||
prts_up_char.parent.mkdir(parents=True, exist_ok=True)
|
||||
if prts_up_char.exists():
|
||||
with open(prts_up_char, 'r', encoding='utf8') as f:
|
||||
data = json.load(f)
|
||||
if not data.get('char'):
|
||||
prts_up_char.unlink()
|
||||
try:
|
||||
data = {'char': {'up_char': {'6': {}, '5': {}, '4': {}}, 'title': '', 'time': '', 'pool_img': ''}}
|
||||
text = await self._get_announcement_text()
|
||||
soup = BeautifulSoup(text, 'lxml')
|
||||
content = soup.find('div', {'class': 'article-content'})
|
||||
contents = [x for x in content.contents if x.text or str(x).find('img') != -1]
|
||||
start_index = -1
|
||||
end_index = -1
|
||||
for i in range(len(contents)):
|
||||
if str(contents[i]).startswith('<p>'):
|
||||
r = re.search('(.*)(寻访|复刻).*?开启', contents[i].text)
|
||||
if r:
|
||||
if str(contents[i+3].text).find('★') != -1:
|
||||
img = contents[i-1].find('img')
|
||||
if img:
|
||||
data['char']['pool_img'] = img['src']
|
||||
start_index = i
|
||||
for j in range(i, len(contents)):
|
||||
if str(contents[j]).find('注意') != -1:
|
||||
end_index = j
|
||||
break
|
||||
break
|
||||
contents = contents[start_index: end_index]
|
||||
title = contents[0].text
|
||||
data['char']['title'] = title[title.find('【'): title.find('】') + 1]
|
||||
data['char']['time'] = str(contents[1].text).split(':', maxsplit=1)[1]
|
||||
for p in contents[2:]:
|
||||
p = str(p.text)
|
||||
r = None
|
||||
if p.find('★') != -1:
|
||||
if p.find('权值') == -1:
|
||||
r = re.search(r'.*?:(.*)(占(.*)★.*?的(.*)%)', p)
|
||||
else:
|
||||
r = re.search(r'.*?:(.*)(在(.*)★.*?以(.*)倍权值.*?)', p)
|
||||
star = r.group(2)
|
||||
if r:
|
||||
chars = r.group(1)
|
||||
if chars.find('/') != -1:
|
||||
chars = chars.strip().split('/')
|
||||
elif chars.find('\\') != -1:
|
||||
chars = chars.strip().split('\\')
|
||||
else:
|
||||
chars = chars.split('\n')
|
||||
chars = [x.replace('[限定]', '').strip() for x in chars]
|
||||
probability = r.group(3)
|
||||
probability = probability if int(probability) > 10 else f'权{probability}'
|
||||
for char in chars:
|
||||
if char.strip():
|
||||
data['char']['up_char'][star][char.strip()] = probability
|
||||
except TimeoutError:
|
||||
logger.warning(f'更新明日方舟UP池信息超时...')
|
||||
if prts_up_char.exists():
|
||||
with open(prts_up_char, 'r', encoding='utf8') as f:
|
||||
data = json.load(f)
|
||||
except Exception as e:
|
||||
logger.error(f'更新明日方舟未知错误 e:{e}')
|
||||
if prts_up_char.exists():
|
||||
with open(prts_up_char, 'r', encoding='utf8') as f:
|
||||
data = json.load(f)
|
||||
return check_write(data, prts_up_char)
|
||||
|
||||
|
||||
class GenshinAnnouncement:
|
||||
|
||||
def __init__(self):
|
||||
self.game_name = '原神'
|
||||
|
||||
async def _get_announcement_text(self):
|
||||
return (await AsyncHttpx.get(genshin_url, timeout=7)).text
|
||||
|
||||
async def update_up_char(self):
|
||||
genshin_up_char.parent.mkdir(exist_ok=True, parents=True)
|
||||
data = {
|
||||
'char': {'up_char': {'5': {}, '4': {}}, 'title': '', 'time': '', 'pool_img': ''},
|
||||
'arms': {'up_char': {'5': {}, '4': {}}, 'title': '', 'time': '', 'pool_img': ''}
|
||||
}
|
||||
text = await self._get_announcement_text()
|
||||
soup = BeautifulSoup(text, 'lxml')
|
||||
try:
|
||||
div = soup.find_all('div', {'class': 'row'})[1]
|
||||
tables = div.find_all('table', {'class': 'wikitable'})
|
||||
for table in tables:
|
||||
trs = table.find('tbody').find_all('tr')
|
||||
pool_img = trs[0].find('th').find('img')
|
||||
if pool_img['title'].find('角色活动') == -1:
|
||||
type_ = 'arms'
|
||||
else:
|
||||
type_ = 'char'
|
||||
try:
|
||||
data[type_]['pool_img'] = str(pool_img['srcset']).split(' ')[0]
|
||||
except KeyError:
|
||||
data[type_]['pool_img'] = pool_img['src']
|
||||
data[type_]['title'] = str(pool_img['title']).split(f'期{"角色" if type_ == "char" else "武器"}')[0][:-3]
|
||||
data[type_]['time'] = trs[1].find('td').text
|
||||
if data[type_]['time'][-1] == '\n':
|
||||
data[type_]['time'] = data[type_]['time'][:-1]
|
||||
if '版本更新后' in data[type_]['time']:
|
||||
sp = data[type_]['time'].split('~')
|
||||
end_time = datetime.strptime(sp[1].strip(), "%Y/%m/%d %H:%M")
|
||||
start_time = end_time - timedelta(days=20)
|
||||
data[type_]['time'] = start_time.strftime('%Y/%m/%d') + ' ~ ' + end_time.strftime('%Y/%m/%d')
|
||||
tmp = ''
|
||||
for tm in data[type_]['time'].split('~'):
|
||||
date_time_sp = tm.split('/')
|
||||
date_time_sp[2] = date_time_sp[2].strip().replace(' ', '日 ')
|
||||
tmp += date_time_sp[1] + '月' + date_time_sp[2] + ' - '
|
||||
data[type_]['time'] = tmp[:-2].strip()
|
||||
for a in trs[2].find('td').find_all('a'):
|
||||
char_name = a['title']
|
||||
data[type_]['up_char']['5'][char_name] = "50"
|
||||
for a in trs[3].find('td').find_all('a'):
|
||||
char_name = a['title']
|
||||
data[type_]['up_char']['4'][char_name] = "50"
|
||||
except TimeoutError:
|
||||
logger.warning(f'更新原神UP池信息超时...')
|
||||
if genshin_up_char.exists():
|
||||
with open(genshin_up_char, 'r', encoding='utf8') as f:
|
||||
data = json.load(f)
|
||||
except Exception as e:
|
||||
logger.error(f'更新原神UP失败,疑似UP池已结束, e:{e}')
|
||||
if genshin_up_char.exists():
|
||||
with open(genshin_up_char, 'r', encoding='utf8') as f:
|
||||
data = json.load(f)
|
||||
data['char']['title'] = ''
|
||||
data['arms']['title'] = ''
|
||||
with open(genshin_up_char, 'w', encoding='utf8') as wf:
|
||||
json.dump(data, wf, ensure_ascii=False, indent=4)
|
||||
return data
|
||||
return check_write(data, genshin_up_char)
|
||||
|
||||
|
||||
class PrettyAnnouncement:
|
||||
|
||||
def __init__(self):
|
||||
self.game_name = '赛马娘'
|
||||
|
||||
async def _get_announcement_text(self):
|
||||
res = await AsyncHttpx.get(pretty_url, timeout=7)
|
||||
soup = BeautifulSoup(res.text, 'lxml')
|
||||
divs = soup.find('div', {'id': 'mw-content-text'}).find('div').find_all('div')
|
||||
for div in divs:
|
||||
a = div.find('a')
|
||||
try:
|
||||
title = a['title']
|
||||
except (KeyError, TypeError):
|
||||
continue
|
||||
if title.find('新角色追加') != -1:
|
||||
url = a['href']
|
||||
break
|
||||
return (await AsyncHttpx.get(f'https://wiki.biligame.com/{url}', timeout=7)).text, title[:-2]
|
||||
|
||||
async def update_up_char(self):
|
||||
data = {
|
||||
'char': {'up_char': {'3': {}, '2': {}, '1': {}}, 'title': '', 'time': '', 'pool_img': ''},
|
||||
'card': {'up_char': {'3': {}, '2': {}, '1': {}}, 'title': '', 'time': '', 'pool_img': ''}
|
||||
}
|
||||
try:
|
||||
text, title = await self._get_announcement_text()
|
||||
soup = BeautifulSoup(text, 'lxml')
|
||||
context = soup.find('div', {'class': 'toc-sticky'})
|
||||
if not context:
|
||||
context = soup.find('div', {'class': 'mw-parser-output'})
|
||||
data['char']['title'] = title
|
||||
data['card']['title'] = title
|
||||
for big in context.find_all('big'):
|
||||
r = re.search(r'\d{1,2}/\d{1,2} \d{1,2}:\d{1,2}', str(big.text))
|
||||
if r:
|
||||
time = str(big.text)
|
||||
break
|
||||
else:
|
||||
logger.warning('赛马娘UP无法找到活动日期....取消更新UP池子...')
|
||||
return
|
||||
time = time.replace('~', '-').replace('/', '月').split(' ')
|
||||
time = time[0] + '日 ' + time[1] + ' - ' + time[3] + '日 ' + time[4]
|
||||
data['char']['time'] = time
|
||||
data['card']['time'] = time
|
||||
for p in context.find_all('p'):
|
||||
if str(p).find('当期UP赛马娘') != -1 and str(p).find('■') != -1:
|
||||
if not data['char']['pool_img']:
|
||||
try:
|
||||
data['char']['pool_img'] = p.find('img')['src']
|
||||
except TypeError:
|
||||
for center in context.find_all('center'):
|
||||
try:
|
||||
img = center.find('img')
|
||||
if img and str(img['alt']).find('新马娘') != -1 and str(img['alt']).find('总览') == 1:
|
||||
data['char']['pool_img'] = img['src']
|
||||
except (TypeError, KeyError):
|
||||
pass
|
||||
r = re.findall(r'.*?当期UP赛马娘([\s\S]*)<奖励内容>.*?', str(p))
|
||||
if r:
|
||||
for x in r:
|
||||
x = str(x).split('\n')
|
||||
for msg in x:
|
||||
if msg.find('★') != -1:
|
||||
msg = msg.replace('<br/>', '')
|
||||
char_name = msg[msg.find('['):].strip()
|
||||
if (star := len(msg[:msg.find('[')].strip())) == 3:
|
||||
data['char']['up_char']['3'][char_name] = '70'
|
||||
elif star == 2:
|
||||
data['char']['up_char']['2'][char_name] = '70'
|
||||
elif star == 1:
|
||||
data['char']['up_char']['1'][char_name] = '70'
|
||||
if str(p).find('(当期UP对象)') != -1 and str(p).find('赛马娘') == -1 and str(p).find('■') != -1:
|
||||
# data['card']['pool_img'] = p.find('img')['src']
|
||||
if not data['char']['pool_img']:
|
||||
try:
|
||||
data['char']['pool_img'] = p.find('img')['src']
|
||||
except TypeError:
|
||||
for center in context.find_all('center'):
|
||||
try:
|
||||
img = center.find('img')
|
||||
if img and str(img['alt']).find('新卡') != -1 and str(img['alt']).find('总览') == 1:
|
||||
data['card']['pool_img'] = img['src']
|
||||
except (TypeError, KeyError):
|
||||
pass
|
||||
r = re.search(r'■全?新?支援卡(当期UP对象)([\s\S]*)</p>', str(p))
|
||||
if r:
|
||||
rmsg = r.group(1).strip()
|
||||
rmsg = rmsg.split('<br/>')
|
||||
rmsg = [x for x in rmsg if x]
|
||||
for x in rmsg:
|
||||
x = x.replace('\n', '').replace('・', '')
|
||||
star = x[:x.find('[')].strip()
|
||||
char_name = x[x.find('['):].strip()
|
||||
if star == 'SSR':
|
||||
data['card']['up_char']['3'][char_name] = '70'
|
||||
if star == 'SR':
|
||||
data['card']['up_char']['2'][char_name] = '70'
|
||||
if star == 'R':
|
||||
data['card']['up_char']['1'][char_name] = '70'
|
||||
# 日文->中文
|
||||
with open(DRAW_DATA_PATH / 'pretty_card.json', 'r', encoding='utf8') as f:
|
||||
all_data = json.load(f)
|
||||
for star in data['card']['up_char'].keys():
|
||||
for name in list(data['card']['up_char'][star].keys()):
|
||||
char_name = name.split(']')[1].strip()
|
||||
tp_name = name[name.find('['): name.find(']') + 1].strip().replace('[', '【').replace(']', '】')
|
||||
for x in all_data.keys():
|
||||
if all_data[x]['名称'].find(tp_name) != -1 and all_data[x]['关联角色'] == char_name:
|
||||
data['card']['up_char'][star].pop(name)
|
||||
data['card']['up_char'][star][all_data[x]['中文名']] = '70'
|
||||
except TimeoutError:
|
||||
logger.warning(f'更新赛马娘UP池信息超时...')
|
||||
if pretty_up_char.exists():
|
||||
with open(pretty_up_char, 'r', encoding='utf8') as f:
|
||||
data = json.load(f)
|
||||
except Exception as e:
|
||||
logger.error(f'赛马娘up更新未知错误 {type(e)}:{e}')
|
||||
if pretty_up_char.exists():
|
||||
with open(pretty_up_char, 'r', encoding='utf8') as f:
|
||||
data = json.load(f)
|
||||
return check_write(data, pretty_up_char)
|
||||
|
||||
|
||||
class GuardianAnnouncement:
|
||||
|
||||
def __init__(self):
|
||||
self.game_name = '坎公骑冠剑'
|
||||
|
||||
async def _get_announcement_text(self):
|
||||
return (await AsyncHttpx.get(guardian_url, timeout=7)).text
|
||||
|
||||
async def update_up_char(self):
|
||||
data = {
|
||||
'char': {'up_char': {'3': {}}, 'title': '', 'time': '', 'pool_img': ''},
|
||||
'arms': {'up_char': {'5': {}}, 'title': '', 'time': '', 'pool_img': ''}
|
||||
}
|
||||
try:
|
||||
text = await self._get_announcement_text()
|
||||
soup = BeautifulSoup(text, 'lxml')
|
||||
context = soup.select('div.col-sm-3:nth-child(3) > div:nth-child(2) > div:nth-child(1) '
|
||||
'> div:nth-child(2) > div:nth-child(3) > font:nth-child(1)')[0]
|
||||
title = context.find('p').find('b').text
|
||||
tmp = title.split(',')
|
||||
time = ''
|
||||
for msg in tmp:
|
||||
r = re.search(r'[从|至](.*)(开始|结束)', msg)
|
||||
if r:
|
||||
time += r.group(1).strip() + ' - '
|
||||
time = time[:-3]
|
||||
title = time.split(' - ')[0] + 'UP卡池'
|
||||
data['char']['title'] = title
|
||||
data['arms']['title'] = title
|
||||
data['char']['time'] = time
|
||||
data['arms']['time'] = time
|
||||
start_idx = -1
|
||||
end_idx = -1
|
||||
index = 0
|
||||
divs = context.find_all('div')
|
||||
for x in divs:
|
||||
if x.text == '角色':
|
||||
start_idx = index
|
||||
if x.text == '武器':
|
||||
end_idx = index
|
||||
break
|
||||
index += 1
|
||||
for x in divs[start_idx + 1: end_idx]:
|
||||
name = x.find('p').find_all('a')[-1].text
|
||||
data['char']['up_char']['3'][name] = '0'
|
||||
for x in divs[end_idx + 1:]:
|
||||
name = x.find('p').find_all('a')[-1].text
|
||||
data['arms']['up_char']['5'][name] = '0'
|
||||
except TimeoutError:
|
||||
logger.warning(f'更新坎公骑冠剑UP池信息超时...')
|
||||
if guardian_up_char.exists():
|
||||
with open(guardian_up_char, 'r', encoding='utf8') as f:
|
||||
data = json.load(f)
|
||||
except Exception as e:
|
||||
logger.error(f'坎公骑冠剑up更新未知错误 {type(e)}:{e}')
|
||||
return check_write(data, guardian_up_char)
|
||||
@@ -1,63 +0,0 @@
|
||||
import asyncio
|
||||
import nonebot
|
||||
from nonebot.log import logger
|
||||
from .pcr_handle import update_pcr_info, init_pcr_data
|
||||
from .azur_handle import update_azur_info, init_azur_data
|
||||
from .prts_handle import update_prts_info, init_prts_data
|
||||
from .pretty_handle import update_pretty_info, init_pretty_data
|
||||
from .guardian_handle import update_guardian_info, init_guardian_data
|
||||
from .genshin_handle import update_genshin_info, init_genshin_data
|
||||
from .fgo_handle import update_fgo_info, init_fgo_data
|
||||
from .onmyoji_handle import update_onmyoji_info, init_onmyoji_data
|
||||
from .config import draw_config, DRAW_DATA_PATH
|
||||
|
||||
|
||||
driver = nonebot.get_driver()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def async_update_game():
|
||||
tasks = []
|
||||
init_lst = [init_pcr_data, init_pretty_data, init_azur_data, init_prts_data, init_genshin_data, init_guardian_data,
|
||||
init_fgo_data, init_onmyoji_data]
|
||||
if draw_config.PRTS_FLAG and not (DRAW_DATA_PATH / 'prts.json').exists():
|
||||
tasks.append(asyncio.ensure_future(update_prts_info()))
|
||||
init_lst.remove(init_prts_data)
|
||||
|
||||
if draw_config.PRETTY_FLAG and (not (DRAW_DATA_PATH / 'pretty.json').exists() or
|
||||
not (DRAW_DATA_PATH / 'pretty_card.json').exists()):
|
||||
tasks.append(asyncio.ensure_future(update_pretty_info()))
|
||||
init_lst.remove(init_pretty_data)
|
||||
|
||||
if draw_config.GUARDIAN_FLAG and not (DRAW_DATA_PATH / 'guardian.json').exists():
|
||||
tasks.append(asyncio.ensure_future(update_guardian_info()))
|
||||
init_lst.remove(init_guardian_data)
|
||||
|
||||
if draw_config.PCR_FLAG and not (DRAW_DATA_PATH / 'pcr.json').exists():
|
||||
tasks.append(asyncio.ensure_future(update_pcr_info()))
|
||||
init_lst.remove(init_pcr_data)
|
||||
|
||||
if draw_config.GENSHIN_FLAG and (not (DRAW_DATA_PATH / 'genshin.json').exists() or
|
||||
not (DRAW_DATA_PATH / 'genshin_arms.json').exists()):
|
||||
tasks.append(asyncio.ensure_future(update_genshin_info()))
|
||||
init_lst.remove(init_genshin_data)
|
||||
|
||||
if draw_config.AZUR_FLAG and not (DRAW_DATA_PATH / 'azur.json').exists():
|
||||
tasks.append(asyncio.ensure_future(update_azur_info()))
|
||||
init_lst.remove(init_azur_data)
|
||||
|
||||
if draw_config.FGO_FLAG and (not (DRAW_DATA_PATH / 'fgo.json').exists() or
|
||||
not (DRAW_DATA_PATH / 'fgo_card.json').exists()):
|
||||
tasks.append(asyncio.ensure_future(update_fgo_info()))
|
||||
init_lst.remove(init_fgo_data)
|
||||
|
||||
if draw_config.ONMYOJI_FLAG and not (DRAW_DATA_PATH / 'onmyoji.json').exists():
|
||||
tasks.append(asyncio.ensure_future(update_onmyoji_info()))
|
||||
init_lst.remove(init_onmyoji_data)
|
||||
try:
|
||||
await asyncio.gather(*tasks)
|
||||
for func in init_lst:
|
||||
await func()
|
||||
except asyncio.exceptions.CancelledError:
|
||||
logger.warning('更新异常:CancelledError,再次更新...')
|
||||
await async_update_game()
|
||||
@@ -1,62 +0,0 @@
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
import random
|
||||
from .update_game_simple_info import update_simple_info
|
||||
from .util import generate_img, init_star_rst, BaseData, set_list, get_star, max_card, format_card_information
|
||||
from .config import draw_config, DRAW_DATA_PATH
|
||||
from dataclasses import dataclass
|
||||
from .init_card_pool import init_game_pool
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
ALL_CHAR = []
|
||||
|
||||
|
||||
@dataclass
|
||||
class AzurChar(BaseData):
|
||||
type_: str # 舰娘类型
|
||||
|
||||
|
||||
async def azur_draw(count: int, pool_name: str):
|
||||
# 0 1 2
|
||||
cnlist = ['金', '紫', '蓝', '白']
|
||||
star_list = [0, 0, 0, 0]
|
||||
char_list, char_dict, max_star_list, star_list, max_star_index_list = \
|
||||
format_card_information(count, star_list, _get_azur_card, pool_name, guaranteed=False)
|
||||
rst = init_star_rst(star_list, cnlist, max_star_list, max_star_index_list)
|
||||
if count > 90:
|
||||
char_list = set_list(char_list)
|
||||
return MessageSegment.image("base64://" + await generate_img(char_list, 'azur', star_list)) \
|
||||
+ '\n' + rst[:-1] + '\n' + max_card(char_dict)
|
||||
|
||||
|
||||
async def update_azur_info():
|
||||
global ALL_CHAR
|
||||
url = 'https://wiki.biligame.com/blhx/舰娘图鉴'
|
||||
data, code = await update_simple_info(url, 'azur')
|
||||
if code == 200:
|
||||
ALL_CHAR = init_game_pool('azur', data, AzurChar)
|
||||
|
||||
|
||||
async def init_azur_data():
|
||||
global ALL_CHAR
|
||||
if draw_config.AZUR_FLAG:
|
||||
with (DRAW_DATA_PATH / 'azur.json').open('r', encoding='utf8') as f:
|
||||
azur_dict = json.load(f)
|
||||
ALL_CHAR = init_game_pool('azur', azur_dict, AzurChar)
|
||||
|
||||
|
||||
# 抽取卡池
|
||||
def _get_azur_card(pool_name: str, mode: int = 1):
|
||||
global ALL_CHAR
|
||||
azur_config = draw_config.azur
|
||||
if pool_name == '轻型':
|
||||
type_ = ['驱逐', '轻巡', '维修']
|
||||
elif pool_name == '重型':
|
||||
type_ = ['重巡', '战列', '战巡', '重炮']
|
||||
else:
|
||||
type_ = ['维修', '潜艇', '重巡', '轻航', '航母']
|
||||
star = get_star([4, 3, 2, 1], [azur_config.AZUR_FOUR_P, azur_config.AZUR_THREE_P, azur_config.AZUR_TWO_P, azur_config.AZUR_ONE_P])
|
||||
chars = [x for x in ALL_CHAR if x.star == star and x.type_ in type_ and not x.limited]
|
||||
return random.choice(chars), 4 - star
|
||||
Executable → Regular
+66
-53
@@ -1,8 +1,9 @@
|
||||
import nonebot
|
||||
from pathlib import Path
|
||||
from nonebot.log import logger
|
||||
from pydantic import BaseModel, Extra, ValidationError
|
||||
from configs.path_config import IMAGE_PATH, DATA_PATH
|
||||
from configs.config import Config
|
||||
from pydantic import BaseModel, Extra
|
||||
from configs.config import Config as AConfig
|
||||
from configs.path_config import DATA_PATH
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
@@ -15,8 +16,8 @@ class GenshinConfig(BaseModel, extra=Extra.ignore):
|
||||
GENSHIN_FIVE_P: float = 0.006
|
||||
GENSHIN_FOUR_P: float = 0.051
|
||||
GENSHIN_THREE_P: float = 0.43
|
||||
GENSHIN_G_FIVE_P: float = 0.13
|
||||
GENSHIN_G_FOUR_P: float = 0.016
|
||||
GENSHIN_G_FIVE_P: float = 0.016
|
||||
GENSHIN_G_FOUR_P: float = 0.13
|
||||
I72_ADD: float = 0.0585
|
||||
|
||||
|
||||
@@ -86,44 +87,28 @@ class OnmyojiConfig(BaseModel, extra=Extra.ignore):
|
||||
ONMYOJI_SR: float = 0.2
|
||||
ONMYOJI_R: float = 0.7875
|
||||
|
||||
#碧蓝档案
|
||||
class BaConfig(BaseModel, extra=Extra.ignore):
|
||||
BA_THREE_P: float = 0.025
|
||||
BA_TWO_P: float = 0.185
|
||||
BA_ONE_P: float = 0.79
|
||||
BA_G_TWO_P: float = 0.975
|
||||
|
||||
class PathDict(BaseModel, extra=Extra.ignore):
|
||||
genshin: str = "原神"
|
||||
prts: str = "明日方舟"
|
||||
pretty: str = "赛马娘"
|
||||
guardian: str = "坎公骑冠剑"
|
||||
pcr: str = "公主连结"
|
||||
azur: str = "碧蓝航线"
|
||||
fgo: str = "命运-冠位指定"
|
||||
onmyoji: str = "阴阳师"
|
||||
|
||||
|
||||
class DrawConfig(BaseModel, extra=Extra.ignore):
|
||||
class Config(BaseModel, extra=Extra.ignore):
|
||||
# 开关
|
||||
PRTS_FLAG: bool = Config.get_config("draw_card", "PRTS_FLAG")
|
||||
GENSHIN_FLAG: bool = Config.get_config("draw_card", "GENSHIN_FLAG")
|
||||
PRETTY_FLAG: bool = Config.get_config("draw_card", "PRETTY_FLAG")
|
||||
GUARDIAN_FLAG: bool = Config.get_config("draw_card", "GUARDIAN_FLAG")
|
||||
PCR_FLAG: bool = Config.get_config("draw_card", "PCR_FLAG")
|
||||
AZUR_FLAG: bool = Config.get_config("draw_card", "AZUR_FLAG")
|
||||
FGO_FLAG: bool = Config.get_config("draw_card", "FGO_FLAG")
|
||||
ONMYOJI_FLAG: bool = Config.get_config("draw_card", "ONMYOJI_FLAG")
|
||||
PRTS_FLAG: bool = AConfig.get_config("draw_card", "PRTS_FLAG")
|
||||
GENSHIN_FLAG: bool = AConfig.get_config("draw_card", "GENSHIN_FLAG")
|
||||
PRETTY_FLAG: bool = AConfig.get_config("draw_card", "PRETTY_FLAG")
|
||||
GUARDIAN_FLAG: bool = AConfig.get_config("draw_card", "GUARDIAN_FLAG")
|
||||
PCR_FLAG: bool = AConfig.get_config("draw_card", "PCR_FLAG")
|
||||
AZUR_FLAG: bool = AConfig.get_config("draw_card", "AZUR_FLAG")
|
||||
FGO_FLAG: bool = AConfig.get_config("draw_card", "FGO_FLAG")
|
||||
ONMYOJI_FLAG: bool = AConfig.get_config("draw_card", "ONMYOJI_FLAG")
|
||||
BA_FLAG: bool = AConfig.get_config("draw_card", "BA_FLAG")
|
||||
|
||||
# 其他配置
|
||||
PCR_TAI: bool = Config.get_config("draw_card", "PCR_TAI")
|
||||
SEMAPHORE: int = Config.get_config("draw_card", "SEMAPHORE")
|
||||
|
||||
# 路径
|
||||
path_dict: dict = {
|
||||
"genshin": "原神",
|
||||
"prts": "明日方舟",
|
||||
"pretty": "赛马娘",
|
||||
"guardian": "坎公骑冠剑",
|
||||
"pcr": "公主连结",
|
||||
"azur": "碧蓝航线",
|
||||
"fgo": "命运-冠位指定",
|
||||
"onmyoji": "阴阳师",
|
||||
}
|
||||
PCR_TAI: bool = AConfig.get_config("draw_card", "PCR_TAI")
|
||||
SEMAPHORE: int = AConfig.get_config("draw_card", "SEMAPHORE")
|
||||
|
||||
# 抽卡概率
|
||||
prts: PrtsConfig = PrtsConfig()
|
||||
@@ -134,30 +119,58 @@ class DrawConfig(BaseModel, extra=Extra.ignore):
|
||||
azur: AzurConfig = AzurConfig()
|
||||
fgo: FgoConfig = FgoConfig()
|
||||
onmyoji: OnmyojiConfig = OnmyojiConfig()
|
||||
ba: BaConfig = BaConfig()
|
||||
|
||||
|
||||
driver = nonebot.get_driver()
|
||||
global_config = driver.config
|
||||
DRAW_DATA_PATH = DATA_PATH / "draw_card"
|
||||
DRAW_IMAGE_PATH = IMAGE_PATH / "draw_card"
|
||||
# DRAW_PATH = Path(draw_path) if draw_path else Path("data/draw_card").absolute()
|
||||
config_path = DRAW_DATA_PATH / "draw_card_config" / "draw_card_config.json"
|
||||
|
||||
draw_config: Config = DrawConfig()
|
||||
DRAW_PATH = DATA_PATH / "draw_card"
|
||||
config_path = DRAW_PATH / "draw_card_config" / "draw_card_config.json"
|
||||
|
||||
draw_config: Config = Config()
|
||||
|
||||
|
||||
for game_flag, game_name in zip(
|
||||
[
|
||||
"PRTS_FLAG",
|
||||
"GENSHIN_FLAG",
|
||||
"PRETTY_FLAG",
|
||||
"GUARDIAN_FLAG",
|
||||
"PCR_FLAG",
|
||||
"AZUR_FLAG",
|
||||
"FGO_FLAG",
|
||||
"ONMYOJI_FLAG",
|
||||
"PCR_TAI",
|
||||
"BA_FLAG"
|
||||
],
|
||||
["明日方舟", "原神", "赛马娘", "坎公骑冠剑", "公主连结", "碧蓝航线", "命运-冠位指定(FGO)", "阴阳师", "pcr台服卡池","碧蓝档案"],
|
||||
):
|
||||
AConfig.add_plugin_config(
|
||||
"draw_card",
|
||||
game_flag,
|
||||
True,
|
||||
name="游戏抽卡",
|
||||
help_=f"{game_name} 抽卡开关",
|
||||
default_value=True,
|
||||
)
|
||||
AConfig.add_plugin_config(
|
||||
"draw_card", "SEMAPHORE", 5, help_=f"异步数据下载数量限制", default_value=5
|
||||
)
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
def check_config():
|
||||
global draw_config
|
||||
|
||||
draw_config = Config()
|
||||
if not config_path.exists():
|
||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
draw_config = DrawConfig()
|
||||
draw_config = Config()
|
||||
logger.warning("draw_card:配置文件不存在,已重新生成配置文件.....")
|
||||
|
||||
json.dump(
|
||||
draw_config.dict(),
|
||||
config_path.open("w", encoding="utf8"),
|
||||
indent=4,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
with config_path.open("w", encoding="utf8") as fp:
|
||||
json.dump(
|
||||
draw_config.dict(),
|
||||
fp,
|
||||
indent=4,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
from typing import Optional, Union
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
import time
|
||||
|
||||
|
||||
class BaseUserCount(BaseModel):
|
||||
count: int = 1 # 当前抽卡次数
|
||||
time_: int = time.time() # 抽卡时间,当超过一定时间时将重置抽卡次数
|
||||
timeout: int = 60 # 超时时间60秒
|
||||
|
||||
|
||||
class DrawCountManager:
|
||||
@@ -6,7 +14,9 @@ class DrawCountManager:
|
||||
抽卡统计保底
|
||||
"""
|
||||
|
||||
def __init__(self, game_draw_count_rule: tuple, star2name: tuple):
|
||||
def __init__(
|
||||
self, game_draw_count_rule: tuple, star2name: tuple, max_draw_count: int
|
||||
):
|
||||
"""
|
||||
初始化保底统计
|
||||
|
||||
@@ -14,26 +24,43 @@ class DrawCountManager:
|
||||
|
||||
抽卡保底需要的次数和返回的对应名称,例如星级等
|
||||
|
||||
:param game_draw_count_rule:抽卡规则
|
||||
:param star2name:星级对应的名称
|
||||
:param max_draw_count:最大累计抽卡次数,当下次单次抽卡超过该次数时将会清空数据
|
||||
|
||||
"""
|
||||
# 只有保底
|
||||
self._data = {}
|
||||
self._guarantee_tuple = game_draw_count_rule
|
||||
self._star2name = star2name
|
||||
self._max_draw_count = max_draw_count
|
||||
|
||||
def increase(self, key: int, value: int = 1):
|
||||
"""
|
||||
用户抽卡次数加1
|
||||
"""
|
||||
if self._data.get(key) is None:
|
||||
self._data[key] = {
|
||||
"count": value,
|
||||
}
|
||||
for x in range(len(self._guarantee_tuple)):
|
||||
self._data[key][f"count_{x}"] = 0
|
||||
self._data[key] = BaseUserCount()
|
||||
else:
|
||||
self._data[key][f"count"] += value
|
||||
if self._data[key][f"count"] > self._guarantee_tuple[-1]:
|
||||
self._data[key][f"count"] = self._data[key][f"count"] % self._guarantee_tuple[-1]
|
||||
self._data[key].count += 1
|
||||
|
||||
def get_max_guarantee(self):
|
||||
"""
|
||||
获取最大保底抽卡次数
|
||||
"""
|
||||
return self._guarantee_tuple[-1]
|
||||
|
||||
def get_user_count(self, key: int) -> int:
|
||||
"""
|
||||
获取当前抽卡次数
|
||||
"""
|
||||
return self._data[key].count
|
||||
|
||||
def update_time(self, key: int):
|
||||
"""
|
||||
更新抽卡时间
|
||||
"""
|
||||
self._data[key].time_ = time.time()
|
||||
|
||||
def reset(self, key: int):
|
||||
"""
|
||||
@@ -41,73 +68,102 @@ class DrawCountManager:
|
||||
"""
|
||||
del self._data[key]
|
||||
|
||||
def set_count(self, key: int, type_: int, count: int):
|
||||
if self._data.get(key):
|
||||
self._data[key][f"count_{type_}"] = count
|
||||
|
||||
def check(self, key: int, *args) -> Optional[Union[str, int]]:
|
||||
"""
|
||||
是否保底
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_user_count(self, key: int, type_: Optional[int] = None) -> int:
|
||||
"""
|
||||
获取用户当前抽卡次数
|
||||
"""
|
||||
if self._data.get(key):
|
||||
if type_ is None:
|
||||
return self._data[key]["count"]
|
||||
return self._data[key][f"count_{type_}"]
|
||||
return 0
|
||||
|
||||
def record_count(self, key: int, type_: int):
|
||||
"""
|
||||
抽出对应星级后记录当前次数
|
||||
"""
|
||||
if self._data.get(key):
|
||||
self._data[key][f"count_{type_}"] = self._data[key]["count"]
|
||||
|
||||
|
||||
class GenshinCountManager(DrawCountManager):
|
||||
class UserCount(BaseUserCount):
|
||||
five_index: int = 0 # 获取五星时的抽卡次数
|
||||
four_index: int = 0 # 获取四星时的抽卡次数
|
||||
is_up: bool = False # 下次五星是否必定为up
|
||||
|
||||
def increase(self, key: int, value: int = 1):
|
||||
"""
|
||||
用户抽卡次数加1
|
||||
"""
|
||||
if self._data.get(key) is None:
|
||||
self._data[key] = {
|
||||
"is_up": False,
|
||||
"count": value,
|
||||
}
|
||||
for x in range(len(self._guarantee_tuple)):
|
||||
self._data[key][f"count_{x}"] = 0
|
||||
self._data[key] = self.UserCount()
|
||||
else:
|
||||
self._data[key][f"count"] += value
|
||||
if self._data[key][f"count"] > self._guarantee_tuple[-1]:
|
||||
self._data[key][f"count"] = self._data[key][f"count"] % 180
|
||||
self._data[key].count += 1
|
||||
|
||||
def set_is_up(self, key: int, value: bool):
|
||||
if self._data.get(key):
|
||||
self._data[key]["is_up"] = value
|
||||
"""
|
||||
设置下次是否必定up
|
||||
"""
|
||||
self._data[key].is_up = value
|
||||
|
||||
def is_up(self, key: int) -> bool:
|
||||
if self._data.get(key):
|
||||
return self._data[key]["is_up"]
|
||||
return False
|
||||
"""
|
||||
判断该次保底是否必定为up
|
||||
"""
|
||||
return self._data[key].is_up
|
||||
|
||||
def check(self, key: int, *args) -> Optional[Union[str, int]]:
|
||||
def get_user_five_index(self, key: int) -> int:
|
||||
"""
|
||||
获取用户上次获取五星的次数
|
||||
"""
|
||||
return self._data[key].five_index
|
||||
|
||||
def get_user_four_index(self, key: int) -> int:
|
||||
"""
|
||||
获取用户上次获取四星的次数
|
||||
"""
|
||||
return self._data[key].four_index
|
||||
|
||||
def mark_five_index(self, key: int):
|
||||
"""
|
||||
标记用户该次次数为五星
|
||||
"""
|
||||
self._data[key].five_index = self._data[key].count
|
||||
|
||||
def mark_four_index(self, key: int):
|
||||
"""
|
||||
标记用户该次次数为四星
|
||||
"""
|
||||
self._data[key].four_index = self._data[key].count
|
||||
|
||||
def check_timeout(self, key: int):
|
||||
"""
|
||||
检查用户距离上次抽卡是否超时
|
||||
"""
|
||||
if key in self._data.keys() and self._is_timeout(key):
|
||||
del self._data[key]
|
||||
|
||||
def check_count(self, key: int, count: int):
|
||||
"""
|
||||
检查用户该次抽卡次数累计是否超过最大限制次数
|
||||
"""
|
||||
if (
|
||||
key in self._data.keys()
|
||||
and self._data[key].count + count > self._max_draw_count
|
||||
):
|
||||
del self._data[key]
|
||||
|
||||
def _is_timeout(self, key: int) -> bool:
|
||||
return time.time() - self._data[key].time_ > self._data[key].timeout
|
||||
|
||||
def get_user_guarantee_count(self, key: int) -> int:
|
||||
return (
|
||||
self.get_max_guarantee()
|
||||
- (
|
||||
(
|
||||
self._data[key].count % self.get_max_guarantee()
|
||||
if self._data[key].count > 0
|
||||
else 0
|
||||
)
|
||||
- self._data[key].five_index
|
||||
)
|
||||
) % self.get_max_guarantee() or self.get_max_guarantee()
|
||||
|
||||
def check(self, key: int) -> Optional[int]:
|
||||
"""
|
||||
是否保底
|
||||
"""
|
||||
# print(self._data)
|
||||
if self._data.get(key):
|
||||
for i in [1, 0]:
|
||||
count = self._data[key]["count"]
|
||||
if count - self._data[key][f"count_{i}"] == self._guarantee_tuple[i]:
|
||||
if i in [2, 1]:
|
||||
# print("clean four count")
|
||||
self._data[key][f"count_0"] = self._data[key]['count']
|
||||
self._data[key][f"count_{i}"] = self._data[key]['count']
|
||||
return self._star2name[i]
|
||||
user: GenshinCountManager.UserCount = self._data[key]
|
||||
if user.count - user.five_index == 90:
|
||||
user.five_index = 90
|
||||
return 5
|
||||
if user.count - user.four_index == 10:
|
||||
user.four_index = user.count
|
||||
return 4
|
||||
return None
|
||||
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
import random
|
||||
from .update_game_requests_info import update_requests_info
|
||||
from .util import generate_img, init_star_rst, BaseData, set_list, get_star, max_card
|
||||
from .config import DRAW_DATA_PATH, draw_config
|
||||
from dataclasses import dataclass
|
||||
from .init_card_pool import init_game_pool
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
ALL_CHAR = []
|
||||
ALL_CARD = []
|
||||
|
||||
|
||||
@dataclass
|
||||
class FgoChar(BaseData):
|
||||
pass
|
||||
|
||||
|
||||
async def fgo_draw(count: int):
|
||||
# 0 1 2
|
||||
cnlist = ['★★★★★', '★★★★', '★★★']
|
||||
obj_list, obj_dict, max_star_list, star_list, max_star_index_list = _format_card_information(count)
|
||||
rst = init_star_rst(star_list, cnlist, max_star_list, max_star_index_list)
|
||||
if count > 90:
|
||||
obj_list = set_list(obj_list)
|
||||
return MessageSegment.image("base64://" + await generate_img(obj_list, 'fgo', star_list)) \
|
||||
+ '\n' + rst[:-1] + '\n' + max_card(obj_dict)
|
||||
|
||||
|
||||
async def update_fgo_info():
|
||||
global ALL_CHAR, ALL_CARD
|
||||
data, code = await update_requests_info('fgo')
|
||||
if code == 200:
|
||||
ALL_CHAR = init_game_pool('fgo', data, FgoChar)
|
||||
data, code = await update_requests_info('fgo_card')
|
||||
if code == 200:
|
||||
ALL_CARD = init_game_pool('fgo_card', data, FgoChar)
|
||||
|
||||
|
||||
async def init_fgo_data():
|
||||
global ALL_CHAR, ALL_CARD
|
||||
if draw_config.FGO_FLAG:
|
||||
with (DRAW_DATA_PATH / 'fgo.json').open('r', encoding='utf8') as f:
|
||||
fgo_dict = json.load(f)
|
||||
ALL_CHAR = init_game_pool('fgo', fgo_dict, FgoChar)
|
||||
with (DRAW_DATA_PATH / 'fgo_card.json').open('r', encoding='utf8') as f:
|
||||
fgo_dict = json.load(f)
|
||||
ALL_CARD = init_game_pool('fgo', fgo_dict, FgoChar)
|
||||
|
||||
|
||||
# 抽取卡池
|
||||
def _get_fgo_card(mode: int = 1):
|
||||
global ALL_CHAR, ALL_CARD
|
||||
fgo_config = draw_config.fgo
|
||||
if mode == 1:
|
||||
star = get_star([8, 7, 6, 5, 4, 3], [fgo_config.FGO_SERVANT_FIVE_P, fgo_config.FGO_SERVANT_FOUR_P, fgo_config.FGO_SERVANT_THREE_P,
|
||||
fgo_config.FGO_CARD_FIVE_P, fgo_config.FGO_CARD_FOUR_P, fgo_config.FGO_CARD_THREE_P])
|
||||
elif mode == 2:
|
||||
star = get_star([5, 4], [fgo_config.FGO_CARD_FIVE_P, fgo_config.FGO_CARD_FOUR_P])
|
||||
else:
|
||||
star = get_star([8, 7, 6], [fgo_config.FGO_SERVANT_FIVE_P, fgo_config.FGO_SERVANT_FOUR_P, fgo_config.FGO_SERVANT_THREE_P])
|
||||
if star > 5:
|
||||
type_ = 'servant'
|
||||
star -= 3
|
||||
chars = [x for x in ALL_CHAR if x.star == star if not x.limited]
|
||||
else:
|
||||
type_ = 'card'
|
||||
chars = [x for x in ALL_CARD if x.star == star if not x.limited]
|
||||
return random.choice(chars), 5 - star, type_
|
||||
|
||||
|
||||
# 整理数据
|
||||
def _format_card_information(count: int):
|
||||
max_star_lst = [] # 获取的最高星级角色列表
|
||||
max_index_lst = [] # 获取最高星级角色的次数
|
||||
star_list = [0, 0, 0]
|
||||
obj_list = [] # 获取所有角色
|
||||
obj_dict = {} # 获取角色次数字典
|
||||
servant_count = 0 # 保底计算
|
||||
card_count = 0 # 保底计算
|
||||
for i in range(count):
|
||||
servant_count += 1
|
||||
card_count += 1
|
||||
# 四星卡片保底
|
||||
if card_count == 9:
|
||||
obj, code, type_ = _get_fgo_card(2)
|
||||
# 三星从者保底
|
||||
elif servant_count == 10:
|
||||
obj, code, type_ = _get_fgo_card(3)
|
||||
_count = 0
|
||||
# 普通抽
|
||||
else:
|
||||
obj, code, type_ = _get_fgo_card()
|
||||
star_list[code] += 1
|
||||
if type_ == 'card' and code < 2:
|
||||
card_count = 0
|
||||
if type_ == 'servant':
|
||||
servant_count = 0
|
||||
if code == 0:
|
||||
max_star_lst.append(obj.name)
|
||||
max_index_lst.append(i)
|
||||
try:
|
||||
obj_dict[obj.name] += 1
|
||||
except KeyError:
|
||||
obj_dict[obj.name] = 1
|
||||
obj_list.append(obj)
|
||||
return obj_list, obj_dict, max_star_lst, star_list, max_index_lst
|
||||
@@ -1,288 +0,0 @@
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment, Message
|
||||
import random
|
||||
from .update_game_info import update_info
|
||||
from .util import (
|
||||
generate_img,
|
||||
init_rst,
|
||||
BaseData,
|
||||
set_list,
|
||||
get_star,
|
||||
init_up_char,
|
||||
)
|
||||
from .config import DRAW_DATA_PATH, draw_config
|
||||
from .count_manager import GenshinCountManager
|
||||
from dataclasses import dataclass
|
||||
from .init_card_pool import init_game_pool
|
||||
from .announcement import GenshinAnnouncement
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
|
||||
announcement = GenshinAnnouncement()
|
||||
|
||||
draw_count_manager = GenshinCountManager((10, 90), ("4", "5"))
|
||||
|
||||
# genshin_five = {}
|
||||
# genshin_count = {}
|
||||
# genshin_pl_count = {}
|
||||
|
||||
ALL_CHAR = []
|
||||
ALL_ARMS = []
|
||||
|
||||
UP_CHAR = []
|
||||
UP_ARMS = []
|
||||
|
||||
|
||||
_CURRENT_CHAR_POOL_TITLE = ""
|
||||
_CURRENT_ARMS_POOL_TITLE = ""
|
||||
POOL_IMG = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class GenshinChar(BaseData):
|
||||
pass
|
||||
|
||||
|
||||
async def genshin_draw(user_id: int, count: int, pool_name: str):
|
||||
# 0 1 2
|
||||
star = ["★★★★★", "★★★★", "★★★"]
|
||||
(
|
||||
char_list,
|
||||
five_dict,
|
||||
star_num_list,
|
||||
) = _format_card_information(count, user_id, pool_name)
|
||||
title = ""
|
||||
up_type = []
|
||||
up_list = []
|
||||
if pool_name == "char" and _CURRENT_CHAR_POOL_TITLE:
|
||||
up_type = UP_CHAR
|
||||
title = _CURRENT_CHAR_POOL_TITLE
|
||||
elif pool_name == "arms" and _CURRENT_ARMS_POOL_TITLE:
|
||||
up_type = UP_ARMS
|
||||
title = _CURRENT_ARMS_POOL_TITLE
|
||||
tmp = ""
|
||||
if up_type:
|
||||
for x in up_type:
|
||||
for operator in x.operators:
|
||||
up_list.append(operator)
|
||||
if x.star == 5:
|
||||
tmp += f'五星UP:{" ".join(x.operators)} \n'
|
||||
elif x.star == 4:
|
||||
tmp += f'四星UP:{" ".join(x.operators)}'
|
||||
rst = init_rst(five_dict, star_num_list, star, up_list)
|
||||
pool_info = f"当前up池:{title}\n{tmp}" if title else ""
|
||||
if count > 90:
|
||||
char_list = set_list(char_list)
|
||||
return (
|
||||
pool_info
|
||||
+ "\n"
|
||||
+ MessageSegment.image(
|
||||
"base64://" + await generate_img(char_list, "genshin", star_num_list)
|
||||
)
|
||||
+ rst
|
||||
+ f'\n距离保底发还剩 {draw_count_manager.get_user_count(user_id, 1) % 90} 抽'
|
||||
+ "\n【五星:0.6%,四星:5.1%\n第72抽开始五星概率每抽加0.585%】"
|
||||
)
|
||||
|
||||
|
||||
async def update_genshin_info():
|
||||
global ALL_CHAR, ALL_ARMS
|
||||
url = "https://wiki.biligame.com/ys/角色筛选"
|
||||
data, code = await update_info(url, "genshin")
|
||||
if code == 200:
|
||||
ALL_CHAR = init_game_pool("genshin", data, GenshinChar)
|
||||
url = "https://wiki.biligame.com/ys/武器图鉴"
|
||||
data, code = await update_info(
|
||||
url,
|
||||
"genshin_arms",
|
||||
)
|
||||
if code == 200:
|
||||
ALL_ARMS = init_game_pool("genshin_arms", data, GenshinChar)
|
||||
await _genshin_init_up_char()
|
||||
|
||||
|
||||
async def init_genshin_data():
|
||||
global ALL_CHAR, ALL_ARMS
|
||||
if draw_config.GENSHIN_FLAG:
|
||||
if not (DRAW_DATA_PATH / "genshin.json").exists() or not (DRAW_DATA_PATH / "genshin_arms.json").exists():
|
||||
await update_genshin_info()
|
||||
else:
|
||||
with (DRAW_DATA_PATH / "genshin.json").open("r", encoding="utf8") as f:
|
||||
genshin_dict = json.load(f)
|
||||
with (DRAW_DATA_PATH / "genshin_arms.json").open("r", encoding="utf8") as f:
|
||||
genshin_ARMS_dict = json.load(f)
|
||||
ALL_CHAR = init_game_pool("genshin", genshin_dict, GenshinChar)
|
||||
ALL_ARMS = init_game_pool("genshin_arms", genshin_ARMS_dict, GenshinChar)
|
||||
await _genshin_init_up_char()
|
||||
|
||||
|
||||
# 抽取卡池
|
||||
def _get_genshin_card(mode: int = 1, pool_name: str = "", add: float = 0.0, is_up: bool = False):
|
||||
"""
|
||||
mode 1:普通抽 2:四星保底 3:五星保底
|
||||
"""
|
||||
global ALL_ARMS, ALL_CHAR, UP_ARMS, UP_CHAR, _CURRENT_ARMS_POOL_TITLE, _CURRENT_CHAR_POOL_TITLE
|
||||
genshin_config = draw_config.genshin
|
||||
if mode == 1:
|
||||
star = get_star(
|
||||
[5, 4, 3], [genshin_config.GENSHIN_FIVE_P + add, genshin_config.GENSHIN_FOUR_P, genshin_config.GENSHIN_THREE_P]
|
||||
)
|
||||
elif mode == 2:
|
||||
star = get_star([5, 4], [genshin_config.GENSHIN_G_FIVE_P + add, genshin_config.GENSHIN_G_FOUR_P])
|
||||
else:
|
||||
star = 5
|
||||
if pool_name == "char":
|
||||
data_lst = UP_CHAR
|
||||
flag = _CURRENT_CHAR_POOL_TITLE
|
||||
type_all_lst = ALL_CHAR + [
|
||||
x for x in ALL_ARMS if x.star == star and x.star < 5
|
||||
]
|
||||
elif pool_name == "arms":
|
||||
data_lst = UP_ARMS
|
||||
flag = _CURRENT_ARMS_POOL_TITLE
|
||||
type_all_lst = ALL_ARMS + [
|
||||
x for x in ALL_CHAR if x.star == star and x.star < 5
|
||||
]
|
||||
else:
|
||||
data_lst = ""
|
||||
flag = ""
|
||||
type_all_lst = ""
|
||||
all_lst = ALL_ARMS + ALL_CHAR
|
||||
# 是否UP
|
||||
try:
|
||||
if flag and star > 3:
|
||||
# 获取up角色列表
|
||||
up_char_lst = [x.operators for x in data_lst if x.star == star][0]
|
||||
# print(up_char_lst)
|
||||
# 成功获取up角色
|
||||
if random.random() < 0.5 or is_up:
|
||||
up_char_name = random.choice(up_char_lst)
|
||||
acquire_char = [x for x in all_lst if x.name == up_char_name][0]
|
||||
else:
|
||||
# 无up
|
||||
all_char_lst = [
|
||||
x
|
||||
for x in type_all_lst
|
||||
if x.star == star and x.name not in up_char_lst and not x.limited
|
||||
]
|
||||
acquire_char = random.choice(all_char_lst)
|
||||
else:
|
||||
chars = [x for x in all_lst if x.star == star and not x.limited]
|
||||
acquire_char = random.choice(chars)
|
||||
except IndexError:
|
||||
chars = [x for x in all_lst if x.star == star and not x.limited]
|
||||
acquire_char = random.choice(chars)
|
||||
return acquire_char, 5 - star
|
||||
|
||||
|
||||
def _format_card_information(_count: int, user_id, pool_name):
|
||||
char_list = [] # 获取角色列表
|
||||
star_num_list = [0, 0, 0] # 各个星级数量
|
||||
five_dict = {} # 五星数量
|
||||
add = 0.0
|
||||
pool = UP_CHAR if pool_name == 'char' else UP_ARMS
|
||||
for _ in range(_count):
|
||||
draw_count_manager.increase(user_id)
|
||||
star = draw_count_manager.check(user_id)
|
||||
if (draw_count_manager.get_user_count(user_id) - draw_count_manager.get_user_count(user_id, 1)) % 90 >= 72:
|
||||
add += draw_config.genshin.I72_ADD
|
||||
if star:
|
||||
star = int(star)
|
||||
if star == 4:
|
||||
char, code = _get_genshin_card(2, pool_name, add=add)
|
||||
draw_count_manager.record_count(user_id, 0)
|
||||
else:
|
||||
char, code = _get_genshin_card(3, pool_name, add, draw_count_manager.is_up(user_id))
|
||||
else:
|
||||
char, code = _get_genshin_card(1, pool_name, add, draw_count_manager.is_up(user_id))
|
||||
if code == 0:
|
||||
add = 0
|
||||
if not five_dict.get(char.name):
|
||||
five_dict[char.name] = [draw_count_manager.get_user_count(user_id)]
|
||||
else:
|
||||
five_dict[char.name].append(draw_count_manager.get_user_count(user_id) % 90)
|
||||
draw_count_manager.set_count(user_id, 2, draw_count_manager.get_user_count(user_id, 1))
|
||||
draw_count_manager.record_count(user_id, 0)
|
||||
draw_count_manager.record_count(user_id, 1)
|
||||
if char.name not in [x.operators for x in pool if x.star == 5][0]:
|
||||
draw_count_manager.set_is_up(user_id, True)
|
||||
else:
|
||||
draw_count_manager.set_is_up(user_id, False)
|
||||
star_num_list[code] += 1
|
||||
char_list.append(char)
|
||||
return char_list, five_dict, star_num_list
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# if genshin_count.get(user_id) and _count <= 90:
|
||||
# f_count = genshin_count[user_id]
|
||||
# else:
|
||||
# f_count = 0
|
||||
# if genshin_pl_count.get(user_id) and _count <= 90:
|
||||
# count = genshin_pl_count[user_id]
|
||||
# else:
|
||||
# count = 0
|
||||
# for i in range(_count):
|
||||
# count += 1
|
||||
# f_count += 1
|
||||
# # 十连保底
|
||||
# if count == 10 and f_count != 90:
|
||||
# if f_count >= 72:
|
||||
# add += I72_ADD
|
||||
# char, code = _get_genshin_card(2, pool_name, add=add)
|
||||
# count = 0
|
||||
# # 大保底
|
||||
# elif f_count == 90:
|
||||
# char, code = _get_genshin_card(3, pool_name)
|
||||
# else:
|
||||
# if f_count >= 72:
|
||||
# add += I72_ADD
|
||||
# char, code = _get_genshin_card(pool_name=pool_name, add=add)
|
||||
# if code == 1:
|
||||
# count = 0
|
||||
# star_list[code] += 1
|
||||
# if code == 0:
|
||||
# if _count <= 90:
|
||||
# genshin_five[user_id] = f_count
|
||||
# add = 0.0
|
||||
# f_count = 0
|
||||
# five_list.append(char.name)
|
||||
# five_index_list.append(i)
|
||||
# try:
|
||||
# five_dict[char.name] += 1
|
||||
# except KeyError:
|
||||
# five_dict[char.name] = 1
|
||||
# char_list.append(char)
|
||||
# if _count <= 90:
|
||||
# genshin_count[user_id] = f_count
|
||||
# genshin_pl_count[user_id] = count
|
||||
# return char_list, five_list, five_index_list, five_dict, star_list
|
||||
|
||||
|
||||
def reset_count(user_id: int):
|
||||
draw_count_manager.reset(user_id)
|
||||
|
||||
|
||||
# 获取up和概率
|
||||
async def _genshin_init_up_char():
|
||||
global _CURRENT_CHAR_POOL_TITLE, _CURRENT_ARMS_POOL_TITLE, UP_CHAR, UP_ARMS, POOL_IMG
|
||||
(
|
||||
_CURRENT_CHAR_POOL_TITLE,
|
||||
_CURRENT_ARMS_POOL_TITLE,
|
||||
POOL_IMG,
|
||||
UP_CHAR,
|
||||
UP_ARMS,
|
||||
) = await init_up_char(announcement)
|
||||
|
||||
|
||||
async def reload_genshin_pool():
|
||||
await _genshin_init_up_char()
|
||||
return Message(
|
||||
f"当前UP池子:{_CURRENT_CHAR_POOL_TITLE} & {_CURRENT_ARMS_POOL_TITLE} {POOL_IMG}"
|
||||
)
|
||||
@@ -1,159 +0,0 @@
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment, Message
|
||||
from .update_game_info import update_info
|
||||
from .util import init_star_rst, generate_img, max_card, BaseData,\
|
||||
set_list, get_star, format_card_information, init_up_char
|
||||
import random
|
||||
from .config import DRAW_DATA_PATH, draw_config
|
||||
from dataclasses import dataclass
|
||||
from .init_card_pool import init_game_pool
|
||||
from .announcement import GuardianAnnouncement
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
|
||||
announcement = GuardianAnnouncement()
|
||||
|
||||
ALL_CHAR = []
|
||||
ALL_ARMS = []
|
||||
|
||||
_CURRENT_CHAR_POOL_TITLE = ''
|
||||
_CURRENT_ARMS_POOL_TITLE = ''
|
||||
UP_CHAR = []
|
||||
UP_ARMS = []
|
||||
POOL_IMG = ''
|
||||
|
||||
|
||||
@dataclass
|
||||
class GuardianChar(BaseData):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class GuardianArms(BaseData):
|
||||
pass
|
||||
|
||||
|
||||
async def guardian_draw(count: int, pool_name):
|
||||
if pool_name == 'arms':
|
||||
cnlist = ['★★★★★', '★★★★', '★★★', '★★']
|
||||
star_list = [0, 0, 0, 0]
|
||||
else:
|
||||
cnlist = ['★★★', '★★', '★']
|
||||
star_list = [0, 0, 0]
|
||||
title = ''
|
||||
up_type = []
|
||||
up_list = []
|
||||
if pool_name == 'char' and _CURRENT_CHAR_POOL_TITLE:
|
||||
up_type = UP_CHAR
|
||||
title = _CURRENT_CHAR_POOL_TITLE
|
||||
elif pool_name == 'arms' and _CURRENT_ARMS_POOL_TITLE:
|
||||
up_type = UP_ARMS
|
||||
title = _CURRENT_ARMS_POOL_TITLE
|
||||
tmp = ''
|
||||
if up_type:
|
||||
for x in up_type:
|
||||
for operator in x.operators:
|
||||
up_list.append(operator)
|
||||
if pool_name == 'char':
|
||||
if x.star == 3:
|
||||
tmp += f'三星UP:{" ".join(x.operators)} \n'
|
||||
else:
|
||||
if x.star == 5:
|
||||
tmp += f'五星UP:{" ".join(x.operators)}'
|
||||
obj_list, obj_dict, max_list, star_list, max_index_list = format_card_information(count, star_list,
|
||||
_get_guardian_card, pool_name)
|
||||
rst = init_star_rst(star_list, cnlist, max_list, max_index_list, up_list)
|
||||
pool_info = f'当前up池:{title}\n{tmp}' if title else ''
|
||||
if count > 90:
|
||||
obj_list = set_list(obj_list)
|
||||
return pool_info + '\n' + MessageSegment.image(
|
||||
"base64://" + await generate_img(obj_list, 'guardian', star_list)) \
|
||||
+ '\n' + rst[:-1] + '\n' + max_card(obj_dict)
|
||||
|
||||
|
||||
async def update_guardian_info():
|
||||
global ALL_CHAR, ALL_ARMS
|
||||
url = 'https://wiki.biligame.com/gt/英雄筛选表'
|
||||
data, code = await update_info(url, 'guardian')
|
||||
if code == 200:
|
||||
ALL_CHAR = init_game_pool('guardian', data, GuardianChar)
|
||||
url = 'https://wiki.biligame.com/gt/武器'
|
||||
tmp, code_1 = await update_info(url, 'guardian_arms')
|
||||
url = 'https://wiki.biligame.com/gt/盾牌'
|
||||
data, code_2 = await update_info(url, 'guardian_arms')
|
||||
if code_1 == 200 and code_2 == 200:
|
||||
data.update(tmp)
|
||||
ALL_ARMS = init_game_pool('guardian_arms', data, GuardianArms)
|
||||
await _guardian_init_up_char()
|
||||
|
||||
|
||||
async def init_guardian_data():
|
||||
global ALL_CHAR, ALL_ARMS
|
||||
if draw_config.GUARDIAN_FLAG:
|
||||
if not (DRAW_DATA_PATH / 'guardian.json').exists() or not (DRAW_DATA_PATH / 'guardian_arms.json').exists():
|
||||
await update_guardian_info()
|
||||
else:
|
||||
with (DRAW_DATA_PATH / 'guardian.json').open('r', encoding='utf8') as f:
|
||||
guardian_char_dict = json.load(f)
|
||||
with (DRAW_DATA_PATH / 'guardian_arms.json').open('r', encoding='utf8') as f:
|
||||
guardian_arms_dict = json.load(f)
|
||||
ALL_CHAR = init_game_pool('guardian', guardian_char_dict, GuardianChar)
|
||||
ALL_ARMS = init_game_pool('guardian_arms', guardian_arms_dict, GuardianArms)
|
||||
await _guardian_init_up_char()
|
||||
|
||||
|
||||
# 抽取卡池
|
||||
def _get_guardian_card(pool_name: str = '', mode: int = 1):
|
||||
global ALL_ARMS, ALL_CHAR, UP_ARMS, UP_CHAR, _CURRENT_ARMS_POOL_TITLE, _CURRENT_CHAR_POOL_TITLE
|
||||
guardian_config = draw_config.guardian
|
||||
if pool_name == 'char':
|
||||
if mode == 1:
|
||||
star = get_star([3, 2, 1], [guardian_config.GUARDIAN_THREE_CHAR_P, guardian_config.GUARDIAN_TWO_CHAR_P, guardian_config.GUARDIAN_ONE_CHAR_P])
|
||||
else:
|
||||
star = get_star([3, 2], [guardian_config.GUARDIAN_THREE_CHAR_P, guardian_config.GUARDIAN_TWO_CHAR_P])
|
||||
up_lst = UP_CHAR
|
||||
flag = _CURRENT_CHAR_POOL_TITLE
|
||||
_max_star = 3
|
||||
all_data = ALL_CHAR
|
||||
else:
|
||||
if mode == 1:
|
||||
star = get_star([5, 4, 3, 2], [guardian_config.GUARDIAN_FIVE_ARMS_P, guardian_config.GUARDIAN_FOUR_ARMS_P,
|
||||
guardian_config.GUARDIAN_THREE_ARMS_P, guardian_config.GUARDIAN_TWO_ARMS_P])
|
||||
else:
|
||||
star = get_star([5, 4], [guardian_config.GUARDIAN_FIVE_ARMS_P, guardian_config.GUARDIAN_FOUR_ARMS_P])
|
||||
up_lst = UP_ARMS
|
||||
flag = _CURRENT_ARMS_POOL_TITLE
|
||||
_max_star = 5
|
||||
all_data = ALL_ARMS
|
||||
# 是否UP
|
||||
if flag and star == _max_star and pool_name:
|
||||
# 获取up角色列表
|
||||
up_char_lst = [x.operators for x in up_lst if x.star == star][0]
|
||||
# 成功获取up角色
|
||||
if random.random() < 0.5:
|
||||
up_char_name = random.choice(up_char_lst)
|
||||
acquire_char = [x for x in all_data if x.name == up_char_name][0]
|
||||
else:
|
||||
# 无up
|
||||
all_char_lst = [x for x in all_data if x.star == star and x.name not in up_char_lst and not x.limited]
|
||||
acquire_char = random.choice(all_char_lst)
|
||||
else:
|
||||
chars = [x for x in all_data if x.star == star and not x.limited]
|
||||
acquire_char = random.choice(chars)
|
||||
return acquire_char, _max_star - star
|
||||
|
||||
|
||||
# 获取up和概率
|
||||
async def _guardian_init_up_char():
|
||||
global _CURRENT_CHAR_POOL_TITLE, _CURRENT_ARMS_POOL_TITLE, UP_CHAR, UP_ARMS, POOL_IMG
|
||||
_CURRENT_CHAR_POOL_TITLE, _CURRENT_ARMS_POOL_TITLE, POOL_IMG, UP_CHAR, UP_ARMS = await init_up_char(announcement)
|
||||
|
||||
|
||||
async def reload_guardian_pool():
|
||||
await _guardian_init_up_char()
|
||||
return Message(f'当前UP池子:{_CURRENT_CHAR_POOL_TITLE} & {_CURRENT_ARMS_POOL_TITLE}')
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
import random
|
||||
import dateparser
|
||||
from lxml import etree
|
||||
from typing import List, Optional
|
||||
from urllib.parse import unquote
|
||||
from pydantic import ValidationError
|
||||
from nonebot.log import logger
|
||||
from nonebot.adapters.onebot.v11 import Message
|
||||
from utils.message_builder import image
|
||||
|
||||
from .base_handle import BaseHandle, BaseData, UpEvent as _UpEvent, UpChar as _UpChar
|
||||
from ..config import draw_config
|
||||
from ..util import remove_prohibited_str, cn2py
|
||||
from utils.image_utils import BuildImage
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
|
||||
class AzurChar(BaseData):
|
||||
type_: str # 舰娘类型
|
||||
|
||||
@property
|
||||
def star_str(self) -> str:
|
||||
return ["白", "蓝", "紫", "金"][self.star - 1]
|
||||
|
||||
|
||||
class UpChar(_UpChar):
|
||||
type_: str # 舰娘类型
|
||||
|
||||
|
||||
class UpEvent(_UpEvent):
|
||||
up_char: List[UpChar] # up对象
|
||||
|
||||
|
||||
class AzurHandle(BaseHandle[AzurChar]):
|
||||
def __init__(self):
|
||||
super().__init__("azur", "碧蓝航线")
|
||||
self.max_star = 4
|
||||
self.config = draw_config.azur
|
||||
self.ALL_CHAR: List[AzurChar] = []
|
||||
self.UP_EVENT: Optional[UpEvent] = None
|
||||
|
||||
def get_card(self, pool_name: str, **kwargs) -> AzurChar:
|
||||
if pool_name == "轻型":
|
||||
type_ = ["驱逐", "轻巡", "维修"]
|
||||
elif pool_name == "重型":
|
||||
type_ = ["重巡", "战列", "战巡", "重炮"]
|
||||
else:
|
||||
type_ = ["维修", "潜艇", "重巡", "轻航", "航母"]
|
||||
up_pool_flag = pool_name == "活动"
|
||||
# Up
|
||||
up_ship = [x for x in self.UP_EVENT.up_char if x.zoom > 0]
|
||||
# print(up_ship)
|
||||
acquire_char = None
|
||||
if up_ship and up_pool_flag:
|
||||
up_zoom = [(0, up_ship[0].zoom / 100)]
|
||||
# 初始化概率
|
||||
cur_ = up_ship[0].zoom / 100
|
||||
for i in range(len(up_ship)):
|
||||
try:
|
||||
up_zoom.append((cur_, cur_ + up_ship[i+1].zoom / 100))
|
||||
cur_ += up_ship[i+1].zoom / 100
|
||||
except IndexError:
|
||||
pass
|
||||
rand = random.random()
|
||||
# 抽取up
|
||||
for i, zoom in enumerate(up_zoom):
|
||||
if zoom[0] <= rand <= zoom[1]:
|
||||
try:
|
||||
acquire_char = [x for x in self.ALL_CHAR if x.name == up_ship[i].name][0]
|
||||
except IndexError:
|
||||
pass
|
||||
# 没有up或者未抽取到up
|
||||
if not acquire_char:
|
||||
star = self.get_star(
|
||||
[4, 3, 2, 1],
|
||||
[
|
||||
self.config.AZUR_FOUR_P,
|
||||
self.config.AZUR_THREE_P,
|
||||
self.config.AZUR_TWO_P,
|
||||
self.config.AZUR_ONE_P,
|
||||
],
|
||||
)
|
||||
acquire_char = random.choice([
|
||||
x
|
||||
for x in self.ALL_CHAR
|
||||
if x.star == star and x.type_ in type_ and not x.limited
|
||||
])
|
||||
return acquire_char
|
||||
|
||||
# async def draw(self, count: int, **kwargs) -> Message:
|
||||
# return await asyncio.get_event_loop().run_in_executor(None, self._draw, count)
|
||||
|
||||
async def draw(self, count: int, **kwargs) -> Message:
|
||||
index2card = self.get_cards(count, **kwargs)
|
||||
cards = [card[0] for card in index2card]
|
||||
up_list = [x.name for x in self.UP_EVENT.up_char] if self.UP_EVENT.up_char else []
|
||||
result = self.format_result(index2card, **{**kwargs, "up_list": up_list})
|
||||
return image(b64=self.generate_img(cards).pic2bs4()) + result
|
||||
|
||||
def generate_card_img(self, card: AzurChar) -> BuildImage:
|
||||
sep_w = 5
|
||||
sep_t = 5
|
||||
sep_b = 20
|
||||
w = 100
|
||||
h = 100
|
||||
bg = BuildImage(w + sep_w * 2, h + sep_t + sep_b, font="msyh.ttf")
|
||||
frame_path = self.img_path / f"{card.star}_star.png"
|
||||
frame = BuildImage(w, h, background=frame_path)
|
||||
img_path = self.img_path / f"{cn2py(card.name)}.png"
|
||||
img = BuildImage(w, h, background=img_path)
|
||||
# 加圆角
|
||||
img.circle_corner(6)
|
||||
bg.paste(img, (sep_w, sep_t), alpha=True)
|
||||
bg.paste(frame, (sep_w, sep_t), alpha=True)
|
||||
bg.circle_corner(6)
|
||||
# 加名字
|
||||
text = card.name[:6] + "..." if len(card.name) > 7 else card.name
|
||||
text_w, text_h = bg.getsize(text)
|
||||
bg.text(
|
||||
(sep_w + (w - text_w) / 2, h + sep_t + (sep_b - text_h) / 2),
|
||||
text,
|
||||
fill=["#808080", "#3b8bff", "#8000ff", "#c90", "#ee494c"][card.star - 1],
|
||||
)
|
||||
return bg
|
||||
|
||||
def _init_data(self):
|
||||
self.ALL_CHAR = [
|
||||
AzurChar(
|
||||
name=value["名称"],
|
||||
star=int(value["星级"]),
|
||||
limited="可以建造" not in value["获取途径"],
|
||||
type_=value["类型"],
|
||||
)
|
||||
for value in self.load_data().values()
|
||||
]
|
||||
self.load_up_char()
|
||||
|
||||
def load_up_char(self):
|
||||
try:
|
||||
data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json")
|
||||
self.UP_EVENT = UpEvent.parse_obj(data.get("char", {}))
|
||||
except ValidationError:
|
||||
logger.warning(f"{self.game_name}_up_char 解析出错")
|
||||
|
||||
def dump_up_char(self):
|
||||
if self.UP_EVENT:
|
||||
data = {"char": json.loads(self.UP_EVENT.json())}
|
||||
self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json")
|
||||
|
||||
async def _update_info(self):
|
||||
info = {}
|
||||
# 更新图鉴
|
||||
url = "https://wiki.biligame.com/blhx/舰娘图鉴"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} 出错")
|
||||
return
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
contents = dom.xpath(
|
||||
"//div[@class='resp-tabs-container']/div[@class='resp-tab-content']"
|
||||
)
|
||||
for index, content in enumerate(contents):
|
||||
char_list = content.xpath("./table/tbody/tr[2]/td/div/div/div/div")
|
||||
for char in char_list:
|
||||
try:
|
||||
name = char.xpath("./a/@title")[0]
|
||||
frame = char.xpath("./div/a/img/@alt")[0]
|
||||
avatar = char.xpath("./a/img/@srcset")[0]
|
||||
except IndexError:
|
||||
continue
|
||||
member_dict = {
|
||||
"名称": remove_prohibited_str(name),
|
||||
"头像": unquote(str(avatar).split(" ")[-2]),
|
||||
"星级": self.parse_star(frame),
|
||||
"类型": self.parse_type(index),
|
||||
}
|
||||
info[member_dict["名称"]] = member_dict
|
||||
# 更新额外信息
|
||||
for key in info.keys():
|
||||
url = f"https://wiki.biligame.com/blhx/{key}"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
info[key]["获取途径"] = []
|
||||
logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}")
|
||||
continue
|
||||
try:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
time = dom.xpath(
|
||||
"//table[@class='wikitable sv-general']/tbody[1]/tr[4]/td[2]//text()"
|
||||
)[0]
|
||||
sources = []
|
||||
if "无法建造" in time:
|
||||
sources.append("无法建造")
|
||||
elif "活动已关闭" in time:
|
||||
sources.append("活动限定")
|
||||
else:
|
||||
sources.append("可以建造")
|
||||
info[key]["获取途径"] = sources
|
||||
except IndexError:
|
||||
info[key]["获取途径"] = []
|
||||
logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}")
|
||||
self.dump_data(info)
|
||||
logger.info(f"{self.game_name_cn} 更新成功")
|
||||
# 下载头像
|
||||
for value in info.values():
|
||||
await self.download_img(value["头像"], value["名称"])
|
||||
# 下载头像框
|
||||
idx = 1
|
||||
BLHX_URL = "https://patchwiki.biligame.com/images/blhx"
|
||||
for url in [
|
||||
"/1/15/pxho13xsnkyb546tftvh49etzdh74cf.png",
|
||||
"/a/a9/k8t7nx6c8pan5vyr8z21txp45jxeo66.png",
|
||||
"/a/a5/5whkzvt200zwhhx0h0iz9qo1kldnidj.png",
|
||||
"/a/a2/ptog1j220x5q02hytpwc8al7f229qk9.png",
|
||||
"/6/6d/qqv5oy3xs40d3055cco6bsm0j4k4gzk.png",
|
||||
]:
|
||||
await self.download_img(BLHX_URL + url, f"{idx}_star")
|
||||
idx += 1
|
||||
await self.update_up_char()
|
||||
|
||||
@staticmethod
|
||||
def parse_star(star: str) -> int:
|
||||
if star in ["舰娘头像外框普通.png", "舰娘头像外框白色.png"]:
|
||||
return 1
|
||||
elif star in ["舰娘头像外框稀有.png", "舰娘头像外框蓝色.png"]:
|
||||
return 2
|
||||
elif star in ["舰娘头像外框精锐.png", "舰娘头像外框紫色.png"]:
|
||||
return 3
|
||||
elif star in ["舰娘头像外框超稀有.png", "舰娘头像外框金色.png"]:
|
||||
return 4
|
||||
elif star in ["舰娘头像外框海上传奇.png", "舰娘头像外框彩色.png"]:
|
||||
return 5
|
||||
elif star in [
|
||||
"舰娘头像外框最高方案.png",
|
||||
"舰娘头像外框决战方案.png",
|
||||
"舰娘头像外框超稀有META.png",
|
||||
"舰娘头像外框精锐META.png",
|
||||
]:
|
||||
return 6
|
||||
else:
|
||||
return 6
|
||||
|
||||
@staticmethod
|
||||
def parse_type(index: int) -> str:
|
||||
azur_types = [
|
||||
"驱逐",
|
||||
"轻巡",
|
||||
"重巡",
|
||||
"超巡",
|
||||
"战巡",
|
||||
"战列",
|
||||
"航母",
|
||||
"航站",
|
||||
"轻航",
|
||||
"重炮",
|
||||
"维修",
|
||||
"潜艇",
|
||||
"运输",
|
||||
]
|
||||
try:
|
||||
return azur_types[index]
|
||||
except IndexError:
|
||||
return azur_types[0]
|
||||
|
||||
async def update_up_char(self):
|
||||
url = "https://wiki.biligame.com/blhx/游戏活动表"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"{self.game_name_cn}获取活动表出错")
|
||||
return
|
||||
try:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
dd = dom.xpath("//div[@class='timeline2']/dl/dd/a")[0]
|
||||
url = "https://wiki.biligame.com" + dd.xpath("./@href")[0]
|
||||
title = dd.xpath("string(.)")
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"{self.game_name_cn}获取活动页面出错")
|
||||
return
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
timer = dom.xpath("//span[@class='eventTimer']")[0]
|
||||
start_time = dateparser.parse(timer.xpath("./@data-start")[0])
|
||||
end_time = dateparser.parse(timer.xpath("./@data-end")[0])
|
||||
ships = dom.xpath("//table[@class='shipinfo']")
|
||||
up_chars = []
|
||||
for ship in ships:
|
||||
name = ship.xpath("./tbody/tr/td[2]/p/a/@title")[0]
|
||||
type_ = ship.xpath("./tbody/tr/td[2]/p/small/text()")[0] # 舰船类型
|
||||
try:
|
||||
p = float(str(ship.xpath(".//sup/text()")[0]).strip("%"))
|
||||
except (IndexError, ValueError):
|
||||
p = 0
|
||||
star = self.parse_star(
|
||||
ship.xpath("./tbody/tr/td[1]/div/div/div/a/img/@alt")[0]
|
||||
)
|
||||
up_chars.append(UpChar(name=name, star=star, limited=False, zoom=p, type_=type_))
|
||||
self.UP_EVENT = UpEvent(
|
||||
title=title,
|
||||
pool_img="",
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
up_char=up_chars,
|
||||
)
|
||||
self.dump_up_char()
|
||||
except Exception as e:
|
||||
logger.warning(f"{self.game_name_cn}UP更新出错 {type(e)}:{e}")
|
||||
|
||||
async def _reload_pool(self) -> Optional[Message]:
|
||||
await self.update_up_char()
|
||||
self.load_up_char()
|
||||
if self.UP_EVENT:
|
||||
return Message(f"重载成功!\n当前活动:{self.UP_EVENT.title}")
|
||||
@@ -0,0 +1,150 @@
|
||||
import random
|
||||
from lxml import etree
|
||||
from typing import List, Tuple
|
||||
from PIL import ImageDraw
|
||||
from urllib.parse import unquote
|
||||
from nonebot.log import logger
|
||||
|
||||
from .base_handle import BaseHandle, BaseData
|
||||
from ..config import draw_config
|
||||
from ..util import remove_prohibited_str, cn2py, load_font
|
||||
from utils.image_utils import BuildImage
|
||||
|
||||
|
||||
class BaChar(BaseData):
|
||||
pass
|
||||
|
||||
|
||||
class BaHandle(BaseHandle[BaChar]):
|
||||
def __init__(self):
|
||||
super().__init__("ba", "碧蓝档案")
|
||||
self.max_star = 3
|
||||
self.config = draw_config.ba
|
||||
self.ALL_CHAR: List[BaChar] = []
|
||||
|
||||
def get_card(self, mode: int = 1) -> BaChar:
|
||||
if mode == 2:
|
||||
star = self.get_star(
|
||||
[3, 2], [self.config.BA_THREE_P, self.config.BA_G_TWO_P]
|
||||
)
|
||||
else:
|
||||
star = self.get_star(
|
||||
[3, 2, 1],
|
||||
[self.config.BA_THREE_P, self.config.BA_TWO_P, self.config.BA_ONE_P],
|
||||
)
|
||||
chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited]
|
||||
return random.choice(chars)
|
||||
|
||||
def get_cards(self, count: int, **kwargs) -> List[Tuple[BaChar, int]]:
|
||||
card_list = []
|
||||
card_count = 0 # 保底计算
|
||||
for i in range(count):
|
||||
card_count += 1
|
||||
# 十连保底
|
||||
if card_count == 10:
|
||||
card = self.get_card(2)
|
||||
card_count = 0
|
||||
else:
|
||||
card = self.get_card(1)
|
||||
if card.star > self.max_star - 2:
|
||||
card_count = 0
|
||||
card_list.append((card, i + 1))
|
||||
return card_list
|
||||
|
||||
def generate_card_img(self, card: BaChar) -> BuildImage:
|
||||
sep_w = 5
|
||||
sep_h = 5
|
||||
star_h = 15
|
||||
img_w = 90
|
||||
img_h = 100
|
||||
font_h = 20
|
||||
bar_h = 20
|
||||
bar_w = 90
|
||||
bg = BuildImage(img_w + sep_w * 2, img_h + font_h + sep_h * 2, color="#EFF2F5")
|
||||
img_path = str(self.img_path / f"{cn2py(card.name)}.png")
|
||||
img = BuildImage(img_w, img_h, background=img_path)
|
||||
bar = BuildImage(bar_w, bar_h, color="#6495ED")
|
||||
bg.paste(img, (sep_w, sep_h), alpha=True)
|
||||
bg.paste(bar, (sep_w, img_h - bar_h + sep_h), alpha=True)
|
||||
if (card.star == 1):
|
||||
star_path = str(self.img_path / "star-1.png")
|
||||
star_w = 15
|
||||
elif (card.star == 2):
|
||||
star_path = str(self.img_path / "star-2.png")
|
||||
star_w = 30
|
||||
else:
|
||||
star_path = str(self.img_path / "star-3.png")
|
||||
star_w = 45
|
||||
star = BuildImage(star_w, star_h, background=star_path)
|
||||
bg.paste(star, (img_w // 2 - 15 * (card.star - 1) // 2, img_h - star_h), alpha=True)
|
||||
text = card.name[:5] + "..." if len(card.name) > 6 else card.name
|
||||
font = load_font(fontsize=14)
|
||||
text_w, text_h = font.getsize(text)
|
||||
draw = ImageDraw.Draw(bg.markImg)
|
||||
draw.text(
|
||||
(sep_w + (img_w - text_w) / 2, sep_h + img_h + (font_h - text_h) / 2),
|
||||
text,
|
||||
font=font,
|
||||
fill="gray",
|
||||
)
|
||||
return bg
|
||||
|
||||
def _init_data(self):
|
||||
self.ALL_CHAR = [
|
||||
BaChar(
|
||||
name=value["名称"],
|
||||
star=int(value["星级"]),
|
||||
limited=True if "(" in key else False,
|
||||
)
|
||||
for key, value in self.load_data().items()
|
||||
]
|
||||
|
||||
def title2star(self, title: int):
|
||||
if title == 'Star-3.png':
|
||||
return 3
|
||||
elif title == 'Star-2.png':
|
||||
return 2
|
||||
else:
|
||||
return 1
|
||||
|
||||
async def _update_info(self):
|
||||
info = {}
|
||||
url = "https://wiki.biligame.com/bluearchive/学生筛选"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} 出错")
|
||||
return
|
||||
else:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
char_list = dom.xpath("//div[@class='filters']/table[2]/tbody/tr")
|
||||
for char in char_list:
|
||||
try:
|
||||
name = char.xpath("./td[2]/a/div/text()")[0]
|
||||
avatar = char.xpath("./td[1]/div/div/a/img/@data-src")[0]
|
||||
star_pic = char.xpath("./td[4]/img/@alt")[0]
|
||||
except IndexError:
|
||||
continue
|
||||
member_dict = {
|
||||
"头像": unquote(str(avatar)),
|
||||
"名称": remove_prohibited_str(name),
|
||||
"星级": self.title2star(star_pic),
|
||||
}
|
||||
info[member_dict["名称"]] = member_dict
|
||||
self.dump_data(info)
|
||||
logger.info(f"{self.game_name_cn} 更新成功")
|
||||
# 下载头像
|
||||
for value in info.values():
|
||||
await self.download_img(value["头像"], value["名称"])
|
||||
# 下载星星
|
||||
await self.download_img(
|
||||
"https://patchwiki.biligame.com/images/bluearchive/thumb/e/e0/82nj2x9sxko473g7782r14fztd4zyky.png/15px-Star-1.png",
|
||||
"star-1",
|
||||
)
|
||||
await self.download_img(
|
||||
"https://patchwiki.biligame.com/images/bluearchive/thumb/0/0b/msaff2g0zk6nlyl1rrn7n1ri4yobcqc.png/30px-Star-2.png",
|
||||
"star-2",
|
||||
)
|
||||
await self.download_img(
|
||||
"https://patchwiki.biligame.com/images/bluearchive/thumb/8/8a/577yv79x1rwxk8efdccpblo0lozl158.png/46px-Star-3.png",
|
||||
"star-3"
|
||||
)
|
||||
@@ -0,0 +1,298 @@
|
||||
import math
|
||||
import random
|
||||
import aiohttp
|
||||
import asyncio
|
||||
import aiofiles
|
||||
from PIL import Image
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, Extra
|
||||
from asyncio.exceptions import TimeoutError
|
||||
from typing import Dict, List, Optional, TypeVar, Generic, Tuple
|
||||
from nonebot.adapters.onebot.v11 import Message
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from utils.message_builder import image
|
||||
from nonebot.log import logger
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
from utils.image_utils import BuildImage
|
||||
from ..config import DRAW_PATH, draw_config
|
||||
from ..util import cn2py, circled_number
|
||||
|
||||
|
||||
class BaseData(BaseModel, extra=Extra.ignore):
|
||||
name: str # 名字
|
||||
star: int # 星级
|
||||
limited: bool # 限定
|
||||
|
||||
def __eq__(self, other: "BaseData"):
|
||||
return self.name == other.name
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
|
||||
@property
|
||||
def star_str(self) -> str:
|
||||
return "".join(["★" for _ in range(self.star)])
|
||||
|
||||
|
||||
class UpChar(BaseData):
|
||||
zoom: float # up提升倍率
|
||||
|
||||
|
||||
class UpEvent(BaseModel):
|
||||
title: str # up池标题
|
||||
pool_img: str # up池封面
|
||||
start_time: Optional[datetime] # 开始时间
|
||||
end_time: Optional[datetime] # 结束时间
|
||||
up_char: List[UpChar] # up对象
|
||||
|
||||
|
||||
TC = TypeVar("TC", bound="BaseData")
|
||||
|
||||
|
||||
class BaseHandle(Generic[TC]):
|
||||
def __init__(self, game_name: str, game_name_cn: str, game_card_color: str = "#ffffff"):
|
||||
self.game_name = game_name
|
||||
self.game_name_cn = game_name_cn
|
||||
self.max_star = 1 # 最大星级
|
||||
self.data_path = DRAW_PATH
|
||||
self.img_path = IMAGE_PATH / f"draw_card/{self.game_name}"
|
||||
self.up_path = DRAW_PATH / "draw_card_up"
|
||||
self.img_path.mkdir(parents=True, exist_ok=True)
|
||||
self.up_path.mkdir(parents=True, exist_ok=True)
|
||||
self.data_files: List[str] = [f"{self.game_name}.json"]
|
||||
self.game_card_color: str = game_card_color
|
||||
|
||||
async def draw(self, count: int, **kwargs) -> Message:
|
||||
return await asyncio.get_event_loop().run_in_executor(None, self._draw, count)
|
||||
|
||||
def _draw(self, count: int, **kwargs) -> Message:
|
||||
index2card = self.get_cards(count, **kwargs)
|
||||
cards = [card[0] for card in index2card]
|
||||
result = self.format_result(index2card)
|
||||
return image(b64=self.generate_img(cards).pic2bs4()) + result
|
||||
|
||||
# 抽取卡池
|
||||
def get_card(self, **kwargs) -> TC:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_cards(self, count: int, **kwargs) -> List[Tuple[TC, int]]:
|
||||
return [(self.get_card(**kwargs), i) for i in range(count)]
|
||||
|
||||
# 获取星级
|
||||
@staticmethod
|
||||
def get_star(star_list: List[int], probability_list: List[float]) -> int:
|
||||
return random.choices(star_list, weights=probability_list, k=1)[0]
|
||||
|
||||
def format_result(self, index2card: List[Tuple[TC, int]], **kwargs) -> str:
|
||||
card_list = [card[0] for card in index2card]
|
||||
results = [
|
||||
self.format_star_result(card_list, **kwargs),
|
||||
self.format_max_star(index2card, **kwargs),
|
||||
self.format_max_card(card_list, **kwargs),
|
||||
]
|
||||
results = [rst for rst in results if rst]
|
||||
return "\n".join(results)
|
||||
|
||||
def format_star_result(self, card_list: List[TC], **kwargs) -> str:
|
||||
star_dict: Dict[str, int] = {} # 记录星级及其次数
|
||||
|
||||
card_list_sorted = sorted(card_list, key=lambda c: c.star, reverse=True)
|
||||
for card in card_list_sorted:
|
||||
try:
|
||||
star_dict[card.star_str] += 1
|
||||
except KeyError:
|
||||
star_dict[card.star_str] = 1
|
||||
|
||||
rst = ""
|
||||
for star_str, count in star_dict.items():
|
||||
rst += f"[{star_str}×{count}] "
|
||||
return rst.strip()
|
||||
|
||||
def format_max_star(
|
||||
self, card_list: List[Tuple[TC, int]], up_list: List[str] = [], **kwargs
|
||||
) -> str:
|
||||
up_list = up_list or kwargs.get("up_list", [])
|
||||
rst = ""
|
||||
for card, index in card_list:
|
||||
if card.star == self.max_star:
|
||||
if card.name in up_list:
|
||||
rst += f"第 {index} 抽获取UP {card.name}\n"
|
||||
else:
|
||||
rst += f"第 {index} 抽获取 {card.name}\n"
|
||||
return rst.strip()
|
||||
|
||||
def format_max_card(self, card_list: List[TC], **kwargs) -> str:
|
||||
card_dict: Dict[TC, int] = {} # 记录卡牌抽取次数
|
||||
|
||||
for card in card_list:
|
||||
try:
|
||||
card_dict[card] += 1
|
||||
except KeyError:
|
||||
card_dict[card] = 1
|
||||
|
||||
max_count = max(card_dict.values())
|
||||
max_card = list(card_dict.keys())[list(card_dict.values()).index(max_count)]
|
||||
if max_count <= 1:
|
||||
return ""
|
||||
return f"抽取到最多的是{max_card.name},共抽取了{max_count}次"
|
||||
|
||||
def generate_img(
|
||||
self,
|
||||
cards: List[TC],
|
||||
num_per_line: int = 5,
|
||||
max_per_line: Tuple[int, int] = (40, 10),
|
||||
) -> BuildImage:
|
||||
"""
|
||||
生成统计图片
|
||||
:param cards: 卡牌列表
|
||||
:param num_per_line: 单行角色显示数量
|
||||
:param max_per_line: 当card_list超过一定数值时,更改单行数量
|
||||
"""
|
||||
if len(cards) > max_per_line[0]:
|
||||
num_per_line = max_per_line[1]
|
||||
if len(cards) > 90:
|
||||
card_dict: Dict[TC, int] = {} # 记录卡牌抽取次数
|
||||
for card in cards:
|
||||
try:
|
||||
card_dict[card] += 1
|
||||
except KeyError:
|
||||
card_dict[card] = 1
|
||||
card_list = list(card_dict)
|
||||
num_list = list(card_dict.values())
|
||||
else:
|
||||
card_list = cards
|
||||
num_list = [1] * len(cards)
|
||||
|
||||
card_imgs: List[BuildImage] = []
|
||||
for card, num in zip(card_list, num_list):
|
||||
card_img = self.generate_card_img(card)
|
||||
|
||||
# 数量 > 1 时加数字上标
|
||||
if num > 1:
|
||||
label = circled_number(num)
|
||||
label_w = int(min(card_img.w, card_img.h) / 7)
|
||||
label = label.resize(
|
||||
(
|
||||
int(label_w * label.width / label.height),
|
||||
label_w,
|
||||
),
|
||||
Image.ANTIALIAS,
|
||||
)
|
||||
card_img.paste(label, alpha=True)
|
||||
|
||||
card_imgs.append(card_img)
|
||||
|
||||
img_w = card_imgs[0].w
|
||||
img_h = card_imgs[0].h
|
||||
if len(card_imgs) < num_per_line:
|
||||
w = img_w * len(card_imgs)
|
||||
else:
|
||||
w = img_w * num_per_line
|
||||
h = img_h * math.ceil(len(card_imgs) / num_per_line)
|
||||
img = BuildImage(w, h, img_w, img_h, color=self.game_card_color)
|
||||
for card_img in card_imgs:
|
||||
img.paste(card_img)
|
||||
return img
|
||||
|
||||
def generate_card_img(self, card: TC) -> BuildImage:
|
||||
img = str(self.img_path / f"{cn2py(card.name)}.png")
|
||||
return BuildImage(100, 100, background=img)
|
||||
|
||||
def load_data(self, filename: str = "") -> dict:
|
||||
if not filename:
|
||||
filename = f"{self.game_name}.json"
|
||||
filepath = self.data_path / filename
|
||||
if not filepath.exists():
|
||||
return {}
|
||||
with filepath.open("r", encoding="utf8") as f:
|
||||
return json.load(f)
|
||||
|
||||
def dump_data(self, data: dict, filename: str = ""):
|
||||
if not filename:
|
||||
filename = f"{self.game_name}.json"
|
||||
filepath = self.data_path / filename
|
||||
with filepath.open("w", encoding="utf8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||
|
||||
def data_exists(self) -> bool:
|
||||
for file in self.data_files:
|
||||
if not (self.data_path / file).exists():
|
||||
return False
|
||||
return True
|
||||
|
||||
def _init_data(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def init_data(self):
|
||||
try:
|
||||
self._init_data()
|
||||
except Exception as e:
|
||||
logger.warning(f"{self.game_name_cn} 导入角色数据错误:{type(e)}:{e}")
|
||||
|
||||
async def _update_info(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def client(self) -> aiohttp.ClientSession:
|
||||
headers = {
|
||||
"User-Agent": '"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)"'
|
||||
}
|
||||
return aiohttp.ClientSession(headers=headers)
|
||||
|
||||
async def update_info(self):
|
||||
try:
|
||||
async with asyncio.Semaphore(draw_config.SEMAPHORE):
|
||||
async with self.client() as session:
|
||||
self.session = session
|
||||
await self._update_info()
|
||||
except Exception as e:
|
||||
logger.warning(f"{self.game_name_cn} 更新数据错误:{type(e)}:{e}")
|
||||
self.init_data()
|
||||
|
||||
async def get_url(self, url: str) -> str:
|
||||
result = ""
|
||||
retry = 5
|
||||
for i in range(retry):
|
||||
try:
|
||||
async with self.session.get(url, timeout=10) as response:
|
||||
result = await response.text()
|
||||
break
|
||||
except TimeoutError:
|
||||
logger.warning(f"访问 {url} 超时, 重试 {i + 1}/{retry}")
|
||||
await asyncio.sleep(1)
|
||||
return result
|
||||
|
||||
async def download_img(self, url: str, name: str) -> bool:
|
||||
img_path = self.img_path / f"{cn2py(name)}.png"
|
||||
if img_path.exists():
|
||||
return True
|
||||
try:
|
||||
async with self.session.get(url, timeout=10) as response:
|
||||
async with aiofiles.open(str(img_path), "wb") as f:
|
||||
await f.write(await response.read())
|
||||
return True
|
||||
except TimeoutError:
|
||||
logger.warning(f"下载 {self.game_name_cn} 图片超时,名称:{name},url:{url}")
|
||||
return False
|
||||
except:
|
||||
logger.warning(f"下载 {self.game_name_cn} 链接错误,名称:{name},url:{url}")
|
||||
return False
|
||||
|
||||
async def _reload_pool(self) -> Optional[Message]:
|
||||
return None
|
||||
|
||||
async def reload_pool(self) -> Optional[Message]:
|
||||
try:
|
||||
async with self.client() as session:
|
||||
self.session = session
|
||||
return await self._reload_pool()
|
||||
except Exception as e:
|
||||
logger.warning(f"{self.game_name_cn} 重载UP池错误:{type(e)}:{e}")
|
||||
|
||||
def reset_count(self, user_id: int) -> bool:
|
||||
return False
|
||||
@@ -0,0 +1,221 @@
|
||||
import random
|
||||
from lxml import etree
|
||||
from typing import List, Tuple
|
||||
from PIL import ImageDraw
|
||||
from nonebot.log import logger
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
from .base_handle import BaseHandle, BaseData
|
||||
from ..config import draw_config
|
||||
from ..util import remove_prohibited_str, cn2py, load_font
|
||||
from utils.image_utils import BuildImage
|
||||
|
||||
|
||||
class FgoData(BaseData):
|
||||
pass
|
||||
|
||||
|
||||
class FgoChar(FgoData):
|
||||
pass
|
||||
|
||||
|
||||
class FgoCard(FgoData):
|
||||
pass
|
||||
|
||||
|
||||
class FgoHandle(BaseHandle[FgoData]):
|
||||
def __init__(self):
|
||||
super().__init__("fgo", "命运-冠位指定")
|
||||
self.data_files.append("fgo_card.json")
|
||||
self.max_star = 5
|
||||
self.config = draw_config.fgo
|
||||
self.ALL_CHAR: List[FgoChar] = []
|
||||
self.ALL_CARD: List[FgoCard] = []
|
||||
|
||||
def get_card(self, mode: int = 1) -> FgoData:
|
||||
if mode == 1:
|
||||
star = self.get_star(
|
||||
[8, 7, 6, 5, 4, 3],
|
||||
[
|
||||
self.config.FGO_SERVANT_FIVE_P,
|
||||
self.config.FGO_SERVANT_FOUR_P,
|
||||
self.config.FGO_SERVANT_THREE_P,
|
||||
self.config.FGO_CARD_FIVE_P,
|
||||
self.config.FGO_CARD_FOUR_P,
|
||||
self.config.FGO_CARD_THREE_P,
|
||||
],
|
||||
)
|
||||
elif mode == 2:
|
||||
star = self.get_star(
|
||||
[5, 4], [self.config.FGO_CARD_FIVE_P, self.config.FGO_CARD_FOUR_P]
|
||||
)
|
||||
else:
|
||||
star = self.get_star(
|
||||
[8, 7, 6],
|
||||
[
|
||||
self.config.FGO_SERVANT_FIVE_P,
|
||||
self.config.FGO_SERVANT_FOUR_P,
|
||||
self.config.FGO_SERVANT_THREE_P,
|
||||
],
|
||||
)
|
||||
if star > 5:
|
||||
star -= 3
|
||||
chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited]
|
||||
else:
|
||||
chars = [x for x in self.ALL_CARD if x.star == star and not x.limited]
|
||||
return random.choice(chars)
|
||||
|
||||
def get_cards(self, count: int, **kwargs) -> List[Tuple[FgoData, int]]:
|
||||
card_list = [] # 获取所有角色
|
||||
servant_count = 0 # 保底计算
|
||||
card_count = 0 # 保底计算
|
||||
for i in range(count):
|
||||
servant_count += 1
|
||||
card_count += 1
|
||||
if card_count == 9: # 四星卡片保底
|
||||
mode = 2
|
||||
elif servant_count == 10: # 三星从者保底
|
||||
mode = 3
|
||||
else: # 普通抽
|
||||
mode = 1
|
||||
card = self.get_card(mode)
|
||||
if isinstance(card, FgoCard) and card.star > self.max_star - 2:
|
||||
card_count = 0
|
||||
if isinstance(card, FgoChar):
|
||||
servant_count = 0
|
||||
card_list.append((card, i + 1))
|
||||
return card_list
|
||||
|
||||
def generate_card_img(self, card: FgoData) -> BuildImage:
|
||||
sep_w = 5
|
||||
sep_t = 5
|
||||
sep_b = 20
|
||||
w = 128
|
||||
h = 140
|
||||
bg = BuildImage(w + sep_w * 2, h + sep_t + sep_b)
|
||||
img_path = str(self.img_path / f"{cn2py(card.name)}.png")
|
||||
img = BuildImage(w, h, background=img_path)
|
||||
bg.paste(img, (sep_w, sep_t), alpha=True)
|
||||
# 加名字
|
||||
text = card.name[:6] + "..." if len(card.name) > 7 else card.name
|
||||
font = load_font(fontsize=16)
|
||||
text_w, text_h = font.getsize(text)
|
||||
draw = ImageDraw.Draw(bg.markImg)
|
||||
draw.text(
|
||||
(sep_w + (w - text_w) / 2, h + sep_t + (sep_b - text_h) / 2),
|
||||
text,
|
||||
font=font,
|
||||
fill="gray",
|
||||
)
|
||||
return bg
|
||||
|
||||
def _init_data(self):
|
||||
self.ALL_CHAR = [
|
||||
FgoChar(
|
||||
name=value["名称"],
|
||||
star=int(value["星级"]),
|
||||
limited=True
|
||||
if not ("圣晶石召唤" in value["入手方式"] or "圣晶石召唤(Story卡池)" in value["入手方式"])
|
||||
else False,
|
||||
)
|
||||
for value in self.load_data().values()
|
||||
]
|
||||
self.ALL_CARD = [
|
||||
FgoCard(name=value["名称"], star=int(value["星级"]), limited=False)
|
||||
for value in self.load_data("fgo_card.json").values()
|
||||
]
|
||||
|
||||
async def _update_info(self):
|
||||
# fgo.json
|
||||
fgo_info = {}
|
||||
for i in range(500):
|
||||
url = f"http://fgo.vgtime.com/servant/ajax?card=&wd=&ids=&sort=12777&o=desc&pn={i}"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} page {i} 出错")
|
||||
continue
|
||||
fgo_data = json.loads(result)
|
||||
if int(fgo_data["nums"]) <= 0:
|
||||
break
|
||||
for x in fgo_data["data"]:
|
||||
name = remove_prohibited_str(x["name"])
|
||||
member_dict = {
|
||||
"id": x["id"],
|
||||
"card_id": x["charid"],
|
||||
"头像": x["icon"],
|
||||
"名称": remove_prohibited_str(x["name"]),
|
||||
"职阶": x["classes"],
|
||||
"星级": int(x["star"]),
|
||||
"hp": x["lvmax4hp"],
|
||||
"atk": x["lvmax4atk"],
|
||||
"card_quick": x["cardquick"],
|
||||
"card_arts": x["cardarts"],
|
||||
"card_buster": x["cardbuster"],
|
||||
"宝具": x["tprop"],
|
||||
}
|
||||
fgo_info[name] = member_dict
|
||||
# 更新额外信息
|
||||
for key in fgo_info.keys():
|
||||
url = f'http://fgo.vgtime.com/servant/{fgo_info[key]["id"]}'
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
fgo_info[key]["入手方式"] = ["圣晶石召唤"]
|
||||
logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}")
|
||||
continue
|
||||
try:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
obtain = dom.xpath(
|
||||
"//table[contains(string(.),'入手方式')]/tr[8]/td[3]/text()"
|
||||
)[0]
|
||||
obtain = str(obtain).strip()
|
||||
if "限时活动免费获取 活动结束后无法获得" in obtain:
|
||||
obtain = ["活动获取"]
|
||||
elif "非限时UP无法获得" in obtain:
|
||||
obtain = ["限时召唤"]
|
||||
else:
|
||||
if "&" in obtain:
|
||||
obtain = obtain.split("&")
|
||||
else:
|
||||
obtain = obtain.split(" ")
|
||||
obtain = [s.strip() for s in obtain if s.strip()]
|
||||
fgo_info[key]["入手方式"] = obtain
|
||||
except IndexError:
|
||||
fgo_info[key]["入手方式"] = ["圣晶石召唤"]
|
||||
logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}")
|
||||
self.dump_data(fgo_info)
|
||||
logger.info(f"{self.game_name_cn} 更新成功")
|
||||
# fgo_card.json
|
||||
fgo_card_info = {}
|
||||
for i in range(500):
|
||||
url = f"http://fgo.vgtime.com/equipment/ajax?wd=&ids=&sort=12958&o=desc&pn={i}"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn}卡牌 page {i} 出错")
|
||||
continue
|
||||
fgo_data = json.loads(result)
|
||||
if int(fgo_data["nums"]) <= 0:
|
||||
break
|
||||
for x in fgo_data["data"]:
|
||||
name = remove_prohibited_str(x["name"])
|
||||
member_dict = {
|
||||
"id": x["id"],
|
||||
"card_id": x["equipid"],
|
||||
"头像": x["icon"],
|
||||
"名称": name,
|
||||
"星级": int(x["star"]),
|
||||
"hp": x["lvmax_hp"],
|
||||
"atk": x["lvmax_atk"],
|
||||
"skill_e": str(x["skill_e"]).split("<br />")[:-1],
|
||||
}
|
||||
fgo_card_info[name] = member_dict
|
||||
self.dump_data(fgo_card_info, "fgo_card.json")
|
||||
logger.info(f"{self.game_name_cn} 卡牌更新成功")
|
||||
# 下载头像
|
||||
for value in fgo_info.values():
|
||||
await self.download_img(value["头像"], value["名称"])
|
||||
for value in fgo_card_info.values():
|
||||
await self.download_img(value["头像"], value["名称"])
|
||||
@@ -0,0 +1,429 @@
|
||||
import random
|
||||
import dateparser
|
||||
from lxml import etree
|
||||
from PIL import Image, ImageDraw
|
||||
from urllib.parse import unquote
|
||||
from typing import List, Optional, Tuple
|
||||
from pydantic import ValidationError
|
||||
from datetime import datetime, timedelta
|
||||
from nonebot.adapters.onebot.v11 import Message
|
||||
from utils.message_builder import image
|
||||
from nonebot.log import logger
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
from .base_handle import BaseHandle, BaseData, UpChar, UpEvent
|
||||
from ..config import draw_config
|
||||
from ..count_manager import GenshinCountManager
|
||||
from ..util import remove_prohibited_str, cn2py, load_font
|
||||
from utils.image_utils import BuildImage
|
||||
|
||||
|
||||
class GenshinData(BaseData):
|
||||
pass
|
||||
|
||||
|
||||
class GenshinChar(GenshinData):
|
||||
pass
|
||||
|
||||
|
||||
class GenshinArms(GenshinData):
|
||||
pass
|
||||
|
||||
|
||||
class GenshinHandle(BaseHandle[GenshinData]):
|
||||
def __init__(self):
|
||||
super().__init__("genshin", "原神", "#ebebeb")
|
||||
self.data_files.append("genshin_arms.json")
|
||||
self.max_star = 5
|
||||
self.config = draw_config.genshin
|
||||
|
||||
self.ALL_CHAR: List[GenshinData] = []
|
||||
self.ALL_ARMS: List[GenshinData] = []
|
||||
self.UP_CHAR: Optional[UpEvent] = None
|
||||
self.UP_ARMS: Optional[UpEvent] = None
|
||||
|
||||
self.count_manager = GenshinCountManager((10, 90), ("4", "5"), 180)
|
||||
|
||||
# 抽取卡池
|
||||
def get_card(
|
||||
self, pool_name: str, mode: int = 1, add: float = 0.0, is_up: bool = False
|
||||
):
|
||||
"""
|
||||
mode 1:普通抽 2:四星保底 3:五星保底
|
||||
"""
|
||||
if mode == 1:
|
||||
star = self.get_star(
|
||||
[5, 4, 3],
|
||||
[
|
||||
self.config.GENSHIN_FIVE_P + add,
|
||||
self.config.GENSHIN_FOUR_P,
|
||||
self.config.GENSHIN_THREE_P,
|
||||
],
|
||||
)
|
||||
elif mode == 2:
|
||||
star = self.get_star(
|
||||
[5, 4],
|
||||
[self.config.GENSHIN_G_FIVE_P + add, self.config.GENSHIN_G_FOUR_P],
|
||||
)
|
||||
else:
|
||||
star = 5
|
||||
|
||||
if pool_name == "char":
|
||||
up_event = self.UP_CHAR
|
||||
all_list = self.ALL_CHAR + [
|
||||
x for x in self.ALL_ARMS if x.star == star and x.star < 5
|
||||
]
|
||||
elif pool_name == "arms":
|
||||
up_event = self.UP_ARMS
|
||||
all_list = self.ALL_ARMS + [
|
||||
x for x in self.ALL_CHAR if x.star == star and x.star < 5
|
||||
]
|
||||
else:
|
||||
up_event = None
|
||||
all_list = self.ALL_ARMS + self.ALL_CHAR
|
||||
|
||||
acquire_char = None
|
||||
# 是否UP
|
||||
if up_event and star > 3:
|
||||
# 获取up角色列表
|
||||
up_list = [x.name for x in up_event.up_char if x.star == star]
|
||||
# 成功获取up角色
|
||||
if random.random() < 0.5 or is_up:
|
||||
up_name = random.choice(up_list)
|
||||
try:
|
||||
acquire_char = [x for x in all_list if x.name == up_name][0]
|
||||
except IndexError:
|
||||
pass
|
||||
if not acquire_char:
|
||||
chars = [x for x in all_list if x.star == star and not x.limited]
|
||||
acquire_char = random.choice(chars)
|
||||
return acquire_char
|
||||
|
||||
def get_cards(
|
||||
self, count: int, user_id: int, pool_name: str
|
||||
) -> List[Tuple[GenshinData, int]]:
|
||||
card_list = [] # 获取角色列表
|
||||
add = 0.0
|
||||
count_manager = self.count_manager
|
||||
count_manager.check_timeout(user_id) # 检查上次抽卡次数是否超时
|
||||
count_manager.check_count(user_id, count) # 检查次数累计
|
||||
pool = self.UP_CHAR if pool_name == "char" else self.UP_ARMS
|
||||
for i in range(count):
|
||||
count_manager.increase(user_id)
|
||||
star = count_manager.check(user_id) # 是否有四星或五星保底
|
||||
if (
|
||||
count_manager.get_user_count(user_id)
|
||||
- count_manager.get_user_five_index(user_id)
|
||||
) % count_manager.get_max_guarantee() >= 72:
|
||||
add += draw_config.genshin.I72_ADD
|
||||
if star:
|
||||
if star == 4:
|
||||
card = self.get_card(pool_name, 2, add=add)
|
||||
else:
|
||||
card = self.get_card(
|
||||
pool_name, 3, add, count_manager.is_up(user_id)
|
||||
)
|
||||
else:
|
||||
card = self.get_card(pool_name, 1, add, count_manager.is_up(user_id))
|
||||
# print(f"{count_manager.get_user_count(user_id)}:",
|
||||
# count_manager.get_user_five_index(user_id), star, card.star, add)
|
||||
# 四星角色
|
||||
if card.star == 4:
|
||||
count_manager.mark_four_index(user_id)
|
||||
# 五星角色
|
||||
elif card.star == self.max_star:
|
||||
add = 0
|
||||
count_manager.mark_five_index(user_id) # 记录五星保底
|
||||
count_manager.mark_four_index(user_id) # 记录四星保底
|
||||
if pool and card.name in [
|
||||
x.name for x in pool.up_char if x.star == self.max_star
|
||||
]:
|
||||
count_manager.set_is_up(user_id, True)
|
||||
else:
|
||||
count_manager.set_is_up(user_id, False)
|
||||
card_list.append((card, count_manager.get_user_count(user_id)))
|
||||
count_manager.update_time(user_id)
|
||||
return card_list
|
||||
|
||||
def generate_card_img(self, card: GenshinData) -> BuildImage:
|
||||
sep_w = 10
|
||||
sep_h = 5
|
||||
frame_w = 112
|
||||
frame_h = 132
|
||||
img_w = 106
|
||||
img_h = 106
|
||||
bg = BuildImage(frame_w + sep_w * 2, frame_h + sep_h * 2, color="#EBEBEB")
|
||||
frame_path = str(self.img_path / "avatar_frame.png")
|
||||
frame = Image.open(frame_path)
|
||||
# 加名字
|
||||
text = card.name
|
||||
font = load_font(fontsize=14)
|
||||
text_w, text_h = font.getsize(text)
|
||||
draw = ImageDraw.Draw(frame)
|
||||
draw.text(
|
||||
((frame_w - text_w) / 2, frame_h - 15 - text_h / 2),
|
||||
text,
|
||||
font=font,
|
||||
fill="gray",
|
||||
)
|
||||
img_path = str(self.img_path / f"{cn2py(card.name)}.png")
|
||||
img = BuildImage(img_w, img_h, background=img_path)
|
||||
if isinstance(card, GenshinArms):
|
||||
# 武器卡背景不是透明的,切去上方两个圆弧
|
||||
r = 12
|
||||
circle = Image.new("L", (r * 2, r * 2), 0)
|
||||
alpha = Image.new("L", img.size, 255)
|
||||
alpha.paste(circle, (-r - 3, -r - 3)) # 左上角
|
||||
alpha.paste(circle, (img_h - r + 3, -r - 3)) # 右上角
|
||||
img.markImg.putalpha(alpha)
|
||||
star_path = str(self.img_path / f"{card.star}_star.png")
|
||||
star = Image.open(star_path)
|
||||
bg.paste(frame, (sep_w, sep_h), alpha=True)
|
||||
bg.paste(img, (sep_w + 3, sep_h + 3), alpha=True)
|
||||
bg.paste(star, (sep_w + int((frame_w - star.width) / 2), sep_h - 6), alpha=True)
|
||||
return bg
|
||||
|
||||
def format_pool_info(self, pool_name: str) -> str:
|
||||
info = ""
|
||||
up_event = None
|
||||
if pool_name == "char":
|
||||
up_event = self.UP_CHAR
|
||||
elif pool_name == "arms":
|
||||
up_event = self.UP_ARMS
|
||||
if up_event:
|
||||
star5_list = [x.name for x in up_event.up_char if x.star == 5]
|
||||
star4_list = [x.name for x in up_event.up_char if x.star == 4]
|
||||
if star5_list:
|
||||
info += f"五星UP:{' '.join(star5_list)}\n"
|
||||
if star4_list:
|
||||
info += f"四星UP:{' '.join(star4_list)}\n"
|
||||
info = f"当前up池:{up_event.title}\n{info}"
|
||||
return info.strip()
|
||||
|
||||
async def draw(self, count: int, user_id: int, pool_name: str = "", **kwargs) -> Message:
|
||||
return await asyncio.get_event_loop().run_in_executor(None, self._draw, count, user_id, pool_name)
|
||||
|
||||
def _draw(self, count: int, user_id: int, pool_name: str = "", **kwargs) -> Message:
|
||||
index2cards = self.get_cards(count, user_id, pool_name)
|
||||
cards = [card[0] for card in index2cards]
|
||||
up_event = None
|
||||
if pool_name == "char":
|
||||
up_event = self.UP_CHAR
|
||||
elif pool_name == "arms":
|
||||
up_event = self.UP_ARMS
|
||||
up_list = [x.name for x in up_event.up_char] if up_event else []
|
||||
result = self.format_star_result(cards)
|
||||
result += (
|
||||
"\n" + max_star_str
|
||||
if (max_star_str := self.format_max_star(index2cards, up_list=up_list))
|
||||
else ""
|
||||
)
|
||||
result += f"\n距离保底发还剩 {self.count_manager.get_user_guarantee_count(user_id)} 抽"
|
||||
# result += "\n【五星:0.6%,四星:5.1%,第72抽开始五星概率每抽加0.585%】"
|
||||
pool_info = self.format_pool_info(pool_name)
|
||||
img = self.generate_img(cards)
|
||||
bk = BuildImage(img.w, img.h + 50, font_size=20, color="#ebebeb")
|
||||
bk.paste(img)
|
||||
bk.text((0, img.h + 10), "【五星:0.6%,四星:5.1%,第72抽开始五星概率每抽加0.585%】")
|
||||
return pool_info + image(b64=bk.pic2bs4()) + result
|
||||
|
||||
def _init_data(self):
|
||||
self.ALL_CHAR = [
|
||||
GenshinChar(
|
||||
name=value["名称"],
|
||||
star=int(value["星级"]),
|
||||
limited=value["常驻/限定"] == "限定UP",
|
||||
)
|
||||
for key, value in self.load_data().items()
|
||||
if "旅行者" not in key
|
||||
]
|
||||
self.ALL_ARMS = [
|
||||
GenshinArms(
|
||||
name=value["名称"],
|
||||
star=int(value["星级"]),
|
||||
limited="祈愿" not in value["获取途径"],
|
||||
)
|
||||
for value in self.load_data("genshin_arms.json").values()
|
||||
]
|
||||
self.load_up_char()
|
||||
|
||||
def load_up_char(self):
|
||||
try:
|
||||
data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json")
|
||||
self.UP_CHAR = UpEvent.parse_obj(data.get("char", {}))
|
||||
self.UP_ARMS = UpEvent.parse_obj(data.get("arms", {}))
|
||||
except ValidationError:
|
||||
logger.warning(f"{self.game_name}_up_char 解析出错")
|
||||
|
||||
def dump_up_char(self):
|
||||
if self.UP_CHAR and self.UP_ARMS:
|
||||
data = {
|
||||
"char": json.loads(self.UP_CHAR.json()),
|
||||
"arms": json.loads(self.UP_ARMS.json()),
|
||||
}
|
||||
self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json")
|
||||
|
||||
async def _update_info(self):
|
||||
# genshin.json
|
||||
char_info = {}
|
||||
url = "https://wiki.biligame.com/ys/角色筛选"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} 出错")
|
||||
else:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr")
|
||||
for char in char_list:
|
||||
try:
|
||||
name = char.xpath("./td[1]/a/@title")[0]
|
||||
avatar = char.xpath("./td[1]/a/img/@srcset")[0]
|
||||
star = char.xpath("./td[3]/text()")[0]
|
||||
except IndexError:
|
||||
continue
|
||||
member_dict = {
|
||||
"头像": unquote(str(avatar).split(" ")[-2]),
|
||||
"名称": remove_prohibited_str(name),
|
||||
"星级": int(str(star).strip()[:1]),
|
||||
}
|
||||
char_info[member_dict["名称"]] = member_dict
|
||||
# 更新额外信息
|
||||
for key in char_info.keys():
|
||||
result = await self.get_url(f"https://wiki.biligame.com/ys/{key}")
|
||||
if not result:
|
||||
char_info[key]["常驻/限定"] = "未知"
|
||||
logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}")
|
||||
continue
|
||||
try:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
limit = dom.xpath(
|
||||
"//table[contains(string(.),'常驻/限定')]/tbody/tr[6]/td/text()"
|
||||
)[0]
|
||||
char_info[key]["常驻/限定"] = str(limit).strip()
|
||||
except IndexError:
|
||||
char_info[key]["常驻/限定"] = "未知"
|
||||
logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}")
|
||||
self.dump_data(char_info)
|
||||
logger.info(f"{self.game_name_cn} 更新成功")
|
||||
# genshin_arms.json
|
||||
arms_info = {}
|
||||
url = "https://wiki.biligame.com/ys/武器图鉴"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} 出错")
|
||||
else:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr")
|
||||
for char in char_list:
|
||||
try:
|
||||
name = char.xpath("./td[1]/a/@title")[0]
|
||||
avatar = char.xpath("./td[1]/a/img/@srcset")[0]
|
||||
star = char.xpath("./td[4]/img/@alt")[0]
|
||||
sources = str(char.xpath("./td[5]/text()")[0]).split(",")
|
||||
except IndexError:
|
||||
continue
|
||||
member_dict = {
|
||||
"头像": unquote(str(avatar).split(" ")[-2]),
|
||||
"名称": remove_prohibited_str(name),
|
||||
"星级": int(str(star).strip()[:1]),
|
||||
"获取途径": [s.strip() for s in sources if s.strip()],
|
||||
}
|
||||
arms_info[member_dict["名称"]] = member_dict
|
||||
self.dump_data(arms_info, "genshin_arms.json")
|
||||
logger.info(f"{self.game_name_cn} 武器更新成功")
|
||||
# 下载头像
|
||||
for value in char_info.values():
|
||||
await self.download_img(value["头像"], value["名称"])
|
||||
for value in arms_info.values():
|
||||
await self.download_img(value["头像"], value["名称"])
|
||||
# 下载星星
|
||||
idx = 1
|
||||
YS_URL = "https://patchwiki.biligame.com/images/ys"
|
||||
for url in [
|
||||
"/1/13/7xzg7tgf8dsr2hjpmdbm5gn9wvzt2on.png",
|
||||
"/b/bc/sd2ige6d7lvj7ugfumue3yjg8gyi0d1.png",
|
||||
"/e/ec/l3mnhy56pyailhn3v7r873htf2nofau.png",
|
||||
"/9/9c/sklp02ffk3aqszzvh8k1c3139s0awpd.png",
|
||||
"/c/c7/qu6xcndgj6t14oxvv7yz2warcukqv1m.png",
|
||||
]:
|
||||
await self.download_img(YS_URL + url, f"{idx}_star")
|
||||
idx += 1
|
||||
# 下载头像框
|
||||
await self.download_img(
|
||||
YS_URL + "/2/2e/opbcst4xbtcq0i4lwerucmosawn29ti.png", f"avatar_frame"
|
||||
)
|
||||
await self.update_up_char()
|
||||
|
||||
async def update_up_char(self):
|
||||
url = "https://wiki.biligame.com/ys/祈愿"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"{self.game_name_cn}获取祈愿页面出错")
|
||||
return
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
tables = dom.xpath(
|
||||
"//div[@class='mw-parser-output']/div[@class='row']/div/table[@class='wikitable']/tbody"
|
||||
)
|
||||
if not tables or len(tables) < 2:
|
||||
logger.warning(f"{self.game_name_cn}获取活动祈愿出错")
|
||||
return
|
||||
try:
|
||||
for index, table in enumerate(tables):
|
||||
title = table.xpath("./tr[1]/th/img/@title")[0]
|
||||
title = str(title).split("」")[0] + "」" if "」" in title else title
|
||||
pool_img = str(table.xpath("./tr[1]/th/img/@srcset")[0]).split(" ")[-2]
|
||||
time = table.xpath("./tr[2]/td/text()")[0]
|
||||
star5_list = table.xpath("./tr[3]/td/a/@title")
|
||||
star4_list = table.xpath("./tr[4]/td/a/@title")
|
||||
start, end = str(time).split("~")
|
||||
start_time = dateparser.parse(start)
|
||||
end_time = dateparser.parse(end)
|
||||
if not start_time and end_time:
|
||||
start_time = end_time - timedelta(days=20)
|
||||
if start_time and end_time and start_time <= datetime.now() <= end_time:
|
||||
up_event = UpEvent(
|
||||
title=title,
|
||||
pool_img=pool_img,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
up_char=[
|
||||
UpChar(name=name, star=5, limited=False, zoom=50)
|
||||
for name in star5_list
|
||||
]
|
||||
+ [
|
||||
UpChar(name=name, star=4, limited=False, zoom=50)
|
||||
for name in star4_list
|
||||
],
|
||||
)
|
||||
if index == 0:
|
||||
self.UP_CHAR = up_event
|
||||
elif index == 1:
|
||||
self.UP_ARMS = up_event
|
||||
if self.UP_CHAR and self.UP_ARMS:
|
||||
self.dump_up_char()
|
||||
logger.info(
|
||||
f"成功获取{self.game_name_cn}当前up信息...当前up池: {self.UP_CHAR.title} & {self.UP_ARMS.title}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"{self.game_name_cn}UP更新出错 {type(e)}:{e}")
|
||||
|
||||
def reset_count(self, user_id: int) -> bool:
|
||||
self.count_manager.reset(user_id)
|
||||
return True
|
||||
|
||||
async def _reload_pool(self) -> Optional[Message]:
|
||||
await self.update_up_char()
|
||||
self.load_up_char()
|
||||
if self.UP_CHAR and self.UP_ARMS:
|
||||
return Message(
|
||||
Message.template("重载成功!\n当前UP池子:{} & {}{:image}{:image}").format(
|
||||
self.UP_CHAR.title,
|
||||
self.UP_ARMS.title,
|
||||
self.UP_CHAR.pool_img,
|
||||
self.UP_ARMS.pool_img,
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,400 @@
|
||||
import re
|
||||
import random
|
||||
import dateparser
|
||||
from lxml import etree
|
||||
from PIL import ImageDraw
|
||||
from datetime import datetime
|
||||
from urllib.parse import unquote
|
||||
from typing import List, Optional, Tuple
|
||||
from pydantic import ValidationError
|
||||
from nonebot.adapters.onebot.v11 import Message
|
||||
from utils.message_builder import image
|
||||
from nonebot.log import logger
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
from .base_handle import BaseHandle, BaseData, UpChar, UpEvent
|
||||
from ..config import draw_config
|
||||
from ..util import remove_prohibited_str, cn2py, load_font
|
||||
from utils.image_utils import BuildImage
|
||||
|
||||
|
||||
class GuardianData(BaseData):
|
||||
pass
|
||||
|
||||
|
||||
class GuardianChar(GuardianData):
|
||||
pass
|
||||
|
||||
|
||||
class GuardianArms(GuardianData):
|
||||
pass
|
||||
|
||||
|
||||
class GuardianHandle(BaseHandle[GuardianData]):
|
||||
def __init__(self):
|
||||
super().__init__("guardian", "坎公骑冠剑")
|
||||
self.data_files.append("guardian_arms.json")
|
||||
self.config = draw_config.guardian
|
||||
|
||||
self.ALL_CHAR: List[GuardianChar] = []
|
||||
self.ALL_ARMS: List[GuardianArms] = []
|
||||
self.UP_CHAR: Optional[UpEvent] = None
|
||||
self.UP_ARMS: Optional[UpEvent] = None
|
||||
|
||||
def get_card(self, pool_name: str, mode: int = 1) -> GuardianData:
|
||||
if pool_name == "char":
|
||||
if mode == 1:
|
||||
star = self.get_star(
|
||||
[3, 2, 1],
|
||||
[
|
||||
self.config.GUARDIAN_THREE_CHAR_P,
|
||||
self.config.GUARDIAN_TWO_CHAR_P,
|
||||
self.config.GUARDIAN_ONE_CHAR_P,
|
||||
],
|
||||
)
|
||||
else:
|
||||
star = self.get_star(
|
||||
[3, 2],
|
||||
[
|
||||
self.config.GUARDIAN_THREE_CHAR_P,
|
||||
self.config.GUARDIAN_TWO_CHAR_P,
|
||||
],
|
||||
)
|
||||
up_event = self.UP_CHAR
|
||||
self.max_star = 3
|
||||
all_data = self.ALL_CHAR
|
||||
else:
|
||||
if mode == 1:
|
||||
star = self.get_star(
|
||||
[5, 4, 3, 2],
|
||||
[
|
||||
self.config.GUARDIAN_FIVE_ARMS_P,
|
||||
self.config.GUARDIAN_FOUR_ARMS_P,
|
||||
self.config.GUARDIAN_THREE_ARMS_P,
|
||||
self.config.GUARDIAN_TWO_ARMS_P,
|
||||
],
|
||||
)
|
||||
else:
|
||||
star = self.get_star(
|
||||
[5, 4],
|
||||
[
|
||||
self.config.GUARDIAN_FIVE_ARMS_P,
|
||||
self.config.GUARDIAN_FOUR_ARMS_P,
|
||||
],
|
||||
)
|
||||
up_event = self.UP_ARMS
|
||||
self.max_star = 5
|
||||
all_data = self.ALL_ARMS
|
||||
|
||||
acquire_char = None
|
||||
# 是否UP
|
||||
if up_event and star == self.max_star and pool_name:
|
||||
# 获取up角色列表
|
||||
up_list = [x.name for x in up_event.up_char if x.star == star]
|
||||
# 成功获取up角色
|
||||
if random.random() < 0.5:
|
||||
up_name = random.choice(up_list)
|
||||
try:
|
||||
acquire_char = [x for x in all_data if x.name == up_name][0]
|
||||
except IndexError:
|
||||
pass
|
||||
if not acquire_char:
|
||||
chars = [x for x in all_data if x.star == star and not x.limited]
|
||||
acquire_char = random.choice(chars)
|
||||
return acquire_char
|
||||
|
||||
def get_cards(self, count: int, pool_name: str) -> List[Tuple[GuardianData, int]]:
|
||||
card_list = []
|
||||
card_count = 0 # 保底计算
|
||||
for i in range(count):
|
||||
card_count += 1
|
||||
# 十连保底
|
||||
if card_count == 10:
|
||||
card = self.get_card(pool_name, 2)
|
||||
card_count = 0
|
||||
else:
|
||||
card = self.get_card(pool_name, 1)
|
||||
if card.star > self.max_star - 2:
|
||||
card_count = 0
|
||||
card_list.append((card, i + 1))
|
||||
return card_list
|
||||
|
||||
def format_pool_info(self, pool_name: str) -> str:
|
||||
info = ""
|
||||
up_event = self.UP_CHAR if pool_name == "char" else self.UP_ARMS
|
||||
if up_event:
|
||||
if pool_name == "char":
|
||||
up_list = [x.name for x in up_event.up_char if x.star == 3]
|
||||
info += f'三星UP:{" ".join(up_list)}\n'
|
||||
else:
|
||||
up_list = [x.name for x in up_event.up_char if x.star == 5]
|
||||
info += f'五星UP:{" ".join(up_list)}\n'
|
||||
info = f"当前up池:{up_event.title}\n{info}"
|
||||
return info.strip()
|
||||
|
||||
async def draw(self, count: int, pool_name: str, **kwargs) -> Message:
|
||||
return await asyncio.get_event_loop().run_in_executor(None, self._draw, count, pool_name)
|
||||
|
||||
def _draw(self, count: int, pool_name: str, **kwargs) -> Message:
|
||||
index2card = self.get_cards(count, pool_name)
|
||||
cards = [card[0] for card in index2card]
|
||||
up_event = self.UP_CHAR if pool_name == "char" else self.UP_ARMS
|
||||
up_list = [x.name for x in up_event.up_char] if up_event else []
|
||||
result = self.format_result(index2card, up_list=up_list)
|
||||
pool_info = self.format_pool_info(pool_name)
|
||||
return pool_info + image(b64=self.generate_img(cards).pic2bs4()) + result
|
||||
|
||||
def generate_card_img(self, card: GuardianData) -> BuildImage:
|
||||
sep_w = 1
|
||||
sep_h = 1
|
||||
block_w = 170
|
||||
block_h = 90
|
||||
img_w = 90
|
||||
img_h = 90
|
||||
if isinstance(card, GuardianChar):
|
||||
block_color = "#2e2923"
|
||||
font_color = "#e2ccad"
|
||||
star_w = 90
|
||||
star_h = 30
|
||||
star_name = f"{card.star}_star.png"
|
||||
frame_path = ""
|
||||
else:
|
||||
block_color = "#EEE4D5"
|
||||
font_color = "#A65400"
|
||||
star_w = 45
|
||||
star_h = 45
|
||||
star_name = f"{card.star}_star_rank.png"
|
||||
frame_path = str(self.img_path / "avatar_frame.png")
|
||||
bg = BuildImage(block_w + sep_w * 2, block_h + sep_h * 2, color="#F6F4ED")
|
||||
block = BuildImage(block_w, block_h, color=block_color)
|
||||
star_path = str(self.img_path / star_name)
|
||||
star = BuildImage(star_w, star_h, background=star_path)
|
||||
img_path = str(self.img_path / f"{cn2py(card.name)}.png")
|
||||
img = BuildImage(img_w, img_h, background=img_path)
|
||||
block.paste(img, (0, 0), alpha=True)
|
||||
if frame_path:
|
||||
frame = BuildImage(img_w, img_h, background=frame_path)
|
||||
block.paste(frame, (0, 0), alpha=True)
|
||||
block.paste(
|
||||
star,
|
||||
(int((block_w + img_w - star_w) / 2), block_h - star_h - 30),
|
||||
alpha=True,
|
||||
)
|
||||
# 加名字
|
||||
text = card.name[:4] + "..." if len(card.name) > 5 else card.name
|
||||
font = load_font(fontsize=14)
|
||||
text_w, _ = font.getsize(text)
|
||||
draw = ImageDraw.Draw(block.markImg)
|
||||
draw.text(
|
||||
((block_w + img_w - text_w) / 2, 55),
|
||||
text,
|
||||
font=font,
|
||||
fill=font_color,
|
||||
)
|
||||
bg.paste(block, (sep_w, sep_h))
|
||||
return bg
|
||||
|
||||
def _init_data(self):
|
||||
self.ALL_CHAR = [
|
||||
GuardianChar(name=value["名称"], star=int(value["星级"]), limited=False)
|
||||
for value in self.load_data().values()
|
||||
]
|
||||
self.ALL_ARMS = [
|
||||
GuardianArms(name=value["名称"], star=int(value["星级"]), limited=False)
|
||||
for value in self.load_data("guardian_arms.json").values()
|
||||
]
|
||||
self.load_up_char()
|
||||
|
||||
def load_up_char(self):
|
||||
try:
|
||||
data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json")
|
||||
self.UP_CHAR = UpEvent.parse_obj(data.get("char", {}))
|
||||
self.UP_ARMS = UpEvent.parse_obj(data.get("arms", {}))
|
||||
except ValidationError:
|
||||
logger.warning(f"{self.game_name}_up_char 解析出错")
|
||||
|
||||
def dump_up_char(self):
|
||||
if self.UP_CHAR and self.UP_ARMS:
|
||||
data = {
|
||||
"char": json.loads(self.UP_CHAR.json()),
|
||||
"arms": json.loads(self.UP_ARMS.json()),
|
||||
}
|
||||
self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json")
|
||||
|
||||
async def _update_info(self):
|
||||
# guardian.json
|
||||
guardian_info = {}
|
||||
url = "https://wiki.biligame.com/gt/英雄筛选表"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} 出错")
|
||||
else:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr")
|
||||
for char in char_list:
|
||||
try:
|
||||
name = char.xpath("./td[1]/a/@title")[0]
|
||||
avatar = char.xpath("./td[1]/a/img/@src")[0]
|
||||
star = char.xpath("./td[1]/span/img/@alt")[0]
|
||||
except IndexError:
|
||||
continue
|
||||
member_dict = {
|
||||
"头像": unquote(str(avatar)),
|
||||
"名称": remove_prohibited_str(name),
|
||||
"星级": int(str(star).split(" ")[0].replace("Rank", "")),
|
||||
}
|
||||
guardian_info[member_dict["名称"]] = member_dict
|
||||
self.dump_data(guardian_info)
|
||||
logger.info(f"{self.game_name_cn} 更新成功")
|
||||
# guardian_arms.json
|
||||
guardian_arms_info = {}
|
||||
url = "https://wiki.biligame.com/gt/武器"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} 武器出错")
|
||||
else:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
char_list = dom.xpath(
|
||||
"//div[@class='resp-tabs-container']/div[1]/div/table[2]/tbody/tr"
|
||||
)
|
||||
for char in char_list:
|
||||
try:
|
||||
name = char.xpath("./td[2]/a/@title")[0]
|
||||
avatar = char.xpath("./td[1]/div/div/div/a/img/@src")[0]
|
||||
star = char.xpath("./td[3]/text()")[0]
|
||||
except IndexError:
|
||||
continue
|
||||
member_dict = {
|
||||
"头像": unquote(str(avatar)),
|
||||
"名称": remove_prohibited_str(name),
|
||||
"星级": int(str(star).strip()),
|
||||
}
|
||||
guardian_arms_info[member_dict["名称"]] = member_dict
|
||||
self.dump_data(guardian_arms_info, "guardian_arms.json")
|
||||
logger.info(f"{self.game_name_cn} 武器更新成功")
|
||||
url = "https://wiki.biligame.com/gt/盾牌"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} 盾牌出错")
|
||||
else:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
char_list = dom.xpath(
|
||||
"//div[@class='resp-tabs-container']/div[2]/div/table[1]/tbody/tr"
|
||||
)
|
||||
for char in char_list:
|
||||
try:
|
||||
name = char.xpath("./td[2]/a/@title")[0]
|
||||
avatar = char.xpath("./td[1]/div/div/div/a/img/@src")[0]
|
||||
star = char.xpath("./td[3]/text()")[0]
|
||||
except IndexError:
|
||||
continue
|
||||
member_dict = {
|
||||
"头像": unquote(str(avatar)),
|
||||
"名称": remove_prohibited_str(name),
|
||||
"星级": int(str(star).strip()),
|
||||
}
|
||||
guardian_arms_info[member_dict["名称"]] = member_dict
|
||||
self.dump_data(guardian_arms_info, "guardian_arms.json")
|
||||
logger.info(f"{self.game_name_cn} 盾牌更新成功")
|
||||
# 下载头像
|
||||
for value in guardian_info.values():
|
||||
await self.download_img(value["头像"], value["名称"])
|
||||
for value in guardian_arms_info.values():
|
||||
await self.download_img(value["头像"], value["名称"])
|
||||
# 下载星星
|
||||
idx = 1
|
||||
GT_URL = "https://patchwiki.biligame.com/images/gt"
|
||||
for url in [
|
||||
"/4/4b/ardr3bi2yf95u4zomm263tc1vke6i3i.png",
|
||||
"/5/55/6vow7lh76gzus6b2g9cfn325d1sugca.png",
|
||||
"/b/b9/du8egrd2vyewg0cuyra9t8jh0srl0ds.png",
|
||||
]:
|
||||
await self.download_img(GT_URL + url, f"{idx}_star")
|
||||
idx += 1
|
||||
# 另一种星星
|
||||
idx = 1
|
||||
for url in [
|
||||
"/6/66/4e2tfa9kvhfcbikzlyei76i9crva145.png",
|
||||
"/1/10/r9ihsuvycgvsseyneqz4xs22t53026m.png",
|
||||
"/7/7a/o0k86ru9k915y04azc26hilxead7xp1.png",
|
||||
"/c/c9/rxz99asysz0rg391j3b02ta09mnpa7v.png",
|
||||
"/2/2a/sfxz0ucv1s6ewxveycz9mnmrqs2rw60.png",
|
||||
]:
|
||||
await self.download_img(GT_URL + url, f"{idx}_star_rank")
|
||||
idx += 1
|
||||
# 头像框
|
||||
await self.download_img(
|
||||
GT_URL + "/8/8e/ogbqslbhuykjhnc8trtoa0p0nhfzohs.png", f"avatar_frame"
|
||||
)
|
||||
await self.update_up_char()
|
||||
|
||||
async def update_up_char(self):
|
||||
url = "https://wiki.biligame.com/gt/首页"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"{self.game_name_cn}获取公告出错")
|
||||
return
|
||||
try:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
announcement = dom.xpath(
|
||||
"//div[@class='mw-parser-output']/div/div[3]/div[2]/div/div[2]/div[3]"
|
||||
)[0]
|
||||
title = announcement.xpath("./font/p/b/text()")[0]
|
||||
match = re.search(r"从(.*?)开始.*?至(.*?)结束", title)
|
||||
if not match:
|
||||
logger.warning(f"{self.game_name_cn}找不到UP时间")
|
||||
return
|
||||
start, end = match.groups()
|
||||
start_time = dateparser.parse(start.replace("月", "/").replace("日", ""))
|
||||
end_time = dateparser.parse(end.replace("月", "/").replace("日", ""))
|
||||
if not (start_time and end_time) or not (
|
||||
start_time <= datetime.now() <= end_time
|
||||
):
|
||||
return
|
||||
divs = announcement.xpath("./font/div")
|
||||
char_index = 0
|
||||
arms_index = 0
|
||||
for index, div in enumerate(divs):
|
||||
if div.xpath("string(.)") == "角色":
|
||||
char_index = index
|
||||
elif div.xpath("string(.)") == "武器":
|
||||
arms_index = index
|
||||
chars = divs[char_index + 1 : arms_index]
|
||||
arms = divs[arms_index + 1 :]
|
||||
up_chars = []
|
||||
up_arms = []
|
||||
for char in chars:
|
||||
name = char.xpath("./p/a/@title")[0]
|
||||
up_chars.append(UpChar(name=name, star=3, limited=False, zoom=0))
|
||||
for arm in arms:
|
||||
name = arm.xpath("./p/a/@title")[0]
|
||||
up_arms.append(UpChar(name=name, star=5, limited=False, zoom=0))
|
||||
self.UP_CHAR = UpEvent(
|
||||
title=title,
|
||||
pool_img="",
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
up_char=up_chars,
|
||||
)
|
||||
self.UP_ARMS = UpEvent(
|
||||
title=title,
|
||||
pool_img="",
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
up_char=up_arms,
|
||||
)
|
||||
self.dump_up_char()
|
||||
logger.info(f"成功获取{self.game_name_cn}当前up信息...当前up池: {title}")
|
||||
except Exception as e:
|
||||
logger.warning(f"{self.game_name_cn}UP更新出错 {type(e)}:{e}")
|
||||
|
||||
async def _reload_pool(self) -> Optional[Message]:
|
||||
await self.update_up_char()
|
||||
self.load_up_char()
|
||||
if self.UP_CHAR and self.UP_ARMS:
|
||||
return Message(f"重载成功!\n当前UP池子:{self.UP_CHAR.title}")
|
||||
@@ -0,0 +1,179 @@
|
||||
import random
|
||||
from lxml import etree
|
||||
from typing import List, Tuple
|
||||
from nonebot.log import logger
|
||||
from PIL import Image, ImageDraw
|
||||
from PIL.Image import Image as IMG
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
from .base_handle import BaseHandle, BaseData
|
||||
from ..config import draw_config
|
||||
from ..util import remove_prohibited_str, cn2py, load_font
|
||||
from utils.image_utils import BuildImage
|
||||
|
||||
|
||||
class OnmyojiChar(BaseData):
|
||||
@property
|
||||
def star_str(self) -> str:
|
||||
return ["N", "R", "SR", "SSR", "SP"][self.star - 1]
|
||||
|
||||
|
||||
class OnmyojiHandle(BaseHandle[OnmyojiChar]):
|
||||
def __init__(self):
|
||||
super().__init__("onmyoji", "阴阳师")
|
||||
self.max_star = 5
|
||||
self.config = draw_config.onmyoji
|
||||
self.ALL_CHAR: List[OnmyojiChar] = []
|
||||
|
||||
def get_card(self, **kwargs) -> OnmyojiChar:
|
||||
star = self.get_star(
|
||||
[5, 4, 3, 2],
|
||||
[
|
||||
self.config.ONMYOJI_SP,
|
||||
self.config.ONMYOJI_SSR,
|
||||
self.config.ONMYOJI_SR,
|
||||
self.config.ONMYOJI_R,
|
||||
],
|
||||
)
|
||||
chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited]
|
||||
return random.choice(chars)
|
||||
|
||||
def format_max_star(self, card_list: List[Tuple[OnmyojiChar, int]]) -> str:
|
||||
rst = ""
|
||||
for card, index in card_list:
|
||||
if card.star == self.max_star:
|
||||
rst += f"第 {index} 抽获取SP {card.name}\n"
|
||||
elif card.star == self.max_star - 1:
|
||||
rst += f"第 {index} 抽获取SSR {card.name}\n"
|
||||
return rst.strip()
|
||||
|
||||
@staticmethod
|
||||
def star_label(star: int) -> IMG:
|
||||
text, color1, color2 = [
|
||||
("N", "#7E7E82", "#F5F6F7"),
|
||||
("R", "#014FA8", "#37C6FD"),
|
||||
("SR", "#6E0AA4", "#E94EFD"),
|
||||
("SSR", "#E5511D", "#FAF905"),
|
||||
("SP", "#FA1F2D", "#FFBBAF"),
|
||||
][star - 1]
|
||||
w = 200
|
||||
h = 110
|
||||
# 制作渐变色图片
|
||||
base = Image.new("RGBA", (w, h), color1)
|
||||
top = Image.new("RGBA", (w, h), color2)
|
||||
mask = Image.new("L", (w, h))
|
||||
mask_data = []
|
||||
for y in range(h):
|
||||
mask_data.extend([int(255 * (y / h))] * w)
|
||||
mask.putdata(mask_data)
|
||||
base.paste(top, (0, 0), mask)
|
||||
# 透明图层
|
||||
font = load_font("gorga.otf", 100)
|
||||
alpha = Image.new("L", (w, h))
|
||||
draw = ImageDraw.Draw(alpha)
|
||||
draw.text((20, -30), text, fill="white", font=font)
|
||||
base.putalpha(alpha)
|
||||
# stroke
|
||||
bg = Image.new("RGBA", (w, h))
|
||||
draw = ImageDraw.Draw(bg)
|
||||
draw.text(
|
||||
(20, -30),
|
||||
text,
|
||||
font=font,
|
||||
fill="gray",
|
||||
stroke_width=3,
|
||||
stroke_fill="gray",
|
||||
)
|
||||
bg.paste(base, (0, 0), base)
|
||||
return bg
|
||||
|
||||
def generate_img(self, card_list: List[OnmyojiChar]) -> BuildImage:
|
||||
return super().generate_img(card_list, num_per_line=10)
|
||||
|
||||
def generate_card_img(self, card: OnmyojiChar) -> BuildImage:
|
||||
bg = BuildImage(73, 240, color="#F1EFE9")
|
||||
img_path = str(self.img_path / f"{cn2py(card.name)}_mark_btn.png")
|
||||
img = BuildImage(0, 0, background=img_path)
|
||||
img = Image.open(img_path).convert("RGBA")
|
||||
label = self.star_label(card.star).resize((60, 33), Image.ANTIALIAS)
|
||||
bg.paste(img, (0, 0), alpha=True)
|
||||
bg.paste(label, (0, 135), alpha=True)
|
||||
font = load_font("msyh.ttf", 16)
|
||||
draw = ImageDraw.Draw(bg.markImg)
|
||||
text = "\n".join([t for t in card.name[:4]])
|
||||
_, text_h = font.getsize_multiline(text, spacing=0)
|
||||
draw.text(
|
||||
(40, 150 + (90 - text_h) / 2), text, font=font, fill="gray", spacing=0
|
||||
)
|
||||
return bg
|
||||
|
||||
def _init_data(self):
|
||||
self.ALL_CHAR = [
|
||||
OnmyojiChar(
|
||||
name=value["名称"],
|
||||
star=["N", "R", "SR", "SSR", "SP"].index(value["星级"]) + 1,
|
||||
limited=True
|
||||
if key
|
||||
in [
|
||||
"奴良陆生",
|
||||
"卖药郎",
|
||||
"鬼灯",
|
||||
"阿香",
|
||||
"蜜桃&芥子",
|
||||
"犬夜叉",
|
||||
"杀生丸",
|
||||
"桔梗",
|
||||
"朽木露琪亚",
|
||||
"黑崎一护",
|
||||
"灶门祢豆子",
|
||||
"灶门炭治郎",
|
||||
]
|
||||
else False,
|
||||
)
|
||||
for key, value in self.load_data().items()
|
||||
]
|
||||
|
||||
async def _update_info(self):
|
||||
info = {}
|
||||
url = "https://yys.res.netease.com/pc/zt/20161108171335/js/app/all_shishen.json?v74="
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} 出错")
|
||||
return
|
||||
data = json.loads(result)
|
||||
for x in data:
|
||||
name = remove_prohibited_str(x["name"])
|
||||
member_dict = {
|
||||
"id": x["id"],
|
||||
"名称": name,
|
||||
"星级": x["level"],
|
||||
}
|
||||
info[name] = member_dict
|
||||
# logger.info(f"{name} is update...")
|
||||
# 更新头像
|
||||
for key in info.keys():
|
||||
url = f'https://yys.163.com/shishen/{info[key]["id"]}.html'
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
info[key]["头像"] = ""
|
||||
continue
|
||||
try:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
avatar = dom.xpath("//div[@class='pic_wrap']/img/@src")[0]
|
||||
avatar = "https:" + avatar
|
||||
info[key]["头像"] = avatar
|
||||
except IndexError:
|
||||
info[key]["头像"] = ""
|
||||
logger.warning(f"{self.game_name_cn} 获取头像错误 {key}")
|
||||
self.dump_data(info)
|
||||
logger.info(f"{self.game_name_cn} 更新成功")
|
||||
# 下载头像
|
||||
for value in info.values():
|
||||
await self.download_img(value["头像"], value["名称"])
|
||||
# 下载书签形式的头像
|
||||
url = f"https://yys.res.netease.com/pc/zt/20161108171335/data/mark_btn/{value['id']}.png"
|
||||
await self.download_img(url, value["名称"] + "_mark_btn")
|
||||
@@ -0,0 +1,147 @@
|
||||
import random
|
||||
from lxml import etree
|
||||
from typing import List, Tuple
|
||||
from PIL import ImageDraw
|
||||
from urllib.parse import unquote
|
||||
from nonebot.log import logger
|
||||
|
||||
from .base_handle import BaseHandle, BaseData
|
||||
from ..config import draw_config
|
||||
from ..util import remove_prohibited_str, cn2py, load_font
|
||||
from utils.image_utils import BuildImage
|
||||
|
||||
|
||||
class PcrChar(BaseData):
|
||||
pass
|
||||
|
||||
|
||||
class PcrHandle(BaseHandle[PcrChar]):
|
||||
def __init__(self):
|
||||
super().__init__("pcr", "公主连结")
|
||||
self.max_star = 3
|
||||
self.config = draw_config.pcr
|
||||
self.ALL_CHAR: List[PcrChar] = []
|
||||
|
||||
def get_card(self, mode: int = 1) -> PcrChar:
|
||||
if mode == 2:
|
||||
star = self.get_star(
|
||||
[3, 2], [self.config.PCR_G_THREE_P, self.config.PCR_G_TWO_P]
|
||||
)
|
||||
else:
|
||||
star = self.get_star(
|
||||
[3, 2, 1],
|
||||
[self.config.PCR_THREE_P, self.config.PCR_TWO_P, self.config.PCR_ONE_P],
|
||||
)
|
||||
chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited]
|
||||
return random.choice(chars)
|
||||
|
||||
def get_cards(self, count: int, **kwargs) -> List[Tuple[PcrChar, int]]:
|
||||
card_list = []
|
||||
card_count = 0 # 保底计算
|
||||
for i in range(count):
|
||||
card_count += 1
|
||||
# 十连保底
|
||||
if card_count == 10:
|
||||
card = self.get_card(2)
|
||||
card_count = 0
|
||||
else:
|
||||
card = self.get_card(1)
|
||||
if card.star > self.max_star - 2:
|
||||
card_count = 0
|
||||
card_list.append((card, i + 1))
|
||||
return card_list
|
||||
|
||||
def generate_card_img(self, card: PcrChar) -> BuildImage:
|
||||
sep_w = 5
|
||||
sep_h = 5
|
||||
star_h = 15
|
||||
img_w = 90
|
||||
img_h = 90
|
||||
font_h = 20
|
||||
bg = BuildImage(img_w + sep_w * 2, img_h + font_h + sep_h * 2, color="#EFF2F5")
|
||||
star_path = str(self.img_path / "star.png")
|
||||
star = BuildImage(star_h, star_h, background=star_path)
|
||||
img_path = str(self.img_path / f"{cn2py(card.name)}.png")
|
||||
img = BuildImage(img_w, img_h, background=img_path)
|
||||
bg.paste(img, (sep_w, sep_h), alpha=True)
|
||||
for i in range(card.star):
|
||||
bg.paste(star, (sep_w + img_w - star_h * (i + 1), sep_h), alpha=True)
|
||||
# 加名字
|
||||
text = card.name[:5] + "..." if len(card.name) > 6 else card.name
|
||||
font = load_font(fontsize=14)
|
||||
text_w, text_h = font.getsize(text)
|
||||
draw = ImageDraw.Draw(bg.markImg)
|
||||
draw.text(
|
||||
(sep_w + (img_w - text_w) / 2, sep_h + img_h + (font_h - text_h) / 2),
|
||||
text,
|
||||
font=font,
|
||||
fill="gray",
|
||||
)
|
||||
return bg
|
||||
|
||||
def _init_data(self):
|
||||
self.ALL_CHAR = [
|
||||
PcrChar(
|
||||
name=value["名称"],
|
||||
star=int(value["星级"]),
|
||||
limited=True if "(" in key else False,
|
||||
)
|
||||
for key, value in self.load_data().items()
|
||||
]
|
||||
|
||||
async def _update_info(self):
|
||||
info = {}
|
||||
if draw_config.PCR_TAI:
|
||||
url = "https://wiki.biligame.com/pcr/角色图鉴"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} 出错")
|
||||
return
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
char_list = dom.xpath(
|
||||
"//div[@class='resp-tab-content']/div[@class='unit-icon']"
|
||||
)
|
||||
for char in char_list:
|
||||
try:
|
||||
name = char.xpath("./a/@title")[0]
|
||||
avatar = char.xpath("./a/img/@srcset")[0]
|
||||
star = len(char.xpath("./div[1]/img"))
|
||||
except IndexError:
|
||||
continue
|
||||
member_dict = {
|
||||
"头像": unquote(str(avatar).split(" ")[-2]),
|
||||
"名称": remove_prohibited_str(name),
|
||||
"星级": star,
|
||||
}
|
||||
info[member_dict["名称"]] = member_dict
|
||||
else:
|
||||
url = "https://wiki.biligame.com/pcr/角色筛选表"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} 出错")
|
||||
return
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr")
|
||||
for char in char_list:
|
||||
try:
|
||||
name = char.xpath("./td[1]/a/@title")[0]
|
||||
avatar = char.xpath("./td[1]/a/img/@srcset")[0]
|
||||
star = char.xpath("./td[4]/text()")[0]
|
||||
except IndexError:
|
||||
continue
|
||||
member_dict = {
|
||||
"头像": unquote(str(avatar).split(" ")[-2]),
|
||||
"名称": remove_prohibited_str(name),
|
||||
"星级": int(str(star).strip()),
|
||||
}
|
||||
info[member_dict["名称"]] = member_dict
|
||||
self.dump_data(info)
|
||||
logger.info(f"{self.game_name_cn} 更新成功")
|
||||
# 下载头像
|
||||
for value in info.values():
|
||||
await self.download_img(value["头像"], value["名称"])
|
||||
# 下载星星
|
||||
await self.download_img(
|
||||
"https://patchwiki.biligame.com/images/pcr/0/02/s75ys2ecqhu2xbdw1wf1v9ccscnvi5g.png",
|
||||
"star",
|
||||
)
|
||||
@@ -0,0 +1,426 @@
|
||||
import re
|
||||
import random
|
||||
import dateparser
|
||||
from lxml import etree
|
||||
from PIL import ImageDraw
|
||||
from bs4 import BeautifulSoup
|
||||
from datetime import datetime
|
||||
from urllib.parse import unquote
|
||||
from typing import List, Optional, Tuple
|
||||
from pydantic import ValidationError
|
||||
from nonebot.adapters.onebot.v11 import Message
|
||||
from utils.message_builder import image
|
||||
from nonebot.log import logger
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
from .base_handle import BaseHandle, BaseData, UpChar, UpEvent
|
||||
from ..config import draw_config
|
||||
from ..util import remove_prohibited_str, cn2py, load_font
|
||||
from utils.image_utils import BuildImage
|
||||
|
||||
|
||||
class PrettyData(BaseData):
|
||||
pass
|
||||
|
||||
|
||||
class PrettyChar(PrettyData):
|
||||
pass
|
||||
|
||||
|
||||
class PrettyCard(PrettyData):
|
||||
@property
|
||||
def star_str(self) -> str:
|
||||
return ["R", "SR", "SSR"][self.star - 1]
|
||||
|
||||
|
||||
class PrettyHandle(BaseHandle[PrettyData]):
|
||||
def __init__(self):
|
||||
super().__init__("pretty", "赛马娘", "#eff2f5")
|
||||
self.data_files.append("pretty_card.json")
|
||||
self.max_star = 3
|
||||
self.config = draw_config.pretty
|
||||
|
||||
self.ALL_CHAR: List[PrettyChar] = []
|
||||
self.ALL_CARD: List[PrettyCard] = []
|
||||
self.UP_CHAR: Optional[UpEvent] = None
|
||||
self.UP_CARD: Optional[UpEvent] = None
|
||||
|
||||
def get_card(self, pool_name: str, mode: int = 1) -> PrettyData:
|
||||
if mode == 1:
|
||||
star = self.get_star(
|
||||
[3, 2, 1],
|
||||
[
|
||||
self.config.PRETTY_THREE_P,
|
||||
self.config.PRETTY_TWO_P,
|
||||
self.config.PRETTY_ONE_P,
|
||||
],
|
||||
)
|
||||
else:
|
||||
star = self.get_star(
|
||||
[3, 2], [self.config.PRETTY_THREE_P, self.config.PRETTY_TWO_P]
|
||||
)
|
||||
up_pool = None
|
||||
if pool_name == "char":
|
||||
up_pool = self.UP_CHAR
|
||||
all_list = self.ALL_CHAR
|
||||
else:
|
||||
up_pool = self.UP_CARD
|
||||
all_list = self.ALL_CARD
|
||||
|
||||
all_char = [x for x in all_list if x.star == star and not x.limited]
|
||||
acquire_char = None
|
||||
# 有UP池子
|
||||
if up_pool and star in [x.star for x in up_pool.up_char]:
|
||||
up_list = [x.name for x in up_pool.up_char if x.star == star]
|
||||
# 抽到UP
|
||||
if random.random() < 1 / len(all_char) * (0.7 / 0.1385):
|
||||
up_name = random.choice(up_list)
|
||||
try:
|
||||
acquire_char = [x for x in all_list if x.name == up_name][0]
|
||||
except IndexError:
|
||||
pass
|
||||
if not acquire_char:
|
||||
acquire_char = random.choice(all_char)
|
||||
return acquire_char
|
||||
|
||||
def get_cards(self, count: int, pool_name: str) -> List[Tuple[PrettyData, int]]:
|
||||
card_list = []
|
||||
card_count = 0 # 保底计算
|
||||
for i in range(count):
|
||||
card_count += 1
|
||||
# 十连保底
|
||||
if card_count == 10:
|
||||
card = self.get_card(pool_name, 2)
|
||||
card_count = 0
|
||||
else:
|
||||
card = self.get_card(pool_name, 1)
|
||||
if card.star > self.max_star - 2:
|
||||
card_count = 0
|
||||
card_list.append((card, i + 1))
|
||||
return card_list
|
||||
|
||||
def format_pool_info(self, pool_name: str) -> str:
|
||||
info = ""
|
||||
up_event = self.UP_CHAR if pool_name == "char" else self.UP_CARD
|
||||
if up_event:
|
||||
star3_list = [x.name for x in up_event.up_char if x.star == 3]
|
||||
star2_list = [x.name for x in up_event.up_char if x.star == 2]
|
||||
star1_list = [x.name for x in up_event.up_char if x.star == 1]
|
||||
if star3_list:
|
||||
if pool_name == "char":
|
||||
info += f'三星UP:{" ".join(star3_list)}\n'
|
||||
else:
|
||||
info += f'SSR UP:{" ".join(star3_list)}\n'
|
||||
if star2_list:
|
||||
if pool_name == "char":
|
||||
info += f'二星UP:{" ".join(star2_list)}\n'
|
||||
else:
|
||||
info += f'SR UP:{" ".join(star2_list)}\n'
|
||||
if star1_list:
|
||||
if pool_name == "char":
|
||||
info += f'一星UP:{" ".join(star1_list)}\n'
|
||||
else:
|
||||
info += f'R UP:{" ".join(star1_list)}\n'
|
||||
info = f"当前up池:{up_event.title}\n{info}"
|
||||
return info.strip()
|
||||
|
||||
async def draw(self, count: int, pool_name: str, **kwargs) -> Message:
|
||||
return await asyncio.get_event_loop().run_in_executor(None, self._draw, count, pool_name)
|
||||
|
||||
def _draw(self, count: int, pool_name: str, **kwargs) -> Message:
|
||||
pool_name = "char" if not pool_name else pool_name
|
||||
index2card = self.get_cards(count, pool_name)
|
||||
cards = [card[0] for card in index2card]
|
||||
up_event = self.UP_CHAR if pool_name == "char" else self.UP_CARD
|
||||
up_list = [x.name for x in up_event.up_char] if up_event else []
|
||||
result = self.format_result(index2card, up_list=up_list)
|
||||
pool_info = self.format_pool_info(pool_name)
|
||||
return pool_info + image(b64=self.generate_img(cards).pic2bs4()) + result
|
||||
|
||||
def generate_card_img(self, card: PrettyData) -> BuildImage:
|
||||
if isinstance(card, PrettyChar):
|
||||
star_h = 30
|
||||
img_w = 200
|
||||
img_h = 219
|
||||
font_h = 50
|
||||
bg = BuildImage(img_w, img_h + font_h, color="#EFF2F5")
|
||||
star_path = str(self.img_path / "star.png")
|
||||
star = BuildImage(star_h, star_h, background=star_path)
|
||||
img_path = str(self.img_path / f"{cn2py(card.name)}.png")
|
||||
img = BuildImage(img_w, img_h, background=img_path)
|
||||
star_w = star_h * card.star
|
||||
for i in range(card.star):
|
||||
bg.paste(star, (int((img_w - star_w) / 2) + star_h * i, 0), alpha=True)
|
||||
bg.paste(img, (0, 0), alpha=True)
|
||||
# 加名字
|
||||
text = card.name[:5] + "..." if len(card.name) > 6 else card.name
|
||||
font = load_font(fontsize=30)
|
||||
text_w, _ = font.getsize(text)
|
||||
draw = ImageDraw.Draw(bg.markImg)
|
||||
draw.text(
|
||||
((img_w - text_w) / 2, img_h),
|
||||
text,
|
||||
font=font,
|
||||
fill="gray",
|
||||
)
|
||||
return bg
|
||||
else:
|
||||
sep_w = 10
|
||||
img_w = 200
|
||||
img_h = 267
|
||||
font_h = 75
|
||||
bg = BuildImage(img_w + sep_w * 2, img_h + font_h, color="#EFF2F5")
|
||||
label_path = str(self.img_path / f"{card.star}_label.png")
|
||||
label = BuildImage(40, 40, background=label_path)
|
||||
img_path = str(self.img_path / f"{cn2py(card.name)}.png")
|
||||
img = BuildImage(img_w, img_h, background=img_path)
|
||||
bg.paste(img, (sep_w, 0), alpha=True)
|
||||
bg.paste(label, (30, 3), alpha=True)
|
||||
# 加名字
|
||||
text = ""
|
||||
texts = []
|
||||
font = load_font(fontsize=25)
|
||||
for t in card.name:
|
||||
if font.getsize(text + t)[0] > 190:
|
||||
texts.append(text)
|
||||
text = ""
|
||||
if len(texts) >= 2:
|
||||
texts[-1] += "..."
|
||||
break
|
||||
else:
|
||||
text += t
|
||||
if text:
|
||||
texts.append(text)
|
||||
text = "\n".join(texts)
|
||||
text_w, _ = font.getsize_multiline(text)
|
||||
draw = ImageDraw.Draw(bg.markImg)
|
||||
draw.text(
|
||||
((img_w - text_w) / 2, img_h),
|
||||
text,
|
||||
font=font,
|
||||
align="center",
|
||||
fill="gray",
|
||||
)
|
||||
return bg
|
||||
|
||||
def _init_data(self):
|
||||
self.ALL_CHAR = [
|
||||
PrettyChar(
|
||||
name=value["名称"],
|
||||
star=int(value["初始星级"]),
|
||||
limited=False,
|
||||
)
|
||||
for value in self.load_data().values()
|
||||
]
|
||||
self.ALL_CARD = [
|
||||
PrettyCard(
|
||||
name=value["中文名"],
|
||||
star=["R", "SR", "SSR"].index(value["稀有度"]) + 1,
|
||||
limited=True if "卡池" not in value["获取方式"] else False,
|
||||
)
|
||||
for value in self.load_data("pretty_card.json").values()
|
||||
]
|
||||
self.load_up_char()
|
||||
|
||||
def load_up_char(self):
|
||||
try:
|
||||
data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json")
|
||||
self.UP_CHAR = UpEvent.parse_obj(data.get("char", {}))
|
||||
self.UP_CARD = UpEvent.parse_obj(data.get("card", {}))
|
||||
except ValidationError:
|
||||
logger.warning(f"{self.game_name}_up_char 解析出错")
|
||||
|
||||
def dump_up_char(self):
|
||||
if self.UP_CHAR and self.UP_CARD:
|
||||
data = {
|
||||
"char": json.loads(self.UP_CHAR.json()),
|
||||
"card": json.loads(self.UP_CARD.json()),
|
||||
}
|
||||
self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json")
|
||||
|
||||
async def _update_info(self):
|
||||
# pretty.json
|
||||
pretty_info = {}
|
||||
url = "https://wiki.biligame.com/umamusume/赛马娘图鉴"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} 出错")
|
||||
else:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr")
|
||||
for char in char_list:
|
||||
try:
|
||||
name = char.xpath("./td[1]/a/@title")[0]
|
||||
avatar = char.xpath("./td[1]/a/img/@srcset")[0]
|
||||
star = len(char.xpath("./td[3]/img"))
|
||||
except IndexError:
|
||||
continue
|
||||
member_dict = {
|
||||
"头像": unquote(str(avatar).split(" ")[-2]),
|
||||
"名称": remove_prohibited_str(name),
|
||||
"初始星级": star,
|
||||
}
|
||||
pretty_info[member_dict["名称"]] = member_dict
|
||||
self.dump_data(pretty_info)
|
||||
logger.info(f"{self.game_name_cn} 更新成功")
|
||||
# pretty_card.json
|
||||
pretty_card_info = {}
|
||||
url = "https://wiki.biligame.com/umamusume/支援卡图鉴"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} 卡牌出错")
|
||||
else:
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr")
|
||||
for char in char_list:
|
||||
try:
|
||||
name = char.xpath("./td[1]/div/a/@title")[0]
|
||||
name_cn = char.xpath("./td[3]/a/text()")[0]
|
||||
avatar = char.xpath("./td[1]/div/a/img/@srcset")[0]
|
||||
star = str(char.xpath("./td[5]/text()")[0]).strip()
|
||||
sources = str(char.xpath("./td[7]/text()")[0]).strip()
|
||||
except IndexError:
|
||||
continue
|
||||
member_dict = {
|
||||
"头像": unquote(str(avatar).split(" ")[-2]),
|
||||
"名称": remove_prohibited_str(name),
|
||||
"中文名": remove_prohibited_str(name_cn),
|
||||
"稀有度": star,
|
||||
"获取方式": [sources] if sources else [],
|
||||
}
|
||||
pretty_card_info[member_dict["中文名"]] = member_dict
|
||||
self.dump_data(pretty_card_info, "pretty_card.json")
|
||||
logger.info(f"{self.game_name_cn} 卡牌更新成功")
|
||||
# 下载头像
|
||||
for value in pretty_info.values():
|
||||
await self.download_img(value["头像"], value["名称"])
|
||||
for value in pretty_card_info.values():
|
||||
await self.download_img(value["头像"], value["中文名"])
|
||||
# 下载星星
|
||||
PRETTY_URL = "https://patchwiki.biligame.com/images/umamusume"
|
||||
await self.download_img(
|
||||
PRETTY_URL + "/1/13/e1hwjz4vmhtvk8wlyb7c0x3ld1s2ata.png", "star"
|
||||
)
|
||||
# 下载稀有度标志
|
||||
idx = 1
|
||||
for url in [
|
||||
"/f/f7/afqs7h4snmvovsrlifq5ib8vlpu2wvk.png",
|
||||
"/3/3b/d1jmpwrsk4irkes1gdvoos4ic6rmuht.png",
|
||||
"/0/06/q23szwkbtd7pfkqrk3wcjlxxt9z595o.png",
|
||||
]:
|
||||
await self.download_img(PRETTY_URL + url, f"{idx}_label")
|
||||
idx += 1
|
||||
await self.update_up_char()
|
||||
|
||||
async def update_up_char(self):
|
||||
announcement_url = "https://wiki.biligame.com/umamusume/公告"
|
||||
result = await self.get_url(announcement_url)
|
||||
if not result:
|
||||
logger.warning(f"{self.game_name_cn}获取公告出错")
|
||||
return
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
announcements = dom.xpath("//div[@id='mw-content-text']/div/div/span/a")
|
||||
title = ""
|
||||
url = ""
|
||||
for announcement in announcements:
|
||||
try:
|
||||
title = announcement.xpath("./@title")[0]
|
||||
url = "https://wiki.biligame.com/" + announcement.xpath("./@href")[0]
|
||||
if re.match(r".*?\d{8}$", title) or re.match(
|
||||
r"^\d{1,2}月\d{1,2}日.*?", title
|
||||
):
|
||||
break
|
||||
except IndexError:
|
||||
continue
|
||||
if not title:
|
||||
logger.warning(f"{self.game_name_cn}未找到新UP公告")
|
||||
return
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"{self.game_name_cn}获取UP公告出错")
|
||||
return
|
||||
try:
|
||||
start_time = None
|
||||
end_time = None
|
||||
char_img = ""
|
||||
card_img = ""
|
||||
up_chars = []
|
||||
up_cards = []
|
||||
soup = BeautifulSoup(result, "lxml")
|
||||
heads = soup.find_all("span", {"class": "mw-headline"})
|
||||
for head in heads:
|
||||
if "时间" in head.text:
|
||||
time = head.find_next("p").text.split("\n")[0]
|
||||
if "~" in time:
|
||||
start, end = time.split("~")
|
||||
start_time = dateparser.parse(start)
|
||||
end_time = dateparser.parse(end)
|
||||
elif "赛马娘" in head.text:
|
||||
char_img = head.find_next("a", {"class": "image"}).find("img")[
|
||||
"src"
|
||||
]
|
||||
lines = str(head.find_next("p").text).split("\n")
|
||||
chars = [
|
||||
line
|
||||
for line in lines
|
||||
if "★" in line and "(" in line and ")" in line
|
||||
]
|
||||
for char in chars:
|
||||
star = char.count("★")
|
||||
name = re.split(r"[()]", char)[-2].strip()
|
||||
up_chars.append(
|
||||
UpChar(name=name, star=star, limited=False, zoom=70)
|
||||
)
|
||||
elif "支援卡" in head.text:
|
||||
card_img = head.find_next("a", {"class": "image"}).find("img")[
|
||||
"src"
|
||||
]
|
||||
lines = str(head.find_next("p").text).split("\n")
|
||||
cards = [
|
||||
line
|
||||
for line in lines
|
||||
if "R" in line and "(" in line and ")" in line
|
||||
]
|
||||
for card in cards:
|
||||
star = 3 if "SSR" in card else 2 if "SR" in card else 1
|
||||
name = re.split(r"[()]", card)[-2].strip()
|
||||
up_cards.append(
|
||||
UpChar(name=name, star=star, limited=False, zoom=70)
|
||||
)
|
||||
if start_time and end_time:
|
||||
if start_time <= datetime.now() <= end_time:
|
||||
self.UP_CHAR = UpEvent(
|
||||
title=title,
|
||||
pool_img=char_img,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
up_char=up_chars,
|
||||
)
|
||||
self.UP_CARD = UpEvent(
|
||||
title=title,
|
||||
pool_img=card_img,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
up_char=up_cards,
|
||||
)
|
||||
self.dump_up_char()
|
||||
logger.info(f"成功获取{self.game_name_cn}当前up信息...当前up池: {title}")
|
||||
except Exception as e:
|
||||
logger.warning(f"{self.game_name_cn}UP更新出错 {type(e)}:{e}")
|
||||
|
||||
async def _reload_pool(self) -> Optional[Message]:
|
||||
await self.update_up_char()
|
||||
self.load_up_char()
|
||||
if self.UP_CHAR and self.UP_CARD:
|
||||
return Message(
|
||||
Message.template("重载成功!\n当前UP池子:{}{:image}{:image}").format(
|
||||
self.UP_CHAR.title,
|
||||
self.UP_CHAR.pool_img,
|
||||
self.UP_CARD.pool_img,
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,314 @@
|
||||
import re
|
||||
import random
|
||||
import dateparser
|
||||
from lxml import etree
|
||||
from PIL import ImageDraw
|
||||
from datetime import datetime
|
||||
from urllib.parse import unquote
|
||||
from typing import List, Optional, Tuple
|
||||
from pydantic import ValidationError
|
||||
from nonebot.adapters.onebot.v11 import Message
|
||||
from utils.message_builder import image
|
||||
from nonebot.log import logger
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
from .base_handle import BaseHandle, BaseData, UpChar, UpEvent
|
||||
from ..config import draw_config
|
||||
from ..util import remove_prohibited_str, cn2py, load_font
|
||||
from utils.image_utils import BuildImage
|
||||
|
||||
|
||||
class Operator(BaseData):
|
||||
recruit_only: bool # 公招限定
|
||||
event_only: bool # 活动获得干员
|
||||
# special_only: bool # 升变/异格干员
|
||||
|
||||
|
||||
class PrtsHandle(BaseHandle[Operator]):
|
||||
def __init__(self):
|
||||
super().__init__(game_name="prts", game_name_cn="明日方舟")
|
||||
self.max_star = 6
|
||||
self.game_card_color = "#eff2f5"
|
||||
self.config = draw_config.prts
|
||||
|
||||
self.ALL_OPERATOR: List[Operator] = []
|
||||
self.UP_EVENT: Optional[UpEvent] = None
|
||||
|
||||
def get_card(self, add: float) -> Operator:
|
||||
star = self.get_star(
|
||||
star_list=[6, 5, 4, 3],
|
||||
probability_list=[
|
||||
self.config.PRTS_SIX_P + add,
|
||||
self.config.PRTS_FIVE_P,
|
||||
self.config.PRTS_FOUR_P,
|
||||
self.config.PRTS_THREE_P,
|
||||
],
|
||||
)
|
||||
|
||||
all_operators = [
|
||||
x
|
||||
for x in self.ALL_OPERATOR
|
||||
if x.star == star and not any([x.limited, x.recruit_only, x.event_only])
|
||||
]
|
||||
acquire_operator = None
|
||||
|
||||
if self.UP_EVENT:
|
||||
up_operators = [x for x in self.UP_EVENT.up_char if x.star == star]
|
||||
# UPs
|
||||
try:
|
||||
zooms = [x.zoom for x in up_operators]
|
||||
zoom_sum = sum(zooms)
|
||||
if random.random() < zoom_sum:
|
||||
up_name = random.choices(up_operators, weights=zooms, k=1)[0].name
|
||||
acquire_operator = [
|
||||
x for x in self.ALL_OPERATOR if x.name == up_name
|
||||
][0]
|
||||
except IndexError:
|
||||
pass
|
||||
if not acquire_operator:
|
||||
acquire_operator = random.choice(all_operators)
|
||||
return acquire_operator
|
||||
|
||||
def get_cards(self, count: int, **kwargs) -> List[Tuple[Operator, int]]:
|
||||
card_list = [] # 获取所有角色
|
||||
add = 0.0
|
||||
count_idx = 0
|
||||
for i in range(count):
|
||||
count_idx += 1
|
||||
card = self.get_card(add)
|
||||
if card.star == self.max_star:
|
||||
add = 0.0
|
||||
count_idx = 0
|
||||
elif count_idx > 50:
|
||||
add += 0.02
|
||||
card_list.append((card, i + 1))
|
||||
return card_list
|
||||
|
||||
def format_pool_info(self) -> str:
|
||||
info = ""
|
||||
if self.UP_EVENT:
|
||||
star6_list = [x.name for x in self.UP_EVENT.up_char if x.star == 6]
|
||||
star5_list = [x.name for x in self.UP_EVENT.up_char if x.star == 5]
|
||||
star4_list = [x.name for x in self.UP_EVENT.up_char if x.star == 4]
|
||||
if star6_list:
|
||||
info += f"六星UP:{' '.join(star6_list)}\n"
|
||||
if star5_list:
|
||||
info += f"五星UP:{' '.join(star5_list)}\n"
|
||||
if star4_list:
|
||||
info += f"四星UP:{' '.join(star4_list)}\n"
|
||||
info = f"当前up池: {self.UP_EVENT.title}\n{info}"
|
||||
return info.strip()
|
||||
|
||||
async def draw(self, count: int, **kwargs) -> Message:
|
||||
return await asyncio.get_event_loop().run_in_executor(None, self._draw, count)
|
||||
|
||||
def _draw(self, count: int, **kwargs) -> Message:
|
||||
index2card = self.get_cards(count)
|
||||
"""这里cards修复了抽卡图文不符的bug"""
|
||||
cards = [card[0] for card in index2card]
|
||||
up_list = [x.name for x in self.UP_EVENT.up_char] if self.UP_EVENT else []
|
||||
result = self.format_result(index2card, up_list=up_list)
|
||||
pool_info = self.format_pool_info()
|
||||
return pool_info + image(b64=self.generate_img(cards).pic2bs4()) + result
|
||||
|
||||
def generate_card_img(self, card: Operator) -> BuildImage:
|
||||
sep_w = 5
|
||||
sep_h = 5
|
||||
star_h = 15
|
||||
img_w = 120
|
||||
img_h = 120
|
||||
font_h = 20
|
||||
bg = BuildImage(img_w + sep_w * 2, img_h + font_h + sep_h * 2, color="#EFF2F5")
|
||||
star_path = str(self.img_path / "star.png")
|
||||
star = BuildImage(star_h, star_h, background=star_path)
|
||||
img_path = str(self.img_path / f"{cn2py(card.name)}.png")
|
||||
img = BuildImage(img_w, img_h, background=img_path)
|
||||
bg.paste(img, (sep_w, sep_h), alpha=True)
|
||||
for i in range(card.star):
|
||||
bg.paste(star, (sep_w + img_w - 5 - star_h * (i + 1), sep_h), alpha=True)
|
||||
# 加名字
|
||||
text = card.name[:7] + "..." if len(card.name) > 8 else card.name
|
||||
font = load_font(fontsize=16)
|
||||
text_w, text_h = font.getsize(text)
|
||||
draw = ImageDraw.Draw(bg.markImg)
|
||||
draw.text(
|
||||
(sep_w + (img_w - text_w) / 2, sep_h + img_h + (font_h - text_h) / 2),
|
||||
text,
|
||||
font=font,
|
||||
fill="gray",
|
||||
)
|
||||
return bg
|
||||
|
||||
def _init_data(self):
|
||||
self.ALL_OPERATOR = [
|
||||
Operator(
|
||||
name=value["名称"],
|
||||
star=int(value["星级"]),
|
||||
limited="干员寻访" not in value["获取途径"],
|
||||
recruit_only=True
|
||||
if "干员寻访" not in value["获取途径"] and "公开招募" in value["获取途径"]
|
||||
else False,
|
||||
event_only=True if "活动获取" in value["获取途径"] else False,
|
||||
)
|
||||
for key, value in self.load_data().items()
|
||||
if "阿米娅" not in key
|
||||
]
|
||||
self.load_up_char()
|
||||
|
||||
def load_up_char(self):
|
||||
try:
|
||||
data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json")
|
||||
"""这里的 waring 有点模糊,更新游戏信息时没有up池的情况下也会报错,所以细分了一下"""
|
||||
if not data:
|
||||
logger.warning(f"当前无UP池或 {self.game_name}_up_char.json 文件不存在")
|
||||
else:
|
||||
self.UP_EVENT = UpEvent.parse_obj(data.get("char", {}))
|
||||
except ValidationError:
|
||||
logger.warning(f"{self.game_name}_up_char 解析出错")
|
||||
|
||||
def dump_up_char(self):
|
||||
if self.UP_EVENT:
|
||||
data = {"char": json.loads(self.UP_EVENT.json())}
|
||||
self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json")
|
||||
|
||||
async def _update_info(self):
|
||||
info = {}
|
||||
url = "https://wiki.biligame.com/arknights/干员数据表"
|
||||
result = await self.get_url(url)
|
||||
if not result:
|
||||
logger.warning(f"更新 {self.game_name_cn} 出错")
|
||||
return
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr")
|
||||
for char in char_list:
|
||||
try:
|
||||
avatar = char.xpath("./td[1]/div/div/div/a/img/@srcset")[0]
|
||||
name = char.xpath("./td[2]/a/text()")[0]
|
||||
star = char.xpath("./td[5]/text()")[0]
|
||||
"""这里sources修好了干员获取标签有问题的bug,如三星只能抽到卡缇就是这个原因"""
|
||||
sources = [_.strip('\n') for _ in char.xpath("./td[8]/text()")]
|
||||
except IndexError:
|
||||
continue
|
||||
member_dict = {
|
||||
"头像": unquote(str(avatar).split(" ")[-2]),
|
||||
"名称": remove_prohibited_str(str(name).strip()),
|
||||
"星级": int(str(star).strip()),
|
||||
"获取途径": sources,
|
||||
}
|
||||
info[member_dict["名称"]] = member_dict
|
||||
self.dump_data(info)
|
||||
logger.info(f"{self.game_name_cn} 更新成功")
|
||||
# 下载头像
|
||||
for value in info.values():
|
||||
await self.download_img(value["头像"], value["名称"])
|
||||
# 下载星星
|
||||
await self.download_img(
|
||||
"https://patchwiki.biligame.com/images/pcr/0/02/s75ys2ecqhu2xbdw1wf1v9ccscnvi5g.png",
|
||||
"star",
|
||||
)
|
||||
await self.update_up_char()
|
||||
|
||||
async def update_up_char(self):
|
||||
"""重载卡池"""
|
||||
announcement_url = "https://ak.hypergryph.com/news.html"
|
||||
result = await self.get_url(announcement_url)
|
||||
if not result:
|
||||
logger.warning(f"{self.game_name_cn}获取公告出错")
|
||||
return
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
activity_urls = dom.xpath(
|
||||
"//ol[@class='articleList' and @data-category-key='ACTIVITY']/li/a/@href"
|
||||
)
|
||||
start_time = None
|
||||
end_time = None
|
||||
up_chars = []
|
||||
pool_img = ""
|
||||
for activity_url in activity_urls[:10]: # 减少响应时间, 10个就够了
|
||||
activity_url = f"https://ak.hypergryph.com{activity_url}"
|
||||
result = await self.get_url(activity_url)
|
||||
if not result:
|
||||
logger.warning(f"{self.game_name_cn}获取公告 {activity_url} 出错")
|
||||
continue
|
||||
|
||||
"""因为鹰角的前端太自由了,这里重写了匹配规则以尽可能避免因为前端乱七八糟而导致的重载失败"""
|
||||
dom = etree.HTML(result, etree.HTMLParser())
|
||||
contents = dom.xpath(
|
||||
"//div[@class='article-content']/p/text() | //div[@class='article-content']/p/span/text() | //div[@class='article-content']/div[@class='media-wrap image-wrap']/img/@src"
|
||||
)
|
||||
title = ""
|
||||
time = ""
|
||||
chars: List[str] = []
|
||||
for index, content in enumerate(contents):
|
||||
if re.search("(.*)(寻访|复刻).*?开启", content):
|
||||
title = re.split(r"[【】]", content)
|
||||
title = "".join(title[1:-1]) if "-" in title else title[1]
|
||||
lines = [contents[index-2+_] for _ in range(8)] # 从 -2 开始是因为xpath获取的时间有的会在寻访开启这一句之前
|
||||
lines.append("") # 防止IndexError,加个空字符串
|
||||
for idx, line in enumerate(lines):
|
||||
match = re.search(
|
||||
r"(\d{1,2}月\d{1,2}日.*?-.*?\d{1,2}月\d{1,2}日.*?$)", line
|
||||
)
|
||||
if match:
|
||||
time = match.group(1)
|
||||
"""因为 <p> 的诡异排版,所以有了下面的一段"""
|
||||
if ("★★" in line and "%" in line) or ("★★" in line and "%" in lines[idx + 1]):
|
||||
chars.append(line) if ("★★" in line and "%" in line) else chars.append(line + lines[idx + 1])
|
||||
if not time:
|
||||
continue
|
||||
start, end = time.replace("月", "/").replace("日", " ").split("-")[:2] # 日替换为空格是因为有日后面不接空格的情况,导致 split 出问题
|
||||
start_time = dateparser.parse(start)
|
||||
end_time = dateparser.parse(end)
|
||||
pool_img = contents[index-2]
|
||||
r"""两类格式:用/分割,用\分割;★+(概率)+名字,★+名字+(概率)"""
|
||||
for char in chars:
|
||||
star = char.split("(")[0].count("★")
|
||||
name = re.split(r"[:(]", char)[1] if "★(" not in char else re.split("):", char)[1] # 有的括号在前面有的在后面
|
||||
if "\\" in name:
|
||||
names = name.split("\\")
|
||||
elif "/" in name:
|
||||
names = name.split("/")
|
||||
else:
|
||||
names = [name] # 既有用/分割的,又有用\分割的
|
||||
|
||||
names = [name.replace("[限定]", "").strip() for name in names]
|
||||
if "权值" in char:
|
||||
match = re.search(r"(在.*?以.*?(\d+).*?倍权值.*?)", char)
|
||||
else:
|
||||
match = re.search(r"(占.*?的.*?(\d+).*?%)", char)
|
||||
zoom = 1
|
||||
if match:
|
||||
zoom = float(match.group(1))
|
||||
zoom = zoom / 100 if zoom > 10 else zoom
|
||||
for name in names:
|
||||
up_chars.append(
|
||||
UpChar(name=name, star=star, limited=False, zoom=zoom)
|
||||
)
|
||||
if start_time <= datetime.now() <= end_time: # 跳过过时的卡池
|
||||
break # 这里break会导致个问题:如果一个公告里有两个池子,会漏掉下面的池子,比如 5.19 的定向寻访。但目前我也没啥好想法解决
|
||||
chars: List[str] = [] # 查找下一个卡池之前清空 chars
|
||||
up_chars = [] # 查找下一个卡池之前清空 up_chars
|
||||
if title and start_time and end_time:
|
||||
if start_time <= datetime.now() <= end_time:
|
||||
self.UP_EVENT = UpEvent(
|
||||
title=title,
|
||||
pool_img=pool_img,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
up_char=up_chars,
|
||||
)
|
||||
self.dump_up_char()
|
||||
logger.info(f"成功获取{self.game_name_cn}当前up信息...当前up池: {title}")
|
||||
break
|
||||
|
||||
async def _reload_pool(self) -> Optional[Message]:
|
||||
await self.update_up_char()
|
||||
self.load_up_char()
|
||||
if self.UP_EVENT:
|
||||
return f"重载成功!\n当前UP池子:{self.UP_EVENT.title}" + image(
|
||||
self.UP_EVENT.pool_img
|
||||
)
|
||||
@@ -1,204 +0,0 @@
|
||||
from typing import Any
|
||||
from .config import DRAW_DATA_PATH
|
||||
from .util import is_number
|
||||
from nonebot.log import logger
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
|
||||
def init_game_pool(game: str, data: dict, operator: Any):
|
||||
tmp_lst = []
|
||||
if game == "prts":
|
||||
for key in data.keys():
|
||||
limited = False
|
||||
recruit_only = False
|
||||
event_only = False
|
||||
if "限定寻访" in data[key]["获取途径"]:
|
||||
limited = True
|
||||
if "干员寻访" not in data[key]["获取途径"] and "公开招募" in data[key]["获取途径"]:
|
||||
recruit_only = True
|
||||
if "活动获取" in data[key]["获取途径"]:
|
||||
event_only = True
|
||||
if "干员寻访" not in data[key]["获取途径"] and "凭证交易所" == data[key]["获取途径"][0]:
|
||||
limited = True
|
||||
if "干员寻访" not in data[key]["获取途径"] and "信用累计奖励" == data[key]["获取途径"][0]:
|
||||
limited = True
|
||||
if key.find("阿米娅") != -1:
|
||||
continue
|
||||
try:
|
||||
tmp_lst.append(
|
||||
operator(
|
||||
name=key,
|
||||
star=int(data[key]["星级"]),
|
||||
limited=limited,
|
||||
recruit_only=recruit_only,
|
||||
event_only=event_only,
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"明日方舟导入角色 {key} 数据错误:{type(e)}:{e}")
|
||||
if game == "genshin":
|
||||
for key in data.keys():
|
||||
if key.find("旅行者") != -1:
|
||||
continue
|
||||
limited = False
|
||||
if data[key]["常驻/限定"] == "限定UP":
|
||||
limited = True
|
||||
try:
|
||||
tmp_lst.append(
|
||||
operator(name=key, star=int(data[key]["稀有度"][:1]), limited=limited)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"原神导入角色 {key} 数据错误:{type(e)}:{e}")
|
||||
if game == "genshin_arms":
|
||||
for key in data.keys():
|
||||
if data[key]["获取途径"].find("祈愿") != -1:
|
||||
limited = False
|
||||
if data[key]["获取途径"].find("限定祈愿") != -1:
|
||||
limited = True
|
||||
try:
|
||||
tmp_lst.append(
|
||||
operator(
|
||||
name=key, star=int(data[key]["稀有度"][:1]), limited=limited
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"原神导入武器 {key} 数据错误:{type(e)}:{e}")
|
||||
if game == "pretty":
|
||||
for key in data.keys():
|
||||
try:
|
||||
tmp_lst.append(
|
||||
operator(name=key, star=data[key]["初始星级"], limited=False)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"赛马娘导入角色 {key} 数据错误:{type(e)}:{e}")
|
||||
if game == "pretty_card":
|
||||
for key in data.keys():
|
||||
limited = False
|
||||
if "卡池" not in data[key]["获取方式"]:
|
||||
limited = True
|
||||
if not data[key]["获取方式"]:
|
||||
limited = False
|
||||
try:
|
||||
tmp_lst.append(
|
||||
operator(
|
||||
name=data[key]["中文名"],
|
||||
star=len(data[key]["稀有度"]),
|
||||
limited=limited,
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"赛马娘导入卡片 {key} 数据错误:{type(e)}:{e}")
|
||||
if game in ["guardian", "guardian_arms"]:
|
||||
for key in data.keys():
|
||||
tmp_lst.append(
|
||||
operator(name=data[key]["名称"], star=int(data[key]["星级"]), limited=False)
|
||||
)
|
||||
if game == "pcr":
|
||||
for key in data.keys():
|
||||
limited = False
|
||||
if key.find("(") != -1:
|
||||
limited = True
|
||||
try:
|
||||
tmp_lst.append(
|
||||
operator(
|
||||
name=data[key]["名称"], star=int(data[key]["星级"]), limited=limited
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"公主连接导入角色 {key} 数据错误:{type(e)}:{e}")
|
||||
if game == "azur":
|
||||
for key in data.keys():
|
||||
if is_number(data[key]["星级"]):
|
||||
limited = False
|
||||
if "可以建造" not in data[key]["获取途径"]:
|
||||
limited = True
|
||||
try:
|
||||
tmp_lst.append(
|
||||
operator(
|
||||
name=data[key]["名称"],
|
||||
star=int(data[key]["星级"]),
|
||||
limited=limited,
|
||||
type_=data[key]["类型"],
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"碧蓝航线导入角色 {key} 数据错误:{type(e)}:{e}")
|
||||
if game in ["fgo", "fgo_card"]:
|
||||
for key in data.keys():
|
||||
limited = False
|
||||
try:
|
||||
if (
|
||||
"圣晶石召唤" not in data[key]["入手方式"]
|
||||
and "圣晶石召唤(Story卡池)" not in data[key]["入手方式"]
|
||||
):
|
||||
limited = True
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
tmp_lst.append(
|
||||
operator(
|
||||
name=data[key]["名称"], star=int(data[key]["星级"]), limited=limited
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"FGO导入角色 {key} 数据错误:{type(e)}:{e}")
|
||||
if game == "onmyoji":
|
||||
for key in data.keys():
|
||||
limited = False
|
||||
if key in [
|
||||
"奴良陆生",
|
||||
"卖药郎",
|
||||
"鬼灯",
|
||||
"阿香",
|
||||
"蜜桃&芥子",
|
||||
"犬夜叉",
|
||||
"杀生丸",
|
||||
"桔梗",
|
||||
"朽木露琪亚",
|
||||
"黑崎一护",
|
||||
"灶门祢豆子",
|
||||
"灶门炭治郎",
|
||||
]:
|
||||
limited = True
|
||||
try:
|
||||
tmp_lst.append(
|
||||
operator(
|
||||
name=data[key]["名称"], star=data[key]["星级"], limited=limited
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"阴阳师导入角色 {key} 数据错误:{type(e)}:{e}")
|
||||
# print(tmp_lst)
|
||||
char_name_lst = [x.name for x in tmp_lst]
|
||||
up_char_file = (
|
||||
DRAW_DATA_PATH
|
||||
/ f"draw_card"
|
||||
/ "draw_card_up"
|
||||
/ f"{game.split('_')[0]}_up_char.json"
|
||||
)
|
||||
if up_char_file.exists():
|
||||
data = json.load(open(up_char_file, "r", encoding="utf8"))
|
||||
if len(game.split("_")) == 1:
|
||||
key = "char"
|
||||
else:
|
||||
key = list(data.keys())[1]
|
||||
for x in data[key]["up_char"]:
|
||||
for char in data[key]["up_char"][x]:
|
||||
if char not in char_name_lst:
|
||||
if game.find("prts") != -1:
|
||||
tmp_lst.append(
|
||||
operator(
|
||||
name=char,
|
||||
star=int(x),
|
||||
recruit_only=False,
|
||||
event_only=False,
|
||||
limited=False,
|
||||
)
|
||||
)
|
||||
else:
|
||||
tmp_lst.append(operator(name=char, star=int(x), limited=False))
|
||||
return tmp_lst
|
||||
@@ -1,81 +0,0 @@
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
import random
|
||||
from .update_game_requests_info import update_requests_info
|
||||
from .util import generate_img, init_star_rst, BaseData, set_list, get_star, max_card
|
||||
from .config import DRAW_DATA_PATH, draw_config
|
||||
from dataclasses import dataclass
|
||||
from .init_card_pool import init_game_pool
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
ALL_CHAR = []
|
||||
|
||||
|
||||
@dataclass
|
||||
class OnmyojiChar(BaseData):
|
||||
pass
|
||||
|
||||
|
||||
async def onmyoji_draw(count: int):
|
||||
# 0 1 2
|
||||
cnlist = ['SP', 'SSR', 'SR', 'R']
|
||||
obj_list, obj_dict, star_list, rst = format_card_information(count)
|
||||
rst = init_star_rst(star_list, cnlist, [], []) + rst
|
||||
if count > 90:
|
||||
obj_list = set_list(obj_list)
|
||||
return MessageSegment.image("base64://" + await generate_img(obj_list, 'onmyoji', star_list)) \
|
||||
+ '\n' + rst[:-1] + '\n' + max_card(obj_dict)
|
||||
|
||||
|
||||
async def update_onmyoji_info():
|
||||
global ALL_CHAR
|
||||
data, code = await update_requests_info('onmyoji')
|
||||
if code == 200:
|
||||
ALL_CHAR = init_game_pool('onmyoji', data, OnmyojiChar)
|
||||
|
||||
|
||||
async def init_onmyoji_data():
|
||||
global ALL_CHAR
|
||||
if draw_config.ONMYOJI_FLAG:
|
||||
with (DRAW_DATA_PATH / 'onmyoji.json').open('r', encoding='utf8') as f:
|
||||
azur_dict = json.load(f)
|
||||
ALL_CHAR = init_game_pool('onmyoji', azur_dict, OnmyojiChar)
|
||||
|
||||
|
||||
onmyoji_star = {
|
||||
5: 'SP',
|
||||
4: 'SSR',
|
||||
3: 'SR',
|
||||
2: 'R',
|
||||
}
|
||||
|
||||
|
||||
# 抽取卡池
|
||||
def _get_onmyoji_card():
|
||||
global ALL_CHAR
|
||||
onmyoji_config = draw_config.onmyoji
|
||||
star = get_star([5, 4, 3, 2], [onmyoji_config.ONMYOJI_SP, onmyoji_config.ONMYOJI_SSR, onmyoji_config.ONMYOJI_SR, onmyoji_config.ONMYOJI_R])
|
||||
chars = [x for x in ALL_CHAR if x.star == onmyoji_star[star] and not x.limited]
|
||||
return random.choice(chars), 5 - star
|
||||
|
||||
|
||||
def format_card_information(count: int):
|
||||
star_list = [0, 0, 0, 0]
|
||||
obj_list = [] # 获取所有角色
|
||||
obj_dict = {} # 获取角色次数字典
|
||||
rst = ''
|
||||
for i in range(count):
|
||||
obj, code = _get_onmyoji_card()
|
||||
star_list[code] += 1
|
||||
if code == 0:
|
||||
rst += f'第 {i+1} 抽获取SP {obj.name}\n'
|
||||
elif code == 1:
|
||||
rst += f'第 {i+1} 抽获取SSR {obj.name}\n'
|
||||
try:
|
||||
obj_dict[obj.name] += 1
|
||||
except KeyError:
|
||||
obj_dict[obj.name] = 1
|
||||
obj_list.append(obj)
|
||||
return obj_list, obj_dict, star_list, rst
|
||||
@@ -1,93 +0,0 @@
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
import random
|
||||
from .update_game_info import update_info
|
||||
from .update_game_simple_info import update_simple_info
|
||||
from .util import generate_img, init_star_rst, BaseData, set_list, get_star, max_card
|
||||
from .config import DRAW_DATA_PATH, draw_config
|
||||
from dataclasses import dataclass
|
||||
from .init_card_pool import init_game_pool
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
ALL_CHAR = []
|
||||
|
||||
|
||||
@dataclass
|
||||
class PcrChar(BaseData):
|
||||
pass
|
||||
|
||||
|
||||
async def pcr_draw(count: int):
|
||||
# 0 1 2
|
||||
cnlist = ['★★★', '★★', '★']
|
||||
char_list, three_list, three_index_list, char_dict, star_list = _format_card_information(count)
|
||||
rst = init_star_rst(star_list, cnlist, three_list, three_index_list)
|
||||
if count > 90:
|
||||
char_list = set_list(char_list)
|
||||
return MessageSegment.image("base64://" + await generate_img(char_list, 'pcr', star_list)) \
|
||||
+ '\n' + rst[:-1] + '\n' + max_card(char_dict)
|
||||
|
||||
|
||||
async def update_pcr_info():
|
||||
global ALL_CHAR
|
||||
if draw_config.PCR_TAI:
|
||||
url = 'https://wiki.biligame.com/pcr/角色图鉴'
|
||||
data, code = await update_simple_info(url, 'pcr')
|
||||
else:
|
||||
url = 'https://wiki.biligame.com/pcr/角色筛选表'
|
||||
data, code = await update_info(url, 'pcr')
|
||||
if code == 200:
|
||||
ALL_CHAR = init_game_pool('pcr', data, PcrChar)
|
||||
|
||||
|
||||
async def init_pcr_data():
|
||||
global ALL_CHAR
|
||||
if draw_config.PCR_FLAG:
|
||||
with (DRAW_DATA_PATH / 'pcr.json').open('r', encoding='utf8') as f:
|
||||
pcr_dict = json.load(f)
|
||||
ALL_CHAR = init_game_pool('pcr', pcr_dict, PcrChar)
|
||||
|
||||
|
||||
# 抽取卡池
|
||||
def _get_pcr_card(mode: int = 1):
|
||||
global ALL_CHAR
|
||||
pcr_config = draw_config.pcr
|
||||
if mode == 2:
|
||||
star = get_star([3, 2], [pcr_config.PCR_G_THREE_P, pcr_config.PCR_G_TWO_P])
|
||||
else:
|
||||
star = get_star([3, 2, 1], [pcr_config.PCR_THREE_P, pcr_config.PCR_TWO_P, pcr_config.PCR_ONE_P])
|
||||
chars = [x for x in ALL_CHAR if x.star == star and not x.limited]
|
||||
return random.choice(chars), 3 - star
|
||||
|
||||
|
||||
def _format_card_information(_count: int):
|
||||
char_list = []
|
||||
star_list = [0, 0, 0]
|
||||
three_index_list = []
|
||||
three_list = []
|
||||
char_dict = {}
|
||||
# 保底计算
|
||||
count = 0
|
||||
for i in range(_count):
|
||||
count += 1
|
||||
# 十连保底
|
||||
if count == 10:
|
||||
char, code = _get_pcr_card(2)
|
||||
count = 0
|
||||
else:
|
||||
char, code = _get_pcr_card()
|
||||
if code < 2:
|
||||
count = 0
|
||||
star_list[code] += 1
|
||||
if code == 0:
|
||||
three_list.append(char.name)
|
||||
three_index_list.append(i)
|
||||
try:
|
||||
char_dict[char.name] += 1
|
||||
except KeyError:
|
||||
char_dict[char.name] = 1
|
||||
char_list.append(char)
|
||||
return char_list, three_list, three_index_list, char_dict, star_list
|
||||
@@ -1,148 +0,0 @@
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from .announcement import PrettyAnnouncement
|
||||
from .update_game_info import update_info
|
||||
from .util import init_star_rst, generate_img, max_card, BaseData, \
|
||||
set_list, get_star, format_card_information, init_up_char
|
||||
import random
|
||||
from .config import DRAW_DATA_PATH, draw_config
|
||||
from dataclasses import dataclass
|
||||
from .init_card_pool import init_game_pool
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
|
||||
announcement = PrettyAnnouncement()
|
||||
|
||||
ALL_CHAR = []
|
||||
ALL_CARD = []
|
||||
|
||||
_CURRENT_CHAR_POOL_TITLE = ""
|
||||
_CURRENT_CARD_POOL_TITLE = ""
|
||||
UP_CHAR = []
|
||||
UP_CARD = []
|
||||
POOL_IMG = []
|
||||
|
||||
|
||||
@dataclass
|
||||
class PrettyChar(BaseData):
|
||||
pass
|
||||
|
||||
|
||||
async def pretty_draw(count: int, pool_name):
|
||||
if pool_name == 'card':
|
||||
cnlist = ['SSR', 'SR', 'R']
|
||||
else:
|
||||
cnlist = ['★★★', '★★', '★']
|
||||
star_list = [0, 0, 0]
|
||||
obj_list, obj_dict, three_list, star_list, three_olist = format_card_information(count, star_list,
|
||||
_get_pretty_card, pool_name)
|
||||
up_type = []
|
||||
up_list = []
|
||||
title = ''
|
||||
if pool_name == 'char' and _CURRENT_CHAR_POOL_TITLE:
|
||||
up_type = UP_CHAR
|
||||
title = _CURRENT_CHAR_POOL_TITLE
|
||||
elif pool_name == 'card' and _CURRENT_CARD_POOL_TITLE:
|
||||
up_type = UP_CARD
|
||||
title = _CURRENT_CARD_POOL_TITLE
|
||||
tmp = ''
|
||||
if up_type:
|
||||
for x in up_type:
|
||||
for operator in x.operators:
|
||||
up_list.append(operator.split(']')[1] if pool_name == 'char' else operator)
|
||||
if x.star == 3:
|
||||
if pool_name == 'char':
|
||||
tmp += f'三星UP:{" ".join(x.operators)} \n'
|
||||
else:
|
||||
tmp += f'SSR UP:{" ".join(x.operators)} \n'
|
||||
elif x.star == 2:
|
||||
if pool_name == 'char':
|
||||
tmp += f'二星UP:{" ".join(x.operators)} \n'
|
||||
else:
|
||||
tmp += f'SR UP:{" ".join(x.operators)} \n'
|
||||
elif x.star == 1:
|
||||
if pool_name == 'char':
|
||||
tmp += f'一星UP:{" ".join(x.operators)} '
|
||||
else:
|
||||
tmp += f'R UP:{" ".join(x.operators)} '
|
||||
tmp = tmp[:-1] if tmp and tmp[-1] == '\n' else tmp
|
||||
pool_info = f'当前up池:{title}\n{tmp}' if title else ''
|
||||
rst = init_star_rst(star_list, cnlist, three_list, three_olist, up_list)
|
||||
if count > 90:
|
||||
obj_list = set_list(obj_list)
|
||||
return pool_info + MessageSegment.image(
|
||||
"base64://" + await generate_img(obj_list, 'pretty', star_list)) \
|
||||
+ '\n' + rst[:-1] + '\n' + max_card(obj_dict)
|
||||
|
||||
|
||||
async def update_pretty_info():
|
||||
global ALL_CHAR, ALL_CARD
|
||||
url = 'https://wiki.biligame.com/umamusume/赛马娘图鉴'
|
||||
data, code = await update_info(url, 'pretty')
|
||||
if code == 200:
|
||||
ALL_CHAR = init_game_pool('pretty', data, PrettyChar)
|
||||
url = 'https://wiki.biligame.com/umamusume/支援卡图鉴'
|
||||
data, code = await update_info(url, 'pretty_card')
|
||||
if code == 200:
|
||||
ALL_CARD = init_game_pool('pretty_card', data, PrettyChar)
|
||||
await _pretty_init_up_char()
|
||||
|
||||
|
||||
async def init_pretty_data():
|
||||
global ALL_CHAR, ALL_CARD
|
||||
if draw_config.PRETTY_FLAG:
|
||||
with (DRAW_DATA_PATH / 'pretty.json').open('r', encoding='utf8') as f:
|
||||
pretty_char_dict = json.load(f)
|
||||
with (DRAW_DATA_PATH / 'pretty_card.json').open('r', encoding='utf8') as f:
|
||||
pretty_card_dict = json.load(f)
|
||||
ALL_CHAR = init_game_pool('pretty', pretty_char_dict, PrettyChar)
|
||||
ALL_CARD = init_game_pool('pretty_card', pretty_card_dict, PrettyChar)
|
||||
await _pretty_init_up_char()
|
||||
|
||||
|
||||
# 抽取卡池
|
||||
def _get_pretty_card(pool_name: str, mode: int = 1):
|
||||
global ALL_CHAR, ALL_CARD, _CURRENT_CHAR_POOL_TITLE, _CURRENT_CARD_POOL_TITLE
|
||||
pretty_config = draw_config.pretty
|
||||
if mode == 1:
|
||||
star = get_star([3, 2, 1], [pretty_config.PRETTY_THREE_P, pretty_config.PRETTY_TWO_P, pretty_config.PRETTY_ONE_P])
|
||||
else:
|
||||
star = get_star([3, 2], [pretty_config.PRETTY_THREE_P, pretty_config.PRETTY_TWO_P])
|
||||
if pool_name == 'card':
|
||||
title = _CURRENT_CARD_POOL_TITLE
|
||||
up_data = UP_CARD
|
||||
data = ALL_CARD
|
||||
else:
|
||||
title = _CURRENT_CHAR_POOL_TITLE
|
||||
up_data = UP_CHAR
|
||||
data = ALL_CHAR
|
||||
# 有UP池子
|
||||
if title and star in [x.star for x in up_data]:
|
||||
all_char_lst = [x for x in data if x.star == star and not x.limited]
|
||||
# 抽到UP
|
||||
if random.random() < 1 / len(all_char_lst) * (0.7 / 0.1385):
|
||||
all_up_star = [x.operators for x in up_data if x.star == star][0]
|
||||
acquire_operator = random.choice(all_up_star)
|
||||
if pool_name == 'char':
|
||||
acquire_operator = acquire_operator.split(']')[1]
|
||||
acquire_operator = [x for x in data if x.name == acquire_operator][0]
|
||||
else:
|
||||
acquire_operator = random.choice([x for x in data if x.star == star and not x.limited])
|
||||
else:
|
||||
acquire_operator = random.choice([x for x in data if x.star == star and not x.limited])
|
||||
return acquire_operator, 3 - star
|
||||
|
||||
|
||||
# 获取up和概率
|
||||
async def _pretty_init_up_char():
|
||||
global _CURRENT_CHAR_POOL_TITLE, _CURRENT_CARD_POOL_TITLE, UP_CHAR, UP_CARD, POOL_IMG
|
||||
_CURRENT_CHAR_POOL_TITLE, _CURRENT_CARD_POOL_TITLE, POOL_IMG, UP_CHAR, UP_CARD = await init_up_char(announcement)
|
||||
|
||||
|
||||
async def reload_pretty_pool():
|
||||
await _pretty_init_up_char()
|
||||
return f'当前UP池子:{_CURRENT_CHAR_POOL_TITLE} & {_CURRENT_CARD_POOL_TITLE} {POOL_IMG}'
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment, Message
|
||||
import random
|
||||
from .config import DRAW_DATA_PATH, draw_config
|
||||
from .update_game_info import update_info
|
||||
from .util import generate_img, init_star_rst, max_card, BaseData, UpEvent, set_list, get_star
|
||||
from .init_card_pool import init_game_pool
|
||||
from .announcement import PrtsAnnouncement
|
||||
from dataclasses import dataclass
|
||||
from nonebot.log import logger
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
|
||||
announcement = PrtsAnnouncement()
|
||||
|
||||
prts_dict = {}
|
||||
UP_OPERATOR = []
|
||||
ALL_OPERATOR = []
|
||||
_CURRENT_POOL_TITLE = ''
|
||||
POOL_IMG = ''
|
||||
|
||||
|
||||
@dataclass
|
||||
class Operator(BaseData):
|
||||
recruit_only: bool # 公招限定
|
||||
event_only: bool # 活动获得干员
|
||||
# special_only: bool # 升变/异格干员
|
||||
|
||||
|
||||
async def prts_draw(count: int = 300):
|
||||
cnlist = ['★★★★★★', '★★★★★', '★★★★', '★★★']
|
||||
star_list = [0, 0, 0, 0]
|
||||
operator_list, operator_dict, six_list, star_list, six_index_list = format_card_information(count, star_list)
|
||||
up_list = []
|
||||
tmp = ''
|
||||
if _CURRENT_POOL_TITLE:
|
||||
for x in UP_OPERATOR:
|
||||
for operator in x.operators:
|
||||
up_list.append(operator)
|
||||
if x.star == 6:
|
||||
tmp += f'六星UP:{" ".join(x.operators)} \n'
|
||||
elif x.star == 5:
|
||||
tmp += f'五星UP:{" ".join(x.operators)} \n'
|
||||
elif x.star == 4:
|
||||
tmp += f'四星UP:{" ".join(x.operators)}'
|
||||
rst = init_star_rst(star_list, cnlist, six_list, six_index_list, up_list)
|
||||
if count > 90:
|
||||
operator_list = set_list(operator_list)
|
||||
pool_info = f"当前up池: {_CURRENT_POOL_TITLE}\n{tmp}" if _CURRENT_POOL_TITLE else ""
|
||||
return pool_info + MessageSegment.image(
|
||||
"base64://" + await generate_img(operator_list, 'prts', star_list)) \
|
||||
+ '\n' + rst[:-1] + '\n' + max_card(operator_dict)
|
||||
|
||||
|
||||
async def update_prts_info():
|
||||
global prts_dict, ALL_OPERATOR
|
||||
url = 'https://wiki.biligame.com/arknights/干员数据表'
|
||||
data, code = await update_info(url, 'prts', ['头像', '名称', '阵营', '星级', '性别', '是否感染', '获取途径', '初始生命', '初始防御',
|
||||
'初始法抗', '再部署', '部署费用', '阻挡数', '攻击速度', '标签'])
|
||||
if code == 200:
|
||||
prts_dict = data
|
||||
ALL_OPERATOR = init_game_pool('prts', prts_dict, Operator)
|
||||
await _init_up_char()
|
||||
|
||||
|
||||
async def init_prts_data():
|
||||
global prts_dict, ALL_OPERATOR
|
||||
if draw_config.PRTS_FLAG:
|
||||
with (DRAW_DATA_PATH / 'prts.json').open('r', encoding='utf8') as f:
|
||||
prts_dict = json.load(f)
|
||||
ALL_OPERATOR = init_game_pool('prts', prts_dict, Operator)
|
||||
await _init_up_char()
|
||||
|
||||
|
||||
# 抽取干员
|
||||
def _get_operator_card(add: float):
|
||||
prts_config = draw_config.prts
|
||||
star = get_star([6, 5, 4, 3], [prts_config.PRTS_SIX_P + add, prts_config.PRTS_FIVE_P, prts_config.PRTS_FOUR_P, prts_config.PRTS_THREE_P])
|
||||
if _CURRENT_POOL_TITLE:
|
||||
zooms = [x.zoom for x in UP_OPERATOR if x.star == star]
|
||||
zoom = 0
|
||||
weight = 0
|
||||
# 分配概率和权重
|
||||
for z in zooms:
|
||||
if z < 1:
|
||||
zoom = z
|
||||
else:
|
||||
weight = z
|
||||
up_operator_name = ""
|
||||
# UPs
|
||||
try:
|
||||
if 0 < zoom:
|
||||
up_operators = [x.operators for x in UP_OPERATOR if x.star == star and x.zoom < 1][0]
|
||||
up_operator_name = random.choice(up_operators)
|
||||
acquire_operator = [x for x in ALL_OPERATOR if x.name == up_operator_name][0]
|
||||
else:
|
||||
all_star_operators = [x for x in ALL_OPERATOR if x.star == star
|
||||
and not any([x.limited, x.event_only, x.recruit_only])]
|
||||
weight_up_operators = [x.operators for x in UP_OPERATOR if x.star == star and x.zoom > 1]
|
||||
# 权重
|
||||
if weight_up_operators and random.random() < 1.0 / float(len(all_star_operators)) * weight:
|
||||
up_operator_name = random.choice(weight_up_operators[0])
|
||||
acquire_operator = [x for x in ALL_OPERATOR if x.name == up_operator_name][0]
|
||||
else:
|
||||
acquire_operator = random.choice(all_star_operators)
|
||||
except IndexError:
|
||||
acquire_operator = Operator(up_operator_name, star, True, False, False)
|
||||
else:
|
||||
acquire_operator = random.choice([x for x in ALL_OPERATOR if x.star == star
|
||||
and not any([x.limited, x.event_only, x.recruit_only])])
|
||||
return acquire_operator, 6 - star
|
||||
|
||||
|
||||
# 整理数据
|
||||
def format_card_information(count: int, star_list: list):
|
||||
max_star_lst = [] # 获取的最高星级角色列表
|
||||
max_index_lst = [] # 获取最高星级角色的次数
|
||||
obj_list = [] # 获取所有角色
|
||||
obj_dict = {} # 获取角色次数字典
|
||||
add = 0.0
|
||||
count_idx = 0
|
||||
for i in range(count):
|
||||
count_idx += 1
|
||||
obj, code = _get_operator_card(add)
|
||||
star_list[code] += 1
|
||||
if code == 0:
|
||||
max_star_lst.append(obj.name)
|
||||
max_index_lst.append(i)
|
||||
add = 0.0
|
||||
count_idx = 0
|
||||
elif count_idx > 50:
|
||||
add += 0.02
|
||||
try:
|
||||
obj_dict[obj.name] += 1
|
||||
except KeyError:
|
||||
obj_dict[obj.name] = 1
|
||||
obj_list.append(obj)
|
||||
return obj_list, obj_dict, max_star_lst, star_list, max_index_lst
|
||||
|
||||
|
||||
# 获取up干员和概率
|
||||
async def _init_up_char():
|
||||
global _CURRENT_POOL_TITLE, POOL_IMG, UP_OPERATOR
|
||||
UP_OPERATOR = []
|
||||
up_char_dict = await announcement.update_up_char()
|
||||
_CURRENT_POOL_TITLE = up_char_dict['char']['title']
|
||||
if _CURRENT_POOL_TITLE:
|
||||
POOL_IMG = MessageSegment.image(up_char_dict['char']['pool_img'])
|
||||
up_char_dict = up_char_dict['char']['up_char']
|
||||
logger.info(f'成功获取明日方舟当前up信息...当前up池: {_CURRENT_POOL_TITLE}')
|
||||
average_dict = {'6': {}, '5': {}, '4': {}}
|
||||
for star in up_char_dict.keys():
|
||||
for key in up_char_dict[star].keys():
|
||||
if average_dict[star].get(up_char_dict[star][key]):
|
||||
average_dict[star][up_char_dict[star][key]].append(key)
|
||||
else:
|
||||
average_dict[star][up_char_dict[star][key]] = [key]
|
||||
for star in average_dict.keys():
|
||||
for str_zoom in average_dict[star].keys():
|
||||
if str_zoom[0] == '权':
|
||||
zoom = float(str_zoom[1:])
|
||||
else:
|
||||
zoom = float(str_zoom) / 100
|
||||
UP_OPERATOR.append(UpEvent(star=int(star), operators=average_dict[star][str_zoom], zoom=zoom))
|
||||
|
||||
|
||||
async def reload_prts_pool():
|
||||
await _init_up_char()
|
||||
return Message(f'当前UP池:{_CURRENT_POOL_TITLE} {POOL_IMG}')
|
||||
Executable → Regular
-29
@@ -1,29 +0,0 @@
|
||||
from nonebot.rule import Rule
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent
|
||||
from nonebot.typing import T_State
|
||||
from .config import draw_config
|
||||
|
||||
|
||||
def is_switch(game_name: str) -> Rule:
|
||||
|
||||
async def _is_switch(bot: Bot, event: MessageEvent, state: T_State) -> bool:
|
||||
if game_name == 'prts':
|
||||
return draw_config.PRTS_FLAG
|
||||
if game_name == 'genshin':
|
||||
return draw_config.GENSHIN_FLAG
|
||||
if game_name == 'pretty':
|
||||
return draw_config.PRETTY_FLAG
|
||||
if game_name == 'guardian':
|
||||
return draw_config.GUARDIAN_FLAG
|
||||
if game_name == 'pcr':
|
||||
return draw_config.PCR_FLAG
|
||||
if game_name == 'azur':
|
||||
return draw_config.AZUR_FLAG
|
||||
if game_name == 'fgo':
|
||||
return draw_config.FGO_FLAG
|
||||
if game_name == 'onmyoji':
|
||||
return draw_config.ONMYOJI_FLAG
|
||||
else:
|
||||
return False
|
||||
|
||||
return Rule(_is_switch)
|
||||
|
||||
@@ -1,295 +0,0 @@
|
||||
from typing import Tuple
|
||||
from .config import DRAW_DATA_PATH
|
||||
from asyncio.exceptions import TimeoutError
|
||||
from bs4 import BeautifulSoup
|
||||
from .util import download_img
|
||||
from urllib.parse import unquote
|
||||
from .util import remove_prohibited_str
|
||||
from utils.http_utils import AsyncHttpx
|
||||
from nonebot.log import logger
|
||||
import bs4
|
||||
import re
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
|
||||
headers = {'User-Agent': '"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)"'}
|
||||
|
||||
|
||||
async def update_info(url: str, game_name: str, info_list: list = None) -> Tuple[dict, int]:
|
||||
info_path = DRAW_DATA_PATH / f"{game_name}.json"
|
||||
try:
|
||||
with info_path.open('r', encoding='utf8') as f:
|
||||
data = json.load(f)
|
||||
except (ValueError, FileNotFoundError):
|
||||
data = {}
|
||||
try:
|
||||
response = await AsyncHttpx.get(url, timeout=7)
|
||||
soup = BeautifulSoup(response.text, 'lxml')
|
||||
_tbody = get_tbody(soup, game_name, url)
|
||||
trs = _tbody.find_all('tr')
|
||||
att_dict, start_index, index = init_attr(game_name)
|
||||
if game_name == 'guardian':
|
||||
start_index = 1
|
||||
if game_name == 'azur':
|
||||
start_index = 0
|
||||
for th in trs[0].find_all('th')[start_index:]:
|
||||
text = th.text
|
||||
if text[-1] == '\n':
|
||||
text = text[:-1]
|
||||
att_dict[text] = index
|
||||
index += 1
|
||||
for tr in trs[1:]:
|
||||
member_dict = {}
|
||||
tds = tr.find_all('td')
|
||||
if not info_list:
|
||||
info_list = att_dict.keys()
|
||||
for key in info_list:
|
||||
key, attr = parse_key(key, game_name)
|
||||
td = tds[att_dict[key]]
|
||||
last_tag = unquote(_find_last_tag(td, attr, game_name), 'utf-8')
|
||||
member_dict[key] = last_tag
|
||||
member_dict = intermediate_check(member_dict, key, game_name, td)
|
||||
avatar_img = await _modify_avatar_url(game_name, member_dict["名称"])
|
||||
member_dict['头像'] = avatar_img if avatar_img else member_dict['头像']
|
||||
member_dict, name = replace_update_name(member_dict, game_name)
|
||||
await download_img(member_dict['头像'], game_name, name)
|
||||
data[name] = member_dict
|
||||
logger.info(f'{name} is update...')
|
||||
data = await _last_check(data, game_name)
|
||||
except TimeoutError:
|
||||
logger.warning(f'更新 {game_name} 超时...')
|
||||
return {}, 999
|
||||
with info_path.open('w', encoding='utf8') as wf:
|
||||
wf.write(json.dumps(data, ensure_ascii=False, indent=4))
|
||||
return data, 200
|
||||
|
||||
|
||||
def _find_last_tag(element: bs4.element.Tag, attr: str, game_name: str) -> str:
|
||||
last_tag = []
|
||||
for des in element.descendants:
|
||||
last_tag.append(des)
|
||||
if len(last_tag) == 1 and last_tag[0] == '\n':
|
||||
last_tag = ''
|
||||
elif last_tag[-1] == '\n':
|
||||
last_tag = last_tag[-2]
|
||||
else:
|
||||
last_tag = last_tag[-1]
|
||||
if attr and str(last_tag):
|
||||
last_tag = last_tag[attr]
|
||||
elif str(last_tag).find('<img') != -1:
|
||||
if last_tag.get('srcset'):
|
||||
last_tag = str(last_tag.get('srcset')).strip().split(' ')[-2].strip()
|
||||
else:
|
||||
last_tag = last_tag['src']
|
||||
else:
|
||||
last_tag = str(last_tag)
|
||||
if str(last_tag) and str(last_tag)[-1] == '\n':
|
||||
last_tag = str(last_tag)[:-1]
|
||||
|
||||
if game_name not in ['pretty', 'pretty_card', 'guardian'] and last_tag.find('http') == -1:
|
||||
last_tag = last_tag.split('.')[0]
|
||||
|
||||
return last_tag
|
||||
|
||||
|
||||
# 获取大图(小图快爬)
|
||||
async def _modify_avatar_url(game_name: str, char_name: str):
|
||||
if game_name == 'prts':
|
||||
res = await AsyncHttpx.get(f'https://wiki.biligame.com/arknights/{char_name}', timeout=7)
|
||||
soup = BeautifulSoup(res.text, 'lxml')
|
||||
try:
|
||||
img_url = str(soup.find('img', {'class': 'img-bg'})['srcset']).split(' ')[-2]
|
||||
except KeyError:
|
||||
img_url = str(soup.find('img', {'class': 'img-bg'})['src'])
|
||||
return img_url
|
||||
if game_name == 'genshin':
|
||||
return None
|
||||
if game_name == 'pretty_card':
|
||||
res = await AsyncHttpx.get(f'https://wiki.biligame.com/umamusume/{char_name}', timeout=7)
|
||||
soup = BeautifulSoup(res.text, 'lxml')
|
||||
try:
|
||||
img_url = soup.find('div', {'class': 'support_card-left'}).find('div').find('img').get('src')
|
||||
return img_url
|
||||
except AttributeError:
|
||||
logger.warning("pretty_card 获取大图像失败")
|
||||
return None
|
||||
if game_name == 'guardian':
|
||||
# 未上传图片太多,换成像素图
|
||||
# async with session.get(f'https://wiki.biligame.com/gt/{char_name}', timeout=7) as res:
|
||||
# soup = BeautifulSoup(await res.text(), 'lxml')
|
||||
# soup = soup.find('table', {'class': 'wikitable'}).find('tbody').find('tr')
|
||||
# try:
|
||||
# img_url = str(soup.find('img', {'class': 'img-kk'})['srcset']).split(' ')[-2]
|
||||
# except KeyError:
|
||||
# img_url = str(soup.find('img', {'class': 'img-kk'})['src'])
|
||||
# except TypeError:
|
||||
# print(f'{char_name} 图片还未上传,跳过...')
|
||||
# img_url = ''
|
||||
# return img_url
|
||||
return None
|
||||
|
||||
|
||||
# 数据最后处理(是否需要额外数据或处理数据)
|
||||
async def _last_check(data: dict, game_name: str):
|
||||
# if game_name == 'prts':
|
||||
# tasks = []
|
||||
# for key in data.keys():
|
||||
# tasks.append(asyncio.ensure_future(_async_update_prts_extra_info(key, session)))
|
||||
# asyResult = await asyncio.gather(*tasks)
|
||||
# for x in asyResult:
|
||||
# for key in x.keys():
|
||||
# data[key]['获取途径'] = x[key]['获取途径']
|
||||
if game_name == 'genshin':
|
||||
for key in data.keys():
|
||||
res = await AsyncHttpx.get(f'https://wiki.biligame.com/ys/{key}', timeout=7)
|
||||
soup = BeautifulSoup(res.text, 'lxml')
|
||||
_trs = ''
|
||||
for table in soup.find_all('table', {'class': 'wikitable'}):
|
||||
if str(table).find('常驻/限定') != -1:
|
||||
_trs = table.find('tbody').find_all('tr')
|
||||
break
|
||||
for tr in _trs:
|
||||
data[key]['常驻/限定'] = '未知'
|
||||
if str(tr).find('限定UP') != -1:
|
||||
data[key]['常驻/限定'] = '限定UP'
|
||||
logger.info(f'原神获取额外数据 {key}...{data[key]["常驻/限定"]}')
|
||||
break
|
||||
elif str(tr).find('常驻UP') != -1:
|
||||
data[key]['常驻/限定'] = '常驻UP'
|
||||
logger.info(f'原神获取额外数据 {key}...{data[key]["常驻/限定"]}')
|
||||
break
|
||||
if game_name == 'pretty':
|
||||
for keys in data.keys():
|
||||
for key in data[keys].keys():
|
||||
r = re.search(r'.*?40px-(.*)图标.png', str(data[keys][key]))
|
||||
if r:
|
||||
data[keys][key] = r.group(1)
|
||||
logger.info(f'赛马娘额外修改数据...{keys}[{key}]=> {r.group(1)}')
|
||||
if game_name == 'guardian':
|
||||
for keys in data.keys():
|
||||
for key in data[keys].keys():
|
||||
r = re.search(r'.*?-star_(.*).png', str(data[keys][key]))
|
||||
if r:
|
||||
data[keys][key] = r.group(1)
|
||||
logger.info(f'坎公骑士剑额外修改数据...{keys}[{key}] => {r.group(1)}')
|
||||
return data
|
||||
|
||||
|
||||
# 对抓取每行数据是否需要额外处理?
|
||||
def intermediate_check(member_dict: dict, key: str, game_name: str, td: bs4.element.Tag):
|
||||
if game_name == "genshin_arms":
|
||||
if key == "稀有度":
|
||||
member_dict["稀有度"] = td.find("img")["alt"].split('.')[0]
|
||||
if game_name == 'prts':
|
||||
if key == '获取途径':
|
||||
msg = re.search('<td.*?>([\\s\\S]*)</td>', str(td)).group(1).strip()
|
||||
msg = msg[:-1] if msg and msg[-1] == '\n' else msg
|
||||
if msg.find('<a') != -1:
|
||||
for a in td.find_all('a'):
|
||||
msg = msg.replace(str(a), a.text)
|
||||
member_dict['获取途径'] = msg.split('<br/>')
|
||||
if game_name == 'pretty':
|
||||
if key == '初始星级':
|
||||
member_dict['初始星级'] = len(td.find_all('img'))
|
||||
if game_name == 'pretty_card':
|
||||
if key == '获取方式':
|
||||
obtain = []
|
||||
for x in str(td.text).replace('\n', '').strip().split('、'):
|
||||
if x:
|
||||
obtain.append(x)
|
||||
member_dict['获取方式'] = obtain
|
||||
if game_name == 'guardian':
|
||||
if key == '头像':
|
||||
member_dict['星级'] = str(td.find('span').find('img')['alt'])[-5]
|
||||
try:
|
||||
member_dict['头像'] = str(td.find('img')['srcset']).split(' ')[0]
|
||||
except KeyError:
|
||||
member_dict['头像'] = str(td.find('img')['src'])
|
||||
return member_dict
|
||||
|
||||
|
||||
def init_attr(game_name: str):
|
||||
att_dict = {'头像': 0, '名称': 1}
|
||||
start_index = 2
|
||||
index = 2
|
||||
if game_name == 'guardian':
|
||||
att_dict = {'头像': 0, '名称': 0}
|
||||
start_index = 1
|
||||
index = 1
|
||||
return att_dict, start_index, index
|
||||
|
||||
|
||||
# 解析key
|
||||
def parse_key(key: str, game_name):
|
||||
attr = ''
|
||||
if game_name == 'genshin_arms':
|
||||
if key.find('.') != -1:
|
||||
key = key.split('.')
|
||||
attr = key[-1]
|
||||
key = key[0]
|
||||
return key, attr
|
||||
|
||||
|
||||
# 拿到名称
|
||||
def replace_update_name(member_dict: dict, game_name: str):
|
||||
name = member_dict['名称']
|
||||
if game_name == 'pretty_card':
|
||||
name = member_dict['中文名']
|
||||
name = remove_prohibited_str(name)
|
||||
member_dict['中文名'] = name
|
||||
else:
|
||||
name = remove_prohibited_str(name)
|
||||
member_dict['名称'] = name
|
||||
return member_dict, name
|
||||
|
||||
|
||||
# 拿到tbody,不同游戏tbody可能不同
|
||||
def get_tbody(soup: bs4.BeautifulSoup, game_name: str, url: str):
|
||||
max_count = 0
|
||||
_tbody = None
|
||||
if game_name == 'guardian_arms':
|
||||
if url[-2:] == '盾牌':
|
||||
div = soup.find('div', {'class': 'resp-tabs-container'}).find_all('div', {'class': 'resp-tab-content'})[1]
|
||||
_tbody = div.find('tbody')
|
||||
else:
|
||||
div = soup.find('div', {'class': 'resp-tabs-container'}).find_all('div', {'class': 'resp-tab-content'})[0]
|
||||
_tbody = div.find('table', {'id': 'CardSelectTr'}).find('tbody')
|
||||
else:
|
||||
for tbody in soup.find_all('tbody'):
|
||||
if len(tbody.find_all('tr')) > max_count:
|
||||
_tbody = tbody
|
||||
max_count = len(tbody.find_all('tr'))
|
||||
return _tbody
|
||||
|
||||
|
||||
# async def _async_update_prts_extra_info(key: str, session: aiohttp.ClientSession):
|
||||
# for i in range(10):
|
||||
# try:
|
||||
# async with session.get(f'https://wiki.biligame.com/arknights/{key}', timeout=7) as res:
|
||||
# soup = BeautifulSoup(await res.text(), 'lxml')
|
||||
# obtain = str(soup.find('table', {'class': 'wikitable'}).find('tbody').find_all('td')[-1])
|
||||
# obtain = re.search(r'<td.*?>([\s\S]*)</.*?', obtain).group(1)
|
||||
# obtain = obtain[:-1] if obtain[-1] == '\n' else obtain
|
||||
# if obtain.find('<br/>'):
|
||||
# obtain = obtain.split('<br/>')
|
||||
# elif obtain.find('<br>'):
|
||||
# obtain = obtain.split('<br>')
|
||||
# for i in range(len(obtain)):
|
||||
# if obtain[i].find('<a') != -1:
|
||||
# text = ''
|
||||
# for msg in obtain[i].split('</a>'):
|
||||
# r = re.search('>(.*)', msg)
|
||||
# if r:
|
||||
# text += r.group(1) + ' '
|
||||
# obtain[i] = obtain[i].split('<a')[0] + text[:-1] + obtain[i].split('</a>')[-1]
|
||||
# print(f'明日方舟获取额外信息 {key}...{obtain}')
|
||||
# x = {key: {}}
|
||||
# x[key]['获取途径'] = obtain
|
||||
# return x
|
||||
# except TimeoutError:
|
||||
# print(f'访问 https://wiki.biligame.com/arknights/{key} 第 {i}次 超时...已再次访问')
|
||||
# return {}
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
from .config import DRAW_DATA_PATH, draw_config
|
||||
from asyncio.exceptions import TimeoutError
|
||||
from .util import download_img
|
||||
from bs4 import BeautifulSoup
|
||||
from .util import remove_prohibited_str
|
||||
from utils.http_utils import AsyncHttpx
|
||||
from nonebot.log import logger
|
||||
import asyncio
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
|
||||
headers = {'User-Agent': '"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)"'}
|
||||
|
||||
|
||||
async def update_requests_info(game_name: str):
|
||||
info_path = DRAW_DATA_PATH / f"{game_name}.json"
|
||||
try:
|
||||
with info_path.open('r', encoding='utf8') as f:
|
||||
data = json.load(f)
|
||||
except (ValueError, FileNotFoundError):
|
||||
data = {}
|
||||
try:
|
||||
if game_name in ['fgo', 'fgo_card']:
|
||||
if game_name == 'fgo':
|
||||
url = 'http://fgo.vgtime.com/servant/ajax?card=&wd=&ids=&sort=12777&o=desc&pn='
|
||||
else:
|
||||
url = 'http://fgo.vgtime.com/equipment/ajax?wd=&ids=&sort=12958&o=desc&pn='
|
||||
for i in range(9999):
|
||||
response = await AsyncHttpx.get(f'{url}{i}', timeout=7)
|
||||
fgo_data = json.loads(response.text)
|
||||
if int(fgo_data['nums']) == 0:
|
||||
break
|
||||
for x in fgo_data['data']:
|
||||
x['name'] = remove_prohibited_str(x['name'])
|
||||
key = x['name']
|
||||
data = add_to_data(data, x, game_name)
|
||||
await download_img(data[key]['头像'], game_name, key)
|
||||
logger.info(f'{key} is update...')
|
||||
if game_name == 'onmyoji':
|
||||
url = 'https://yys.res.netease.com/pc/zt/20161108171335/js/app/all_shishen.json?v74='
|
||||
response = await AsyncHttpx.get(url, timeout=7)
|
||||
onmyoji_data = response.json()
|
||||
for x in onmyoji_data:
|
||||
x['name'] = remove_prohibited_str(x['name'])
|
||||
key = x['name']
|
||||
data = add_to_data(data, x, game_name)
|
||||
logger.info(f'{key} is update...')
|
||||
data = await _last_check(data, game_name)
|
||||
except TimeoutError:
|
||||
logger.warning(f'更新 {game_name} 超时...')
|
||||
return {}, 999
|
||||
except Exception as e:
|
||||
logger.warning(f'更新 {game_name} 失败 {type(e)}:{e}...')
|
||||
return {}, 999
|
||||
with info_path.open('w', encoding='utf8') as wf:
|
||||
json.dump(data, wf, ensure_ascii=False, indent=4)
|
||||
return data, 200
|
||||
|
||||
|
||||
# 添加到字典
|
||||
def add_to_data(data: dict, x: dict, game_name: str) -> dict:
|
||||
member_dict = {}
|
||||
if game_name == 'fgo':
|
||||
member_dict = {
|
||||
'id': x['id'],
|
||||
'card_id': x['charid'],
|
||||
'头像': x['icon'],
|
||||
'名称': x['name'],
|
||||
'职阶': x['classes'],
|
||||
'星级': x['star'],
|
||||
'hp': x['lvmax4hp'],
|
||||
'atk': x['lvmax4atk'],
|
||||
'card_quick': x['cardquick'],
|
||||
'card_arts': x['cardarts'],
|
||||
'card_buster': x['cardbuster'],
|
||||
'宝具': x['tprop'],
|
||||
}
|
||||
if game_name == 'fgo_card':
|
||||
member_dict = {
|
||||
'id': x['id'],
|
||||
'card_id': x['equipid'],
|
||||
'头像': x['icon'],
|
||||
'名称': x['name'],
|
||||
'星级': x['star'],
|
||||
'hp': x['lvmax_hp'],
|
||||
'atk': x['lvmax_atk'],
|
||||
'skill_e': x['skill_e'].split('<br />')[: -1],
|
||||
}
|
||||
if game_name == 'onmyoji':
|
||||
member_dict = {
|
||||
'id': x['id'],
|
||||
'名称': x['name'],
|
||||
'星级': x['level'],
|
||||
}
|
||||
data[member_dict['名称']] = member_dict
|
||||
return data
|
||||
|
||||
|
||||
# 获取额外数据
|
||||
async def _last_check(data: dict, game_name: str) -> dict:
|
||||
if game_name == 'fgo':
|
||||
url = 'http://fgo.vgtime.com/servant/'
|
||||
tasks = []
|
||||
semaphore = asyncio.Semaphore(draw_config.SEMAPHORE)
|
||||
for key in data.keys():
|
||||
tasks.append(asyncio.ensure_future(
|
||||
_async_update_fgo_extra_info(url, key, data[key]['id'], semaphore)))
|
||||
result = await asyncio.gather(*tasks)
|
||||
for x in result:
|
||||
for key in x.keys():
|
||||
data[key]['入手方式'] = x[key]['入手方式']
|
||||
if game_name == 'onmyoji':
|
||||
url = 'https://yys.163.com/shishen/{}.html'
|
||||
for key in data.keys():
|
||||
response = await AsyncHttpx.get(f'{url.format(data[key]["id"])}', timeout=7)
|
||||
soup = BeautifulSoup(response.text, 'lxml')
|
||||
data[key]['头像'] = "https:" + soup.find('div', {'class': 'pic_wrap'}).find('img')['src']
|
||||
await download_img(data[key]['头像'], game_name, key)
|
||||
return data
|
||||
|
||||
|
||||
async def _async_update_fgo_extra_info(url: str, key: str, _id: str, semaphore):
|
||||
# 防止访问超时
|
||||
async with semaphore:
|
||||
for i in range(10):
|
||||
try:
|
||||
response = await AsyncHttpx.get(f'{url}{_id}', timeout=7)
|
||||
soup = BeautifulSoup(response.text, 'lxml')
|
||||
obtain = soup.find('table', {'class': 'uk-table uk-codex-table'}).find_all('td')[-1].text
|
||||
if obtain.find('限时活动免费获取 活动结束后无法获得') != -1:
|
||||
obtain = ['活动获取']
|
||||
elif obtain.find('非限时UP无法获得') != -1:
|
||||
obtain = ['限时召唤']
|
||||
else:
|
||||
if obtain.find('&') != -1:
|
||||
obtain = obtain.strip().split('&')
|
||||
else:
|
||||
obtain = obtain.strip().split(' ')
|
||||
logger.info(f'Fgo获取额外信息 {key}....{obtain}')
|
||||
x = {key: {}}
|
||||
x[key]['入手方式'] = obtain
|
||||
return x
|
||||
except TimeoutError:
|
||||
logger.warning(f'访问{url}{_id} 第 {i}次 超时...已再次访问')
|
||||
except Exception as e:
|
||||
logger.warning(f'访问{url}{_id} 第 {i}次 发生错误:{e}...已再次访问')
|
||||
return {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
from typing import Tuple
|
||||
from .config import DRAW_DATA_PATH, draw_config
|
||||
from asyncio.exceptions import TimeoutError
|
||||
from bs4 import BeautifulSoup
|
||||
from .util import download_img
|
||||
from .util import remove_prohibited_str
|
||||
from urllib.parse import unquote
|
||||
from utils.http_utils import AsyncHttpx
|
||||
from nonebot.log import logger
|
||||
import bs4
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ModuleNotFoundError:
|
||||
import json
|
||||
|
||||
headers = {
|
||||
"User-Agent": '"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)"'
|
||||
}
|
||||
|
||||
|
||||
async def update_simple_info(url: str, game_name: str) -> Tuple[dict, int]:
|
||||
info_path = DRAW_DATA_PATH / f"{game_name}.json"
|
||||
try:
|
||||
with info_path.open("r", encoding="utf8") as f:
|
||||
data = json.load(f)
|
||||
except (ValueError, FileNotFoundError):
|
||||
data = {}
|
||||
try:
|
||||
response = await AsyncHttpx.get(url, timeout=7)
|
||||
soup = BeautifulSoup(response.text, "lxml")
|
||||
divs = get_char_divs(soup, game_name)
|
||||
for div in divs:
|
||||
type_lst = get_type_lst(div, game_name)
|
||||
index = 0
|
||||
for char_lst in type_lst:
|
||||
try:
|
||||
contents = get_char_lst_contents(char_lst, game_name)
|
||||
except AttributeError:
|
||||
continue
|
||||
for char in contents[1:]:
|
||||
try:
|
||||
data = await retrieve_char_data(
|
||||
char, game_name, data, index
|
||||
)
|
||||
except AttributeError:
|
||||
continue
|
||||
index += 1
|
||||
data = await _last_check(data, game_name)
|
||||
except TimeoutError:
|
||||
logger.warning(f"更新 {game_name} 超时...")
|
||||
return {}, 999
|
||||
with info_path.open("w", encoding="utf8") as wf:
|
||||
wf.write(json.dumps(data, ensure_ascii=False, indent=4))
|
||||
return data, 200
|
||||
|
||||
|
||||
# 获取所有包含需要图片的divs
|
||||
def get_char_divs(soup: bs4.BeautifulSoup, game_name: str) -> bs4.element.ResultSet:
|
||||
# if game_name == "pcr":
|
||||
# return soup.find_all("div", {"class": "tabbertab"})
|
||||
if game_name in ["azur", "pcr"]:
|
||||
return soup.find_all("div", {"class": "resp-tabs"})
|
||||
|
||||
|
||||
# 拿到所有类型
|
||||
def get_type_lst(div: bs4.element.Tag, game_name: str):
|
||||
if game_name in ["pcr", "azur"]:
|
||||
return div.find("div", {"class": "resp-tabs-container"}).find_all(
|
||||
"div", {"class": "resp-tab-content"}
|
||||
)
|
||||
|
||||
|
||||
# 获取所有角色div
|
||||
def get_char_lst_contents(char_lst: bs4.element.Tag, game_name: str):
|
||||
contents = []
|
||||
# print(len(char_lst.find_all('tr')))
|
||||
if game_name == "pcr":
|
||||
contents = char_lst.contents
|
||||
if game_name == "azur":
|
||||
contents = char_lst.find("table").find("tbody").contents[-1].find("td").contents
|
||||
return [x for x in contents if x != "\n"]
|
||||
|
||||
|
||||
# 额外数据
|
||||
async def _last_check(
|
||||
data: dict, game_name: str
|
||||
) -> dict:
|
||||
if game_name == "azur":
|
||||
idx = 1
|
||||
for url in [
|
||||
"https://patchwiki.biligame.com/images/blhx/thumb/1/15/pxho13xsnkyb546tftvh49etzdh74cf.png/60px"
|
||||
"-舰娘头像外框普通.png",
|
||||
"https://patchwiki.biligame.com/images/blhx/thumb/a/a9/k8t7nx6c8pan5vyr8z21txp45jxeo66.png/60px"
|
||||
"-舰娘头像外框稀有.png",
|
||||
"https://patchwiki.biligame.com/images/blhx/thumb/a/a5/5whkzvt200zwhhx0h0iz9qo1kldnidj.png/60px"
|
||||
"-舰娘头像外框精锐.png",
|
||||
"https://patchwiki.biligame.com/images/blhx/thumb/a/a2/ptog1j220x5q02hytpwc8al7f229qk9.png/60px-"
|
||||
"舰娘头像外框超稀有.png",
|
||||
]:
|
||||
await download_img(url, "azur", f"{idx}_star")
|
||||
idx += 1
|
||||
tasks = []
|
||||
semaphore = asyncio.Semaphore(draw_config.SEMAPHORE)
|
||||
for key in data.keys():
|
||||
tasks.append(
|
||||
asyncio.ensure_future(
|
||||
_async_update_azur_extra_info(key, semaphore)
|
||||
)
|
||||
)
|
||||
result = await asyncio.gather(*tasks)
|
||||
for x in result:
|
||||
for key in x.keys():
|
||||
data[key]["获取途径"] = x[key]["获取途径"]
|
||||
return data
|
||||
|
||||
|
||||
azur_type = {
|
||||
"0": "驱逐",
|
||||
"1": "轻巡",
|
||||
"2": "重巡",
|
||||
"3": "超巡",
|
||||
"4": "战巡",
|
||||
"5": "战列",
|
||||
"6": "航母",
|
||||
"7": "航站",
|
||||
"8": "轻航",
|
||||
"9": "重炮",
|
||||
"10": "维修",
|
||||
"11": "潜艇",
|
||||
"12": "运输",
|
||||
}
|
||||
|
||||
|
||||
# 整理数据
|
||||
async def retrieve_char_data(
|
||||
char: bs4.element.Tag,
|
||||
game_name: str,
|
||||
data: dict,
|
||||
index: int = 0,
|
||||
) -> dict:
|
||||
member_dict = {}
|
||||
if game_name == "pcr":
|
||||
member_dict = {
|
||||
"头像": unquote(char.find("a").find("img")["src"]),
|
||||
"名称": remove_prohibited_str(char.find("a")["title"]),
|
||||
"星级": 3 - index,
|
||||
}
|
||||
if game_name == "azur":
|
||||
char = char.find("div").find("div").find("div")
|
||||
avatar_img = char.find("a").find("img")
|
||||
char = char.find("div")
|
||||
try:
|
||||
member_dict["名称"] = remove_prohibited_str(char.find("a")["title"])
|
||||
except TypeError:
|
||||
member_dict["名称"] = char.find("a")["title"][:-4]
|
||||
try:
|
||||
member_dict["头像"] = unquote(str(avatar_img["srcset"]).split(" ")[-2])
|
||||
except KeyError:
|
||||
member_dict["头像"] = unquote(str(avatar_img["src"]).split(" ")[-2])
|
||||
except TypeError:
|
||||
member_dict["头像"] = "img link not find..."
|
||||
logger.warning(f'{member_dict["名称"]} 图片缺失....')
|
||||
star = char.find("img")["alt"]
|
||||
if star == "舰娘头像外框普通.png":
|
||||
star = 1
|
||||
elif star == "舰娘头像外框稀有.png":
|
||||
star = 2
|
||||
elif star == "舰娘头像外框精锐.png":
|
||||
star = 3
|
||||
elif star == "舰娘头像外框超稀有.png":
|
||||
star = 4
|
||||
elif star == "舰娘头像外框海上传奇.png":
|
||||
star = 5
|
||||
elif star in [
|
||||
"舰娘头像外框最高方案.png",
|
||||
"舰娘头像外框决战方案.png",
|
||||
"舰娘头像外框超稀有META.png",
|
||||
"舰娘头像外框精锐META.png",
|
||||
]:
|
||||
star = 6
|
||||
else:
|
||||
star = 6
|
||||
member_dict["星级"] = star
|
||||
member_dict["类型"] = azur_type[str(index)]
|
||||
await download_img(member_dict["头像"], game_name, member_dict["名称"])
|
||||
data[member_dict["名称"]] = member_dict
|
||||
logger.info(f'{member_dict["名称"]} is update...')
|
||||
return data
|
||||
|
||||
|
||||
async def _async_update_azur_extra_info(
|
||||
key: str, semaphore
|
||||
):
|
||||
if key[-1] == "改":
|
||||
return {key: {"获取途径": ["无法建造"]}}
|
||||
async with semaphore:
|
||||
for i in range(20):
|
||||
try:
|
||||
res = await AsyncHttpx.get(f"https://wiki.biligame.com/blhx/{key}", timeout=7)
|
||||
soup = BeautifulSoup(res.text, "lxml")
|
||||
try:
|
||||
construction_time = str(
|
||||
soup.find("table", {"class": "wikitable sv-general"}).find(
|
||||
"tbody"
|
||||
)
|
||||
)
|
||||
x = {key: {"获取途径": []}}
|
||||
if construction_time.find("无法建造") != -1:
|
||||
x[key]["获取途径"].append("无法建造")
|
||||
elif construction_time.find("活动已关闭") != -1:
|
||||
x[key]["获取途径"].append("活动限定")
|
||||
else:
|
||||
x[key]["获取途径"].append("可以建造")
|
||||
logger.info(f'碧蓝航线获取额外信息 {key}...{x[key]["获取途径"]}')
|
||||
except AttributeError:
|
||||
x = {key: {"获取途径": []}}
|
||||
logger.warning(f"碧蓝航线获取额外信息错误 {key}...{[]}")
|
||||
return x
|
||||
except TimeoutError:
|
||||
logger.warning(
|
||||
f"访问 https://wiki.biligame.com/blhx/{key} 第 {i}次 超时...已再次访问"
|
||||
)
|
||||
return {}
|
||||
Executable → Regular
+39
-340
@@ -1,346 +1,16 @@
|
||||
import platform
|
||||
from asyncio.exceptions import TimeoutError
|
||||
from utils.utils import cn2py
|
||||
from utils.http_utils import AsyncHttpx
|
||||
from typing import List, Tuple, Union, Set
|
||||
from .config import draw_config, DRAW_IMAGE_PATH
|
||||
import nonebot
|
||||
from PIL import UnidentifiedImageError
|
||||
from utils.image_utils import BuildImage
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from nonebot.log import logger
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
import os
|
||||
import asyncio
|
||||
import pypinyin
|
||||
from PIL.ImageFont import FreeTypeFont
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from PIL.Image import Image as IMG
|
||||
from configs.path_config import FONT_PATH
|
||||
|
||||
|
||||
driver = nonebot.get_driver()
|
||||
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseData:
|
||||
name: str
|
||||
star: int
|
||||
limited: bool # 限定
|
||||
|
||||
|
||||
@dataclass
|
||||
class UpEvent:
|
||||
star: int # 对应up星级
|
||||
operators: List[BaseData] # 干员列表
|
||||
zoom: float # up提升倍率
|
||||
|
||||
|
||||
async def download_img(url: str, path: str, name: str) -> bool:
|
||||
path = path.split("_")[0]
|
||||
codename = cn2py(name)
|
||||
img_path = DRAW_IMAGE_PATH / f"{path}" / f"{codename}.png"
|
||||
if not img_path.exists():
|
||||
try:
|
||||
if await AsyncHttpx.download_file(url, img_path):
|
||||
logger.info(
|
||||
f"下载 {draw_config.path_dict[path]} 图片成功,名称:{name},url:{url}"
|
||||
)
|
||||
return True
|
||||
except TimeoutError:
|
||||
logger.warning(f"下载 {draw_config.path_dict[path]} 图片超时,名称:{name},url:{url}")
|
||||
# logger.info(f'{path_dict[path]} 图片 {name} 已存在')
|
||||
return False
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
def _check_dir():
|
||||
for dir_name in draw_config.path_dict.keys():
|
||||
dir_path = DRAW_IMAGE_PATH / f"{dir_name}"
|
||||
if not dir_path.exists():
|
||||
dir_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
async def generate_img(
|
||||
card_set: Union[Set[BaseData], List[BaseData]], game_name: str, star_list: list
|
||||
) -> str:
|
||||
# try:
|
||||
img_list = []
|
||||
background_list = []
|
||||
for x in card_set:
|
||||
if game_name == "prts":
|
||||
if x.star == 6:
|
||||
background_list.append("#FFD700")
|
||||
elif x.star == 5:
|
||||
background_list.append("#DAA520")
|
||||
elif x.star == 4:
|
||||
background_list.append("#9370D8")
|
||||
else:
|
||||
background_list.append("white")
|
||||
img_path = DRAW_IMAGE_PATH / f"{game_name}" / f"{x.star}_star.png"
|
||||
if game_name == "azur":
|
||||
if img_path.exists():
|
||||
background_list.append(str(img_path))
|
||||
py_name = cn2py(x.name)
|
||||
img_list.append(str(DRAW_IMAGE_PATH / f"{game_name}" / f"{py_name}.png"))
|
||||
img_len = len(img_list)
|
||||
w = 100 * 10
|
||||
if img_len <= 10:
|
||||
w = 100 * img_len
|
||||
h = 100
|
||||
elif img_len % 10 == 0:
|
||||
h = 100 * int(img_len / 10)
|
||||
else:
|
||||
h = 100 * int(img_len / 10) + 100
|
||||
card_img = await asyncio.get_event_loop().run_in_executor(
|
||||
None, _pst, h, img_list, game_name, background_list
|
||||
)
|
||||
num = 0
|
||||
for n in star_list:
|
||||
num += n
|
||||
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 = BuildImage(100 * 10, h, 100, 100)
|
||||
idx = 0
|
||||
for img in img_list:
|
||||
try:
|
||||
if game_name == "prts":
|
||||
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 = BuildImage(100, 100, background=background_list[idx])
|
||||
b = BuildImage(98, 90, background=img)
|
||||
bk.paste(b, (1, 5))
|
||||
b = bk
|
||||
else:
|
||||
try:
|
||||
b = BuildImage(100, 100, background=img)
|
||||
except UnidentifiedImageError as e:
|
||||
logger.warning(f"无法识别图片 已删除图片,下次更新重新下载... e:{e}")
|
||||
if os.path.exists(img):
|
||||
os.remove(img)
|
||||
b = BuildImage(100, 100, color="black")
|
||||
except FileNotFoundError:
|
||||
logger.warning(f"{img} not exists")
|
||||
b = BuildImage(100, 100, color="black")
|
||||
card_img.paste(b)
|
||||
idx += 1
|
||||
return card_img
|
||||
|
||||
|
||||
# 初始化输出数据
|
||||
def init_star_rst(
|
||||
star_list: list,
|
||||
cnlist: list,
|
||||
max_star_list: list,
|
||||
max_star_index_list: list,
|
||||
up_list: list = None,
|
||||
) -> str:
|
||||
if not up_list:
|
||||
up_list = []
|
||||
rst = ""
|
||||
for i in range(len(star_list)):
|
||||
if star_list[i]:
|
||||
rst += f"[{cnlist[i]}×{star_list[i]}] "
|
||||
rst += "\n"
|
||||
for i in range(len(max_star_list)):
|
||||
if max_star_list[i] in up_list:
|
||||
rst += f"第 {max_star_index_list[i]+1} 抽获取UP {max_star_list[i]}\n"
|
||||
else:
|
||||
rst += f"第 {max_star_index_list[i]+1} 抽获取 {max_star_list[i]}\n"
|
||||
return rst
|
||||
|
||||
|
||||
# 更好的初始化
|
||||
def init_rst(
|
||||
max_star_char_dict: dict,
|
||||
star_num_list: List[int],
|
||||
star: List[str],
|
||||
up_list: list = None,
|
||||
):
|
||||
# print(max_star_char_dict)
|
||||
# print(star_num_list)
|
||||
# print(up_list)
|
||||
up_list = up_list if up_list else []
|
||||
rst = ""
|
||||
for i in range(len(star_num_list)):
|
||||
if star_num_list[i]:
|
||||
rst += f"[{star[i]}×{star_num_list[i]}] "
|
||||
rst += "\n"
|
||||
_tmp = []
|
||||
for name in max_star_char_dict.keys():
|
||||
_tmp += max_star_char_dict[name]
|
||||
for index in sorted(_tmp):
|
||||
for name in max_star_char_dict.keys():
|
||||
if index in max_star_char_dict[name]:
|
||||
if name in up_list:
|
||||
rst += f"第 {index} 抽获取UP {name}\n"
|
||||
else:
|
||||
rst += f"第 {index} 抽获取 {name}\n"
|
||||
return rst[:-1] if rst else ""
|
||||
|
||||
|
||||
def max_card(_dict: dict):
|
||||
_max_value = max(_dict.values())
|
||||
_max_user = list(_dict.keys())[list(_dict.values()).index(_max_value)]
|
||||
return f"抽取到最多的是{_max_user},共抽取了{_max_value}次"
|
||||
# ThreeHighest = nlargest(3, operator_dict, key=operator_dict.get)
|
||||
# rst = '最喜欢你的前三位是干员是:\n'
|
||||
# for name in ThreeHighest:
|
||||
# rst += f'{name} 共投了 {operator_dict[name]} 份简历\n'
|
||||
# return rst[:-1]
|
||||
|
||||
|
||||
# 获取up和概率
|
||||
async def init_up_char(announcement):
|
||||
UP_CHAR = []
|
||||
UP_ARMS = []
|
||||
tmp = ""
|
||||
up_char_dict = await announcement.update_up_char()
|
||||
for x in list(up_char_dict.keys()):
|
||||
tmp += up_char_dict[x]["title"] + "[\n]"
|
||||
tmp = tmp.split("[\n]")
|
||||
_CURRENT_CHAR_POOL_TITLE = tmp[0]
|
||||
if len(up_char_dict) > 1:
|
||||
_CURRENT_ARMS_POOL_TITLE = tmp[1]
|
||||
else:
|
||||
_CURRENT_ARMS_POOL_TITLE = ""
|
||||
POOL_IMG = ""
|
||||
x = [x for x in list(up_char_dict.keys())]
|
||||
if _CURRENT_CHAR_POOL_TITLE:
|
||||
POOL_IMG += MessageSegment.image(up_char_dict[x[0]]["pool_img"])
|
||||
try:
|
||||
if _CURRENT_ARMS_POOL_TITLE:
|
||||
POOL_IMG += MessageSegment.image(up_char_dict[x[1]]["pool_img"])
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
logger.info(
|
||||
f"成功获取{announcement.game_name}当前up信息...当前up池: {_CURRENT_CHAR_POOL_TITLE} & {_CURRENT_ARMS_POOL_TITLE}"
|
||||
)
|
||||
for key in up_char_dict.keys():
|
||||
for star in up_char_dict[key]["up_char"].keys():
|
||||
up_char_lst = []
|
||||
for char in up_char_dict[key]["up_char"][star].keys():
|
||||
up_char_lst.append(char)
|
||||
if up_char_lst:
|
||||
if key == "char":
|
||||
UP_CHAR.append(
|
||||
UpEvent(star=int(star), operators=up_char_lst, zoom=0)
|
||||
)
|
||||
else:
|
||||
UP_ARMS.append(
|
||||
UpEvent(star=int(star), operators=up_char_lst, zoom=0)
|
||||
)
|
||||
return (
|
||||
_CURRENT_CHAR_POOL_TITLE,
|
||||
_CURRENT_ARMS_POOL_TITLE,
|
||||
POOL_IMG,
|
||||
UP_CHAR,
|
||||
UP_ARMS,
|
||||
)
|
||||
|
||||
|
||||
def is_number(s) -> bool:
|
||||
try:
|
||||
float(s)
|
||||
return True
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
import unicodedata
|
||||
|
||||
unicodedata.numeric(s)
|
||||
return True
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def set_list(lst: List[BaseData]) -> list:
|
||||
tmp = []
|
||||
name_lst = []
|
||||
for x in lst:
|
||||
if x.name not in name_lst:
|
||||
tmp.append(x)
|
||||
name_lst.append(x.name)
|
||||
return tmp
|
||||
|
||||
|
||||
# 获取星级
|
||||
def get_star(star_lst: List[int], probability_lst: List[float]) -> int:
|
||||
rand = random.random()
|
||||
add = 0
|
||||
tmp_lst = [(0, probability_lst[0])]
|
||||
for i in range(1, len(probability_lst) - 1):
|
||||
add += probability_lst[i - 1]
|
||||
tmp_lst.append((tmp_lst[i - 1][1], probability_lst[i] + add))
|
||||
tmp_lst.append((tmp_lst[-1][1], 1))
|
||||
for i in range(len(tmp_lst)):
|
||||
if tmp_lst[i][0] <= rand <= tmp_lst[i][1]:
|
||||
return star_lst[i]
|
||||
|
||||
|
||||
# 整理数据
|
||||
def format_card_information(
|
||||
count: int, star_list: List[int], func, pool_name: str = "", guaranteed: bool = True
|
||||
):
|
||||
max_star_lst = [] # 获取的最高星级角色列表
|
||||
max_index_lst = [] # 获取最高星级角色的次数
|
||||
obj_list = [] # 获取所有角色
|
||||
obj_dict = {} # 获取角色次数字典
|
||||
_count = -1
|
||||
if guaranteed:
|
||||
_count = 0
|
||||
for i in range(count):
|
||||
if guaranteed:
|
||||
_count += 1
|
||||
if pool_name:
|
||||
if _count == 10:
|
||||
obj, code = func(pool_name, 2)
|
||||
_count = 0
|
||||
else:
|
||||
obj, code = func(pool_name)
|
||||
else:
|
||||
if _count == 10:
|
||||
obj, code = func(mode=2)
|
||||
_count = 0
|
||||
else:
|
||||
obj, code = func()
|
||||
star_list[code] += 1
|
||||
if code == 0:
|
||||
max_star_lst.append(obj.name)
|
||||
max_index_lst.append(i)
|
||||
if guaranteed:
|
||||
_count = 0
|
||||
if code == 1:
|
||||
if guaranteed:
|
||||
_count = 0
|
||||
try:
|
||||
obj_dict[obj.name] += 1
|
||||
except KeyError:
|
||||
obj_dict[obj.name] = 1
|
||||
obj_list.append(obj)
|
||||
return obj_list, obj_dict, max_star_lst, star_list, max_index_lst
|
||||
|
||||
|
||||
# 检测次数是否合法
|
||||
def check_num(num: str, max_num: int) -> Tuple[str, bool]:
|
||||
if is_number(num):
|
||||
try:
|
||||
num = int(num)
|
||||
except ValueError:
|
||||
return "必!须!是!数!字!", False
|
||||
if num > max_num:
|
||||
return "一井都满不足不了你嘛!快爬开!", False
|
||||
if num < 1:
|
||||
return "虚空抽卡???", False
|
||||
else:
|
||||
return str(num), True
|
||||
def cn2py(word) -> str:
|
||||
temp = ""
|
||||
for i in pypinyin.pinyin(word, style=pypinyin.NORMAL):
|
||||
temp += "".join(i)
|
||||
return temp
|
||||
|
||||
|
||||
# 移除windows和linux下特殊字符
|
||||
@@ -354,3 +24,32 @@ def remove_prohibited_str(name: str) -> str:
|
||||
else:
|
||||
name = name.replace("/", "\\")
|
||||
return name
|
||||
|
||||
|
||||
def load_font(font_name: str = "msyh.ttf", fontsize: int = 16) -> FreeTypeFont:
|
||||
return ImageFont.truetype(
|
||||
str(FONT_PATH / f"{font_name}"), fontsize, encoding="utf-8"
|
||||
)
|
||||
|
||||
|
||||
def circled_number(num: int) -> IMG:
|
||||
font = load_font(fontsize=450)
|
||||
text = str(num)
|
||||
text_w = font.getsize(text)[0]
|
||||
w = 240 + text_w
|
||||
w = w if w >= 500 else 500
|
||||
img = Image.new("RGBA", (w, 500))
|
||||
draw = ImageDraw.Draw(img)
|
||||
draw.ellipse(((0, 0), (500, 500)), fill="red")
|
||||
draw.ellipse(((w - 500, 0), (w, 500)), fill="red")
|
||||
draw.rectangle(((250, 0), (w - 250, 500)), fill="red")
|
||||
draw.text(
|
||||
(120, -60),
|
||||
text,
|
||||
font=font,
|
||||
fill="white",
|
||||
stroke_width=10,
|
||||
stroke_fill="white",
|
||||
)
|
||||
return img
|
||||
|
||||
|
||||
+24
-37
@@ -1,58 +1,34 @@
|
||||
from httpx import AsyncClient
|
||||
from datetime import datetime
|
||||
from nonebot.log import logger
|
||||
from nonebot.adapters.onebot.v11 import Bot
|
||||
from configs.config import NICKNAME
|
||||
from utils.http_utils import AsyncHttpx
|
||||
|
||||
|
||||
# 获取所有 Epic Game Store 促销游戏
|
||||
# 方法参考:RSSHub /epicgames 路由
|
||||
# https://github.com/DIYgod/RSSHub/blob/master/lib/routes/epicgames/index.js
|
||||
async def get_epic_game():
|
||||
# 现在没用 graphql 辣
|
||||
"""prv_graphql Code
|
||||
epic_url = "https://www.epicgames.com/store/backend/graphql-proxy"
|
||||
headers = {
|
||||
"Referer": "https://www.epicgames.com/store/zh-CN/",
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
}
|
||||
data = {
|
||||
"query": "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, $keywords: String, $locale: String, $namespace: String, $sortBy: String, $sortDir: String, $start: Int, $tag: String, $withPrice: Boolean = false, $withPromotions: Boolean = false) {\n Catalog {\n searchStore(allowCountries: $allowCountries, category: $category, count: $count, country: $country, keywords: $keywords, locale: $locale, namespace: $namespace, sortBy: $sortBy, sortDir: $sortDir, start: $start, tag: $tag) {\n elements {\n title\n id\n namespace\n description\n effectiveDate\n keyImages {\n type\n url\n }\n seller {\n id\n name\n }\n productSlug\n urlSlug\n url\n items {\n id\n namespace\n }\n customAttributes {\n key\n value\n }\n categories {\n path\n }\n price(country: $country) @include(if: $withPrice) {\n totalPrice {\n discountPrice\n originalPrice\n voucherDiscount\n discount\n currencyCode\n currencyInfo {\n decimals\n }\n fmtPrice(locale: $locale) {\n originalPrice\n discountPrice\n intermediatePrice\n }\n }\n lineOffers {\n appliedRules {\n id\n endDate\n discountSetting {\n discountType\n }\n }\n }\n }\n promotions(category: $category) @include(if: $withPromotions) {\n promotionalOffers {\n promotionalOffers {\n startDate\n endDate\n discountSetting {\n discountType\n discountPercentage\n }\n }\n }\n upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n endDate\n discountSetting {\n discountType\n discountPercentage\n }\n }\n }\n }\n }\n paging {\n count\n total\n }\n }\n }\n}\n",
|
||||
"variables": {
|
||||
"allowCountries": "CN",
|
||||
"category": "freegames",
|
||||
"count": 1000,
|
||||
"country": "CN",
|
||||
"locale": "zh-CN",
|
||||
"sortBy": "effectiveDate",
|
||||
"sortDir": "asc",
|
||||
"withPrice": True,
|
||||
"withPromotions": True,
|
||||
},
|
||||
}
|
||||
"""
|
||||
|
||||
epic_url = "https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions?locale=zh-CN&country=CN&allowCountries=CN"
|
||||
headers = {
|
||||
"Referer": "https://www.epicgames.com/store/zh-CN/",
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36",
|
||||
}
|
||||
async with AsyncClient(headers=headers) as client:
|
||||
try:
|
||||
res = await client.get(epic_url, timeout=10.0)
|
||||
res_json = res.json()
|
||||
games = res_json["data"]["Catalog"]["searchStore"]["elements"]
|
||||
return games
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
return None
|
||||
try:
|
||||
res = await AsyncHttpx.get(epic_url, headers=headers, timeout=10)
|
||||
res_json = res.json()
|
||||
games = res_json["data"]["Catalog"]["searchStore"]["elements"]
|
||||
return games
|
||||
except Exception as e:
|
||||
logger.error(f"Epic 访问接口错误 {type(e)}:{e}")
|
||||
return None
|
||||
|
||||
|
||||
# 获取 Epic Game Store 免费游戏信息
|
||||
# 处理免费游戏的信息方法借鉴 pip 包 epicstore_api 示例
|
||||
# https://github.com/SD4RK/epicstore_api/blob/master/examples/free_games_example.py
|
||||
async def get_epic_free(bot: Bot, Type_Event: str):
|
||||
async def get_epic_free(bot: Bot, type_event: str):
|
||||
games = await get_epic_game()
|
||||
if not games:
|
||||
return "Epic 可能又抽风啦,请稍后再试(", 404
|
||||
@@ -81,7 +57,7 @@ async def get_epic_free(bot: Bot, Type_Event: str):
|
||||
end_date = datetime.fromisoformat(end_date_iso).strftime(
|
||||
"%b.%d %H:%M"
|
||||
)
|
||||
if Type_Event == "Group":
|
||||
if type_event == "Group":
|
||||
_message = "\n由 {} 公司发行的游戏 {} ({}) 在 UTC 时间 {} 即将推出免费游玩,预计截至 {}。".format(
|
||||
game_corp, game_name, game_price, start_date, end_date
|
||||
)
|
||||
@@ -101,8 +77,19 @@ async def get_epic_free(bot: Bot, Type_Event: str):
|
||||
msg_list.append(msg)
|
||||
else:
|
||||
for image in game["keyImages"]:
|
||||
if image["type"] == "Thumbnail":
|
||||
if (
|
||||
image.get("url")
|
||||
and not game_thumbnail
|
||||
and image["type"]
|
||||
in [
|
||||
"Thumbnail",
|
||||
"VaultOpened",
|
||||
"DieselStoreFrontWide",
|
||||
"OfferImageWide",
|
||||
]
|
||||
):
|
||||
game_thumbnail = image["url"]
|
||||
break
|
||||
for pair in game["customAttributes"]:
|
||||
if pair["key"] == "developerName":
|
||||
game_dev = pair["value"]
|
||||
@@ -127,7 +114,7 @@ async def get_epic_free(bot: Bot, Type_Event: str):
|
||||
game_url = "https://www.epicgames.com/store/zh-CN/p/{}".format(
|
||||
game_url_part
|
||||
)
|
||||
if Type_Event == "Group":
|
||||
if type_event == "Group":
|
||||
_message = "[CQ:image,file={}]\n\nFREE now :: {} ({})\n{}\n此游戏由 {} 开发、{} 发行,将在 UTC 时间 {} 结束免费游玩,戳链接速度加入你的游戏库吧~\n{}\n".format(
|
||||
game_thumbnail,
|
||||
game_name,
|
||||
|
||||
+12
-10
@@ -9,6 +9,7 @@ from nonebot.adapters.onebot.v11 import GroupMessageEvent
|
||||
from configs.config import Config
|
||||
from utils.http_utils import AsyncHttpx
|
||||
from services.log import logger
|
||||
from configs.config import NICKNAME
|
||||
|
||||
|
||||
__zx_plugin_name__ = "复读"
|
||||
@@ -74,15 +75,15 @@ class Fudu:
|
||||
_fudu_list = Fudu()
|
||||
|
||||
|
||||
fudu = on_message(permission=GROUP, priority=9)
|
||||
fudu = on_message(permission=GROUP, priority=999)
|
||||
|
||||
|
||||
@fudu.handle()
|
||||
async def _(event: GroupMessageEvent):
|
||||
if event.is_tome():
|
||||
return
|
||||
if get_message_text(event.json()):
|
||||
if get_message_text(event.json()).find("@可爱的小真寻") != -1:
|
||||
if msg := get_message_text(event.json()):
|
||||
if msg.startswith(f"@可爱的{NICKNAME}"):
|
||||
await fudu.finish("复制粘贴的虚空艾特?", at_sender=True)
|
||||
img = get_message_img(event.json())
|
||||
msg = get_message_text(event.json())
|
||||
@@ -105,20 +106,21 @@ async def _(event: GroupMessageEvent):
|
||||
"fudu", "FUDU_PROBABILITY"
|
||||
) and not _fudu_list.is_repeater(event.group_id):
|
||||
if random.random() < 0.2:
|
||||
await fudu.finish("[[_task|fudu]]打断施法!")
|
||||
if msg.endswith("打断施法!"):
|
||||
await fudu.finish("[[_task|fudu]]打断" + msg)
|
||||
else:
|
||||
await fudu.finish("[[_task|fudu]]打断施法!")
|
||||
_fudu_list.set_repeater(event.group_id)
|
||||
if img and msg:
|
||||
rst = msg + image(f"compare_{event.group_id}_img.jpg", "temp")
|
||||
rst = msg + image(TEMP_PATH / f"compare_{event.group_id}_img.jpg")
|
||||
elif img:
|
||||
rst = image(f"compare_{event.group_id}_img.jpg", "temp")
|
||||
rst = image(TEMP_PATH / f"compare_{event.group_id}_img.jpg")
|
||||
elif msg:
|
||||
rst = msg
|
||||
else:
|
||||
rst = ""
|
||||
if rst:
|
||||
if rst.endswith("打断施法!"):
|
||||
rst = "打断" + rst
|
||||
await fudu.send("[[_task|fudu]]" + rst)
|
||||
await fudu.finish("[[_task|fudu]]" + rst)
|
||||
|
||||
|
||||
async def get_fudu_img_hash(url, group_id):
|
||||
@@ -132,4 +134,4 @@ async def get_fudu_img_hash(url, group_id):
|
||||
logger.warning(f"复读下载图片失败...")
|
||||
except Exception as e:
|
||||
logger.warning(f"复读读取图片Hash出错 {type(e)}:{e}")
|
||||
return ""
|
||||
return ""
|
||||
|
||||
@@ -35,7 +35,7 @@ Config.add_plugin_config(
|
||||
default_value=True,
|
||||
)
|
||||
|
||||
almanac = on_command("原神黄历", priority=5, block=True)
|
||||
almanac = on_command("原神黄历", priority=15, block=True)
|
||||
|
||||
|
||||
ALC_PATH = IMAGE_PATH / "genshin" / "alc"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from pathlib import Path
|
||||
from typing import Tuple, Optional, List
|
||||
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 BuildImage
|
||||
@@ -137,7 +137,7 @@ async def download_resource_data(semaphore: Semaphore):
|
||||
asyncio.ensure_future(
|
||||
download_image(
|
||||
img_url,
|
||||
f"{icon_path}/{id_}.png",
|
||||
icon_path / f"{id_}.png",
|
||||
semaphore,
|
||||
True,
|
||||
)
|
||||
@@ -179,7 +179,7 @@ async def download_map_init(semaphore: Semaphore, flag: bool = False):
|
||||
map_url = _map_data["url"]
|
||||
await download_image(
|
||||
map_url,
|
||||
f"{map_path}/{idx}.png",
|
||||
map_path / f"{idx}.png",
|
||||
semaphore,
|
||||
force_flag=flag,
|
||||
)
|
||||
@@ -228,15 +228,14 @@ async def download_resource_type():
|
||||
except (TimeoutError, ConnectTimeout):
|
||||
logger.warning("下载原神资源类型数据超时....")
|
||||
except Exception as e:
|
||||
logger.error(f"载原神资源类型数据超时 {type(e)}:{e}")
|
||||
logger.error(f"载原神资源类型数据错误 {type(e)}:{e}")
|
||||
|
||||
|
||||
# 初始化资源图标
|
||||
def gen_icon(icon: str):
|
||||
def gen_icon(icon: Path):
|
||||
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 = BuildImage(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)
|
||||
@@ -247,22 +246,22 @@ def gen_icon(icon: str):
|
||||
# 下载图片
|
||||
async def download_image(
|
||||
img_url: str,
|
||||
path: str,
|
||||
path: Path,
|
||||
semaphore: Semaphore,
|
||||
gen_flag: bool = False,
|
||||
force_flag: bool = False,
|
||||
):
|
||||
async with semaphore:
|
||||
try:
|
||||
if not os.path.exists(path) or not is_valid or force_flag:
|
||||
if not path.exists() or not is_valid(path) or force_flag:
|
||||
if await AsyncHttpx.download_file(img_url, path, timeout=10):
|
||||
logger.info(f"下载原神资源图标:{img_url}")
|
||||
if gen_flag:
|
||||
gen_icon(path)
|
||||
else:
|
||||
logger.info(f"下载原神资源图标:{img_url} 失败,等待下次更新...")
|
||||
except UnidentifiedImageError:
|
||||
logger.warning(f"原神图片打开错误..已删除,等待下次更新... file: {path}")
|
||||
except Exception as e:
|
||||
logger.warning(f"原神图片错误..已删除,等待下次更新... file: {path} {type(e)}:{e}")
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
|
||||
@@ -254,10 +254,9 @@ class Genshin(db.Model):
|
||||
query = cls.query.where(cls.today_query_uid.contains(str(uid)))
|
||||
x = await query.gino.first()
|
||||
if x:
|
||||
await cls._add_query_uid(uid, uid)
|
||||
return x.cookie
|
||||
for u in [
|
||||
x for x in await cls.query.order_by(db.func.random()).gino.all() if x.cookie
|
||||
]:
|
||||
for u in await cls.query.where(cls.cookie != "").order_by(db.func.random()).gino.all():
|
||||
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
|
||||
@@ -356,7 +355,7 @@ class Genshin(db.Model):
|
||||
"""
|
||||
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()
|
||||
await user.update(today_query_uid=user.today_query_uid + f"{uid} ").apply()
|
||||
|
||||
@classmethod
|
||||
async def _get_user_data(
|
||||
|
||||
@@ -23,17 +23,17 @@ async def _():
|
||||
g_list = await Genshin.get_all_auto_sign_user()
|
||||
for u in g_list:
|
||||
if u.auto_sign_time:
|
||||
date = await Genshin.random_sign_time(u.uid)
|
||||
scheduler.add_job(
|
||||
_sign,
|
||||
"date",
|
||||
run_date=date.replace(microsecond=0),
|
||||
id=f"genshin_auto_sign_{u.uid}_{u.user_qq}_0",
|
||||
args=[u.user_qq, u.uid, 0],
|
||||
)
|
||||
logger.info(
|
||||
f"genshin_sign add_job:USER:{u.user_qq} UID:{u.uid} " f"{date} 原神自动签到"
|
||||
)
|
||||
if date := await Genshin.random_sign_time(u.uid):
|
||||
scheduler.add_job(
|
||||
_sign,
|
||||
"date",
|
||||
run_date=date.replace(microsecond=0),
|
||||
id=f"genshin_auto_sign_{u.uid}_{u.user_qq}_0",
|
||||
args=[u.user_qq, u.uid, 0],
|
||||
)
|
||||
logger.info(
|
||||
f"genshin_sign add_job:USER:{u.user_qq} UID:{u.uid} " f"{date} 原神自动签到"
|
||||
)
|
||||
|
||||
|
||||
def add_job(user_id: int, uid: int, date: datetime):
|
||||
|
||||
@@ -200,7 +200,7 @@ def _parse_data_and_draw(
|
||||
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()
|
||||
# _ava_img.circle()
|
||||
if x["status"] == "Finished":
|
||||
msg = "探索完成"
|
||||
font_color = (146, 188, 63)
|
||||
@@ -218,10 +218,10 @@ def _parse_data_and_draw(
|
||||
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)
|
||||
a_circle.paste(b_circle, (4, 4), True)
|
||||
_circle_bk.paste(a_circle, (4, 4), True)
|
||||
|
||||
_bk.paste(_circle_bk, (25, 0), True, center_type="by_height")
|
||||
_bk.paste(_circle_bk, (25, 0), True, "by_height")
|
||||
_bk.paste(_ava_img, (19, -13), True)
|
||||
_bk.text((100, 0), msg, font_color, "by_height")
|
||||
_bk.circle_corner(20)
|
||||
|
||||
@@ -128,7 +128,7 @@ async def get_character(
|
||||
) -> Optional[dict]:
|
||||
try:
|
||||
req = await AsyncHttpx.post(
|
||||
url="https://api-takumi.mihoyo.com/game_record/app/genshin/api/character",
|
||||
url="https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/character",
|
||||
headers={
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"DS": get_ds(
|
||||
@@ -182,7 +182,7 @@ def parsed_data(
|
||||
role_data = {
|
||||
"active_day_number": data["stats"]["active_day_number"], # 活跃天数
|
||||
"achievement_number": data["stats"]["achievement_number"], # 达成成就数量
|
||||
"win_rate": data["stats"]["win_rate"],
|
||||
# "win_rate": data["stats"]["win_rate"],
|
||||
"anemoculus_number": data["stats"]["anemoculus_number"], # 风神瞳已收集
|
||||
"geoculus_number": data["stats"]["geoculus_number"], # 岩神瞳已收集
|
||||
"avatar_number": data["stats"]["avatar_number"], # 获得角色数量
|
||||
@@ -232,7 +232,7 @@ async def get_mys_data(uid: str, mys_id: Optional[str]) -> Optional[List[Dict]]:
|
||||
if mys_id:
|
||||
try:
|
||||
req = await AsyncHttpx.get(
|
||||
url=f"https://api-takumi.mihoyo.com/game_record/card/wapi/getGameRecordCard?uid={mys_id}",
|
||||
url=f"https://api-takumi-record.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"),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user