mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-09-29 00:32:06 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8697670fe1 | ||
|
|
64d8543af9 | ||
|
|
482cc242cb | ||
|
|
2051e8183c | ||
|
|
d183903917 | ||
|
|
5c695899a1 | ||
|
|
708e912069 | ||
|
|
db1f4b2b52 | ||
|
|
1508782f49 | ||
|
|
445b830683 | ||
|
|
1a17e88142 | ||
|
|
c0ec2e7856 | ||
|
|
9e962bd0d1 | ||
|
|
7efcff13bb | ||
|
|
40fed798a1 | ||
|
|
0876afc08c | ||
|
|
ba4c4d06ff | ||
|
|
f64c30af48 | ||
|
|
bb0cea2dd3 | ||
|
|
e16afc3fc2 | ||
|
|
1b8753fead | ||
|
|
ba960d1ced | ||
|
|
41b60b6152 | ||
|
|
70ddce713c | ||
|
|
e15622748b | ||
|
|
96687021ff | ||
|
|
3c0a7ef510 | ||
|
|
4d84c9fb3f | ||
|
|
aa35a16610 | ||
|
|
a380fcb680 | ||
|
|
e544afe475 | ||
|
|
c089388323 | ||
|
|
e97a043031 | ||
|
|
d1b6dfe577 | ||
|
|
9189fd411e | ||
|
|
432eba80a9 | ||
|
|
db39612c54 | ||
|
|
5794c82a2d | ||
|
|
acf875a886 | ||
|
|
79185bd90f | ||
|
|
d695c30988 | ||
|
|
e4f2b761d6 | ||
|
|
6ff6009a70 | ||
|
|
dadeb4006e | ||
|
|
ee0ff762e5 | ||
|
|
1f01ba1d10 | ||
|
|
62965fe1df | ||
|
|
e29eb4cce5 | ||
|
|
699791e318 | ||
|
|
d2aae0103d | ||
|
|
ab06e38b7c | ||
|
|
8a0ca3c2e5 | ||
|
|
97317ed2c6 | ||
|
|
833e266a1f | ||
|
|
1bb9b2cb3f | ||
|
|
7ba129b3bf | ||
|
|
1e2c9ca28a | ||
|
|
c6f37fd05f | ||
|
|
b6b569fff9 | ||
|
|
ac57c016a6 | ||
|
|
28fdd9de95 | ||
|
|
1ec946aaa9 | ||
|
|
d6110e3c6f | ||
|
|
4c5302bd4c | ||
|
|
21ee3d5c3e | ||
|
|
e5c036cee8 | ||
|
|
6f82488af7 | ||
|
|
57a7903c63 | ||
|
|
27b89b333a | ||
|
|
ce2c4e7492 | ||
|
|
cad98a2a33 | ||
|
|
40da48e685 | ||
|
|
1c393e5c3e | ||
|
|
667c143c74 | ||
|
|
bc089c18ce | ||
|
|
24b730c0e2 | ||
|
|
9ed9e9b973 | ||
|
|
5b0027aa3d | ||
|
|
810c5e4e8b | ||
|
|
907534958f | ||
|
|
f08a18ab07 | ||
|
|
40549d5ca7 | ||
|
|
3e67edc506 | ||
|
|
9c56a81f65 | ||
|
|
a9a636c9ae | ||
|
|
98374dcd74 | ||
|
|
10ca6eec91 | ||
|
|
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 |
@@ -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错误...
|
||||
+15
-1
@@ -138,10 +138,24 @@ dmypy.json
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
demo.py
|
||||
test.py
|
||||
server_ip.py
|
||||
member_activity_handle.py
|
||||
Yu-Gi-Oh/
|
||||
black_word/
|
||||
csgo/
|
||||
fantasy_card/
|
||||
data/
|
||||
log/
|
||||
backup/
|
||||
extensive_plugin/
|
||||
test/
|
||||
bot.py
|
||||
data/
|
||||
.env
|
||||
.env.dev
|
||||
resources/
|
||||
!/configs/config.py
|
||||
!/configs/config.yaml
|
||||
!/.env
|
||||
!/.env.dev
|
||||
|
||||
@@ -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框架
|
||||
|
||||
## 未完成的文档
|
||||
|
||||
@@ -113,7 +114,7 @@
|
||||
- [x] 获取b站视频封面
|
||||
- [x] 通过PID获取图片
|
||||
- [x] 功能统计可视化
|
||||
|
||||
- [x] 词云
|
||||
- [x] 关于
|
||||
|
||||
### 已实现的管理员功能
|
||||
@@ -127,7 +128,7 @@
|
||||
- [x] 移动图片 (同上)
|
||||
- [x] 删除图片 (同上)
|
||||
- [x] 群内B站订阅
|
||||
- [x] 群词条
|
||||
- [x] 词条设置
|
||||
- [x] 休息吧/醒来
|
||||
|
||||
### 已实现的超级用户功能
|
||||
@@ -234,14 +235,339 @@ python bot.py
|
||||
|
||||
|
||||
## 使用Docker
|
||||
__Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__
|
||||
#### GitHub:[Sakuracio/zxenv](https://github.com/Sakuracio/zxenv)
|
||||
#### DockerHub:[hibikier/zhenxun_bot](https://hub.docker.com/r/hibikier/zhenxun_bot)
|
||||
__Docker 单机版(仅真寻Bot)__
|
||||
**点击下方的 GitHub 徽标查看教程**
|
||||
[](https://github.com/Sakuracio/zhenxun_bot_docker)
|
||||
[](https://hub.docker.com/r/hibikier/zhenxun_bot)
|
||||
__Docker 全量版(包含 真寻Bot PostgreSQL数据库 go-cqhttp webui等)__
|
||||
[](https://github.com/SinKy-Yan/zhenxunbot-docker)
|
||||
[](https://hub.docker.com/r/jyishit/zhenxun_bot)
|
||||
**点击上方的 GitHub 徽标查看教程**
|
||||
PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可能无法正常启动全量版容器**
|
||||
|
||||
## [爱发电](https://afdian.net/@HibiKier)
|
||||
<details>
|
||||
<summary>爱发电 以及 感谢投喂 </summary>
|
||||
<img width="365px" height="450px" src="https://user-images.githubusercontent.com/45528451/175059389-cfeb8174-fa07-4939-80ab-a039087a50f6.png">
|
||||
|
||||
### 感谢名单
|
||||
(可以告诉我你的 __github__ 地址,我偷偷换掉0v|)
|
||||
[烟寒若雨](https://afdian.net/u/067bd2161eec11eda62b52540025c377)
|
||||
[ln](https://afdian.net/u/b51914ba1c6611ed8a4e52540025c377)
|
||||
[爱发电用户_b9S4](https://afdian.net/u/3d8f30581a2911edba6d52540025c377)
|
||||
[爱发电用户_c58s](https://afdian.net/u/a6ad8dda195e11ed9a4152540025c377)
|
||||
[爱发电用户_eNr9](https://afdian.net/u/05fdb41c0c9a11ed814952540025c377)
|
||||
[MangataAkihi](https://github.com/Sakuracio)
|
||||
[炀](https://afdian.net/u/69b76e9ec77b11ec874f52540025c377)
|
||||
[爱发电用户_Bc6j](https://afdian.net/u/8546be24f44111eca64052540025c377)
|
||||
[大魔王](https://github.com/xipesoy)
|
||||
[CopilotLaLaLa](https://github.com/CopilotLaLaLa)
|
||||
[嘿小欧](https://afdian.net/u/daa4bec4f24911ec82e552540025c377)
|
||||
[回忆的秋千](https://afdian.net/u/e315d9c6f14f11ecbeef52540025c377)
|
||||
[十年くん](https://github.com/shinianj)
|
||||
[哇](https://afdian.net/u/9b266244f23911eca19052540025c377)
|
||||
[yajiwa](https://github.com/yajiwa)
|
||||
[爆金币](https://afdian.net/u/0d78879ef23711ecb22452540025c377)
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## 更新
|
||||
|
||||
### 2022/8/21 \[v0.1.6.3]
|
||||
|
||||
* 重构群词条,改为词库Plus,增加 精准|模糊|正则 问题匹配,问题与回答均支持at,image,face,超级用户额外提供 全局|私聊 词库设置,数据迁移目前只提供了问题和回答都是纯文本的词条
|
||||
* 修复b站转发解析av号无法解析
|
||||
* B站订阅直播订阅支持短号
|
||||
* 开箱提供重置开箱命令,重置今日所有开箱数据(重置次数,并不会删除今日已开箱记录)
|
||||
* 提供全局字典GDict,通过from utils.manager import GDict导入
|
||||
* 适配omega 13w张图的数据结构表(建议删表重导)
|
||||
* 除首次启动外将配置替换加入单次定时任务,加快启动速度
|
||||
* fix: WordBank.check() [@pull/1008](https://github.com/HibiKier/zhenxun_bot/pull/1008)
|
||||
* 改进插件 `我有一个朋友`,避免触发过于频繁 [@pull/1001](https://github.com/HibiKier/zhenxun_bot/pull/1001)
|
||||
* 原神便笺新增洞天宝钱和参量质变仪提示 [@pull/1005](https://github.com/HibiKier/zhenxun_bot/pull/1005)
|
||||
* 新增米游社签到功能,自动领取(白嫖)米游币 [@pull/991](https://github.com/HibiKier/zhenxun_bot/pull/991)
|
||||
|
||||
### 2022/8/14
|
||||
|
||||
* 修复epic未获取到时间时出错
|
||||
* 修复订阅主播时动态获取的id是直播间id
|
||||
|
||||
### 2022/8/8
|
||||
|
||||
* 修复赛马娘重载卡池失败的问题 [@pull/969](https://github.com/HibiKier/zhenxun_bot/pull/969)
|
||||
|
||||
### 2022/8/3
|
||||
|
||||
* 修复 bili动态链接在投稿视频时URL和分割线连在一起 [@pull/951](https://github.com/HibiKier/zhenxun_bot/pull/961)
|
||||
* 更新 Epic 免费游戏商城链接拼接规则 [@pull/957](https://github.com/HibiKier/zhenxun_bot/pull/957)
|
||||
|
||||
### 2022/8/6
|
||||
|
||||
* 修复了原神自动签到返回invalid request的问题,新增查看我的cookie命令 [@pull/971](https://github.com/HibiKier/zhenxun_bot/pull/971)
|
||||
|
||||
### 2022/7/31
|
||||
|
||||
* 对nonebot.beta4中PluginMeta进行解析
|
||||
* 修改webui元祖类型无法正确转换类型
|
||||
* 查看订阅以图片形式发送
|
||||
* 修复pix无图时不会提醒而是出错
|
||||
|
||||
### 2022/7/30
|
||||
|
||||
* 替换了cos和bt的url [@pull/951](https://github.com/HibiKier/zhenxun_bot/pull/951)
|
||||
* 发言记录统计添加日消息统计 [@pull/953](https://github.com/HibiKier/zhenxun_bot/pull/953)
|
||||
|
||||
### 2022/7/24 \[v0.1.6.2]
|
||||
|
||||
* 订阅up动态提供直链
|
||||
|
||||
### 2022/7/23
|
||||
|
||||
* 优化开箱次数判断
|
||||
|
||||
### 2022/7/16
|
||||
|
||||
* cos提供连发命令
|
||||
* 修复B站视频解析中b23解析错误
|
||||
|
||||
### 2022/7/13
|
||||
|
||||
* B站订阅提供配置项`LIVE_MSG_AT_ALL`,`UP_MSG_AT_ALL`:控制UP动态投稿和直播是否AT全体
|
||||
* 修改了Omega图库检测的数据库文件名
|
||||
* game_utils提供了Game类
|
||||
|
||||
### 2022/7/7
|
||||
|
||||
* 微博热搜选择单条热搜时也会检测时效性 [@pull/891](https://github.com/HibiKier/zhenxun_bot/pull/891)
|
||||
|
||||
### 2022/7/4
|
||||
|
||||
* 修复商品未设置限购时无法购买
|
||||
* 修复B站订阅UP动态 [@pull/886](https://github.com/HibiKier/zhenxun_bot/pull/886)
|
||||
* 修复色图命令带tag时配置项`MAX_ONCE_NUM2FORWARD`无法生效
|
||||
|
||||
### 2022/7/3 \[v0.1.6.1]
|
||||
|
||||
* BagUser提供方法`check_props`方法,用于判断用户是否含有该道具并是否使用
|
||||
* 优化商品显示图片,提供限制,限购,折扣提示
|
||||
* 修复图库内图片无法被连续删除的问题 [@pull/879](https://github.com/HibiKier/zhenxun_bot/pull/879)
|
||||
* 色图提供配置项`MAX_ONCE_NUM2FORWARD`:群聊中单次发送图片数量达到指定时使用合并转发
|
||||
* 优化修复了商品修改命令
|
||||
* B站订阅UP动态改为详情页截图
|
||||
|
||||
### 2022/6/28
|
||||
|
||||
* 修复色图插件api修改无法搜索图片 [@pull/866](https://github.com/HibiKier/zhenxun_bot/pull/866)
|
||||
|
||||
### 2022/6/26
|
||||
|
||||
* 替换了bt URL
|
||||
* PIX当使用pid查询图片时,会发送该pid下所有图片
|
||||
* 商店提供了每日购买次数限制
|
||||
|
||||
### 2022/6/24
|
||||
|
||||
* 对WSL虚拟机 代理 添加写法 [@pull/847](https://github.com/HibiKier/zhenxun_bot/pull/847)
|
||||
|
||||
### 2022/6/21
|
||||
|
||||
* update parse_bilibili [@pull/840](https://github.com/HibiKier/zhenxun_bot/pull/840)
|
||||
|
||||
### 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
|
||||
|
||||
* 修复了群白名单无法正确添加
|
||||
@@ -323,7 +649,7 @@ __Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__
|
||||
* 修复点歌无法正确发送
|
||||
* 修复我有一个朋友有时文本会包含CQ码
|
||||
* 修复群欢消息被动控制文本未删除 [@pull/124](https://github.com/HibiKier/zhenxun_bot/pull/124)
|
||||
* message_builder.image不再提供参数:abspath
|
||||
* message_builder.image不再提供参数:abspath
|
||||
|
||||
### 2022/2/23
|
||||
|
||||
@@ -460,7 +786,6 @@ __Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__
|
||||
|
||||
__..... 更多更新信息请查看文档__
|
||||
|
||||
|
||||
## Todo
|
||||
- [ ] web管理
|
||||
|
||||
@@ -477,4 +802,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.5.0
|
||||
__version__: v0.1.6.3
|
||||
|
||||
@@ -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 "那我先睡觉了..."
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -4,9 +4,8 @@ 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
|
||||
import shutil
|
||||
|
||||
@@ -42,7 +41,7 @@ Config.add_plugin_config(
|
||||
)
|
||||
|
||||
|
||||
cx = on_message(priority=9, block=False)
|
||||
cx = on_message(priority=9999, block=False, rule=lambda: False)
|
||||
|
||||
|
||||
# 早上好
|
||||
@@ -107,6 +106,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:
|
||||
|
||||
@@ -125,11 +125,13 @@ async def _(
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ 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
|
||||
|
||||
@@ -22,10 +23,10 @@ chat_history = on_message(rule=rule, priority=1, block=False)
|
||||
async def _(event: MessageEvent):
|
||||
if isinstance(event, GroupMessageEvent):
|
||||
await ChatHistory.add_chat_msg(
|
||||
event.user_id, event.group_id, str(event.get_message())
|
||||
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()))
|
||||
await ChatHistory.add_chat_msg(event.user_id, None, str(event.get_message()), get_message_text(event.json()))
|
||||
|
||||
|
||||
# @test.handle()
|
||||
|
||||
@@ -16,11 +16,12 @@ __zx_plugin_name__ = "消息统计"
|
||||
__plugin_usage__ = """
|
||||
usage:
|
||||
发言记录统计
|
||||
regex:(周|月)?消息排行(des|DES)?(n=[0-9]{1,2})?
|
||||
regex:(周|月|日)?消息排行(des|DES)?(n=[0-9]{1,2})?
|
||||
指令:
|
||||
消息统计?(des)?(n=?)
|
||||
周消息统计?(des)?(n=?)
|
||||
月消息统计?(des)?(n=?)
|
||||
日消息统计?(des)?(n=?)
|
||||
示例:
|
||||
消息统计
|
||||
消息统计des
|
||||
@@ -31,7 +32,8 @@ __plugin_des__ = "发言消息排行"
|
||||
__plugin_cmd__ = [
|
||||
"消息统计",
|
||||
"周消息统计",
|
||||
"月消息统计"
|
||||
"月消息统计",
|
||||
"日消息统计"
|
||||
]
|
||||
__plugin_type__ = ("数据统计", 1)
|
||||
__plugin_version__ = 0.1
|
||||
@@ -42,7 +44,7 @@ __plugin_settings__ = {
|
||||
}
|
||||
|
||||
|
||||
msg_handler = on_regex(r"(周|月)?消息统计(des|DES)?(n=[0-9]{1,2})?", priority=5, block=True)
|
||||
msg_handler = on_regex(r"^(周|月|日)?消息统计(des|DES)?(n=[0-9]{1,2})?$", priority=5, block=True)
|
||||
|
||||
|
||||
@msg_handler.handle()
|
||||
@@ -53,10 +55,14 @@ async def _(event: GroupMessageEvent, reg_group: Tuple[Any, ...] = RegexGroup())
|
||||
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())
|
||||
time_now = datetime.now()
|
||||
zero_today = time_now - timedelta(hours=time_now.hour, minutes=time_now.minute, seconds=time_now.second)
|
||||
if date in ["日"]:
|
||||
date_scope = (zero_today, time_now)
|
||||
elif date in ["周"]:
|
||||
date_scope = (time_now - timedelta(days=7), time_now)
|
||||
elif date in ["月"]:
|
||||
date_scope = (datetime.now() - timedelta(days=30), datetime.now())
|
||||
date_scope = (time_now - timedelta(days=30), time_now)
|
||||
if rank_data := await ChatHistory.get_group_msg_rank(
|
||||
gid, num, order or "DESC", date_scope
|
||||
):
|
||||
@@ -79,7 +85,7 @@ async def _(event: GroupMessageEvent, reg_group: Tuple[Any, ...] = RegexGroup())
|
||||
pytz.timezone("Asia/Shanghai")
|
||||
).replace(microsecond=0)
|
||||
else:
|
||||
date_scope = datetime.now().replace(microsecond=0)
|
||||
date_scope = time_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)}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -44,7 +44,7 @@ async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
|
||||
|
||||
@simple_help.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State, arg: Message = CommandArg()):
|
||||
async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
|
||||
msg = arg.extract_plain_text().strip()
|
||||
is_super = False
|
||||
if msg:
|
||||
|
||||
@@ -44,7 +44,7 @@ def _create_help_img(
|
||||
:param help_image: 图片路径
|
||||
:param simple_help_image: 简易帮助图片路径
|
||||
"""
|
||||
_matchers = get_matchers()
|
||||
_matchers = get_matchers(True)
|
||||
width = 0
|
||||
matchers_data = {}
|
||||
_des_tmp = {}
|
||||
@@ -55,12 +55,16 @@ def _create_help_img(
|
||||
# 插件分类
|
||||
for matcher in _matchers:
|
||||
plugin_name = None
|
||||
_plugin = nonebot.plugin.get_plugin(matcher.plugin_name)
|
||||
_plugin = matcher.plugin
|
||||
metadata = _plugin.metadata
|
||||
if not _plugin:
|
||||
logger.warning(f"获取 功能:{matcher.plugin_name} 失败...")
|
||||
continue
|
||||
_module = _plugin.module
|
||||
try:
|
||||
plugin_name = _module.__getattribute__("__zx_plugin_name__")
|
||||
plugin_name = metadata.name if metadata else _module.__getattribute__("__zx_plugin_name__")
|
||||
try:
|
||||
plugin_des = _module.__getattribute__("__plugin_des__")
|
||||
plugin_des = metadata.description if metadata else _module.__getattribute__("__plugin_des__")
|
||||
except AttributeError:
|
||||
plugin_des = "_"
|
||||
if (
|
||||
@@ -277,7 +281,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 +293,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),
|
||||
@@ -340,13 +344,14 @@ def get_plugin_help(msg: str, is_super: bool = False) -> Optional[str]:
|
||||
if module:
|
||||
try:
|
||||
plugin = nonebot.plugin.get_plugin(module)
|
||||
metadata = plugin.metadata
|
||||
if plugin:
|
||||
if is_super:
|
||||
result = plugin.module.__getattribute__(
|
||||
"__plugin_superuser_usage__"
|
||||
)
|
||||
else:
|
||||
result = plugin.module.__getattribute__("__plugin_usage__")
|
||||
result = metadata.usage if metadata else plugin.module.__getattribute__("__plugin_usage__")
|
||||
if result:
|
||||
width = 0
|
||||
for x in result.split("\n"):
|
||||
|
||||
@@ -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
|
||||
@@ -23,6 +22,7 @@ from nonebot.typing import T_State
|
||||
from typing import Optional
|
||||
from nonebot.adapters.onebot.v11 import (
|
||||
Bot,
|
||||
ActionFailed,
|
||||
MessageEvent,
|
||||
GroupMessageEvent,
|
||||
PokeNotifyEvent,
|
||||
@@ -91,7 +91,10 @@ async def _(matcher: Matcher, bot: Bot, event: Event, state: T_State):
|
||||
except AttributeError:
|
||||
pass
|
||||
# 群黑名单检测 群总开关检测
|
||||
if isinstance(event, GroupMessageEvent) or matcher.plugin_name == other_limit_plugins:
|
||||
if (
|
||||
isinstance(event, GroupMessageEvent)
|
||||
or matcher.plugin_name in other_limit_plugins
|
||||
):
|
||||
try:
|
||||
if (
|
||||
group_manager.get_group_level(event.group_id) < 0
|
||||
@@ -106,7 +109,7 @@ async def _(matcher: Matcher, bot: Bot, event: Event, 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 (
|
||||
@@ -146,9 +149,13 @@ async def _(matcher: Matcher, bot: Bot, event: Event, 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) or matcher.plugin_name in other_limit_plugins:
|
||||
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)
|
||||
if plugins2info_dict[module]["level"] > group_manager.get_group_level(
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
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,
|
||||
)
|
||||
@@ -31,7 +31,7 @@ _flmt = FreqLimiter(300)
|
||||
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, 9]
|
||||
and matcher.priority not in [1, 999]
|
||||
) or matcher.plugin_name in other_limit_plugins:
|
||||
try:
|
||||
if (
|
||||
@@ -58,7 +58,7 @@ async def _(matcher: Matcher, bot: Bot, event: Event, 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 (
|
||||
@@ -79,7 +79,7 @@ async def _(matcher: Matcher, bot: Bot, event: Event, 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 matcher.plugin_name not in ignore_rst_module:
|
||||
|
||||
@@ -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,15 +2,12 @@ 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会自己和自己聊天
|
||||
@@ -41,11 +38,6 @@ 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\|?(.*?)[[")):
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import re
|
||||
|
||||
|
||||
@Bot.on_calling_api
|
||||
async def handle_api_call(bot: Bot, api: str, data: Dict[str, Any]):
|
||||
async def _(bot: Bot, api: str, data: Dict[str, Any]):
|
||||
r = None
|
||||
if (
|
||||
(
|
||||
|
||||
@@ -14,7 +14,6 @@ from .check_plugin_status import check_plugin_status
|
||||
from nonebot.adapters.onebot.v11 import Bot
|
||||
from configs.path_config import DATA_PATH
|
||||
from services.log import logger
|
||||
from pathlib import Path
|
||||
from nonebot import Driver
|
||||
import nonebot
|
||||
|
||||
@@ -28,7 +27,7 @@ driver: Driver = nonebot.get_driver()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
def _():
|
||||
async def _():
|
||||
"""
|
||||
初始化数据
|
||||
"""
|
||||
|
||||
@@ -5,7 +5,6 @@ from utils.manager import (
|
||||
plugins2settings_manager,
|
||||
plugins2block_manager,
|
||||
plugins_manager,
|
||||
resources_manager
|
||||
)
|
||||
from services.log import logger
|
||||
from utils.utils import get_matchers
|
||||
@@ -20,26 +19,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()
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
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 utils.utils import scheduler
|
||||
from ruamel import yaml
|
||||
import nonebot
|
||||
|
||||
|
||||
_yaml = YAML(typ="safe")
|
||||
@@ -20,23 +23,37 @@ def init_plugins_config(data_path):
|
||||
_data = {}
|
||||
if plugins2config_file.exists():
|
||||
_data = _yaml.load(open(plugins2config_file, "r", encoding="utf8"))
|
||||
_matchers = get_matchers()
|
||||
_matchers = get_matchers(True)
|
||||
# 优先使用 metadata 数据
|
||||
for matcher in _matchers:
|
||||
_plugin = nonebot.plugin.get_plugin(matcher.plugin_name)
|
||||
_plugin = matcher.plugin
|
||||
metadata = _plugin.metadata
|
||||
try:
|
||||
_module = _plugin.module
|
||||
except AttributeError:
|
||||
continue
|
||||
try:
|
||||
plugin_version = _module.__getattribute__("__plugin_version__")
|
||||
except AttributeError:
|
||||
plugin_version = None
|
||||
try:
|
||||
plugin_configs = _module.__getattribute__("__plugin_configs__")
|
||||
except AttributeError:
|
||||
continue
|
||||
# 插件配置版本更新或为Version为None或不在存储配置内
|
||||
if (
|
||||
plugin_version = None
|
||||
if metadata:
|
||||
plugin_version = metadata.extra.get("version")
|
||||
if not plugin_version:
|
||||
try:
|
||||
plugin_version = _module.__getattribute__("__plugin_version__")
|
||||
except AttributeError:
|
||||
pass
|
||||
if metadata and metadata.config:
|
||||
plugin_configs = {}
|
||||
for key, value in metadata.config.__fields__.items():
|
||||
plugin_configs[key.upper()] = {
|
||||
"value": value.default,
|
||||
"default_value": value.default
|
||||
}
|
||||
else:
|
||||
try:
|
||||
plugin_configs = _module.__getattribute__("__plugin_configs__")
|
||||
except AttributeError:
|
||||
continue
|
||||
# 插件配置版本更新或为Version为None或不在存储配置内,当使用metadata时,必定更新
|
||||
if isinstance(plugin_version, str) or (
|
||||
plugin_version is None
|
||||
or (
|
||||
_data.get(matcher.plugin_name)
|
||||
@@ -56,7 +73,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,75 +98,105 @@ 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(
|
||||
_data, wf, indent=2, Dumper=yaml.RoundTripDumper, allow_unicode=True
|
||||
)
|
||||
# 再开始读取用户配置
|
||||
user_config_file = Path() / "configs" / "config.yaml"
|
||||
_data = {}
|
||||
_tmp_data = {}
|
||||
if user_config_file.exists():
|
||||
with open(user_config_file, "r", encoding="utf8") as f:
|
||||
_data = _yaml.load(f)
|
||||
# 数据替换
|
||||
for plugin in Config.keys():
|
||||
_tmp_data[plugin] = {}
|
||||
for k in Config[plugin].keys():
|
||||
user_config_file = Path() / "configs" / "config.yaml"
|
||||
if not user_config_file.exists():
|
||||
_replace_config()
|
||||
else:
|
||||
logger.info('五分钟后将进行配置数据替换,请注意...')
|
||||
scheduler.add_job(
|
||||
_replace_config,
|
||||
"date",
|
||||
run_date=datetime.now() + timedelta(minutes=5),
|
||||
id=f"_replace_config"
|
||||
)
|
||||
|
||||
|
||||
def _replace_config():
|
||||
"""
|
||||
说明:
|
||||
定时任务加载的配置读取替换
|
||||
"""
|
||||
# 再开始读取用户配置
|
||||
user_config_file = Path() / "configs" / "config.yaml"
|
||||
_data = {}
|
||||
_tmp_data = {}
|
||||
if user_config_file.exists():
|
||||
with open(user_config_file, "r", encoding="utf8") as f:
|
||||
_data = _yaml.load(f)
|
||||
# 数据替换
|
||||
for plugin in Config.keys():
|
||||
_tmp_data[plugin] = {}
|
||||
for k in Config[plugin].keys():
|
||||
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])
|
||||
admin_manager.set_admin_level(
|
||||
level2module, _data[plugin][k]
|
||||
)
|
||||
except KeyError:
|
||||
logger.warning(f"{level2module} 设置权限等级失败:{_data[plugin][k]}")
|
||||
logger.warning(
|
||||
f"{level2module} 设置权限等级失败:{_data[plugin][k]}"
|
||||
)
|
||||
_tmp_data[plugin][k] = Config.get_config(plugin, k)
|
||||
Config.save()
|
||||
temp_file = Path() / "configs" / "temp_config.yaml"
|
||||
try:
|
||||
with open(temp_file, "w", encoding="utf8") as wf:
|
||||
yaml.dump(
|
||||
_tmp_data, wf, Dumper=yaml.RoundTripDumper, allow_unicode=True
|
||||
except AttributeError as e:
|
||||
raise AttributeError(
|
||||
f"{e}\n" + prompt2cn("可能为config.yaml配置文件填写不规范", 46)
|
||||
)
|
||||
with open(temp_file, "r", encoding="utf8") as rf:
|
||||
_data = round_trip_load(rf)
|
||||
# 添加注释
|
||||
for plugin in _data.keys():
|
||||
rst = ""
|
||||
plugin_name = None
|
||||
try:
|
||||
plugin_data = Config.get(plugin)
|
||||
for x in list(Config.get(plugin).keys()):
|
||||
try:
|
||||
_x = plugin_data[x].get("name")
|
||||
if _x:
|
||||
plugin_name = _x
|
||||
except AttributeError:
|
||||
pass
|
||||
except (KeyError, AttributeError):
|
||||
plugin_name = None
|
||||
if not plugin_name:
|
||||
Config.save()
|
||||
temp_file = Path() / "configs" / "temp_config.yaml"
|
||||
try:
|
||||
with open(temp_file, "w", encoding="utf8") as wf:
|
||||
yaml.dump(
|
||||
_tmp_data, wf, Dumper=yaml.RoundTripDumper, allow_unicode=True
|
||||
)
|
||||
with open(temp_file, "r", encoding="utf8") as rf:
|
||||
_data = round_trip_load(rf)
|
||||
# 添加注释
|
||||
for plugin in _data.keys():
|
||||
rst = ""
|
||||
plugin_name = None
|
||||
try:
|
||||
plugin_data = Config.get(plugin)
|
||||
for x in list(Config.get(plugin).keys()):
|
||||
try:
|
||||
plugin_name = plugins_manager.get(plugin)["plugin_name"]
|
||||
except (AttributeError, TypeError):
|
||||
plugin_name = plugin
|
||||
plugin_name = (
|
||||
plugin_name.replace("[Hidden]", "")
|
||||
.replace("[Superuser]", "")
|
||||
.replace("[Admin]", "")
|
||||
.strip()
|
||||
)
|
||||
rst += plugin_name + "\n"
|
||||
for k in _data[plugin].keys():
|
||||
rst += f'{k}: {Config[plugin][k]["help"]}' + "\n"
|
||||
_data[plugin].yaml_set_start_comment(rst[:-1], indent=2)
|
||||
with open(Path() / "configs" / "config.yaml", "w", encoding="utf8") as wf:
|
||||
round_trip_dump(
|
||||
_data, wf, Dumper=yaml.RoundTripDumper, allow_unicode=True
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"生成简易配置注释错误 {type(e)}:{e}")
|
||||
if temp_file.exists():
|
||||
temp_file.unlink()
|
||||
_x = plugin_data[x].get("name")
|
||||
if _x:
|
||||
plugin_name = _x
|
||||
except AttributeError:
|
||||
pass
|
||||
except (KeyError, AttributeError):
|
||||
plugin_name = None
|
||||
if not plugin_name:
|
||||
try:
|
||||
plugin_name = plugins_manager.get(plugin)["plugin_name"]
|
||||
except (AttributeError, TypeError):
|
||||
plugin_name = plugin
|
||||
plugin_name = (
|
||||
plugin_name.replace("[Hidden]", "")
|
||||
.replace("[Superuser]", "")
|
||||
.replace("[Admin]", "")
|
||||
.strip()
|
||||
)
|
||||
rst += plugin_name + "\n"
|
||||
for k in _data[plugin].keys():
|
||||
rst += f'{k}: {Config[plugin][k]["help"]}' + "\n"
|
||||
_data[plugin].yaml_set_start_comment(rst[:-1], indent=2)
|
||||
with open(Path() / "configs" / "config.yaml", "w", encoding="utf8") as wf:
|
||||
round_trip_dump(
|
||||
_data, wf, Dumper=yaml.RoundTripDumper, allow_unicode=True
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"生成简易配置注释错误 {type(e)}:{e}")
|
||||
if temp_file.exists():
|
||||
temp_file.unlink()
|
||||
|
||||
@@ -22,9 +22,10 @@ def init_plugins_data(data_path):
|
||||
_data = {}
|
||||
if plugin2data_file.exists():
|
||||
_data = json.load(open(plugin2data_file, "r", encoding="utf8"))
|
||||
_matchers = get_matchers()
|
||||
_matchers = get_matchers(True)
|
||||
for matcher in _matchers:
|
||||
_plugin = nonebot.plugin.get_plugin(matcher.plugin_name)
|
||||
_plugin = matcher.plugin
|
||||
metadata = _plugin.metadata
|
||||
try:
|
||||
_module = _plugin.module
|
||||
except AttributeError:
|
||||
@@ -40,18 +41,28 @@ def init_plugins_data(data_path):
|
||||
matcher.plugin_name, "version", plugin_data.get("version")
|
||||
)
|
||||
else:
|
||||
try:
|
||||
plugin_version = _module.__getattribute__("__plugin_version__")
|
||||
except AttributeError:
|
||||
plugin_version = None
|
||||
try:
|
||||
plugin_name = _module.__getattribute__("__zx_plugin_name__")
|
||||
except AttributeError:
|
||||
plugin_name = matcher.plugin_name
|
||||
plugin_version = None
|
||||
if metadata:
|
||||
plugin_version = metadata.extra.get("version")
|
||||
if not plugin_version:
|
||||
try:
|
||||
plugin_version = _module.__getattribute__("__plugin_version__")
|
||||
except AttributeError:
|
||||
pass
|
||||
if metadata:
|
||||
plugin_name = metadata.name
|
||||
else:
|
||||
try:
|
||||
plugin_name = _module.__getattribute__("__zx_plugin_name__")
|
||||
except AttributeError:
|
||||
plugin_name = matcher.plugin_name
|
||||
plugin_author = None
|
||||
if metadata:
|
||||
plugin_author = metadata.extra.get('author')
|
||||
try:
|
||||
plugin_author = _module.__getattribute__("__plugin_author__")
|
||||
except AttributeError:
|
||||
plugin_author = None
|
||||
pass
|
||||
if matcher.plugin_name in plugins_manager.keys():
|
||||
plugins_manager.set_module_data(matcher.plugin_name, "error", False)
|
||||
if matcher.plugin_name not in plugins_manager.keys():
|
||||
@@ -61,7 +72,8 @@ def init_plugins_data(data_path):
|
||||
author=plugin_author,
|
||||
version=plugin_version,
|
||||
)
|
||||
elif plugins_manager[matcher.plugin_name]["version"] is None or (
|
||||
# metadata不检测version
|
||||
elif isinstance(plugin_version, str) or plugins_manager[matcher.plugin_name]["version"] is None or (
|
||||
plugin_version is not None
|
||||
and plugin_version > plugins_manager[matcher.plugin_name]["version"]
|
||||
):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -26,18 +26,8 @@ def init_plugins_resources():
|
||||
else:
|
||||
path = Path(_module.__getattribute__("__file__")).parent
|
||||
for resource in resources.keys():
|
||||
resources_manager.add_resource(matcher.plugin_name, path / resource, resources[resource])
|
||||
resources_manager.add_resource(
|
||||
matcher.plugin_name, path / resource, resources[resource]
|
||||
)
|
||||
resources_manager.save()
|
||||
resources_manager.start_move()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16,28 +16,37 @@ def init_plugins_settings(data_path: str):
|
||||
"""
|
||||
plugins2settings_file = data_path / "configs" / "plugins2settings.yaml"
|
||||
plugins2settings_file.parent.mkdir(exist_ok=True, parents=True)
|
||||
_matchers = get_matchers()
|
||||
_matchers = get_matchers(True)
|
||||
_tmp_module = {}
|
||||
_tmp = []
|
||||
for x in plugins2settings_manager.keys():
|
||||
try:
|
||||
_plugin = nonebot.plugin.get_plugin(x)
|
||||
_module = _plugin.module
|
||||
plugin_name = _module.__getattribute__("__zx_plugin_name__")
|
||||
metadata = _plugin.metadata
|
||||
plugin_name = (
|
||||
metadata.name
|
||||
if metadata
|
||||
else _module.__getattribute__("__zx_plugin_name__")
|
||||
)
|
||||
_tmp_module[x] = plugin_name
|
||||
except (KeyError, AttributeError) as e:
|
||||
logger.warning(f"配置文件 模块:{x} 获取 plugin_name 失败...{e}")
|
||||
_tmp_module[x] = ""
|
||||
for matcher in _matchers:
|
||||
if matcher.plugin_name not in plugins2settings_manager.keys():
|
||||
_plugin = nonebot.plugin.get_plugin(matcher.plugin_name)
|
||||
_plugin = matcher.plugin
|
||||
metadata = _plugin.metadata
|
||||
try:
|
||||
_module = _plugin.module
|
||||
except AttributeError:
|
||||
logger.warning(f"插件 {matcher.plugin_name} 加载失败...,插件控制未加载.")
|
||||
else:
|
||||
try:
|
||||
plugin_name = _module.__getattribute__("__zx_plugin_name__")
|
||||
if metadata:
|
||||
plugin_name = metadata.name
|
||||
else:
|
||||
plugin_name = _module.__getattribute__("__zx_plugin_name__")
|
||||
if "[admin]" in plugin_name.lower():
|
||||
try:
|
||||
admin_settings = _module.__getattribute__(
|
||||
@@ -66,43 +75,41 @@ def init_plugins_settings(data_path: str):
|
||||
f"获取插件 {matcher.plugin_name} __zx_plugin_name__ 失败...,插件控制未加载."
|
||||
)
|
||||
else:
|
||||
_tmp_module[matcher.plugin_name] = plugin_name
|
||||
try:
|
||||
_tmp_module[matcher.plugin_name] = plugin_name
|
||||
plugin_settings = _module.__getattribute__(
|
||||
"__plugin_settings__"
|
||||
)
|
||||
if plugin_settings.get('cost_gold') is None:
|
||||
plugin_settings['cost_gold'] = 0
|
||||
if (
|
||||
plugin_settings.get("cmd") is not None
|
||||
and plugin_name not in plugin_settings["cmd"]
|
||||
):
|
||||
plugin_settings["cmd"].append(plugin_name)
|
||||
if plugins2settings_manager.get(
|
||||
matcher.plugin_name
|
||||
) and plugins2settings_manager[matcher.plugin_name].get(
|
||||
"plugin_type"
|
||||
):
|
||||
plugin_type = tuple(
|
||||
plugins2settings_manager.get_plugin_data(
|
||||
matcher.plugin_name
|
||||
)["plugin_type"]
|
||||
)
|
||||
else:
|
||||
try:
|
||||
plugin_type = _module.__getattribute__(
|
||||
"__plugin_type__"
|
||||
)
|
||||
except AttributeError:
|
||||
plugin_type = ("normal",)
|
||||
if plugin_settings and matcher.plugin_name:
|
||||
plugins2settings_manager.add_plugin_settings(
|
||||
matcher.plugin_name,
|
||||
plugin_type=plugin_type,
|
||||
**plugin_settings,
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
plugin_settings = {"cmd": [matcher.plugin_name, plugin_name]}
|
||||
if not plugin_settings.get("cost_gold"):
|
||||
plugin_settings["cost_gold"] = 0
|
||||
if (
|
||||
plugin_settings.get("cmd") is not None
|
||||
and plugin_name not in plugin_settings["cmd"]
|
||||
):
|
||||
plugin_settings["cmd"].append(plugin_name)
|
||||
if plugins2settings_manager.get(
|
||||
matcher.plugin_name
|
||||
) and plugins2settings_manager[matcher.plugin_name].get(
|
||||
"plugin_type"
|
||||
):
|
||||
plugin_type = tuple(
|
||||
plugins2settings_manager.get_plugin_data(
|
||||
matcher.plugin_name
|
||||
)["plugin_type"]
|
||||
)
|
||||
else:
|
||||
try:
|
||||
plugin_type = _module.__getattribute__("__plugin_type__")
|
||||
except AttributeError:
|
||||
plugin_type = ("normal",)
|
||||
if plugin_settings and matcher.plugin_name:
|
||||
plugins2settings_manager.add_plugin_settings(
|
||||
matcher.plugin_name,
|
||||
plugin_type=plugin_type,
|
||||
**plugin_settings,
|
||||
)
|
||||
_tmp.append(matcher.plugin_name)
|
||||
_tmp_data = {"PluginSettings": plugins2settings_manager.get_data()}
|
||||
with open(plugins2settings_file, "w", encoding="utf8") as wf:
|
||||
|
||||
@@ -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, rule=lambda: 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
|
||||
+33
-26
@@ -1,3 +1,5 @@
|
||||
import random
|
||||
|
||||
from asyncpg.exceptions import (
|
||||
DuplicateColumnError,
|
||||
UndefinedColumnError,
|
||||
@@ -13,6 +15,7 @@ from configs.path_config import TEXT_PATH
|
||||
from asyncio.exceptions import TimeoutError
|
||||
from typing import List
|
||||
from utils.http_utils import AsyncHttpx
|
||||
from utils.manager import GDict
|
||||
from utils.utils import scheduler
|
||||
import nonebot
|
||||
|
||||
@@ -108,11 +111,38 @@ async def _():
|
||||
"ALTER TABLE genshin ADD bind_group Integer;",
|
||||
"genshin"
|
||||
), # 新增原神群号绑定字段
|
||||
(
|
||||
"ALTER TABLE genshin ADD login_ticket VARCHAR(255) DEFAULT '';",
|
||||
"genshin"
|
||||
), # 新增米游社login_ticket绑定字段
|
||||
(
|
||||
"ALTER TABLE genshin ADD stuid VARCHAR(255) DEFAULT '';",
|
||||
"genshin"
|
||||
), # 新增米游社stuid绑定字段
|
||||
(
|
||||
"ALTER TABLE genshin ADD stoken VARCHAR(255) DEFAULT '';",
|
||||
"genshin"
|
||||
), # 新增米游社stoken绑定字段
|
||||
(
|
||||
"ALTER TABLE chat_history ADD plain_text Text;",
|
||||
"chat_history"
|
||||
), # 新增纯文本
|
||||
(
|
||||
"ALTER TABLE goods_info ADD daily_limit Integer DEFAULT 0;",
|
||||
"goods_info"
|
||||
), # 新增纯文本
|
||||
(
|
||||
"ALTER TABLE goods_info ADD daily_purchase_limit Json DEFAULT '{}';",
|
||||
"goods_info"
|
||||
), # 新增纯文本
|
||||
]
|
||||
for sql in sql_str:
|
||||
for sql in sql_str + GDict.get('run_sql', []):
|
||||
try:
|
||||
flag = sql[1]
|
||||
sql = sql[0]
|
||||
if isinstance(sql, str):
|
||||
flag = f'{random.randint(1, 10000)}'
|
||||
else:
|
||||
flag = sql[1]
|
||||
sql = sql[0]
|
||||
query = db.text(sql)
|
||||
await db.first(query)
|
||||
logger.info(f"完成sql操作:{sql}")
|
||||
@@ -124,29 +154,6 @@ async def _():
|
||||
# bag_user 将文本转为字典格式
|
||||
await __database_script(_flag)
|
||||
|
||||
# 完成后
|
||||
end_sql_str = [
|
||||
# "ALTER TABLE bag_users DROP COLUMN props;" # 删除 bag_users 的 props 字段(还不到时候)
|
||||
]
|
||||
for sql in end_sql_str:
|
||||
try:
|
||||
query = db.text(sql)
|
||||
await db.first(query)
|
||||
logger.info(f"完成执行sql操作:{sql}")
|
||||
except (DuplicateColumnError, UndefinedColumnError):
|
||||
pass
|
||||
except PostgresSyntaxError:
|
||||
logger.error(f"语法错误:执行sql失败:{sql}")
|
||||
|
||||
# str2json_sql = ["alter table bag_users alter COLUMN props type json USING props::json;"] # 字段类型替换
|
||||
# rename_sql = 'alter table {} rename {} to {};' # 字段更名
|
||||
# for sql in str2json_sql:
|
||||
# try:
|
||||
# query = db.text(sql)
|
||||
# await db.first(query)
|
||||
# except DuplicateColumnError:
|
||||
# pass
|
||||
|
||||
|
||||
@driver.on_bot_connect
|
||||
async def _(bot: Bot):
|
||||
|
||||
@@ -77,7 +77,11 @@ async def _(event: GroupMessageEvent, arg: Message = CommandArg()):
|
||||
await BagUser.get_gold(event.user_id, event.group_id)
|
||||
) < goods.goods_price * num * goods.goods_discount:
|
||||
await buy.finish("您的金币好像不太够哦", at_sender=True)
|
||||
flag, n = await GoodsInfo.check_user_daily_purchase(goods, event.user_id, event.group_id, num)
|
||||
if flag:
|
||||
await buy.finish(f"该次购买将超过每日次数限制,目前该道具还可以购买{n}次哦", at_sender=True)
|
||||
if await BagUser.buy_property(event.user_id, event.group_id, goods, num):
|
||||
await GoodsInfo.add_user_daily_purchase(goods, event.user_id, event.group_id, num)
|
||||
await buy.send(
|
||||
f"花费 {goods.goods_price * num * goods.goods_discount} 金币购买 {goods.goods_name} ×{num} 成功!",
|
||||
at_sender=True,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from .data_source import create_shop_help, delete_goods, update_goods, register_goods, parse_goods_info
|
||||
from .data_source import create_shop_help, delete_goods, update_goods, register_goods, parse_goods_info, GoodsInfo
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent, Message
|
||||
from nonebot import on_command
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from utils.message_builder import image
|
||||
from nonebot.permission import SUPERUSER
|
||||
from utils.utils import is_number
|
||||
from utils.utils import is_number, scheduler
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.plugin import export
|
||||
from services.log import logger
|
||||
@@ -128,6 +128,18 @@ async def _(event: MessageEvent, arg: Message = CommandArg()):
|
||||
await shop_update_goods.send(f"修改商品 {name} 成功了...\n{text}", at_sender=True)
|
||||
logger.info(f"USER {event.user_id} 修改商品 {name} 数据 {text} 成功")
|
||||
else:
|
||||
await shop_update_goods.send(f"修改商品 {name} 失败了...", at_sender=True)
|
||||
await shop_update_goods.send(name, at_sender=True)
|
||||
logger.info(f"USER {event.user_id} 修改商品 {name} 数据 {text} 失败")
|
||||
|
||||
|
||||
@scheduler.scheduled_job(
|
||||
"cron",
|
||||
hour=0,
|
||||
minute=0,
|
||||
)
|
||||
async def _():
|
||||
try:
|
||||
await GoodsInfo.reset_daily_purchase()
|
||||
logger.info("商品每日限购次数重置成功...")
|
||||
except Exception as e:
|
||||
logger.error(f"商品每日限购次数重置发生错误 {type(e)}:{e}")
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
from PIL import Image
|
||||
|
||||
from models.goods_info import GoodsInfo
|
||||
from utils.image_utils import BuildImage
|
||||
from models.sign_group_user import SignGroupUser
|
||||
from utils.utils import is_number
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from typing import Optional, Union
|
||||
from typing import Optional, Union, Tuple
|
||||
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 +24,27 @@ 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"),
|
||||
daily_limit=(10, 20, 30),
|
||||
** {"好感度双倍加持卡Ⅰ_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()
|
||||
|
||||
|
||||
# 创建商店界面
|
||||
@@ -58,10 +65,9 @@ async def create_shop_help() -> str:
|
||||
_list.append(goods)
|
||||
A = BuildImage(1000, h, color="#f9f6f2")
|
||||
current_h = 0
|
||||
total_n = 0
|
||||
for goods in _list:
|
||||
bk = BuildImage(
|
||||
700, 80, font_size=15, color="#f9f6f2", font="CJGaoDeGuo.otf"
|
||||
)
|
||||
bk = BuildImage(1180, 80, font_size=15, color="#f9f6f2", font="CJGaoDeGuo.otf")
|
||||
goods_image = BuildImage(
|
||||
600, 80, font_size=20, color="#a29ad6", font="CJGaoDeGuo.otf"
|
||||
)
|
||||
@@ -73,16 +79,27 @@ async def create_shop_help() -> str:
|
||||
)
|
||||
await name_image.aline((380, -5, 280, 45), "#a29ad6", 5)
|
||||
await name_image.atext((390, 0), "售价:", center_type="by_height")
|
||||
await name_image.atext(
|
||||
(440, 0), str(goods.goods_price), (255, 255, 255), center_type="by_height"
|
||||
)
|
||||
if goods.goods_discount != 1:
|
||||
discount_price = int(goods.goods_discount * goods.goods_price)
|
||||
old_price_image = BuildImage(0, 0, plain_text=str(goods.goods_price), font_color=(194, 194, 194), font="CJGaoDeGuo.otf", font_size=15)
|
||||
await old_price_image.aline((0, int(old_price_image.h / 2), old_price_image.w + 1, int(old_price_image.h / 2)), (0, 0, 0))
|
||||
await name_image.apaste(
|
||||
old_price_image, (440, 0), True
|
||||
)
|
||||
await name_image.atext(
|
||||
(440, 15), str(discount_price), (255, 255, 255)
|
||||
)
|
||||
else:
|
||||
await name_image.atext(
|
||||
(440, 0), str(goods.goods_price), (255, 255, 255), center_type="by_height"
|
||||
)
|
||||
await name_image.atext(
|
||||
(
|
||||
440
|
||||
+ BuildImage(0, 0, plain_text=str(goods.goods_price), font_size=25).w,
|
||||
0,
|
||||
),
|
||||
" 金币",
|
||||
f" 金币",
|
||||
center_type="by_height",
|
||||
)
|
||||
await name_image.acircle_corner(5)
|
||||
@@ -90,48 +107,97 @@ async def create_shop_help() -> str:
|
||||
await goods_image.atext((15, 50), f"简介:{goods.goods_description}")
|
||||
await goods_image.acircle_corner(20)
|
||||
await bk.apaste(goods_image, alpha=True)
|
||||
n = 0
|
||||
_w = 550
|
||||
# 添加限时图标和时间
|
||||
if goods.goods_limit_time > 0:
|
||||
_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()
|
||||
n += 140
|
||||
_limit_time_logo = BuildImage(
|
||||
40, 40, background=f"{IMAGE_PATH}/other/time.png"
|
||||
)
|
||||
await bk.apaste(_limit_time_logo, (_w + 50, 0), True)
|
||||
await bk.apaste(
|
||||
BuildImage(0, 0, plain_text="限时!", font_size=23, font="CJGaoDeGuo.otf"),
|
||||
(_w + 90, 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] + "分"
|
||||
await bk.atext((605, 38), str(y_m_d))
|
||||
await bk.atext((615, 57), str(h_m))
|
||||
await bk.aline((550, -1, 710, -1), "#a29ad6", 5)
|
||||
await bk.aline((550, 80, 710, 80), "#a29ad6", 5)
|
||||
await bk.atext((_w + 55, 38), str(y_m_d))
|
||||
await bk.atext((_w + 65, 57), str(h_m))
|
||||
_w += 140
|
||||
if goods.goods_discount != 1:
|
||||
n += 140
|
||||
_discount_logo = BuildImage(30, 30, background=f"{IMAGE_PATH}/other/discount.png")
|
||||
await bk.apaste(_discount_logo, (_w + 50, 10), True)
|
||||
await bk.apaste(
|
||||
BuildImage(0, 0, plain_text="折扣!", font_size=23, font="CJGaoDeGuo.otf"),
|
||||
(_w + 90, 15),
|
||||
True,
|
||||
)
|
||||
await bk.apaste(
|
||||
BuildImage(0, 0, plain_text=f"{10 * goods.goods_discount:.1f} 折", font_size=30, font="CJGaoDeGuo.otf", font_color=(85, 156, 75)),
|
||||
(_w + 50, 44),
|
||||
True,
|
||||
)
|
||||
_w += 140
|
||||
if goods.daily_limit != 0:
|
||||
n += 140
|
||||
_daily_limit_logo = BuildImage(35, 35, background=f"{IMAGE_PATH}/other/daily_limit.png")
|
||||
await bk.apaste(_daily_limit_logo, (_w + 50, 10), True)
|
||||
await bk.apaste(
|
||||
BuildImage(0, 0, plain_text="限购!", font_size=23, font="CJGaoDeGuo.otf"),
|
||||
(_w + 90, 20),
|
||||
True,
|
||||
)
|
||||
await bk.apaste(
|
||||
BuildImage(0, 0, plain_text=f"{goods.daily_limit}", font_size=30, font="CJGaoDeGuo.otf"),
|
||||
(_w + 72, 45),
|
||||
True,
|
||||
)
|
||||
if total_n < n:
|
||||
total_n = n
|
||||
if n:
|
||||
await bk.aline((550, -1, 550 + n, -1), "#a29ad6", 5)
|
||||
await bk.aline((550, 80, 550 + n, 80), "#a29ad6", 5)
|
||||
|
||||
# 添加限时图标和时间
|
||||
idx += 1
|
||||
await A.apaste(bk, (0, current_h), True)
|
||||
current_h += 90
|
||||
w = 1000
|
||||
w = 850
|
||||
if total_n:
|
||||
w += total_n
|
||||
h = A.h + 230 + 100
|
||||
h = 1000 if h < 1000 else h
|
||||
shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png")
|
||||
shop = BuildImage(w, h, font_size=20, color="#f9f6f2")
|
||||
shop.paste(A, (20, 230))
|
||||
zx_img = BuildImage(0, 0, background=f"{IMAGE_PATH}/zhenxun/toukan.png")
|
||||
zx_img = BuildImage(0, 0, background=f"{IMAGE_PATH}/zhenxun/toukan_3.png")
|
||||
zx_img.transpose(Image.FLIP_LEFT_RIGHT)
|
||||
zx_img.replace_color_tran(((240, 240, 240), (255, 255, 255)), (249, 246, 242))
|
||||
await shop.apaste(zx_img, (780, 100))
|
||||
await shop.apaste(zx_img, (0, 100))
|
||||
shop.paste(A, (20 + zx_img.w, 230))
|
||||
await shop.apaste(shop_logo, (450, 30), True)
|
||||
shop.text(
|
||||
(int((1000 - shop.getsize("注【通过 序号 或者 商品名称 购买】")[0]) / 2), 170),
|
||||
"注【通过 序号 或者 商品名称 购买】",
|
||||
)
|
||||
shop.text((20, h - 100), "神秘药水\t\t售价:9999999金币\n\t\t鬼知道会有什么效果~")
|
||||
shop.text((20 + zx_img.w, h - 100), "神秘药水\t\t售价:9999999金币\n\t\t鬼知道会有什么效果~")
|
||||
return shop.pic2bs4()
|
||||
|
||||
|
||||
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,
|
||||
daily_limit: Optional[int] = 0,
|
||||
) -> bool:
|
||||
"""
|
||||
添加商品
|
||||
例如: 折扣:可选参数↓ 限时时间:可选,单位为小时
|
||||
@@ -142,21 +208,21 @@ async def register_goods(
|
||||
:param des: 商品简介
|
||||
:param discount: 商品折扣
|
||||
:param limit_time: 商品限时销售时间,单位为小时
|
||||
:param kwargs: kwargs
|
||||
:param daily_limit: 每日购买次数限制
|
||||
: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, daily_limit
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
# 删除商品
|
||||
@@ -184,7 +250,7 @@ async def delete_goods(name: str, id_: int) -> "str, str, int":
|
||||
|
||||
|
||||
# 更新商品信息
|
||||
async def update_goods(**kwargs) -> "str, str, int":
|
||||
async def update_goods(**kwargs) -> Tuple[bool, str, str]:
|
||||
"""
|
||||
更新商品信息
|
||||
:param kwargs: kwargs
|
||||
@@ -194,17 +260,18 @@ async def update_goods(**kwargs) -> "str, str, int":
|
||||
goods_lst = await GoodsInfo.get_all_goods()
|
||||
if is_number(kwargs["name"]):
|
||||
if int(kwargs["name"]) < 1 or int(kwargs["name"]) > len(goods_lst):
|
||||
return "序号错误,没有该序号的商品...", "", 999
|
||||
return False, "序号错误,没有该序号的商品...", ""
|
||||
goods = goods_lst[int(kwargs["name"]) - 1]
|
||||
else:
|
||||
goods = await GoodsInfo.get_goods_info(kwargs["name"])
|
||||
if not goods:
|
||||
return "名称错误,没有该名称的商品...", "", 999
|
||||
name = goods.goods_name
|
||||
return False, "名称错误,没有该名称的商品...", ""
|
||||
name: str = goods.goods_name
|
||||
price = goods.goods_price
|
||||
des = goods.goods_description
|
||||
discount = goods.goods_discount
|
||||
limit_time = goods.goods_limit_time
|
||||
daily_limit = goods.daily_limit
|
||||
new_time = 0
|
||||
tmp = ""
|
||||
if kwargs.get("price"):
|
||||
@@ -221,20 +288,25 @@ async def update_goods(**kwargs) -> "str, str, int":
|
||||
new_time = time.strftime(
|
||||
"%Y-%m-%d %H:%M:%S",
|
||||
time.localtime(time.time() + kwargs["limit_time"] * 60 * 60),
|
||||
)
|
||||
tmp += f"限时至: {new_time}\n"
|
||||
) if kwargs["limit_time"] != 0 else 0
|
||||
tmp += f"限时至: {new_time}\n" if new_time else "取消了限时\n"
|
||||
limit_time = kwargs["limit_time"]
|
||||
return (
|
||||
await GoodsInfo.update_goods(
|
||||
name,
|
||||
int(price),
|
||||
des,
|
||||
float(discount),
|
||||
int(time.time() + limit_time * 60 * 60 if limit_time != 0 and new_time else 0),
|
||||
),
|
||||
if kwargs.get("daily_limit"):
|
||||
tmp += f'每日购买限制:{daily_limit} --> {kwargs["daily_limit"]}\n' if daily_limit else "取消了购买限制\n"
|
||||
daily_limit = int(kwargs["daily_limit"])
|
||||
await GoodsInfo.update_goods(
|
||||
name,
|
||||
tmp[:-1],
|
||||
int(price),
|
||||
des,
|
||||
float(discount),
|
||||
int(
|
||||
time.time() + limit_time * 60 * 60
|
||||
if limit_time != 0 and new_time
|
||||
else 0
|
||||
),
|
||||
daily_limit
|
||||
)
|
||||
return True, name, tmp[:-1],
|
||||
|
||||
|
||||
def parse_goods_info(msg: str) -> Union[dict, str]:
|
||||
@@ -264,6 +336,10 @@ def parse_goods_info(msg: str) -> Union[dict, str]:
|
||||
data["discount"] = sp[1]
|
||||
elif sp[0] == "limit_time":
|
||||
if not is_number(sp[1]) or float(sp[1]) < 0:
|
||||
return "limit_time参数不合法,必须大于0!"
|
||||
return "limit_time参数不合法,必须为数字且大于0!"
|
||||
data["limit_time"] = sp[1]
|
||||
elif sp[0] == "daily_limit":
|
||||
if not is_number(sp[1]) or float(sp[1]) < 0:
|
||||
return "daily_limit参数不合法,必须为数字且大于0!"
|
||||
data["daily_limit"] = sp[1]
|
||||
return data
|
||||
|
||||
@@ -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} 的使用函数")
|
||||
|
||||
@@ -119,18 +119,18 @@ async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandAr
|
||||
)
|
||||
flag = await requests_manager.approve(bot, id_, "group")
|
||||
else:
|
||||
await friend_handle.send("同意群聊请求失败,未找到此id的请求..")
|
||||
await group_handle.send("同意群聊请求失败,未找到此id的请求..")
|
||||
else:
|
||||
flag = await requests_manager.refused(bot, id_, "group")
|
||||
if flag == 1:
|
||||
await friend_handle.send(f"{cmd[:2]}群聊请求失败,该请求已失效..")
|
||||
await group_handle.send(f"{cmd[:2]}群聊请求失败,该请求已失效..")
|
||||
requests_manager.delete_request(id_, "group")
|
||||
elif flag == 2:
|
||||
await friend_handle.send(f"{cmd[:2]}群聊请求失败,未找到此id的请求..")
|
||||
await group_handle.send(f"{cmd[:2]}群聊请求失败,未找到此id的请求..")
|
||||
else:
|
||||
await friend_handle.send(f"{cmd[:2]}群聊请求成功!")
|
||||
await group_handle.send(f"{cmd[:2]}群聊请求成功!")
|
||||
else:
|
||||
await friend_handle.send("id必须为纯数字!")
|
||||
await group_handle.send("id必须为纯数字!")
|
||||
|
||||
|
||||
@cls_request.handle()
|
||||
@@ -150,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]):
|
||||
@@ -137,9 +143,7 @@ async def _():
|
||||
async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()):
|
||||
cmd = cmd[0]
|
||||
msg = arg.extract_plain_text().strip().split()
|
||||
all_group = [
|
||||
g["group_id"] for g in await bot.get_group_list()
|
||||
]
|
||||
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]}群认证..")
|
||||
|
||||
@@ -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,10 @@ from typing import Optional
|
||||
from pathlib import Path
|
||||
from .utils import ConfigsManager
|
||||
|
||||
import platform
|
||||
if platform.system() == "Linux":
|
||||
import os
|
||||
hostip = os.popen("cat /etc/resolv.conf | grep nameserver | awk '{ print $2 }'").read().replace("\n","")
|
||||
|
||||
# 回复消息名称
|
||||
NICKNAME: str = "小真寻"
|
||||
@@ -18,6 +22,7 @@ port: str = "" # 数据库端口
|
||||
database: str = "" # 数据库名称
|
||||
|
||||
# 代理,例如 "http://127.0.0.1:7890"
|
||||
# 如果是WLS 可以 f"http://{hostip}:7890" 使用寄主机的代理
|
||||
SYSTEM_PROXY: Optional[str] = None # 全局代理
|
||||
|
||||
|
||||
|
||||
+51
-20
@@ -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:
|
||||
@@ -21,9 +22,24 @@ class ConfigsManager:
|
||||
if file.exists():
|
||||
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():
|
||||
with open(self._simple_file, "r", encoding="utf8") as f:
|
||||
self._simple_data = _yaml.load(f)
|
||||
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,
|
||||
@@ -78,18 +94,22 @@ class ConfigsManager:
|
||||
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 and self._data[module][key] != value:
|
||||
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()
|
||||
self.save(save_simple_data=save_simple_data)
|
||||
|
||||
def set_help(self, module: str, key: str, help_: str):
|
||||
"""
|
||||
@@ -103,7 +123,7 @@ class ConfigsManager:
|
||||
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: 模块名
|
||||
@@ -115,7 +135,9 @@ class ConfigsManager:
|
||||
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: 模块名
|
||||
@@ -123,12 +145,18 @@ class ConfigsManager:
|
||||
:param default: 没有key值内容的默认返回值
|
||||
"""
|
||||
key = key.upper()
|
||||
if module in self._data.keys():
|
||||
# 优先使用simple_data
|
||||
if module in self._simple_data.keys():
|
||||
for key in [key, f"{key} [LEVEL]"]:
|
||||
if self._data[module].get(key) is not None:
|
||||
if self._data[module][key]["value"] is None:
|
||||
return self._data[module][key]["default_value"]
|
||||
return self._data[module][key]["value"]
|
||||
if self._simple_data[module].get(key) is not None:
|
||||
return self._simple_data[module][key]
|
||||
# if module in self._data.keys():
|
||||
# if module in self._data.keys():
|
||||
# for key in [key, f"{key} [LEVEL]"]:
|
||||
# if self._data[module].get(key) is not None:
|
||||
# if self._data[module][key]["value"] is None:
|
||||
# return self._data[module][key]["value"]
|
||||
# return self._data[module][key]["default_value"]
|
||||
if default is not None:
|
||||
return default
|
||||
return None
|
||||
@@ -161,7 +189,11 @@ class ConfigsManager:
|
||||
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
|
||||
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:
|
||||
@@ -174,13 +206,12 @@ class ConfigsManager:
|
||||
重新加载配置文件
|
||||
"""
|
||||
_yaml = YAML()
|
||||
temp_file = Path() / "configs" / "config.yaml"
|
||||
if temp_file.exists():
|
||||
with open(temp_file, "r", encoding="utf8") as f:
|
||||
temp = _yaml.load(f)
|
||||
for key in temp.keys():
|
||||
for k in temp[key].keys():
|
||||
self._data[key][k]["value"] = temp[key][k]
|
||||
if self._simple_file.exists():
|
||||
with open(self._simple_file, "r", encoding="utf8") as f:
|
||||
self._simple_data = _yaml.load(f)
|
||||
for key in self._simple_data.keys():
|
||||
for k in self._simple_data[key].keys():
|
||||
self._data[key][k]["value"] = self._simple_data[key][k]
|
||||
self.save()
|
||||
|
||||
def get_admin_level_data(self):
|
||||
|
||||
+1450
-155
File diff suppressed because it is too large
Load Diff
+19
-19
@@ -22,9 +22,9 @@ class BagUser(db.Model):
|
||||
@classmethod
|
||||
async def get_user_total_gold(cls, user_qq: int, group_id: int) -> str:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取金币概况
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 所在群号
|
||||
"""
|
||||
@@ -44,9 +44,9 @@ class BagUser(db.Model):
|
||||
@classmethod
|
||||
async def get_gold(cls, user_qq: int, group_id: int) -> int:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取当前金币
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 所在群号
|
||||
"""
|
||||
@@ -64,9 +64,9 @@ class BagUser(db.Model):
|
||||
@classmethod
|
||||
async def get_property(cls, user_qq: int, group_id: int) -> Dict[str, int]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取当前道具
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 所在群号
|
||||
"""
|
||||
@@ -84,9 +84,9 @@ class BagUser(db.Model):
|
||||
@classmethod
|
||||
async def add_gold(cls, user_qq: int, group_id: int, num: int):
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
增加金币
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 所在群号
|
||||
:param num: 金币数量
|
||||
@@ -112,9 +112,9 @@ class BagUser(db.Model):
|
||||
@classmethod
|
||||
async def spend_gold(cls, user_qq: int, group_id: int, num: int):
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
花费金币
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 所在群号
|
||||
:param num: 金币数量
|
||||
@@ -140,9 +140,9 @@ class BagUser(db.Model):
|
||||
@classmethod
|
||||
async def add_property(cls, user_qq: int, group_id: int, name: str):
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
增加道具
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 所在群号
|
||||
:param name: 道具名称
|
||||
@@ -165,9 +165,9 @@ class BagUser(db.Model):
|
||||
cls, user_qq: int, group_id: int, name: str, num: int = 1
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
使用/删除 道具
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 所在群号
|
||||
:param name: 道具名称
|
||||
@@ -192,9 +192,9 @@ class BagUser(db.Model):
|
||||
cls, user_qq: int, group_id: int, goods: "GoodsInfo", goods_num: int
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
购买道具
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: 用户qq
|
||||
:param group_id: 所在群聊
|
||||
:param goods: 商品
|
||||
@@ -209,14 +209,14 @@ class BagUser(db.Model):
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"buy_property 发生错误 {type(e)}:{e}")
|
||||
return False
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def get_all_users(cls, group_id: Optional[int] = None) -> List["BagUser"]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取所有用户数据
|
||||
参数:
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
"""
|
||||
if not group_id:
|
||||
|
||||
+12
-12
@@ -15,9 +15,9 @@ class BanUser(db.Model):
|
||||
@classmethod
|
||||
async def check_ban_level(cls, user_qq: int, level: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
检测ban掉目标的用户与unban用户的权限等级大小
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: unban用户的qq号
|
||||
:param level: ban掉目标用户的权限等级
|
||||
"""
|
||||
@@ -31,9 +31,9 @@ class BanUser(db.Model):
|
||||
@classmethod
|
||||
async def check_ban_time(cls, user_qq: int) -> str:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
检测用户被ban时长
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
"""
|
||||
query = cls.query.where((cls.user_qq == user_qq))
|
||||
@@ -49,9 +49,9 @@ class BanUser(db.Model):
|
||||
@classmethod
|
||||
async def is_ban(cls, user_qq: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
判断用户是否被ban
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
"""
|
||||
if await cls.check_ban_time(user_qq):
|
||||
@@ -63,9 +63,9 @@ class BanUser(db.Model):
|
||||
@classmethod
|
||||
async def is_super_ban(cls, user_qq: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
判断用户是否被ban
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
"""
|
||||
user = await cls.query.where((cls.user_qq == user_qq)).gino.first()
|
||||
@@ -77,9 +77,9 @@ class BanUser(db.Model):
|
||||
@classmethod
|
||||
async def ban(cls, user_qq: int, ban_level: int, duration: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
ban掉目标用户
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: 目标用户qq号
|
||||
:param ban_level: 使用ban命令用户的权限
|
||||
:param duration: ban时长,秒
|
||||
@@ -104,9 +104,9 @@ class BanUser(db.Model):
|
||||
@classmethod
|
||||
async def unban(cls, user_qq: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
unban用户
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
"""
|
||||
query = cls.query.where((cls.user_qq == user_qq))
|
||||
|
||||
+21
-21
@@ -11,12 +11,13 @@ class ChatHistory(db.Model):
|
||||
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):
|
||||
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, create_time=datetime.now()
|
||||
user_qq=user_qq, group_id=group_id, text=text, plain_text=plain_text, create_time=datetime.now()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -27,9 +28,9 @@ class ChatHistory(db.Model):
|
||||
days: Optional[int] = None,
|
||||
) -> List["ChatHistory"]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取用户消息
|
||||
参数:
|
||||
参数:
|
||||
:param uid: 用户qq
|
||||
:param msg_type: 消息类型,私聊或群聊
|
||||
:param days: 限制日期
|
||||
@@ -45,9 +46,9 @@ class ChatHistory(db.Model):
|
||||
date_scope: Tuple[datetime, datetime] = None,
|
||||
) -> List["ChatHistory"]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取群聊指定用户聊天记录
|
||||
参数:
|
||||
参数:
|
||||
:param uid: qq
|
||||
:param gid: 群号
|
||||
:param limit: 获取数量
|
||||
@@ -62,9 +63,9 @@ class ChatHistory(db.Model):
|
||||
@classmethod
|
||||
async def get_group_user_msg_count(cls, uid: int, gid: int) -> Optional[int]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
查询群聊指定用户的聊天记录数量
|
||||
参数:
|
||||
参数:
|
||||
:param uid: qq
|
||||
:param gid: 群号
|
||||
"""
|
||||
@@ -85,9 +86,9 @@ class ChatHistory(db.Model):
|
||||
date_scope: Optional[Tuple[datetime, datetime]] = None,
|
||||
) -> Optional[Tuple[int, int]]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取排行数据
|
||||
参数:
|
||||
参数:
|
||||
:param gid: 群号
|
||||
:param limit: 获取数量
|
||||
:param order: 排序类型,desc,des
|
||||
@@ -97,15 +98,14 @@ class ChatHistory(db.Model):
|
||||
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}"
|
||||
print(sql)
|
||||
return await db.all(db.text(sql))
|
||||
|
||||
@classmethod
|
||||
async def get_group_first_msg_datetime(cls, gid: int) -> Optional[datetime]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取群第一条记录消息时间
|
||||
参数:
|
||||
参数:
|
||||
:param gid:
|
||||
"""
|
||||
if (
|
||||
@@ -124,9 +124,9 @@ class ChatHistory(db.Model):
|
||||
days: Optional[int] = None,
|
||||
) -> int:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取用户消息数量
|
||||
参数:
|
||||
参数:
|
||||
:param uid: 用户qq
|
||||
:param msg_type: 消息类型,私聊或群聊
|
||||
:param days: 限制日期
|
||||
@@ -142,9 +142,9 @@ class ChatHistory(db.Model):
|
||||
days: Optional[int] = None,
|
||||
) -> List["ChatHistory"]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取群聊消息
|
||||
参数:
|
||||
参数:
|
||||
:param gid: 用户qq
|
||||
:param days: 限制日期
|
||||
"""
|
||||
@@ -157,9 +157,9 @@ class ChatHistory(db.Model):
|
||||
days: Optional[int] = None,
|
||||
) -> List["ChatHistory"]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取群聊消息数量
|
||||
参数:
|
||||
参数:
|
||||
:param gid: 用户qq
|
||||
:param days: 限制日期
|
||||
"""
|
||||
@@ -178,9 +178,9 @@ class ChatHistory(db.Model):
|
||||
is_select_count: bool = False,
|
||||
):
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取消息查询query
|
||||
参数:
|
||||
参数:
|
||||
:param uid: 用户qq
|
||||
:param gid: 群号
|
||||
:param type_: 类型,私聊或群聊
|
||||
|
||||
+10
-10
@@ -15,9 +15,9 @@ class FriendUser(db.Model):
|
||||
@classmethod
|
||||
async def get_user_name(cls, user_id: int) -> str:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取好友用户名称
|
||||
参数:
|
||||
参数:
|
||||
:param user_id: qq号
|
||||
"""
|
||||
query = cls.query.where(cls.user_id == user_id)
|
||||
@@ -30,9 +30,9 @@ class FriendUser(db.Model):
|
||||
@classmethod
|
||||
async def add_friend_info(cls, user_id: int, user_name: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
添加好友信息
|
||||
参数:
|
||||
参数:
|
||||
:param user_id: qq号
|
||||
:param user_name: 用户名称
|
||||
"""
|
||||
@@ -55,9 +55,9 @@ class FriendUser(db.Model):
|
||||
@classmethod
|
||||
async def delete_friend_info(cls, user_id: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
删除好友信息
|
||||
参数:
|
||||
参数:
|
||||
:param user_id: qq号
|
||||
"""
|
||||
try:
|
||||
@@ -72,9 +72,9 @@ class FriendUser(db.Model):
|
||||
@classmethod
|
||||
async def get_friend_nickname(cls, user_id: int) -> str:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取用户昵称
|
||||
参数:
|
||||
参数:
|
||||
:param user_id: qq号
|
||||
"""
|
||||
query = cls.query.where(cls.user_id == user_id)
|
||||
@@ -92,9 +92,9 @@ class FriendUser(db.Model):
|
||||
@classmethod
|
||||
async def set_friend_nickname(cls, user_id: int, nickname: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
设置用户昵称
|
||||
参数:
|
||||
参数:
|
||||
:param user_id: qq号
|
||||
:param nickname: 昵称
|
||||
"""
|
||||
|
||||
+103
-37
@@ -1,5 +1,5 @@
|
||||
from services.db_context import db
|
||||
from typing import Optional, List
|
||||
from typing import Optional, List, Tuple
|
||||
from services.log import logger
|
||||
|
||||
|
||||
@@ -12,27 +12,33 @@ class GoodsInfo(db.Model):
|
||||
goods_description = db.Column(db.TEXT(), nullable=False) # 商品描述
|
||||
goods_discount = db.Column(db.Numeric(scale=3, asdecimal=False), default=1) # 打折
|
||||
goods_limit_time = db.Column(db.BigInteger(), default=0) # 限时
|
||||
daily_limit = db.Column(db.Integer(), nullable=False, default=0) # 每日购买限制
|
||||
daily_purchase_limit = db.Column(
|
||||
db.JSON(), nullable=False, default={}
|
||||
) # 每日购买限制数据存储
|
||||
|
||||
_idx1 = db.Index("goods_group_users_idx1", "goods_name", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def add_goods(
|
||||
cls,
|
||||
goods_name: str,
|
||||
goods_price: int,
|
||||
goods_description: str,
|
||||
goods_discount: float = 1,
|
||||
goods_limit_time: int = 0,
|
||||
cls,
|
||||
goods_name: str,
|
||||
goods_price: int,
|
||||
goods_description: str,
|
||||
goods_discount: float = 1,
|
||||
goods_limit_time: int = 0,
|
||||
daily_limit: int = 0,
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
添加商品
|
||||
参数:
|
||||
参数:
|
||||
:param goods_name: 商品名称
|
||||
:param goods_price: 商品价格
|
||||
:param goods_description: 商品简介
|
||||
:param goods_discount: 商品折扣
|
||||
:param goods_limit_time: 商品限时
|
||||
:param daily_limit: 每日购买限制
|
||||
"""
|
||||
try:
|
||||
if not await cls.get_goods_info(goods_name):
|
||||
@@ -42,6 +48,7 @@ class GoodsInfo(db.Model):
|
||||
goods_description=goods_description,
|
||||
goods_discount=goods_discount,
|
||||
goods_limit_time=goods_limit_time,
|
||||
daily_limit=daily_limit,
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
@@ -51,15 +58,15 @@ class GoodsInfo(db.Model):
|
||||
@classmethod
|
||||
async def delete_goods(cls, goods_name: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
删除商品
|
||||
参数:
|
||||
参数:
|
||||
:param goods_name: 商品名称
|
||||
"""
|
||||
query = (
|
||||
await cls.query.where(cls.goods_name == goods_name)
|
||||
.with_for_update()
|
||||
.gino.first()
|
||||
.with_for_update()
|
||||
.gino.first()
|
||||
)
|
||||
if not query:
|
||||
return False
|
||||
@@ -68,59 +75,59 @@ class GoodsInfo(db.Model):
|
||||
|
||||
@classmethod
|
||||
async def update_goods(
|
||||
cls,
|
||||
goods_name: str,
|
||||
goods_price: Optional[int] = None,
|
||||
goods_description: Optional[str] = None,
|
||||
goods_discount: Optional[float] = None,
|
||||
goods_limit_time: Optional[int] = None,
|
||||
cls,
|
||||
goods_name: str,
|
||||
goods_price: Optional[int] = None,
|
||||
goods_description: Optional[str] = None,
|
||||
goods_discount: Optional[float] = None,
|
||||
goods_limit_time: Optional[int] = None,
|
||||
daily_limit: Optional[int] = None
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
更新商品信息
|
||||
参数:
|
||||
参数:
|
||||
:param goods_name: 商品名称
|
||||
:param goods_price: 商品价格
|
||||
:param goods_description: 商品简介
|
||||
:param goods_discount: 商品折扣
|
||||
:param goods_limit_time: 商品限时时间
|
||||
:param daily_limit: 每日次数限制
|
||||
"""
|
||||
try:
|
||||
query = (
|
||||
await cls.query.where(cls.goods_name == goods_name)
|
||||
.with_for_update()
|
||||
.gino.first()
|
||||
.with_for_update()
|
||||
.gino.first()
|
||||
)
|
||||
if not query:
|
||||
return False
|
||||
if goods_price:
|
||||
await query.update(goods_price=goods_price).apply()
|
||||
if goods_description:
|
||||
await query.update(goods_description=goods_description).apply()
|
||||
if goods_discount:
|
||||
await query.update(goods_discount=goods_discount).apply()
|
||||
if goods_limit_time:
|
||||
await query.update(goods_limit_time=goods_limit_time).apply()
|
||||
await query.update(
|
||||
goods_price=goods_price or query.goods_price,
|
||||
goods_description=goods_description or query.goods_description,
|
||||
goods_discount=goods_discount or query.goods_discount,
|
||||
goods_limit_time=goods_limit_time if goods_limit_time is not None else query.goods_limit_time,
|
||||
daily_limit=daily_limit if daily_limit is not None else query.daily_limit,
|
||||
).apply()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"GoodsInfo update_goods 发生错误 {type(e)}:{e}")
|
||||
return False
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def get_goods_info(cls, goods_name: str) -> "GoodsInfo":
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取商品对象
|
||||
参数:
|
||||
参数:
|
||||
: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"]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获得全部有序商品对象
|
||||
"""
|
||||
query = await cls.query.gino.all()
|
||||
@@ -131,3 +138,62 @@ class GoodsInfo(db.Model):
|
||||
goods_lst.append([x for x in query if x.id == min_id][0])
|
||||
id_lst.remove(min_id)
|
||||
return goods_lst
|
||||
|
||||
@classmethod
|
||||
async def add_user_daily_purchase(
|
||||
cls, goods: "GoodsInfo", user_id: int, group_id: int, num: int = 1
|
||||
):
|
||||
"""
|
||||
说明:
|
||||
添加用户明日购买限制
|
||||
参数:
|
||||
:param goods: 商品
|
||||
:param user_id: 用户id
|
||||
:param group_id: 群号
|
||||
:param num: 数量
|
||||
"""
|
||||
user_id = str(user_id)
|
||||
group_id = str(group_id)
|
||||
if goods and goods.daily_limit and goods.daily_limit > 0:
|
||||
if not goods.daily_purchase_limit.get(group_id):
|
||||
goods.daily_purchase_limit[group_id] = {}
|
||||
if not goods.daily_purchase_limit[group_id].get(user_id):
|
||||
goods.daily_purchase_limit[group_id][user_id] = 0
|
||||
goods.daily_purchase_limit[group_id][user_id] += num
|
||||
await goods.update(daily_purchase_limit=goods.daily_purchase_limit).apply()
|
||||
|
||||
@classmethod
|
||||
async def check_user_daily_purchase(
|
||||
cls, goods: "GoodsInfo", user_id: int, group_id: int, num: int = 1
|
||||
) -> Tuple[bool, int]:
|
||||
"""
|
||||
说明:
|
||||
检测用户每日购买上限
|
||||
参数:
|
||||
:param goods: 商品
|
||||
:param user_id: 用户id
|
||||
:param group_id: 群号
|
||||
:param num: 数量
|
||||
"""
|
||||
user_id = str(user_id)
|
||||
group_id = str(group_id)
|
||||
if goods and goods.daily_limit > 0:
|
||||
if (
|
||||
not goods.daily_limit
|
||||
or not goods.daily_purchase_limit.get(group_id)
|
||||
or not goods.daily_purchase_limit[group_id].get(user_id)
|
||||
):
|
||||
return goods.daily_limit - num < 0, goods.daily_limit
|
||||
if goods.daily_purchase_limit[group_id][user_id] + num > goods.daily_limit:
|
||||
return (
|
||||
True,
|
||||
goods.daily_limit - goods.daily_purchase_limit[group_id][user_id],
|
||||
)
|
||||
return False, 0
|
||||
|
||||
@classmethod
|
||||
async def reset_daily_purchase(cls):
|
||||
"""
|
||||
重置每次次数限制
|
||||
"""
|
||||
await cls.update.values(daily_purchase_limit={}).gino.status()
|
||||
|
||||
@@ -17,9 +17,9 @@ class GroupInfo(db.Model):
|
||||
@classmethod
|
||||
async def get_group_info(cls, group_id: int) -> "GroupInfo":
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取群信息
|
||||
参数:
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
"""
|
||||
query = cls.query.where(cls.group_id == group_id)
|
||||
@@ -35,9 +35,9 @@ class GroupInfo(db.Model):
|
||||
group_flag: Optional[int] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
添加群信息
|
||||
参数:
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
:param group_name: 群名称
|
||||
:param max_member_count: 群员最大数量
|
||||
@@ -74,9 +74,9 @@ class GroupInfo(db.Model):
|
||||
@classmethod
|
||||
async def delete_group_info(cls, group_id: int):
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
删除群信息
|
||||
参数:
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
"""
|
||||
await cls.delete.where(cls.group_id == group_id).gino.status()
|
||||
@@ -84,7 +84,7 @@ class GroupInfo(db.Model):
|
||||
@classmethod
|
||||
async def get_all_group(cls) -> List["GroupInfo"]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取所有群对象
|
||||
"""
|
||||
query = await cls.query.gino.all()
|
||||
|
||||
+14
-14
@@ -27,9 +27,9 @@ class GroupInfoUser(db.Model):
|
||||
uid: Optional[int] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
添加群内用户信息
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
:param user_name: 用户名称
|
||||
@@ -57,9 +57,9 @@ class GroupInfoUser(db.Model):
|
||||
cls, user_qq: int, group_id: int
|
||||
) -> "GroupInfoUser":
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
查询群员信息
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
"""
|
||||
@@ -71,9 +71,9 @@ class GroupInfoUser(db.Model):
|
||||
@classmethod
|
||||
async def delete_member_info(cls, user_qq: int, group_id: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
删除群员信息
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
"""
|
||||
@@ -96,9 +96,9 @@ class GroupInfoUser(db.Model):
|
||||
@classmethod
|
||||
async def get_group_member_id_list(cls, group_id: int) -> List[int]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取该群所有用户qq
|
||||
参数:
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
"""
|
||||
member_list = []
|
||||
@@ -112,9 +112,9 @@ class GroupInfoUser(db.Model):
|
||||
cls, user_qq: int, group_id: int, nickname: str
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
设置群员在该群内的昵称
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
:param nickname: 昵称
|
||||
@@ -131,9 +131,9 @@ class GroupInfoUser(db.Model):
|
||||
@classmethod
|
||||
async def get_user_all_group(cls, user_qq: int) -> List[int]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取该用户所在的所有群聊
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: 用户qq
|
||||
"""
|
||||
query = await cls.query.where(cls.user_qq == user_qq).gino.all()
|
||||
@@ -144,9 +144,9 @@ class GroupInfoUser(db.Model):
|
||||
@classmethod
|
||||
async def get_group_member_nickname(cls, user_qq: int, group_id: int) -> str:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取用户在该群的昵称
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
"""
|
||||
|
||||
+25
-20
@@ -1,3 +1,5 @@
|
||||
from asyncpg import UniqueViolationError
|
||||
|
||||
from services.db_context import db
|
||||
|
||||
|
||||
@@ -15,9 +17,9 @@ class LevelUser(db.Model):
|
||||
@classmethod
|
||||
async def get_user_level(cls, user_qq: int, group_id: int) -> int:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取用户在群内的等级
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
"""
|
||||
@@ -33,9 +35,9 @@ class LevelUser(db.Model):
|
||||
cls, user_qq: int, group_id: int, level: int, group_flag: int = 0
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
设置用户在群内的权限
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
:param level: 权限等级
|
||||
@@ -44,24 +46,27 @@ 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
|
||||
async def delete_level(cls, user_qq: int, group_id: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
删除用户权限
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
"""
|
||||
@@ -77,9 +82,9 @@ class LevelUser(db.Model):
|
||||
@classmethod
|
||||
async def check_level(cls, user_qq: int, group_id: int, level: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
检查用户权限等级是否大于 level
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
:param level: 权限等级
|
||||
@@ -107,9 +112,9 @@ class LevelUser(db.Model):
|
||||
@classmethod
|
||||
async def is_group_flag(cls, user_qq: int, group_id: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
检测是否会被自动更新刷新权限
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
"""
|
||||
|
||||
@@ -80,9 +80,9 @@ class SignGroupUser(db.Model):
|
||||
@classmethod
|
||||
async def get_all_impression(cls, group_id: int) -> "list, list, list":
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取该群所有用户 id 及对应 好感度
|
||||
参数:
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
"""
|
||||
impression_list = []
|
||||
|
||||
@@ -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()
|
||||
|
||||
+33
-29
@@ -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())
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from nonebot import on_command
|
||||
from nonebot import on_command, on_regex
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent, Message
|
||||
|
||||
from utils.image_utils import text2image
|
||||
from utils.message_builder import image
|
||||
from .data_source import (
|
||||
add_live_sub,
|
||||
delete_sub,
|
||||
@@ -14,10 +17,10 @@ from .data_source import (
|
||||
from models.level_user import LevelUser
|
||||
from configs.config import Config
|
||||
from utils.utils import is_number, scheduler, get_bot
|
||||
from typing import Optional
|
||||
from typing import Optional, Tuple, Any
|
||||
from services.log import logger
|
||||
from nonebot import Driver
|
||||
from nonebot.params import CommandArg, ArgStr
|
||||
from nonebot.params import CommandArg, ArgStr, RegexGroup
|
||||
import nonebot
|
||||
|
||||
__zx_plugin_name__ = "B站订阅"
|
||||
@@ -38,7 +41,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,
|
||||
@@ -50,12 +53,22 @@ __plugin_configs__ = {
|
||||
"value": 5,
|
||||
"help": "群内bilibili订阅需要管理的权限",
|
||||
"default_value": 5,
|
||||
}
|
||||
},
|
||||
"LIVE_MSG_AT_ALL": {
|
||||
"value": False,
|
||||
"help": "直播提醒是否AT全体(仅在真寻是管理员时生效)",
|
||||
"default_value": False,
|
||||
},
|
||||
"UP_MSG_AT_ALL": {
|
||||
"value": False,
|
||||
"help": "UP动态投稿提醒是否AT全体(仅在真寻是管理员时生效)",
|
||||
"default_value": False,
|
||||
},
|
||||
}
|
||||
|
||||
add_sub = on_command("添加订阅", priority=5, block=True)
|
||||
del_sub = on_command("删除订阅", priority=5, block=True)
|
||||
show_sub_info = on_command("查看订阅", priority=5, block=True)
|
||||
del_sub = on_regex(r"^删除订阅(\d+)$", priority=5, block=True)
|
||||
show_sub_info = on_regex("^查看订阅$", priority=5, block=True)
|
||||
|
||||
driver: Driver = nonebot.get_driver()
|
||||
|
||||
@@ -144,10 +157,8 @@ async def _(
|
||||
|
||||
|
||||
@del_sub.handle()
|
||||
async def _(event: MessageEvent, arg: Message = CommandArg()):
|
||||
msg = arg.extract_plain_text().strip()
|
||||
if not is_number(msg):
|
||||
await del_sub.finish("Id必须为数字!", at_sender=True)
|
||||
async def _(event: MessageEvent, reg_group: Tuple[Any, ...] = RegexGroup()):
|
||||
msg = reg_group[0]
|
||||
id_ = (
|
||||
f"{event.user_id}:{event.group_id}"
|
||||
if isinstance(event, GroupMessageEvent)
|
||||
@@ -195,7 +206,15 @@ async def _(event: MessageEvent):
|
||||
live_rst = (
|
||||
"该群目前没有任何订阅..." if isinstance(event, GroupMessageEvent) else "您目前没有任何订阅..."
|
||||
)
|
||||
await show_sub_info.send(live_rst + up_rst + season_rst)
|
||||
await show_sub_info.send(
|
||||
image(
|
||||
b64=(
|
||||
await text2image(
|
||||
live_rst + up_rst + season_rst, padding=10, color="#f9f6f2"
|
||||
)
|
||||
).pic2bs4()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# 推送
|
||||
@@ -207,18 +226,18 @@ async def _():
|
||||
bot = get_bot()
|
||||
sub = None
|
||||
if bot:
|
||||
try:
|
||||
await sub_manager.reload_sub_data()
|
||||
sub = await sub_manager.random_sub_data()
|
||||
if sub:
|
||||
logger.debug(f"Bilibili订阅开始检测:{sub.sub_id}")
|
||||
rst = await get_sub_status(sub.sub_id, sub.sub_type)
|
||||
# try:
|
||||
await sub_manager.reload_sub_data()
|
||||
sub = await sub_manager.random_sub_data()
|
||||
if sub:
|
||||
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":
|
||||
rst = await get_sub_status(sub.sub_id, "up")
|
||||
await send_sub_msg(rst, sub, bot)
|
||||
if sub.sub_type == "live":
|
||||
rst = await get_sub_status(sub.sub_id, "up")
|
||||
await send_sub_msg(rst, sub, bot)
|
||||
except Exception as e:
|
||||
logger.error(f"B站订阅推送发生错误 sub_id:{sub.sub_id if sub else 0} {type(e)}:{e}")
|
||||
# except Exception as e:
|
||||
# logger.error(f"B站订阅推送发生错误 sub_id:{sub.sub_id if sub else 0} {type(e)}:{e}")
|
||||
|
||||
|
||||
async def send_sub_msg(rst: str, sub: BilibiliSub, bot: Bot):
|
||||
@@ -228,13 +247,27 @@ 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:
|
||||
await bot.send_group_msg(
|
||||
group_id=int(x.split(":")[1]), message=Message(rst)
|
||||
)
|
||||
if ":" in x and x.split(":")[1] not in temp_group:
|
||||
group_id = int(x.split(":")[1])
|
||||
temp_group.append(group_id)
|
||||
if (
|
||||
await bot.get_group_member_info(
|
||||
group_id=group_id, user_id=int(bot.self_id), no_cache=True
|
||||
)
|
||||
)["role"] in ["owner", "admin"]:
|
||||
if (
|
||||
sub.sub_type == "live"
|
||||
and Config.get_config("bilibili_sub", "LIVE_MSG_AT_ALL")
|
||||
) or (
|
||||
sub.sub_type == "up"
|
||||
and Config.get_config("bilibili_sub", "UP_MSG_AT_ALL")
|
||||
):
|
||||
rst = "[CQ:at,qq=all]\n" + rst
|
||||
await bot.send_group_msg(group_id=group_id, message=Message(rst))
|
||||
else:
|
||||
await bot.send_private_msg(user_id=int(x), message=Message(rst))
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
from bilibili_api.exceptions.ResponseCodeException import ResponseCodeException
|
||||
from bilireq.exceptions import ResponseCodeError
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
|
||||
from utils.manager import resources_manager
|
||||
from asyncio.exceptions import TimeoutError
|
||||
|
||||
from utils.utils import get_bot
|
||||
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 typing import Optional
|
||||
from bilireq.user import get_user_info
|
||||
from bilireq import dynamic
|
||||
from .utils import get_videos
|
||||
from typing import Optional, Tuple
|
||||
from configs.path_config import IMAGE_PATH
|
||||
from datetime import datetime
|
||||
from utils.browser import get_browser
|
||||
@@ -34,36 +39,35 @@ async def add_live_sub(live_id: int, sub_user: str) -> str:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
async with db.transaction():
|
||||
try:
|
||||
live = LiveRoom(live_id)
|
||||
live_info = (await live.get_room_info())["room_info"]
|
||||
except ResponseCodeException:
|
||||
return f"未找到房间号Id:{live_id} 的信息,请检查Id是否正确"
|
||||
uid = live_info["uid"]
|
||||
room_id = live_info["room_id"]
|
||||
short_id = live_info["short_id"]
|
||||
title = live_info["title"]
|
||||
live_status = live_info["live_status"]
|
||||
if await BilibiliSub.add_bilibili_sub(
|
||||
room_id,
|
||||
"live",
|
||||
sub_user,
|
||||
uid=uid,
|
||||
live_short_id=short_id,
|
||||
live_status=live_status,
|
||||
):
|
||||
await _get_up_status(live_id)
|
||||
uname = (await BilibiliSub.get_sub(live_id)).uname
|
||||
return (
|
||||
"已成功订阅主播:\n"
|
||||
f"\ttitle:{title}\n"
|
||||
f"\tname: {uname}\n"
|
||||
f"\tlive_id:{live_id}\n"
|
||||
f"\tuid:{uid}"
|
||||
try:
|
||||
"""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"]
|
||||
short_id = live_info["short_id"]
|
||||
title = live_info["title"]
|
||||
live_status = live_info["live_status"]
|
||||
if await BilibiliSub.add_bilibili_sub(
|
||||
room_id,
|
||||
"live",
|
||||
sub_user,
|
||||
uid=uid,
|
||||
live_short_id=short_id,
|
||||
live_status=live_status,
|
||||
):
|
||||
await _get_up_status(room_id)
|
||||
uname = (await BilibiliSub.get_sub(room_id)).uname
|
||||
return (
|
||||
"已成功订阅主播:\n"
|
||||
f"\ttitle:{title}\n"
|
||||
f"\tname: {uname}\n"
|
||||
f"\tlive_id:{room_id}\n"
|
||||
f"\tuid:{uid}"
|
||||
)
|
||||
else:
|
||||
return "添加订阅失败..."
|
||||
else:
|
||||
return "添加订阅失败..."
|
||||
except Exception as e:
|
||||
logger.error(f"订阅主播live_id:{live_id} 发生了错误 {type(e)}:{e}")
|
||||
return "添加订阅失败..."
|
||||
@@ -78,16 +82,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 +123,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 +207,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,17 +219,18 @@ 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)
|
||||
if sub.live_status == 0 and live_status == 1:
|
||||
return (
|
||||
f""
|
||||
f"{image(cover)}\n"
|
||||
f"{sub.uname} 开播啦!\n"
|
||||
f"标题:{title}\n"
|
||||
@@ -234,33 +242,35 @@ async def _get_live_status(id_: int) -> Optional[str]:
|
||||
async def _get_up_status(id_: int) -> Optional[str]:
|
||||
"""
|
||||
获取用户投稿状态
|
||||
:param id_: 用户 id
|
||||
:param id_: 订阅 id
|
||||
: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(_user.uid)
|
||||
latest_video_created = 0
|
||||
video = None
|
||||
dividing_line = "\n-------------\n"
|
||||
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, link = await get_user_dynamic(_user.uid, _user)
|
||||
if video_info["list"].get("vlist"):
|
||||
video = video_info["list"]["vlist"][0]
|
||||
latest_video_created = video["created"]
|
||||
rst = ""
|
||||
if dynamic_img:
|
||||
await BilibiliSub.update_sub_info(id_, dynamic_upload_time=dynamic_upload_time)
|
||||
rst += f"{uname} 发布了动态!\n" f"{dynamic_img}\n"
|
||||
rst += f"{uname} 发布了动态!\n" f"{dynamic_img}\n{link}"
|
||||
if (
|
||||
latest_video_created
|
||||
and _user.latest_video_created
|
||||
and video
|
||||
and _user.latest_video_created < latest_video_created
|
||||
):
|
||||
rst = rst + "-------------\n" if rst else rst
|
||||
rst = rst + dividing_line if rst else rst
|
||||
await BilibiliSub.update_sub_info(
|
||||
id_, latest_video_created=latest_video_created
|
||||
)
|
||||
@@ -271,7 +281,7 @@ async def _get_up_status(id_: int) -> Optional[str]:
|
||||
f'Bvid:{video["bvid"]}\n'
|
||||
f'直链:https://www.bilibili.com/video/{video["bvid"]}'
|
||||
)
|
||||
rst = None if rst == "-------------\n" else rst
|
||||
rst = None if rst == dividing_line else rst
|
||||
return rst
|
||||
|
||||
|
||||
@@ -280,7 +290,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,45 +308,63 @@ async def _get_season_status(id_) -> Optional[str]:
|
||||
|
||||
|
||||
async def get_user_dynamic(
|
||||
u: User, local_user: BilibiliSub
|
||||
) -> "Optional[MessageSegment], int":
|
||||
uid: int, local_user: BilibiliSub
|
||||
) -> Tuple[Optional[MessageSegment], int, str]:
|
||||
"""
|
||||
获取用户动态
|
||||
: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"]
|
||||
dynamic_id = dynamic_info["cards"][0]["desc"]["dynamic_id"]
|
||||
if local_user.dynamic_upload_time < dynamic_upload_time:
|
||||
page = await browser.new_page()
|
||||
context = await browser.new_context()
|
||||
page = await context.new_page()
|
||||
try:
|
||||
await page.goto(
|
||||
f"https://space.bilibili.com/{local_user.uid}/dynamic",
|
||||
f"https://t.bilibili.com/{dynamic_id}",
|
||||
wait_until="networkidle",
|
||||
timeout=10000,
|
||||
)
|
||||
await page.set_viewport_size({"width": 2560, "height": 1080})
|
||||
# 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();
|
||||
# }
|
||||
# """
|
||||
# )
|
||||
# async with page.expect_popup() as popup_info:
|
||||
# await page.locator(".bili-rich-text__content").click()
|
||||
# details_page = await popup_info.value
|
||||
await page.set_viewport_size(
|
||||
{"width": 2560, "height": 1080, "timeout": 10000 * 20}
|
||||
)
|
||||
await page.wait_for_selector(".panel-area")
|
||||
await page.evaluate(
|
||||
"""
|
||||
xs = document.getElementsByClassName('first-card-with-title');
|
||||
for (x of xs) {
|
||||
x.remove();
|
||||
}
|
||||
xs = document.getElementById('internationalHeader');
|
||||
xs.remove();
|
||||
xs = document.getElementsByClassName('panel-area')
|
||||
xs[0].remove();
|
||||
"""
|
||||
)
|
||||
card = await page.query_selector(".card")
|
||||
# 截图并保存
|
||||
card = page.locator(".detail-card")
|
||||
await card.wait_for()
|
||||
await card.screenshot(
|
||||
path=dynamic_path / f"{local_user.sub_id}_{dynamic_upload_time}.jpg",
|
||||
timeout=100000,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"B站订阅:获取用户动态 发送错误 {type(e)}:{e}")
|
||||
finally:
|
||||
await context.close()
|
||||
await page.close()
|
||||
return (
|
||||
image(
|
||||
@@ -343,8 +372,9 @@ async def get_user_dynamic(
|
||||
"bilibili_sub/dynamic",
|
||||
),
|
||||
dynamic_upload_time,
|
||||
f"https://t.bilibili.com/{dynamic_id}"
|
||||
)
|
||||
return None, None
|
||||
return None, 0, ''
|
||||
|
||||
|
||||
class SubManager:
|
||||
|
||||
@@ -47,9 +47,9 @@ class BilibiliSub(db.Model):
|
||||
season_update_time: Optional[datetime] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
添加订阅
|
||||
参数:
|
||||
参数:
|
||||
:param sub_id: 订阅名称,房间号,番剧号等
|
||||
:param sub_type: 订阅类型
|
||||
:param sub_user: 订阅此条目的用户
|
||||
@@ -65,44 +65,43 @@ class BilibiliSub(db.Model):
|
||||
:param season_update_time: 番剧更新时间
|
||||
"""
|
||||
try:
|
||||
async with db.transaction():
|
||||
query = (
|
||||
await cls.query.where(cls.sub_id == sub_id)
|
||||
.with_for_update()
|
||||
.gino.first()
|
||||
query = (
|
||||
await cls.query.where(cls.sub_id == sub_id)
|
||||
.with_for_update()
|
||||
.gino.first()
|
||||
)
|
||||
sub_user = sub_user if sub_user[-1] == "," else f"{sub_user},"
|
||||
if query:
|
||||
if sub_user not in query.sub_users:
|
||||
sub_users = query.sub_users + sub_user
|
||||
await query.update(sub_users=sub_users).apply()
|
||||
else:
|
||||
sub = await cls.create(
|
||||
sub_id=sub_id, sub_type=sub_type, sub_users=sub_user
|
||||
)
|
||||
sub_user = sub_user if sub_user[-1] == "," else f"{sub_user},"
|
||||
if query:
|
||||
if sub_user not in query.sub_users:
|
||||
sub_users = query.sub_users + sub_user
|
||||
await query.update(sub_users=sub_users).apply()
|
||||
else:
|
||||
sub = await cls.create(
|
||||
sub_id=sub_id, sub_type=sub_type, sub_users=sub_user
|
||||
)
|
||||
await sub.update(
|
||||
live_short_id=live_short_id
|
||||
if live_short_id
|
||||
else sub.live_short_id,
|
||||
live_status=live_status if live_status else sub.live_status,
|
||||
dynamic_upload_time=dynamic_upload_time
|
||||
if dynamic_upload_time
|
||||
else sub.dynamic_upload_time,
|
||||
uid=uid if uid else sub.uid,
|
||||
uname=uname if uname else sub.uname,
|
||||
latest_video_created=latest_video_created
|
||||
if latest_video_created
|
||||
else sub.latest_video_created,
|
||||
season_update_time=season_update_time
|
||||
if season_update_time
|
||||
else sub.season_update_time,
|
||||
season_current_episode=season_current_episode
|
||||
if season_current_episode
|
||||
else sub.season_current_episode,
|
||||
season_id=season_id if season_id else sub.season_id,
|
||||
season_name=season_name if season_name else sub.season_name,
|
||||
).apply()
|
||||
return True
|
||||
await sub.update(
|
||||
live_short_id=live_short_id
|
||||
if live_short_id
|
||||
else sub.live_short_id,
|
||||
live_status=live_status if live_status else sub.live_status,
|
||||
dynamic_upload_time=dynamic_upload_time
|
||||
if dynamic_upload_time
|
||||
else sub.dynamic_upload_time,
|
||||
uid=uid if uid else sub.uid,
|
||||
uname=uname if uname else sub.uname,
|
||||
latest_video_created=latest_video_created
|
||||
if latest_video_created
|
||||
else sub.latest_video_created,
|
||||
season_update_time=season_update_time
|
||||
if season_update_time
|
||||
else sub.season_update_time,
|
||||
season_current_episode=season_current_episode
|
||||
if season_current_episode
|
||||
else sub.season_current_episode,
|
||||
season_id=season_id if season_id else sub.season_id,
|
||||
season_name=season_name if season_name else sub.season_name,
|
||||
).apply()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.info(f"bilibili_sub 添加订阅错误 {type(e)}: {e}")
|
||||
return False
|
||||
@@ -110,9 +109,9 @@ class BilibiliSub(db.Model):
|
||||
@classmethod
|
||||
async def delete_bilibili_sub(cls, sub_id: int, sub_user: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
删除订阅
|
||||
参数:
|
||||
参数:
|
||||
:param sub_id: 订阅名称
|
||||
:param sub_user: 删除此条目的用户
|
||||
"""
|
||||
@@ -140,9 +139,9 @@ class BilibiliSub(db.Model):
|
||||
@classmethod
|
||||
async def get_sub(cls, sub_id: int) -> Optional["BilibiliSub"]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取订阅对象
|
||||
参数:
|
||||
参数:
|
||||
:param sub_id: 订阅 id
|
||||
"""
|
||||
return await cls.query.where(cls.sub_id == sub_id).gino.first()
|
||||
@@ -173,9 +172,9 @@ class BilibiliSub(db.Model):
|
||||
season_update_time: Optional[datetime] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
更新订阅信息
|
||||
参数:
|
||||
参数:
|
||||
:param sub_id: 订阅名称,房间号,番剧号等
|
||||
:param live_short_id: 直接短 id
|
||||
:param live_status: 主播开播状态
|
||||
@@ -232,7 +231,7 @@ class BilibiliSub(db.Model):
|
||||
cls,
|
||||
) -> "List[BilibiliSub], List[BilibiliSub], List[BilibiliSub]":
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
分类获取所有数据
|
||||
"""
|
||||
live_data = []
|
||||
|
||||
@@ -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.mobi"
|
||||
url = "http://www.eclzz.plus"
|
||||
|
||||
|
||||
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"})
|
||||
|
||||
@@ -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,17 @@ 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 +92,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 +112,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()
|
||||
@@ -162,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()
|
||||
@@ -174,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}'
|
||||
@@ -189,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
|
||||
|
||||
|
||||
|
||||
+23
-15
@@ -1,4 +1,7 @@
|
||||
from typing import Tuple, Any
|
||||
|
||||
from nonebot import on_regex
|
||||
from nonebot.params import RegexGroup
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent
|
||||
from utils.message_builder import image
|
||||
@@ -11,7 +14,9 @@ __plugin_usage__ = """
|
||||
usage:
|
||||
三次元也不戳,嘿嘿嘿
|
||||
指令:
|
||||
cos/coser
|
||||
?N连cos/coser
|
||||
示例:cos
|
||||
示例:5连cos (单次请求张数小于9)
|
||||
""".strip()
|
||||
__plugin_des__ = "三次元也不戳,嘿嘿嘿"
|
||||
__plugin_cmd__ = ["cos/coser"]
|
||||
@@ -31,21 +36,24 @@ __plugin_configs__ = {
|
||||
},
|
||||
}
|
||||
|
||||
coser = on_regex("^(cos|COS|coser|括丝)$", priority=5, block=True)
|
||||
coser = on_regex(r"^(\d)?连?(cos|COS|coser|括丝)$", priority=5, block=True)
|
||||
|
||||
|
||||
url = "https://api.iyk0.com/cos"
|
||||
# 纯cos,较慢:https://picture.yinux.workers.dev
|
||||
# 比较杂,有福利姬,较快:https://api.jrsgslb.cn/cos/url.php?return=img
|
||||
url = "https://picture.yinux.workers.dev/"
|
||||
|
||||
|
||||
@coser.handle()
|
||||
async def _(bot: Bot, event: MessageEvent, state: T_State):
|
||||
try:
|
||||
msg_id = await coser.send(image(url))
|
||||
withdraw_message_manager.withdraw_message(
|
||||
event,
|
||||
msg_id["message_id"],
|
||||
Config.get_config("coser", "WITHDRAW_COS_MESSAGE"),
|
||||
)
|
||||
except Exception as e:
|
||||
await coser.send("你cos给我看!")
|
||||
logger.error(f"coser 发送了未知错误 {type(e)}:{e}")
|
||||
async def _(event: MessageEvent, reg_group: Tuple[Any, ...] = RegexGroup()):
|
||||
num = reg_group[0] or 1
|
||||
for _ in range(int(num)):
|
||||
try:
|
||||
msg_id = await coser.send(image(url))
|
||||
withdraw_message_manager.withdraw_message(
|
||||
event,
|
||||
msg_id["message_id"],
|
||||
Config.get_config("coser", "WITHDRAW_COS_MESSAGE"),
|
||||
)
|
||||
except Exception as e:
|
||||
await coser.send("你cos给我看!")
|
||||
logger.error(f"coser 发送了未知错误 {type(e)}:{e}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ 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
|
||||
|
||||
@@ -30,7 +31,7 @@ from .config import draw_config
|
||||
__zx_plugin_name__ = "游戏抽卡"
|
||||
__plugin_usage__ = """
|
||||
usage:
|
||||
模拟赛马娘,原神,明日方舟,坎公骑冠剑,公主连结(国/台),碧蓝航线,FGO,阴阳师进行抽卡
|
||||
模拟赛马娘,原神,明日方舟,坎公骑冠剑,公主连结(国/台),碧蓝航线,FGO,阴阳师,碧蓝档案进行抽卡
|
||||
指令:
|
||||
原神[1-180]抽: 原神常驻池
|
||||
原神角色[1-180]抽: 原神角色UP池子
|
||||
@@ -43,6 +44,7 @@ usage:
|
||||
碧蓝航线/碧蓝[重型/轻型/特型/活动][1-300]抽: 碧蓝航线重型/轻型/特型/活动卡池
|
||||
fgo[1-300]抽: fgo卡池
|
||||
阴阳师[1-300]抽: 阴阳师卡池
|
||||
ba/碧蓝档案[1-200]抽:碧蓝档案卡池
|
||||
* 以上指令可以通过 XX一井 来指定最大抽取数量 *
|
||||
* 示例:原神一井 *
|
||||
""".strip()
|
||||
@@ -73,6 +75,7 @@ __plugin_cmd__ = [
|
||||
"pcr/公主连接[1-300]抽",
|
||||
"fgo[1-300]抽",
|
||||
"阴阳师[1-300]抽",
|
||||
"碧蓝档案[1-200]抽",
|
||||
"更新方舟信息 [_superuser]",
|
||||
"重载方舟卡池 [_superuser]",
|
||||
"更新原神信息 [_superuser]",
|
||||
@@ -83,6 +86,7 @@ __plugin_cmd__ = [
|
||||
"更新碧蓝航线信息 [_superuser]",
|
||||
"更新fgo信息 [_superuser]",
|
||||
"更新阴阳师信息 [_superuser]",
|
||||
"更新碧蓝档案信息 [_superuser]",
|
||||
]
|
||||
__plugin_type__ = ("抽卡相关", 1)
|
||||
__plugin_version__ = 0.1
|
||||
@@ -105,7 +109,7 @@ class Game:
|
||||
|
||||
|
||||
games = (
|
||||
Game({"azur", "碧蓝", "碧蓝航线"}, AzurHandle(), draw_config.AZUR_FLAG),
|
||||
Game({"azur", "碧蓝航线"}, AzurHandle(), draw_config.AZUR_FLAG),
|
||||
Game({"fgo", "命运冠位指定"}, FgoHandle(), draw_config.FGO_FLAG),
|
||||
Game(
|
||||
{"genshin", "原神"},
|
||||
@@ -130,6 +134,11 @@ games = (
|
||||
reload_time=4,
|
||||
),
|
||||
Game({"prts", "方舟", "明日方舟"}, PrtsHandle(), draw_config.PRTS_FLAG, reload_time=4),
|
||||
Game(
|
||||
{"ba","碧蓝档案"},BaHandle(),
|
||||
draw_config.BA_FLAG,
|
||||
max_count=200,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -87,6 +87,12 @@ 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 Config(BaseModel, extra=Extra.ignore):
|
||||
# 开关
|
||||
@@ -98,6 +104,7 @@ class Config(BaseModel, extra=Extra.ignore):
|
||||
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 = AConfig.get_config("draw_card", "PCR_TAI")
|
||||
@@ -112,6 +119,7 @@ class Config(BaseModel, extra=Extra.ignore):
|
||||
azur: AzurConfig = AzurConfig()
|
||||
fgo: FgoConfig = FgoConfig()
|
||||
onmyoji: OnmyojiConfig = OnmyojiConfig()
|
||||
ba: BaConfig = BaConfig()
|
||||
|
||||
|
||||
driver = nonebot.get_driver()
|
||||
@@ -133,8 +141,9 @@ for game_flag, game_name in zip(
|
||||
"FGO_FLAG",
|
||||
"ONMYOJI_FLAG",
|
||||
"PCR_TAI",
|
||||
"BA_FLAG"
|
||||
],
|
||||
["明日方舟", "原神", "赛马娘", "坎公骑冠剑", "公主连结", "碧蓝航线", "命运-冠位指定(FGO)", "阴阳师", "pcr台服卡池"],
|
||||
["明日方舟", "原神", "赛马娘", "坎公骑冠剑", "公主连结", "碧蓝航线", "命运-冠位指定(FGO)", "阴阳师", "pcr台服卡池","碧蓝档案"],
|
||||
):
|
||||
AConfig.add_plugin_config(
|
||||
"draw_card",
|
||||
|
||||
@@ -293,7 +293,7 @@ class AzurHandle(BaseHandle[AzurChar]):
|
||||
type_ = ship.xpath("./tbody/tr/td[2]/p/small/text()")[0] # 舰船类型
|
||||
try:
|
||||
p = float(str(ship.xpath(".//sup/text()")[0]).strip("%"))
|
||||
except IndexError:
|
||||
except (IndexError, ValueError):
|
||||
p = 0
|
||||
star = self.parse_star(
|
||||
ship.xpath("./tbody/tr/td[1]/div/div/div/a/img/@alt")[0]
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -50,6 +50,7 @@ class UpEvent(BaseModel):
|
||||
start_time: Optional[datetime] # 开始时间
|
||||
end_time: Optional[datetime] # 结束时间
|
||||
up_char: List[UpChar] # up对象
|
||||
up_name: str = "" # up名称
|
||||
|
||||
|
||||
TC = TypeVar("TC", bound="BaseData")
|
||||
|
||||
@@ -350,48 +350,52 @@ class PrettyHandle(BaseHandle[PrettyData]):
|
||||
char_img = ""
|
||||
card_img = ""
|
||||
up_chars = []
|
||||
up_chars_name = []
|
||||
up_cards = []
|
||||
up_cards_name = []
|
||||
soup = BeautifulSoup(result, "lxml")
|
||||
heads = soup.find_all("span", {"class": "mw-headline"})
|
||||
for head in heads:
|
||||
if "时间" in head.text:
|
||||
if "时间" in head.text or "期间" 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")[
|
||||
char_img = head.find_next("center").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
|
||||
if "★" in line and "【" in line and "】" in line
|
||||
]
|
||||
for char in chars:
|
||||
for char in set(chars): # list去重
|
||||
star = char.count("★")
|
||||
name = re.split(r"[()]", char)[-2].strip()
|
||||
name = re.split(r"[【】]", char)[-2].strip()
|
||||
up_chars.append(
|
||||
UpChar(name=name, star=star, limited=False, zoom=70)
|
||||
)
|
||||
up_chars_name.append(name)
|
||||
elif "支援卡" in head.text:
|
||||
card_img = head.find_next("a", {"class": "image"}).find("img")[
|
||||
card_img = head.find_next("center").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
|
||||
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()
|
||||
name = re.split(r"[【】]", card)[-2].strip()
|
||||
up_cards.append(
|
||||
UpChar(name=name, star=star, limited=False, zoom=70)
|
||||
)
|
||||
up_cards_name.append(name)
|
||||
if start_time and end_time:
|
||||
if start_time <= datetime.now() <= end_time:
|
||||
self.UP_CHAR = UpEvent(
|
||||
@@ -400,6 +404,7 @@ class PrettyHandle(BaseHandle[PrettyData]):
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
up_char=up_chars,
|
||||
up_name=up_chars_name,
|
||||
)
|
||||
self.UP_CARD = UpEvent(
|
||||
title=title,
|
||||
@@ -407,6 +412,7 @@ class PrettyHandle(BaseHandle[PrettyData]):
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
up_char=up_cards,
|
||||
up_name=up_cards_name,
|
||||
)
|
||||
self.dump_up_char()
|
||||
logger.info(f"成功获取{self.game_name_cn}当前up信息...当前up池: {title}")
|
||||
@@ -418,9 +424,10 @@ class PrettyHandle(BaseHandle[PrettyData]):
|
||||
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,
|
||||
Message.template("重载成功!\n当前UP池子:{}{:image}\n当前支援卡池子:{}{:image}").format(
|
||||
self.UP_CHAR.up_name,
|
||||
self.UP_CHAR.pool_img,
|
||||
self.UP_CARD.up_name,
|
||||
self.UP_CARD.pool_img,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@ 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
|
||||
@@ -32,8 +31,9 @@ class Operator(BaseData):
|
||||
|
||||
class PrtsHandle(BaseHandle[Operator]):
|
||||
def __init__(self):
|
||||
super().__init__("prts", "明日方舟", "#eff2f5")
|
||||
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] = []
|
||||
@@ -41,8 +41,8 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
|
||||
def get_card(self, add: float) -> Operator:
|
||||
star = self.get_star(
|
||||
[6, 5, 4, 3],
|
||||
[
|
||||
star_list=[6, 5, 4, 3],
|
||||
probability_list=[
|
||||
self.config.PRTS_SIX_P + add,
|
||||
self.config.PRTS_FIVE_P,
|
||||
self.config.PRTS_FOUR_P,
|
||||
@@ -53,7 +53,7 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
all_operators = [
|
||||
x
|
||||
for x in self.ALL_OPERATOR
|
||||
if x.star == star and not any([x.limited, x.event_only, x.recruit_only])
|
||||
if x.star == star and not any([x.limited, x.recruit_only, x.event_only])
|
||||
]
|
||||
acquire_operator = None
|
||||
|
||||
@@ -74,7 +74,7 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
acquire_operator = random.choice(all_operators)
|
||||
return acquire_operator
|
||||
|
||||
def get_cards(self, count: int) -> List[Tuple[Operator, int]]:
|
||||
def get_cards(self, count: int, **kwargs) -> List[Tuple[Operator, int]]:
|
||||
card_list = [] # 获取所有角色
|
||||
add = 0.0
|
||||
count_idx = 0
|
||||
@@ -109,7 +109,8 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
|
||||
def _draw(self, count: int, **kwargs) -> Message:
|
||||
index2card = self.get_cards(count)
|
||||
cards = [card[0] for card in 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()
|
||||
@@ -162,7 +163,11 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
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", {}))
|
||||
"""这里的 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 解析出错")
|
||||
|
||||
@@ -185,14 +190,15 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
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 = str(char.xpath("./td[8]/text()")[0]).split("\n")
|
||||
"""这里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()),
|
||||
"获取途径": [s for s in sources if s],
|
||||
"获取途径": sources,
|
||||
}
|
||||
info[member_dict["名称"]] = member_dict
|
||||
self.dump_data(info)
|
||||
@@ -208,6 +214,7 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
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:
|
||||
@@ -221,41 +228,53 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
end_time = None
|
||||
up_chars = []
|
||||
pool_img = ""
|
||||
title = ""
|
||||
for activity_url in activity_urls:
|
||||
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
|
||||
soup = BeautifulSoup(result, "lxml")
|
||||
contents = soup.find_all("p")
|
||||
|
||||
"""因为鹰角的前端太自由了,这里重写了匹配规则以尽可能避免因为前端乱七八糟而导致的重载失败"""
|
||||
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.text):
|
||||
title = content.text
|
||||
if "【" in title and "】" in title:
|
||||
title = re.split(r"[【】]", title)[1]
|
||||
lines = [str(contents[index + i + 1].text) for i in range(5)]
|
||||
time = ""
|
||||
chars: List[str] = []
|
||||
for line in lines:
|
||||
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)
|
||||
if "★" in line:
|
||||
chars.append(line)
|
||||
"""因为 <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]
|
||||
start, end = time.replace("月", "/").replace("日", " ").split("-")[:2] # 日替换为空格是因为有日后面不接空格的情况,导致 split 出问题
|
||||
start_time = dateparser.parse(start)
|
||||
end_time = dateparser.parse(end)
|
||||
pool_img = content.find_previous("img")["src"]
|
||||
pool_img = contents[index-2]
|
||||
r"""两类格式:用/分割,用\分割;★+(概率)+名字,★+名字+(概率)"""
|
||||
for char in chars:
|
||||
star = char.split("(")[0].count("★")
|
||||
name = re.split(r"[:(]", char)[1]
|
||||
names = name.split("/") if "/" in name else [name]
|
||||
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)
|
||||
@@ -269,7 +288,10 @@ class PrtsHandle(BaseHandle[Operator]):
|
||||
up_chars.append(
|
||||
UpChar(name=name, star=star, limited=False, zoom=zoom)
|
||||
)
|
||||
break
|
||||
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(
|
||||
|
||||
+62
-56
@@ -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
|
||||
# https://github.com/DIYgod/RSSHub/blob/master/lib/v2/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
|
||||
@@ -63,7 +39,7 @@ async def get_epic_free(bot: Bot, Type_Event: str):
|
||||
game_corp = game["seller"]["name"]
|
||||
game_price = game["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
|
||||
# 赋初值以避免 local variable referenced before assignment
|
||||
game_dev, game_pub, game_thumbnail = (None, None, None)
|
||||
game_thumbnail, game_dev, game_pub = None, game_corp, game_corp
|
||||
try:
|
||||
game_promotions = game["promotions"]["promotionalOffers"]
|
||||
upcoming_promotions = game["promotions"]["upcomingPromotionalOffers"]
|
||||
@@ -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,33 +77,63 @@ 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"]
|
||||
if pair["key"] == "publisherName":
|
||||
game_pub = pair["value"]
|
||||
# 如 game['customAttributes'] 未找到则均使用 game_corp 值
|
||||
game_dev = game_dev if game_dev is not None else game_corp
|
||||
game_pub = game_pub if game_pub is not None else game_corp
|
||||
game_desp = game["description"]
|
||||
end_date_iso = game["promotions"]["promotionalOffers"][0][
|
||||
"promotionalOffers"
|
||||
][0]["endDate"][:-1]
|
||||
end_date = datetime.fromisoformat(end_date_iso).strftime(
|
||||
"%b.%d %H:%M"
|
||||
)
|
||||
try:
|
||||
end_date_iso = game["promotions"]["promotionalOffers"][0][
|
||||
"promotionalOffers"
|
||||
][0]["endDate"][:-1]
|
||||
end_date = datetime.fromisoformat(end_date_iso).strftime(
|
||||
"%b.%d %H:%M"
|
||||
)
|
||||
except IndexError:
|
||||
end_date = '未知'
|
||||
# API 返回不包含游戏商店 URL,此处自行拼接,可能出现少数游戏 404 请反馈
|
||||
game_url_part = (
|
||||
(game["productSlug"].replace("/home", ""))
|
||||
if ("/home" in game["productSlug"])
|
||||
else game["productSlug"]
|
||||
)
|
||||
game_url = "https://www.epicgames.com/store/zh-CN/p/{}".format(
|
||||
game_url_part
|
||||
)
|
||||
if Type_Event == "Group":
|
||||
if game.get("productSlug"):
|
||||
game_url = "https://store.epicgames.com/zh-CN/p/{}".format(
|
||||
game["productSlug"].replace("/home", "")
|
||||
)
|
||||
elif game.get("url"):
|
||||
game_url = game["url"]
|
||||
else:
|
||||
slugs = (
|
||||
[
|
||||
x["pageSlug"]
|
||||
for x in game.get("offerMappings", [])
|
||||
if x.get("pageType") == "productHome"
|
||||
]
|
||||
+ [
|
||||
x["pageSlug"]
|
||||
for x in game.get("catalogNs", {}).get("mappings", [])
|
||||
if x.get("pageType") == "productHome"
|
||||
]
|
||||
+ [
|
||||
x["value"]
|
||||
for x in game.get("customAttributes", [])
|
||||
if "productSlug" in x.get("key")
|
||||
]
|
||||
)
|
||||
game_url = "https://store.epicgames.com/zh-CN{}".format(
|
||||
f"/p/{slugs[0]}" if len(slugs) else ""
|
||||
)
|
||||
if type_event == "Group":
|
||||
_message = "[CQ:image,file={}]\n\nFREE now :: {} ({})\n{}\n此游戏由 {} 开发、{} 发行,将在 UTC 时间 {} 结束免费游玩,戳链接速度加入你的游戏库吧~\n{}\n".format(
|
||||
game_thumbnail,
|
||||
game_name,
|
||||
|
||||
+11
-9
@@ -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):
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -30,7 +30,7 @@ class Map:
|
||||
ratio: float = 1,
|
||||
):
|
||||
"""
|
||||
参数:
|
||||
参数:
|
||||
:param resource_name: 资源名称
|
||||
:param center_point: 中心点
|
||||
:param deviation: 坐标误差
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -19,15 +19,18 @@ class Genshin(db.Model):
|
||||
resin_remind = db.Column(db.Boolean(), default=False) # 树脂提醒
|
||||
resin_recovery_time = db.Column(db.DateTime(timezone=True)) # 满树脂提醒日期
|
||||
bind_group = db.Column(db.BigInteger())
|
||||
login_ticket = db.Column(db.String(), default="")
|
||||
stuid = db.Column(db.String(), default="")
|
||||
stoken = db.Column(db.String(), default="")
|
||||
|
||||
_idx1 = db.Index("genshin_uid_idx1", "user_qq", "uid", unique=True)
|
||||
|
||||
@classmethod
|
||||
async def add_uid(cls, user_qq: int, uid: int):
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
添加一个uid
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: 用户qq
|
||||
:param uid: 原神uid
|
||||
"""
|
||||
@@ -44,9 +47,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def set_mys_id(cls, uid: int, mys_id: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
设置米游社id
|
||||
参数:
|
||||
参数:
|
||||
:param uid: 原神uid
|
||||
:param mys_id: 米游社id
|
||||
"""
|
||||
@@ -60,9 +63,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def set_bind_group(cls, uid: int, bind_group) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
绑定group_id,除私聊外的提醒将在此群发送
|
||||
参数:
|
||||
参数:
|
||||
:param uid: uid
|
||||
:param bind_group: 群号
|
||||
"""
|
||||
@@ -76,9 +79,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def get_bind_group(cls, uid: int) -> Optional[int]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取用户绑定的群聊
|
||||
参数:
|
||||
参数:
|
||||
:param uid: uid
|
||||
"""
|
||||
user = await cls.query.where(cls.uid == uid).gino.first()
|
||||
@@ -89,9 +92,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def set_cookie(cls, uid: int, cookie: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
设置cookie
|
||||
参数:
|
||||
参数:
|
||||
:param uid: 原神uid
|
||||
:param cookie: 米游社id
|
||||
"""
|
||||
@@ -105,9 +108,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def set_resin_remind(cls, uid: int, flag: bool) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
设置体力提醒
|
||||
参数:
|
||||
参数:
|
||||
:param uid: 原神uid
|
||||
:param flag: 开关状态
|
||||
"""
|
||||
@@ -121,9 +124,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def set_user_resin_recovery_time(cls, uid: int, date: datetime):
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
设置体力完成时间
|
||||
参数:
|
||||
参数:
|
||||
:param uid: uid
|
||||
:param date: 提醒日期
|
||||
"""
|
||||
@@ -134,9 +137,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def get_user_resin_recovery_time(cls, uid: int) -> Optional[datetime]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取体力完成时间
|
||||
参数:
|
||||
参数:
|
||||
:param uid: uid
|
||||
"""
|
||||
u = await cls.query.where(cls.uid == uid).gino.first()
|
||||
@@ -147,7 +150,7 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def get_all_resin_remind_user(cls) -> List["Genshin"]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取所有开启体力提醒的用户
|
||||
"""
|
||||
return await cls.query.where(cls.resin_remind == True).gino.all()
|
||||
@@ -155,9 +158,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def clear_resin_remind_time(cls, uid: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
清空提醒日期
|
||||
参数:
|
||||
参数:
|
||||
:param uid: uid
|
||||
"""
|
||||
user = await cls.query.where(cls.uid == uid).gino.first()
|
||||
@@ -169,9 +172,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def set_auto_sign(cls, uid: int, flag: bool) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
设置米游社/原神自动签到
|
||||
参数:
|
||||
参数:
|
||||
:param uid: 原神uid
|
||||
:param flag: 开关状态
|
||||
"""
|
||||
@@ -185,7 +188,7 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def get_all_auto_sign_user(cls) -> List["Genshin"]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取所有开启自动签到的用户
|
||||
"""
|
||||
return await cls.query.where(cls.auto_sign == True).gino.all()
|
||||
@@ -193,7 +196,7 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def get_all_sign_user(cls) -> List["Genshin"]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取 原神 所有今日签到用户
|
||||
"""
|
||||
return await cls.query.where(cls.auto_sign_time != None).gino.all()
|
||||
@@ -201,9 +204,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def clear_sign_time(cls, uid: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
清空签到日期
|
||||
参数:
|
||||
参数:
|
||||
:param uid: uid
|
||||
"""
|
||||
user = await cls.query.where(cls.uid == uid).gino.first()
|
||||
@@ -215,9 +218,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def random_sign_time(cls, uid: int) -> Optional[datetime]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
随机签到时间
|
||||
说明:
|
||||
说明:
|
||||
:param uid: uid
|
||||
"""
|
||||
query = cls.query.where(cls.uid == uid).with_for_update()
|
||||
@@ -245,9 +248,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def get_query_cookie(cls, uid: int) -> Optional[str]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取查询角色信息cookie
|
||||
参数:
|
||||
参数:
|
||||
:param uid: 原神uid
|
||||
"""
|
||||
# 查找用户今日是否已经查找过,防止重复
|
||||
@@ -265,9 +268,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def get_user_cookie(cls, uid: int, flag: bool = False) -> Optional[str]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取用户cookie
|
||||
参数:
|
||||
参数:
|
||||
:param uid:原神uid
|
||||
:param flag:必须使用自己的cookie
|
||||
"""
|
||||
@@ -279,9 +282,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def get_user_by_qq(cls, user_qq: int) -> Optional["Genshin"]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
通过qq获取用户对象
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq
|
||||
"""
|
||||
return await cls.query.where(cls.user_qq == user_qq).gino.first()
|
||||
@@ -289,9 +292,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def get_user_by_uid(cls, uid: int) -> Optional["Genshin"]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
通过uid获取用户对象
|
||||
参数:
|
||||
参数:
|
||||
:param uid: qq
|
||||
"""
|
||||
return await cls.query.where(cls.uid == uid).gino.first()
|
||||
@@ -299,9 +302,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def get_user_uid(cls, user_qq: int) -> Optional[int]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取用户uid
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq:用户qq
|
||||
"""
|
||||
return await cls._get_user_data(user_qq, None, "uid")
|
||||
@@ -311,7 +314,7 @@ class Genshin(db.Model):
|
||||
"""
|
||||
说嘛:
|
||||
获取用户米游社id
|
||||
参数:
|
||||
参数:
|
||||
:param uid:原神id
|
||||
"""
|
||||
return await cls._get_user_data(None, uid, "mys_id")
|
||||
@@ -319,9 +322,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def delete_user_cookie(cls, uid: int):
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
删除用户cookie
|
||||
参数:
|
||||
参数:
|
||||
:param uid: 原神uid
|
||||
"""
|
||||
query = cls.query.where(cls.uid == uid).with_for_update()
|
||||
@@ -332,9 +335,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def delete_user(cls, user_qq: int):
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
删除用户数据
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: 用户qq
|
||||
"""
|
||||
query = cls.query.where(cls.user_qq == user_qq).with_for_update()
|
||||
@@ -347,9 +350,9 @@ class Genshin(db.Model):
|
||||
@classmethod
|
||||
async def _add_query_uid(cls, uid: int, cookie_uid: int):
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
添加每日查询重复uid的cookie
|
||||
参数:
|
||||
参数:
|
||||
:param uid: 原神uid
|
||||
:param cookie_uid: cookie的uid
|
||||
"""
|
||||
@@ -362,9 +365,9 @@ class Genshin(db.Model):
|
||||
cls, user_qq: Optional[int], uid: Optional[int], type_: str
|
||||
) -> Optional[Union[int, str]]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取用户数据
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: 用户qq
|
||||
:param uid: uid
|
||||
:param type_: 数据类型
|
||||
@@ -386,3 +389,96 @@ class Genshin(db.Model):
|
||||
for u in await cls.query.with_for_update().gino.all():
|
||||
if u.today_query_uid:
|
||||
await u.update(today_query_uid="").apply()
|
||||
|
||||
@classmethod
|
||||
async def set_stuid(cls, uid: int, stuid: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
设置stuid
|
||||
参数:
|
||||
:param uid: 原神uid
|
||||
:param stuid: stuid
|
||||
"""
|
||||
query = cls.query.where(cls.uid == uid).with_for_update()
|
||||
user = await query.gino.first()
|
||||
if user:
|
||||
await user.update(stuid=stuid).apply()
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def set_stoken(cls, uid: int, stoken: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
设置stoken
|
||||
参数:
|
||||
:param uid: 原神uid
|
||||
:param stoken: stoken
|
||||
"""
|
||||
query = cls.query.where(cls.uid == uid).with_for_update()
|
||||
user = await query.gino.first()
|
||||
if user:
|
||||
await user.update(stoken=stoken).apply()
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def set_login_ticket(cls, uid: int, login_ticket: str) -> bool:
|
||||
"""
|
||||
说明:
|
||||
设置login_ticket
|
||||
参数:
|
||||
:param uid: 原神uid
|
||||
:param login_ticket: login_ticket
|
||||
"""
|
||||
query = cls.query.where(cls.uid == uid).with_for_update()
|
||||
user = await query.gino.first()
|
||||
if user:
|
||||
await user.update(login_ticket=login_ticket).apply()
|
||||
return True
|
||||
return False
|
||||
|
||||
# 获取login_ticket
|
||||
@classmethod
|
||||
async def get_login_ticket(cls, uid: int) -> Optional[str]:
|
||||
"""
|
||||
说明:
|
||||
获取login_ticket
|
||||
参数:
|
||||
:param uid: 原神uid
|
||||
"""
|
||||
query = cls.query.where(cls.uid == uid)
|
||||
user = await query.gino.first()
|
||||
if user:
|
||||
return user.login_ticket
|
||||
return None
|
||||
|
||||
# 获取stuid
|
||||
@classmethod
|
||||
async def get_stuid(cls, uid: int) -> Optional[str]:
|
||||
"""
|
||||
说明:
|
||||
获取stuid
|
||||
参数:
|
||||
:param uid: 原神uid
|
||||
"""
|
||||
query = cls.query.where(cls.uid == uid)
|
||||
user = await query.gino.first()
|
||||
if user:
|
||||
return user.stuid
|
||||
return None
|
||||
|
||||
# 获取stoken
|
||||
@classmethod
|
||||
async def get_stoken(cls, uid: int) -> Optional[str]:
|
||||
"""
|
||||
说明:
|
||||
获取stoken
|
||||
参数:
|
||||
:param uid: 原神uid
|
||||
"""
|
||||
query = cls.query.where(cls.uid == uid)
|
||||
user = await query.gino.first()
|
||||
if user:
|
||||
return user.stoken
|
||||
return None
|
||||
|
||||
@@ -5,6 +5,8 @@ from .._models import Genshin
|
||||
from services.log import logger
|
||||
from nonebot.params import CommandArg, Command
|
||||
from typing import Tuple
|
||||
from utils.http_utils import AsyncHttpx
|
||||
import json
|
||||
|
||||
|
||||
__zx_plugin_name__ = "原神绑定"
|
||||
@@ -39,6 +41,10 @@ bind = on_command(
|
||||
|
||||
unbind = on_command("原神解绑", priority=5, block=True)
|
||||
|
||||
web_Api = "https://api-takumi.mihoyo.com"
|
||||
bbs_Cookie_url = "https://webapi.account.mihoyo.com/Api/cookie_accountinfo_by_loginticket?login_ticket={}"
|
||||
bbs_Cookie_url2 = web_Api + "/auth/api/getMultiTokenByLoginTicket?login_ticket={}&token_types=3&uid={}"
|
||||
|
||||
|
||||
@bind.handle()
|
||||
async def _(event: MessageEvent, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()):
|
||||
@@ -64,9 +70,13 @@ async def _(event: MessageEvent, cmd: Tuple[str, ...] = Command(), arg: Message
|
||||
_x = f"已成功为uid:{uid} 设置米游社id:{msg}"
|
||||
else:
|
||||
if not msg:
|
||||
await bind.finish(
|
||||
"私聊发送!!\n打开 https://bbs.mihoyo.com/ys/\n登录后按F12点击控制台输入document.cookie复制输出的内容即可"
|
||||
)
|
||||
await bind.finish("""私聊发送!!
|
||||
1.以无痕模式打开浏览器(Edge请新建InPrivate窗口)
|
||||
2.打开http://bbs.mihoyo.com/ys/并登陆
|
||||
3.登陆后打开http://user.mihoyo.com/进行登陆
|
||||
4.按下F12,打开控制台,输入以下命令:
|
||||
var cookie=document.cookie;var ask=confirm('Cookie:'+cookie+'\\n\\nDo you want to copy the cookie to the clipboard?');if(ask==true){copy(cookie);msg=cookie}else{msg='Cancel'}
|
||||
5.私聊发送:原神绑定cookie 刚刚复制的cookie""")
|
||||
if isinstance(event, GroupMessageEvent):
|
||||
await bind.finish("请立即撤回你的消息并私聊发送!")
|
||||
uid = await Genshin.get_user_uid(event.user_id)
|
||||
@@ -77,6 +87,33 @@ async def _(event: MessageEvent, cmd: Tuple[str, ...] = Command(), arg: Message
|
||||
if msg.endswith('"') or msg.endswith("'"):
|
||||
msg = msg[:-1]
|
||||
await Genshin.set_cookie(uid, msg)
|
||||
cookie = msg
|
||||
# 用: 代替=, ,代替;
|
||||
cookie = '{"' + cookie.replace('=', '": "').replace("; ", '","') + '"}'
|
||||
print(cookie)
|
||||
cookie_json = json.loads(cookie)
|
||||
print(cookie_json)
|
||||
login_ticket = cookie_json['login_ticket']
|
||||
# try:
|
||||
res = await AsyncHttpx.get(url=bbs_Cookie_url.format(login_ticket))
|
||||
res.encoding = "utf-8"
|
||||
data = json.loads(res.text)
|
||||
print(data)
|
||||
if "成功" in data["data"]["msg"]:
|
||||
stuid = str(data["data"]["cookie_info"]["account_id"])
|
||||
res = await AsyncHttpx.get(url=bbs_Cookie_url2.format(
|
||||
login_ticket, stuid))
|
||||
res.encoding = "utf-8"
|
||||
data = json.loads(res.text)
|
||||
stoken = data["data"]["list"][0]["token"]
|
||||
# await Genshin.set_cookie(uid, cookie)
|
||||
await Genshin.set_stoken(uid, stoken)
|
||||
await Genshin.set_stuid(uid, stuid)
|
||||
await Genshin.set_login_ticket(uid, login_ticket)
|
||||
# except Exception as e:
|
||||
# await bind.finish("获取登陆信息失败,请检查cookie是否正确或更新cookie")
|
||||
elif data["data"]["msg"] == "登录信息已失效,请重新登录":
|
||||
await bind.finish("登录信息失效,请重新获取最新cookie进行绑定")
|
||||
_x = f"已成功为uid:{uid} 设置cookie"
|
||||
if isinstance(event, GroupMessageEvent):
|
||||
await Genshin.set_bind_group(uid, event.group_id)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from .data_source import get_sign_reward_list, genshin_sign
|
||||
from ..mihoyobbs_sign import mihoyobbs_sign
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent
|
||||
from nonebot import on_command
|
||||
from services.log import logger
|
||||
@@ -14,15 +15,15 @@ __plugin_usage__ = """
|
||||
usage:
|
||||
米游社原神签到,需要uid以及cookie
|
||||
且在第二天自动排序签到时间
|
||||
# 不听,就要手动签到!(使用命令 “原神我硬签 or 米游社我硬签”
|
||||
# 不听,就要手动签到!(使用命令 “原神我硬签
|
||||
指令:
|
||||
开/关原神自动签到
|
||||
原神我硬签
|
||||
""".strip()
|
||||
__plugin_des__ = "原神懒人签到"
|
||||
__plugin_cmd__ = ["开启/关闭原神自动签到", "原神我硬签"]
|
||||
__plugin_cmd__ = ["开启/关闭原神自动签到", "原神我硬签", "查看我的cookie"]
|
||||
__plugin_type__ = ("原神相关",)
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_version__ = 0.2
|
||||
__plugin_author__ = "HibiKier"
|
||||
__plugin_settings__ = {
|
||||
"level": 5,
|
||||
@@ -33,7 +34,7 @@ __plugin_settings__ = {
|
||||
|
||||
|
||||
genshin_matcher = on_command(
|
||||
"开原神自动签到", aliases={"关原神自动签到", "原神我硬签"}, priority=5, block=True
|
||||
"开原神自动签到", aliases={"关原神自动签到", "原神我硬签", "查看我的cookie"}, priority=5, block=True
|
||||
)
|
||||
|
||||
|
||||
@@ -41,17 +42,23 @@ genshin_matcher = on_command(
|
||||
async def _(event: MessageEvent, cmd: Tuple[str, ...] = Command()):
|
||||
cmd = cmd[0]
|
||||
uid = await Genshin.get_user_uid(event.user_id)
|
||||
if cmd == "查看我的cookie":
|
||||
my_cookie = await Genshin.get_user_cookie(uid, True)
|
||||
await genshin_matcher.finish("您的cookie为" + my_cookie)
|
||||
if not uid or not await Genshin.get_user_cookie(uid, True):
|
||||
await genshin_matcher.finish("请先绑定uid和cookie!")
|
||||
if "account_id" not in await Genshin.get_user_cookie(uid, True):
|
||||
await genshin_matcher.finish("请更新cookie!")
|
||||
# if "account_id" not in await Genshin.get_user_cookie(uid, True):
|
||||
# await genshin_matcher.finish("请更新cookie!")
|
||||
if cmd == "原神我硬签":
|
||||
try:
|
||||
msg = await genshin_sign(uid)
|
||||
return_data = await mihoyobbs_sign(event.user_id)
|
||||
await genshin_matcher.send(return_data)
|
||||
logger.info(
|
||||
f"(USER {event.user_id}, "
|
||||
f"GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) UID:{uid} 原神签到"
|
||||
)
|
||||
logger.info(msg)
|
||||
# 硬签,移除定时任务
|
||||
try:
|
||||
for i in range(3):
|
||||
@@ -63,7 +70,7 @@ async def _(event: MessageEvent, cmd: Tuple[str, ...] = Command()):
|
||||
await u.clear_sign_time(uid)
|
||||
next_date = await Genshin.random_sign_time(uid)
|
||||
add_job(event.user_id, uid, next_date)
|
||||
msg += f"因开启自动签到\n下一次签到时间为:{next_date.replace(microsecond=0)}"
|
||||
msg += f"\n因开启自动签到\n下一次签到时间为:{next_date.replace(microsecond=0)}"
|
||||
except Exception as e:
|
||||
msg = "原神签到失败..请尝试检查cookie或报告至管理员!"
|
||||
logger.info(
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
from utils.http_utils import AsyncHttpx
|
||||
from configs.config import Config
|
||||
from services.log import logger
|
||||
from .._utils import random_hex, get_old_ds
|
||||
from ..mihoyobbs_sign.setting import *
|
||||
from .._models import Genshin
|
||||
from typing import Optional, Dict
|
||||
import hashlib
|
||||
import random
|
||||
import string
|
||||
import uuid
|
||||
import time
|
||||
|
||||
|
||||
async def genshin_sign(uid: int) -> Optional[str]:
|
||||
@@ -16,22 +21,62 @@ async def genshin_sign(uid: int) -> Optional[str]:
|
||||
return "签到失败..."
|
||||
status = data["message"]
|
||||
if status == "OK":
|
||||
sign_info = await _get_sign_info(uid)
|
||||
if sign_info:
|
||||
sign_info = sign_info["data"]
|
||||
sign_list = await get_sign_reward_list()
|
||||
get_reward = sign_list["data"]["awards"][
|
||||
int(sign_info["total_sign_day"]) - 1
|
||||
]["name"]
|
||||
reward_num = sign_list["data"]["awards"][
|
||||
int(sign_info["total_sign_day"]) - 1
|
||||
]["cnt"]
|
||||
get_im = f"本次签到获得:{get_reward}x{reward_num}"
|
||||
if status == "OK" and sign_info["is_sign"]:
|
||||
return f"\n原神签到成功!\n{get_im}\n本月漏签次数:{sign_info['sign_cnt_missed']}"
|
||||
try:
|
||||
sign_info = await _get_sign_info(uid)
|
||||
if sign_info:
|
||||
sign_info = sign_info["data"]
|
||||
sign_list = await get_sign_reward_list()
|
||||
get_reward = sign_list["data"]["awards"][
|
||||
int(sign_info["total_sign_day"]) - 1
|
||||
]["name"]
|
||||
reward_num = sign_list["data"]["awards"][
|
||||
int(sign_info["total_sign_day"]) - 1
|
||||
]["cnt"]
|
||||
get_im = f"本次签到获得:{get_reward}x{reward_num}"
|
||||
logger.info("get_im:" + get_im + "\nsign_info:" + str(sign_info))
|
||||
if status == "OK" and sign_info["is_sign"]:
|
||||
return f"原神签到成功!\n{get_im}\n本月漏签次数:{sign_info['sign_cnt_missed']}"
|
||||
except Exception as e:
|
||||
logger.error(f"原神签到发生错误 UID:{str(data)}")
|
||||
return f"原神签到发生错误: {str(data)}"
|
||||
else:
|
||||
return status
|
||||
return None
|
||||
if data["data"]["risk_code"] == 375:
|
||||
return "原神签到失败\n账号可能被风控,请前往米游社手动签到!"
|
||||
return str(data)
|
||||
|
||||
|
||||
# 获取请求Header里的DS 当web为true则生成网页端的DS
|
||||
def get_ds(web: bool) -> str:
|
||||
if web:
|
||||
n = mihoyobbs_Salt_web
|
||||
else:
|
||||
n = mihoyobbs_Salt
|
||||
i = str(timestamp())
|
||||
r = random_text(6)
|
||||
c = md5("salt=" + n + "&t=" + i + "&r=" + r)
|
||||
return f"{i},{r},{c}"
|
||||
|
||||
|
||||
# 时间戳
|
||||
def timestamp() -> int:
|
||||
return int(time.time())
|
||||
|
||||
|
||||
def random_text(num: int) -> str:
|
||||
return ''.join(random.sample(string.ascii_lowercase + string.digits, num))
|
||||
|
||||
|
||||
def md5(text: str) -> str:
|
||||
md5 = hashlib.md5()
|
||||
md5.update(text.encode())
|
||||
return md5.hexdigest()
|
||||
|
||||
|
||||
# 生成一个device id
|
||||
def get_device_id(cookie) -> str:
|
||||
return str(uuid.uuid3(uuid.NAMESPACE_URL, cookie)).replace(
|
||||
'-', '').upper()
|
||||
|
||||
|
||||
async def _sign(uid: int, server_id: str = "cn_gf01") -> Optional[Dict[str, str]]:
|
||||
@@ -43,20 +88,16 @@ async def _sign(uid: int, server_id: str = "cn_gf01") -> Optional[Dict[str, str]
|
||||
if str(uid)[0] == "5":
|
||||
server_id = "cn_qd01"
|
||||
try:
|
||||
cookie = await Genshin.get_user_cookie(uid, True)
|
||||
headers['DS'] = get_ds(web=True)
|
||||
headers['Referer'] = 'https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?bbs_auth_required=true' \
|
||||
f'&act_id={genshin_Act_id}&utm_source=bbs&utm_medium=mys&utm_campaign=icon'
|
||||
headers['Cookie'] = cookie
|
||||
headers['x-rpc-device_id'] = get_device_id(cookie)
|
||||
req = await AsyncHttpx.post(
|
||||
url="https://api-takumi.mihoyo.com/event/bbs_sign_reward/sign",
|
||||
headers={
|
||||
"User_Agent": "Mozilla/5.0 (Linux; Android 10; MIX 2 Build/QKQ1.190825.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.101 Mobile Safari/537.36 miHoYoBBS/2.3.0",
|
||||
"Cookie": await Genshin.get_user_cookie(int(uid), True),
|
||||
"x-rpc-device_id": random_hex(32),
|
||||
"Origin": "https://webstatic.mihoyo.com",
|
||||
"X_Requested_With": "com.mihoyo.hyperion",
|
||||
"DS": get_old_ds(),
|
||||
"x-rpc-client_type": "5",
|
||||
"Referer": "https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?bbs_auth_required=true&act_id=e202009291139501&utm_source=bbs&utm_medium=mys&utm_campaign=icon",
|
||||
"x-rpc-app_version": "2.3.0",
|
||||
},
|
||||
json={"act_id": "e202009291139501", "uid": uid, "region": server_id},
|
||||
url=genshin_Signurl,
|
||||
headers=headers,
|
||||
json={"act_id": genshin_Act_id, "uid": uid, "region": server_id},
|
||||
)
|
||||
return req.json()
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from .data_source import genshin_sign
|
||||
from ..mihoyobbs_sign import mihoyobbs_sign
|
||||
from models.group_member_info import GroupInfoUser
|
||||
from utils.message_builder import at
|
||||
from services.log import logger
|
||||
@@ -57,6 +58,11 @@ async def _sign(user_id: int, uid: int, count: int):
|
||||
:param uid: uid
|
||||
:param count: 执行次数
|
||||
"""
|
||||
try:
|
||||
return_data = await mihoyobbs_sign(user_id)
|
||||
except Exception as e:
|
||||
logger.error(f"mihoyobbs_sign error:{e}")
|
||||
return_data = "米游社签到失败,请尝试发送'米游社签到'进行手动签到"
|
||||
if count < 3:
|
||||
try:
|
||||
msg = await genshin_sign(uid)
|
||||
@@ -101,6 +107,7 @@ async def _sign(user_id: int, uid: int, count: int):
|
||||
bot = get_bot()
|
||||
if bot:
|
||||
if user_id in [x["user_id"] for x in await bot.get_friend_list()]:
|
||||
await bot.send_private_msg(user_id=user_id, message=return_data)
|
||||
await bot.send_private_msg(user_id=user_id, message=msg)
|
||||
else:
|
||||
if not (group_id := await Genshin.get_bind_group(uid)):
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent
|
||||
from nonebot import on_command
|
||||
from services.log import logger
|
||||
# from .init_task import add_job, scheduler, _sign
|
||||
# from apscheduler.jobstores.base import JobLookupError
|
||||
from .._models import Genshin
|
||||
from nonebot.params import Command
|
||||
from typing import Tuple
|
||||
from .mihoyobbs import *
|
||||
|
||||
|
||||
__zx_plugin_name__ = "米游社自动签到"
|
||||
__plugin_usage__ = """
|
||||
usage:
|
||||
发送'米游社签到'或绑定原神自动签到
|
||||
即可手动/自动进行米游社签到
|
||||
(若启用了原神自动签到会在签到原神同时完成米游币领取)
|
||||
--> 每天白嫖90-110米游币不香吗
|
||||
注:需要重新绑定原神cookie!!!
|
||||
遇到问题请提issue或@作者
|
||||
""".strip()
|
||||
__plugin_des__ = "米游社自动签到任务"
|
||||
__plugin_cmd__ = ["米游社签到", "米游社我硬签"]
|
||||
__plugin_type__ = ("原神相关",)
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HDU_Nbsp"
|
||||
__plugin_settings__ = {
|
||||
"level": 5,
|
||||
"default_status": True,
|
||||
"limit_superuser": False,
|
||||
"cmd": ["原神签到"],
|
||||
}
|
||||
|
||||
mihoyobbs_matcher = on_command(
|
||||
"米游社签到", aliases={"米游社我硬签"}, priority=5, block=True
|
||||
)
|
||||
|
||||
|
||||
@mihoyobbs_matcher.handle()
|
||||
async def _(event: MessageEvent, cmd: Tuple[str, ...] = Command()):
|
||||
await mihoyobbs_matcher.send("提交米游社签到申请")
|
||||
return_data = await mihoyobbs_sign(event.user_id)
|
||||
if return_data:
|
||||
await mihoyobbs_matcher.finish(return_data)
|
||||
else:
|
||||
await mihoyobbs_matcher.finish("米游社签到失败,请查看控制台输出")
|
||||
|
||||
|
||||
async def mihoyobbs_sign(user_id):
|
||||
uid = await Genshin.get_user_uid(user_id)
|
||||
stuid = await Genshin.get_stuid(uid)
|
||||
stoken = await Genshin.get_stoken(uid)
|
||||
cookie = await Genshin.get_user_cookie(uid)
|
||||
bbs = mihoyobbs.Mihoyobbs(stuid=stuid, stoken=stoken, cookie=cookie)
|
||||
await bbs.init()
|
||||
return_data = ""
|
||||
if bbs.Task_do["bbs_Sign"] and bbs.Task_do["bbs_Read_posts"] and bbs.Task_do["bbs_Like_posts"] and \
|
||||
bbs.Task_do["bbs_Share"]:
|
||||
return_data += f"今天的米游社签到任务已经全部完成了!\n" \
|
||||
f"一共获得{mihoyobbs.today_have_get_coins}个米游币\n目前有{mihoyobbs.Have_coins}个米游币"
|
||||
logger.info(f"今天已经全部完成了!一共获得{mihoyobbs.today_have_get_coins}个米游币,目前有{mihoyobbs.Have_coins}个米游币")
|
||||
else:
|
||||
i = 0
|
||||
print("开始签到")
|
||||
print(mihoyobbs.today_have_get_coins)
|
||||
while mihoyobbs.today_get_coins != 0 and i < 3:
|
||||
# if i > 0:
|
||||
await bbs.refresh_list()
|
||||
await bbs.signing()
|
||||
await bbs.read_posts()
|
||||
await bbs.like_posts()
|
||||
await bbs.share_post()
|
||||
await bbs.get_tasks_list()
|
||||
i += 1
|
||||
return_data += "\n" + f"今天已经获得{mihoyobbs.today_have_get_coins}个米游币\n" \
|
||||
f"还能获得{mihoyobbs.today_get_coins}个米游币\n目前有{mihoyobbs.Have_coins}个米游币"
|
||||
logger.info(f"今天已经获得{mihoyobbs.today_have_get_coins}个米游币,"
|
||||
f"还能获得{mihoyobbs.today_get_coins}个米游币,目前有{mihoyobbs.Have_coins}个米游币")
|
||||
return return_data
|
||||
@@ -0,0 +1,6 @@
|
||||
class CookieError(Exception):
|
||||
def __init__(self, info):
|
||||
self.info = info
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.info)
|
||||
@@ -0,0 +1,193 @@
|
||||
from services.log import logger
|
||||
from .error import CookieError
|
||||
from utils.http_utils import AsyncHttpx
|
||||
from .setting import *
|
||||
from .tools import *
|
||||
import json
|
||||
|
||||
today_get_coins = 0
|
||||
today_have_get_coins = 0 # 这个变量以后可能会用上,先留着了
|
||||
Have_coins = 0
|
||||
|
||||
|
||||
class Mihoyobbs:
|
||||
def __init__(self, stuid: str, stoken: str, cookie: str) -> None:
|
||||
self.postsList = None
|
||||
self.headers = {
|
||||
"DS": get_ds(web=False),
|
||||
"cookie": f'stuid={stuid};stoken={stoken}',
|
||||
"x-rpc-client_type": mihoyobbs_Client_type,
|
||||
"x-rpc-app_version": mihoyobbs_Version,
|
||||
"x-rpc-sys_version": "6.0.1",
|
||||
"x-rpc-channel": "miyousheluodi",
|
||||
"x-rpc-device_id": get_device_id(cookie=cookie),
|
||||
"x-rpc-device_name": random_text(random.randint(1, 10)),
|
||||
"x-rpc-device_model": "Mi 10",
|
||||
"Referer": "https://app.mihoyo.com",
|
||||
"Host": "bbs-api.mihoyo.com",
|
||||
"User-Agent": "okhttp/4.8.0"
|
||||
}
|
||||
self.Task_do = {
|
||||
"bbs_Sign": False,
|
||||
"bbs_Read_posts": False,
|
||||
"bbs_Read_posts_num": 3,
|
||||
"bbs_Like_posts": False,
|
||||
"bbs_Like_posts_num": 5,
|
||||
"bbs_Share": False
|
||||
}
|
||||
|
||||
async def init(self):
|
||||
await self.get_tasks_list()
|
||||
# 如果这三个任务都做了就没必要获取帖子了
|
||||
if self.Task_do["bbs_Read_posts"] and self.Task_do["bbs_Like_posts"] and self.Task_do["bbs_Share"]:
|
||||
pass
|
||||
else:
|
||||
self.postsList = await self.get_list()
|
||||
|
||||
async def refresh_list(self) -> None:
|
||||
self.postsList = await self.get_list()
|
||||
|
||||
# 获取任务列表,用来判断做了哪些任务
|
||||
async def get_tasks_list(self):
|
||||
global today_get_coins
|
||||
global today_have_get_coins
|
||||
global Have_coins
|
||||
logger.info("正在获取任务列表")
|
||||
req = await AsyncHttpx.get(url=bbs_Tasks_list, headers=self.headers)
|
||||
data = req.json()
|
||||
if "err" in data["message"] or data["retcode"] == -100:
|
||||
logger.error("获取任务列表失败,你的cookie可能已过期,请重新设置cookie。")
|
||||
raise CookieError('Cookie expires')
|
||||
else:
|
||||
today_get_coins = data["data"]["can_get_points"]
|
||||
today_have_get_coins = data["data"]["already_received_points"]
|
||||
Have_coins = data["data"]["total_points"]
|
||||
# 如果当日可获取米游币数量为0直接判断全部任务都完成了
|
||||
if today_get_coins == 0:
|
||||
self.Task_do["bbs_Sign"] = True
|
||||
self.Task_do["bbs_Read_posts"] = True
|
||||
self.Task_do["bbs_Like_posts"] = True
|
||||
self.Task_do["bbs_Share"] = True
|
||||
else:
|
||||
# 如果第0个大于或等于62则直接判定任务没做
|
||||
if data["data"]["states"][0]["mission_id"] >= 62:
|
||||
logger.info(f"今天可以获得{today_get_coins}个米游币")
|
||||
pass
|
||||
else:
|
||||
logger.info(f"还有任务未完成,今天还能获得{today_get_coins}米游币")
|
||||
for i in data["data"]["states"]:
|
||||
# 58是讨论区签到
|
||||
if i["mission_id"] == 58:
|
||||
if i["is_get_award"]:
|
||||
self.Task_do["bbs_Sign"] = True
|
||||
# 59是看帖子
|
||||
elif i["mission_id"] == 59:
|
||||
if i["is_get_award"]:
|
||||
self.Task_do["bbs_Read_posts"] = True
|
||||
else:
|
||||
self.Task_do["bbs_Read_posts_num"] -= i["happened_times"]
|
||||
# 60是给帖子点赞
|
||||
elif i["mission_id"] == 60:
|
||||
if i["is_get_award"]:
|
||||
self.Task_do["bbs_Like_posts"] = True
|
||||
else:
|
||||
self.Task_do["bbs_Like_posts_num"] -= i["happened_times"]
|
||||
# 61是分享帖子
|
||||
elif i["mission_id"] == 61:
|
||||
if i["is_get_award"]:
|
||||
self.Task_do["bbs_Share"] = True
|
||||
# 分享帖子,是最后一个任务,到这里了下面都是一次性任务,直接跳出循环
|
||||
break
|
||||
|
||||
# 获取要帖子列表
|
||||
async def get_list(self) -> list:
|
||||
temp_list = []
|
||||
logger.info("正在获取帖子列表......")
|
||||
req = await AsyncHttpx.get(url=bbs_List_url.format(mihoyobbs_List_Use[0]["forumId"]),
|
||||
headers=self.headers)
|
||||
data = req.json()["data"]["list"]
|
||||
for n in range(5):
|
||||
r_l = random.choice(data)
|
||||
while r_l["post"]["subject"] in str(temp_list):
|
||||
r_l = random.choice(data)
|
||||
temp_list.append([r_l["post"]["post_id"], r_l["post"]["subject"]])
|
||||
# temp_list.append([data["data"]["list"][n]["post"]["post_id"], data["data"]["list"][n]["post"]["subject"]])
|
||||
|
||||
logger.info("已获取{}个帖子".format(len(temp_list)))
|
||||
return temp_list
|
||||
|
||||
# 进行签到操作
|
||||
async def signing(self):
|
||||
if self.Task_do["bbs_Sign"]:
|
||||
logger.info("讨论区任务已经完成过了~")
|
||||
else:
|
||||
logger.info("正在签到......")
|
||||
header = {}
|
||||
header.update(self.headers)
|
||||
for i in mihoyobbs_List_Use:
|
||||
header["DS"] = get_ds2("", json.dumps({"gids": i["id"]}))
|
||||
req = await AsyncHttpx.post(url=bbs_Sign_url, json={"gids": i["id"]}, headers=header)
|
||||
data = req.json()
|
||||
if "err" not in data["message"]:
|
||||
logger.info(str(i["name"] + data["message"]))
|
||||
time.sleep(random.randint(2, 8))
|
||||
else:
|
||||
logger.error("签到失败,你的cookie可能已过期,请重新设置cookie。")
|
||||
raise CookieError('Cookie expires')
|
||||
|
||||
# 看帖子
|
||||
async def read_posts(self):
|
||||
if self.Task_do["bbs_Read_posts"]:
|
||||
logger.info("看帖任务已经完成过了~")
|
||||
else:
|
||||
logger.info("正在看帖......")
|
||||
for i in range(self.Task_do["bbs_Read_posts_num"]):
|
||||
req = await AsyncHttpx.get(url=bbs_Detail_url.format(self.postsList[i][0]), headers=self.headers)
|
||||
data = req.json()
|
||||
if data["message"] == "OK":
|
||||
logger.debug("看帖:{} 成功".format(self.postsList[i][1]))
|
||||
time.sleep(random.randint(2, 8))
|
||||
|
||||
# 点赞
|
||||
async def like_posts(self):
|
||||
if self.Task_do["bbs_Like_posts"]:
|
||||
logger.info("点赞任务已经完成过了~")
|
||||
else:
|
||||
logger.info("正在点赞......")
|
||||
for i in range(self.Task_do["bbs_Like_posts_num"]):
|
||||
req = await AsyncHttpx.post(url=bbs_Like_url, headers=self.headers,
|
||||
json={"post_id": self.postsList[i][0], "is_cancel": False})
|
||||
data = req.json()
|
||||
if data["message"] == "OK":
|
||||
logger.debug("点赞:{} 成功".format(self.postsList[i][1]))
|
||||
# 判断取消点赞是否打开
|
||||
# if config.config["mihoyobbs"]["un_like"] :
|
||||
# time.sleep(random.randint(2, 8))
|
||||
# req = httpx.post(url=bbs_Like_url, headers=self.headers,
|
||||
# json={"post_id": self.postsList[i][0], "is_cancel": True})
|
||||
# data = req.json()
|
||||
# if data["message"] == "OK":
|
||||
# logger.debug("取消点赞:{} 成功".format(self.postsList[i][1]))
|
||||
time.sleep(random.randint(2, 8))
|
||||
|
||||
# 分享操作
|
||||
|
||||
async def share_post(self):
|
||||
if self.Task_do["bbs_Share"]:
|
||||
logger.info("分享任务已经完成过了~")
|
||||
else:
|
||||
logger.info("正在执行分享任务......")
|
||||
for i in range(3):
|
||||
req = await AsyncHttpx.get(url=bbs_Share_url.format(self.postsList[0][0]), headers=self.headers)
|
||||
data = req.json()
|
||||
if data["message"] == "OK":
|
||||
logger.debug("分享:{} 成功".format(self.postsList[0][1]))
|
||||
logger.info("分享任务执行成功......")
|
||||
break
|
||||
else:
|
||||
logger.debug(f"分享任务执行失败,正在执行第{i + 2}次,共3次")
|
||||
time.sleep(random.randint(2, 8))
|
||||
time.sleep(random.randint(2, 8))
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# 米游社的Salt
|
||||
mihoyobbs_Salt = "z8DRIUjNDT7IT5IZXvrUAxyupA1peND9"
|
||||
mihoyobbs_Salt2 = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v"
|
||||
mihoyobbs_Salt_web = "9nQiU3AV0rJSIBWgdynfoGMGKaklfbM7"
|
||||
# 米游社的版本
|
||||
mihoyobbs_Version = "2.34.1" # Slat和Version相互对应
|
||||
# 米游社的客户端类型
|
||||
mihoyobbs_Client_type = "2" # 1为ios 2为安卓
|
||||
mihoyobbs_Client_type_web = "5" # 4为pc web 5为mobile web
|
||||
# 米游社的分区列表
|
||||
mihoyobbs_List = [{
|
||||
"id": "1",
|
||||
"forumId": "1",
|
||||
"name": "崩坏3",
|
||||
"url": "https://bbs.mihoyo.com/bh3/"
|
||||
}, {
|
||||
"id": "2",
|
||||
"forumId": "26",
|
||||
"name": "原神",
|
||||
"url": "https://bbs.mihoyo.com/ys/"
|
||||
}, {
|
||||
"id": "3",
|
||||
"forumId": "30",
|
||||
"name": "崩坏2",
|
||||
"url": "https://bbs.mihoyo.com/bh2/"
|
||||
}, {
|
||||
"id": "4",
|
||||
"forumId": "37",
|
||||
"name": "未定事件簿",
|
||||
"url": "https://bbs.mihoyo.com/wd/"
|
||||
}, {
|
||||
"id": "5",
|
||||
"forumId": "34",
|
||||
"name": "大别野",
|
||||
"url": "https://bbs.mihoyo.com/dby/"
|
||||
}, {
|
||||
"id": "6",
|
||||
"forumId": "52",
|
||||
"name": "崩坏:星穹铁道",
|
||||
"url": "https://bbs.mihoyo.com/sr/"
|
||||
}, {
|
||||
"id": "8",
|
||||
"forumId": "57",
|
||||
"name": "绝区零",
|
||||
"url": "https://bbs.mihoyo.com/zzz/"
|
||||
}]
|
||||
|
||||
game_id2name = {
|
||||
"bh2_cn": "崩坏2",
|
||||
"bh3_cn": "崩坏3",
|
||||
"nxx_cn": "未定事件簿",
|
||||
"hk4e_cn": "原神",
|
||||
}
|
||||
# Config Load之后run里面进行列表的选择
|
||||
mihoyobbs_List_Use = [{
|
||||
"id": "2",
|
||||
"forumId": "26",
|
||||
"name": "原神",
|
||||
"url": "https://bbs.mihoyo.com/ys/"
|
||||
},
|
||||
# 不玩原神可以把签到讨论区换为大别墅
|
||||
# {
|
||||
# "id": "5",
|
||||
# "forumId": "34",
|
||||
# "name": "大别野",
|
||||
# "url": "https://bbs.mihoyo.com/dby/"
|
||||
# }
|
||||
]
|
||||
|
||||
# 游戏签到的请求头
|
||||
headers = {
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'DS': "",
|
||||
'Origin': 'https://webstatic.mihoyo.com',
|
||||
'x-rpc-app_version': mihoyobbs_Version,
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 12; Unspecified Device) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
f'Version/4.0 Chrome/103.0.5060.129 Mobile Safari/537.36 miHoYoBBS/{mihoyobbs_Version}',
|
||||
'x-rpc-client_type': mihoyobbs_Client_type_web,
|
||||
'Referer': '',
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
'Accept-Language': 'zh-CN,en-US;q=0.8',
|
||||
'X-Requested-With': 'com.mihoyo.hyperion',
|
||||
"Cookie": "",
|
||||
'x-rpc-device_id': ""
|
||||
}
|
||||
|
||||
# 通用设置
|
||||
bbs_Api = "https://bbs-api.mihoyo.com"
|
||||
web_Api = "https://api-takumi.mihoyo.com"
|
||||
account_Info_url = web_Api + "/binding/api/getUserGameRolesByCookie?game_biz="
|
||||
|
||||
# 米游社的API列表
|
||||
bbs_Cookie_url = "https://webapi.account.mihoyo.com/Api/cookie_accountinfo_by_loginticket?login_ticket={}"
|
||||
bbs_Cookie_url2 = web_Api + "/auth/api/getMultiTokenByLoginTicket?login_ticket={}&token_types=3&uid={}"
|
||||
bbs_Tasks_list = bbs_Api + "/apihub/sapi/getUserMissionsState" # 获取任务列表
|
||||
bbs_Sign_url = bbs_Api + "/apihub/app/api/signIn" # post
|
||||
bbs_List_url = bbs_Api + "/post/api/getForumPostList?forum_id={}&is_good=false&is_hot=false&page_size=20&sort_type=1"
|
||||
bbs_Detail_url = bbs_Api + "/post/api/getPostFull?post_id={}"
|
||||
bbs_Share_url = bbs_Api + "/apihub/api/getShareConf?entity_id={}&entity_type=1"
|
||||
bbs_Like_url = bbs_Api + "/apihub/sapi/upvotePost" # post json
|
||||
|
||||
# 崩坏2自动签到相关的相关设置
|
||||
honkai2_Act_id = "e202203291431091"
|
||||
honkai2_checkin_rewards = f'{web_Api}/event/luna/home?lang=zh-cn&act_id={honkai2_Act_id}'
|
||||
honkai2_Is_signurl = web_Api + "/event/luna/info?lang=zh-cn&act_id={}®ion={}&uid={}"
|
||||
honkai2_Sign_url = web_Api + "/event/luna/sign"
|
||||
|
||||
# 崩坏3自动签到相关的设置
|
||||
honkai3rd_Act_id = "e202207181446311"
|
||||
honkai3rd_checkin_rewards = f'{web_Api}/event/luna/home?lang=zh-cn&act_id={honkai3rd_Act_id}'
|
||||
honkai3rd_Is_signurl = web_Api + "/event/luna/info?lang=zh-cn&act_id={}®ion={}&uid={}"
|
||||
honkai3rd_Sign_url = web_Api + "/event/luna/sign"
|
||||
|
||||
# 未定事件簿自动签到相关设置
|
||||
tearsofthemis_Act_id = "e202202251749321"
|
||||
tearsofthemis_checkin_rewards = f'{web_Api}/event/luna/home?lang=zh-cn&act_id={tearsofthemis_Act_id}'
|
||||
tearsofthemis_Is_signurl = honkai2_Is_signurl
|
||||
tearsofthemis_Sign_url = honkai2_Sign_url # 和二崩完全一致
|
||||
|
||||
# 原神自动签到相关的设置
|
||||
genshin_Act_id = "e202009291139501"
|
||||
genshin_checkin_rewards = f'{web_Api}/event/bbs_sign_reward/home?act_id={genshin_Act_id}'
|
||||
genshin_Is_signurl = web_Api + "/event/bbs_sign_reward/info?act_id={}®ion={}&uid={}"
|
||||
genshin_Signurl = web_Api + "/event/bbs_sign_reward/sign"
|
||||
@@ -0,0 +1,65 @@
|
||||
import uuid
|
||||
import time
|
||||
import random
|
||||
import string
|
||||
import hashlib
|
||||
from .setting import *
|
||||
|
||||
|
||||
# md5计算
|
||||
def md5(text: str) -> str:
|
||||
md5 = hashlib.md5()
|
||||
md5.update(text.encode())
|
||||
return md5.hexdigest()
|
||||
|
||||
|
||||
# 随机文本
|
||||
def random_text(num: int) -> str:
|
||||
return ''.join(random.sample(string.ascii_lowercase + string.digits, num))
|
||||
|
||||
|
||||
# 时间戳
|
||||
def timestamp() -> int:
|
||||
return int(time.time())
|
||||
|
||||
|
||||
# 获取请求Header里的DS 当web为true则生成网页端的DS
|
||||
def get_ds(web: bool) -> str:
|
||||
if web:
|
||||
n = mihoyobbs_Salt_web
|
||||
else:
|
||||
n = mihoyobbs_Salt
|
||||
i = str(timestamp())
|
||||
r = random_text(6)
|
||||
c = md5("salt=" + n + "&t=" + i + "&r=" + r)
|
||||
return f"{i},{r},{c}"
|
||||
|
||||
|
||||
# 获取请求Header里的DS(版本2) 这个版本ds之前见到都是查询接口里的
|
||||
def get_ds2(q: str, b: str) -> str:
|
||||
n = mihoyobbs_Salt2
|
||||
i = str(timestamp())
|
||||
r = str(random.randint(100001, 200000))
|
||||
add = f'&b={b}&q={q}'
|
||||
c = md5("salt=" + n + "&t=" + i + "&r=" + r + add)
|
||||
return f"{i},{r},{c}"
|
||||
|
||||
|
||||
# 生成一个device id
|
||||
def get_device_id(cookie) -> str:
|
||||
return str(uuid.uuid3(uuid.NAMESPACE_URL, cookie))
|
||||
|
||||
|
||||
# 获取签到的奖励名称
|
||||
def get_item(raw_data: dict) -> str:
|
||||
temp_name = raw_data["name"]
|
||||
temp_cnt = raw_data["cnt"]
|
||||
return f"{temp_name}x{temp_cnt}"
|
||||
|
||||
|
||||
# 获取明天早晨0点的时间戳
|
||||
def next_day() -> int:
|
||||
now_time = int(time.time())
|
||||
next_day_time = now_time - now_time % 86400 + time.timezone + 86400
|
||||
return next_day_time
|
||||
|
||||
@@ -26,12 +26,17 @@ memo_path.mkdir(exist_ok=True, parents=True)
|
||||
@driver.on_startup
|
||||
async def _():
|
||||
for name, url in zip(
|
||||
["resin.png", "task.png", "resin_discount.png"],
|
||||
[
|
||||
"resin.png", "task.png", "resin_discount.png", "chengehu.png",
|
||||
"zhibian.png"
|
||||
],
|
||||
[
|
||||
"https://upload-bbs.mihoyo.com/upload/2021/09/29/8819732/54266243c7d15ba31690c8f5d63cc3c6_71491376413333325"
|
||||
"20.png?x-oss-process=image//resize,s_600/quality,q_80/auto-orient,0/interlace,1/format,png",
|
||||
"https://patchwiki.biligame.com/images/ys/thumb/c/cc/6k6kuj1kte6m1n7hexqfrn92z6h4yhh.png/60px-委托任务logo.png",
|
||||
"https://patchwiki.biligame.com/images/ys/d/d9/t1hv6wpucbwucgkhjntmzroh90nmcdv.png",
|
||||
"https://s3.bmp.ovh/imgs/2022/07/24/28d9338c7da4bcb2.png",
|
||||
"https://genshin.honeyhunterworld.com/img/gadget/i_3016.png",
|
||||
],
|
||||
):
|
||||
file = memo_path / name
|
||||
@@ -40,7 +45,8 @@ async def _():
|
||||
logger.info(f"已下载原神便签资源 -> {file}...")
|
||||
|
||||
|
||||
async def get_user_memo(user_id: int, uid: int, uname: str) -> Optional[Union[str, MessageSegment]]:
|
||||
async def get_user_memo(user_id: int, uid: int,
|
||||
uname: str) -> Optional[Union[str, MessageSegment]]:
|
||||
uid = str(uid)
|
||||
if uid[0] in ["1", "2"]:
|
||||
server_id = "cn_gf01"
|
||||
@@ -54,12 +60,16 @@ async def get_user_memo(user_id: int, uid: int, uname: str) -> Optional[Union[st
|
||||
async def get_memo(uid: str, server_id: str) -> "Union[str, dict], int":
|
||||
try:
|
||||
req = await AsyncHttpx.get(
|
||||
url=f"https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/dailyNote?server={server_id}&role_id={uid}",
|
||||
url=
|
||||
f"https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/dailyNote?server={server_id}&role_id={uid}",
|
||||
headers={
|
||||
"DS": get_ds(f"role_id={uid}&server={server_id}"),
|
||||
"x-rpc-app_version": Config.get_config("genshin", "mhyVersion"),
|
||||
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1",
|
||||
"x-rpc-client_type": Config.get_config("genshin", "client_type"),
|
||||
"x-rpc-app_version": Config.get_config("genshin",
|
||||
"mhyVersion"),
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1",
|
||||
"x-rpc-client_type": Config.get_config("genshin",
|
||||
"client_type"),
|
||||
"Referer": "https://webstatic.mihoyo.com/",
|
||||
"Cookie": await Genshin.get_user_cookie(int(uid))
|
||||
},
|
||||
@@ -75,11 +85,18 @@ async def get_memo(uid: str, server_id: str) -> "Union[str, dict], int":
|
||||
return "发生了一些错误,请稍后再试", 998
|
||||
|
||||
|
||||
def create_border(
|
||||
image_name: str, content: str, notice_text: str, value: str
|
||||
) -> BuildImage:
|
||||
border = BuildImage(500, 100, color="#E0D9D1", font="HYWenHei-85W.ttf", font_size=20)
|
||||
text_bk = BuildImage(350, 96, color="#F5F1EB", font_size=23, font="HYWenHei-85W.ttf")
|
||||
def create_border(image_name: str, content: str, notice_text: str,
|
||||
value: str) -> BuildImage:
|
||||
border = BuildImage(500,
|
||||
75,
|
||||
color="#E0D9D1",
|
||||
font="HYWenHei-85W.ttf",
|
||||
font_size=20)
|
||||
text_bk = BuildImage(350,
|
||||
75,
|
||||
color="#F5F1EB",
|
||||
font_size=23,
|
||||
font="HYWenHei-85W.ttf")
|
||||
_x = 70 if image_name == "resin.png" else 50
|
||||
_px = 10 if image_name == "resin.png" else 20
|
||||
text_bk.paste(
|
||||
@@ -88,7 +105,7 @@ def create_border(
|
||||
True,
|
||||
center_type="by_height",
|
||||
)
|
||||
text_bk.text((87, 20), content)
|
||||
text_bk.text((87, 15), content)
|
||||
text_bk.paste(
|
||||
BuildImage(
|
||||
0,
|
||||
@@ -98,18 +115,19 @@ def create_border(
|
||||
font="HYWenHei-85W.ttf",
|
||||
font_size=17,
|
||||
),
|
||||
(87, 50),
|
||||
(87, 45),
|
||||
True,
|
||||
)
|
||||
font_width, _ = border.getsize(value)
|
||||
border.text((350 + 76 - int(font_width / 2), 0), value, center_type="by_height")
|
||||
border.text((350 + 76 - int(font_width / 2), 0),
|
||||
value,
|
||||
center_type="by_height")
|
||||
border.paste(text_bk, (2, 0), center_type="by_height")
|
||||
return border
|
||||
|
||||
|
||||
async def parse_data_and_draw(
|
||||
user_id: int, uid: str, server_id: str, uname: str
|
||||
) -> Union[str, MessageSegment]:
|
||||
async def parse_data_and_draw(user_id: int, uid: str, server_id: str,
|
||||
uname: str) -> Union[str, MessageSegment]:
|
||||
data, code = await get_memo(uid, server_id)
|
||||
if code != 200:
|
||||
return data
|
||||
@@ -120,13 +138,11 @@ async def parse_data_and_draw(
|
||||
if not role_avatar.exists():
|
||||
await AsyncHttpx.download_file(x["avatar_side_icon"], role_avatar)
|
||||
return await asyncio.get_event_loop().run_in_executor(
|
||||
None, _parse_data_and_draw, data, user_avatar, uid, uname
|
||||
)
|
||||
None, _parse_data_and_draw, data, user_avatar, uid, uname)
|
||||
|
||||
|
||||
def _parse_data_and_draw(
|
||||
data: dict, user_avatar: BytesIO, uid: int, uname: str
|
||||
) -> Union[str, MessageSegment]:
|
||||
def _parse_data_and_draw(data: dict, user_avatar: BytesIO, uid: int,
|
||||
uname: str) -> Union[str, MessageSegment]:
|
||||
current_resin = data["current_resin"] # 当前树脂
|
||||
max_resin = data["max_resin"] # 最大树脂
|
||||
resin_recovery_time = data["resin_recovery_time"] # 树脂全部回复时间
|
||||
@@ -137,17 +153,37 @@ def _parse_data_and_draw(
|
||||
current_expedition_num = data["current_expedition_num"] # 当前挖矿人数
|
||||
max_expedition_num = data["max_expedition_num"] # 每日挖矿最大人数
|
||||
expeditions = data["expeditions"] # 挖矿详情
|
||||
|
||||
current_coin = data["current_home_coin"] # 当前宝钱
|
||||
max_coin = data["max_home_coin"] # 最大宝钱
|
||||
coin_recovery_time = data["home_coin_recovery_time"] # 宝钱全部回复时间
|
||||
transformer_available = data["transformer"]["obtained"] # 参量质变仪可获取
|
||||
transformer_state = data["transformer"]["recovery_time"][
|
||||
"reached"] # 参量质变仪状态
|
||||
transformer_recovery_time = data["transformer"]["recovery_time"][
|
||||
"Day"] # 参量质变仪回复时间
|
||||
transformer_recovery_hour = data["transformer"]["recovery_time"][
|
||||
"Hour"] # 参量质变仪回复时间
|
||||
coin_minute, coin_second = divmod(int(coin_recovery_time), 60)
|
||||
coin_hour, coin_minute = divmod(coin_minute, 60)
|
||||
#print(data)
|
||||
minute, second = divmod(int(resin_recovery_time), 60)
|
||||
hour, minute = divmod(minute, 60)
|
||||
|
||||
A = BuildImage(1030, 520, color="#f1e9e1", font_size=15, font="HYWenHei-85W.ttf")
|
||||
A = BuildImage(1030,
|
||||
570,
|
||||
color="#f1e9e1",
|
||||
font_size=15,
|
||||
font="HYWenHei-85W.ttf")
|
||||
A.text((10, 15), "原神便笺 | Create By ZhenXun", (198, 186, 177))
|
||||
ava = BuildImage(100, 100, background=user_avatar)
|
||||
ava.circle()
|
||||
A.paste(ava, (40, 40), True)
|
||||
A.paste(
|
||||
BuildImage(0, 0, plain_text=uname, font_size=20, font="HYWenHei-85W.ttf"),
|
||||
BuildImage(0,
|
||||
0,
|
||||
plain_text=uname,
|
||||
font_size=20,
|
||||
font="HYWenHei-85W.ttf"),
|
||||
(160, 62),
|
||||
True,
|
||||
)
|
||||
@@ -177,26 +213,52 @@ def _parse_data_and_draw(
|
||||
"今日委托已全部完成" if finished_task_num == total_task_num else "今日委托完成数量不足",
|
||||
f"{finished_task_num}/{total_task_num}",
|
||||
)
|
||||
A.paste(border, (10, 265))
|
||||
A.paste(border, (10, 235))
|
||||
border = create_border(
|
||||
"resin_discount.png",
|
||||
"值得铭记的强敌",
|
||||
"本周剩余消耗减半次数",
|
||||
f"{remain_resin_discount_num}/{resin_discount_num_limit}",
|
||||
)
|
||||
A.paste(border, (10, 375))
|
||||
expeditions_border = BuildImage(
|
||||
470, 430, color="#E0D9D1", font="HYWenHei-85W.ttf", font_size=20
|
||||
A.paste(border, (10, 315))
|
||||
border = create_border(
|
||||
"chengehu.png",
|
||||
"洞天财翁-洞天宝钱",
|
||||
"洞天财翁已达到存储上限"
|
||||
if current_coin == max_coin else f"{coin_hour}小时{coin_minute}分钟后存满",
|
||||
f"{current_coin}/{max_coin}",
|
||||
)
|
||||
expeditions_text = BuildImage(
|
||||
466, 426, color="#F5F1EB", font_size=23, font="HYWenHei-85W.ttf"
|
||||
A.paste(border, (10, 395))
|
||||
border = create_border(
|
||||
"zhibian.png",
|
||||
"参量质变仪",
|
||||
"不存在" if not transformer_available else
|
||||
"已准备完成 " if transformer_state else f"{transformer_recovery_hour}小时后可使用" if not transformer_recovery_time else f"{transformer_recovery_time}天后可使用",
|
||||
"不存在" if not transformer_available else
|
||||
"可使用" if transformer_state else "冷却中",
|
||||
)
|
||||
A.paste(border, (10, 475))
|
||||
|
||||
expeditions_border = BuildImage(470,
|
||||
510,
|
||||
color="#E0D9D1",
|
||||
font="HYWenHei-85W.ttf",
|
||||
font_size=20)
|
||||
expeditions_text = BuildImage(466,
|
||||
506,
|
||||
color="#F5F1EB",
|
||||
font_size=23,
|
||||
font="HYWenHei-85W.ttf")
|
||||
expeditions_text.text(
|
||||
(5, 5), f"探索派遣限制{current_expedition_num}/{max_expedition_num}", (100, 100, 98)
|
||||
)
|
||||
(5, 5), f"探索派遣限制{current_expedition_num}/{max_expedition_num}",
|
||||
(100, 100, 98))
|
||||
h = 45
|
||||
for x in expeditions:
|
||||
_bk = BuildImage(400, 66, color="#ECE3D8", font="HYWenHei-85W.ttf", font_size=21)
|
||||
_bk = BuildImage(400,
|
||||
82,
|
||||
color="#ECE3D8",
|
||||
font="HYWenHei-85W.ttf",
|
||||
font_size=21)
|
||||
file_name = x["avatar_side_icon"].split("_")[-1]
|
||||
role_avatar = memo_path / "role_avatar" / file_name
|
||||
_ava_img = BuildImage(75, 75, background=role_avatar)
|
||||
@@ -227,7 +289,7 @@ def _parse_data_and_draw(
|
||||
_bk.circle_corner(20)
|
||||
|
||||
expeditions_text.paste(_bk, (25, h), True)
|
||||
h += 75
|
||||
h += 75 + 16
|
||||
|
||||
expeditions_border.paste(expeditions_text, center_type="center")
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -300,6 +300,8 @@ def get_country_data_image(world_data_dict: Dict) -> BuildImage:
|
||||
region = BuildImage(790, 267 * (len(world_data_dict) - 1), color="#F9F6F2")
|
||||
height = 0
|
||||
for country in ["蒙德", "龙脊雪山", "璃月", "层岩巨渊", "稻妻", "渊下宫"]:
|
||||
if not world_data_dict.get(country):
|
||||
continue
|
||||
x = BuildImage(790, 250, color="#3A4467")
|
||||
logo = BuildImage(180, 180, background=image_path / "logo" / f"{country}.png")
|
||||
tmp_bk = BuildImage(770, 230, color="#606779")
|
||||
@@ -328,18 +330,19 @@ def get_country_data_image(world_data_dict: Dict) -> BuildImage:
|
||||
f"{world_data_dict['层岩巨渊']['exploration_percentage'] / 10}%",
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
content_bk.text((300, 85), "地下矿区探索", fill=(239, 211, 114))
|
||||
content_bk.text(
|
||||
(570, 85),
|
||||
f"{world_data_dict['层岩巨渊·地下矿区']['exploration_percentage'] / 10}%",
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
content_bk.text((300, 150), "流明石触媒", fill=(239, 211, 114))
|
||||
content_bk.text(
|
||||
(570, 150),
|
||||
f"LV.{world_data_dict['层岩巨渊·地下矿区']['offerings'][0]['level']}",
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
if world_data_dict.get('层岩巨渊·地下矿区'):
|
||||
content_bk.text((300, 85), "地下矿区探索", fill=(239, 211, 114))
|
||||
content_bk.text(
|
||||
(570, 85),
|
||||
f"{world_data_dict['层岩巨渊·地下矿区']['exploration_percentage'] / 10}%",
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
content_bk.text((300, 150), "流明石触媒", fill=(239, 211, 114))
|
||||
content_bk.text(
|
||||
(570, 150),
|
||||
f"LV.{world_data_dict['层岩巨渊·地下矿区']['offerings'][0]['level']}",
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
elif country in ["龙脊雪山"]:
|
||||
content_bk.text((300, 40), "探索", fill=(239, 211, 114))
|
||||
content_bk.text(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from nonebot.adapters.onebot.v11 import ActionFailed
|
||||
|
||||
from utils.utils import get_bot, scheduler
|
||||
from utils.message_builder import at
|
||||
from models.group_member_info import GroupInfoUser
|
||||
@@ -84,30 +86,28 @@ async def _():
|
||||
if await Genshin.get_user_resin_recovery_time(u.uid) > datetime.now(
|
||||
pytz.timezone("Asia/Shanghai")
|
||||
):
|
||||
date = await Genshin.get_user_resin_recovery_time(u.uid)
|
||||
scheduler.add_job(
|
||||
_remind,
|
||||
"date",
|
||||
run_date=date.replace(microsecond=0),
|
||||
id=f"genshin_resin_remind_{u.uid}_{u.user_qq}",
|
||||
args=[u.user_qq, u.uid],
|
||||
)
|
||||
# date = await Genshin.get_user_resin_recovery_time(u.uid) # 不能要,因为可能在这期间用户使用了树脂
|
||||
add_job(u.user_qq, u.uid)
|
||||
# scheduler.add_job(
|
||||
# _remind,
|
||||
# "date",
|
||||
# run_date=date.replace(microsecond=0),
|
||||
# id=f"genshin_resin_remind_{u.uid}_{u.user_qq}",
|
||||
# args=[u.user_qq, u.uid],
|
||||
# )
|
||||
logger.info(
|
||||
f"genshin_resin_remind add_job:USER:{u.user_qq} UID:{u.uid} "
|
||||
f"{date} 原神树脂提醒"
|
||||
f"genshin_resin_remind add_job:USER:{u.user_qq} UID:{u.uid}启动原神树脂提醒 "
|
||||
)
|
||||
else:
|
||||
await Genshin.clear_resin_remind_time(u.uid)
|
||||
add_job(u.user_qq, u.uid)
|
||||
logger.info(
|
||||
f"genshin_resin_remind add_job CHECK:USER:{u.user_qq} UID:{u.uid} "
|
||||
f"{date} 原神树脂提醒"
|
||||
f"genshin_resin_remind add_job CHECK:USER:{u.user_qq} UID:{u.uid}启动原神树脂提醒 "
|
||||
)
|
||||
else:
|
||||
add_job(u.user_qq, u.uid)
|
||||
logger.info(
|
||||
f"genshin_resin_remind add_job CHECK:USER:{u.user_qq} UID:{u.uid} "
|
||||
f"{date} 原神树脂提醒"
|
||||
f"genshin_resin_remind add_job CHECK:USER:{u.user_qq} UID:{u.uid}启动原神树脂提醒 "
|
||||
)
|
||||
|
||||
|
||||
@@ -142,17 +142,19 @@ async def _remind(user_id: int, uid: str):
|
||||
now = datetime.now(pytz.timezone("Asia/Shanghai"))
|
||||
next_time = None
|
||||
if code == 200:
|
||||
current_resin = data["current_resin"] # 当前树脂
|
||||
max_resin = data["max_resin"] # 最大树脂
|
||||
current_resin = int(data["current_resin"]) # 当前树脂
|
||||
max_resin = int(data["max_resin"]) # 最大树脂
|
||||
msg = f"你的已经存了 {current_resin} 个树脂了!不要忘记刷掉!"
|
||||
# resin_recovery_time = data["resin_recovery_time"] # 树脂全部回复时间
|
||||
if current_resin < max_resin:
|
||||
user_manager.remove(uid)
|
||||
user_manager.remove_overflow(uid)
|
||||
if max_resin - 40 < current_resin <= max_resin - 20:
|
||||
next_time = now + timedelta(minutes=(max_resin - 20 - current_resin + 1) * 8, seconds=10)
|
||||
elif current_resin < max_resin:
|
||||
next_time = now + timedelta(minutes=(max_resin - current_resin) * 8, seconds=10)
|
||||
if current_resin < max_resin - 40:
|
||||
next_time = now + timedelta(minutes=(max_resin - 40 - current_resin) * 8)
|
||||
elif max_resin - 40 <= current_resin < max_resin - 20:
|
||||
next_time = now + timedelta(minutes=(max_resin - 20 - current_resin) * 8)
|
||||
elif max_resin - 20 <= current_resin < max_resin:
|
||||
next_time = now + timedelta(minutes=(max_resin - current_resin) * 8)
|
||||
elif current_resin == max_resin:
|
||||
custom_overflow_resin = Config.get_config("resin_remind", "CUSTOM_RESIN_OVERFLOW_REMIND")
|
||||
if user_manager.is_overflow(uid) and custom_overflow_resin:
|
||||
@@ -162,6 +164,7 @@ async def _remind(user_id: int, uid: str):
|
||||
msg = f"你的树脂都溢出 {custom_overflow_resin} 个了!浪费可耻!"
|
||||
else:
|
||||
next_time = now + timedelta(minutes=40 * 8 + random.randint(5, 50))
|
||||
|
||||
if not user_manager.exists(uid) and current_resin >= max_resin - 40:
|
||||
if current_resin == max_resin:
|
||||
user_manager.append(uid)
|
||||
@@ -178,10 +181,14 @@ async def _remind(user_id: int, uid: str):
|
||||
group_list = await GroupInfoUser.get_user_all_group(user_id)
|
||||
if group_list:
|
||||
group_id = group_list[0]
|
||||
await bot.send_group_msg(
|
||||
group_id=group_id,
|
||||
message=at(user_id) + msg
|
||||
)
|
||||
try:
|
||||
await bot.send_group_msg(
|
||||
group_id=group_id,
|
||||
message=at(user_id) + msg
|
||||
)
|
||||
except ActionFailed as e:
|
||||
logger.error(f"树脂提醒推送发生错误 {type(e)}:{e}")
|
||||
|
||||
if not next_time:
|
||||
if user_manager.check(uid) and Config.get_config("resin_remind", "AUTO_CLOSE_QUERY_FAIL_RESIN_REMIND"):
|
||||
await Genshin.set_resin_remind(int(uid), False)
|
||||
@@ -192,10 +199,13 @@ async def _remind(user_id: int, uid: str):
|
||||
user_manager.remove_error_count(uid)
|
||||
await Genshin.set_user_resin_recovery_time(int(uid), next_time)
|
||||
scheduler.add_job(
|
||||
_remind,
|
||||
_remind,
|
||||
"date",
|
||||
run_date=next_time,
|
||||
id=f"genshin_resin_remind_{uid}_{user_id}",
|
||||
args=[user_id, uid],
|
||||
)
|
||||
logger.info(
|
||||
f"genshin_resin_remind add_job:USER:{user_id} UID:{uid} "
|
||||
f"{next_time} 原神树脂提醒"
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from nonebot import on_command, on_notice
|
||||
from nonebot.adapters.onebot.v11 import (
|
||||
Bot,
|
||||
ActionFailed,
|
||||
GroupMessageEvent,
|
||||
PokeNotifyEvent,
|
||||
Message
|
||||
@@ -24,7 +25,6 @@ from nonebot.rule import to_me
|
||||
from datetime import datetime, timedelta
|
||||
from configs.config import NICKNAME
|
||||
from apscheduler.jobstores.base import JobLookupError
|
||||
from nonebot.adapters.onebot.v11.exception import ActionFailed
|
||||
from nonebot.params import CommandArg
|
||||
import random
|
||||
import time
|
||||
@@ -124,7 +124,7 @@ async def _(bot: Bot, event: GroupMessageEvent, arg: Message = CommandArg()):
|
||||
redbag_data[event.group_id]["amount"]
|
||||
- redbag_data[event.group_id]["open_amount"]
|
||||
)
|
||||
await return_gold(event.user_id, event.group_id, amount)
|
||||
await return_gold(redbag_data[event.group_id]["user_id"], event.group_id, amount)
|
||||
await gold_redbag.send(
|
||||
f'{redbag_data[event.group_id]["nickname"]}的红包过时未开完,退还{amount}金币...'
|
||||
)
|
||||
|
||||
@@ -61,22 +61,23 @@ async def generate_open_redbag_pic(user_id: int, send_user_nickname: str, amount
|
||||
async def _generate_open_redbag_pic(user_id: int, send_user_nickname: str, amount: int, text: str):
|
||||
send_user_nickname += '的红包'
|
||||
amount = str(amount)
|
||||
head = BuildImage(1000, 980, font_size=30, background=f'{IMAGE_PATH}/prts/redbag_12.png')
|
||||
random_redbag = random.choice(os.listdir(f"{IMAGE_PATH}/prts/redbag_1"))
|
||||
head = BuildImage(1000, 980, font_size=30, background=f'{IMAGE_PATH}/prts/redbag_1/{random_redbag}')
|
||||
size = BuildImage(0, 0, font_size=50).getsize(send_user_nickname)
|
||||
# QQ头像
|
||||
ava_bk = BuildImage(100 + size[0], 66, color='white', font_size=50)
|
||||
ava = BuildImage(66, 66, background=BytesIO(await get_user_avatar(user_id)))
|
||||
ava_bk = BuildImage(100 + size[0], 66, is_alpha=True, font_size=50)
|
||||
ava = BuildImage(66, 66, is_alpha=True, background=BytesIO(await get_user_avatar(user_id)))
|
||||
ava_bk.paste(ava)
|
||||
ava_bk.text((100, 7), send_user_nickname)
|
||||
# ava_bk.show()
|
||||
ava_bk_w, ava_bk_h = ava_bk.size
|
||||
head.paste(ava_bk, (int((1000 - ava_bk_w) / 2), 300))
|
||||
head.paste(ava_bk, (int((1000 - ava_bk_w) / 2), 300), alpha=True)
|
||||
# 金额
|
||||
size = BuildImage(0, 0, font_size=150).getsize(amount)
|
||||
price = BuildImage(size[0], size[1], font_size=150)
|
||||
price = BuildImage(size[0], size[1], is_alpha=True, font_size=150)
|
||||
price.text((0, 0), amount, fill=(209, 171, 108))
|
||||
# 金币中文
|
||||
head.paste(price, (int((1000 - size[0]) / 2) - 50, 460))
|
||||
head.paste(price, (int((1000 - size[0]) / 2) - 50, 460), alpha=True)
|
||||
head.text((int((1000 - size[0]) / 2 + size[0]) - 50, 500 + size[1] - 70), '金币', fill=(209, 171, 108))
|
||||
# 剩余数量和金额
|
||||
head.text((350, 900), text, (198, 198, 198))
|
||||
|
||||
@@ -18,9 +18,9 @@ class RedbagUser(db.Model):
|
||||
@classmethod
|
||||
async def add_redbag_data(cls, user_qq: int, group_id: int, itype: str, money: int):
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
添加收发红包数据
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
:param itype: 收或发
|
||||
@@ -45,9 +45,9 @@ class RedbagUser(db.Model):
|
||||
@classmethod
|
||||
async def ensure(cls, user_qq: int, group_id: int) -> bool:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取用户对象
|
||||
参数:
|
||||
参数:
|
||||
:param user_qq: qq号
|
||||
:param group_id: 群号
|
||||
"""
|
||||
@@ -61,9 +61,9 @@ class RedbagUser(db.Model):
|
||||
@classmethod
|
||||
async def get_user_all(cls, group_id: int = None) -> List["RedbagUser"]:
|
||||
"""
|
||||
说明:
|
||||
说明:
|
||||
获取所有用户对象
|
||||
参数:
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
"""
|
||||
if not group_id:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user