mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2025-12-14 21:52:56 +08:00
feat✨: 更新内置插件
This commit is contained in:
parent
131200a28e
commit
eb0572ea77
11
.env.dev
11
.env.dev
@ -10,11 +10,18 @@ NICKNAME=["真寻", "小真寻", "绪山真寻", "小寻子"]
|
||||
|
||||
SESSION_EXPIRE_TIMEOUT=30
|
||||
|
||||
PLATFORM_SUPERUSERS = '
|
||||
{
|
||||
"qq": [""],
|
||||
"dodo": [""]
|
||||
}
|
||||
'
|
||||
|
||||
# DRIVER=~fastapi
|
||||
DRIVER=~fastapi+~httpx+~websockets
|
||||
|
||||
# kook adapter toekn
|
||||
kaiheila_bots =[{""}]
|
||||
kaiheila_bots =[{"token": ""}]
|
||||
|
||||
# discode adapter
|
||||
DISCORD_BOTS='
|
||||
@ -41,6 +48,8 @@ DODO_BOTS='
|
||||
]
|
||||
'
|
||||
|
||||
# application_commands的{"*": ["*"]}代表将全部应用命令注册为全局应用命令
|
||||
# {"admin": ["123", "456"]}则代表将admin命令注册为id是123、456服务器的局部命令,其余命令不注册
|
||||
|
||||
LOG_LEVEL=DEBUG
|
||||
# 服务器和端口
|
||||
|
||||
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@ -7,9 +7,15 @@
|
||||
"Alconna",
|
||||
"arclet",
|
||||
"Arparma",
|
||||
"displayname",
|
||||
"getbbox",
|
||||
"httpx",
|
||||
"kaiheila",
|
||||
"nonebot",
|
||||
"onebot",
|
||||
"tobytes",
|
||||
"userinfo",
|
||||
"zhenxun"
|
||||
]
|
||||
}
|
||||
],
|
||||
"python.analysis.autoImportCompletions": true
|
||||
}
|
||||
|
||||
486
poetry.lock
generated
486
poetry.lock
generated
@ -1,5 +1,21 @@
|
||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiofiles"
|
||||
version = "23.2.1"
|
||||
description = "File support for asyncio."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107"},
|
||||
{file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"},
|
||||
]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "aiosqlite"
|
||||
version = "0.17.0"
|
||||
@ -267,6 +283,22 @@ type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "5.3.2"
|
||||
description = "Extensible memoizing collections and decorators"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"},
|
||||
{file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"},
|
||||
]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "cashews"
|
||||
version = "6.4.0"
|
||||
@ -534,6 +566,25 @@ type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "emoji"
|
||||
version = "2.10.1"
|
||||
description = "Emoji for Python"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
files = [
|
||||
{file = "emoji-2.10.1-py2.py3-none-any.whl", hash = "sha256:11fb369ea79d20c14efa4362c732d67126df294a7959a2c98bfd7447c12a218e"},
|
||||
{file = "emoji-2.10.1.tar.gz", hash = "sha256:16287283518fb7141bde00198f9ffff4e1c1cb570efb68b2f1ec50975c3a581d"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["coverage", "coveralls", "pytest"]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.0"
|
||||
@ -553,6 +604,30 @@ type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.109.2"
|
||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "fastapi-0.109.2-py3-none-any.whl", hash = "sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d"},
|
||||
{file = "fastapi-0.109.2.tar.gz", hash = "sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
|
||||
starlette = ">=0.36.3,<0.37.0"
|
||||
typing-extensions = ">=4.8.0"
|
||||
|
||||
[package.extras]
|
||||
all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.13.1"
|
||||
@ -574,21 +649,6 @@ type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "fleep"
|
||||
version = "1.0.1"
|
||||
description = "File format determination library"
|
||||
optional = false
|
||||
python-versions = ">=3.1"
|
||||
files = [
|
||||
{file = "fleep-1.0.1.tar.gz", hash = "sha256:c8f62b258ee5364d7f6c1ed1f3f278e99020fc3f0a60a24ad1e10846e31d104c"},
|
||||
]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.0.3"
|
||||
@ -707,6 +767,59 @@ type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "httptools"
|
||||
version = "0.6.1"
|
||||
description = "A collection of framework independent HTTP protocol utils."
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"},
|
||||
{file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"},
|
||||
{file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"},
|
||||
{file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"},
|
||||
{file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"},
|
||||
{file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"},
|
||||
{file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"},
|
||||
{file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"},
|
||||
{file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"},
|
||||
{file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"},
|
||||
{file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"},
|
||||
{file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"},
|
||||
{file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"},
|
||||
{file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"},
|
||||
{file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"},
|
||||
{file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"},
|
||||
{file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"},
|
||||
{file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"},
|
||||
{file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"},
|
||||
{file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"},
|
||||
{file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"},
|
||||
{file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"},
|
||||
{file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"},
|
||||
{file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"},
|
||||
{file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"},
|
||||
{file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"},
|
||||
{file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"},
|
||||
{file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"},
|
||||
{file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"},
|
||||
{file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"},
|
||||
{file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"},
|
||||
{file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"},
|
||||
{file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"},
|
||||
{file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"},
|
||||
{file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"},
|
||||
{file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
test = ["Cython (>=0.29.24,<0.30.0)"]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.26.0"
|
||||
@ -813,6 +926,26 @@ type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "3.5.2"
|
||||
description = "Python implementation of John Gruber's Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "Markdown-3.5.2-py3-none-any.whl", hash = "sha256:d43323865d89fc0cb9b20c75fc8ad313af307cc087e84b657d9eec768eddeadd"},
|
||||
{file = "Markdown-3.5.2.tar.gz", hash = "sha256:e1ac7b3dc550ee80e602e71c1d168002f062e49f1b11e26a36264dafd4df2ef8"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
|
||||
testing = ["coverage", "pyyaml"]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "3.0.0"
|
||||
@ -1223,19 +1356,18 @@ reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-plugin-alconna"
|
||||
version = "0.36.0"
|
||||
version = "0.36.3"
|
||||
description = "Alconna Adapter for Nonebot"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "nonebot_plugin_alconna-0.36.0-py3-none-any.whl", hash = "sha256:37f8afc272924802fe75146df5f68b44e8e5537420cbb983d2d9d65195e625e7"},
|
||||
{file = "nonebot_plugin_alconna-0.36.0.tar.gz", hash = "sha256:e524fac76ee0f1a08817007e649c2b491b44094e0262a3d36fcef3e1259edfa2"},
|
||||
{file = "nonebot_plugin_alconna-0.36.3-py3-none-any.whl", hash = "sha256:8f26f96c711d3adadc538ebf40d51ba2249c18fe1689bf36baed0e4d1e05246a"},
|
||||
{file = "nonebot_plugin_alconna-0.36.3.tar.gz", hash = "sha256:ed8e4f2fd845d0c3d8becdd68678c203ee76109b9104a3b1c18f63525e85c6d4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
arclet-alconna = ">=1.7.38,<2.0.0"
|
||||
arclet-alconna-tools = ">=0.6.7,<0.7.0"
|
||||
fleep = ">=1.0.1"
|
||||
arclet-alconna = ">=1.7.42,<2.0.0"
|
||||
arclet-alconna-tools = ">=0.6.11,<0.7.0"
|
||||
nepattern = ">=0.5.14,<0.6.0"
|
||||
nonebot2 = ">=2.1.0"
|
||||
|
||||
@ -1264,6 +1396,32 @@ type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-plugin-htmlrender"
|
||||
version = "0.3.0"
|
||||
description = "通过浏览器渲染图片"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8"
|
||||
files = [
|
||||
{file = "nonebot_plugin_htmlrender-0.3.0-py3-none-any.whl", hash = "sha256:c05588bad4738421a49a47a7db974359adeb624c1ed6af49d6237023fa014bcf"},
|
||||
{file = "nonebot_plugin_htmlrender-0.3.0.tar.gz", hash = "sha256:34b4ff5b898ea47480d3488a2a0b01c46e0ca3d938ab4b891d1db91a70d83d2d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
aiofiles = ">=0.8.0"
|
||||
jinja2 = ">=3.0.3"
|
||||
markdown = ">=3.3.6"
|
||||
nonebot2 = {version = ">=2.2.0", extras = ["fastapi"]}
|
||||
playwright = ">=1.17.2"
|
||||
Pygments = ">=2.10.0"
|
||||
pymdown-extensions = ">=9.1"
|
||||
python-markdown-math = ">=0.8"
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-plugin-send-anything-anywhere"
|
||||
version = "0.5.0"
|
||||
@ -1306,23 +1464,49 @@ type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-plugin-userinfo"
|
||||
version = "0.1.3"
|
||||
description = "Nonebot2 用户信息获取插件"
|
||||
optional = false
|
||||
python-versions = ">=3.8,<4.0"
|
||||
files = [
|
||||
{file = "nonebot_plugin_userinfo-0.1.3-py3-none-any.whl", hash = "sha256:e20b22c81e86e81f7953560bd8ce0a54559a87ad615358c613b78cb5a4918191"},
|
||||
{file = "nonebot_plugin_userinfo-0.1.3.tar.gz", hash = "sha256:d0a4d64c612486df63cd16950446072f8dfd2063ea28f15d56305a585a6b0b6e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cachetools = ">=5.0.0,<6.0.0"
|
||||
emoji = ">=2.0.0,<3.0.0"
|
||||
httpx = ">=0.20.0,<1.0.0"
|
||||
nonebot2 = {version = ">=2.0.0,<3.0.0", extras = ["fastapi"]}
|
||||
strenum = ">=0.4.8,<0.5.0"
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot2"
|
||||
version = "2.1.3"
|
||||
version = "2.2.0"
|
||||
description = "An asynchronous python bot framework."
|
||||
optional = false
|
||||
python-versions = ">=3.8,<4.0"
|
||||
files = [
|
||||
{file = "nonebot2-2.1.3-py3-none-any.whl", hash = "sha256:c36c1a60ce4355d9777fee431c08619f22ffd60f7060993fbbbd1fe67b6368f7"},
|
||||
{file = "nonebot2-2.1.3.tar.gz", hash = "sha256:e750e615f1ad2503721ce055fbe55ec3b061277135d995be112fecd27f7232e5"},
|
||||
{file = "nonebot2-2.2.0-py3-none-any.whl", hash = "sha256:447fa63d384414c0e610f4ce6d2b3999db81ac2becd8d86716c4117013dc032f"},
|
||||
{file = "nonebot2-2.2.0.tar.gz", hash = "sha256:138800846fa3dc635bda9f2ddc589519ee8d9d3b401013fbb95e47676fc830fb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
fastapi = {version = ">=0.93.0,<1.0.0", optional = true, markers = "extra == \"fastapi\" or extra == \"all\""}
|
||||
loguru = ">=0.6.0,<1.0.0"
|
||||
pydantic = {version = ">=1.10.0,<2.0.0", extras = ["dotenv"]}
|
||||
pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<3.0.0"
|
||||
pygtrie = ">=2.4.1,<3.0.0"
|
||||
python-dotenv = ">=0.21.0,<2.0.0"
|
||||
tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = ">=4.4.0,<5.0.0"
|
||||
uvicorn = {version = ">=0.20.0,<1.0.0", extras = ["standard"], optional = true, markers = "extra == \"quart\" or extra == \"fastapi\" or extra == \"all\""}
|
||||
yarl = ">=1.7.2,<2.0.0"
|
||||
|
||||
[package.extras]
|
||||
@ -1551,7 +1735,6 @@ files = [
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
python-dotenv = {version = ">=0.10.4", optional = true, markers = "extra == \"dotenv\""}
|
||||
typing-extensions = ">=4.2.0"
|
||||
|
||||
[package.extras]
|
||||
@ -1637,6 +1820,29 @@ type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "pymdown-extensions"
|
||||
version = "10.7"
|
||||
description = "Extension pack for Python Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pymdown_extensions-10.7-py3-none-any.whl", hash = "sha256:6ca215bc57bc12bf32b414887a68b810637d039124ed9b2e5bd3325cbb2c050c"},
|
||||
{file = "pymdown_extensions-10.7.tar.gz", hash = "sha256:c0d64d5cf62566f59e6b2b690a4095c931107c250a8c8e1351c1de5f6b036deb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
markdown = ">=3.5"
|
||||
pyyaml = "*"
|
||||
|
||||
[package.extras]
|
||||
extra = ["pygments (>=2.12)"]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "pypika-tortoise"
|
||||
version = "0.1.6"
|
||||
@ -1691,6 +1897,25 @@ type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "python-markdown-math"
|
||||
version = "0.8"
|
||||
description = "Math extension for Python-Markdown"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "python-markdown-math-0.8.tar.gz", hash = "sha256:8564212af679fc18d53f38681f16080fcd3d186073f23825c7ce86fadd3e3635"},
|
||||
{file = "python_markdown_math-0.8-py3-none-any.whl", hash = "sha256:c685249d84b5b697e9114d7beb352bd8ca2e07fd268fd4057ffca888c14641e5"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
Markdown = ">=3.0"
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "python-slugify"
|
||||
version = "8.0.2"
|
||||
@ -1820,6 +2045,25 @@ type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "retrying"
|
||||
version = "1.3.4"
|
||||
description = "Retrying"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "retrying-1.3.4-py3-none-any.whl", hash = "sha256:8cc4d43cb8e1125e0ff3344e9de678fefd85db3b750b81b2240dc0183af37b35"},
|
||||
{file = "retrying-1.3.4.tar.gz", hash = "sha256:345da8c5765bd982b1d1915deb9102fd3d1f7ad16bd84a9700b85f64d24e8f3e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.7.0"
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "13.7.0"
|
||||
@ -1962,6 +2206,28 @@ type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.36.3"
|
||||
description = "The little ASGI library that shines."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "starlette-0.36.3-py3-none-any.whl", hash = "sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044"},
|
||||
{file = "starlette-0.36.3.tar.gz", hash = "sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
anyio = ">=3.4.0,<5"
|
||||
|
||||
[package.extras]
|
||||
full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "strenum"
|
||||
version = "0.4.15"
|
||||
@ -2287,6 +2553,86 @@ type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.27.1"
|
||||
description = "The lightning-fast ASGI server."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "uvicorn-0.27.1-py3-none-any.whl", hash = "sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4"},
|
||||
{file = "uvicorn-0.27.1.tar.gz", hash = "sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.0"
|
||||
colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""}
|
||||
h11 = ">=0.8"
|
||||
httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""}
|
||||
python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
|
||||
pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
|
||||
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
|
||||
uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
|
||||
watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
|
||||
websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""}
|
||||
|
||||
[package.extras]
|
||||
standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "uvloop"
|
||||
version = "0.19.0"
|
||||
description = "Fast implementation of asyncio event loop on top of libuv"
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"},
|
||||
{file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"},
|
||||
{file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"},
|
||||
{file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"},
|
||||
{file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"},
|
||||
{file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"},
|
||||
{file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"},
|
||||
{file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"},
|
||||
{file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"},
|
||||
{file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"},
|
||||
{file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"},
|
||||
{file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"},
|
||||
{file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"},
|
||||
{file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"},
|
||||
{file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"},
|
||||
{file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"},
|
||||
{file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"},
|
||||
{file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"},
|
||||
{file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"},
|
||||
{file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"},
|
||||
{file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"},
|
||||
{file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"},
|
||||
{file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"},
|
||||
{file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"},
|
||||
{file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"},
|
||||
{file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"},
|
||||
{file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"},
|
||||
{file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"},
|
||||
{file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"},
|
||||
{file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"},
|
||||
{file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
|
||||
test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.25.0"
|
||||
@ -2420,6 +2766,92 @@ type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "12.0"
|
||||
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"},
|
||||
{file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"},
|
||||
{file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"},
|
||||
{file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"},
|
||||
{file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"},
|
||||
{file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"},
|
||||
{file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"},
|
||||
{file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"},
|
||||
{file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"},
|
||||
{file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"},
|
||||
{file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"},
|
||||
{file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"},
|
||||
{file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"},
|
||||
{file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"},
|
||||
{file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"},
|
||||
{file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"},
|
||||
{file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"},
|
||||
{file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"},
|
||||
{file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"},
|
||||
{file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"},
|
||||
{file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"},
|
||||
{file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"},
|
||||
{file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"},
|
||||
{file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"},
|
||||
{file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"},
|
||||
{file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"},
|
||||
{file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"},
|
||||
{file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"},
|
||||
{file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"},
|
||||
{file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"},
|
||||
{file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"},
|
||||
{file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"},
|
||||
{file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"},
|
||||
{file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"},
|
||||
{file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"},
|
||||
{file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"},
|
||||
{file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"},
|
||||
{file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"},
|
||||
{file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"},
|
||||
{file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"},
|
||||
{file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"},
|
||||
{file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"},
|
||||
{file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"},
|
||||
{file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"},
|
||||
{file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"},
|
||||
{file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"},
|
||||
{file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"},
|
||||
{file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"},
|
||||
{file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"},
|
||||
{file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"},
|
||||
{file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"},
|
||||
{file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"},
|
||||
{file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"},
|
||||
{file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"},
|
||||
{file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"},
|
||||
{file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"},
|
||||
{file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"},
|
||||
{file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"},
|
||||
{file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"},
|
||||
{file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"},
|
||||
{file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"},
|
||||
{file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"},
|
||||
{file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"},
|
||||
{file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"},
|
||||
{file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"},
|
||||
{file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"},
|
||||
{file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"},
|
||||
{file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"},
|
||||
{file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"},
|
||||
{file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"},
|
||||
{file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"},
|
||||
{file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"},
|
||||
]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "ali"
|
||||
|
||||
[[package]]
|
||||
name = "win32-setctime"
|
||||
version = "1.1.0"
|
||||
@ -2550,4 +2982,4 @@ reference = "ali"
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "bc2932cc9955e05badaaf34f0bda8031edd80d3a832ccd05f9c079fadc4c5cdf"
|
||||
content-hash = "2e5c4963196533949601dff69762b6f5586056a8775419c2ee1aef0df91b016a"
|
||||
|
||||
@ -29,6 +29,10 @@ nonebot2 = "^2.1.3"
|
||||
nonebot-adapter-discord = "^0.1.3"
|
||||
nonebot-adapter-dodo = "^0.1.4"
|
||||
pillow = "9.5"
|
||||
retrying = "^1.3.4"
|
||||
aiofiles = "^23.2.1"
|
||||
nonebot-plugin-htmlrender = "^0.3.0"
|
||||
nonebot-plugin-userinfo = "^0.1.3"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
@ -1,3 +1,5 @@
|
||||
import os
|
||||
|
||||
from nonebot import require
|
||||
|
||||
require("nonebot_plugin_apscheduler")
|
||||
@ -8,3 +10,10 @@ require("nonebot_plugin_saa")
|
||||
from nonebot_plugin_saa import enable_auto_select_bot
|
||||
|
||||
enable_auto_select_bot()
|
||||
from pathlib import Path
|
||||
|
||||
import nonebot
|
||||
|
||||
path = Path(__file__).parent / "platform"
|
||||
for d in os.listdir(path):
|
||||
nonebot.load_plugins(str((path / d).resolve()))
|
||||
|
||||
165
zhenxun/builtin_plugins/admin/admin_help.py
Normal file
165
zhenxun/builtin_plugins/admin/admin_help.py
Normal file
@ -0,0 +1,165 @@
|
||||
import nonebot
|
||||
from arclet.alconna import Args, Option
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
|
||||
from nonebot_plugin_alconna.matcher import AlconnaMatcher
|
||||
from nonebot_plugin_saa import Image, Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
|
||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.exception import EmptyError
|
||||
from zhenxun.utils.image_utils import (
|
||||
BuildImage,
|
||||
build_sort_image,
|
||||
group_image,
|
||||
text2image,
|
||||
)
|
||||
from zhenxun.utils.rules import admin_check, ensure_group
|
||||
|
||||
base_config = Config.get("admin_bot_manage")
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="群组管理员帮助",
|
||||
description="管理员帮助列表",
|
||||
usage="""
|
||||
管理员帮助
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.ADMIN,
|
||||
admin_level=1,
|
||||
).dict(),
|
||||
)
|
||||
|
||||
_matcher = on_alconna(
|
||||
Alconna("管理员帮助"),
|
||||
rule=admin_check(1) & ensure_group,
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
|
||||
ADMIN_HELP_IMAGE = IMAGE_PATH / "ADMIN_HELP.png"
|
||||
if ADMIN_HELP_IMAGE.exists():
|
||||
ADMIN_HELP_IMAGE.unlink()
|
||||
|
||||
|
||||
async def build_help() -> BuildImage:
|
||||
"""构造管理员帮助图片
|
||||
|
||||
异常:
|
||||
EmptyError: 管理员帮助为空
|
||||
|
||||
返回:
|
||||
BuildImage: 管理员帮助图片
|
||||
"""
|
||||
plugin_list = await PluginInfo.filter(
|
||||
plugin_type__in=[PluginType.ADMIN, PluginType.SUPER_AND_ADMIN]
|
||||
).all()
|
||||
data_list = []
|
||||
for plugin in plugin_list:
|
||||
if _plugin := nonebot.get_plugin_by_module_name(plugin.module_path):
|
||||
if _plugin.metadata:
|
||||
data_list.append({"plugin": plugin, "metadata": _plugin.metadata})
|
||||
font = BuildImage.load_font("HYWenHei-85W.ttf", 20)
|
||||
image_list = []
|
||||
for data in data_list:
|
||||
plugin = data["plugin"]
|
||||
metadata = data["metadata"]
|
||||
try:
|
||||
usage = None
|
||||
description = None
|
||||
if metadata.usage:
|
||||
usage = await text2image(
|
||||
metadata.usage,
|
||||
padding=5,
|
||||
color=(255, 255, 255),
|
||||
font_color=(0, 0, 0),
|
||||
)
|
||||
if metadata.description:
|
||||
description = await text2image(
|
||||
metadata.description,
|
||||
padding=5,
|
||||
color=(255, 255, 255),
|
||||
font_color=(0, 0, 0),
|
||||
)
|
||||
width = 0
|
||||
height = 100
|
||||
if usage:
|
||||
width = usage.width
|
||||
height += usage.height
|
||||
if description and description.width > width:
|
||||
width = description.width
|
||||
height += description.height
|
||||
font_width, font_height = BuildImage.get_text_size(
|
||||
plugin.name + f"[{plugin.level}]", font
|
||||
)
|
||||
if font_width > width:
|
||||
width = font_width
|
||||
A = BuildImage(width + 30, height + 120, "#EAEDF2")
|
||||
await A.text((15, 10), plugin.name + f"[{plugin.level}]")
|
||||
await A.text((15, 70), "简介:")
|
||||
if not description:
|
||||
description = BuildImage(A.width - 30, 30, (255, 255, 255))
|
||||
await description.circle_corner(10)
|
||||
await A.paste(description, (15, 100))
|
||||
if not usage:
|
||||
usage = BuildImage(A.width - 30, 30, (255, 255, 255))
|
||||
await usage.circle_corner(10)
|
||||
await A.text((15, description.height + 115), "用法:")
|
||||
await A.paste(usage, (15, description.height + 145))
|
||||
await A.circle_corner(10)
|
||||
image_list.append(A)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"获取群管理员插件 {plugin.module}: {plugin.name} 设置失败...",
|
||||
"管理员帮助",
|
||||
e=e,
|
||||
)
|
||||
if task_list := await TaskInfo.all():
|
||||
task_str = "\n".join([task.name for task in task_list])
|
||||
task_str = "通过 开启/关闭 来控制群被动\n----------\n" + task_str
|
||||
task_image = await text2image(task_str, padding=5, color=(255, 255, 255))
|
||||
await task_image.circle_corner(10)
|
||||
A = BuildImage(task_image.width + 50, task_image.height + 85, "#EAEDF2")
|
||||
await A.text((25, 10), "被动技能")
|
||||
await A.paste(task_image, (25, 50))
|
||||
await A.circle_corner(10)
|
||||
image_list.append(A)
|
||||
if not image_list:
|
||||
raise EmptyError()
|
||||
image_group, _ = group_image(image_list)
|
||||
A = await build_sort_image(image_group, color=(255, 255, 255), padding_top=160)
|
||||
text = await BuildImage.build_text_image(
|
||||
"群管理员帮助",
|
||||
size=40,
|
||||
)
|
||||
tip = await BuildImage.build_text_image(
|
||||
"注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数", size=25, font_color="red"
|
||||
)
|
||||
await A.paste(text, (50, 30))
|
||||
await A.paste(tip, (50, 90))
|
||||
await A.save(ADMIN_HELP_IMAGE)
|
||||
return BuildImage(1, 1)
|
||||
|
||||
|
||||
@_matcher.handle()
|
||||
async def _(
|
||||
session: EventSession,
|
||||
matcher: AlconnaMatcher,
|
||||
arparma: Arparma,
|
||||
):
|
||||
if not ADMIN_HELP_IMAGE.exists():
|
||||
try:
|
||||
await build_help()
|
||||
except EmptyError:
|
||||
await Text("管理员帮助为空").finish(reply=True)
|
||||
await Image(ADMIN_HELP_IMAGE).send()
|
||||
logger.info("查看管理员帮助", arparma.header_result, session=session)
|
||||
@ -9,11 +9,6 @@ from zhenxun.models.level_user import LevelUser
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
|
||||
__zx_plugin_name__ = "群管理员变动监测 [Hidden]"
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="群管理员变动监测",
|
||||
description="检测群管理员变动, 添加与删除管理员默认权限, 当配置项 ADMIN_DEFAULT_AUTH 为空时, 不会添加管理员权限",
|
||||
@ -40,20 +35,25 @@ async def _(event: GroupAdminNoticeEvent):
|
||||
admin_default_auth = base_config.get("ADMIN_DEFAULT_AUTH")
|
||||
if admin_default_auth is not None:
|
||||
await LevelUser.set_level(
|
||||
event.user_id,
|
||||
event.group_id,
|
||||
str(event.user_id),
|
||||
str(event.group_id),
|
||||
admin_default_auth,
|
||||
)
|
||||
logger.info(
|
||||
f"成为管理员,添加权限: {admin_default_auth}",
|
||||
"群管理员变动监测",
|
||||
event.user_id,
|
||||
event.group_id,
|
||||
session=event.user_id,
|
||||
group_id=event.group_id,
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f"配置项 MODULE: [<u><y>admin_bot_manage</y></u>] | KEY: [<u><y>ADMIN_DEFAULT_AUTH</y></u>] 为空"
|
||||
)
|
||||
elif event.sub_type == "unset":
|
||||
await LevelUser.delete_level(event.user_id, event.group_id)
|
||||
logger.info("撤销群管理员, 取消权限等级", "群管理员变动监测", event.user_id, event.group_id)
|
||||
await LevelUser.delete_level(str(event.user_id), str(event.group_id))
|
||||
logger.info(
|
||||
"撤销群管理员, 取消权限等级",
|
||||
"群管理员变动监测",
|
||||
session=event.user_id,
|
||||
group_id=event.group_id,
|
||||
)
|
||||
|
||||
196
zhenxun/builtin_plugins/admin/ban/__init__.py
Normal file
196
zhenxun/builtin_plugins/admin/ban/__init__.py
Normal file
@ -0,0 +1,196 @@
|
||||
from arclet.alconna import Args
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import (
|
||||
Alconna,
|
||||
Arparma,
|
||||
At,
|
||||
Match,
|
||||
Option,
|
||||
Subcommand,
|
||||
on_alconna,
|
||||
store_true,
|
||||
)
|
||||
from nonebot_plugin_saa import Image, Mention, MessageFactory, Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.rules import admin_check
|
||||
|
||||
from ._data_source import BanManage
|
||||
|
||||
base_config = Config.get("ban")
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="封禁用户/群组",
|
||||
description="你被逮捕了!丢进小黑屋!封禁用户以及群组,屏蔽消息",
|
||||
usage="""
|
||||
.ban [at] ?[小时] ?[分钟]
|
||||
.unban
|
||||
示例:.ban @user
|
||||
示例:.ban @user 6
|
||||
示例:.ban @user 3 10
|
||||
示例:.unban @user
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.SUPER_AND_ADMIN,
|
||||
admin_level=base_config.get("BAN_LEVEL", 5),
|
||||
configs=[
|
||||
RegisterConfig(
|
||||
key="BAN_LEVEL",
|
||||
value=5,
|
||||
help="ban/unban所需要的管理员权限等级",
|
||||
default_value=5,
|
||||
type=int,
|
||||
)
|
||||
],
|
||||
).dict(),
|
||||
)
|
||||
|
||||
|
||||
_matcher = on_alconna(
|
||||
Alconna(
|
||||
"ban-console",
|
||||
Subcommand(
|
||||
"ban",
|
||||
Args["user?", [str, At]]["duration?", int],
|
||||
Option("-g|--group", Args["group_id", str]),
|
||||
),
|
||||
Subcommand(
|
||||
"unban",
|
||||
Args["user?", [str, At]],
|
||||
Option("-g|--group", Args["group_id", str]),
|
||||
),
|
||||
),
|
||||
rule=admin_check("ban", "BAN_LEVEL"),
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
_status_matcher = on_alconna(
|
||||
Alconna(
|
||||
"ban-status",
|
||||
Option("-u|--user", Args["user_id", str]),
|
||||
Option("-g|--group", Args["group_id", str]),
|
||||
),
|
||||
permission=SUPERUSER,
|
||||
priority=1,
|
||||
block=True,
|
||||
)
|
||||
# TODO: shortcut
|
||||
|
||||
|
||||
@_status_matcher.handle()
|
||||
async def _(
|
||||
bot: Bot,
|
||||
session: EventSession,
|
||||
arparma: Arparma,
|
||||
user_id: Match[str],
|
||||
group_id: Match[str],
|
||||
):
|
||||
_user_id = user_id.result if user_id.available else None
|
||||
_group_id = group_id.result if group_id.available else None
|
||||
if image := await BanManage.build_ban_image(_user_id, _group_id):
|
||||
await Image(image.pic2bs4()).finish(reply=True)
|
||||
else:
|
||||
await Text("数据为空捏...").finish(reply=True)
|
||||
|
||||
|
||||
@_matcher.assign("ban")
|
||||
async def _(
|
||||
bot: Bot,
|
||||
session: EventSession,
|
||||
arparma: Arparma,
|
||||
user: Match[str | At],
|
||||
duration: Match[int],
|
||||
group_id: Match[str],
|
||||
):
|
||||
user_id = None
|
||||
if user.available:
|
||||
if isinstance(user.result, At):
|
||||
user_id = user.result.target
|
||||
else:
|
||||
user_id = user.result
|
||||
_duration = duration.result * 60 if duration.available else -1
|
||||
if gid := session.id3 or session.id2:
|
||||
if group_id.available:
|
||||
gid = group_id.result
|
||||
await BanManage.ban(
|
||||
user_id, gid, _duration, session, session.id1 in bot.config.superusers
|
||||
)
|
||||
logger.info(
|
||||
f"管理员Ban",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
target=f"{gid}:{user_id}",
|
||||
)
|
||||
await MessageFactory(
|
||||
[
|
||||
Text("对 "),
|
||||
Mention(user_id), # type: ignore
|
||||
Text(f" 狠狠惩戒了一番,一脚踢进了小黑屋!"),
|
||||
]
|
||||
).finish(reply=True)
|
||||
elif session.id1 in bot.config.superusers:
|
||||
_group_id = group_id.result if group_id.available else None
|
||||
await BanManage.ban(user_id, _group_id, _duration, session, True)
|
||||
logger.info(
|
||||
f"超级用户Ban",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
target=f"{_group_id}:{user_id}",
|
||||
)
|
||||
at_msg = user_id if user_id else f"群组:{_group_id}"
|
||||
await Text(f"对 {at_msg} 狠狠惩戒了一番,一脚踢进了小黑屋!").finish(reply=True)
|
||||
|
||||
|
||||
@_matcher.assign("unban")
|
||||
async def _(
|
||||
bot: Bot,
|
||||
session: EventSession,
|
||||
arparma: Arparma,
|
||||
user: Match[str | At],
|
||||
group_id: Match[str],
|
||||
):
|
||||
user_id = None
|
||||
if user.available:
|
||||
if isinstance(user.result, At):
|
||||
user_id = user.result.target
|
||||
else:
|
||||
user_id = user.result
|
||||
if gid := session.id3 or session.id2:
|
||||
if group_id.available:
|
||||
gid = group_id.result
|
||||
await BanManage.unban(
|
||||
user_id, gid, session, session.id1 in bot.config.superusers
|
||||
)
|
||||
logger.info(
|
||||
f"管理员UnBan",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
target=f"{gid}:{user_id}",
|
||||
)
|
||||
await MessageFactory(
|
||||
[
|
||||
Text("将 "),
|
||||
Mention(user_id), # type: ignore
|
||||
Text(f" 从黑屋中拉了出来并急救了一下!"),
|
||||
]
|
||||
).finish(reply=True)
|
||||
elif session.id1 in bot.config.superusers:
|
||||
_group_id = group_id.result if group_id.available else None
|
||||
await BanManage.unban(user_id, _group_id, session, True)
|
||||
logger.info(
|
||||
f"超级用户UnBan",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
target=f"{_group_id}:{user_id}",
|
||||
)
|
||||
at_msg = user_id if user_id else f"群组:{_group_id}"
|
||||
await Text(f"对 {at_msg} 从黑屋中拉了出来并急救了一下!").finish(reply=True)
|
||||
120
zhenxun/builtin_plugins/admin/ban/_data_source.py
Normal file
120
zhenxun/builtin_plugins/admin/ban/_data_source.py
Normal file
@ -0,0 +1,120 @@
|
||||
import time
|
||||
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.models.level_user import LevelUser
|
||||
from zhenxun.utils.image_utils import ImageTemplate
|
||||
|
||||
|
||||
class BanManage:
|
||||
|
||||
@classmethod
|
||||
async def build_ban_image(cls, user_id: str | None, group_id: str | None):
|
||||
data_list = None
|
||||
if not user_id and not group_id:
|
||||
data_list = await BanConsole.all()
|
||||
elif user_id:
|
||||
if group_id:
|
||||
data_list = await BanConsole.filter(
|
||||
user_id=user_id, group_id=group_id
|
||||
).all()
|
||||
else:
|
||||
data_list = await BanConsole.filter(
|
||||
user_id=user_id, group_id__isnull=True
|
||||
).all()
|
||||
else:
|
||||
if group_id:
|
||||
data_list = await BanConsole.filter(
|
||||
user_id__isnull=True, group_id=group_id
|
||||
).all()
|
||||
if not data_list:
|
||||
return None
|
||||
column_name = [
|
||||
"ID",
|
||||
"用户ID",
|
||||
"群组ID",
|
||||
"BAN LEVEL",
|
||||
"剩余时长(分钟)",
|
||||
"操作员ID",
|
||||
]
|
||||
row_data = []
|
||||
for data in data_list:
|
||||
duration = int((data.ban_time + data.duration - time.time()) / 60)
|
||||
if duration < 0:
|
||||
duration = 0
|
||||
row_data.append(
|
||||
[
|
||||
data.id,
|
||||
data.user_id,
|
||||
data.group_id,
|
||||
data.ban_level,
|
||||
duration,
|
||||
data.operator,
|
||||
]
|
||||
)
|
||||
return await ImageTemplate.table_page(
|
||||
"Ban / UnBan 列表", "在黑屋中狠狠调教!", column_name, row_data
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def is_ban(cls, user_id: str, group_id: str | None):
|
||||
"""判断用户是否被ban
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
|
||||
返回:
|
||||
bool: 是否被ban
|
||||
"""
|
||||
return await BanConsole.is_ban(user_id, group_id)
|
||||
|
||||
@classmethod
|
||||
async def unban(
|
||||
cls,
|
||||
user_id: str | None,
|
||||
group_id: str | None,
|
||||
session: EventSession,
|
||||
is_superuser: bool = False,
|
||||
) -> bool:
|
||||
"""ban掉目标用户
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
session: Session
|
||||
is_superuser: 是否为超级用户操作
|
||||
|
||||
返回:
|
||||
bool: 是否unban成功
|
||||
"""
|
||||
user_level = 9999
|
||||
if not is_superuser and user_id and session.id1:
|
||||
user_level = await LevelUser.get_user_level(session.id1, group_id)
|
||||
if await BanConsole.check_ban_level(user_id, group_id, user_level):
|
||||
await BanConsole.unban(user_id, group_id)
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def ban(
|
||||
cls,
|
||||
user_id: str | None,
|
||||
group_id: str | None,
|
||||
duration: int,
|
||||
session: EventSession,
|
||||
is_superuser: bool,
|
||||
):
|
||||
"""ban掉目标用户
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
duration: 时长,秒
|
||||
session: Session
|
||||
is_superuser: 是否为超级用户操作
|
||||
"""
|
||||
level = 9999
|
||||
if not is_superuser and user_id and session.id1:
|
||||
level = await LevelUser.get_user_level(session.id1, group_id)
|
||||
await BanConsole.ban(user_id, group_id, level, duration, session.id1)
|
||||
@ -0,0 +1,63 @@
|
||||
from nonebot import on_notice
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.onebot.v11 import GroupIncreaseNoticeEvent
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
|
||||
from nonebot_plugin_saa import Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.rules import admin_check, ensure_group
|
||||
|
||||
from ._data_source import MemberUpdateManage
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="更新群组成员列表",
|
||||
description="更新群组成员列表",
|
||||
usage="""
|
||||
更新群组成员的基本信息
|
||||
指令:
|
||||
更新群组成员信息
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.SUPER_AND_ADMIN,
|
||||
admin_level=1,
|
||||
).dict(),
|
||||
)
|
||||
|
||||
|
||||
_matcher = on_alconna(
|
||||
Alconna("更新群组成员信息"),
|
||||
rule=admin_check(1) & ensure_group,
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
|
||||
@_matcher.handle()
|
||||
async def _(bot: Bot, session: EventSession, arparma: Arparma):
|
||||
if gid := session.id3 or session.id2:
|
||||
logger.info("更新群组成员信息", arparma.header_result, session=session)
|
||||
await MemberUpdateManage.update(bot, gid)
|
||||
await Text("已经成功更新了群组成员信息!").finish(reply=True)
|
||||
await Text("群组id为空...").send()
|
||||
|
||||
|
||||
_notice = on_notice(priority=1, block=False)
|
||||
|
||||
|
||||
@_notice.handle()
|
||||
async def _(bot: Bot, event: GroupIncreaseNoticeEvent):
|
||||
# TODO: 其他适配器的加群自动更新群组成员信息
|
||||
if str(event.user_id) == bot.self_id:
|
||||
await MemberUpdateManage.update(bot, str(event.group_id))
|
||||
logger.info(
|
||||
"{NICKNAME}加入群聊更新群组信息",
|
||||
"更新群组成员列表",
|
||||
session=event.user_id,
|
||||
group_id=event.group_id,
|
||||
)
|
||||
@ -0,0 +1,177 @@
|
||||
import time
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.discord import Bot as DiscordBot
|
||||
from nonebot.adapters.dodo import Bot as DodoBot
|
||||
from nonebot.adapters.dodo.models import MemberInfo
|
||||
from nonebot.adapters.kaiheila import Bot as KaiheilaBot
|
||||
from nonebot.adapters.onebot.v11 import Bot as v11Bot
|
||||
from nonebot.adapters.onebot.v12 import Bot as v12Bot
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.group_member_info import GroupInfoUser
|
||||
from zhenxun.models.level_user import LevelUser
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
|
||||
class MemberUpdateManage:
|
||||
|
||||
@classmethod
|
||||
async def update(cls, bot: Bot, group_id: str):
|
||||
if isinstance(bot, v11Bot):
|
||||
await cls.v11(bot, group_id)
|
||||
elif isinstance(bot, v12Bot):
|
||||
await cls.v12(bot, group_id)
|
||||
elif isinstance(bot, KaiheilaBot):
|
||||
await cls.kaiheila(bot, group_id)
|
||||
elif isinstance(bot, DodoBot):
|
||||
await cls.dodo(bot, group_id)
|
||||
elif isinstance(bot, DiscordBot):
|
||||
await cls.discord(bot, group_id)
|
||||
|
||||
@classmethod
|
||||
async def discord(cls, bot: DiscordBot, group_id: str):
|
||||
# TODO: discord更新群组成员信息
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
async def dodo(cls, bot: DodoBot, group_id: str):
|
||||
page_size = 100
|
||||
result_size = 100
|
||||
max_id = 0
|
||||
exist_member_list = []
|
||||
group_member_list: list[MemberInfo] = []
|
||||
while result_size == page_size:
|
||||
group_member_data = await bot.get_member_list(
|
||||
island_source_id=group_id, page_size=page_size
|
||||
)
|
||||
result_size = len(group_member_data.list)
|
||||
group_member_list += group_member_data.list
|
||||
max_id = group_member_data.max_id
|
||||
if group_member_list:
|
||||
for user in group_member_list:
|
||||
exist_member_list.append(user.dodo_source_id)
|
||||
await GroupInfoUser.update_or_create(
|
||||
user_id=user.dodo_source_id,
|
||||
group_id=group_id,
|
||||
defaults={
|
||||
"user_name": user.nick_name or user.personal_nick_name,
|
||||
"user_join_time": user.join_time,
|
||||
"platform": "dodo",
|
||||
},
|
||||
)
|
||||
if delete_member_list := list(
|
||||
set(exist_member_list).difference(
|
||||
set(await GroupInfoUser.get_group_member_id_list(group_id))
|
||||
)
|
||||
):
|
||||
await GroupInfoUser.filter(
|
||||
user_id__in=delete_member_list, group_id=group_id
|
||||
).delete()
|
||||
logger.info(
|
||||
f"删除已退群用户",
|
||||
"更新群组成员信息",
|
||||
group_id=group_id,
|
||||
platform="dodo",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def kaiheila(cls, bot: KaiheilaBot, group_id: str):
|
||||
# TODO: kaiheila 更新群组成员信息
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
async def v11(cls, bot: v11Bot, group_id: str):
|
||||
exist_member_list = []
|
||||
default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH")
|
||||
group_member_list = await bot.get_group_member_list(group_id=int(group_id))
|
||||
for user_info in group_member_list:
|
||||
user_id = user_info["user_id"]
|
||||
nickname = user_info["card"] or user_info["nickname"]
|
||||
role = user_info["role"]
|
||||
if default_auth:
|
||||
if role in ["owner", "admin"] and not LevelUser.is_group_flag(
|
||||
str(user_id), group_id
|
||||
):
|
||||
await LevelUser.set_level(user_id, group_id, default_auth)
|
||||
if str(user_id) in bot.config.superusers:
|
||||
await LevelUser.set_level(str(user_id), group_id, 9)
|
||||
join_time = datetime.strptime(
|
||||
time.strftime(
|
||||
"%Y-%m-%d %H:%M:%S", time.localtime(user_info["join_time"])
|
||||
),
|
||||
"%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
await GroupInfoUser.update_or_create(
|
||||
user_id=str(user_id),
|
||||
group_id=group_id,
|
||||
defaults={
|
||||
"user_name": nickname,
|
||||
"user_join_time": join_time.replace(
|
||||
tzinfo=timezone(timedelta(hours=8))
|
||||
),
|
||||
"platform": "qq",
|
||||
},
|
||||
)
|
||||
exist_member_list.append(str(user_id))
|
||||
logger.debug(
|
||||
"更新成功", "更新群组成员信息", session=user_id, group_id=group_id
|
||||
)
|
||||
if delete_member_list := list(
|
||||
set(exist_member_list).difference(
|
||||
set(await GroupInfoUser.get_group_member_id_list(group_id))
|
||||
)
|
||||
):
|
||||
await GroupInfoUser.filter(
|
||||
user_id__in=delete_member_list, group_id=group_id
|
||||
).delete()
|
||||
logger.info(
|
||||
f"删除已退群用户", "更新群组成员信息", group_id=group_id, platform="qq"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def v12(cls, bot: v12Bot, group_id: str):
|
||||
# TODO: v12更新群组成员信息
|
||||
pass
|
||||
# exist_member_list = []
|
||||
# default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH")
|
||||
# group_member_list: list[GetGroupMemberInfoResp] = await bot.get_group_member_list(
|
||||
# group_id=group_id
|
||||
# )
|
||||
# for user_info in group_member_list:
|
||||
# user_id = user_info.user_id
|
||||
# nickname = user_info.user_displayname or user_info.user_name
|
||||
# role = user_info["role"]
|
||||
# if default_auth:
|
||||
# if role in ["owner", "admin"] and not LevelUser.is_group_flag(
|
||||
# str(user_id), group_id
|
||||
# ):
|
||||
# await LevelUser.set_level(user_id, group_id, default_auth)
|
||||
# if str(user_id) in bot.config.superusers:
|
||||
# await LevelUser.set_level(str(user_id), group_id, 9)
|
||||
# join_time = datetime.strptime(
|
||||
# time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(user_info["join_time"])),
|
||||
# "%Y-%m-%d %H:%M:%S",
|
||||
# )
|
||||
# await GroupInfoUser.update_or_create(
|
||||
# user_id=str(user_id),
|
||||
# group_id=group_id,
|
||||
# defaults={
|
||||
# "user_name": nickname,
|
||||
# "user_join_time": join_time.replace(
|
||||
# tzinfo=timezone(timedelta(hours=8))
|
||||
# ),
|
||||
# },
|
||||
# )
|
||||
# exist_member_list.append(str(user_id))
|
||||
# logger.debug("更新成功", "更新群组成员信息", session=user_id, group_id=group_id)
|
||||
# if delete_member_list := list(
|
||||
# set(exist_member_list).difference(
|
||||
# set(await GroupInfoUser.get_group_member_id_list(group_id))
|
||||
# )
|
||||
# ):
|
||||
# await GroupInfoUser.filter(
|
||||
# user_id__in=delete_member_list, group_id=group_id
|
||||
# ).delete()
|
||||
# logger.info(f"删除已退群用户", "更新群组成员信息", group_id=group_id)
|
||||
162
zhenxun/builtin_plugins/admin/plugin_switch/__init__.py
Normal file
162
zhenxun/builtin_plugins/admin/plugin_switch/__init__.py
Normal file
@ -0,0 +1,162 @@
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import (
|
||||
Alconna,
|
||||
Args,
|
||||
Arparma,
|
||||
Match,
|
||||
Option,
|
||||
Subcommand,
|
||||
on_alconna,
|
||||
store_true,
|
||||
)
|
||||
from nonebot_plugin_saa import Image, Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
from requests import session
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import BlockType, PluginType
|
||||
from zhenxun.utils.rules import admin_check, ensure_group
|
||||
|
||||
from ._data_source import PluginManage, build_plugin, build_task
|
||||
|
||||
base_config = Config.get("admin_bot_manage")
|
||||
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="功能开关",
|
||||
description="对群组内的功能限制,超级用户可以对群组以及全局的功能被动开关限制",
|
||||
usage="""
|
||||
开启/关闭[功能]
|
||||
群被动状态
|
||||
开启全部被动
|
||||
关闭全部被动
|
||||
醒来/休息吧
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.SUPER_AND_ADMIN,
|
||||
admin_level=base_config.get("CHANGE_GROUP_SWITCH_LEVEL", 2),
|
||||
configs=[
|
||||
RegisterConfig(
|
||||
key="CHANGE_GROUP_SWITCH_LEVEL",
|
||||
value=2,
|
||||
help="开关群功能权限",
|
||||
default_value=2,
|
||||
type=int,
|
||||
)
|
||||
],
|
||||
).dict(),
|
||||
)
|
||||
|
||||
|
||||
_status_matcher = on_alconna(
|
||||
Alconna(
|
||||
"switch",
|
||||
Option("-t|--task", action=store_true, help_text="被动技能"),
|
||||
Subcommand(
|
||||
"open",
|
||||
Args["name", str],
|
||||
Option(
|
||||
"-g|--group",
|
||||
Args["group_id", str],
|
||||
),
|
||||
),
|
||||
Subcommand(
|
||||
"close",
|
||||
Args["name", str],
|
||||
Option(
|
||||
"-t|--type",
|
||||
Args["block_type", ["all", "a", "private", "p", "group", "g"]],
|
||||
),
|
||||
Option(
|
||||
"-g|--group",
|
||||
Args["group_id", str],
|
||||
),
|
||||
),
|
||||
),
|
||||
rule=admin_check("admin_bot_manage", "CHANGE_GROUP_SWITCH_LEVEL"),
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
# TODO: shortcut
|
||||
|
||||
_group_status_matcher = on_alconna(
|
||||
Alconna("group-status", Args["status", ["sleep", "wake"]]),
|
||||
rule=admin_check("admin_bot_manage", "CHANGE_GROUP_SWITCH_LEVEL") & ensure_group,
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
|
||||
@_status_matcher.assign("$main")
|
||||
async def _(bot: Bot, session: EventSession, arparma: Arparma):
|
||||
image = None
|
||||
if arparma.find("task"):
|
||||
image = await build_task(session.id3 or session.id2)
|
||||
elif session.id1 in bot.config.superusers:
|
||||
image = await build_plugin()
|
||||
if image:
|
||||
await Image(image.pic2bs4()).send(reply=True)
|
||||
logger.info(
|
||||
f"查看{'被动' if arparma.find('task') else '功能'}列表",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
|
||||
|
||||
@_status_matcher.assign("open")
|
||||
async def _(
|
||||
bot: Bot,
|
||||
session: EventSession,
|
||||
arparma: Arparma,
|
||||
name: str,
|
||||
group: Match[str],
|
||||
):
|
||||
if gid := session.id3 or session.id2:
|
||||
result = await PluginManage.block_group_plugin(name, gid)
|
||||
await Text(result).send(reply=True)
|
||||
logger.info(f"开启功能 {name}", arparma.header_result, session=session)
|
||||
elif session.id1 in bot.config.superusers:
|
||||
result = await PluginManage.superuser_block(name, None, group.result)
|
||||
await Text(result).send(reply=True)
|
||||
logger.info(
|
||||
f"超级用户开启功能 {name}",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
target=group.result,
|
||||
)
|
||||
|
||||
|
||||
@_status_matcher.assign("close")
|
||||
async def _(
|
||||
bot: Bot,
|
||||
session: EventSession,
|
||||
arparma: Arparma,
|
||||
name: str,
|
||||
block_type: Match[str],
|
||||
group: Match[str],
|
||||
):
|
||||
if gid := session.id3 or session.id2:
|
||||
result = await PluginManage.unblock_group_plugin(name, gid)
|
||||
await Text(result).send(reply=True)
|
||||
logger.info(f"关闭功能 {name}", arparma.header_result, session=session)
|
||||
elif session.id1 in bot.config.superusers:
|
||||
_type = BlockType.ALL
|
||||
if block_type.available:
|
||||
if block_type.result in ["p", "private"]:
|
||||
_type = BlockType.FRIEND
|
||||
elif block_type.result in ["g", "group"]:
|
||||
_type = BlockType.GROUP
|
||||
result = await PluginManage.superuser_block(name, _type, group.result)
|
||||
await Text(result).send(reply=True)
|
||||
logger.info(
|
||||
f"超级用户关闭功能 {name}, 禁用类型: {_type}",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
target=group.result,
|
||||
)
|
||||
244
zhenxun/builtin_plugins/admin/plugin_switch/_data_source.py
Normal file
244
zhenxun/builtin_plugins/admin/plugin_switch/_data_source.py
Normal file
@ -0,0 +1,244 @@
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.utils.enum import BlockType, PluginType
|
||||
from zhenxun.utils.exception import GroupInfoNotFound
|
||||
from zhenxun.utils.image_utils import BuildImage, ImageTemplate, RowStyle
|
||||
|
||||
|
||||
def plugin_row_style(column: str, text: str) -> RowStyle:
|
||||
"""被动技能文本风格
|
||||
|
||||
参数:
|
||||
column: 表头
|
||||
text: 文本内容
|
||||
|
||||
返回:
|
||||
RowStyle: RowStyle
|
||||
"""
|
||||
style = RowStyle()
|
||||
if column == "全局状态":
|
||||
if text == "开启":
|
||||
style.font_color = "#67C23A"
|
||||
else:
|
||||
style.font_color = "#F56C6C"
|
||||
if column == "加载状态":
|
||||
if text == "SUCCESS":
|
||||
style.font_color = "#67C23A"
|
||||
else:
|
||||
style.font_color = "#F56C6C"
|
||||
return style
|
||||
|
||||
|
||||
async def build_plugin() -> BuildImage:
|
||||
column_name = [
|
||||
"ID",
|
||||
"模块",
|
||||
"名称",
|
||||
"全局状态",
|
||||
"禁用类型",
|
||||
"加载状态",
|
||||
"菜单分类",
|
||||
"作者",
|
||||
"版本",
|
||||
"金币花费",
|
||||
]
|
||||
plugin_list = await PluginInfo.filter(plugin_type__not=PluginType.HIDDEN).all()
|
||||
column_data = []
|
||||
for plugin in plugin_list:
|
||||
column_data.append(
|
||||
[
|
||||
plugin.id,
|
||||
plugin.module,
|
||||
plugin.name,
|
||||
"开启" if plugin.status else "关闭",
|
||||
plugin.block_type,
|
||||
"SUCCESS" if plugin.load_status else "ERROR",
|
||||
plugin.menu_type,
|
||||
plugin.author,
|
||||
plugin.version,
|
||||
plugin.cost_gold,
|
||||
]
|
||||
)
|
||||
return await ImageTemplate.table_page(
|
||||
"Plugin",
|
||||
"插件状态",
|
||||
column_name,
|
||||
column_data,
|
||||
text_style=plugin_row_style,
|
||||
)
|
||||
|
||||
|
||||
def task_row_style(column: str, text: str) -> RowStyle:
|
||||
"""被动技能文本风格
|
||||
|
||||
参数:
|
||||
column: 表头
|
||||
text: 文本内容
|
||||
|
||||
返回:
|
||||
RowStyle: RowStyle
|
||||
"""
|
||||
style = RowStyle()
|
||||
if column in ["群组状态", "全局状态"]:
|
||||
if text == "开启":
|
||||
style.font_color = "#67C23A"
|
||||
else:
|
||||
style.font_color = "#F56C6C"
|
||||
return style
|
||||
|
||||
|
||||
async def build_task(group_id: str | None) -> BuildImage:
|
||||
"""构造被动技能状态图片
|
||||
|
||||
参数:
|
||||
group_id: 群组id
|
||||
|
||||
异常:
|
||||
GroupInfoNotFound: 未找到群组
|
||||
|
||||
返回:
|
||||
BuildImage: 被动技能状态图片
|
||||
"""
|
||||
task_list = await TaskInfo.all()
|
||||
column_name = ["ID", "模块", "名称", "群组状态", "全局状态", "运行时间"]
|
||||
group = None
|
||||
if group_id:
|
||||
group = await GroupConsole.get_or_none(group_id=group_id)
|
||||
if not group:
|
||||
raise GroupInfoNotFound()
|
||||
else:
|
||||
column_name.remove("群组状态")
|
||||
column_data = []
|
||||
for task in task_list:
|
||||
if group:
|
||||
column_data.append(
|
||||
[
|
||||
task.id,
|
||||
task.module,
|
||||
task.name,
|
||||
"开启" if task.module not in group.block_task else "关闭",
|
||||
"开启" if task.status else "关闭",
|
||||
task.run_time,
|
||||
]
|
||||
)
|
||||
else:
|
||||
column_data.append(
|
||||
[
|
||||
task.id,
|
||||
task.module,
|
||||
task.name,
|
||||
"开启" if task.status else "关闭",
|
||||
task.run_time,
|
||||
]
|
||||
)
|
||||
return await ImageTemplate.table_page(
|
||||
"Task",
|
||||
"被动技能状态",
|
||||
column_name,
|
||||
column_data,
|
||||
text_style=task_row_style,
|
||||
)
|
||||
|
||||
|
||||
class PluginManage:
|
||||
|
||||
@classmethod
|
||||
async def block(cls, module: str):
|
||||
await PluginInfo.filter(module=module).update(status=False)
|
||||
|
||||
@classmethod
|
||||
async def unblock(cls, module: str):
|
||||
await PluginInfo.filter(module=module).update(status=True)
|
||||
|
||||
@classmethod
|
||||
async def block_group_plugin(cls, plugin_name: str, group_id: str) -> str:
|
||||
"""禁用群组插件
|
||||
|
||||
参数:
|
||||
plugin_name: 插件名称
|
||||
group_id: 群组id
|
||||
|
||||
返回:
|
||||
str: 返回信息
|
||||
"""
|
||||
return await cls._change_group_plugin(plugin_name, group_id, True)
|
||||
|
||||
@classmethod
|
||||
async def unblock_group_plugin(cls, plugin_name: str, group_id: str) -> str:
|
||||
"""启用群组插件
|
||||
|
||||
参数:
|
||||
plugin_name: 插件名称
|
||||
group_id: 群组id
|
||||
|
||||
返回:
|
||||
str: 返回信息
|
||||
"""
|
||||
return await cls._change_group_plugin(plugin_name, group_id, False)
|
||||
|
||||
@classmethod
|
||||
async def _change_group_plugin(
|
||||
cls, plugin_name: str, group_id: str, status: bool
|
||||
) -> str:
|
||||
"""修改群组插件状态
|
||||
|
||||
参数:
|
||||
plugin_name: 插件名称
|
||||
group_id: 群组id
|
||||
status: 插件状态
|
||||
|
||||
返回:
|
||||
str: 返回信息
|
||||
"""
|
||||
status_str = "开启" if status else "关闭"
|
||||
if plugin := await PluginInfo.get_or_none(name=plugin_name):
|
||||
group, _ = await GroupConsole.get_or_create(group_id=group_id)
|
||||
if status:
|
||||
if plugin.module in group.block_plugin:
|
||||
group.block_plugin = group.block_plugin.replace(
|
||||
f"{plugin.module},", ""
|
||||
)
|
||||
await group.save(update_fields=["block_plugin"])
|
||||
return f"已成功{status_str} {plugin_name} 功能!"
|
||||
else:
|
||||
if plugin.module not in group.block_plugin:
|
||||
group.block_plugin += f"{plugin.module},"
|
||||
await group.save(update_fields=["block_plugin"])
|
||||
return f"已成功{status_str} {plugin_name} 功能!"
|
||||
return f"该功能已经{status_str}了喔,不要重复{status_str}..."
|
||||
return "没有找到这个功能喔..."
|
||||
|
||||
@classmethod
|
||||
async def superuser_block(
|
||||
cls, plugin_name: str, block_type: BlockType | None, group_id: str | None
|
||||
) -> str:
|
||||
"""超级用户禁用
|
||||
|
||||
参数:
|
||||
plugin_name: 插件名称
|
||||
block_type: 禁用类型
|
||||
group_id: 群组id
|
||||
|
||||
返回:
|
||||
str: 返回信息
|
||||
"""
|
||||
if plugin := await PluginInfo.get_or_none(name=plugin_name):
|
||||
if group_id:
|
||||
if group := await GroupConsole.get_or_none(group_id=group_id):
|
||||
if f"super:{plugin_name}," not in group.block_plugin:
|
||||
group.block_plugin += f"super:{plugin_name},"
|
||||
await group.save(update_fields=["block_plugin"])
|
||||
return (
|
||||
f"已成功关闭群组 {group.group_name} 的 {plugin_name} 功能!"
|
||||
)
|
||||
return "此群组该功能已被超级用户关闭,不要重复关闭..."
|
||||
return "群组信息未更新,请先更新群组信息..."
|
||||
plugin.block_type = block_type
|
||||
plugin.status = not bool(block_type)
|
||||
await plugin.save(update_fields=["status", "block_type"])
|
||||
if not block_type:
|
||||
return f"已成功将 {plugin_name} 全局启用!"
|
||||
else:
|
||||
return f"已成功将 {plugin_name} 全局关闭!"
|
||||
return "没有找到这个功能喔..."
|
||||
@ -1,26 +1,22 @@
|
||||
import os
|
||||
import shutil
|
||||
from typing import Dict
|
||||
from typing import Annotated, Dict
|
||||
|
||||
import ujson as json
|
||||
from arclet.alconna import Args, Option
|
||||
from nonebot import on_command
|
||||
from nonebot.params import Command
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import (
|
||||
Alconna,
|
||||
AlconnaMatch,
|
||||
Arparma,
|
||||
Match,
|
||||
on_alconna,
|
||||
store_true,
|
||||
)
|
||||
from nonebot_plugin_alconna.matcher import AlconnaMatcher
|
||||
from nonebot_plugin_saa import Text
|
||||
from nonebot_plugin_alconna import Image
|
||||
from nonebot_plugin_alconna import Text as alcText
|
||||
from nonebot_plugin_alconna import UniMsg
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.path_config import DATA_PATH
|
||||
from zhenxun.configs.utils import PluginCdBlock, PluginExtraData, RegisterConfig
|
||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.http_utils import AsyncHttpx
|
||||
from zhenxun.utils.rules import admin_check, ensure_group
|
||||
|
||||
base_config = Config.get("admin_bot_manage")
|
||||
@ -48,18 +44,15 @@ __plugin_meta__ = PluginMetadata(
|
||||
).dict(),
|
||||
)
|
||||
|
||||
_matcher = on_alconna(
|
||||
Alconna(
|
||||
"设置欢迎消息",
|
||||
Args["message", str],
|
||||
Option("-at", action=store_true, help_text="是否at新入群用户"),
|
||||
),
|
||||
_matcher = on_command(
|
||||
"设置欢迎消息",
|
||||
rule=admin_check("admin_bot_manage", "SET_GROUP_WELCOME_MESSAGE_LEVEL")
|
||||
& ensure_group,
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
|
||||
BASE_PATH = DATA_PATH / "welcome_message"
|
||||
BASE_PATH.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@ -86,31 +79,43 @@ if old_file.exists():
|
||||
@_matcher.handle()
|
||||
async def _(
|
||||
session: EventSession,
|
||||
matcher: AlconnaMatcher,
|
||||
arparma: Arparma,
|
||||
message: str,
|
||||
message: UniMsg,
|
||||
command: Annotated[tuple[str, ...], Command()],
|
||||
):
|
||||
file = (
|
||||
BASE_PATH
|
||||
/ f"{session.platform or session.bot_type}"
|
||||
/ f"{session.id2}"
|
||||
/ "text.json"
|
||||
)
|
||||
path = BASE_PATH / f"{session.platform or session.bot_type}" / f"{session.id2}"
|
||||
if session.id3:
|
||||
file = (
|
||||
path = (
|
||||
BASE_PATH
|
||||
/ f"{session.platform or session.bot_type}"
|
||||
/ f"{session.id3}"
|
||||
/ f"{session.id2}"
|
||||
/ "text.json"
|
||||
)
|
||||
file = path / "text.json"
|
||||
idx = 0
|
||||
text = ""
|
||||
for f in os.listdir(path):
|
||||
(path / f).unlink()
|
||||
message[0].text = message[0].text.replace(command[0], "").strip()
|
||||
for msg in message:
|
||||
if isinstance(msg, alcText):
|
||||
text += msg.text
|
||||
elif isinstance(msg, Image):
|
||||
if msg.url:
|
||||
text += f"[image:{idx}]"
|
||||
await AsyncHttpx.download_file(msg.url, path / f"{idx}.png")
|
||||
idx += 1
|
||||
else:
|
||||
logger.debug("图片 URL 为空...", command[0])
|
||||
if not file.exists():
|
||||
file.parent.mkdir(exist_ok=True, parents=True)
|
||||
is_at = "-at" in message
|
||||
text = text.replace("-at", "")
|
||||
json.dump(
|
||||
{"at": arparma.find("at"), "message": message},
|
||||
{"at": is_at, "message": text},
|
||||
file.open("w"),
|
||||
ensure_ascii=False,
|
||||
indent=4,
|
||||
)
|
||||
logger.info(f"设置群欢迎消息成功: {message}", arparma.header_result, session=session)
|
||||
await Text(f"设置欢迎消息成功: \n{message}").send()
|
||||
uni_msg = alcText("设置欢迎消息成功: \n") + message
|
||||
await uni_msg.send()
|
||||
logger.info(f"设置群欢迎消息成功: {text}", command[0], session=session)
|
||||
|
||||
4
zhenxun/builtin_plugins/chat_history/__init__.py
Normal file
4
zhenxun/builtin_plugins/chat_history/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
import nonebot
|
||||
from pathlib import Path
|
||||
|
||||
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
|
||||
83
zhenxun/builtin_plugins/chat_history/chat_message.py
Normal file
83
zhenxun/builtin_plugins/chat_history/chat_message.py
Normal file
@ -0,0 +1,83 @@
|
||||
from nonebot import on_message
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import UniMsg
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="消息存储",
|
||||
description="消息存储,被动存储群消息",
|
||||
usage="",
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.HIDDEN,
|
||||
configs=[
|
||||
RegisterConfig(
|
||||
module="chat_history",
|
||||
key="FLAG",
|
||||
value=True,
|
||||
help="是否开启消息自从存储",
|
||||
default_value=True,
|
||||
type=bool,
|
||||
)
|
||||
],
|
||||
).dict(),
|
||||
)
|
||||
|
||||
|
||||
def rule(message: UniMsg) -> bool:
|
||||
return bool(Config.get_config("chat_history", "FLAG") and message)
|
||||
|
||||
|
||||
chat_history = on_message(rule=rule, priority=1, block=False)
|
||||
|
||||
|
||||
TEMP_LIST = []
|
||||
|
||||
|
||||
@chat_history.handle()
|
||||
async def _(message: UniMsg, session: EventSession):
|
||||
group_id = session.id3 or session.id2
|
||||
TEMP_LIST.append(
|
||||
ChatHistory(
|
||||
user_id=session.id1,
|
||||
group_id=group_id,
|
||||
text=str(message),
|
||||
plain_text=message.extract_plain_text(),
|
||||
bot_id=session.bot_id,
|
||||
platform=session.platform,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@scheduler.scheduled_job(
|
||||
"interval",
|
||||
minutes=1,
|
||||
)
|
||||
async def _():
|
||||
try:
|
||||
message_list = TEMP_LIST.copy()
|
||||
TEMP_LIST.clear()
|
||||
if message_list:
|
||||
await ChatHistory.bulk_create(message_list)
|
||||
logger.debug(f"批量添加聊天记录 {len(message_list)} 条", "定时任务")
|
||||
except Exception as e:
|
||||
logger.error(f"定时批量添加聊天记录", "定时任务", e=e)
|
||||
|
||||
|
||||
# @test.handle()
|
||||
# async def _(event: MessageEvent):
|
||||
# print(await ChatHistory.get_user_msg(event.user_id, "private"))
|
||||
# print(await ChatHistory.get_user_msg_count(event.user_id, "private"))
|
||||
# print(await ChatHistory.get_user_msg(event.user_id, "group"))
|
||||
# print(await ChatHistory.get_user_msg_count(event.user_id, "group"))
|
||||
# print(await ChatHistory.get_group_msg(event.group_id))
|
||||
# print(await ChatHistory.get_group_msg_count(event.group_id))
|
||||
116
zhenxun/builtin_plugins/chat_history/chat_message_handle.py
Normal file
116
zhenxun/builtin_plugins/chat_history/chat_message_handle.py
Normal file
@ -0,0 +1,116 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytz
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import (
|
||||
Alconna,
|
||||
Args,
|
||||
Arparma,
|
||||
Match,
|
||||
Option,
|
||||
on_alconna,
|
||||
store_true,
|
||||
)
|
||||
from nonebot_plugin_saa import Image, Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
from zhenxun.models.group_member_info import GroupInfoUser
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.image_utils import ImageTemplate
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="消息统计查询",
|
||||
description="消息统计查询",
|
||||
usage="",
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.NORMAL,
|
||||
menu_type="数据统计",
|
||||
).dict(),
|
||||
)
|
||||
|
||||
# TODO: shortcut
|
||||
|
||||
_matcher = on_alconna(
|
||||
Alconna(
|
||||
"消息排行",
|
||||
Option("--des", default=False, action=store_true),
|
||||
Args["type?", ["日", "周", "月", "年"]]["count?", int, 10],
|
||||
),
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
|
||||
@_matcher.handle()
|
||||
async def _(
|
||||
bot: Bot,
|
||||
session: EventSession,
|
||||
arparma: Arparma,
|
||||
type: Match[str],
|
||||
count: Match[int],
|
||||
):
|
||||
group_id = session.id3 or session.id2
|
||||
if not group_id:
|
||||
await Text("群组id为空...").finish()
|
||||
time_now = datetime.now()
|
||||
date_scope = None
|
||||
zero_today = time_now - timedelta(
|
||||
hours=time_now.hour, minutes=time_now.minute, seconds=time_now.second
|
||||
)
|
||||
date = type.result if type.available else None
|
||||
if date:
|
||||
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 = (time_now - timedelta(days=30), time_now)
|
||||
column_name = ["名次", "昵称", "发言次数"]
|
||||
if rank_data := await ChatHistory.get_group_msg_rank(
|
||||
group_id, count.result, "DES" if arparma.find("des") else "DESC", date_scope
|
||||
):
|
||||
idx = 1
|
||||
data_list = []
|
||||
for uid, num in rank_data:
|
||||
if user := await GroupInfoUser.filter(
|
||||
user_id=uid, group_id=group_id
|
||||
).first():
|
||||
user_name = user.user_name
|
||||
else:
|
||||
user_name = uid
|
||||
data_list.append([idx, user_name, num])
|
||||
idx += 1
|
||||
if not date_scope:
|
||||
if date_scope := await ChatHistory.get_group_first_msg_datetime(group_id):
|
||||
date_scope = date_scope.astimezone(
|
||||
pytz.timezone("Asia/Shanghai")
|
||||
).replace(microsecond=0)
|
||||
else:
|
||||
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)}"
|
||||
A = await ImageTemplate.table_page(
|
||||
f"消息排行({count.result})", date_str, column_name, data_list
|
||||
)
|
||||
logger.info(
|
||||
f"查看消息排行 数量={count.result}", arparma.header_result, session=session
|
||||
)
|
||||
await Image(A.pic2bs4()).finish(reply=True)
|
||||
await Text("群组消息记录为空...").finish()
|
||||
|
||||
|
||||
# # @test.handle()
|
||||
# # async def _(event: MessageEvent):
|
||||
# # print(await ChatHistory.get_user_msg(event.user_id, "private"))
|
||||
# # print(await ChatHistory.get_user_msg_count(event.user_id, "private"))
|
||||
# # print(await ChatHistory.get_user_msg(event.user_id, "group"))
|
||||
# # print(await ChatHistory.get_user_msg_count(event.user_id, "group"))
|
||||
# # print(await ChatHistory.get_group_msg(event.group_id))
|
||||
# # print(await ChatHistory.get_group_msg_count(event.group_id))
|
||||
80
zhenxun/builtin_plugins/help/__init__.py
Normal file
80
zhenxun/builtin_plugins/help/__init__.py
Normal file
@ -0,0 +1,80 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot.rule import to_me
|
||||
from nonebot_plugin_alconna import Alconna, Args, Match, on_alconna
|
||||
from nonebot_plugin_saa import Image, Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
|
||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
|
||||
from ._data_source import create_help_img, get_plugin_help
|
||||
from ._utils import GROUP_HELP_PATH
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="帮助",
|
||||
description="帮助",
|
||||
usage="",
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.HIDDEN,
|
||||
configs=[
|
||||
RegisterConfig(
|
||||
key="type",
|
||||
value="normal",
|
||||
help="帮助图片样式 ['normal', 'HTML']",
|
||||
default_value="normal",
|
||||
)
|
||||
],
|
||||
).dict(),
|
||||
)
|
||||
|
||||
|
||||
SIMPLE_HELP_IMAGE = IMAGE_PATH / "SIMPLE_HELP.png"
|
||||
if SIMPLE_HELP_IMAGE.exists():
|
||||
SIMPLE_HELP_IMAGE.unlink()
|
||||
|
||||
_matcher = on_alconna(
|
||||
Alconna(
|
||||
"功能",
|
||||
Args["name?", str],
|
||||
),
|
||||
aliases={"help", "帮助"},
|
||||
rule=to_me(),
|
||||
priority=1,
|
||||
block=True,
|
||||
)
|
||||
|
||||
# TODO: 插件使用详情 图片形式的帮助回复
|
||||
|
||||
|
||||
@_matcher.handle()
|
||||
async def _(
|
||||
name: Match[str],
|
||||
session: EventSession,
|
||||
):
|
||||
|
||||
if name.available:
|
||||
if text := await get_plugin_help(name.result):
|
||||
await Text(text).send(reply=True)
|
||||
else:
|
||||
await Text("没有此功能的帮助信息...").send()
|
||||
logger.info(
|
||||
f"查看帮助详情: {name.result}",
|
||||
"帮助",
|
||||
session=session,
|
||||
)
|
||||
else:
|
||||
if gid := session.id3 or session.id2:
|
||||
_image_path = GROUP_HELP_PATH / f"{gid}.png"
|
||||
if not _image_path.exists():
|
||||
await create_help_img(gid)
|
||||
await Image(_image_path).finish()
|
||||
else:
|
||||
if not SIMPLE_HELP_IMAGE.exists():
|
||||
if SIMPLE_HELP_IMAGE.exists():
|
||||
SIMPLE_HELP_IMAGE.unlink()
|
||||
await create_help_img(None)
|
||||
await Image(SIMPLE_HELP_IMAGE).finish()
|
||||
13
zhenxun/builtin_plugins/help/_config.py
Normal file
13
zhenxun/builtin_plugins/help/_config.py
Normal file
@ -0,0 +1,13 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
plugin_name: str
|
||||
sta: int
|
||||
|
||||
|
||||
class PluginList(BaseModel):
|
||||
plugin_type: str
|
||||
icon: str
|
||||
logo: str
|
||||
items: list[Item]
|
||||
35
zhenxun/builtin_plugins/help/_data_source.py
Normal file
35
zhenxun/builtin_plugins/help/_data_source.py
Normal file
@ -0,0 +1,35 @@
|
||||
import nonebot
|
||||
|
||||
from zhenxun.configs.path_config import IMAGE_PATH
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.utils.image_utils import BuildImage
|
||||
|
||||
from ._utils import HelpImageBuild
|
||||
|
||||
random_bk_path = IMAGE_PATH / "background" / "help" / "simple_help"
|
||||
|
||||
background = IMAGE_PATH / "background" / "0.png"
|
||||
|
||||
|
||||
async def create_help_img(group_id: int | None):
|
||||
"""
|
||||
说明:
|
||||
生成帮助图片
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
"""
|
||||
await HelpImageBuild().build_image(group_id)
|
||||
|
||||
|
||||
async def get_plugin_help(name: str) -> str:
|
||||
"""获取功能的帮助信息
|
||||
|
||||
参数:
|
||||
name: 插件名称
|
||||
"""
|
||||
if plugin := await PluginInfo.get_or_none(name=name):
|
||||
_plugin = nonebot.get_plugin_by_module_name(plugin.module_path)
|
||||
if _plugin and _plugin.metadata:
|
||||
return _plugin.metadata.usage
|
||||
return "糟糕! 该功能没有帮助喔..."
|
||||
return "没有查找到这个功能噢..."
|
||||
242
zhenxun/builtin_plugins/help/_utils.py
Normal file
242
zhenxun/builtin_plugins/help/_utils.py
Normal file
@ -0,0 +1,242 @@
|
||||
import os
|
||||
import random
|
||||
from typing import Dict
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH, TEMPLATE_PATH
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.utils.enum import BlockType, PluginType
|
||||
from zhenxun.utils.image_utils import BuildImage, build_sort_image, group_image
|
||||
|
||||
from ._config import Item
|
||||
|
||||
GROUP_HELP_PATH = DATA_PATH / "group_help"
|
||||
GROUP_HELP_PATH.mkdir(exist_ok=True, parents=True)
|
||||
for f in os.listdir(GROUP_HELP_PATH):
|
||||
group_help_image = GROUP_HELP_PATH / f
|
||||
group_help_image.unlink()
|
||||
|
||||
BACKGROUND_PATH = IMAGE_PATH / "background" / "help" / "simple_help"
|
||||
|
||||
LOGO_PATH = TEMPLATE_PATH / "menu" / "res" / "logo"
|
||||
|
||||
|
||||
class HelpImageBuild:
|
||||
def __init__(self):
|
||||
self._data: list[PluginInfo] = []
|
||||
self._sort_data: Dict[str, list[PluginInfo]] = {}
|
||||
self._image_list = []
|
||||
self.icon2str = {
|
||||
"normal": "fa fa-cog",
|
||||
"原神相关": "fa fa-circle-o",
|
||||
"常规插件": "fa fa-cubes",
|
||||
"联系管理员": "fa fa-envelope-o",
|
||||
"抽卡相关": "fa fa-credit-card-alt",
|
||||
"来点好康的": "fa fa-picture-o",
|
||||
"数据统计": "fa fa-bar-chart",
|
||||
"一些工具": "fa fa-shopping-cart",
|
||||
"商店": "fa fa-shopping-cart",
|
||||
"其它": "fa fa-tags",
|
||||
"群内小游戏": "fa fa-gamepad",
|
||||
}
|
||||
|
||||
async def sort_type(self):
|
||||
"""
|
||||
对插件按照菜单类型分类
|
||||
"""
|
||||
if not self._data:
|
||||
self._data = await PluginInfo.filter(plugin_type=PluginType.NORMAL)
|
||||
if not self._sort_data:
|
||||
for plugin in self._data:
|
||||
menu_type = plugin.menu_type or "normal"
|
||||
if not self._sort_data.get(menu_type):
|
||||
self._sort_data[menu_type] = []
|
||||
self._sort_data[menu_type].append(plugin)
|
||||
|
||||
async def build_image(self, group_id: int | None):
|
||||
if group_id:
|
||||
help_image = GROUP_HELP_PATH / f"{group_id}.png"
|
||||
else:
|
||||
help_image = IMAGE_PATH / f"SIMPLE_HELP.png"
|
||||
build_type = Config.get_config("help", "TYPE")
|
||||
if build_type == "HTML":
|
||||
byt = await self.build_html_image(group_id)
|
||||
with open(help_image, "wb") as f:
|
||||
f.write(byt)
|
||||
else:
|
||||
img = await self.build_pil_image(group_id)
|
||||
await img.save(help_image)
|
||||
|
||||
async def build_html_image(self, group_id: int | None) -> bytes:
|
||||
from nonebot_plugin_htmlrender import template_to_pic
|
||||
|
||||
await self.sort_type()
|
||||
classify = {}
|
||||
for menu in self._sort_data:
|
||||
for plugin in self._sort_data[menu]:
|
||||
sta = 0
|
||||
if not plugin.status:
|
||||
if group_id and plugin.block_type in [
|
||||
BlockType.ALL,
|
||||
BlockType.GROUP,
|
||||
]:
|
||||
sta = 2
|
||||
if not group_id and plugin.block_type in [
|
||||
BlockType.ALL,
|
||||
BlockType.FRIEND,
|
||||
]:
|
||||
sta = 2
|
||||
if group_id and (
|
||||
group := await GroupConsole.get_or_none(group_id=group_id)
|
||||
):
|
||||
if f"{plugin.module}:super," in group.block_plugin:
|
||||
sta = 2
|
||||
if f"{plugin.module}," in group.block_plugin:
|
||||
sta = 1
|
||||
if classify.get(menu):
|
||||
classify[menu].append(Item(plugin_name=plugin.name, sta=sta))
|
||||
else:
|
||||
classify[menu] = [Item(plugin_name=plugin.name, sta=sta)]
|
||||
max_len = 0
|
||||
flag_index = -1
|
||||
max_data = None
|
||||
plugin_list = []
|
||||
for index, plu in enumerate(classify.keys()):
|
||||
if plu in self.icon2str.keys():
|
||||
icon = self.icon2str[plu]
|
||||
else:
|
||||
icon = "fa fa-pencil-square-o"
|
||||
logo = LOGO_PATH / random.choice(os.listdir(LOGO_PATH))
|
||||
data = {
|
||||
"name": plu if plu != "normal" else "功能",
|
||||
"items": classify[plu],
|
||||
"icon": icon,
|
||||
"logo": str(logo.absolute()),
|
||||
}
|
||||
if len(classify[plu]) > max_len:
|
||||
max_len = len(classify[plu])
|
||||
flag_index = index
|
||||
max_data = data
|
||||
plugin_list.append(data)
|
||||
del plugin_list[flag_index]
|
||||
plugin_list.insert(0, max_data)
|
||||
pic = await template_to_pic(
|
||||
template_path=str((TEMPLATE_PATH / "menu").absolute()),
|
||||
template_name="zhenxun_menu.html",
|
||||
templates={"plugin_list": plugin_list},
|
||||
pages={
|
||||
"viewport": {"width": 1903, "height": 975},
|
||||
"base_url": f"file://{TEMPLATE_PATH}",
|
||||
},
|
||||
wait=2,
|
||||
)
|
||||
return pic
|
||||
|
||||
async def build_pil_image(self, group_id: int | None) -> BuildImage:
|
||||
"""构造帮助图片
|
||||
|
||||
参数:
|
||||
group_id: 群号
|
||||
"""
|
||||
self._image_list = []
|
||||
await self.sort_type()
|
||||
font_size = 24
|
||||
build_type = Config.get_config("help", "TYPE")
|
||||
_image = BuildImage.build_text_image("1", size=font_size)
|
||||
font = BuildImage.load_font("HYWenHei-85W.ttf", 20)
|
||||
for idx, menu_type in enumerate(self._sort_data.keys()):
|
||||
plugin_list = self._sort_data[menu_type]
|
||||
wh_list = [BuildImage.get_text_size(x.name, font) for x in plugin_list]
|
||||
wh_list.append(BuildImage.get_text_size(menu_type, font))
|
||||
# sum_height = sum([x[1] for x in wh_list])
|
||||
if build_type == "VV":
|
||||
sum_height = 50 * len(plugin_list) + 10
|
||||
else:
|
||||
sum_height = (font_size + 6) * len(plugin_list) + 10
|
||||
max_width = max([x[0] for x in wh_list]) + 20
|
||||
bk = BuildImage(
|
||||
max_width + 40,
|
||||
sum_height + 50,
|
||||
font_size=30,
|
||||
color="#a7d1fc",
|
||||
font="CJGaoDeGuo.otf",
|
||||
)
|
||||
title_size = bk.getsize(menu_type)
|
||||
max_width = max_width if max_width > title_size[0] else title_size[0]
|
||||
B = BuildImage(
|
||||
max_width + 40,
|
||||
sum_height,
|
||||
font_size=font_size,
|
||||
color="white" if not idx % 2 else "black",
|
||||
)
|
||||
curr_h = 10
|
||||
if group := await GroupConsole.get_or_none(group_id=group_id):
|
||||
for i, plugin in enumerate(plugin_list):
|
||||
text_color = (255, 255, 255) if idx % 2 else (0, 0, 0)
|
||||
if f"{plugin.module}," in group.block_plugin:
|
||||
text_color = (252, 75, 13)
|
||||
pos = None
|
||||
# 禁用状态划线
|
||||
if (
|
||||
plugin.block_type in [BlockType.ALL, BlockType.GROUP]
|
||||
or f"{plugin.module}:super," in group.block_plugin
|
||||
):
|
||||
w = curr_h + int(B.getsize(plugin.name)[1] / 2) + 2
|
||||
pos = (
|
||||
7,
|
||||
w,
|
||||
B.getsize(plugin.name)[0] + 35,
|
||||
w,
|
||||
)
|
||||
if build_type == "VV":
|
||||
name_image = await self.build_name_image( # type: ignore
|
||||
max_width,
|
||||
plugin.name,
|
||||
"black" if not idx % 2 else "white",
|
||||
text_color,
|
||||
pos,
|
||||
)
|
||||
await B.paste(name_image, (0, curr_h), center_type="width")
|
||||
curr_h += name_image.h + 5
|
||||
else:
|
||||
await B.text((10, curr_h), f"{i + 1}.{plugin.name}", text_color)
|
||||
if pos:
|
||||
await B.line(pos, (236, 66, 7), 3)
|
||||
curr_h += font_size + 5
|
||||
if menu_type == "normal":
|
||||
menu_type = "功能"
|
||||
await bk.text((0, 14), menu_type, center_type="width")
|
||||
await bk.paste(B, (0, 50))
|
||||
await bk.transparent(2)
|
||||
# await bk.acircle_corner(point_list=['lt', 'rt'])
|
||||
self._image_list.append(bk)
|
||||
image_group, h = group_image(self._image_list)
|
||||
B = await build_sort_image(
|
||||
image_group,
|
||||
h,
|
||||
background_path=BACKGROUND_PATH,
|
||||
background_handle=lambda image: image.filter("GaussianBlur", 5),
|
||||
)
|
||||
w = 10
|
||||
h = 10
|
||||
for msg in [
|
||||
"目前支持的功能列表:",
|
||||
"可以通过 ‘帮助[功能名称]’ 来获取对应功能的使用方法",
|
||||
]:
|
||||
text = await BuildImage.build_text_image(msg, "HYWenHei-85W.ttf", 24)
|
||||
await B.paste(text, (w, h))
|
||||
h += 50
|
||||
if msg == "目前支持的功能列表:":
|
||||
w += 50
|
||||
text = await BuildImage.build_text_image(
|
||||
"注: 红字代表功能被群管理员禁用,红线代表功能正在维护",
|
||||
"HYWenHei-85W.ttf",
|
||||
24,
|
||||
(231, 74, 57),
|
||||
)
|
||||
await B.paste(
|
||||
text,
|
||||
(300, 10),
|
||||
)
|
||||
return B
|
||||
43
zhenxun/builtin_plugins/hooks/__init__.py
Normal file
43
zhenxun/builtin_plugins/hooks/__init__.py
Normal file
@ -0,0 +1,43 @@
|
||||
from pathlib import Path
|
||||
|
||||
import nonebot
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
|
||||
Config.add_plugin_config(
|
||||
"hook",
|
||||
"CHECK_NOTICE_INFO_CD",
|
||||
300,
|
||||
help="群检测,个人权限检测等各种检测提示信息cd",
|
||||
default_value=300,
|
||||
type=int,
|
||||
)
|
||||
|
||||
Config.add_plugin_config(
|
||||
"hook",
|
||||
"MALICIOUS_BAN_TIME",
|
||||
30,
|
||||
help="恶意命令触发检测触发后ban的时长(分钟)",
|
||||
default_value=30,
|
||||
type=int,
|
||||
)
|
||||
|
||||
Config.add_plugin_config(
|
||||
"hook",
|
||||
"MALICIOUS_CHECK_TIME",
|
||||
5,
|
||||
help="恶意命令触发检测规定时间内(秒)",
|
||||
default_value=5,
|
||||
type=int,
|
||||
)
|
||||
|
||||
Config.add_plugin_config(
|
||||
"hook",
|
||||
"MALICIOUS_BAN_COUNT",
|
||||
6,
|
||||
help="恶意命令触发检测最大触发次数",
|
||||
default_value=6,
|
||||
type=int,
|
||||
)
|
||||
|
||||
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
|
||||
61
zhenxun/builtin_plugins/hooks/ban_hook.py
Normal file
61
zhenxun/builtin_plugins/hooks/ban_hook.py
Normal file
@ -0,0 +1,61 @@
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.message import run_preprocessor
|
||||
from nonebot.typing import T_State
|
||||
from nonebot_plugin_saa import Mention, MessageFactory, Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.utils import FreqLimiter
|
||||
|
||||
Config.add_plugin_config(
|
||||
"hook",
|
||||
"BAN_RESULT",
|
||||
"才不会给你发消息.",
|
||||
help="对被ban用户发送的消息",
|
||||
)
|
||||
|
||||
_flmt = FreqLimiter(300)
|
||||
|
||||
|
||||
# 检查是否被ban
|
||||
@run_preprocessor
|
||||
async def _(
|
||||
matcher: Matcher, bot: Bot, event: Event, state: T_State, session: EventSession
|
||||
):
|
||||
if plugin := matcher.plugin:
|
||||
if metadata := plugin.metadata:
|
||||
extra = metadata.extra
|
||||
if extra.get("plugin_type") == PluginType.HIDDEN:
|
||||
return
|
||||
user_id = session.id1
|
||||
group_id = session.id3 or session.id2
|
||||
if user_id:
|
||||
ban_result = Config.get_config("hook", "BAN_RESULT")
|
||||
if user_id in bot.config.superusers:
|
||||
return
|
||||
if await BanConsole.is_ban(user_id) or await BanConsole.is_ban(
|
||||
user_id, group_id
|
||||
):
|
||||
time = await BanConsole.check_ban_time(user_id)
|
||||
if time == -1:
|
||||
time_str = "∞"
|
||||
else:
|
||||
time = abs(int(time))
|
||||
if time < 60:
|
||||
time_str = str(time) + " 秒"
|
||||
else:
|
||||
time_str = str(int(time / 60)) + " 分钟"
|
||||
if ban_result and _flmt.check(user_id):
|
||||
_flmt.start_cd(user_id)
|
||||
await MessageFactory(
|
||||
[
|
||||
Mention(user_id),
|
||||
Text(f"{ban_result}\n在..在 {time_str} 后才会理你喔"),
|
||||
]
|
||||
).send()
|
||||
raise IgnoredException("用户处于黑名单中")
|
||||
104
zhenxun/builtin_plugins/hooks/chkdsk_hook.py
Normal file
104
zhenxun/builtin_plugins/hooks/chkdsk_hook.py
Normal file
@ -0,0 +1,104 @@
|
||||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
from click import command
|
||||
from nonebot.adapters.onebot.v11 import ActionFailed, Bot, GroupMessageEvent
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.message import run_preprocessor
|
||||
from nonebot.typing import T_State
|
||||
from nonebot_plugin_alconna import Arparma
|
||||
from nonebot_plugin_saa import Mention, MessageFactory, Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
|
||||
malicious_check_time = Config.get_config("hook", "MALICIOUS_CHECK_TIME")
|
||||
malicious_ban_count = Config.get_config("hook", "MALICIOUS_BAN_COUNT")
|
||||
|
||||
if not malicious_check_time:
|
||||
raise ValueError("模块: [hook], 配置项: [MALICIOUS_CHECK_TIME] 为空或小于0")
|
||||
if not malicious_ban_count:
|
||||
raise ValueError("模块: [hook], 配置项: [MALICIOUS_BAN_COUNT] 为空或小于0")
|
||||
|
||||
|
||||
class BanCheckLimiter:
|
||||
"""
|
||||
恶意命令触发检测
|
||||
"""
|
||||
|
||||
def __init__(self, default_check_time: float = 5, default_count: int = 4):
|
||||
self.mint = defaultdict(int)
|
||||
self.mtime = defaultdict(float)
|
||||
self.default_check_time = default_check_time
|
||||
self.default_count = default_count
|
||||
|
||||
def add(self, key: str | int | float):
|
||||
if self.mint[key] == 1:
|
||||
self.mtime[key] = time.time()
|
||||
self.mint[key] += 1
|
||||
|
||||
def check(self, key: str | int | float) -> bool:
|
||||
if time.time() - self.mtime[key] > self.default_check_time:
|
||||
self.mtime[key] = time.time()
|
||||
self.mint[key] = 0
|
||||
return False
|
||||
if (
|
||||
self.mint[key] >= self.default_count
|
||||
and time.time() - self.mtime[key] < self.default_check_time
|
||||
):
|
||||
self.mtime[key] = time.time()
|
||||
self.mint[key] = 0
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
_blmt = BanCheckLimiter(
|
||||
malicious_check_time,
|
||||
malicious_ban_count,
|
||||
)
|
||||
|
||||
|
||||
# 恶意触发命令检测
|
||||
@run_preprocessor
|
||||
async def _(matcher: Matcher, bot: Bot, session: EventSession, state: T_State):
|
||||
if plugin := matcher.plugin:
|
||||
if metadata := plugin.metadata:
|
||||
extra = metadata.extra
|
||||
if extra.get("plugin_type") == PluginType.HIDDEN:
|
||||
return
|
||||
user_id = session.id1
|
||||
group_id = session.id3 or session.id2
|
||||
malicious_ban_time = Config.get_config("hook", "MALICIOUS_BAN_TIME")
|
||||
if not malicious_ban_time:
|
||||
raise ValueError("模块: [hook], 配置项: [MALICIOUS_BAN_TIME] 为空或小于0")
|
||||
if user_id:
|
||||
command = state["_prefix"]["raw_command"]
|
||||
if state["_alc_result"]:
|
||||
command = state["_alc_result"].source.command
|
||||
if command:
|
||||
if _blmt.check(f"{user_id}__{command}"):
|
||||
await BanConsole.ban(
|
||||
user_id, group_id, 9, malicious_ban_time * 60, bot.self_id
|
||||
)
|
||||
logger.info(
|
||||
f"触发了恶意触发检测: {matcher.plugin_name}",
|
||||
"HOOK",
|
||||
session=session,
|
||||
)
|
||||
await MessageFactory(
|
||||
[
|
||||
Mention(user_id),
|
||||
Text(f"检测到恶意触发命令,您将被封禁 30 分钟"),
|
||||
]
|
||||
).send()
|
||||
logger.debug(
|
||||
f"触发了恶意触发检测: {matcher.plugin_name}",
|
||||
"HOOK",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("检测到恶意触发命令")
|
||||
_blmt.add(f"{user_id}__{command}")
|
||||
46
zhenxun/builtin_plugins/hooks/withdraw_hook.py
Normal file
46
zhenxun/builtin_plugins/hooks/withdraw_hook.py
Normal file
@ -0,0 +1,46 @@
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.discord import Bot as DiscordBot
|
||||
from nonebot.adapters.dodo import Bot as DodoBot
|
||||
from nonebot.adapters.kaiheila import Bot as KaiheilaBot
|
||||
from nonebot.adapters.onebot.v11 import Bot as v11Bot
|
||||
from nonebot.adapters.onebot.v12 import Bot as v12Bot
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.message import run_postprocessor
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.utils import WithdrawManager
|
||||
|
||||
# TODO: 其他平台撤回消息
|
||||
|
||||
|
||||
# 消息撤回
|
||||
@run_postprocessor
|
||||
async def _(
|
||||
matcher: Matcher,
|
||||
exception: Optional[Exception],
|
||||
bot: Bot,
|
||||
):
|
||||
tasks = []
|
||||
for message_id in WithdrawManager._data:
|
||||
second = WithdrawManager._data[message_id]
|
||||
tasks.append(asyncio.ensure_future(_withdraw_message(bot, message_id, second)))
|
||||
WithdrawManager.remove(message_id)
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
|
||||
async def _withdraw_message(bot: Bot, message_id: str, time: int):
|
||||
await asyncio.sleep(time)
|
||||
logger.debug(f"撤回消息ID: {message_id}", "HOOK")
|
||||
if isinstance(bot, v11Bot):
|
||||
await bot.delete_msg(message_id=int(message_id))
|
||||
elif isinstance(bot, v12Bot):
|
||||
await bot.delete_message(message_id=message_id)
|
||||
elif isinstance(bot, DodoBot):
|
||||
pass
|
||||
elif isinstance(bot, KaiheilaBot):
|
||||
pass
|
||||
elif isinstance(bot, DiscordBot):
|
||||
pass
|
||||
@ -1,26 +1,15 @@
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
import nonebot
|
||||
from nonebot import get_loaded_plugins
|
||||
from nonebot.drivers import Driver
|
||||
from nonebot.plugin import Plugin
|
||||
from ruamel import yaml
|
||||
from ruamel.yaml import YAML, round_trip_dump, round_trip_load
|
||||
from ruamel.yaml.comments import CommentedMap
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.path_config import DATA_PATH
|
||||
from zhenxun.configs.utils import (
|
||||
BaseBlock,
|
||||
PluginExtraData,
|
||||
PluginSetting,
|
||||
RegisterConfig,
|
||||
)
|
||||
from zhenxun.configs.utils import PluginExtraData, PluginSetting
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.plugin_limit import PluginLimit
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginLimitType
|
||||
from zhenxun.utils.enum import PluginType
|
||||
|
||||
_yaml = YAML(pure=True)
|
||||
_yaml.allow_unicode = True
|
||||
@ -29,8 +18,11 @@ _yaml.indent = 2
|
||||
driver: Driver = nonebot.get_driver()
|
||||
|
||||
|
||||
def _handle_setting(
|
||||
plugin: Plugin, plugin_list: List[PluginInfo], limit_list: List[PluginLimit]
|
||||
async def _handle_setting(
|
||||
plugin: Plugin,
|
||||
plugin_list: list[PluginInfo],
|
||||
limit_list: list[PluginLimit],
|
||||
task_list: list[TaskInfo],
|
||||
):
|
||||
"""处理插件设置
|
||||
|
||||
@ -45,6 +37,8 @@ def _handle_setting(
|
||||
extra_data = PluginExtraData(**extra)
|
||||
logger.debug(f"{metadata.name}:{plugin.name} -> {extra}", "初始化插件数据")
|
||||
setting = extra_data.setting or PluginSetting()
|
||||
if metadata.type == "library":
|
||||
extra_data.plugin_type = PluginType.HIDDEN
|
||||
plugin_list.append(
|
||||
PluginInfo(
|
||||
module=plugin.name,
|
||||
@ -76,6 +70,16 @@ def _handle_setting(
|
||||
max_count=getattr(limit, "max_count", None),
|
||||
)
|
||||
)
|
||||
if extra_data.tasks:
|
||||
for task in extra_data.tasks:
|
||||
task_list.append(
|
||||
TaskInfo(
|
||||
module=task.module,
|
||||
name=task.name,
|
||||
status=task.status,
|
||||
run_time=task.run_time,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
@ -83,14 +87,15 @@ async def _():
|
||||
"""
|
||||
初始化插件数据配置
|
||||
"""
|
||||
plugin_list: List[PluginInfo] = []
|
||||
limit_list: List[PluginLimit] = []
|
||||
plugin_list: list[PluginInfo] = []
|
||||
limit_list: list[PluginLimit] = []
|
||||
task_list: list[TaskInfo] = []
|
||||
module2id = {}
|
||||
if module_list := await PluginInfo.all().values("id", "module_path"):
|
||||
module2id = {m["module_path"]: m["id"] for m in module_list}
|
||||
for plugin in get_loaded_plugins():
|
||||
if plugin.metadata:
|
||||
_handle_setting(plugin, plugin_list, limit_list)
|
||||
await _handle_setting(plugin, plugin_list, limit_list, task_list)
|
||||
create_list = []
|
||||
update_list = []
|
||||
for plugin in plugin_list:
|
||||
@ -124,3 +129,23 @@ async def _():
|
||||
limit_create.append(limit)
|
||||
if limit_create:
|
||||
await PluginLimit.bulk_create(limit_create, 10)
|
||||
if task_list:
|
||||
module_dict = {
|
||||
t[1]: t[0] for t in await TaskInfo.all().values_list("id", "module")
|
||||
}
|
||||
create_list = []
|
||||
update_list = []
|
||||
for task in task_list:
|
||||
if task.module not in module_list:
|
||||
create_list.append(task)
|
||||
else:
|
||||
task.id = module_dict[task.module]
|
||||
update_list.append(task)
|
||||
if create_list:
|
||||
await TaskInfo.bulk_create(create_list, 10)
|
||||
if update_list:
|
||||
await TaskInfo.bulk_update(
|
||||
update_list,
|
||||
["run_time", "status", "name"],
|
||||
10,
|
||||
)
|
||||
|
||||
240
zhenxun/builtin_plugins/nickname.py
Normal file
240
zhenxun/builtin_plugins/nickname.py
Normal file
@ -0,0 +1,240 @@
|
||||
import random
|
||||
from typing import Any, List
|
||||
|
||||
from nonebot import on_regex
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import Depends, RegexGroup
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot.rule import to_me
|
||||
from nonebot_plugin_alconna import Alconna, Option, UniMsg, on_alconna, store_true
|
||||
from nonebot_plugin_saa import Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
|
||||
|
||||
from zhenxun.configs.config import NICKNAME, Config
|
||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.models.friend_user import FriendUser
|
||||
from zhenxun.models.group_member_info import GroupInfoUser
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="昵称系统",
|
||||
description="区区昵称,才不想叫呢!",
|
||||
usage=f"""
|
||||
个人昵称,将替换{NICKNAME}称呼你的名称,群聊 与 私聊 昵称相互独立,全局昵称设置将更改您目前所有群聊中及私聊的昵称
|
||||
指令:
|
||||
以后叫我 [昵称]: 设置当前群聊/私聊的昵称
|
||||
全局昵称设置 [昵称]: 设置当前所有群聊和私聊的昵称
|
||||
{NICKNAME}我是谁
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.NORMAL,
|
||||
menu_type="商店",
|
||||
configs=[
|
||||
RegisterConfig(
|
||||
key="BLACK_WORD",
|
||||
value=["爸", "爹", "爷", "父"],
|
||||
help="昵称所屏蔽的关键词,已设置的昵称会被替换为 *,未设置的昵称会在设置时提示",
|
||||
default_value=None,
|
||||
type=List[str],
|
||||
)
|
||||
],
|
||||
).dict(),
|
||||
)
|
||||
|
||||
_nickname_matcher = on_regex(
|
||||
"(?:以后)?(?:叫我|请叫我|称呼我)(.*)",
|
||||
rule=to_me(),
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
_global_nickname_matcher = on_regex(
|
||||
"设置全局昵称(.*)", rule=to_me(), priority=5, block=True
|
||||
)
|
||||
|
||||
_matcher = on_alconna(
|
||||
Alconna(
|
||||
"nickname",
|
||||
Option("--name", action=store_true, help_text="用户昵称"),
|
||||
Option("--cancel", action=store_true, help_text="取消昵称"),
|
||||
),
|
||||
rule=to_me(),
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
|
||||
CALL_NAME = [
|
||||
"好啦好啦,我知道啦,{},以后就这么叫你吧",
|
||||
f"嗯嗯,{NICKNAME}" + "记住你的昵称了哦,{}",
|
||||
"好突然,突然要叫你昵称什么的...{}..",
|
||||
f"{NICKNAME}" + "会好好记住{}的,放心吧",
|
||||
"好..好.,那窝以后就叫你{}了.",
|
||||
]
|
||||
|
||||
REMIND = [
|
||||
"我肯定记得你啊,你是{}啊",
|
||||
"我不会忘记你的,你也不要忘记我!{}",
|
||||
f"哼哼,{NICKNAME}" + "记忆力可是很好的,{}",
|
||||
"嗯?你是失忆了嘛...{}..",
|
||||
f"不要小看{NICKNAME}" + "的记忆力啊!笨蛋{}!QAQ",
|
||||
"哎?{}..怎么了吗..突然这样问..",
|
||||
]
|
||||
|
||||
CANCEL = [
|
||||
f"呜..{NICKNAME}" + "睡一觉就会忘记的..和梦一样..{}",
|
||||
"窝知道了..{}..",
|
||||
f"是{NICKNAME}" + "哪里做的不好嘛..好吧..晚安{}",
|
||||
"呃,{},下次我绝对绝对绝对不会再忘记你!",
|
||||
"可..可恶!{}!太可恶了!呜",
|
||||
]
|
||||
|
||||
|
||||
def CheckNickname():
|
||||
"""
|
||||
检查名称是否合法
|
||||
"""
|
||||
|
||||
async def dependency(
|
||||
bot: Bot,
|
||||
matcher: Matcher,
|
||||
session: EventSession,
|
||||
message: UniMsg,
|
||||
reg_group: tuple[Any, ...] = RegexGroup(),
|
||||
):
|
||||
black_word = Config.get_config("nickname", "BLACK_WORD")
|
||||
(name,) = reg_group
|
||||
logger.debug(f"昵称检查: {name}", "昵称设置", session=session)
|
||||
if not name:
|
||||
await Text("叫你空白?叫你虚空?叫你无名??").finish(at_sender=True)
|
||||
if session.id1 in bot.config.superusers:
|
||||
logger.debug(
|
||||
f"超级用户设置昵称, 跳过合法检测: {name}", "昵称设置", session=session
|
||||
)
|
||||
return
|
||||
if len(name) > 20:
|
||||
await Text("昵称可不能超过20个字!").finish(at_sender=True)
|
||||
if name in bot.config.nickname:
|
||||
await Text("笨蛋!休想占用我的名字! #").finish(at_sender=True)
|
||||
if black_word:
|
||||
for x in name:
|
||||
if x in black_word:
|
||||
logger.debug("昵称设置禁止字符: [{x}]", "昵称设置", session=session)
|
||||
await Text(f"字符 [{x}] 为禁止字符!").finish(at_sender=True)
|
||||
for word in black_word:
|
||||
if word in name:
|
||||
logger.debug(
|
||||
"昵称设置禁止字符: [{word}]", "昵称设置", session=session
|
||||
)
|
||||
await Text(f"字符 [{x}] 为禁止字符!").finish(at_sender=True)
|
||||
|
||||
return Depends(dependency)
|
||||
|
||||
|
||||
@_nickname_matcher.handle(parameterless=[CheckNickname()])
|
||||
async def _(
|
||||
session: EventSession,
|
||||
user_info: UserInfo = EventUserInfo(),
|
||||
reg_group: tuple[Any, ...] = RegexGroup(),
|
||||
):
|
||||
if session.id1:
|
||||
(name,) = reg_group
|
||||
if len(name) < 5:
|
||||
if random.random() < 0.3:
|
||||
name = "~".join(name)
|
||||
if gid := session.id3 or session.id2:
|
||||
await GroupInfoUser.set_user_nickname(
|
||||
session.id1,
|
||||
gid,
|
||||
name,
|
||||
user_info.user_displayname
|
||||
or user_info.user_remark
|
||||
or user_info.user_name,
|
||||
session.platform,
|
||||
)
|
||||
logger.info(f"设置群昵称成功: {name}", "昵称设置", session=session)
|
||||
await Text(random.choice(CALL_NAME).format(name)).finish(reply=True)
|
||||
else:
|
||||
await FriendUser.set_user_nickname(
|
||||
session.id1,
|
||||
name,
|
||||
user_info.user_displayname
|
||||
or user_info.user_remark
|
||||
or user_info.user_name,
|
||||
session.platform,
|
||||
)
|
||||
logger.info(f"设置私聊昵称成功: {name}", "昵称设置", session=session)
|
||||
await Text(random.choice(CALL_NAME).format(name)).finish(reply=True)
|
||||
await Text("用户id为空...").send()
|
||||
|
||||
|
||||
@_global_nickname_matcher.handle(parameterless=[CheckNickname()])
|
||||
async def _(
|
||||
session: EventSession,
|
||||
user_info: UserInfo = EventUserInfo(),
|
||||
reg_group: tuple[Any, ...] = RegexGroup(),
|
||||
):
|
||||
if session.id1:
|
||||
(name,) = reg_group
|
||||
await FriendUser.set_user_nickname(
|
||||
session.id1,
|
||||
name,
|
||||
user_info.user_displayname or user_info.user_remark or user_info.user_name,
|
||||
session.platform,
|
||||
)
|
||||
await GroupInfoUser.filter(user_id=session.id1).update(nickname=name)
|
||||
logger.info(f"设置全局昵称成功: {name}", "设置全局昵称", session=session)
|
||||
await Text(random.choice(CALL_NAME).format(name)).finish(reply=True)
|
||||
await Text("用户id为空...").send()
|
||||
|
||||
|
||||
@_matcher.assign("name")
|
||||
async def _(session: EventSession, user_info: UserInfo = EventUserInfo()):
|
||||
if session.id1:
|
||||
if gid := session.id3 or session.id2:
|
||||
nickname = await GroupInfoUser.get_user_nickname(session.id1, gid)
|
||||
card = user_info.user_displayname or user_info.user_name
|
||||
else:
|
||||
nickname = await FriendUser.get_user_nickname(session.id1)
|
||||
card = user_info.user_name
|
||||
if nickname:
|
||||
await Text(random.choice(REMIND).format(nickname)).finish(reply=True)
|
||||
else:
|
||||
await Text(
|
||||
random.choice(
|
||||
[
|
||||
"没..没有昵称嘛,{}",
|
||||
"啊,你是{}啊,我想叫你的昵称!",
|
||||
"是{}啊,有什么事吗?",
|
||||
"你是{}?",
|
||||
]
|
||||
).format(card)
|
||||
).finish(reply=True)
|
||||
await Text("用户id为空...").send()
|
||||
|
||||
|
||||
@_matcher.assign("cancel")
|
||||
async def _(bot: Bot, session: EventSession, user_info: UserInfo = EventUserInfo()):
|
||||
if session.id1:
|
||||
gid = session.id3 or session.id2
|
||||
if gid:
|
||||
nickname = await GroupInfoUser.get_user_nickname(session.id1, gid)
|
||||
else:
|
||||
nickname = await FriendUser.get_user_nickname(session.id1)
|
||||
if nickname:
|
||||
await Text(random.choice(CANCEL).format(nickname)).send(reply=True)
|
||||
if gid:
|
||||
await GroupInfoUser.set_user_nickname(session.id1, gid, "")
|
||||
else:
|
||||
await FriendUser.set_user_nickname(session.id1, "")
|
||||
await BanConsole.ban(session.id1, gid, 9, 60, bot.self_id)
|
||||
return
|
||||
else:
|
||||
await Text("你在做梦吗?你没有昵称啊").finish(reply=True)
|
||||
await Text("用户id为空...").send()
|
||||
297
zhenxun/builtin_plugins/platform/qq/group_handle.py
Normal file
297
zhenxun/builtin_plugins/platform/qq/group_handle.py
Normal file
@ -0,0 +1,297 @@
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
import nonebot
|
||||
import ujson as json
|
||||
from nonebot import on_notice, on_request
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.onebot.v11 import (
|
||||
GroupDecreaseNoticeEvent,
|
||||
GroupIncreaseNoticeEvent,
|
||||
)
|
||||
from nonebot.adapters.onebot.v12 import (
|
||||
GroupMemberDecreaseEvent,
|
||||
GroupMemberIncreaseEvent,
|
||||
)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_saa import Image, Mention, MessageFactory, Text
|
||||
|
||||
from zhenxun.configs.config import NICKNAME, Config
|
||||
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
|
||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task
|
||||
from zhenxun.models.fg_request import FgRequest
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.group_member_info import GroupInfoUser
|
||||
from zhenxun.models.level_user import LevelUser
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType, RequestHandleType, RequestType
|
||||
from zhenxun.utils.utils import FreqLimiter
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="QQ群事件处理",
|
||||
description="群事件处理",
|
||||
usage="",
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.HIDDEN,
|
||||
configs=[
|
||||
RegisterConfig(
|
||||
module="invite_manager",
|
||||
key="message",
|
||||
value=f"请不要未经同意就拉{NICKNAME}入群!告辞!",
|
||||
help="强制拉群后进群回复的内容",
|
||||
),
|
||||
RegisterConfig(
|
||||
module="invite_manager",
|
||||
key="flag",
|
||||
value=True,
|
||||
help="强制拉群后进群回复的内容",
|
||||
default_value=True,
|
||||
type=bool,
|
||||
),
|
||||
RegisterConfig(
|
||||
module="invite_manager",
|
||||
key="welcome_msg_cd",
|
||||
value=5,
|
||||
help="群欢迎消息cd",
|
||||
default_value=5,
|
||||
type=int,
|
||||
),
|
||||
RegisterConfig(
|
||||
module="_task",
|
||||
key="DEFAULT_GROUP_WELCOME",
|
||||
value=True,
|
||||
help="被动 进群欢迎 进群默认开关状态",
|
||||
default_value=True,
|
||||
type=bool,
|
||||
),
|
||||
RegisterConfig(
|
||||
module="_task",
|
||||
key="DEFAULT_REFUND_GROUP_REMIND",
|
||||
value=True,
|
||||
help="被动 退群提醒 进群默认开关状态",
|
||||
default_value=True,
|
||||
type=bool,
|
||||
),
|
||||
],
|
||||
tasks=[
|
||||
Task(module="group_welcome", name="进群欢迎"),
|
||||
Task(module="refund_group_remind", name="退群提醒"),
|
||||
],
|
||||
).dict(),
|
||||
)
|
||||
|
||||
|
||||
superuser = nonebot.get_driver().config.platform_superusers["qq"][0]
|
||||
|
||||
base_config = Config.get("invite_manager")
|
||||
|
||||
|
||||
limit_cd = base_config.get("welcome_msg_cd")
|
||||
|
||||
_flmt = FreqLimiter(limit_cd)
|
||||
|
||||
|
||||
group_increase_handle = on_notice(priority=1, block=False)
|
||||
"""群员增加处理"""
|
||||
group_decrease_handle = on_notice(priority=1, block=False)
|
||||
"""群员减少处理"""
|
||||
add_group = on_request(priority=1, block=False)
|
||||
"""加群同意请求"""
|
||||
|
||||
|
||||
@group_increase_handle.handle()
|
||||
async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent):
|
||||
user_id = str(event.user_id)
|
||||
group_id = str(event.group_id)
|
||||
if user_id == bot.self_id:
|
||||
"""新成员为bot本身"""
|
||||
group = await GroupConsole.get_or_none(group_id=group_id)
|
||||
if (not group or group.group_flag == 0) and base_config.get("flag"):
|
||||
"""群聊不存在或被强制拉群,退出该群"""
|
||||
try:
|
||||
if result_msg := base_config.get("message"):
|
||||
await bot.send_group_msg(
|
||||
group_id=event.group_id, message=result_msg
|
||||
)
|
||||
await bot.set_group_leave(group_id=event.group_id)
|
||||
await bot.send_private_msg(
|
||||
user_id=int(superuser),
|
||||
message=f"触发强制入群保护,已成功退出群聊 {group_id}...",
|
||||
)
|
||||
logger.info(
|
||||
f"强制拉群或未有群信息,退出群聊成功",
|
||||
"入群检测",
|
||||
group_id=event.group_id,
|
||||
)
|
||||
if req := await FgRequest.get_or_none(group_id=group_id):
|
||||
req.handle_type = RequestHandleType.IGNORE
|
||||
await req.save(update_fields=["handle_type"])
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"强制拉群或未有群信息,退出群聊失败",
|
||||
"入群检测",
|
||||
group_id=event.group_id,
|
||||
e=e,
|
||||
)
|
||||
await bot.send_private_msg(
|
||||
user_id=int(superuser),
|
||||
message=f"触发强制入群保护,退出群聊 {event.group_id} 失败...",
|
||||
)
|
||||
elif group_id not in await GroupConsole.all().values_list(
|
||||
"group_id", flat=True
|
||||
):
|
||||
"""默认群功能开关"""
|
||||
block_plugin = ""
|
||||
if plugin_list := await PluginInfo.filter(default_status=False).all():
|
||||
for plugin in plugin_list:
|
||||
block_plugin += f"{plugin.module},"
|
||||
group_info = await bot.get_group_info(group_id=event.group_id)
|
||||
await GroupConsole.create(
|
||||
group_id=group_info["group_id"],
|
||||
group_name=group_info["group_name"],
|
||||
max_member_count=group_info["max_member_count"],
|
||||
member_count=group_info["member_count"],
|
||||
group_flag=1,
|
||||
block_plugin=block_plugin,
|
||||
platform="qq",
|
||||
)
|
||||
admin_default_auth = Config.get_config(
|
||||
"admin_bot_manage", "ADMIN_DEFAULT_AUTH"
|
||||
)
|
||||
# 即刻刷新权限
|
||||
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"], group_id
|
||||
)
|
||||
and admin_default_auth is not None
|
||||
):
|
||||
await LevelUser.set_level(
|
||||
user_info["user_id"],
|
||||
user_info["group_id"],
|
||||
admin_default_auth,
|
||||
)
|
||||
logger.debug(
|
||||
f"添加默认群管理员权限: {admin_default_auth}",
|
||||
"入群检测",
|
||||
session=user_info["user_id"],
|
||||
group_id=user_info["group_id"],
|
||||
)
|
||||
if str(user_info["user_id"]) in bot.config.superusers:
|
||||
await LevelUser.set_level(
|
||||
user_info["user_id"], user_info["group_id"], 9
|
||||
)
|
||||
logger.debug(
|
||||
f"添加超级用户权限: 9",
|
||||
"入群检测",
|
||||
session=user_info["user_id"],
|
||||
group_id=user_info["group_id"],
|
||||
)
|
||||
else:
|
||||
join_time = datetime.now()
|
||||
user_info = await bot.get_group_member_info(
|
||||
group_id=event.group_id, user_id=event.user_id
|
||||
)
|
||||
await GroupInfoUser.update_or_create(
|
||||
user_id=str(user_info["user_id"]),
|
||||
group_id=str(user_info["group_id"]),
|
||||
defaults={"user_name": user_info["nickname"], "user_join_time": join_time},
|
||||
)
|
||||
logger.info(f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新成功")
|
||||
|
||||
if _flmt.check(group_id):
|
||||
"""群欢迎消息"""
|
||||
_flmt.start_cd(group_id)
|
||||
path = DATA_PATH / "welcome_message" / "qq" / f"{group_id}"
|
||||
data = json.load((path / "text.json").open())
|
||||
message = data["message"]
|
||||
msg_split = re.split(r"\[image:\d+\]", message)
|
||||
msg_list = []
|
||||
if data["at"]:
|
||||
msg_list.append(Mention(user_id))
|
||||
for i, text in enumerate(msg_split):
|
||||
msg_list.append(Text(text))
|
||||
img_file = path / f"{i}.png"
|
||||
if img_file.exists():
|
||||
msg_list.append(Image(img_file))
|
||||
if GroupConsole.is_block_task(group_id, "group_welcome"):
|
||||
logger.info(f"发送群欢迎消息...", "入群检测", group_id=group_id)
|
||||
if msg_list:
|
||||
await MessageFactory(msg_list).send()
|
||||
else:
|
||||
await MessageFactory(
|
||||
[
|
||||
Text("新人快跑啊!!本群现状↓(快使用自定义!)"),
|
||||
Image(
|
||||
IMAGE_PATH
|
||||
/ "qxz"
|
||||
/ random.choice(os.listdir(IMAGE_PATH / "qxz"))
|
||||
),
|
||||
]
|
||||
).send()
|
||||
|
||||
|
||||
@group_decrease_handle.handle()
|
||||
async def _(bot: Bot, event: GroupDecreaseNoticeEvent | GroupMemberDecreaseEvent):
|
||||
if event.sub_type == "kick_me":
|
||||
"""踢出Bot"""
|
||||
group_id = event.group_id
|
||||
operator_id = event.operator_id
|
||||
if user := await GroupInfoUser.get_or_none(
|
||||
user_id=str(event.operator_id), group_id=str(event.group_id)
|
||||
):
|
||||
operator_name = user.user_name
|
||||
else:
|
||||
operator_name = "None"
|
||||
group = await GroupConsole.filter(group_id=str(group_id)).first()
|
||||
group_name = group.group_name if group else ""
|
||||
coffee = int(list(bot.config.superusers)[0])
|
||||
await bot.send_private_msg(
|
||||
user_id=coffee,
|
||||
message=f"****呜..一份踢出报告****\n"
|
||||
f"我被 {operator_name}({operator_id})\n"
|
||||
f"踢出了 {group_name}({group_id})\n"
|
||||
f"日期:{str(datetime.now()).split('.')[0]}",
|
||||
)
|
||||
return
|
||||
if str(event.user_id) == bot.self_id:
|
||||
"""踢出Bot"""
|
||||
await GroupConsole.filter(group_id=str(event.group_id)).delete()
|
||||
return
|
||||
if user := await GroupInfoUser.get_or_none(
|
||||
user_id=str(event.user_id), group_id=str(event.group_id)
|
||||
):
|
||||
user_name = user.user_name
|
||||
else:
|
||||
user_name = f"{event.user_id}"
|
||||
await GroupInfoUser.filter(
|
||||
user_id=str(event.user_id), group_id=str(event.group_id)
|
||||
).delete()
|
||||
logger.info(
|
||||
f"名称: {user_name} 退出群聊",
|
||||
"group_decrease_handle",
|
||||
session=event.user_id,
|
||||
group_id=event.group_id,
|
||||
)
|
||||
result = ""
|
||||
if event.sub_type == "leave":
|
||||
result = f"{user_name}离开了我们..."
|
||||
if event.sub_type == "kick":
|
||||
operator = await bot.get_group_member_info(
|
||||
user_id=event.operator_id, group_id=event.group_id
|
||||
)
|
||||
operator_name = operator["card"] if operator["card"] else operator["nickname"]
|
||||
result = f"{user_name} 被 {operator_name} 送走了."
|
||||
if GroupConsole.is_block_task(str(event.group_id), "refund_group_remind"):
|
||||
await group_decrease_handle.send(f"{result}")
|
||||
@ -2,7 +2,8 @@ import time
|
||||
from datetime import datetime
|
||||
from typing import Dict
|
||||
|
||||
from nonebot import on_message, on_request
|
||||
import nonebot
|
||||
from nonebot import drivers, on_message, on_request
|
||||
from nonebot.adapters.onebot.v11 import (
|
||||
ActionFailed,
|
||||
Bot,
|
||||
@ -18,7 +19,7 @@ from zhenxun.configs.config import NICKNAME, Config
|
||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||
from zhenxun.models.fg_request import FgRequest
|
||||
from zhenxun.models.friend_user import FriendUser
|
||||
from zhenxun.models.group_info import GroupInfo
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType, RequestType
|
||||
|
||||
@ -26,7 +27,7 @@ base_config = Config.get("invite_manager")
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="记录请求",
|
||||
description="自定义群欢迎消息",
|
||||
description="记录 好友/群组 请求",
|
||||
usage="",
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
@ -61,6 +62,8 @@ class Timer:
|
||||
cls.data = {k: v for k, v in cls.data.items() if v - now < 5 * 60}
|
||||
|
||||
|
||||
# TODO: 其他平台请求
|
||||
|
||||
friend_req = on_request(priority=5, block=True)
|
||||
group_req = on_request(priority=5, block=True)
|
||||
_t = on_message(priority=999, block=False, rule=lambda: False)
|
||||
@ -69,13 +72,14 @@ _t = on_message(priority=999, block=False, rule=lambda: False)
|
||||
@friend_req.handle()
|
||||
async def _(bot: Bot, event: FriendRequestEvent, session: EventSession):
|
||||
if event.user_id and Timer.check(event.user_id):
|
||||
superuser = nonebot.get_driver().config.platform_superusers["qq"][0]
|
||||
logger.debug(f"收录好友请求...", "好友请求", target=event.user_id)
|
||||
user = await bot.get_stranger_info(user_id=event.user_id)
|
||||
nickname = user["nickname"]
|
||||
# sex = user["sex"]
|
||||
# age = str(user["age"])
|
||||
comment = event.comment
|
||||
superuser = int(list(bot.config.superusers)[0])
|
||||
superuser = int(superuser)
|
||||
await Text(
|
||||
f"*****一份好友申请*****\n"
|
||||
f"昵称:{nickname}({event.user_id})\n"
|
||||
@ -84,7 +88,11 @@ async def _(bot: Bot, event: FriendRequestEvent, session: EventSession):
|
||||
f"备注:{event.comment}"
|
||||
).send_to(target=TargetQQPrivate(user_id=superuser), bot=bot)
|
||||
if base_config.get("AUTO_ADD_FRIEND"):
|
||||
logger.debug(f"已开启好友请求自动同意,成功通过该请求", "好友请求", target=event.user_id)
|
||||
logger.debug(
|
||||
f"已开启好友请求自动同意,成功通过该请求",
|
||||
"好友请求",
|
||||
target=event.user_id,
|
||||
)
|
||||
await bot.set_friend_add_request(flag=event.flag, approve=True)
|
||||
await FriendUser.create(
|
||||
user_id=str(user["user_id"]), user_name=user["nickname"]
|
||||
@ -119,7 +127,7 @@ async def _(bot: Bot, event: GroupRequestEvent, session: EventSession):
|
||||
flag=event.flag, sub_type="invite", approve=True
|
||||
)
|
||||
group_info = await bot.get_group_info(group_id=event.group_id)
|
||||
await GroupInfo.update_or_create(
|
||||
await GroupConsole.update_or_create(
|
||||
group_id=str(group_info["group_id"]),
|
||||
defaults={
|
||||
"group_name": group_info["group_name"],
|
||||
|
||||
5
zhenxun/builtin_plugins/scheduler/__init__.py
Normal file
5
zhenxun/builtin_plugins/scheduler/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
import nonebot
|
||||
|
||||
nonebot.load_plugins(str(Path(__file__).parent.resolve()))
|
||||
62
zhenxun/builtin_plugins/scheduler/auto_backup.py
Normal file
62
zhenxun/builtin_plugins/scheduler/auto_backup.py
Normal file
@ -0,0 +1,62 @@
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
Config.add_plugin_config(
|
||||
"_backup",
|
||||
"BACKUP_FLAG",
|
||||
True,
|
||||
help="是否开启文件备份",
|
||||
default_value=True,
|
||||
type=bool,
|
||||
)
|
||||
|
||||
Config.add_plugin_config(
|
||||
"_backup",
|
||||
"BACKUP_DIR_OR_FILE",
|
||||
[
|
||||
"data/black_word",
|
||||
"data/configs",
|
||||
"data/statistics",
|
||||
"data/word_bank",
|
||||
"data/manager",
|
||||
"configs",
|
||||
],
|
||||
help="备份的文件夹或文件",
|
||||
default_value=[],
|
||||
type=list[str],
|
||||
)
|
||||
|
||||
|
||||
# 自动备份
|
||||
@scheduler.scheduled_job(
|
||||
"cron",
|
||||
hour=3,
|
||||
minute=25,
|
||||
)
|
||||
async def _():
|
||||
if Config.get_config("_backup", "BACKUP_FLAG"):
|
||||
_backup_path = Path() / "backup"
|
||||
_backup_path.mkdir(exist_ok=True, parents=True)
|
||||
if backup_dir_or_file := Config.get_config("_backup", "BACKUP_DIR_OR_FILE"):
|
||||
for path_file in backup_dir_or_file:
|
||||
try:
|
||||
path = Path(path_file)
|
||||
_p = _backup_path / path_file
|
||||
if path.exists():
|
||||
if path.is_dir():
|
||||
if _p.exists():
|
||||
shutil.rmtree(_p, ignore_errors=True)
|
||||
shutil.copytree(path_file, _p)
|
||||
else:
|
||||
if _p.exists():
|
||||
_p.unlink()
|
||||
shutil.copy(path_file, _p)
|
||||
logger.debug(f"已完成自动备份:{path_file}", "自动备份")
|
||||
except Exception as e:
|
||||
logger.error(f"自动备份文件 {path_file} 发生错误", "自动备份", e=e)
|
||||
logger.info("自动备份成功...", "自动备份")
|
||||
68
zhenxun/builtin_plugins/scheduler/auto_update_group.py
Normal file
68
zhenxun/builtin_plugins/scheduler/auto_update_group.py
Normal file
@ -0,0 +1,68 @@
|
||||
import nonebot
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
from zhenxun.models.friend_user import FriendUser
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
# TODO: 其他平台更新
|
||||
|
||||
|
||||
# 自动更新群组信息
|
||||
@scheduler.scheduled_job(
|
||||
"cron",
|
||||
hour=3,
|
||||
minute=1,
|
||||
)
|
||||
async def _():
|
||||
bots = nonebot.get_bots()
|
||||
_used_group = []
|
||||
for bot in bots.values():
|
||||
try:
|
||||
group_list = await bot.get_group_list()
|
||||
gl = [g["group_id"] for g in group_list if g["group_id"] not in _used_group]
|
||||
for g in gl:
|
||||
_used_group.append(g)
|
||||
group_info = await bot.get_group_info(group_id=g)
|
||||
await GroupConsole.update_or_create(
|
||||
group_id=str(group_info["group_id"]),
|
||||
defaults={
|
||||
"group_name": group_info["group_name"],
|
||||
"max_member_count": group_info["max_member_count"],
|
||||
"member_count": group_info["member_count"],
|
||||
"group_flag": 1,
|
||||
},
|
||||
)
|
||||
logger.debug("自动更新群组信息成功", "自动更新群组", group_id=g)
|
||||
except Exception as e:
|
||||
logger.error(f"Bot: {bot.self_id} 自动更新群组信息", e=e)
|
||||
logger.info("自动更新群组成员信息成功...")
|
||||
|
||||
|
||||
# 自动更新好友信息
|
||||
@scheduler.scheduled_job(
|
||||
"cron",
|
||||
hour=3,
|
||||
minute=1,
|
||||
)
|
||||
async def _():
|
||||
bots = nonebot.get_bots()
|
||||
for key in bots:
|
||||
try:
|
||||
bot = bots[key]
|
||||
fl = await bot.get_friend_list()
|
||||
for f in fl:
|
||||
if FriendUser.exists(user_id=str(f["user_id"])):
|
||||
await FriendUser.create(
|
||||
user_id=str(f["user_id"]), user_name=f["nickname"]
|
||||
)
|
||||
logger.debug(
|
||||
f"更新好友信息成功", "自动更新好友", session=f["user_id"]
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
f"好友信息已存在", "自动更新好友", session=f["user_id"]
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"自动更新好友信息错误", "自动更新好友", e=e)
|
||||
logger.info("自动更新好友信息成功...")
|
||||
28
zhenxun/builtin_plugins/scheduler/morning.py
Normal file
28
zhenxun/builtin_plugins/scheduler/morning.py
Normal file
@ -0,0 +1,28 @@
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
# TODO: 消息发送
|
||||
|
||||
# # 早上好
|
||||
# @scheduler.scheduled_job(
|
||||
# "cron",
|
||||
# hour=6,
|
||||
# minute=1,
|
||||
# )
|
||||
# async def _():
|
||||
# img = image(IMAGE_PATH / "zhenxun" / "zao.jpg")
|
||||
# await broadcast_group("[[_task|zwa]]早上好" + img, log_cmd="被动早晚安")
|
||||
# logger.info("每日早安发送...")
|
||||
|
||||
|
||||
# # 睡觉了
|
||||
# @scheduler.scheduled_job(
|
||||
# "cron",
|
||||
# hour=23,
|
||||
# minute=59,
|
||||
# )
|
||||
# async def _():
|
||||
# img = image(IMAGE_PATH / "zhenxun" / "sleep.jpg")
|
||||
# await broadcast_group(
|
||||
# f"[[_task|zwa]]{NICKNAME}要睡觉了,你们也要早点睡呀" + img, log_cmd="被动早晚安"
|
||||
# )
|
||||
# logger.info("每日晚安发送...")
|
||||
59
zhenxun/builtin_plugins/scripts.py
Normal file
59
zhenxun/builtin_plugins/scripts.py
Normal file
@ -0,0 +1,59 @@
|
||||
from asyncio.exceptions import TimeoutError
|
||||
|
||||
import nonebot
|
||||
import ujson as json
|
||||
from nonebot.drivers import Driver
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
from zhenxun.configs.path_config import TEXT_PATH
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.http_utils import AsyncHttpx
|
||||
|
||||
driver: Driver = nonebot.get_driver()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def update_city():
|
||||
"""
|
||||
部分插件需要中国省份城市
|
||||
这里直接更新,避免插件内代码重复
|
||||
"""
|
||||
china_city = TEXT_PATH / "china_city.json"
|
||||
data = {}
|
||||
if not china_city.exists():
|
||||
try:
|
||||
logger.debug("开始更新城市列表...")
|
||||
res = await AsyncHttpx.get(
|
||||
"http://www.weather.com.cn/data/city3jdata/china.html", timeout=5
|
||||
)
|
||||
res.encoding = "utf8"
|
||||
provinces_data = json.loads(res.text)
|
||||
for province in provinces_data.keys():
|
||||
data[provinces_data[province]] = []
|
||||
res = await AsyncHttpx.get(
|
||||
f"http://www.weather.com.cn/data/city3jdata/provshi/{province}.html",
|
||||
timeout=5,
|
||||
)
|
||||
res.encoding = "utf8"
|
||||
city_data = json.loads(res.text)
|
||||
for city in city_data.keys():
|
||||
data[provinces_data[province]].append(city_data[city])
|
||||
with open(china_city, "w", encoding="utf8") as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
logger.info("自动更新城市列表完成...")
|
||||
except TimeoutError as e:
|
||||
logger.warning("自动更新城市列表超时...", e=e)
|
||||
except ValueError as e:
|
||||
logger.warning("自动城市列表失败...", e=e)
|
||||
except Exception as e:
|
||||
logger.error(f"自动城市列表未知错误...", e=e)
|
||||
|
||||
|
||||
# 自动更新城市列表
|
||||
@scheduler.scheduled_job(
|
||||
"cron",
|
||||
hour=6,
|
||||
minute=1,
|
||||
)
|
||||
async def _():
|
||||
await update_city()
|
||||
102
zhenxun/builtin_plugins/shop/__init__.py
Normal file
102
zhenxun/builtin_plugins/shop/__init__.py
Normal file
@ -0,0 +1,102 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import Alconna, Args, Arparma, Subcommand, on_alconna
|
||||
from nonebot_plugin_saa import Image, Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
|
||||
|
||||
from zhenxun.configs.utils import BaseBlock, PluginExtraData
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import BlockType, PluginType
|
||||
|
||||
from ._data_source import ShopManage
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="商店",
|
||||
description="商店系统[金币回收计划]",
|
||||
usage="""
|
||||
商品操作
|
||||
指令:
|
||||
添加商品 name:[名称] price:[价格] des:[描述] ?discount:[折扣](小数) ?limit_time:[限时时间](小时)
|
||||
删除商品 [名称或序号]
|
||||
修改商品 name:[名称或序号] price:[价格] des:[描述] discount:[折扣] limit_time:[限时]
|
||||
示例:添加商品 name:萝莉酒杯 price:9999 des:普通的酒杯,但是里面.. discount:0.4 limit_time:90
|
||||
示例:添加商品 name:可疑的药 price:5 des:效果未知
|
||||
示例:删除商品 2
|
||||
示例:修改商品 name:1 price:900 修改序号为1的商品的价格为900
|
||||
* 修改商品只需添加需要值即可 *
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.NORMAL,
|
||||
menu_type="商店",
|
||||
limits=[BaseBlock(check_type=BlockType.GROUP)],
|
||||
).dict(),
|
||||
)
|
||||
|
||||
# TODO: 修改操作,shortcut
|
||||
|
||||
_matcher = on_alconna(
|
||||
Alconna(
|
||||
"shop",
|
||||
Subcommand("my-cost", help_text="我的金币"),
|
||||
Subcommand("my-props", help_text="我的道具"),
|
||||
Subcommand("buy", Args["name", str]["num", int, 1], help_text="购买道具"),
|
||||
Subcommand("use", Args["name", str]["num?", int, 1], help_text="使用道具"),
|
||||
),
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
|
||||
@_matcher.assign("$main")
|
||||
async def _(session: EventSession, arparma: Arparma):
|
||||
image = await ShopManage.build_shop_image()
|
||||
logger.info("查看商店", arparma.header_result, session=session)
|
||||
await Image(image.pic2bs4()).send()
|
||||
|
||||
|
||||
@_matcher.assign("my-cost")
|
||||
async def _(session: EventSession, arparma: Arparma):
|
||||
if session.id1:
|
||||
logger.info("查看金币", arparma.header_result, session=session)
|
||||
gold = await ShopManage.my_cost(session.id1, session.platform)
|
||||
await Text(f"你的当前余额: {gold}").send(reply=True)
|
||||
else:
|
||||
await Text(f"用户id为空...").send(reply=True)
|
||||
|
||||
|
||||
@_matcher.assign("my-props")
|
||||
async def _(
|
||||
session: EventSession, arparma: Arparma, user_info: UserInfo = EventUserInfo()
|
||||
):
|
||||
if session.id1:
|
||||
logger.info("查看道具", arparma.header_result, session=session)
|
||||
if image := await ShopManage.my_props(
|
||||
session.id1,
|
||||
user_info.user_displayname or user_info.user_name,
|
||||
session.platform,
|
||||
):
|
||||
await Image(image.pic2bs4()).finish(reply=True)
|
||||
return await Text(f"你的道具为空捏...").send(reply=True)
|
||||
else:
|
||||
await Text(f"用户id为空...").send(reply=True)
|
||||
|
||||
|
||||
@_matcher.assign("buy")
|
||||
async def _(session: EventSession, arparma: Arparma, name: str, num: int):
|
||||
if session.id1:
|
||||
logger.info(
|
||||
f"购买道具 {name}, 数量: {num}",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
result = await ShopManage.buy_prop(session.id1, name, num, session.platform)
|
||||
await Text(result).send(reply=True)
|
||||
else:
|
||||
await Text(f"用户id为空...").send(reply=True)
|
||||
|
||||
|
||||
@_matcher.assign("use")
|
||||
async def _(session: EventSession, arparma: Arparma, name: str, num: int):
|
||||
pass
|
||||
319
zhenxun/builtin_plugins/shop/_data_source.py
Normal file
319
zhenxun/builtin_plugins/shop/_data_source.py
Normal file
@ -0,0 +1,319 @@
|
||||
import time
|
||||
from typing import Dict
|
||||
|
||||
from zhenxun.configs.path_config import IMAGE_PATH
|
||||
from zhenxun.models.goods_info import GoodsInfo
|
||||
from zhenxun.models.user_console import UserConsole
|
||||
from zhenxun.models.user_gold_log import UserGoldLog
|
||||
from zhenxun.models.user_props_log import UserPropsLog
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import GoldHandle, PropHandle
|
||||
from zhenxun.utils.image_utils import BuildImage, ImageTemplate, text2image
|
||||
|
||||
ICON_PATH = IMAGE_PATH / "shop_icon"
|
||||
|
||||
|
||||
class ShopManage:
|
||||
|
||||
@classmethod
|
||||
async def buy_prop(
|
||||
cls, user_id: str, name: str, num: int = 1, platform: str | None = None
|
||||
) -> str:
|
||||
if name == "神秘药水":
|
||||
return "你们看看就好啦,这是不可能卖给你们的~"
|
||||
if num < 0:
|
||||
return "购买的数量要大于0!"
|
||||
goods_list = await GoodsInfo.annotate().order_by("-id").all()
|
||||
goods_list = [
|
||||
goods
|
||||
for goods in goods_list
|
||||
if goods.goods_limit_time > time.time() or goods.goods_limit_time == 0
|
||||
]
|
||||
if name.isdigit():
|
||||
goods = goods_list[int(name) - 1]
|
||||
else:
|
||||
if filter_goods := [g for g in goods_list if g.goods_name == name]:
|
||||
goods = filter_goods[0]
|
||||
else:
|
||||
return "道具名称不存在..."
|
||||
user, _ = await UserConsole.get_or_create(
|
||||
user_id=user_id, defaults={"platform": platform}
|
||||
)
|
||||
price = goods.goods_price * num * goods.goods_discount
|
||||
if user.gold < price:
|
||||
return "糟糕! 您的金币好像不太够哦..."
|
||||
count = await UserPropsLog.filter(
|
||||
user_id=user_id, handle=PropHandle.BUY
|
||||
).count()
|
||||
if goods.daily_limit and count >= goods.daily_limit:
|
||||
return "今天的购买已达限制了喔!"
|
||||
await UserGoldLog.create(user_id=user_id, gold=price, handle=GoldHandle.BUY)
|
||||
await UserPropsLog.create(
|
||||
user_id=user_id, uuid=goods.uuid, gold=price, num=num, handle=PropHandle.BUY
|
||||
)
|
||||
logger.info(
|
||||
f"花费 {price} 金币购买 {goods.goods_name} ×{num} 成功!",
|
||||
"购买道具",
|
||||
session=user_id,
|
||||
)
|
||||
user.gold -= int(price)
|
||||
if goods.uuid not in user.props:
|
||||
user.props[goods.uuid] = 0
|
||||
user.props[goods.uuid] += num
|
||||
await user.save(update_fields=["gold", "props"])
|
||||
return f"花费 {price} 金币购买 {goods.goods_name} ×{num} 成功!"
|
||||
|
||||
@classmethod
|
||||
async def my_props(
|
||||
cls, user_id: str, name: str, platform: str | None = None
|
||||
) -> BuildImage | None:
|
||||
"""获取道具背包
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
name: 用户昵称
|
||||
platform: 平台.
|
||||
|
||||
返回:
|
||||
BuildImage | None: 道具背包图片
|
||||
"""
|
||||
user, _ = await UserConsole.get_or_create(
|
||||
user_id=user_id, defaults={"platform": platform}
|
||||
)
|
||||
if not user.props:
|
||||
return None
|
||||
result = await GoodsInfo.filter(uuid__in=user.props.keys()).all()
|
||||
data_list = []
|
||||
uuid2goods = {item.uuid: item for item in result}
|
||||
column_name = ["-", "使用ID", "名称", "数量", "简介"]
|
||||
for i, p in enumerate(user.props):
|
||||
prop = uuid2goods[p]
|
||||
data_list.append(
|
||||
[
|
||||
(ICON_PATH / prop.icon, 33, 33) if prop.icon else "",
|
||||
i,
|
||||
prop.goods_name,
|
||||
user.props[p],
|
||||
prop.goods_description,
|
||||
]
|
||||
)
|
||||
|
||||
return await ImageTemplate.table_page(
|
||||
f"{name}的道具仓库", "", column_name, data_list
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def my_cost(cls, user_id: str, platform: str | None = None) -> int:
|
||||
"""用户金币
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
platform: 平台.
|
||||
|
||||
返回:
|
||||
int: 金币数量
|
||||
"""
|
||||
user, _ = await UserConsole.get_or_create(
|
||||
user_id=user_id, defaults={"platform": platform}
|
||||
)
|
||||
return user.gold
|
||||
|
||||
@classmethod
|
||||
async def build_shop_image(cls) -> BuildImage:
|
||||
"""制作商店图片
|
||||
|
||||
返回:
|
||||
BuildImage: 商店图片
|
||||
"""
|
||||
goods_lst = await GoodsInfo.get_all_goods()
|
||||
_dc = {}
|
||||
font_h = BuildImage.get_text_size("正")[1]
|
||||
h = 10
|
||||
_list: list[GoodsInfo] = []
|
||||
for goods in goods_lst:
|
||||
if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time:
|
||||
_list.append(goods)
|
||||
# A = BuildImage(1100, h, color="#f9f6f2")
|
||||
total_n = 0
|
||||
image_list = []
|
||||
for idx, goods in enumerate(_list):
|
||||
name_image = BuildImage(
|
||||
580, 40, font_size=25, color="#e67b6b", font="CJGaoDeGuo.otf"
|
||||
)
|
||||
await name_image.text(
|
||||
(15, 0), f"{idx + 1}.{goods.goods_name}", center_type="height"
|
||||
)
|
||||
await name_image.line((380, -5, 280, 45), "#a29ad6", 5)
|
||||
await name_image.text((390, 0), "售价:", center_type="height")
|
||||
if goods.goods_discount != 1:
|
||||
discount_price = int(goods.goods_discount * goods.goods_price)
|
||||
old_price_image = await BuildImage.build_text_image(
|
||||
str(goods.goods_price), font_color=(194, 194, 194), size=15
|
||||
)
|
||||
await old_price_image.line(
|
||||
(
|
||||
0,
|
||||
int(old_price_image.height / 2),
|
||||
old_price_image.width + 1,
|
||||
int(old_price_image.height / 2),
|
||||
),
|
||||
(0, 0, 0),
|
||||
)
|
||||
await name_image.paste(old_price_image, (440, 0))
|
||||
await name_image.text((440, 15), str(discount_price), (255, 255, 255))
|
||||
else:
|
||||
await name_image.text(
|
||||
(440, 0),
|
||||
str(goods.goods_price),
|
||||
(255, 255, 255),
|
||||
center_type="height",
|
||||
)
|
||||
_tmp = await BuildImage.build_text_image(str(goods.goods_price), size=25)
|
||||
await name_image.text(
|
||||
(
|
||||
440 + _tmp.width,
|
||||
0,
|
||||
),
|
||||
f" 金币",
|
||||
center_type="height",
|
||||
)
|
||||
des_image = None
|
||||
font_img = BuildImage(600, 80, font_size=20, color="#a29ad6")
|
||||
p = font_img.getsize("简介:")[0] + 20
|
||||
if goods.goods_description:
|
||||
des_list = goods.goods_description.split("\n")
|
||||
desc = ""
|
||||
for des in des_list:
|
||||
if font_img.getsize(des)[0] > font_img.width - p - 20:
|
||||
msg = ""
|
||||
tmp = ""
|
||||
for i in range(len(des)):
|
||||
if font_img.getsize(tmp)[0] < font_img.width - p - 20:
|
||||
tmp += des[i]
|
||||
else:
|
||||
msg += tmp + "\n"
|
||||
tmp = des[i]
|
||||
desc += msg
|
||||
if tmp:
|
||||
desc += tmp
|
||||
else:
|
||||
desc += des + "\n"
|
||||
if desc[-1] == "\n":
|
||||
desc = desc[:-1]
|
||||
des_image = await text2image(desc, color="#a29ad6")
|
||||
goods_image = BuildImage(
|
||||
600,
|
||||
(50 + des_image.height) if des_image else 50,
|
||||
font_size=20,
|
||||
color="#a29ad6",
|
||||
font="CJGaoDeGuo.otf",
|
||||
)
|
||||
if des_image:
|
||||
await goods_image.text((15, 50), "简介:")
|
||||
await goods_image.paste(des_image, (p, 50))
|
||||
await name_image.circle_corner(5)
|
||||
await goods_image.paste(name_image, (0, 5), center_type="width")
|
||||
await goods_image.circle_corner(20)
|
||||
bk = BuildImage(
|
||||
1180,
|
||||
(50 + des_image.height) if des_image else 50,
|
||||
font_size=15,
|
||||
color="#f9f6f2",
|
||||
font="CJGaoDeGuo.otf",
|
||||
)
|
||||
if goods.icon and (ICON_PATH / goods.icon).exists():
|
||||
icon = BuildImage(70, 70, background=ICON_PATH / goods.icon)
|
||||
await bk.paste(icon)
|
||||
await bk.paste(goods_image, (70, 0))
|
||||
n = 0
|
||||
_w = 650
|
||||
# 添加限时图标和时间
|
||||
if goods.goods_limit_time > 0:
|
||||
n += 140
|
||||
_limit_time_logo = BuildImage(
|
||||
40, 40, background=f"{IMAGE_PATH}/other/time.png"
|
||||
)
|
||||
await bk.paste(_limit_time_logo, (_w + 50, 0))
|
||||
_time_img = await BuildImage.build_text_image("限时!", size=23)
|
||||
await bk.paste(
|
||||
_time_img,
|
||||
(_w + 90, 10),
|
||||
)
|
||||
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.text((_w + 55, 38), str(y_m_d))
|
||||
await bk.text((_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.paste(_discount_logo, (_w + 50, 10))
|
||||
_tmp = await BuildImage.build_text_image("折扣!", size=23)
|
||||
await bk.paste(_tmp, (_w + 90, 15))
|
||||
_tmp = await BuildImage.build_text_image(
|
||||
f"{10 * goods.goods_discount:.1f} 折",
|
||||
size=30,
|
||||
font_color=(85, 156, 75),
|
||||
)
|
||||
await bk.paste(_tmp, (_w + 50, 44))
|
||||
_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.paste(_daily_limit_logo, (_w + 50, 10))
|
||||
_tmp = await BuildImage.build_text_image(
|
||||
"限购!",
|
||||
size=23,
|
||||
)
|
||||
await bk.paste(_tmp, (_w + 90, 20))
|
||||
_tmp = await BuildImage.build_text_image(
|
||||
f"{goods.daily_limit}", size=30
|
||||
)
|
||||
await bk.paste(_tmp, (_w + 72, 45))
|
||||
if total_n < n:
|
||||
total_n = n
|
||||
if n:
|
||||
await bk.line((650, -1, 650 + n, -1), "#a29ad6", 5)
|
||||
# await bk.aline((650, 80, 650 + n, 80), "#a29ad6", 5)
|
||||
|
||||
# 添加限时图标和时间
|
||||
image_list.append(bk)
|
||||
# await A.apaste(bk, (0, current_h), True)
|
||||
# current_h += 90
|
||||
h = 0
|
||||
current_h = 0
|
||||
for img in image_list:
|
||||
h += img.height + 10
|
||||
A = BuildImage(1100, h, color="#f9f6f2")
|
||||
for img in image_list:
|
||||
await A.paste(img, (0, current_h))
|
||||
current_h += img.height + 10
|
||||
w = 950
|
||||
if total_n:
|
||||
w += total_n
|
||||
h = A.height + 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")
|
||||
await shop.paste(A, (20, 230))
|
||||
await shop.paste(shop_logo, (450, 30))
|
||||
await shop.text(
|
||||
(
|
||||
int((1000 - shop.getsize("注【通过 序号 或者 商品名称 购买】")[0]) / 2),
|
||||
170,
|
||||
),
|
||||
"注【通过 序号 或者 商品名称 购买】",
|
||||
)
|
||||
await shop.text(
|
||||
(20, h - 100),
|
||||
"神秘药水\t\t售价:9999999金币\n\t\t鬼知道会有什么效果~",
|
||||
)
|
||||
return shop
|
||||
148
zhenxun/builtin_plugins/sign_in/__init__.py
Normal file
148
zhenxun/builtin_plugins/sign_in/__init__.py
Normal file
@ -0,0 +1,148 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import (
|
||||
Alconna,
|
||||
Args,
|
||||
Arparma,
|
||||
Option,
|
||||
on_alconna,
|
||||
store_true,
|
||||
)
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
from nonebot_plugin_saa import Image, Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
|
||||
|
||||
from zhenxun.configs.utils import PluginCdBlock, PluginExtraData, RegisterConfig
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from ._data_source import SignManage
|
||||
from .goods_register import driver
|
||||
from .utils import clear_sign_data_pic
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="签到",
|
||||
description="每日签到,证明你在这里",
|
||||
usage="""
|
||||
每日签到
|
||||
会影响色图概率和开箱次数,以及签到的随机道具获取
|
||||
指令:
|
||||
我的签到
|
||||
好感度排行
|
||||
* 签到时有 3% 概率 * 2 *
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
configs=[
|
||||
RegisterConfig(
|
||||
module="send_setu",
|
||||
key="INITIAL_SETU_PROBABILITY",
|
||||
value=0.7,
|
||||
help="初始色图概率,总概率 = 初始色图概率 + 好感度",
|
||||
default_value=0.7,
|
||||
type=float,
|
||||
),
|
||||
RegisterConfig(
|
||||
key="MAX_SIGN_GOLD",
|
||||
value=200,
|
||||
help="签到好感度加成额外获得的最大金币数",
|
||||
default_value=200,
|
||||
type=int,
|
||||
),
|
||||
RegisterConfig(
|
||||
key="SIGN_CARD1_PROB",
|
||||
value=0.2,
|
||||
help="签到好感度双倍加持卡Ⅰ掉落概率",
|
||||
default_value=0.2,
|
||||
type=float,
|
||||
),
|
||||
RegisterConfig(
|
||||
key="SIGN_CARD2_PROB",
|
||||
value=0.09,
|
||||
help="签到好感度双倍加持卡Ⅲ掉落概率",
|
||||
default_value=0.09,
|
||||
type=float,
|
||||
),
|
||||
RegisterConfig(
|
||||
key="SIGN_CARD3_PROB",
|
||||
value=0.05,
|
||||
help="签到好感度双倍加持卡Ⅲ掉落概率",
|
||||
default_value=0.05,
|
||||
type=float,
|
||||
),
|
||||
],
|
||||
limits=[PluginCdBlock()],
|
||||
).dict(),
|
||||
)
|
||||
|
||||
|
||||
_sign_matcher = on_alconna(
|
||||
Alconna(
|
||||
"签到",
|
||||
Option("--my", action=store_true, help_text="我的签到"),
|
||||
Option(
|
||||
"-l|--list", Args["num", int, 10], action=store_true, help_text="好感度排行"
|
||||
),
|
||||
),
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
# TODO: shortcut
|
||||
|
||||
|
||||
@_sign_matcher.assign("$main")
|
||||
async def _(
|
||||
session: EventSession, arparma: Arparma, user_info: UserInfo = EventUserInfo()
|
||||
):
|
||||
nickname = (
|
||||
user_info.user_displayname or user_info.user_remark or user_info.user_name
|
||||
)
|
||||
if session.id1:
|
||||
if path := await SignManage.sign(session, nickname):
|
||||
logger.info("签到成功", arparma.header_result, session=session)
|
||||
await Image(path).finish(reply=True)
|
||||
return Text("用户id为空...").send()
|
||||
|
||||
|
||||
@_sign_matcher.assign("my")
|
||||
async def _(
|
||||
session: EventSession, arparma: Arparma, user_info: UserInfo = EventUserInfo()
|
||||
):
|
||||
nickname = (
|
||||
user_info.user_displayname or user_info.user_remark or user_info.user_name
|
||||
)
|
||||
if session.id1:
|
||||
if image := await SignManage.sign(session, nickname, True):
|
||||
logger.info("查看我的签到", arparma.header_result, session=session)
|
||||
await Image(image).finish(reply=True)
|
||||
return Text("用户id为空...").send()
|
||||
|
||||
|
||||
@_sign_matcher.assign("list")
|
||||
async def _(
|
||||
session: EventSession,
|
||||
arparma: Arparma,
|
||||
num: int,
|
||||
user_info: UserInfo = EventUserInfo(),
|
||||
):
|
||||
nickname = (
|
||||
user_info.user_displayname or user_info.user_remark or user_info.user_name
|
||||
)
|
||||
if session.id1:
|
||||
if image := await SignManage.rank(session.id1, num):
|
||||
logger.info("查看签到排行", arparma.header_result, session=session)
|
||||
await Image(image.pic2bs4()).finish()
|
||||
return Text("用户id为空...").send()
|
||||
|
||||
|
||||
@scheduler.scheduled_job(
|
||||
"interval",
|
||||
hours=1,
|
||||
)
|
||||
async def _():
|
||||
try:
|
||||
clear_sign_data_pic()
|
||||
logger.info("清理日常签到图片数据数据完成...", "签到")
|
||||
except Exception as e:
|
||||
logger.error(f"清理日常签到图片数据数据失败...", e=e)
|
||||
175
zhenxun/builtin_plugins/sign_in/_data_source.py
Normal file
175
zhenxun/builtin_plugins/sign_in/_data_source.py
Normal file
@ -0,0 +1,175 @@
|
||||
import os
|
||||
import random
|
||||
import secrets
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import pytz
|
||||
from nonebot_plugin_session import EventSession
|
||||
from tortoise.functions import Count
|
||||
|
||||
from zhenxun.configs.path_config import IMAGE_PATH
|
||||
from zhenxun.models.friend_user import FriendUser
|
||||
from zhenxun.models.group_member_info import GroupInfoUser
|
||||
from zhenxun.models.sign_log import SignLog
|
||||
from zhenxun.models.sign_user import SignUser
|
||||
from zhenxun.models.user_console import UserConsole
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.image_utils import BuildImage, ImageTemplate
|
||||
from zhenxun.utils.utils import get_user_avatar
|
||||
|
||||
from ._random_event import random_event
|
||||
from .utils import SIGN_TODAY_CARD_PATH, get_card
|
||||
|
||||
ICON_PATH = IMAGE_PATH / "_icon"
|
||||
|
||||
PLATFORM_PATH = {
|
||||
"dodo": ICON_PATH / "dodo.png",
|
||||
"discord": ICON_PATH / "discord.png",
|
||||
"kaiheila": ICON_PATH / "kook.png",
|
||||
"qq": ICON_PATH / "qq.png",
|
||||
}
|
||||
|
||||
|
||||
class SignManage:
|
||||
|
||||
@classmethod
|
||||
async def rank(cls, user_id: str, num: int) -> BuildImage:
|
||||
all_list = (
|
||||
await SignUser.annotate()
|
||||
.order_by("impression")
|
||||
.values_list("user_id", flat=True)
|
||||
)
|
||||
index = all_list.index(user_id) + 1 # type: ignore
|
||||
user_list = await SignUser.annotate().order_by("impression").limit(num).all()
|
||||
user_id_list = [u.user_id for u in user_list]
|
||||
log_list = (
|
||||
await SignLog.filter(user_id__in=user_id_list)
|
||||
.annotate(count=Count("id"))
|
||||
.group_by("user_id")
|
||||
.values_list("user_id", "count")
|
||||
)
|
||||
uid2cnt = {l[0]: l[1] for l in log_list}
|
||||
column_name = ["排名", "-", "名称", "好感度", "签到次数", "平台"]
|
||||
friend_list = await FriendUser.filter(user_id__in=user_id_list).values_list(
|
||||
"user_id", "user_name"
|
||||
)
|
||||
uid2name = {f[0]: f[1] for f in friend_list}
|
||||
group_member_list = await GroupInfoUser.filter(
|
||||
user_id__in=user_id_list
|
||||
).values_list("user_id", "user_name")
|
||||
for gm in group_member_list:
|
||||
uid2name[gm[0]] = gm[1]
|
||||
data_list = []
|
||||
for i, user in enumerate(user_list):
|
||||
bytes = await get_user_avatar(user.user_id)
|
||||
data_list.append(
|
||||
[
|
||||
f"{i+1}",
|
||||
(bytes, 30, 30) if user.platform == "qq" else "",
|
||||
uid2name.get(user.user_id),
|
||||
user.impression,
|
||||
uid2cnt.get(user.user_id) or 0,
|
||||
(PLATFORM_PATH.get(user.platform), 30, 30),
|
||||
]
|
||||
)
|
||||
return await ImageTemplate.table_page(
|
||||
"好感度排行", f"你的排名在第 {index} 位哦!", column_name, data_list
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def sign(
|
||||
cls, session: EventSession, nickname: str, is_view_card: bool = False
|
||||
) -> Path | None:
|
||||
"""签到
|
||||
|
||||
参数:
|
||||
session: Session
|
||||
nickname: 用户昵称
|
||||
is_view_card: 是否展示卡片
|
||||
|
||||
返回:
|
||||
Path: 卡片路径
|
||||
"""
|
||||
if not session.id1:
|
||||
return None
|
||||
now = datetime.now(pytz.timezone("Asia/Shanghai"))
|
||||
user_console, _ = await UserConsole.get_or_create(
|
||||
user_id=session.id1,
|
||||
defaults={
|
||||
"uid": await UserConsole.get_new_uid(),
|
||||
"platform": session.platform,
|
||||
},
|
||||
)
|
||||
user, _ = await SignUser.get_or_create(
|
||||
user_id=session.id1,
|
||||
defaults={"user_console": user_console, "platform": session.platform},
|
||||
)
|
||||
new_log = await SignLog.filter(user_id=session.id1).first()
|
||||
file_name = f"{user}_sign_{datetime.now().date()}.png"
|
||||
if (
|
||||
user.sign_count != 0
|
||||
or (new_log and now > new_log.create_time)
|
||||
or file_name in os.listdir(SIGN_TODAY_CARD_PATH)
|
||||
):
|
||||
user_console, _ = await UserConsole.get_or_create(user_id=session.id1)
|
||||
path = await get_card(user, nickname, -1, user_console.gold, "")
|
||||
else:
|
||||
path = await cls._handle_sign_in(user, nickname, session, is_view_card)
|
||||
return path
|
||||
|
||||
@classmethod
|
||||
async def _handle_sign_in(
|
||||
cls,
|
||||
user: SignUser,
|
||||
nickname: str,
|
||||
session: EventSession,
|
||||
is_view_card: bool,
|
||||
) -> Path:
|
||||
"""签到处理
|
||||
|
||||
参数:
|
||||
user: SignUser
|
||||
nickname: 用户昵称
|
||||
session: Session
|
||||
is_view_card: 是否展示卡片
|
||||
|
||||
返回:
|
||||
Path: 卡片路径
|
||||
"""
|
||||
impression_added = (secrets.randbelow(99) + 1) / 100
|
||||
rand = random.random()
|
||||
add_probability = float(user.add_probability)
|
||||
specify_probability = user.specify_probability
|
||||
if rand + add_probability > 0.97:
|
||||
impression_added *= 2
|
||||
elif rand < specify_probability:
|
||||
impression_added *= 2
|
||||
await SignUser.sign(user, impression_added, session.bot_id, session.platform)
|
||||
gold = random.randint(1, 100)
|
||||
gift = random_event(float(user.impression))
|
||||
if isinstance(gift, int):
|
||||
gold += gift
|
||||
await UserConsole.add_gold(
|
||||
user.user_id, gold + gift, "sign_in", session.platform
|
||||
)
|
||||
gift = f"额外金币 +{gift}"
|
||||
else:
|
||||
await UserConsole.add_gold(user.user_id, gold, "sign_in", session.platform)
|
||||
await UserConsole.add_props(user.user_id, gift, 1, session.platform)
|
||||
gift += " + 1"
|
||||
logger.info(
|
||||
f"签到成功. score: {user.impression:.2f} "
|
||||
f"(+{impression_added:.2f}).获取金币/道具: {gold}",
|
||||
"签到",
|
||||
session=session,
|
||||
)
|
||||
return await get_card(
|
||||
user,
|
||||
nickname,
|
||||
impression_added,
|
||||
gold,
|
||||
gift,
|
||||
rand + add_probability > 0.97 or rand < specify_probability,
|
||||
is_view_card,
|
||||
)
|
||||
33
zhenxun/builtin_plugins/sign_in/_random_event.py
Normal file
33
zhenxun/builtin_plugins/sign_in/_random_event.py
Normal file
@ -0,0 +1,33 @@
|
||||
import random
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
|
||||
PROB_DATA = None
|
||||
|
||||
|
||||
def random_event(impression: float) -> str | int:
|
||||
"""签到随机事件
|
||||
|
||||
参数:
|
||||
impression: 好感度
|
||||
|
||||
返回:
|
||||
额外奖励 和 类型
|
||||
"""
|
||||
global PROB_DATA
|
||||
if not PROB_DATA:
|
||||
PROB_DATA = {
|
||||
Config.get_config("sign_in", "SIGN_CARD3_PROB"): "好感度双倍加持卡Ⅲ",
|
||||
Config.get_config("sign_in", "SIGN_CARD2_PROB"): "好感度双倍加持卡Ⅱ",
|
||||
Config.get_config("sign_in", "SIGN_CARD1_PROB"): "好感度双倍加持卡Ⅰ",
|
||||
}
|
||||
rand = random.random() - impression / 1000
|
||||
for prob in PROB_DATA.keys():
|
||||
if rand <= prob:
|
||||
return PROB_DATA[prob]
|
||||
gold = random.randint(
|
||||
1, random.randint(1, int(1 if impression < 1 else impression))
|
||||
)
|
||||
max_sign_gold = Config.get_config("sign_in", "MAX_SIGN_GOLD")
|
||||
gold = max_sign_gold if gold > max_sign_gold else gold
|
||||
return gold
|
||||
49
zhenxun/builtin_plugins/sign_in/config.py
Normal file
49
zhenxun/builtin_plugins/sign_in/config.py
Normal file
@ -0,0 +1,49 @@
|
||||
from zhenxun.configs.path_config import IMAGE_PATH
|
||||
|
||||
SIGN_RESOURCE_PATH = IMAGE_PATH / "sign" / "sign_res"
|
||||
SIGN_TODAY_CARD_PATH = IMAGE_PATH / "sign" / "today_card"
|
||||
SIGN_BORDER_PATH = SIGN_RESOURCE_PATH / "border"
|
||||
SIGN_BACKGROUND_PATH = SIGN_RESOURCE_PATH / "background"
|
||||
|
||||
SIGN_BORDER_PATH.mkdir(exist_ok=True, parents=True)
|
||||
SIGN_BACKGROUND_PATH.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
|
||||
lik2relation = {
|
||||
"0": "路人",
|
||||
"1": "陌生",
|
||||
"2": "初识",
|
||||
"3": "普通",
|
||||
"4": "熟悉",
|
||||
"5": "信赖",
|
||||
"6": "相知",
|
||||
"7": "厚谊",
|
||||
"8": "亲密",
|
||||
}
|
||||
|
||||
level2attitude = {
|
||||
"0": "排斥",
|
||||
"1": "警惕",
|
||||
"2": "可以交流",
|
||||
"3": "一般",
|
||||
"4": "是个好人",
|
||||
"5": "好朋友",
|
||||
"6": "可以分享小秘密",
|
||||
"7": "喜欢",
|
||||
"8": "恋人",
|
||||
}
|
||||
|
||||
weekdays = {1: "Mon", 2: "Tue", 3: "Wed", 4: "Thu", 5: "Fri", 6: "Sat", 7: "Sun"}
|
||||
|
||||
lik2level = {
|
||||
9999: "9",
|
||||
400: "8",
|
||||
270: "7",
|
||||
200: "6",
|
||||
140: "5",
|
||||
90: "4",
|
||||
50: "3",
|
||||
25: "2",
|
||||
10: "1",
|
||||
0: "0",
|
||||
}
|
||||
75
zhenxun/builtin_plugins/sign_in/goods_register.py
Normal file
75
zhenxun/builtin_plugins/sign_in/goods_register.py
Normal file
@ -0,0 +1,75 @@
|
||||
from decimal import Decimal
|
||||
|
||||
import nonebot
|
||||
from nonebot.drivers import Driver
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.sign_user import SignUser
|
||||
from zhenxun.models.user_console import UserConsole
|
||||
from zhenxun.utils.decorator.shop import NotMeetUseConditionsException, shop_register
|
||||
|
||||
driver: Driver = nonebot.get_driver()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def _():
|
||||
"""
|
||||
导入内置的三个商品
|
||||
"""
|
||||
|
||||
@shop_register(
|
||||
name=("好感度双倍加持卡Ⅰ", "好感度双倍加持卡Ⅱ", "好感度双倍加持卡Ⅲ"),
|
||||
price=(30, 150, 250),
|
||||
des=(
|
||||
"下次签到双倍好感度概率 + 10%(谁才是真命天子?)(同类商品将覆盖)",
|
||||
"下次签到双倍好感度概率 + 20%(平平庸庸)(同类商品将覆盖)",
|
||||
"下次签到双倍好感度概率 + 30%(金币才是真命天子!)(同类商品将覆盖)",
|
||||
),
|
||||
load_status=bool(Config.get_config("shop", "IMPORT_DEFAULT_SHOP_GOODS")),
|
||||
icon=(
|
||||
"favorability_card_1.png",
|
||||
"favorability_card_2.png",
|
||||
"favorability_card_3.png",
|
||||
),
|
||||
**{"好感度双倍加持卡Ⅰ_prob": 0.1, "好感度双倍加持卡Ⅱ_prob": 0.2, "好感度双倍加持卡Ⅲ_prob": 0.3}, # type: ignore
|
||||
)
|
||||
async def _(session: EventSession, user_id: int, group_id: int, prob: float):
|
||||
user_console, _ = await UserConsole.get_or_create(
|
||||
user_id=session.id1,
|
||||
defaults={
|
||||
"uid": await UserConsole.get_new_uid(),
|
||||
"platform": session.platform,
|
||||
},
|
||||
)
|
||||
user, _ = await SignUser.get_or_create(
|
||||
user_id=user_id,
|
||||
defaults={"platform": session.platform, "user_console": user_console},
|
||||
)
|
||||
user.add_probability = Decimal(prob)
|
||||
await user.save(update_fields=["add_probability"])
|
||||
|
||||
@shop_register(
|
||||
name="测试道具A",
|
||||
price=99,
|
||||
des="随便侧而出",
|
||||
load_status=False,
|
||||
icon="sword.png",
|
||||
)
|
||||
async def _(user_id: int, group_id: int):
|
||||
print(user_id, group_id, "使用测试道具")
|
||||
|
||||
@shop_register.before_handle(name="测试道具A", load_status=False)
|
||||
async def _(user_id: int, group_id: int):
|
||||
print(user_id, group_id, "第一个使用前函数(before handle)")
|
||||
|
||||
@shop_register.before_handle(name="测试道具A", load_status=False)
|
||||
async def _(user_id: int, group_id: int):
|
||||
print(user_id, group_id, "第二个使用前函数(before handle)222")
|
||||
raise NotMeetUseConditionsException(
|
||||
"太笨了!"
|
||||
) # 抛出异常,阻断使用,并返回信息
|
||||
|
||||
@shop_register.after_handle(name="测试道具A", load_status=False)
|
||||
async def _(user_id: int, group_id: int):
|
||||
print(user_id, group_id, "第一个使用后函数(after handle)")
|
||||
326
zhenxun/builtin_plugins/sign_in/utils.py
Normal file
326
zhenxun/builtin_plugins/sign_in/utils.py
Normal file
@ -0,0 +1,326 @@
|
||||
import os
|
||||
import random
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import nonebot
|
||||
import pytz
|
||||
from nonebot.drivers import Driver
|
||||
|
||||
from zhenxun.configs.config import NICKNAME, Config
|
||||
from zhenxun.configs.path_config import IMAGE_PATH
|
||||
from zhenxun.models.sign_log import SignLog
|
||||
from zhenxun.models.sign_user import SignUser
|
||||
from zhenxun.utils.image_utils import BuildImage
|
||||
from zhenxun.utils.utils import get_user_avatar
|
||||
|
||||
from .config import (
|
||||
SIGN_BACKGROUND_PATH,
|
||||
SIGN_BORDER_PATH,
|
||||
SIGN_RESOURCE_PATH,
|
||||
SIGN_TODAY_CARD_PATH,
|
||||
level2attitude,
|
||||
lik2level,
|
||||
lik2relation,
|
||||
)
|
||||
|
||||
driver: Driver = nonebot.get_driver()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def init_image():
|
||||
SIGN_RESOURCE_PATH.mkdir(parents=True, exist_ok=True)
|
||||
SIGN_TODAY_CARD_PATH.mkdir(exist_ok=True, parents=True)
|
||||
await generate_progress_bar_pic()
|
||||
clear_sign_data_pic()
|
||||
|
||||
|
||||
async def get_card(
|
||||
user: SignUser,
|
||||
nickname: str,
|
||||
add_impression: float,
|
||||
gold: int | None,
|
||||
gift: str,
|
||||
is_double: bool = False,
|
||||
is_card_view: bool = False,
|
||||
) -> Path:
|
||||
"""获取好感度卡片
|
||||
|
||||
参数:
|
||||
user: SignUser
|
||||
nickname: 用户昵称
|
||||
impression: 新增的好感度
|
||||
gold: 金币
|
||||
gift: 礼物
|
||||
is_double: 是否触发双倍.
|
||||
is_card_view: 是否展示好感度卡片.
|
||||
|
||||
返回:
|
||||
Path: 卡片路径
|
||||
"""
|
||||
user_id = user.user_id
|
||||
date = datetime.now().date()
|
||||
_type = "view" if is_card_view else "sign"
|
||||
file_name = f"{user_id}_{_type}_{date}.png"
|
||||
view_name = f"{user_id}_view_{date}.png"
|
||||
card_file = Path(SIGN_TODAY_CARD_PATH) / file_name
|
||||
if card_file.exists():
|
||||
return IMAGE_PATH / "sign" / "today_card" / file_name
|
||||
else:
|
||||
if add_impression == -1:
|
||||
card_file = Path(SIGN_TODAY_CARD_PATH) / view_name
|
||||
if card_file.exists():
|
||||
return card_file
|
||||
is_card_view = True
|
||||
return await _generate_card(
|
||||
user, nickname, add_impression, gold, gift, is_double, is_card_view
|
||||
)
|
||||
|
||||
|
||||
async def _generate_card(
|
||||
user: SignUser,
|
||||
nickname: str,
|
||||
impression: float,
|
||||
gold: int | None,
|
||||
gift: str,
|
||||
is_double: bool = False,
|
||||
is_card_view: bool = False,
|
||||
) -> Path:
|
||||
"""生成签到卡片
|
||||
|
||||
参数:
|
||||
user: SignUser
|
||||
nickname: 用户昵称
|
||||
impression: 新增的好感度
|
||||
gold: 金币
|
||||
gift: 礼物
|
||||
is_double: 是否触发双倍.
|
||||
is_card_view: 是否展示好感度卡片.
|
||||
|
||||
返回:
|
||||
Path: 卡片路径
|
||||
"""
|
||||
ava_bk = BuildImage(140, 140, (255, 255, 255, 0))
|
||||
ava_border = BuildImage(
|
||||
140,
|
||||
140,
|
||||
background=SIGN_BORDER_PATH / "ava_border_01.png",
|
||||
)
|
||||
if user.platform == "qq" and (byt := await get_user_avatar(user.user_id)):
|
||||
ava = BuildImage(107, 107, background=BytesIO(byt))
|
||||
else:
|
||||
ava = BuildImage(107, 107, (0, 0, 0))
|
||||
await ava.circle()
|
||||
await ava_bk.paste(ava, (19, 18))
|
||||
await ava_bk.paste(ava_border, center_type="center")
|
||||
add_impression = impression
|
||||
impression = float(user.impression)
|
||||
info_img = BuildImage(250, 150, color=(255, 255, 255, 0), font_size=15)
|
||||
level, next_impression, previous_impression = get_level_and_next_impression(
|
||||
impression
|
||||
)
|
||||
interpolation = next_impression - impression
|
||||
if level == "9":
|
||||
level = "8"
|
||||
interpolation = 0
|
||||
await info_img.text((0, 0), f"· 好感度等级:{level} [{lik2relation[level]}]")
|
||||
await info_img.text((0, 20), f"· {NICKNAME}对你的态度:{level2attitude[level]}")
|
||||
await info_img.text((0, 40), f"· 距离升级还差 {interpolation:.2f} 好感度")
|
||||
|
||||
bar_bk = BuildImage(220, 20, background=SIGN_RESOURCE_PATH / "bar_white.png")
|
||||
bar = BuildImage(220, 20, background=SIGN_RESOURCE_PATH / "bar.png")
|
||||
ratio = 1 - (next_impression - user.impression) / (
|
||||
next_impression - previous_impression
|
||||
)
|
||||
if next_impression == 0:
|
||||
ratio = 0
|
||||
await bar.resize(width=int(bar.width * ratio) or bar.width, height=bar.height)
|
||||
await bar_bk.paste(bar)
|
||||
font_size = 30
|
||||
if "好感度双倍加持卡" in gift:
|
||||
font_size = 20
|
||||
gift_border = BuildImage(
|
||||
270,
|
||||
100,
|
||||
background=SIGN_BORDER_PATH / "gift_border_02.png",
|
||||
font_size=font_size,
|
||||
)
|
||||
await gift_border.text((0, 0), gift, center_type="center")
|
||||
|
||||
bk = BuildImage(
|
||||
876,
|
||||
424,
|
||||
background=SIGN_BACKGROUND_PATH
|
||||
/ random.choice(os.listdir(SIGN_BACKGROUND_PATH)),
|
||||
font_size=25,
|
||||
)
|
||||
A = BuildImage(876, 274, background=SIGN_RESOURCE_PATH / "white.png")
|
||||
line = BuildImage(2, 180, color="black")
|
||||
await A.transparent(2)
|
||||
await A.paste(ava_bk, (25, 80))
|
||||
await A.paste(line, (200, 70))
|
||||
nickname_img = await BuildImage.build_text_image(
|
||||
nickname, size=50, font_color=(255, 255, 255)
|
||||
)
|
||||
user_console = await user.user_console.first()
|
||||
if user_console and user_console.uid:
|
||||
uid = f"{user_console.uid}".rjust(12, "0")
|
||||
uid = uid[:4] + " " + uid[4:8] + " " + uid[8:]
|
||||
else:
|
||||
uid = "XXXX XXXX XXXX"
|
||||
uid_img = await BuildImage.build_text_image(
|
||||
f"UID: {uid}", size=30, font_color=(255, 255, 255)
|
||||
)
|
||||
sign_count = await SignLog.filter(user_id=user.user_id).count()
|
||||
sign_day_img = await BuildImage.build_text_image(
|
||||
f"{sign_count}", size=40, font_color=(211, 64, 33)
|
||||
)
|
||||
lik_text1_img = await BuildImage.build_text_image("当前", size=20)
|
||||
lik_text2_img = await BuildImage.build_text_image(
|
||||
f"好感度:{user.impression:.2f}", size=30
|
||||
)
|
||||
watermark = await BuildImage.build_text_image(
|
||||
f"{NICKNAME}@{datetime.now().year}", size=15, font_color=(155, 155, 155)
|
||||
)
|
||||
today_data = BuildImage(300, 300, color=(255, 255, 255, 0), font_size=20)
|
||||
if is_card_view:
|
||||
today_sign_text_img = await BuildImage.build_text_image("", size=30)
|
||||
value_list = (
|
||||
await SignUser.annotate()
|
||||
.order_by("impression")
|
||||
.values_list("user_id", flat=True)
|
||||
)
|
||||
index = value_list.index(user.user_id) + 1 # type: ignore
|
||||
rank_img = await BuildImage.build_text_image(
|
||||
f"* 好感度排名第 {index} 位", size=30
|
||||
)
|
||||
await A.paste(rank_img, ((A.width - rank_img.width - 32), 20))
|
||||
last_log = (
|
||||
await SignLog.filter(user_id=user.user_id).order_by("create_time").first()
|
||||
)
|
||||
last_date = "从未"
|
||||
if last_log:
|
||||
last_date = last_log.create_time.astimezone(
|
||||
pytz.timezone("Asia/Shanghai")
|
||||
).date()
|
||||
await today_data.text(
|
||||
(0, 0),
|
||||
f"上次签到日期:{last_date}",
|
||||
)
|
||||
await today_data.text((0, 25), f"总金币:{gold}")
|
||||
default_setu_prob = (
|
||||
Config.get_config("send_setu", "INITIAL_SETU_PROBABILITY") * 100 # type: ignore
|
||||
)
|
||||
await today_data.text(
|
||||
(0, 50),
|
||||
f"色图概率:{(default_setu_prob + float(user.impression) if user.impression < 100 else 100):.2f}%",
|
||||
)
|
||||
await today_data.text((0, 75), f"开箱次数:{(20 + int(user.impression / 3))}")
|
||||
_type = "view"
|
||||
else:
|
||||
await A.paste(gift_border, (570, 140))
|
||||
today_sign_text_img = await BuildImage.build_text_image("今日签到", size=30)
|
||||
if is_double:
|
||||
await today_data.text((0, 0), f"好感度 + {add_impression / 2:.2f} × 2")
|
||||
else:
|
||||
await today_data.text((0, 0), f"好感度 + {add_impression:.2f}")
|
||||
await today_data.text((0, 25), f"金币 + {gold}")
|
||||
_type = "sign"
|
||||
current_date = datetime.now()
|
||||
current_datetime_str = current_date.strftime("%Y-%m-%d %a %H:%M:%S")
|
||||
data = current_date.date()
|
||||
data_img = await BuildImage.build_text_image(
|
||||
f"时间:{current_datetime_str}", size=20
|
||||
)
|
||||
await bk.paste(nickname_img, (30, 15))
|
||||
await bk.paste(uid_img, (30, 85))
|
||||
await bk.paste(A, (0, 150))
|
||||
await bk.text((30, 167), "Accumulative check-in for")
|
||||
_x = bk.getsize("Accumulative check-in for")[0] + sign_day_img.width + 45
|
||||
await bk.paste(sign_day_img, (380, 158))
|
||||
await bk.text((_x, 167), "days")
|
||||
await bk.paste(data_img, (220, 370))
|
||||
await bk.paste(lik_text1_img, (220, 240))
|
||||
await bk.paste(lik_text2_img, (262, 234))
|
||||
await bk.paste(bar_bk, (225, 275))
|
||||
await bk.paste(info_img, (220, 305))
|
||||
await bk.paste(today_sign_text_img, (550, 180))
|
||||
await bk.paste(today_data, (580, 220))
|
||||
await bk.paste(watermark, (15, 400))
|
||||
await bk.save(SIGN_TODAY_CARD_PATH / f"{user.user_id}_{_type}_{data}.png")
|
||||
return IMAGE_PATH / "sign" / "today_card" / f"{user.user_id}_{_type}_{data}.png"
|
||||
|
||||
|
||||
async def generate_progress_bar_pic():
|
||||
"""
|
||||
初始化进度条图片
|
||||
"""
|
||||
bg_2 = (254, 1, 254)
|
||||
bg_1 = (0, 245, 246)
|
||||
|
||||
bk = BuildImage(1000, 50)
|
||||
img_x = BuildImage(50, 50, color=bg_2)
|
||||
await img_x.circle()
|
||||
await img_x.crop((25, 0, 50, 50))
|
||||
img_y = BuildImage(50, 50, color=bg_1)
|
||||
await img_y.circle()
|
||||
await img_y.crop((0, 0, 25, 50))
|
||||
A = BuildImage(950, 50)
|
||||
width, height = A.size
|
||||
|
||||
step_r = (bg_2[0] - bg_1[0]) / width
|
||||
step_g = (bg_2[1] - bg_1[1]) / width
|
||||
step_b = (bg_2[2] - bg_1[2]) / width
|
||||
|
||||
for y in range(0, width):
|
||||
bg_r = round(bg_1[0] + step_r * y)
|
||||
bg_g = round(bg_1[1] + step_g * y)
|
||||
bg_b = round(bg_1[2] + step_b * y)
|
||||
for x in range(0, height):
|
||||
await A.point((y, x), fill=(bg_r, bg_g, bg_b))
|
||||
await bk.paste(img_y, (0, 0))
|
||||
await bk.paste(A, (25, 0))
|
||||
await bk.paste(img_x, (975, 0))
|
||||
await bk.save(SIGN_RESOURCE_PATH / "bar.png")
|
||||
|
||||
A = BuildImage(950, 50)
|
||||
bk = BuildImage(1000, 50)
|
||||
img_x = BuildImage(50, 50)
|
||||
await img_x.circle()
|
||||
await img_x.crop((25, 0, 50, 50))
|
||||
img_y = BuildImage(50, 50)
|
||||
await img_y.circle()
|
||||
await img_y.crop((0, 0, 25, 50))
|
||||
await bk.paste(img_y, (0, 0))
|
||||
await bk.paste(A, (25, 0))
|
||||
await bk.paste(img_x, (975, 0))
|
||||
await bk.save(SIGN_RESOURCE_PATH / "bar_white.png")
|
||||
|
||||
|
||||
def get_level_and_next_impression(impression: float) -> tuple[str, int, int]:
|
||||
"""获取当前好感等级与下一等级的差距
|
||||
|
||||
参数:
|
||||
impression: 好感度
|
||||
|
||||
返回:
|
||||
tuple[str, int, int]: 好感度等级中文,好感度等级,下一等级好感差距
|
||||
"""
|
||||
if impression == 0:
|
||||
return lik2level[10], 10, 0
|
||||
keys = list(lik2level.keys())
|
||||
for i in range(len(keys)):
|
||||
if impression > keys[i]:
|
||||
return lik2level[keys[i]], keys[i - 1], keys[i]
|
||||
return lik2level[10], 10, 0
|
||||
|
||||
|
||||
def clear_sign_data_pic():
|
||||
"""
|
||||
清空当前签到图片数据
|
||||
"""
|
||||
date = datetime.now().date()
|
||||
for file in os.listdir(SIGN_TODAY_CARD_PATH):
|
||||
if str(date) not in file:
|
||||
os.remove(SIGN_TODAY_CARD_PATH / file)
|
||||
61
zhenxun/builtin_plugins/superuser/broadcast/__init__.py
Normal file
61
zhenxun/builtin_plugins/superuser/broadcast/__init__.py
Normal file
@ -0,0 +1,61 @@
|
||||
from typing import Annotated
|
||||
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.params import Command
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import UniMsg
|
||||
from nonebot_plugin_saa import Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
|
||||
from ._data_source import BroadcastManage
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="广播",
|
||||
description="昭告天下!",
|
||||
usage="""
|
||||
广播 [消息] [图片]
|
||||
示例:广播 你们好!
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.SUPERUSER,
|
||||
configs=[
|
||||
RegisterConfig(
|
||||
module="_task",
|
||||
key="DEFAULT_BROADCAST",
|
||||
value=True,
|
||||
help="被动 广播 进群默认开关状态",
|
||||
default_value=True,
|
||||
type=bool,
|
||||
)
|
||||
],
|
||||
tasks=[Task(module="broadcast", name="广播")],
|
||||
).dict(),
|
||||
)
|
||||
|
||||
_matcher = on_command("广播", priority=1, permission=SUPERUSER, block=True)
|
||||
|
||||
|
||||
@_matcher.handle()
|
||||
async def _(
|
||||
bot: Bot,
|
||||
session: EventSession,
|
||||
message: UniMsg,
|
||||
command: Annotated[tuple[str, ...], Command()],
|
||||
):
|
||||
message[0].text = message[0].text.replace(command[0], "").strip()
|
||||
# await Text("正在发送..请等一下哦!").send()
|
||||
count, error_count = await BroadcastManage.send(bot, message, session)
|
||||
result = f"成功广播 {count} 个群组"
|
||||
if error_count:
|
||||
result += f"\n广播失败 {error_count} 个群组"
|
||||
await Text(f"发送广播完成!\n{result}").send(reply=True)
|
||||
logger.info(f"发送广播信息: {message}", "广播", session=session)
|
||||
117
zhenxun/builtin_plugins/superuser/broadcast/_data_source.py
Normal file
117
zhenxun/builtin_plugins/superuser/broadcast/_data_source.py
Normal file
@ -0,0 +1,117 @@
|
||||
import nonebot_plugin_alconna as alc
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.discord import Bot as DiscordBot
|
||||
from nonebot.adapters.dodo import Bot as DodoBot
|
||||
from nonebot.adapters.kaiheila import Bot as KaiheilaBot
|
||||
from nonebot.adapters.onebot.v11 import Bot as v11Bot
|
||||
from nonebot.adapters.onebot.v12 import Bot as v12Bot
|
||||
from nonebot_plugin_alconna import UniMsg
|
||||
from nonebot_plugin_saa import (
|
||||
Image,
|
||||
MessageFactory,
|
||||
TargetDoDoChannel,
|
||||
TargetQQGroup,
|
||||
Text,
|
||||
)
|
||||
from nonebot_plugin_session import EventSession
|
||||
from pydantic import BaseModel
|
||||
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
|
||||
class GroupChannel(BaseModel):
|
||||
|
||||
group_id: str
|
||||
"""群组id"""
|
||||
channel_id: str | None = None
|
||||
"""频道id"""
|
||||
|
||||
|
||||
class BroadcastManage:
|
||||
|
||||
@classmethod
|
||||
async def send(
|
||||
cls, bot: Bot, message: UniMsg, session: EventSession
|
||||
) -> tuple[int, int]:
|
||||
"""发送广播消息
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
message: 消息内容
|
||||
session: Session
|
||||
|
||||
返回:
|
||||
tuple[int, int]: 发送成功的群组数量, 发送失败的群组数量
|
||||
"""
|
||||
message_list = []
|
||||
for msg in message:
|
||||
if isinstance(msg, alc.Image) and msg.url:
|
||||
message_list.append(Image(msg.url))
|
||||
elif isinstance(msg, alc.Text):
|
||||
message_list.append(Text(msg.text))
|
||||
if group_list := await cls.__get_group_list(bot):
|
||||
error_count = 0
|
||||
for group in group_list:
|
||||
try:
|
||||
if not await GroupConsole.is_block_task(
|
||||
group.group_id, "broadcast", group.channel_id
|
||||
):
|
||||
if isinstance(bot, (v11Bot, v12Bot)):
|
||||
target = TargetQQGroup(group_id=int(group.group_id))
|
||||
elif isinstance(bot, DodoBot):
|
||||
target = TargetDoDoChannel(channel_id=group.channel_id) # type: ignore
|
||||
await MessageFactory(message_list).send_to(target, bot)
|
||||
logger.debug(
|
||||
"发送成功",
|
||||
"广播",
|
||||
session=session,
|
||||
target=f"{group.group_id}:{group.channel_id}",
|
||||
)
|
||||
except Exception as e:
|
||||
error_count += 1
|
||||
logger.error(
|
||||
"发送失败",
|
||||
"广播",
|
||||
session=session,
|
||||
target=f"{group.group_id}:{group.channel_id}",
|
||||
e=e,
|
||||
)
|
||||
return len(group_list) - error_count, error_count
|
||||
return 0, 0
|
||||
|
||||
@classmethod
|
||||
async def __get_group_list(cls, bot: Bot) -> list[GroupChannel]:
|
||||
"""获取群组id列表
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
|
||||
返回:
|
||||
list[str]: 群组id列表
|
||||
"""
|
||||
if isinstance(bot, (v11Bot, v12Bot)):
|
||||
group_list = await bot.get_group_list()
|
||||
return [GroupChannel(group_id=str(g["group_id"])) for g in group_list]
|
||||
if isinstance(bot, DodoBot):
|
||||
island_list = await bot.get_island_list()
|
||||
source_id_list = [
|
||||
g.island_source_id for g in island_list if g.island_source_id
|
||||
]
|
||||
channel_id_list = []
|
||||
for id in source_id_list:
|
||||
channel_list = await bot.get_channel_list(island_source_id=id)
|
||||
channel_id_list += [
|
||||
GroupChannel(group_id=id, channel_id=c.channel_id)
|
||||
for c in channel_list
|
||||
]
|
||||
return channel_id_list
|
||||
if isinstance(bot, KaiheilaBot):
|
||||
pass
|
||||
# group_list = await bot.guild_list()
|
||||
# if group_list.guilds:
|
||||
# return [g.open_id for g in group_list.guilds if g.open_id]
|
||||
if isinstance(bot, DiscordBot):
|
||||
# TODO: discord获取群组列表
|
||||
pass
|
||||
return []
|
||||
@ -1,21 +1,9 @@
|
||||
|
||||
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.kaiheila.exception import ApiNotAvailable
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot.rule import to_me
|
||||
from nonebot_plugin_alconna import (
|
||||
Alconna,
|
||||
AlconnaMatch,
|
||||
Arparma,
|
||||
Match,
|
||||
Query,
|
||||
Subcommand,
|
||||
UniMessage,
|
||||
on_alconna,
|
||||
store_true,
|
||||
)
|
||||
from nonebot_plugin_alconna import Alconna, on_alconna
|
||||
from nonebot_plugin_alconna.matcher import AlconnaMatcher
|
||||
from nonebot_plugin_saa import Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
161
zhenxun/builtin_plugins/superuser/super_help.py
Normal file
161
zhenxun/builtin_plugins/superuser/super_help.py
Normal file
@ -0,0 +1,161 @@
|
||||
import nonebot
|
||||
from arclet.alconna import Args, Option
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
|
||||
from nonebot_plugin_alconna.matcher import AlconnaMatcher
|
||||
from nonebot_plugin_saa import Image, Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
|
||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.exception import EmptyError
|
||||
from zhenxun.utils.image_utils import (
|
||||
BuildImage,
|
||||
build_sort_image,
|
||||
group_image,
|
||||
text2image,
|
||||
)
|
||||
from zhenxun.utils.rules import admin_check, ensure_group
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="超级用户帮助",
|
||||
description="超级用户帮助",
|
||||
usage="""
|
||||
超级用户帮助
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.SUPERUSER,
|
||||
).dict(),
|
||||
)
|
||||
|
||||
_matcher = on_alconna(
|
||||
Alconna("超级用户帮助"),
|
||||
permission=SUPERUSER,
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
|
||||
SUPERUSER_HELP_IMAGE = IMAGE_PATH / "SUPERUSER_HELP.png"
|
||||
if SUPERUSER_HELP_IMAGE.exists():
|
||||
SUPERUSER_HELP_IMAGE.unlink()
|
||||
|
||||
|
||||
async def build_help() -> BuildImage:
|
||||
"""构造超级用户帮助图片
|
||||
|
||||
异常:
|
||||
EmptyError: 超级用户帮助为空
|
||||
|
||||
返回:
|
||||
BuildImage: 超级用户帮助图片
|
||||
"""
|
||||
plugin_list = await PluginInfo.filter(plugin_type=PluginType.SUPERUSER).all()
|
||||
data_list = []
|
||||
for plugin in plugin_list:
|
||||
if _plugin := nonebot.get_plugin_by_module_name(plugin.module_path):
|
||||
if _plugin.metadata:
|
||||
data_list.append({"plugin": plugin, "metadata": _plugin.metadata})
|
||||
font = BuildImage.load_font("HYWenHei-85W.ttf", 20)
|
||||
image_list = []
|
||||
for data in data_list:
|
||||
plugin = data["plugin"]
|
||||
metadata = data["metadata"]
|
||||
try:
|
||||
usage = None
|
||||
description = None
|
||||
if metadata.usage:
|
||||
usage = await text2image(
|
||||
metadata.usage,
|
||||
padding=5,
|
||||
color=(255, 255, 255),
|
||||
font_color=(0, 0, 0),
|
||||
)
|
||||
if metadata.description:
|
||||
description = await text2image(
|
||||
metadata.description,
|
||||
padding=5,
|
||||
color=(255, 255, 255),
|
||||
font_color=(0, 0, 0),
|
||||
)
|
||||
width = 0
|
||||
height = 100
|
||||
if usage:
|
||||
width = usage.width
|
||||
height += usage.height
|
||||
if description and description.width > width:
|
||||
width = description.width
|
||||
height += description.height
|
||||
font_width, font_height = BuildImage.get_text_size(
|
||||
plugin.name + f"[{plugin.level}]", font
|
||||
)
|
||||
if font_width > width:
|
||||
width = font_width
|
||||
A = BuildImage(width + 30, height + 120, "#EAEDF2")
|
||||
await A.text((15, 10), plugin.name + f"[{plugin.level}]")
|
||||
await A.text((15, 70), "简介:")
|
||||
if not description:
|
||||
description = BuildImage(A.width - 30, 30, (255, 255, 255))
|
||||
await description.circle_corner(10)
|
||||
await A.paste(description, (15, 100))
|
||||
if not usage:
|
||||
usage = BuildImage(A.width - 30, 30, (255, 255, 255))
|
||||
await usage.circle_corner(10)
|
||||
await A.text((15, description.height + 115), "用法:")
|
||||
await A.paste(usage, (15, description.height + 145))
|
||||
await A.circle_corner(10)
|
||||
image_list.append(A)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"获取超级用户管理员插件 {plugin.module}: {plugin.name} 设置失败...",
|
||||
"超级用户帮助",
|
||||
e=e,
|
||||
)
|
||||
if task_list := await TaskInfo.all():
|
||||
task_str = "\n".join([task.name for task in task_list])
|
||||
task_str = "通过 开启/关闭 来控制群被动\n----------\n" + task_str
|
||||
task_image = await text2image(task_str, padding=5, color=(255, 255, 255))
|
||||
await task_image.circle_corner(10)
|
||||
A = BuildImage(task_image.width + 50, task_image.height + 85, "#EAEDF2")
|
||||
await A.text((25, 10), "被动技能")
|
||||
await A.paste(task_image, (25, 50))
|
||||
await A.circle_corner(10)
|
||||
image_list.append(A)
|
||||
if not image_list:
|
||||
raise EmptyError()
|
||||
image_group, _ = group_image(image_list)
|
||||
A = await build_sort_image(image_group, color=(255, 255, 255), padding_top=160)
|
||||
text = await BuildImage.build_text_image(
|
||||
"超级用户帮助",
|
||||
size=40,
|
||||
)
|
||||
tip = await BuildImage.build_text_image(
|
||||
"注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数", size=25, font_color="red"
|
||||
)
|
||||
await A.paste(text, (50, 30))
|
||||
await A.paste(tip, (50, 90))
|
||||
await A.save(SUPERUSER_HELP_IMAGE)
|
||||
return BuildImage(1, 1)
|
||||
|
||||
|
||||
@_matcher.handle()
|
||||
async def _(
|
||||
session: EventSession,
|
||||
matcher: AlconnaMatcher,
|
||||
arparma: Arparma,
|
||||
):
|
||||
if not SUPERUSER_HELP_IMAGE.exists():
|
||||
try:
|
||||
await build_help()
|
||||
except EmptyError:
|
||||
await Text("超级用户帮助为空").finish(reply=True)
|
||||
await Image(SUPERUSER_HELP_IMAGE).send()
|
||||
logger.info("查看超级用户帮助", arparma.header_result, session=session)
|
||||
@ -1,119 +0,0 @@
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.kaiheila.exception import ApiNotAvailable
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot.rule import to_me
|
||||
from nonebot_plugin_alconna import Alconna, Arparma, At, Match, on_alconna
|
||||
from nonebot_plugin_saa import Mention, MessageFactory, Text
|
||||
from nonebot_plugin_session import EventSession, SessionLevel
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
from zhenxun.models.friend_user import FriendUser
|
||||
from zhenxun.models.group_info import GroupInfo
|
||||
from zhenxun.models.level_user import LevelUser
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="更新群组/好友信息",
|
||||
description="更新群组/好友信息",
|
||||
usage="""
|
||||
更新群组信息
|
||||
更新好友信息
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.SUPERUSER,
|
||||
).dict(),
|
||||
)
|
||||
|
||||
|
||||
_group_matcher = on_alconna(
|
||||
Alconna(
|
||||
"更新群组信息",
|
||||
),
|
||||
permission=SUPERUSER,
|
||||
rule=to_me(),
|
||||
priority=1,
|
||||
block=True,
|
||||
)
|
||||
|
||||
_friend_matcher = on_alconna(
|
||||
Alconna(
|
||||
"更新好友信息",
|
||||
),
|
||||
permission=SUPERUSER,
|
||||
rule=to_me(),
|
||||
priority=1,
|
||||
block=True,
|
||||
)
|
||||
|
||||
# TODO: 其他adapter的更新操作
|
||||
|
||||
@_group_matcher.handle()
|
||||
async def _(
|
||||
bot: Bot,
|
||||
session: EventSession,
|
||||
arparma: Arparma,
|
||||
):
|
||||
try:
|
||||
gl = await bot.get_group_list()
|
||||
gl = [g["group_id"] for g in gl]
|
||||
num = 0
|
||||
for g in gl:
|
||||
try:
|
||||
group_info = await bot.get_group_info(group_id=g)
|
||||
await GroupInfo.update_or_create(
|
||||
group_id=str(group_info["group_id"]),
|
||||
defaults={
|
||||
"group_name": group_info["group_name"],
|
||||
"max_member_count": group_info["max_member_count"],
|
||||
"member_count": group_info["member_count"],
|
||||
},
|
||||
)
|
||||
num += 1
|
||||
logger.debug(
|
||||
"群聊信息更新成功", "更新群信息", session=session, target=group_info["group_id"]
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"更新群聊信息失败",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
target=g,
|
||||
)
|
||||
await Text(f"成功更新了 {len(gl)} 个群的信息").send()
|
||||
logger.info(
|
||||
f"更新群聊信息完成,共更新了 {len(gl)} 个群的信息", arparma.header_result, session=session
|
||||
)
|
||||
except (ApiNotAvailable, AttributeError) as e:
|
||||
await Text("Api未实现...").send()
|
||||
except Exception as e:
|
||||
logger.error("更新好友信息发生错误", arparma.header_result, session=session, e=e)
|
||||
await Text("其他未知错误...").send()
|
||||
|
||||
|
||||
@_friend_matcher.assign("delete")
|
||||
async def _(
|
||||
bot: Bot,
|
||||
session: EventSession,
|
||||
arparma: Arparma,
|
||||
):
|
||||
num = 0
|
||||
error_list = []
|
||||
fl = await bot.get_friend_list()
|
||||
for f in fl:
|
||||
try:
|
||||
await FriendUser.update_or_create(
|
||||
user_id=str(f["user_id"]), defaults={"nickname": f["nickname"]}
|
||||
)
|
||||
logger.debug(f"更新好友信息成功", "更新好友信息", session=session, target=f["user_id"])
|
||||
num += 1
|
||||
except Exception as e:
|
||||
logger.error(f"更新好友信息失败", "更新好友信息", session=session, target=f["user_id"], e=e)
|
||||
await Text(f"成功更新了 {num} 个好友的信息!").send()
|
||||
if error_list:
|
||||
await Text(f"以下好友更新失败:\n" + "\n".join(error_list)).send()
|
||||
logger.info(f"更新好友信息完成,共更新了 {num} 个群的信息", arparma.header_result, session=session)
|
||||
92
zhenxun/builtin_plugins/superuser/update_fg_info/__init__.py
Normal file
92
zhenxun/builtin_plugins/superuser/update_fg_info/__init__.py
Normal file
@ -0,0 +1,92 @@
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.kaiheila.exception import ApiNotAvailable
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot.rule import to_me
|
||||
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
|
||||
from nonebot_plugin_saa import Text
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
from zhenxun.models.friend_user import FriendUser
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
|
||||
from ._data_source import FgUpdateManage
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="更新群组/好友信息",
|
||||
description="更新群组/好友信息",
|
||||
usage="""
|
||||
更新群组信息
|
||||
更新好友信息
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.SUPERUSER,
|
||||
).dict(),
|
||||
)
|
||||
|
||||
|
||||
_group_matcher = on_alconna(
|
||||
Alconna(
|
||||
"更新群组信息",
|
||||
),
|
||||
permission=SUPERUSER,
|
||||
rule=to_me(),
|
||||
priority=1,
|
||||
block=True,
|
||||
)
|
||||
|
||||
_friend_matcher = on_alconna(
|
||||
Alconna(
|
||||
"更新好友信息",
|
||||
),
|
||||
permission=SUPERUSER,
|
||||
rule=to_me(),
|
||||
priority=1,
|
||||
block=True,
|
||||
)
|
||||
|
||||
|
||||
@_group_matcher.handle()
|
||||
async def _(
|
||||
bot: Bot,
|
||||
session: EventSession,
|
||||
arparma: Arparma,
|
||||
):
|
||||
try:
|
||||
num = await FgUpdateManage.update_group(bot, session.platform)
|
||||
logger.info(
|
||||
f"更新群聊信息完成,共更新了 {num} 个群组的信息!",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
await Text(f"成功更新了 {num} 个群组的信息").send()
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"更新群组信息发生错误", arparma.header_result, session=session, e=e
|
||||
)
|
||||
await Text("其他未知错误...").send()
|
||||
|
||||
|
||||
@_friend_matcher.handle()
|
||||
async def _(
|
||||
bot: Bot,
|
||||
session: EventSession,
|
||||
arparma: Arparma,
|
||||
):
|
||||
try:
|
||||
num = await FgUpdateManage.update_friend(bot, session.platform)
|
||||
logger.info(
|
||||
f"更新好友信息完成,共更新了 {num} 个好友的信息!",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
await Text(f"成功更新了 {num} 个好友的信息").send()
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"更新好友信息发生错误", arparma.header_result, session=session, e=e
|
||||
)
|
||||
await Text("其他未知错误...").send()
|
||||
156
zhenxun/builtin_plugins/superuser/update_fg_info/_data_source.py
Normal file
156
zhenxun/builtin_plugins/superuser/update_fg_info/_data_source.py
Normal file
@ -0,0 +1,156 @@
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.discord import Bot as DiscordBot
|
||||
from nonebot.adapters.dodo import Bot as DodoBot
|
||||
from nonebot.adapters.kaiheila import Bot as KaiheilaBot
|
||||
from nonebot.adapters.onebot.v11 import Bot as v11Bot
|
||||
from nonebot.adapters.onebot.v12 import Bot as v12Bot
|
||||
|
||||
from zhenxun.models.friend_user import FriendUser
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
|
||||
class FgUpdateManage:
|
||||
|
||||
@classmethod
|
||||
async def update_group(cls, bot: Bot, platform: str) -> int:
|
||||
"""更新群组信息
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
platform: 平台
|
||||
|
||||
返回:
|
||||
int: 更新个数
|
||||
"""
|
||||
create_list = []
|
||||
if group_list := await cls.__get_group_list(bot):
|
||||
exists_group_list = await GroupConsole.all().values_list(
|
||||
"group_id", "channel_id"
|
||||
)
|
||||
for group in group_list:
|
||||
group.platform = platform
|
||||
if (group.group_id, group.channel_id) not in exists_group_list:
|
||||
create_list.append(group)
|
||||
logger.debug(
|
||||
"群聊信息更新成功",
|
||||
"更新群信息",
|
||||
target=f"{group.group_id}:{group.channel_id}",
|
||||
)
|
||||
if create_list:
|
||||
await GroupConsole.bulk_create(create_list, 10)
|
||||
return len(create_list)
|
||||
|
||||
@classmethod
|
||||
async def __get_group_list(cls, bot: Bot) -> list[GroupConsole]:
|
||||
"""获取群组列表
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
|
||||
返回:
|
||||
list[GroupConsole]: 群组列表
|
||||
"""
|
||||
if isinstance(bot, v11Bot):
|
||||
group_list = await bot.get_group_list()
|
||||
return [
|
||||
GroupConsole(
|
||||
group_id=str(g["group_id"]),
|
||||
group_name=g["group_name"],
|
||||
max_member_count=g["max_member_count"],
|
||||
member_count=g["member_count"],
|
||||
)
|
||||
for g in group_list
|
||||
]
|
||||
if isinstance(bot, v12Bot):
|
||||
group_list = await bot.get_group_list()
|
||||
return [
|
||||
GroupConsole(
|
||||
group_id=g.group_id, # type: ignore
|
||||
user_name=g.group_name, # type: ignore
|
||||
)
|
||||
for g in group_list
|
||||
]
|
||||
if isinstance(bot, DodoBot):
|
||||
island_list = await bot.get_island_list()
|
||||
source_id_list = [
|
||||
(g.island_source_id, g.island_name)
|
||||
for g in island_list
|
||||
if g.island_source_id
|
||||
]
|
||||
group_list = []
|
||||
for id, name in source_id_list:
|
||||
channel_list = await bot.get_channel_list(island_source_id=id)
|
||||
group_list.append(GroupConsole(group_id=id, group_name=name))
|
||||
group_list += [
|
||||
GroupConsole(
|
||||
group_id=id, group_name=c.channel_name, channel_id=c.channel_id
|
||||
)
|
||||
for c in channel_list
|
||||
]
|
||||
return group_list
|
||||
if isinstance(bot, KaiheilaBot):
|
||||
# TODO: kaiheila群组列表
|
||||
pass
|
||||
if isinstance(bot, DiscordBot):
|
||||
# TODO: discord群组列表
|
||||
pass
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
async def update_friend(cls, bot: Bot, platform: str) -> int:
|
||||
"""更新好友信息
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
platform: 平台
|
||||
|
||||
返回:
|
||||
int: 更新个数
|
||||
"""
|
||||
create_list = []
|
||||
if friend_list := await cls.__get_friend_list(bot):
|
||||
user_id_list = await FriendUser.all().values_list("user_id", flat=True)
|
||||
for friend in friend_list:
|
||||
friend.platform = platform
|
||||
if friend.user_id not in user_id_list:
|
||||
create_list.append(friend)
|
||||
if create_list:
|
||||
await FriendUser.bulk_create(create_list, 10)
|
||||
return len(create_list)
|
||||
|
||||
@classmethod
|
||||
async def __get_friend_list(cls, bot: Bot) -> list[FriendUser]:
|
||||
"""获取好友列表
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
|
||||
返回:
|
||||
list[FriendUser]: 好友列表
|
||||
"""
|
||||
if isinstance(bot, v11Bot):
|
||||
friend_list = await bot.get_friend_list()
|
||||
return [
|
||||
FriendUser(user_id=str(f["user_id"]), user_name=f["nickname"])
|
||||
for f in friend_list
|
||||
]
|
||||
if isinstance(bot, v12Bot):
|
||||
friend_list = await bot.get_friend_list()
|
||||
return [
|
||||
FriendUser(
|
||||
user_id=f.user_id, # type: ignore
|
||||
user_name=f.user_displayname or f.user_remark or f.user_name, # type: ignore
|
||||
)
|
||||
for f in friend_list
|
||||
]
|
||||
if isinstance(bot, DodoBot):
|
||||
# TODO: dodo好友列表
|
||||
pass
|
||||
if isinstance(bot, KaiheilaBot):
|
||||
# TODO: kaiheila好友列表
|
||||
pass
|
||||
if isinstance(bot, DiscordBot):
|
||||
# TODO: discord好友列表
|
||||
pass
|
||||
return []
|
||||
@ -1,6 +1,6 @@
|
||||
import copy
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, List, Type, Union
|
||||
from typing import Any, Callable, Dict, Type
|
||||
|
||||
import cattrs
|
||||
from pydantic import BaseModel
|
||||
@ -17,7 +17,6 @@ _yaml.allow_unicode = True
|
||||
|
||||
|
||||
class RegisterConfig(BaseModel):
|
||||
|
||||
"""
|
||||
注册配置项
|
||||
"""
|
||||
@ -39,7 +38,6 @@ class RegisterConfig(BaseModel):
|
||||
|
||||
|
||||
class ConfigModel(BaseModel):
|
||||
|
||||
"""
|
||||
配置项
|
||||
"""
|
||||
@ -57,7 +55,6 @@ class ConfigModel(BaseModel):
|
||||
|
||||
|
||||
class ConfigGroup(BaseModel):
|
||||
|
||||
"""
|
||||
配置组
|
||||
"""
|
||||
@ -72,7 +69,7 @@ class ConfigGroup(BaseModel):
|
||||
def get(self, c: str, default: Any = None) -> Any:
|
||||
cfg = self.configs.get(c)
|
||||
if cfg is not None:
|
||||
return cfg
|
||||
return cfg.value
|
||||
return default
|
||||
|
||||
|
||||
@ -130,6 +127,17 @@ class PluginSetting(BaseModel):
|
||||
"""调用插件花费金币"""
|
||||
|
||||
|
||||
class Task(BaseBlock):
|
||||
module: str
|
||||
"""被动技能模块名"""
|
||||
name: str
|
||||
"""被动技能名称"""
|
||||
status: bool = True
|
||||
"""全局开关状态"""
|
||||
run_time: str | None = None
|
||||
"""运行时间"""
|
||||
|
||||
|
||||
class PluginExtraData(BaseModel):
|
||||
"""
|
||||
插件扩展信息
|
||||
@ -139,18 +147,20 @@ class PluginExtraData(BaseModel):
|
||||
"""作者"""
|
||||
version: str | None = None
|
||||
"""版本"""
|
||||
plugin_type: PluginType | None = None
|
||||
plugin_type: PluginType = PluginType.NORMAL
|
||||
"""插件类型"""
|
||||
menu_type: str = "功能"
|
||||
"""菜单类型"""
|
||||
admin_level: int | None = None
|
||||
"""管理员插件所需权限等级"""
|
||||
configs: List[RegisterConfig] | None = None
|
||||
configs: list[RegisterConfig] | None = None
|
||||
"""插件配置"""
|
||||
setting: PluginSetting | None = None
|
||||
"""插件基本配置"""
|
||||
limits: List[BaseBlock | PluginCdBlock | PluginCountBlock] | None = None
|
||||
limits: list[BaseBlock | PluginCdBlock | PluginCountBlock] | None = None
|
||||
"""插件限制"""
|
||||
tasks: list[Task] | None = None
|
||||
"""技能被动"""
|
||||
|
||||
|
||||
class NoSuchConfig(Exception):
|
||||
@ -287,7 +297,9 @@ class ConfigsManager:
|
||||
if not config:
|
||||
config = self._data[module].configs.get(f"{key} [LEVEL]")
|
||||
if not config:
|
||||
raise NoSuchConfig(f"未查询到配置项 MODULE: [ {module} ] | KEY: [ {key} ]")
|
||||
raise NoSuchConfig(
|
||||
f"未查询到配置项 MODULE: [ {module} ] | KEY: [ {key} ]"
|
||||
)
|
||||
if config.arg_parser:
|
||||
value = config.arg_parser(value or config.default_value)
|
||||
else:
|
||||
|
||||
160
zhenxun/models/bag_user.py
Normal file
160
zhenxun/models/bag_user.py
Normal file
@ -0,0 +1,160 @@
|
||||
# from typing import Dict
|
||||
|
||||
# from services.db_context import Model
|
||||
# from tortoise import fields
|
||||
|
||||
# from .goods_info import GoodsInfo
|
||||
|
||||
|
||||
# class BagUser(Model):
|
||||
|
||||
# id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
||||
# """自增id"""
|
||||
# user_id = fields.CharField(255)
|
||||
# """用户id"""
|
||||
# group_id = fields.CharField(255)
|
||||
# """群聊id"""
|
||||
# gold = fields.IntField(default=100)
|
||||
# """金币数量"""
|
||||
# spend_total_gold = fields.IntField(default=0)
|
||||
# """花费金币总数"""
|
||||
# get_total_gold = fields.IntField(default=0)
|
||||
# """获取金币总数"""
|
||||
# get_today_gold = fields.IntField(default=0)
|
||||
# """今日获取金币"""
|
||||
# spend_today_gold = fields.IntField(default=0)
|
||||
# """今日获取金币"""
|
||||
# property: Dict[str, int] = fields.JSONField(default={}) # type: ignore
|
||||
# """道具"""
|
||||
|
||||
# class Meta:
|
||||
# table = "bag_users"
|
||||
# table_description = "用户道具数据表"
|
||||
# unique_together = ("user_id", "group_id")
|
||||
|
||||
# @classmethod
|
||||
# async def get_gold(cls, user_id: str, group_id: str) -> int:
|
||||
# """获取当前金币
|
||||
|
||||
# 参数:
|
||||
# user_id: 用户id
|
||||
# group_id: 所在群组id
|
||||
|
||||
# 返回:
|
||||
# int: 金币数量
|
||||
# """
|
||||
# user, _ = await cls.get_or_create(user_id=user_id, group_id=group_id)
|
||||
# return user.gold
|
||||
|
||||
# @classmethod
|
||||
# async def get_property(
|
||||
# cls, user_id: str, group_id: str, only_active: bool = False
|
||||
# ) -> Dict[str, int]:
|
||||
# """获取当前道具
|
||||
|
||||
# 参数:
|
||||
# user_id: 用户id
|
||||
# group_id: 所在群组id
|
||||
# only_active: 仅仅获取主动使用的道具
|
||||
|
||||
# 返回:
|
||||
# Dict[str, int]: 道具名称与数量
|
||||
# """
|
||||
# user, _ = await cls.get_or_create(user_id=user_id, group_id=group_id)
|
||||
# if only_active and user.property:
|
||||
# data = {}
|
||||
# name_list = [
|
||||
# x.goods_name
|
||||
# for x in await GoodsInfo.get_all_goods()
|
||||
# if not x.is_passive
|
||||
# ]
|
||||
# for key in [x for x in user.property if x in name_list]:
|
||||
# data[key] = user.property[key]
|
||||
# return data
|
||||
# return user.property
|
||||
|
||||
# @classmethod
|
||||
# async def add_gold(cls, user_id: str, group_id: str, num: int):
|
||||
# """增加金币
|
||||
|
||||
# 参数:
|
||||
# user_id: 用户id
|
||||
# group_id: 所在群组id
|
||||
# num: 金币数量
|
||||
# """
|
||||
# user, _ = await cls.get_or_create(user_id=user_id, group_id=group_id)
|
||||
# user.gold = user.gold + num
|
||||
# user.get_total_gold = user.get_total_gold + num
|
||||
# user.get_today_gold = user.get_today_gold + num
|
||||
# await user.save(update_fields=["gold", "get_today_gold", "get_total_gold"])
|
||||
|
||||
# @classmethod
|
||||
# async def spend_gold(cls, user_id: str, group_id: str, num: int):
|
||||
# """花费金币
|
||||
|
||||
# 参数:
|
||||
# user_id: 用户id
|
||||
# group_id: 所在群组id
|
||||
# num: 金币数量
|
||||
# """
|
||||
# user, _ = await cls.get_or_create(user_id=str(user_id), group_id=str(group_id))
|
||||
# user.gold = user.gold - num
|
||||
# user.spend_total_gold = user.spend_total_gold + num
|
||||
# user.spend_today_gold = user.spend_today_gold + num
|
||||
# await user.save(update_fields=["gold", "spend_total_gold", "spend_today_gold"])
|
||||
|
||||
# @classmethod
|
||||
# async def add_property(cls, user_id: str, group_id: str, name: str, num: int = 1):
|
||||
# """增加道具
|
||||
|
||||
# 参数:
|
||||
# user_id: 用户id
|
||||
# group_id: 所在群组id
|
||||
# name: 道具名称
|
||||
# num: 道具数量
|
||||
# """
|
||||
# user, _ = await cls.get_or_create(user_id=str(user_id), group_id=str(group_id))
|
||||
# property_ = user.property
|
||||
# if property_.get(name) is None:
|
||||
# property_[name] = 0
|
||||
# property_[name] += num
|
||||
# user.property = property_
|
||||
# await user.save(update_fields=["property"])
|
||||
|
||||
# @classmethod
|
||||
# async def delete_property(
|
||||
# cls, user_id: str, group_id: str, name: str, num: int = 1
|
||||
# ) -> bool:
|
||||
# """使用/删除 道具
|
||||
|
||||
# 参数:
|
||||
# user_id: 用户id
|
||||
# group_id: 所在群组id
|
||||
# name: 道具名称
|
||||
# num: 使用个数
|
||||
|
||||
# 返回:
|
||||
# bool: 是否使用/删除成功
|
||||
# """
|
||||
# user, _ = await cls.get_or_create(user_id=str(user_id), group_id=str(group_id))
|
||||
# property_ = user.property
|
||||
# if name in property_:
|
||||
# if (n := property_.get(name, 0)) < num:
|
||||
# return False
|
||||
# if n == num:
|
||||
# del property_[name]
|
||||
# else:
|
||||
# property_[name] -= num
|
||||
# await user.save(update_fields=["property"])
|
||||
# return True
|
||||
# return False
|
||||
|
||||
# @classmethod
|
||||
# async def _run_script(cls):
|
||||
# return [
|
||||
# "ALTER TABLE bag_users DROP props;", # 删除 props 字段
|
||||
# "ALTER TABLE bag_users RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id
|
||||
# "ALTER TABLE bag_users ALTER COLUMN user_id TYPE character varying(255);",
|
||||
# # 将user_id字段类型改为character varying(255)
|
||||
# "ALTER TABLE bag_users ALTER COLUMN group_id TYPE character varying(255);",
|
||||
# ]
|
||||
172
zhenxun/models/ban_console.py
Normal file
172
zhenxun/models/ban_console.py
Normal file
@ -0,0 +1,172 @@
|
||||
import time
|
||||
|
||||
from tortoise import fields
|
||||
from typing_extensions import Self
|
||||
|
||||
from zhenxun.services.db_context import Model
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.exception import UserAndGroupIsNone
|
||||
|
||||
|
||||
class BanConsole(Model):
|
||||
|
||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
||||
"""自增id"""
|
||||
user_id = fields.CharField(255, null=True)
|
||||
"""用户id"""
|
||||
group_id = fields.CharField(255, null=True)
|
||||
"""群组id"""
|
||||
ban_level = fields.IntField()
|
||||
"""使用ban命令的用户等级"""
|
||||
ban_time = fields.BigIntField()
|
||||
"""ban开始的时间"""
|
||||
duration = fields.BigIntField()
|
||||
"""ban时长"""
|
||||
operator = fields.CharField(255)
|
||||
"""使用Ban命令的用户"""
|
||||
|
||||
class Meta:
|
||||
table = "ban_console"
|
||||
table_description = ".ban/b了 封禁人员/群组数据表"
|
||||
|
||||
@classmethod
|
||||
async def _get_data(cls, user_id: str | None, group_id: str | None) -> Self | None:
|
||||
"""获取数据
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
|
||||
异常:
|
||||
UserAndGroupIsNone: 用户id和群组id都为空
|
||||
|
||||
返回:
|
||||
Self | None: Self
|
||||
"""
|
||||
if not user_id and not group_id:
|
||||
raise UserAndGroupIsNone()
|
||||
user = None
|
||||
if user_id:
|
||||
if group_id:
|
||||
user = await cls.get_or_none(user_id=user_id, group_id=group_id)
|
||||
else:
|
||||
user = await cls.get_or_none(user_id=user_id, group_id__isnull=True)
|
||||
else:
|
||||
if group_id:
|
||||
user = await cls.get_or_none(user_id__isnull=True, group_id=group_id)
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
async def check_ban_level(
|
||||
cls, user_id: str | None, group_id: str | None, level: int
|
||||
) -> bool:
|
||||
"""检测ban掉目标的用户与unban用户的权限等级大小
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
level: 权限等级
|
||||
|
||||
返回:
|
||||
bool: 权限判断
|
||||
"""
|
||||
user = await cls._get_data(user_id, group_id)
|
||||
if user:
|
||||
logger.debug(
|
||||
f"检测用户被ban等级,user_level: {user.ban_level},level: {level}",
|
||||
target=f"{group_id}:{user_id}",
|
||||
)
|
||||
return bool(user and user.ban_level >= level)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def check_ban_time(
|
||||
cls, user_id: str | None, group_id: str | None = None
|
||||
) -> int:
|
||||
"""检测用户被ban时长
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
|
||||
返回:
|
||||
int: ban剩余时长,-1时为永久ban,0表示未被ban
|
||||
"""
|
||||
logger.debug(f"获取用户ban时长", target=f"{group_id}:{user_id}")
|
||||
user = await cls._get_data(user_id, group_id)
|
||||
if user:
|
||||
if user.duration == -1:
|
||||
return -1
|
||||
_time = time.time() - (user.ban_time + user.duration)
|
||||
if _time > 0:
|
||||
return 0
|
||||
return int(time.time() - user.ban_time - user.duration)
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
async def is_ban(cls, user_id: str | None, group_id: str | None = None) -> bool:
|
||||
"""判断用户是否被ban
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
|
||||
返回:
|
||||
bool: 是否被ban
|
||||
"""
|
||||
logger.debug(f"检测是否被ban", target=f"{group_id}:{user_id}")
|
||||
if await cls.check_ban_time(user_id, group_id):
|
||||
return True
|
||||
else:
|
||||
await cls.unban(user_id, group_id)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def ban(
|
||||
cls,
|
||||
user_id: str | None,
|
||||
group_id: str | None,
|
||||
ban_level: int,
|
||||
duration: int,
|
||||
operator: str | None,
|
||||
):
|
||||
"""ban掉目标用户
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
ban_level: 使用命令者的权限等级
|
||||
duration: 时长,秒
|
||||
operator: 操作者id
|
||||
"""
|
||||
logger.debug(
|
||||
f"封禁用户,等级:{ban_level},时长: {duration}",
|
||||
target=f"{group_id}:{user_id}",
|
||||
)
|
||||
user = await cls._get_data(user_id, group_id)
|
||||
if user:
|
||||
await cls.unban(user_id, group_id)
|
||||
await cls.create(
|
||||
user_id=user_id,
|
||||
group_id=group_id,
|
||||
ban_level=ban_level,
|
||||
ban_time=int(time.time()),
|
||||
duration=duration,
|
||||
operator=operator or 0,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def unban(cls, user_id: str | None, group_id: str | None = None) -> bool:
|
||||
"""unban用户
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
|
||||
返回:
|
||||
bool: 是否被ban
|
||||
"""
|
||||
user = await cls._get_data(user_id, group_id)
|
||||
if user:
|
||||
logger.debug("解除封禁", target=f"{group_id}:{user_id}")
|
||||
await user.delete()
|
||||
return True
|
||||
return False
|
||||
125
zhenxun/models/chat_history.py
Normal file
125
zhenxun/models/chat_history.py
Normal file
@ -0,0 +1,125 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Literal, Tuple
|
||||
|
||||
from tortoise import fields
|
||||
from tortoise.functions import Count
|
||||
from typing_extensions import Self
|
||||
|
||||
from zhenxun.services.db_context import Model
|
||||
|
||||
|
||||
class ChatHistory(Model):
|
||||
|
||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
||||
"""自增id"""
|
||||
user_id = fields.CharField(255)
|
||||
"""用户id"""
|
||||
group_id = fields.CharField(255, null=True)
|
||||
"""群聊id"""
|
||||
text = fields.TextField(null=True)
|
||||
"""文本内容"""
|
||||
plain_text = fields.TextField(null=True)
|
||||
"""纯文本"""
|
||||
create_time = fields.DatetimeField(auto_now_add=True)
|
||||
"""创建时间"""
|
||||
bot_id = fields.CharField(255, null=True)
|
||||
"""bot记录id"""
|
||||
platform = fields.CharField(255, null=True)
|
||||
"""平台"""
|
||||
|
||||
class Meta:
|
||||
table = "chat_history"
|
||||
table_description = "聊天记录数据表"
|
||||
|
||||
@classmethod
|
||||
async def get_group_msg_rank(
|
||||
cls,
|
||||
gid: str,
|
||||
limit: int = 10,
|
||||
order: str = "DESC",
|
||||
date_scope: tuple[datetime, datetime] | None = None,
|
||||
) -> list[Self]:
|
||||
"""获取排行数据
|
||||
|
||||
参数:
|
||||
gid: 群号
|
||||
limit: 获取数量
|
||||
order: 排序类型,desc,des
|
||||
date_scope: 日期范围
|
||||
"""
|
||||
o = "-" if order == "DESC" else ""
|
||||
query = cls.filter(group_id=gid)
|
||||
if date_scope:
|
||||
query = query.filter(create_time__range=date_scope)
|
||||
return list(
|
||||
await query.annotate(count=Count("user_id"))
|
||||
.order_by(o + "count")
|
||||
.group_by("user_id")
|
||||
.limit(limit)
|
||||
.values_list("user_id", "count")
|
||||
) # type: ignore
|
||||
|
||||
@classmethod
|
||||
async def get_group_first_msg_datetime(cls, group_id: str) -> datetime | None:
|
||||
"""获取群第一条记录消息时间
|
||||
|
||||
参数:
|
||||
group_id: 群组id
|
||||
"""
|
||||
if (
|
||||
message := await cls.filter(group_id=group_id)
|
||||
.order_by("create_time")
|
||||
.first()
|
||||
):
|
||||
return message.create_time
|
||||
|
||||
@classmethod
|
||||
async def get_message(
|
||||
cls,
|
||||
uid: str,
|
||||
gid: str,
|
||||
type_: Literal["user", "group"],
|
||||
msg_type: Literal["private", "group"] | None = None,
|
||||
days: int | Tuple[datetime, datetime] | None = None,
|
||||
) -> list[Self]:
|
||||
"""获取消息查询query
|
||||
|
||||
参数:
|
||||
uid: 用户id
|
||||
gid: 群聊id
|
||||
type_: 类型,私聊或群聊
|
||||
msg_type: 消息类型,用户或群聊
|
||||
days: 限制日期
|
||||
"""
|
||||
if type_ == "user":
|
||||
query = cls.filter(user_id=uid)
|
||||
if msg_type == "private":
|
||||
query = query.filter(group_id__isnull=True)
|
||||
elif msg_type == "group":
|
||||
query = query.filter(group_id__not_isnull=True)
|
||||
else:
|
||||
query = cls.filter(group_id=gid)
|
||||
if uid:
|
||||
query = query.filter(user_id=uid)
|
||||
if days:
|
||||
if isinstance(days, int):
|
||||
query = query.filter(
|
||||
create_time__gte=datetime.now() - timedelta(days=days)
|
||||
)
|
||||
elif isinstance(days, tuple):
|
||||
query = query.filter(create_time__range=days)
|
||||
return await query.all() # type: ignore
|
||||
|
||||
@classmethod
|
||||
async def _run_script(cls):
|
||||
return [
|
||||
"alter table chat_history alter group_id drop not null;", # 允许 group_id 为空
|
||||
"alter table chat_history alter text drop not null;", # 允许 text 为空
|
||||
"alter table chat_history alter plain_text drop not null;", # 允许 plain_text 为空
|
||||
"ALTER TABLE chat_history RENAME COLUMN user_qq TO user_id;", # 将user_id改为user_id
|
||||
"ALTER TABLE chat_history ALTER COLUMN user_id TYPE character varying(255);",
|
||||
"ALTER TABLE chat_history ALTER COLUMN group_id TYPE character varying(255);",
|
||||
"ALTER TABLE chat_history ADD bot_id VARCHAR(255);", # 添加bot_id字段
|
||||
"ALTER TABLE chat_history ALTER COLUMN bot_id TYPE character varying(255);",
|
||||
"ALTER TABLE chat_history ADD COLUMN platform character varying(255);",
|
||||
]
|
||||
@ -15,32 +15,32 @@ class FriendUser(Model):
|
||||
"""用户名称"""
|
||||
nickname = fields.CharField(max_length=255, null=True, description="用户自定义昵称")
|
||||
"""私聊下自定义昵称"""
|
||||
platform = fields.CharField(255, null=True, description="平台")
|
||||
"""平台"""
|
||||
|
||||
class Meta:
|
||||
table = "friend_users"
|
||||
table_description = "好友信息数据表"
|
||||
|
||||
@classmethod
|
||||
async def get_user_name(cls, user_id: Union[int, str]) -> str:
|
||||
"""
|
||||
说明:
|
||||
获取好友用户名称
|
||||
async def get_user_name(cls, user_id: str) -> str:
|
||||
"""获取好友用户名称
|
||||
|
||||
参数:
|
||||
:param user_id: 用户id
|
||||
user_id: 用户id
|
||||
"""
|
||||
if user := await cls.get_or_none(user_id=str(user_id)):
|
||||
if user := await cls.get_or_none(user_id=user_id):
|
||||
return user.user_name
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
async def get_user_nickname(cls, user_id: Union[int, str]) -> str:
|
||||
"""
|
||||
说明:
|
||||
获取用户昵称
|
||||
async def get_user_nickname(cls, user_id: str) -> str:
|
||||
"""获取用户昵称
|
||||
|
||||
参数:
|
||||
:param user_id: 用户id
|
||||
user_id: 用户id
|
||||
"""
|
||||
if user := await cls.get_or_none(user_id=str(user_id)):
|
||||
if user := await cls.get_or_none(user_id=user_id):
|
||||
if user.nickname:
|
||||
_tmp = ""
|
||||
if black_word := Config.get_config("nickname", "BLACK_WORD"):
|
||||
@ -50,21 +50,34 @@ class FriendUser(Model):
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
async def set_user_nickname(cls, user_id: Union[int, str], nickname: str):
|
||||
"""
|
||||
说明:
|
||||
设置用户昵称
|
||||
async def set_user_nickname(
|
||||
cls,
|
||||
user_id: str,
|
||||
nickname: str,
|
||||
uname: str | None = None,
|
||||
platform: str | None = None,
|
||||
):
|
||||
"""设置用户昵称
|
||||
|
||||
参数:
|
||||
:param user_id: 用户id
|
||||
:param nickname: 昵称
|
||||
user_id: 用户id
|
||||
nickname: 昵称
|
||||
uname: 用户昵称
|
||||
platform: 平台
|
||||
"""
|
||||
defaults = {"nickname": nickname}
|
||||
if uname is not None:
|
||||
defaults["user_name"] = uname
|
||||
if platform is not None:
|
||||
defaults["platform"] = platform
|
||||
await cls.update_or_create(
|
||||
user_id=str(user_id), defaults={"nickname": nickname}
|
||||
user_id=user_id,
|
||||
defaults=defaults,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def _run_script(cls):
|
||||
await cls.raw(
|
||||
"ALTER TABLE friend_users ALTER COLUMN user_id TYPE character varying(255);"
|
||||
)
|
||||
# 将user_id字段类型改为character varying(255))
|
||||
def _run_script(cls):
|
||||
return [
|
||||
"ALTER TABLE friend_users ALTER COLUMN user_id TYPE character varying(255);",
|
||||
"ALTER TABLE friend_users ADD COLUMN platform character varying(255) default 'qq';",
|
||||
]
|
||||
|
||||
162
zhenxun/models/goods_info.py
Normal file
162
zhenxun/models/goods_info.py
Normal file
@ -0,0 +1,162 @@
|
||||
import uuid
|
||||
from typing import Dict
|
||||
|
||||
from tortoise import fields
|
||||
from typing_extensions import Self
|
||||
|
||||
from zhenxun.services.db_context import Model
|
||||
|
||||
|
||||
class GoodsInfo(Model):
|
||||
__tablename__ = "goods_info"
|
||||
|
||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
||||
"""自增id"""
|
||||
uuid = fields.CharField(255, null=True)
|
||||
"""uuid"""
|
||||
goods_name = fields.CharField(255, unique=True)
|
||||
"""商品名称"""
|
||||
goods_price = fields.IntField()
|
||||
"""价格"""
|
||||
goods_description = fields.TextField()
|
||||
"""描述"""
|
||||
goods_discount = fields.FloatField(default=1)
|
||||
"""折扣"""
|
||||
goods_limit_time = fields.BigIntField(default=0)
|
||||
"""限时"""
|
||||
daily_limit = fields.IntField(default=0)
|
||||
"""每日限购"""
|
||||
is_passive = fields.BooleanField(default=False)
|
||||
"""是否为被动道具"""
|
||||
icon = fields.TextField(null=True)
|
||||
"""图标路径"""
|
||||
|
||||
class Meta:
|
||||
table = "goods_info"
|
||||
table_description = "商品数据表"
|
||||
|
||||
@classmethod
|
||||
async def add_goods(
|
||||
cls,
|
||||
goods_name: str,
|
||||
goods_price: int,
|
||||
goods_description: str,
|
||||
goods_discount: float = 1,
|
||||
goods_limit_time: int = 0,
|
||||
daily_limit: int = 0,
|
||||
is_passive: bool = False,
|
||||
icon: str | None = None,
|
||||
):
|
||||
"""添加商品
|
||||
|
||||
参数:
|
||||
goods_name: 商品名称
|
||||
goods_price: 商品价格
|
||||
goods_description: 商品简介
|
||||
goods_discount: 商品折扣
|
||||
goods_limit_time: 商品限时
|
||||
daily_limit: 每日购买限制
|
||||
is_passive: 是否为被动道具
|
||||
icon: 图标
|
||||
"""
|
||||
if not await cls.exists(goods_name=goods_name):
|
||||
await cls.create(
|
||||
uuid=uuid.uuid1(),
|
||||
goods_name=goods_name,
|
||||
goods_price=goods_price,
|
||||
goods_description=goods_description,
|
||||
goods_discount=goods_discount,
|
||||
goods_limit_time=goods_limit_time,
|
||||
daily_limit=daily_limit,
|
||||
is_passive=is_passive,
|
||||
icon=icon,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def delete_goods(cls, goods_name: str) -> bool:
|
||||
"""删除商品
|
||||
|
||||
参数:
|
||||
goods_name: 商品名称
|
||||
|
||||
返回:
|
||||
bool: 是否删除成功
|
||||
"""
|
||||
if goods := await cls.get_or_none(goods_name=goods_name):
|
||||
await goods.delete()
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def update_goods(
|
||||
cls,
|
||||
goods_name: str,
|
||||
goods_price: int | None = None,
|
||||
goods_description: str | None = None,
|
||||
goods_discount: float | None = None,
|
||||
goods_limit_time: int | None = None,
|
||||
daily_limit: int | None = None,
|
||||
is_passive: bool | None = None,
|
||||
icon: str | None = None,
|
||||
):
|
||||
"""更新商品信息
|
||||
|
||||
参数:
|
||||
goods_name: 商品名称
|
||||
goods_price: 商品价格
|
||||
goods_description: 商品简介
|
||||
goods_discount: 商品折扣
|
||||
goods_limit_time: 商品限时时间
|
||||
daily_limit: 每日次数限制
|
||||
is_passive: 是否为被动
|
||||
icon: 图标
|
||||
"""
|
||||
if goods := await cls.get_or_none(goods_name=goods_name):
|
||||
await cls.update_or_create(
|
||||
goods_name=goods_name,
|
||||
defaults={
|
||||
"goods_price": goods_price or goods.goods_price,
|
||||
"goods_description": goods_description or goods.goods_description,
|
||||
"goods_discount": goods_discount or goods.goods_discount,
|
||||
"goods_limit_time": (
|
||||
goods_limit_time
|
||||
if goods_limit_time is not None
|
||||
else goods.goods_limit_time
|
||||
),
|
||||
"daily_limit": (
|
||||
daily_limit if daily_limit is not None else goods.daily_limit
|
||||
),
|
||||
"is_passive": (
|
||||
is_passive if is_passive is not None else goods.is_passive
|
||||
),
|
||||
"icon": icon or goods.icon,
|
||||
},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_all_goods(cls) -> list[Self]:
|
||||
"""
|
||||
获得全部有序商品对象
|
||||
"""
|
||||
query = await cls.all()
|
||||
id_lst = [x.id for x in query]
|
||||
goods_lst = []
|
||||
for _ in range(len(query)):
|
||||
min_id = min(id_lst)
|
||||
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 _run_script(cls):
|
||||
if goods_list := await cls.filter(uuid__isnull=True).all():
|
||||
for goods in goods_list:
|
||||
goods.uuid = uuid.uuid1()
|
||||
await cls.bulk_update(goods_list, ["uuid"], 10)
|
||||
return [
|
||||
"ALTER TABLE goods_info ADD uuid VARCHAR(255);",
|
||||
"ALTER TABLE goods_info ADD daily_limit Integer DEFAULT 0;",
|
||||
"ALTER TABLE goods_info ADD is_passive boolean DEFAULT False;",
|
||||
"ALTER TABLE goods_info ADD icon VARCHAR(255);",
|
||||
"ALTER TABLE goods_info DROP daily_purchase_limit;", # 删除 daily_purchase_limit 字段
|
||||
]
|
||||
54
zhenxun/models/group_console.py
Normal file
54
zhenxun/models/group_console.py
Normal file
@ -0,0 +1,54 @@
|
||||
from tortoise import fields
|
||||
|
||||
from zhenxun.services.db_context import Model
|
||||
|
||||
|
||||
class GroupConsole(Model):
|
||||
|
||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
||||
"""自增id"""
|
||||
group_id = fields.CharField(255, description="群组id")
|
||||
"""群聊id"""
|
||||
channel_id = fields.CharField(255, null=True, description="频道id")
|
||||
"""频道id"""
|
||||
group_name = fields.TextField(default="", description="群组名称")
|
||||
"""群聊名称"""
|
||||
max_member_count = fields.IntField(default=0, description="最大人数")
|
||||
"""最大人数"""
|
||||
member_count = fields.IntField(default=0, description="当前人数")
|
||||
"""当前人数"""
|
||||
group_flag = fields.IntField(default=0, description="群认证标记")
|
||||
"""群认证标记"""
|
||||
block_plugin = fields.TextField(default="", description="禁用插件")
|
||||
"""禁用插件"""
|
||||
block_task = fields.TextField(default="", description="禁用插件")
|
||||
"""禁用插件"""
|
||||
platform = fields.CharField(255, default="qq", description="所属平台")
|
||||
"""所属平台"""
|
||||
|
||||
class Meta:
|
||||
table = "group_console"
|
||||
table_description = "群组信息表"
|
||||
unique_together = ("group_id", "channel_id")
|
||||
|
||||
@classmethod
|
||||
async def is_block_task(
|
||||
cls, group_id: str, task: str, channel_id: str | None = None
|
||||
) -> bool:
|
||||
"""查看群组是否禁用被动
|
||||
|
||||
参数:
|
||||
group_id: 群组id
|
||||
task: 任务模块
|
||||
channel_id: 频道id
|
||||
|
||||
返回:
|
||||
bool: 是否禁用被动
|
||||
"""
|
||||
return await cls.exists(
|
||||
group_id=group_id, channel_id=channel_id, block_task__contains=f"{task},"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _run_script(cls):
|
||||
return []
|
||||
@ -1,31 +0,0 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from tortoise import fields
|
||||
|
||||
from zhenxun.services.db_context import Model
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
|
||||
class GroupInfo(Model):
|
||||
group_id = fields.CharField(255, pk=True)
|
||||
"""群聊id"""
|
||||
group_name = fields.TextField(default="")
|
||||
"""群聊名称"""
|
||||
max_member_count = fields.IntField(default=0)
|
||||
"""最大人数"""
|
||||
member_count = fields.IntField(default=0)
|
||||
"""当前人数"""
|
||||
group_flag = fields.IntField(default=0)
|
||||
"""群认证标记"""
|
||||
|
||||
class Meta:
|
||||
table = "group_info"
|
||||
table_description = "群聊信息表"
|
||||
|
||||
@classmethod
|
||||
def _run_script(cls):
|
||||
return [
|
||||
"ALTER TABLE group_info ADD group_flag Integer NOT NULL DEFAULT 0;", # group_info表添加一个group_flag
|
||||
"ALTER TABLE group_info ALTER COLUMN group_id TYPE character varying(255);"
|
||||
# 将group_id字段类型改为character varying(255)
|
||||
]
|
||||
@ -1,5 +1,3 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from tortoise import fields
|
||||
|
||||
from zhenxun.services.db_context import Model
|
||||
@ -8,6 +6,8 @@ from zhenxun.services.db_context import Model
|
||||
class GroupInfo(Model):
|
||||
group_id = fields.CharField(255, pk=True, description="群组id")
|
||||
"""群聊id"""
|
||||
# channel_id = fields.CharField(255, description="群组id")
|
||||
# """频道id"""
|
||||
group_name = fields.TextField(default="", description="群组名称")
|
||||
"""群聊名称"""
|
||||
max_member_count = fields.IntField(default=0, description="最大人数")
|
||||
@ -16,15 +16,36 @@ class GroupInfo(Model):
|
||||
"""当前人数"""
|
||||
group_flag = fields.IntField(default=0, description="群认证标记")
|
||||
"""群认证标记"""
|
||||
block_plugin = fields.TextField(default="", description="禁用插件")
|
||||
"""禁用插件"""
|
||||
block_task = fields.TextField(default="", description="禁用插件")
|
||||
"""禁用插件"""
|
||||
platform = fields.CharField(255, default="qq", description="所属平台")
|
||||
"""所属平台"""
|
||||
|
||||
class Meta:
|
||||
table = "group_info"
|
||||
table_description = "群聊信息表"
|
||||
|
||||
@classmethod
|
||||
async def is_block_task(cls, group_id: str, task: str) -> bool:
|
||||
"""查看群组是否禁用被动
|
||||
|
||||
参数:
|
||||
group_id: 群组id
|
||||
task: 任务模块
|
||||
|
||||
返回:
|
||||
bool: 是否禁用被动
|
||||
"""
|
||||
return await cls.exists(group_id=group_id, block_task__contains=f"{task},")
|
||||
|
||||
@classmethod
|
||||
def _run_script(cls):
|
||||
return [
|
||||
"ALTER TABLE group_info ADD group_flag Integer NOT NULL DEFAULT 0;", # group_info表添加一个group_flag
|
||||
"ALTER TABLE group_info ALTER COLUMN group_id TYPE character varying(255);"
|
||||
# 将group_id字段类型改为character varying(255)
|
||||
"ALTER TABLE group_info ALTER COLUMN group_id TYPE character varying(255);",
|
||||
"ALTER TABLE group_info ADD block_plugin Text NOT NULL DEFAULT '';",
|
||||
"ALTER TABLE group_info ADD block_task Text NOT NULL DEFAULT '';",
|
||||
"ALTER TABLE group_info ADD platform character varying(255) NOT NULL DEFAULT 'qq';",
|
||||
]
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Set, Union
|
||||
from typing import Set
|
||||
|
||||
from tortoise import fields
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
|
||||
from zhenxun.services.db_context import Model
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
@ -23,6 +22,8 @@ class GroupInfoUser(Model):
|
||||
"""群聊昵称"""
|
||||
uid = fields.BigIntField(null=True)
|
||||
"""用户uid"""
|
||||
platform = fields.CharField(255, null=True, description="平台")
|
||||
"""平台"""
|
||||
|
||||
class Meta:
|
||||
table = "group_info_users"
|
||||
@ -30,59 +31,65 @@ class GroupInfoUser(Model):
|
||||
unique_together = ("user_id", "group_id")
|
||||
|
||||
@classmethod
|
||||
async def get_group_member_id_list(cls, group_id: Union[int, str]) -> Set[int]:
|
||||
"""
|
||||
说明:
|
||||
获取该群所有用户id
|
||||
async def get_group_member_id_list(cls, group_id: str) -> Set[int]:
|
||||
"""获取该群所有用户id
|
||||
|
||||
参数:
|
||||
:param group_id: 群号
|
||||
group_id: 群号
|
||||
"""
|
||||
return set(
|
||||
await cls.filter(group_id=str(group_id)).values_list("user_id", flat=True)
|
||||
await cls.filter(group_id=group_id).values_list("user_id", flat=True)
|
||||
) # type: ignore
|
||||
|
||||
@classmethod
|
||||
async def set_user_nickname(
|
||||
cls, user_id: Union[int, str], group_id: Union[int, str], nickname: str
|
||||
cls,
|
||||
user_id: str,
|
||||
group_id: str,
|
||||
nickname: str,
|
||||
uname: str | None = None,
|
||||
platform: str | None = None,
|
||||
):
|
||||
"""
|
||||
说明:
|
||||
设置群员在该群内的昵称
|
||||
"""设置群员在该群内的昵称
|
||||
|
||||
参数:
|
||||
:param user_id: 用户id
|
||||
:param group_id: 群号
|
||||
:param nickname: 昵称
|
||||
user_id: 用户id
|
||||
group_id: 群号
|
||||
nickname: 昵称
|
||||
uname: 用户昵称
|
||||
platform: 平台
|
||||
"""
|
||||
defaults = {"nickname": nickname}
|
||||
if uname is not None:
|
||||
defaults["user_name"] = uname
|
||||
if platform is not None:
|
||||
defaults["platform"] = platform
|
||||
await cls.update_or_create(
|
||||
user_id=str(user_id),
|
||||
group_id=str(group_id),
|
||||
defaults={"nickname": nickname},
|
||||
user_id=user_id,
|
||||
group_id=group_id,
|
||||
defaults=defaults,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_user_all_group(cls, user_id: Union[int, str]) -> List[int]:
|
||||
"""
|
||||
说明:
|
||||
获取该用户所在的所有群聊
|
||||
async def get_user_all_group(cls, user_id: str) -> list[int]:
|
||||
"""获取该用户所在的所有群聊
|
||||
|
||||
参数:
|
||||
:param user_id: 用户id
|
||||
user_id: 用户id
|
||||
"""
|
||||
return list(
|
||||
await cls.filter(user_id=str(user_id)).values_list("group_id", flat=True)
|
||||
) # type: ignore
|
||||
|
||||
@classmethod
|
||||
async def get_user_nickname(
|
||||
cls, user_id: Union[int, str], group_id: Union[int, str]
|
||||
) -> str:
|
||||
"""
|
||||
说明:
|
||||
获取用户在该群的昵称
|
||||
async def get_user_nickname(cls, user_id: str, group_id: str) -> str:
|
||||
"""获取用户在该群的昵称
|
||||
|
||||
参数:
|
||||
:param user_id: 用户id
|
||||
:param group_id: 群号
|
||||
user_id: 用户id
|
||||
group_id: 群号
|
||||
"""
|
||||
if user := await cls.get_or_none(user_id=str(user_id), group_id=str(group_id)):
|
||||
if user := await cls.get_or_none(user_id=user_id, group_id=group_id):
|
||||
if user.nickname:
|
||||
nickname = ""
|
||||
if black_word := Config.get_config("nickname", "BLACK_WORD"):
|
||||
@ -92,28 +99,6 @@ class GroupInfoUser(Model):
|
||||
return user.nickname
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
async def get_group_member_uid(
|
||||
cls, user_id: Union[int, str], group_id: Union[int, str]
|
||||
) -> Optional[int]:
|
||||
logger.debug(
|
||||
f"GroupInfoUser 尝试获取 用户[<u><e>{user_id}</e></u>] 群聊[<u><e>{group_id}</e></u>] UID"
|
||||
)
|
||||
user, _ = await cls.get_or_create(user_id=str(user_id), group_id=str(group_id))
|
||||
_max_uid_user, _ = await cls.get_or_create(user_id="114514", group_id="114514")
|
||||
_max_uid = _max_uid_user.uid
|
||||
if not user.uid:
|
||||
all_user = await cls.filter(user_id=str(user_id)).all()
|
||||
for x in all_user:
|
||||
if x.uid:
|
||||
return x.uid
|
||||
user.uid = _max_uid + 1
|
||||
_max_uid_user.uid = _max_uid + 1
|
||||
await cls.bulk_update([user, _max_uid_user], ["uid"])
|
||||
logger.debug(
|
||||
f"GroupInfoUser 获取 用户[<u><e>{user_id}</e></u>] 群聊[<u><e>{group_id}</e></u>] UID: {user.uid}"
|
||||
)
|
||||
return user.uid
|
||||
|
||||
@classmethod
|
||||
async def _run_script(cls):
|
||||
@ -124,4 +109,5 @@ class GroupInfoUser(Model):
|
||||
"ALTER TABLE group_info_users ALTER COLUMN user_id TYPE character varying(255);",
|
||||
# 将user_id字段类型改为character varying(255)
|
||||
"ALTER TABLE group_info_users ALTER COLUMN group_id TYPE character varying(255);",
|
||||
"ALTER TABLE group_info_users ADD COLUMN platform character varying(255) default 'qq';",
|
||||
]
|
||||
|
||||
@ -24,9 +24,8 @@ class LevelUser(Model):
|
||||
unique_together = ("user_id", "group_id")
|
||||
|
||||
@classmethod
|
||||
async def get_user_level(cls, user_id: int | str, group_id: int | str) -> int:
|
||||
"""
|
||||
获取用户在群内的等级
|
||||
async def get_user_level(cls, user_id: str, group_id: str | None) -> int:
|
||||
"""获取用户在群内的等级
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
@ -35,20 +34,21 @@ class LevelUser(Model):
|
||||
返回:
|
||||
int: 权限等级
|
||||
"""
|
||||
if user := await cls.get_or_none(user_id=str(user_id), group_id=str(group_id)):
|
||||
if not group_id:
|
||||
return 0
|
||||
if user := await cls.get_or_none(user_id=user_id, group_id=group_id):
|
||||
return user.user_level
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
async def set_level(
|
||||
cls,
|
||||
user_id: int | str,
|
||||
group_id: int | str,
|
||||
user_id: str,
|
||||
group_id: str,
|
||||
level: int,
|
||||
group_flag: int = 0,
|
||||
):
|
||||
"""
|
||||
设置用户在群内的权限
|
||||
"""设置用户在群内的权限
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
@ -57,8 +57,8 @@ class LevelUser(Model):
|
||||
group_flag: 是否被自动更新刷新权限 0:是, 1:否.
|
||||
"""
|
||||
await cls.update_or_create(
|
||||
user_id=str(user_id),
|
||||
group_id=str(group_id),
|
||||
user_id=user_id,
|
||||
group_id=group_id,
|
||||
defaults={
|
||||
"user_level": level,
|
||||
"group_flag": group_flag,
|
||||
@ -66,9 +66,8 @@ class LevelUser(Model):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def delete_level(cls, user_id: int | str, group_id: int | str) -> bool:
|
||||
"""
|
||||
删除用户权限
|
||||
async def delete_level(cls, user_id: str, group_id: str) -> bool:
|
||||
"""删除用户权限
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
@ -77,17 +76,14 @@ class LevelUser(Model):
|
||||
返回:
|
||||
bool: 是否含有用户权限
|
||||
"""
|
||||
if user := await cls.get_or_none(user_id=str(user_id), group_id=str(group_id)):
|
||||
if user := await cls.get_or_none(user_id=user_id, group_id=group_id):
|
||||
await user.delete()
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def check_level(
|
||||
cls, user_id: int | str, group_id: int | str, level: int
|
||||
) -> bool:
|
||||
"""
|
||||
检查用户权限等级是否大于 level
|
||||
async def check_level(cls, user_id: str, group_id: str, level: int) -> bool:
|
||||
"""检查用户权限等级是否大于 level
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
@ -98,20 +94,17 @@ class LevelUser(Model):
|
||||
bool: 是否大于level
|
||||
"""
|
||||
if group_id:
|
||||
if user := await cls.get_or_none(
|
||||
user_id=str(user_id), group_id=str(group_id)
|
||||
):
|
||||
if user := await cls.get_or_none(user_id=user_id, group_id=group_id):
|
||||
return user.user_level >= level
|
||||
else:
|
||||
user_list = await cls.filter(user_id=str(user_id)).all()
|
||||
user_list = await cls.filter(user_id=user_id).all()
|
||||
user = max(user_list, key=lambda x: x.user_level)
|
||||
return user.user_level >= level
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def is_group_flag(cls, user_id: int | str, group_id: int | str) -> bool:
|
||||
"""
|
||||
检测是否会被自动更新刷新权限
|
||||
async def is_group_flag(cls, user_id: str, group_id: str) -> bool:
|
||||
"""检测是否会被自动更新刷新权限
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
@ -120,7 +113,7 @@ class LevelUser(Model):
|
||||
返回:
|
||||
bool: 是否会被自动更新权限刷新
|
||||
"""
|
||||
if user := await cls.get_or_none(user_id=str(user_id), group_id=str(group_id)):
|
||||
if user := await cls.get_or_none(user_id=user_id, group_id=group_id):
|
||||
return user.group_flag == 1
|
||||
return False
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ class PluginInfo(Model):
|
||||
"""插件名称"""
|
||||
status = fields.BooleanField(default=True, description="全局开关状态")
|
||||
"""全局开关状态"""
|
||||
block_type = fields.CharEnumField(
|
||||
block_type: BlockType | None = fields.CharEnumField(
|
||||
BlockType, default=None, null=True, description="禁用类型"
|
||||
)
|
||||
"""禁用类型"""
|
||||
|
||||
@ -22,6 +22,7 @@ class PluginLimit(Model):
|
||||
on_delete=fields.CASCADE,
|
||||
description="所属插件",
|
||||
)
|
||||
"""所属插件"""
|
||||
limit_type = fields.CharEnumField(PluginLimitType, description="限制类型")
|
||||
"""限制类型"""
|
||||
watch_type = fields.CharEnumField(LimitWatchType, description="监听类型")
|
||||
|
||||
26
zhenxun/models/sign_log.py
Normal file
26
zhenxun/models/sign_log.py
Normal file
@ -0,0 +1,26 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Literal, Optional, Tuple, Union
|
||||
|
||||
from tortoise import fields
|
||||
|
||||
from zhenxun.services.db_context import Model
|
||||
|
||||
|
||||
class SignLog(Model):
|
||||
|
||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
||||
"""自增id"""
|
||||
user_id = fields.CharField(255, unique=True, description="用户id")
|
||||
"""用户id"""
|
||||
impression = fields.DecimalField(10, 3, default=0, description="好感度")
|
||||
"""好感度"""
|
||||
create_time = fields.DatetimeField(auto_now_add=True, description="创建时间")
|
||||
"""创建时间"""
|
||||
bot_id = fields.CharField(255, null=True, description="botId")
|
||||
"""bot记录id"""
|
||||
platform = fields.CharField(255, null=True, description="平台")
|
||||
"""平台"""
|
||||
|
||||
class Meta:
|
||||
table = "sign_log"
|
||||
table_description = "用户签到记录表"
|
||||
72
zhenxun/models/sign_user.py
Normal file
72
zhenxun/models/sign_user.py
Normal file
@ -0,0 +1,72 @@
|
||||
from tortoise import fields
|
||||
from typing_extensions import Self
|
||||
|
||||
from zhenxun.services.db_context import Model
|
||||
|
||||
from .sign_log import SignLog
|
||||
from .user_console import UserConsole
|
||||
|
||||
|
||||
class SignUser(Model):
|
||||
|
||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
||||
"""自增id"""
|
||||
user_id = fields.CharField(255, unique=True, description="用户id")
|
||||
"""用户id"""
|
||||
sign_count = fields.IntField(default=0, description="签到次数")
|
||||
"""签到次数"""
|
||||
impression = fields.DecimalField(10, 3, default=0, description="好感度")
|
||||
"""好感度"""
|
||||
user_console: fields.OneToOneRelation[UserConsole] = fields.OneToOneField(
|
||||
"models.UserConsole", related_name="user_console", description="用户数据"
|
||||
)
|
||||
"""用户数据"""
|
||||
add_probability = fields.DecimalField(
|
||||
10, 3, default=0, description="双倍签到增加概率"
|
||||
)
|
||||
"""双倍签到增加概率"""
|
||||
specify_probability = fields.DecimalField(
|
||||
10, 3, default=0, description="指定双倍概率"
|
||||
)
|
||||
"""使用指定双倍概率"""
|
||||
platform = fields.CharField(255, null=True, description="平台")
|
||||
"""平台"""
|
||||
|
||||
class Meta:
|
||||
table = "sign_users"
|
||||
table_description = "用户签到数据表"
|
||||
|
||||
@classmethod
|
||||
async def sign(
|
||||
cls,
|
||||
user_id: str | Self,
|
||||
impression: float,
|
||||
bot_id: str | None = None,
|
||||
platform: str | None = None,
|
||||
) -> Self:
|
||||
"""签到
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
impression: 好感度
|
||||
bot_id: bot Id
|
||||
platform: 平台
|
||||
"""
|
||||
if isinstance(user_id, SignUser):
|
||||
user = user_id
|
||||
else:
|
||||
user, _ = await cls.get_or_create(
|
||||
user_id=user_id, defaults={"platform": platform}
|
||||
)
|
||||
user.impression = float(user.impression) + impression
|
||||
user.add_probability = 0
|
||||
user.specify_probability = 0
|
||||
user.sign_count += 1
|
||||
await user.save()
|
||||
await SignLog.create(
|
||||
user_id=user.user_id,
|
||||
impression=impression,
|
||||
bot_id=bot_id,
|
||||
platform=platform,
|
||||
)
|
||||
return user
|
||||
22
zhenxun/models/task_info.py
Normal file
22
zhenxun/models/task_info.py
Normal file
@ -0,0 +1,22 @@
|
||||
from tortoise import fields
|
||||
|
||||
from zhenxun.services.db_context import Model
|
||||
|
||||
|
||||
class TaskInfo(Model):
|
||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
||||
"""自增id"""
|
||||
module = fields.CharField(255, description="被动技能模块名")
|
||||
"""被动技能模块名"""
|
||||
name = fields.CharField(255, description="被动技能名称")
|
||||
"""被动技能名称"""
|
||||
status = fields.BooleanField(default=True, description="全局开关状态")
|
||||
"""全局开关状态"""
|
||||
run_time = fields.CharField(255, null=True, description="运行时间")
|
||||
"""运行时间"""
|
||||
run_count = fields.IntField(default=0, description="运行次数")
|
||||
"""运行次数"""
|
||||
|
||||
class Meta:
|
||||
table = "task_info"
|
||||
table_description = "被动技能基本信息"
|
||||
79
zhenxun/models/user_console.py
Normal file
79
zhenxun/models/user_console.py
Normal file
@ -0,0 +1,79 @@
|
||||
from typing import Dict
|
||||
|
||||
from tortoise import fields
|
||||
|
||||
from zhenxun.services.db_context import Model
|
||||
from zhenxun.utils.enum import GoldHandle
|
||||
|
||||
from .user_gold_log import UserGoldLog
|
||||
|
||||
|
||||
class UserConsole(Model):
|
||||
|
||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
||||
"""自增id"""
|
||||
user_id = fields.CharField(255, unique=True, description="用户id")
|
||||
"""用户id"""
|
||||
uid = fields.IntField(description="UID")
|
||||
"""UID"""
|
||||
gold = fields.IntField(default=100, description="金币数量")
|
||||
"""金币数量"""
|
||||
sign = fields.ReverseRelation["SignUser"] # type: ignore
|
||||
"""好感度"""
|
||||
props: Dict[str, int] = fields.JSONField(default={}) # type: ignore
|
||||
"""道具"""
|
||||
platform = fields.CharField(255, null=True, description="平台")
|
||||
"""平台"""
|
||||
create_time = fields.DatetimeField(auto_now_add=True, description="创建时间")
|
||||
"""创建时间"""
|
||||
|
||||
class Meta:
|
||||
table = "user_console"
|
||||
table_description = "用户数据表"
|
||||
|
||||
@classmethod
|
||||
async def get_new_uid(cls):
|
||||
if user := await cls.annotate().order_by("uid").first():
|
||||
return user.uid + 1
|
||||
return 1
|
||||
|
||||
@classmethod
|
||||
async def add_gold(
|
||||
cls, user_id: str, gold: int, source: str, platform: str | None = None
|
||||
):
|
||||
"""添加金币
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
gold: 金币
|
||||
source: 来源
|
||||
platform: 平台.
|
||||
"""
|
||||
user, _ = await cls.get_or_create(
|
||||
user_id=user_id, defaults={"platform": platform, "uid": cls.get_new_uid()}
|
||||
)
|
||||
user.gold += gold
|
||||
await user.save(update_fields=["gold"])
|
||||
await UserGoldLog.create(
|
||||
user_id=user_id, gold=gold, handle=GoldHandle.GET, source=source
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def add_props(
|
||||
cls, user_id: str, goods_uuid: str, num: int = 1, platform: str | None = None
|
||||
):
|
||||
"""添加道具
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
goods_uuid: 道具uuid
|
||||
num: 道具数量.
|
||||
platform: 平台.
|
||||
"""
|
||||
user, _ = await cls.get_or_create(
|
||||
user_id=user_id, defaults={"platform": platform, "uid": cls.get_new_uid()}
|
||||
)
|
||||
if goods_uuid not in user.props:
|
||||
user.props[goods_uuid] = 0
|
||||
user.props[goods_uuid] += num
|
||||
await user.save(update_fields=["props"])
|
||||
24
zhenxun/models/user_gold_log.py
Normal file
24
zhenxun/models/user_gold_log.py
Normal file
@ -0,0 +1,24 @@
|
||||
from tortoise import fields
|
||||
|
||||
from zhenxun.services.db_context import Model
|
||||
from zhenxun.utils.enum import GoldHandle
|
||||
|
||||
|
||||
class UserGoldLog(Model):
|
||||
|
||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
||||
"""自增id"""
|
||||
user_id = fields.CharField(255, description="用户id")
|
||||
"""用户id"""
|
||||
gold = fields.IntField(description="金币")
|
||||
"""金币"""
|
||||
handle = fields.CharEnumField(GoldHandle, default=None, description="道具处理类型")
|
||||
"""金币处理类型"""
|
||||
source = fields.CharField(255, null=True, description="来源插件")
|
||||
"""来源插件"""
|
||||
create_time = fields.DatetimeField(auto_now_add=True, description="创建时间")
|
||||
"""创建时间"""
|
||||
|
||||
class Meta:
|
||||
table = "user_gold_log"
|
||||
table_description = "用户金币记录表"
|
||||
25
zhenxun/models/user_props.py
Normal file
25
zhenxun/models/user_props.py
Normal file
@ -0,0 +1,25 @@
|
||||
from typing import Dict
|
||||
|
||||
from tortoise import fields
|
||||
|
||||
from zhenxun.services.db_context import Model
|
||||
|
||||
from .sign_user import SignUser
|
||||
|
||||
|
||||
class UserProps(Model):
|
||||
|
||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
||||
"""自增id"""
|
||||
user_id = fields.CharField(255, unique=True, description="用户id")
|
||||
"""用户id"""
|
||||
name = fields.CharField(255, description="道具名称")
|
||||
"""道具名称"""
|
||||
property: Dict[str, int] = fields.JSONField(default={}) # type: ignore
|
||||
"""道具"""
|
||||
platform = fields.CharField(255, null=True)
|
||||
"""平台"""
|
||||
|
||||
class Meta:
|
||||
table = "user_props"
|
||||
table_description = "用户道具表"
|
||||
30
zhenxun/models/user_props_log.py
Normal file
30
zhenxun/models/user_props_log.py
Normal file
@ -0,0 +1,30 @@
|
||||
from typing import Dict
|
||||
|
||||
from tortoise import fields
|
||||
|
||||
from zhenxun.services.db_context import Model
|
||||
from zhenxun.utils.enum import PropHandle
|
||||
|
||||
from .sign_user import SignUser
|
||||
|
||||
|
||||
class UserPropsLog(Model):
|
||||
|
||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
||||
"""自增id"""
|
||||
user_id = fields.CharField(255, description="用户id")
|
||||
"""用户id"""
|
||||
uuid = fields.CharField(255, description="道具uuid")
|
||||
"""道具uuid"""
|
||||
num = fields.IntField(null=True, description="道具金币")
|
||||
"""数量"""
|
||||
gold = fields.IntField(null=True, description="道具金币")
|
||||
"""道具金币"""
|
||||
handle = fields.CharEnumField(PropHandle, default=None, description="道具处理类型")
|
||||
"""道具处理类型"""
|
||||
create_time = fields.DatetimeField(auto_now_add=True, description="创建时间")
|
||||
"""创建时间"""
|
||||
|
||||
class Meta:
|
||||
table = "user_props_log"
|
||||
table_description = "用户道具记录表"
|
||||
@ -1,10 +1,7 @@
|
||||
from typing import List
|
||||
|
||||
from nonebot.utils import is_coroutine_callable
|
||||
from tortoise import Tortoise, fields
|
||||
from tortoise.connection import connections
|
||||
from tortoise.models import Model as Model_
|
||||
from tortoise.queryset import RawSQLQuery
|
||||
|
||||
from zhenxun.configs.config import (
|
||||
address,
|
||||
@ -18,7 +15,7 @@ from zhenxun.configs.config import (
|
||||
|
||||
from .log import logger
|
||||
|
||||
MODELS: List[str] = []
|
||||
MODELS: list[str] = []
|
||||
|
||||
SCRIPT_METHOD = []
|
||||
|
||||
@ -60,12 +57,14 @@ async def init():
|
||||
modules={"models": MODELS},
|
||||
# timezone="Asia/Shanghai"
|
||||
)
|
||||
await Tortoise.generate_schemas()
|
||||
logger.info(f"Database loaded successfully!")
|
||||
# except Exception as e:
|
||||
# raise Exception(f"数据库连接错误... {type(e)}: {e}")
|
||||
if SCRIPT_METHOD:
|
||||
logger.debug(f"即将运行SCRIPT_METHOD方法, 合计 <u><y>{len(SCRIPT_METHOD)}</y></u> 个...")
|
||||
db = Tortoise.get_connection("default")
|
||||
logger.debug(
|
||||
f"即将运行SCRIPT_METHOD方法, 合计 <u><y>{len(SCRIPT_METHOD)}</y></u> 个..."
|
||||
)
|
||||
sql_list = []
|
||||
for module, func in SCRIPT_METHOD:
|
||||
try:
|
||||
@ -75,15 +74,16 @@ async def init():
|
||||
sql = func()
|
||||
if sql:
|
||||
sql_list += sql
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"{module} 执行SCRIPT_METHOD方法出错...", e=e)
|
||||
for sql in sql_list:
|
||||
logger.debug(f"执行SQL: {sql}")
|
||||
try:
|
||||
await TestSQL.raw(sql)
|
||||
await db.execute_query_dict(sql)
|
||||
# await TestSQL.raw(sql)
|
||||
except Exception as e:
|
||||
logger.debug(f"执行SQL: {sql} 错误...", e=e)
|
||||
await Tortoise.generate_schemas()
|
||||
|
||||
|
||||
async def disconnect():
|
||||
|
||||
@ -40,6 +40,7 @@ class logger:
|
||||
TEMPLATE_USER = "用户[<u><e>{}</e></u>] "
|
||||
TEMPLATE_GROUP = "群聊[<u><e>{}</e></u>] "
|
||||
TEMPLATE_COMMAND = "CMD[<u><c>{}</c></u>] "
|
||||
TEMPLATE_PLATFORM = "平台[<u><m>{}</m></u>] "
|
||||
TEMPLATE_TARGET = "[Target]([<u><e>{}</e></u>]) "
|
||||
|
||||
SUCCESS_TEMPLATE = "[<u><c>{}</c></u>]: {} | 参数[{}] 返回: [<y>{}</y>]"
|
||||
@ -59,8 +60,8 @@ class logger:
|
||||
group_id: int | str | None = None,
|
||||
adapter: str | None = None,
|
||||
target: Any = None,
|
||||
):
|
||||
...
|
||||
platform: str | None = None,
|
||||
): ...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
@ -71,8 +72,8 @@ class logger:
|
||||
*,
|
||||
session: Session | None = None,
|
||||
target: Any = None,
|
||||
):
|
||||
...
|
||||
platform: str | None = None,
|
||||
): ...
|
||||
|
||||
@classmethod
|
||||
def info(
|
||||
@ -84,16 +85,20 @@ class logger:
|
||||
group_id: int | str | None = None,
|
||||
adapter: str | None = None,
|
||||
target: Any = None,
|
||||
platform: str | None = None,
|
||||
):
|
||||
user_id: str | None = session # type: ignore
|
||||
group_id = None
|
||||
if type(session) == Session:
|
||||
user_id = session.id1
|
||||
adapter = session.bot_type
|
||||
if session.id3 or session.id2:
|
||||
if session.id3:
|
||||
group_id = f"{session.id3}:{session.id2}"
|
||||
elif session.id2:
|
||||
group_id = f"{session.id2}"
|
||||
platform = platform or session.platform
|
||||
template = cls.__parser_template(
|
||||
info, command, user_id, group_id, adapter, target
|
||||
info, command, user_id, group_id, adapter, target, platform
|
||||
)
|
||||
logger_.opt(colors=True).info(template)
|
||||
|
||||
@ -123,9 +128,9 @@ class logger:
|
||||
group_id: int | str | None = None,
|
||||
adapter: str | None = None,
|
||||
target: Any = None,
|
||||
platform: str | None = None,
|
||||
e: Exception | None = None,
|
||||
):
|
||||
...
|
||||
): ...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
@ -137,9 +142,9 @@ class logger:
|
||||
session: Session | None = None,
|
||||
adapter: str | None = None,
|
||||
target: Any = None,
|
||||
platform: str | None = None,
|
||||
e: Exception | None = None,
|
||||
):
|
||||
...
|
||||
): ...
|
||||
|
||||
@classmethod
|
||||
def warning(
|
||||
@ -151,6 +156,7 @@ class logger:
|
||||
group_id: int | str | None = None,
|
||||
adapter: str | None = None,
|
||||
target: Any = None,
|
||||
platform: str | None = None,
|
||||
e: Exception | None = None,
|
||||
):
|
||||
user_id: str | None = session # type: ignore
|
||||
@ -158,10 +164,13 @@ class logger:
|
||||
if type(session) == Session:
|
||||
user_id = session.id1
|
||||
adapter = session.bot_type
|
||||
if session.id3 or session.id2:
|
||||
if session.id3:
|
||||
group_id = f"{session.id3}:{session.id2}"
|
||||
elif session.id2:
|
||||
group_id = f"{session.id2}"
|
||||
platform = platform or session.platform
|
||||
template = cls.__parser_template(
|
||||
info, command, user_id, group_id, adapter, target
|
||||
info, command, user_id, group_id, adapter, target, platform
|
||||
)
|
||||
if e:
|
||||
template += f" || 错误<r>{type(e)}: {e}</r>"
|
||||
@ -178,9 +187,9 @@ class logger:
|
||||
group_id: int | str | None = None,
|
||||
adapter: str | None = None,
|
||||
target: Any = None,
|
||||
platform: str | None = None,
|
||||
e: Exception | None = None,
|
||||
):
|
||||
...
|
||||
): ...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
@ -191,9 +200,9 @@ class logger:
|
||||
*,
|
||||
session: Session | None = None,
|
||||
target: Any = None,
|
||||
platform: str | None = None,
|
||||
e: Exception | None = None,
|
||||
):
|
||||
...
|
||||
): ...
|
||||
|
||||
@classmethod
|
||||
def error(
|
||||
@ -205,6 +214,7 @@ class logger:
|
||||
group_id: int | str | None = None,
|
||||
adapter: str | None = None,
|
||||
target: Any = None,
|
||||
platform: str | None = None,
|
||||
e: Exception | None = None,
|
||||
):
|
||||
user_id: str | None = session # type: ignore
|
||||
@ -212,10 +222,13 @@ class logger:
|
||||
if type(session) == Session:
|
||||
user_id = session.id1
|
||||
adapter = session.bot_type
|
||||
if session.id3 or session.id2:
|
||||
if session.id3:
|
||||
group_id = f"{session.id3}:{session.id2}"
|
||||
elif session.id2:
|
||||
group_id = f"{session.id2}"
|
||||
platform = platform or session.platform
|
||||
template = cls.__parser_template(
|
||||
info, command, user_id, group_id, adapter, target
|
||||
info, command, user_id, group_id, adapter, target, platform
|
||||
)
|
||||
if e:
|
||||
template += f" || 错误 <r>{type(e)}: {e}</r>"
|
||||
@ -232,9 +245,9 @@ class logger:
|
||||
group_id: int | str | None = None,
|
||||
adapter: str | None = None,
|
||||
target: Any = None,
|
||||
platform: str | None = None,
|
||||
e: Exception | None = None,
|
||||
):
|
||||
...
|
||||
): ...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
@ -245,9 +258,9 @@ class logger:
|
||||
*,
|
||||
session: Session | None = None,
|
||||
target: Any = None,
|
||||
platform: str | None = None,
|
||||
e: Exception | None = None,
|
||||
):
|
||||
...
|
||||
): ...
|
||||
|
||||
@classmethod
|
||||
def debug(
|
||||
@ -259,6 +272,7 @@ class logger:
|
||||
group_id: int | str | None = None,
|
||||
adapter: str | None = None,
|
||||
target: Any = None,
|
||||
platform: str | None = None,
|
||||
e: Exception | None = None,
|
||||
):
|
||||
user_id: str | None = session # type: ignore
|
||||
@ -266,10 +280,13 @@ class logger:
|
||||
if type(session) == Session:
|
||||
user_id = session.id1
|
||||
adapter = session.bot_type
|
||||
if session.id3 or session.id2:
|
||||
if session.id3:
|
||||
group_id = f"{session.id3}:{session.id2}"
|
||||
elif session.id2:
|
||||
group_id = f"{session.id2}"
|
||||
platform = platform or session.platform
|
||||
template = cls.__parser_template(
|
||||
info, command, user_id, group_id, adapter, target
|
||||
info, command, user_id, group_id, adapter, target, platform
|
||||
)
|
||||
if e:
|
||||
template += f" || 错误 <r>{type(e)}: {e}</r>"
|
||||
@ -284,12 +301,16 @@ class logger:
|
||||
group_id: int | str | None = None,
|
||||
adapter: str | None = None,
|
||||
target: Any = None,
|
||||
platform: str | None = None,
|
||||
) -> str:
|
||||
arg_list = []
|
||||
template = ""
|
||||
if adapter is not None:
|
||||
template += cls.TEMPLATE_ADAPTER
|
||||
arg_list.append(adapter)
|
||||
if platform is not None:
|
||||
template += cls.TEMPLATE_PLATFORM
|
||||
arg_list.append(platform)
|
||||
if group_id is not None:
|
||||
template += cls.TEMPLATE_GROUP
|
||||
arg_list.append(group_id)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import base64
|
||||
import math
|
||||
import uuid
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import List, Literal, Tuple, TypeAlias, overload
|
||||
from typing import Literal, Tuple, TypeAlias, overload
|
||||
|
||||
from nonebot.utils import run_sync
|
||||
from PIL import Image, ImageDraw, ImageFilter, ImageFont
|
||||
@ -40,12 +41,13 @@ class BuildImage:
|
||||
self,
|
||||
width: int = 0,
|
||||
height: int = 0,
|
||||
color: ColorAlias = None,
|
||||
color: ColorAlias = (255, 255, 255),
|
||||
mode: ModeType = "RGBA",
|
||||
font: str | Path | FreeTypeFont = "HYWenHei-85W.ttf",
|
||||
font_size: int = 20,
|
||||
background: str | BytesIO | Path | None = None,
|
||||
) -> None:
|
||||
self.uid = uuid.uuid1()
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.color = color
|
||||
@ -72,7 +74,7 @@ class BuildImage:
|
||||
async def build_text_image(
|
||||
cls,
|
||||
text: str,
|
||||
font: str | Path = "HYWenHei-85W.ttf",
|
||||
font: str | FreeTypeFont | Path = "HYWenHei-85W.ttf",
|
||||
size: int = 10,
|
||||
font_color: str | Tuple[int, int, int] = (0, 0, 0),
|
||||
color: ColorAlias = None,
|
||||
@ -91,12 +93,16 @@ class BuildImage:
|
||||
返回:
|
||||
Self: Self
|
||||
"""
|
||||
_font = cls.load_font(font, size)
|
||||
width, height = cls.get_text_size(text, _font)
|
||||
if type(padding) == int:
|
||||
_font = None
|
||||
if isinstance(font, FreeTypeFont):
|
||||
_font = font
|
||||
elif isinstance(font, (str, Path)):
|
||||
_font = cls.load_font(font, size)
|
||||
width, height = cls.get_text_size(text or "A", _font)
|
||||
if isinstance(padding, int):
|
||||
width += padding * 2
|
||||
height += padding * 2
|
||||
elif type(padding) == tuple:
|
||||
elif isinstance(padding, tuple):
|
||||
width += padding[1] + padding[3]
|
||||
height += padding[0] + padding[2]
|
||||
markImg = cls(width, height, color)
|
||||
@ -112,9 +118,9 @@ class BuildImage:
|
||||
row: int,
|
||||
space: int = 10,
|
||||
padding: int = 50,
|
||||
color: ColorAlias = (255, 255, 255, 0),
|
||||
color: ColorAlias = (255, 255, 255),
|
||||
background: str | BytesIO | Path | None = None,
|
||||
) -> Self | None:
|
||||
) -> Self:
|
||||
"""自动贴图
|
||||
|
||||
参数:
|
||||
@ -129,11 +135,15 @@ class BuildImage:
|
||||
Self: Self
|
||||
"""
|
||||
if not img_list:
|
||||
return None
|
||||
raise ValueError("贴图类别为空...")
|
||||
width, height = img_list[0].size
|
||||
background_width = width * row + space * (row - 1) + padding * 2
|
||||
column = math.ceil(len(img_list) / row)
|
||||
background_height = height * column + space * (column - 1) + padding * 2
|
||||
row_count = math.ceil(len(img_list) / row)
|
||||
if row_count == 1:
|
||||
background_width = (
|
||||
sum([img.width for img in img_list]) + space * (row - 1) + padding * 2
|
||||
)
|
||||
background_height = height * row_count + space * (row_count - 1) + padding * 2
|
||||
background_image = cls(
|
||||
background_width, background_height, color=color, background=background
|
||||
)
|
||||
@ -141,15 +151,16 @@ class BuildImage:
|
||||
for img in img_list:
|
||||
await background_image.paste(img, (_cur_width, _cur_height))
|
||||
_cur_width += space + img.width
|
||||
_cur_height += space + img.height
|
||||
if _cur_width + padding >= background_image.width:
|
||||
_cur_height += space + img.height
|
||||
_cur_width = padding
|
||||
return background_image
|
||||
|
||||
@classmethod
|
||||
def load_font(cls, font: str | Path, font_size: int) -> FreeTypeFont:
|
||||
"""
|
||||
加载字体
|
||||
def load_font(
|
||||
cls, font: str | Path = "HYWenHei-85W.ttf", font_size: int = 10
|
||||
) -> FreeTypeFont:
|
||||
"""加载字体
|
||||
|
||||
参数:
|
||||
font: 字体名称
|
||||
@ -165,19 +176,20 @@ class BuildImage:
|
||||
@classmethod
|
||||
def get_text_size(
|
||||
cls, text: str, font: FreeTypeFont | None = None
|
||||
) -> Tuple[int, int]:
|
||||
...
|
||||
) -> Tuple[int, int]: ...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
def get_text_size(
|
||||
cls, text: str, font: str | None = None, font_size: int = 10
|
||||
) -> Tuple[int, int]:
|
||||
...
|
||||
) -> Tuple[int, int]: ...
|
||||
|
||||
@classmethod
|
||||
def get_text_size(
|
||||
cls, text: str, font: str | FreeTypeFont | None = None, font_size: int = 10
|
||||
cls,
|
||||
text: str,
|
||||
font: str | FreeTypeFont | None = "HYWenHei-85W.ttf",
|
||||
font_size: int = 10,
|
||||
) -> Tuple[int, int]:
|
||||
"""获取该字体下文本需要的长宽
|
||||
|
||||
@ -192,7 +204,7 @@ class BuildImage:
|
||||
_font = font
|
||||
if font and type(font) == str:
|
||||
_font = cls.load_font(font, font_size)
|
||||
return _font.getsize(text) # type: ignore
|
||||
return _font.getsize(str(text)) # type: ignore
|
||||
|
||||
def getsize(self, msg: str) -> Tuple[int, int]:
|
||||
"""
|
||||
@ -265,7 +277,10 @@ class BuildImage:
|
||||
_image = image.markImg
|
||||
if _image.width and _image.height and center_type:
|
||||
pos = self.__center_xy(pos, _image.width, _image.height, center_type)
|
||||
self.markImg.paste(_image, pos, _image) # type: ignore
|
||||
try:
|
||||
self.markImg.paste(_image, pos, _image) # type: ignore
|
||||
except ValueError:
|
||||
self.markImg.paste(_image, pos) # type: ignore
|
||||
return self
|
||||
|
||||
@run_sync
|
||||
@ -335,6 +350,7 @@ class BuildImage:
|
||||
异常:
|
||||
ValueError: 居中类型错误
|
||||
"""
|
||||
text = str(text)
|
||||
if center_type and center_type not in ["center", "height", "width"]:
|
||||
raise ValueError("center_type must be 'center', 'width' or 'height'")
|
||||
width, height = 0, 0
|
||||
@ -484,7 +500,7 @@ class BuildImage:
|
||||
@run_sync
|
||||
def polygon(
|
||||
self,
|
||||
xy: List[Tuple[int, int]],
|
||||
xy: list[Tuple[int, int]],
|
||||
fill: Tuple[int, int, int] = (0, 0, 0),
|
||||
outline: int = 1,
|
||||
) -> Self:
|
||||
@ -560,7 +576,7 @@ class BuildImage:
|
||||
def circle_corner(
|
||||
self,
|
||||
radii: int = 30,
|
||||
point_list: List[Literal["lt", "rt", "lb", "rb"]] = ["lt", "rt", "lb", "rb"],
|
||||
point_list: list[Literal["lt", "rt", "lb", "rb"]] = ["lt", "rt", "lb", "rb"],
|
||||
) -> Self:
|
||||
"""
|
||||
矩形四角变圆
|
||||
@ -654,3 +670,11 @@ class BuildImage:
|
||||
self.markImg = self.markImg.filter(_type)
|
||||
self.draw = ImageDraw.Draw(self.markImg)
|
||||
return self
|
||||
|
||||
def tobytes(self) -> bytes:
|
||||
"""转换为bytes
|
||||
|
||||
返回:
|
||||
bytes: bytes
|
||||
"""
|
||||
return self.markImg.tobytes()
|
||||
|
||||
156
zhenxun/utils/_image_template.py
Normal file
156
zhenxun/utils/_image_template.py
Normal file
@ -0,0 +1,156 @@
|
||||
from email.mime import image
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from PIL.ImageFont import FreeTypeFont
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ._build_image import BuildImage
|
||||
|
||||
|
||||
class RowStyle(BaseModel):
|
||||
|
||||
font: FreeTypeFont | str | Path | None = "HYWenHei-85W.ttf"
|
||||
"""字体"""
|
||||
font_size: int = 20
|
||||
"""字体大小"""
|
||||
font_color: str | tuple[int, int, int] = (0, 0, 0)
|
||||
"""字体颜色"""
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
class ImageTemplate:
|
||||
|
||||
@classmethod
|
||||
async def table_page(
|
||||
cls,
|
||||
head_text: str,
|
||||
tip_text: str | None,
|
||||
column_name: list[str],
|
||||
data_list: list[list[str]],
|
||||
row_space: int = 35,
|
||||
column_space: int = 30,
|
||||
padding: int = 5,
|
||||
text_style: Callable[[str, str], RowStyle] | None = None,
|
||||
) -> BuildImage:
|
||||
"""表格页
|
||||
|
||||
参数:
|
||||
head_text: 标题文本.
|
||||
tip_text: 标题注释.
|
||||
column_name: 表头列表.
|
||||
data_list: 数据列表.
|
||||
row_space: 行间距.
|
||||
column_space: 列间距.
|
||||
padding: 文本内间距.
|
||||
text_style: 文本样式.
|
||||
|
||||
返回:
|
||||
BuildImage: 表格图片
|
||||
"""
|
||||
table = await cls.table(
|
||||
column_name, data_list, row_space, column_space, padding, text_style
|
||||
)
|
||||
await table.circle_corner()
|
||||
table_bk = BuildImage(table.width + 100, table.height + 50, "#EAEDF2")
|
||||
await table_bk.paste(table, center_type="center")
|
||||
height = table_bk.height + 200
|
||||
background = BuildImage(table_bk.width, height, (255, 255, 255), font_size=50)
|
||||
await background.paste(table_bk, (0, 200))
|
||||
await background.text((0, 50), head_text, "#334762", center_type="width")
|
||||
if tip_text:
|
||||
text_image = await BuildImage.build_text_image(tip_text, size=22)
|
||||
await background.paste(text_image, (0, 110), center_type="width")
|
||||
return background
|
||||
|
||||
@classmethod
|
||||
async def table(
|
||||
cls,
|
||||
column_name: list[str],
|
||||
data_list: list[list[str | tuple[Path, int, int]]],
|
||||
row_space: int = 25,
|
||||
column_space: int = 10,
|
||||
padding: int = 5,
|
||||
text_style: Callable[[str, str], RowStyle] | None = None,
|
||||
) -> BuildImage:
|
||||
"""表格
|
||||
|
||||
参数:
|
||||
column_name: 表头列表
|
||||
data_list: 数据列表
|
||||
row_space: 行间距.
|
||||
column_space: 列间距.
|
||||
padding: 文本内间距.
|
||||
text_style: 文本样式.
|
||||
|
||||
返回:
|
||||
BuildImage: 表格图片
|
||||
"""
|
||||
font = BuildImage.load_font("HYWenHei-85W.ttf", 20)
|
||||
column_num = max([len(l) for l in data_list])
|
||||
list_data = []
|
||||
column_data = []
|
||||
for i in range(len(column_name)):
|
||||
c = []
|
||||
for l in data_list:
|
||||
if len(l) > i:
|
||||
c.append(l[i])
|
||||
else:
|
||||
c.append("")
|
||||
column_data.append(c)
|
||||
build_data_list = []
|
||||
_, base_h = BuildImage.get_text_size("A", font)
|
||||
for i, column_list in enumerate(column_data):
|
||||
name_width, name_height = BuildImage.get_text_size(column_name[i], font)
|
||||
_temp = {"width": name_width, "data": column_list}
|
||||
for s in column_list:
|
||||
if isinstance(s, tuple):
|
||||
w = s[1]
|
||||
else:
|
||||
w, _ = BuildImage.get_text_size(s, font)
|
||||
if w > _temp["width"]:
|
||||
_temp["width"] = w
|
||||
build_data_list.append(_temp)
|
||||
column_image_list = []
|
||||
for i, data in enumerate(build_data_list):
|
||||
width = data["width"] + padding * 2
|
||||
height = (base_h + row_space) * (len(data["data"]) + 1) + padding * 2
|
||||
background = BuildImage(width, height, (255, 255, 255))
|
||||
column_name_image = await BuildImage.build_text_image(
|
||||
column_name[i], font, 12, "#C8CCCF"
|
||||
)
|
||||
await background.paste(column_name_image, (0, 20), center_type="width")
|
||||
cur_h = column_name_image.height + row_space + 20
|
||||
for item in data["data"]:
|
||||
style = RowStyle(font=font)
|
||||
if text_style:
|
||||
style = text_style(column_name[i], item)
|
||||
if isinstance(item, tuple):
|
||||
"""图片"""
|
||||
data, width, height = item
|
||||
if isinstance(data, Path):
|
||||
image_ = BuildImage(width, height, background=data)
|
||||
elif isinstance(data, bytes):
|
||||
image_ = BuildImage(width, height, background=BytesIO(data))
|
||||
elif isinstance(data, BuildImage):
|
||||
image_ = data
|
||||
await background.paste(image_, (padding, cur_h))
|
||||
else:
|
||||
await background.text(
|
||||
(padding, cur_h),
|
||||
item if item is not None else "",
|
||||
style.font_color,
|
||||
font=style.font,
|
||||
font_size=style.font_size,
|
||||
)
|
||||
cur_h += base_h + row_space
|
||||
column_image_list.append(background)
|
||||
height = max([bk.height for bk in column_image_list])
|
||||
width = sum([bk.width for bk in column_image_list])
|
||||
return await BuildImage.auto_paste(
|
||||
column_image_list, len(column_image_list), column_space
|
||||
)
|
||||
@ -1,14 +1,12 @@
|
||||
from typing import Optional
|
||||
|
||||
from nonebot import get_driver
|
||||
from playwright.async_api import Browser, Playwright, async_playwright
|
||||
|
||||
from services.log import logger
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
_playwright: Optional[Playwright] = None
|
||||
_browser: Optional[Browser] = None
|
||||
_playwright: Playwright | None = None
|
||||
_browser: Browser | None = None
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
|
||||
204
zhenxun/utils/decorator/shop.py
Normal file
204
zhenxun/utils/decorator/shop.py
Normal file
@ -0,0 +1,204 @@
|
||||
from typing import Callable, Union, Tuple, Optional
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment, Message
|
||||
from nonebot.plugin import require
|
||||
|
||||
|
||||
class ShopRegister(dict):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ShopRegister, self).__init__(*args, **kwargs)
|
||||
self._data = {}
|
||||
self._flag = True
|
||||
|
||||
def before_handle(self, name: Union[str, Tuple[str, ...]], load_status: bool = True):
|
||||
"""
|
||||
说明:
|
||||
使用前检查方法
|
||||
参数:
|
||||
:param name: 道具名称
|
||||
:param load_status: 加载状态
|
||||
"""
|
||||
def register_before_handle(name_list: Tuple[str, ...], func: Callable):
|
||||
if load_status:
|
||||
for name_ in name_list:
|
||||
if not self._data[name_]:
|
||||
self._data[name_] = {}
|
||||
if not self._data[name_].get('before_handle'):
|
||||
self._data[name_]['before_handle'] = []
|
||||
self._data[name]['before_handle'].append(func)
|
||||
_name = (name,) if isinstance(name, str) else name
|
||||
return lambda func: register_before_handle(_name, func)
|
||||
|
||||
def after_handle(self, name: Union[str, Tuple[str, ...]], load_status: bool = True):
|
||||
"""
|
||||
说明:
|
||||
使用后执行方法
|
||||
参数:
|
||||
:param name: 道具名称
|
||||
:param load_status: 加载状态
|
||||
"""
|
||||
def register_after_handle(name_list: Tuple[str, ...], func: Callable):
|
||||
if load_status:
|
||||
for name_ in name_list:
|
||||
if not self._data[name_]:
|
||||
self._data[name_] = {}
|
||||
if not self._data[name_].get('after_handle'):
|
||||
self._data[name_]['after_handle'] = []
|
||||
self._data[name_]['after_handle'].append(func)
|
||||
_name = (name,) if isinstance(name, str) else name
|
||||
return lambda func: register_after_handle(_name, func)
|
||||
|
||||
def register(
|
||||
self,
|
||||
name: Tuple[str, ...],
|
||||
price: Tuple[float, ...],
|
||||
des: Tuple[str, ...],
|
||||
discount: Tuple[float, ...],
|
||||
limit_time: Tuple[int, ...],
|
||||
load_status: Tuple[bool, ...],
|
||||
daily_limit: Tuple[int, ...],
|
||||
is_passive: Tuple[bool, ...],
|
||||
icon: Tuple[str, ...],
|
||||
**kwargs,
|
||||
):
|
||||
def add_register_item(func: Callable):
|
||||
if name in self._data.keys():
|
||||
raise ValueError("该商品已注册,请替换其他名称!")
|
||||
for n, p, d, dd, l, s, dl, pa, i in zip(
|
||||
name, price, des, discount, limit_time, load_status, daily_limit, is_passive, icon
|
||||
):
|
||||
if s:
|
||||
_temp_kwargs = {}
|
||||
for key, value in kwargs.items():
|
||||
if key.startswith(f"{n}_"):
|
||||
_temp_kwargs[key.split("_", maxsplit=1)[-1]] = value
|
||||
else:
|
||||
_temp_kwargs[key] = value
|
||||
temp = self._data.get(n, {})
|
||||
temp.update({
|
||||
"price": p,
|
||||
"des": d,
|
||||
"discount": dd,
|
||||
"limit_time": l,
|
||||
"daily_limit": dl,
|
||||
"icon": i,
|
||||
"is_passive": pa,
|
||||
"func": func,
|
||||
"kwargs": _temp_kwargs,
|
||||
})
|
||||
self._data[n] = temp
|
||||
return func
|
||||
|
||||
return lambda func: add_register_item(func)
|
||||
|
||||
async def load_register(self):
|
||||
require("use")
|
||||
require("shop_handle")
|
||||
from basic_plugins.shop.use.data_source import register_use, func_manager
|
||||
from basic_plugins.shop.shop_handle.data_source import register_goods
|
||||
# 统一进行注册
|
||||
if self._flag:
|
||||
# 只进行一次注册
|
||||
self._flag = False
|
||||
for name in self._data.keys():
|
||||
await register_goods(
|
||||
name,
|
||||
self._data[name]["price"],
|
||||
self._data[name]["des"],
|
||||
self._data[name]["discount"],
|
||||
self._data[name]["limit_time"],
|
||||
self._data[name]["daily_limit"],
|
||||
self._data[name]["is_passive"],
|
||||
self._data[name]["icon"],
|
||||
)
|
||||
register_use(
|
||||
name, self._data[name]["func"], **self._data[name]["kwargs"]
|
||||
)
|
||||
func_manager.register_use_before_handle(name, self._data[name].get('before_handle', []))
|
||||
func_manager.register_use_after_handle(name, self._data[name].get('after_handle', []))
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
name: Union[str, Tuple[str, ...]], # 名称
|
||||
price: Union[float, Tuple[float, ...]], # 价格
|
||||
des: Union[str, Tuple[str, ...]], # 简介
|
||||
discount: Union[float, Tuple[float, ...]] = 1, # 折扣
|
||||
limit_time: Union[int, Tuple[int, ...]] = 0, # 限时
|
||||
load_status: Union[bool, Tuple[bool, ...]] = True, # 加载状态
|
||||
daily_limit: Union[int, Tuple[int, ...]] = 0, # 每日限购
|
||||
is_passive: Union[bool, Tuple[bool, ...]] = False, # 被动道具(无法被'使用道具'命令消耗)
|
||||
icon: Union[str, Tuple[str, ...]] = False, # 图标
|
||||
**kwargs,
|
||||
):
|
||||
_tuple_list = []
|
||||
_current_len = -1
|
||||
for x in [name, price, des, discount, limit_time, load_status]:
|
||||
if isinstance(x, tuple):
|
||||
if _current_len == -1:
|
||||
_current_len = len(x)
|
||||
if _current_len != len(x):
|
||||
raise ValueError(
|
||||
f"注册商品 {name} 中 name,price,des,discount,limit_time,load_status,daily_limit 数量不符!"
|
||||
)
|
||||
_current_len = _current_len if _current_len > -1 else 1
|
||||
_name = self.__get(name, _current_len)
|
||||
_price = self.__get(price, _current_len)
|
||||
_discount = self.__get(discount, _current_len)
|
||||
_limit_time = self.__get(limit_time, _current_len)
|
||||
_des = self.__get(des, _current_len)
|
||||
_load_status = self.__get(load_status, _current_len)
|
||||
_daily_limit = self.__get(daily_limit, _current_len)
|
||||
_is_passive = self.__get(is_passive, _current_len)
|
||||
_icon = self.__get(icon, _current_len)
|
||||
return self.register(
|
||||
_name,
|
||||
_price,
|
||||
_des,
|
||||
_discount,
|
||||
_limit_time,
|
||||
_load_status,
|
||||
_daily_limit,
|
||||
_is_passive,
|
||||
_icon,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def __get(self, value, _current_len):
|
||||
return value if isinstance(value, tuple) else tuple([value for _ in range(_current_len)])
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._data[key] = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._data[key]
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._data
|
||||
|
||||
def __str__(self):
|
||||
return str(self._data)
|
||||
|
||||
def keys(self):
|
||||
return self._data.keys()
|
||||
|
||||
def values(self):
|
||||
return self._data.values()
|
||||
|
||||
def items(self):
|
||||
return self._data.items()
|
||||
|
||||
|
||||
class NotMeetUseConditionsException(Exception):
|
||||
|
||||
"""
|
||||
不满足条件异常类
|
||||
"""
|
||||
|
||||
def __init__(self, info: Optional[Union[str, MessageSegment, Message]]):
|
||||
super().__init__(self)
|
||||
self._info = info
|
||||
|
||||
def get_info(self):
|
||||
return self._info
|
||||
|
||||
|
||||
shop_register = ShopRegister()
|
||||
@ -1,15 +1,38 @@
|
||||
from strenum import StrEnum
|
||||
|
||||
|
||||
class GoldHandle(StrEnum):
|
||||
"""
|
||||
金币处理
|
||||
"""
|
||||
|
||||
BUY = "BUY"
|
||||
"""购买"""
|
||||
GET = "GET"
|
||||
"""获取"""
|
||||
|
||||
|
||||
class PropHandle(StrEnum):
|
||||
"""
|
||||
道具处理
|
||||
"""
|
||||
|
||||
BUY = "BUY"
|
||||
"""购买"""
|
||||
USE = "USE"
|
||||
"""使用"""
|
||||
|
||||
|
||||
class PluginType(StrEnum):
|
||||
"""
|
||||
插件类型
|
||||
"""
|
||||
|
||||
SUPERUSER = "超级管理员插件"
|
||||
ADMIN = "管理员插件"
|
||||
NORMAL = "普通插件"
|
||||
HIDDEN = "被动插件"
|
||||
SUPERUSER = "SUPERUSER"
|
||||
ADMIN = "ADMIN"
|
||||
SUPER_AND_ADMIN = "ADMIN_SUPER"
|
||||
NORMAL = "NORMAL"
|
||||
HIDDEN = "HIDDEN"
|
||||
|
||||
|
||||
class BlockType(StrEnum):
|
||||
|
||||
@ -1,2 +1,14 @@
|
||||
class NotFoundError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GroupInfoNotFound(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class EmptyError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UserAndGroupIsNone(Exception):
|
||||
pass
|
||||
|
||||
379
zhenxun/utils/http_utils.py
Normal file
379
zhenxun/utils/http_utils.py
Normal file
@ -0,0 +1,379 @@
|
||||
import asyncio
|
||||
from asyncio.exceptions import TimeoutError
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from typing import Any, AsyncGenerator, Dict, Literal
|
||||
|
||||
import aiofiles
|
||||
import httpx
|
||||
import rich
|
||||
from httpx import ConnectTimeout, Response
|
||||
from nonebot import require
|
||||
from playwright.async_api import Page
|
||||
from retrying import retry
|
||||
|
||||
from zhenxun.configs.config import SYSTEM_PROXY
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.user_agent import get_user_agent
|
||||
|
||||
from .browser import get_browser
|
||||
|
||||
require("nonebot_plugin_saa")
|
||||
|
||||
from nonebot_plugin_saa import Image
|
||||
|
||||
|
||||
class AsyncHttpx:
|
||||
|
||||
proxy = {"http://": SYSTEM_PROXY, "https://": SYSTEM_PROXY}
|
||||
|
||||
@classmethod
|
||||
@retry(stop_max_attempt_number=3)
|
||||
async def get(
|
||||
cls,
|
||||
url: str,
|
||||
*,
|
||||
params: Dict[str, Any] | None = None,
|
||||
headers: Dict[str, str] | None = None,
|
||||
cookies: Dict[str, str] | None = None,
|
||||
verify: bool = True,
|
||||
use_proxy: bool = True,
|
||||
proxy: Dict[str, str] | None = None,
|
||||
timeout: int = 30,
|
||||
**kwargs,
|
||||
) -> Response:
|
||||
"""Get
|
||||
|
||||
参数:
|
||||
url: url
|
||||
params: params
|
||||
headers: 请求头
|
||||
cookies: cookies
|
||||
verify: verify
|
||||
use_proxy: 使用默认代理
|
||||
proxy: 指定代理
|
||||
timeout: 超时时间
|
||||
"""
|
||||
if not headers:
|
||||
headers = get_user_agent()
|
||||
_proxy = proxy if proxy else cls.proxy if use_proxy else None
|
||||
async with httpx.AsyncClient(proxies=_proxy, verify=verify) as client: # type: ignore
|
||||
return await client.get(
|
||||
url,
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
timeout=timeout,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def post(
|
||||
cls,
|
||||
url: str,
|
||||
*,
|
||||
data: Dict[str, str] | None = None,
|
||||
content: Any = None,
|
||||
files: Any = None,
|
||||
verify: bool = True,
|
||||
use_proxy: bool = True,
|
||||
proxy: Dict[str, str] | None = None,
|
||||
json: Dict[str, Any] | None = None,
|
||||
params: Dict[str, str] | None = None,
|
||||
headers: Dict[str, str] | None = None,
|
||||
cookies: Dict[str, str] | None = None,
|
||||
timeout: int = 30,
|
||||
**kwargs,
|
||||
) -> Response:
|
||||
"""
|
||||
说明:
|
||||
Post
|
||||
参数:
|
||||
url: url
|
||||
data: data
|
||||
content: content
|
||||
files: files
|
||||
use_proxy: 是否默认代理
|
||||
proxy: 指定代理
|
||||
json: json
|
||||
params: params
|
||||
headers: 请求头
|
||||
cookies: cookies
|
||||
timeout: 超时时间
|
||||
"""
|
||||
if not headers:
|
||||
headers = get_user_agent()
|
||||
_proxy = proxy if proxy else cls.proxy if use_proxy else None
|
||||
async with httpx.AsyncClient(proxies=_proxy, verify=verify) as client: # type: ignore
|
||||
return await client.post(
|
||||
url,
|
||||
content=content,
|
||||
data=data,
|
||||
files=files,
|
||||
json=json,
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
timeout=timeout,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def download_file(
|
||||
cls,
|
||||
url: str,
|
||||
path: str | Path,
|
||||
*,
|
||||
params: Dict[str, str] | None = None,
|
||||
verify: bool = True,
|
||||
use_proxy: bool = True,
|
||||
proxy: Dict[str, str] | None = None,
|
||||
headers: Dict[str, str] | None = None,
|
||||
cookies: Dict[str, str] | None = None,
|
||||
timeout: int = 30,
|
||||
stream: bool = False,
|
||||
**kwargs,
|
||||
) -> bool:
|
||||
"""下载文件
|
||||
|
||||
参数:
|
||||
url: url
|
||||
path: 存储路径
|
||||
params: params
|
||||
verify: verify
|
||||
use_proxy: 使用代理
|
||||
proxy: 指定代理
|
||||
headers: 请求头
|
||||
cookies: cookies
|
||||
timeout: 超时时间
|
||||
stream: 是否使用流式下载(流式写入+进度条,适用于下载大文件)
|
||||
"""
|
||||
if isinstance(path, str):
|
||||
path = Path(path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
for _ in range(3):
|
||||
if not stream:
|
||||
try:
|
||||
content = (
|
||||
await cls.get(
|
||||
url,
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
use_proxy=use_proxy,
|
||||
proxy=proxy,
|
||||
timeout=timeout,
|
||||
**kwargs,
|
||||
)
|
||||
).content
|
||||
async with aiofiles.open(path, "wb") as wf:
|
||||
await wf.write(content)
|
||||
logger.info(f"下载 {url} 成功.. Path:{path.absolute()}")
|
||||
return True
|
||||
except (TimeoutError, ConnectTimeout):
|
||||
pass
|
||||
else:
|
||||
if not headers:
|
||||
headers = get_user_agent()
|
||||
_proxy = proxy if proxy else cls.proxy if use_proxy else None
|
||||
try:
|
||||
async with httpx.AsyncClient(
|
||||
proxies=_proxy, verify=verify # type: ignore
|
||||
) as client:
|
||||
async with client.stream(
|
||||
"GET",
|
||||
url,
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
timeout=timeout,
|
||||
**kwargs,
|
||||
) as response:
|
||||
logger.info(
|
||||
f"开始下载 {path.name}.. Path: {path.absolute()}"
|
||||
)
|
||||
async with aiofiles.open(path, "wb") as wf:
|
||||
total = int(response.headers["Content-Length"])
|
||||
with rich.progress.Progress( # type: ignore
|
||||
rich.progress.TextColumn(path.name), # type: ignore
|
||||
"[progress.percentage]{task.percentage:>3.0f}%", # type: ignore
|
||||
rich.progress.BarColumn(bar_width=None), # type: ignore
|
||||
rich.progress.DownloadColumn(), # type: ignore
|
||||
rich.progress.TransferSpeedColumn(), # type: ignore
|
||||
) as progress:
|
||||
download_task = progress.add_task(
|
||||
"Download", total=total
|
||||
)
|
||||
async for chunk in response.aiter_bytes():
|
||||
await wf.write(chunk)
|
||||
await wf.flush()
|
||||
progress.update(
|
||||
download_task,
|
||||
completed=response.num_bytes_downloaded,
|
||||
)
|
||||
logger.info(
|
||||
f"下载 {url} 成功.. Path:{path.absolute()}"
|
||||
)
|
||||
return True
|
||||
except (TimeoutError, ConnectTimeout):
|
||||
pass
|
||||
else:
|
||||
logger.error(f"下载 {url} 下载超时.. Path:{path.absolute()}")
|
||||
except Exception as e:
|
||||
logger.error(f"下载 {url} 错误 Path:{path.absolute()}", e=e)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def gather_download_file(
|
||||
cls,
|
||||
url_list: list[str],
|
||||
path_list: list[str | Path],
|
||||
*,
|
||||
limit_async_number: int | None = None,
|
||||
params: Dict[str, str] | None = None,
|
||||
use_proxy: bool = True,
|
||||
proxy: Dict[str, str] | None = None,
|
||||
headers: Dict[str, str] | None = None,
|
||||
cookies: Dict[str, str] | None = None,
|
||||
timeout: int = 30,
|
||||
**kwargs,
|
||||
) -> list[bool]:
|
||||
"""分组同时下载文件
|
||||
|
||||
参数:
|
||||
url_list: url列表
|
||||
path_list: 存储路径列表
|
||||
limit_async_number: 限制同时请求数量
|
||||
params: params
|
||||
use_proxy: 使用代理
|
||||
proxy: 指定代理
|
||||
headers: 请求头
|
||||
cookies: cookies
|
||||
timeout: 超时时间
|
||||
"""
|
||||
if n := len(url_list) != len(path_list):
|
||||
raise UrlPathNumberNotEqual(
|
||||
f"Url数量与Path数量不对等,Url:{len(url_list)},Path:{len(path_list)}"
|
||||
)
|
||||
if limit_async_number and n > limit_async_number:
|
||||
m = float(n) / limit_async_number
|
||||
x = 0
|
||||
j = limit_async_number
|
||||
_split_url_list = []
|
||||
_split_path_list = []
|
||||
for _ in range(int(m)):
|
||||
_split_url_list.append(url_list[x:j])
|
||||
_split_path_list.append(path_list[x:j])
|
||||
x += limit_async_number
|
||||
j += limit_async_number
|
||||
if int(m) < m:
|
||||
_split_url_list.append(url_list[j:])
|
||||
_split_path_list.append(path_list[j:])
|
||||
else:
|
||||
_split_url_list = [url_list]
|
||||
_split_path_list = [path_list]
|
||||
tasks = []
|
||||
result_ = []
|
||||
for x, y in zip(_split_url_list, _split_path_list):
|
||||
for url, path in zip(x, y):
|
||||
tasks.append(
|
||||
asyncio.create_task(
|
||||
cls.download_file(
|
||||
url,
|
||||
path,
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
use_proxy=use_proxy,
|
||||
timeout=timeout,
|
||||
proxy=proxy,
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
)
|
||||
_x = await asyncio.gather(*tasks)
|
||||
result_ = result_ + list(_x)
|
||||
tasks.clear()
|
||||
return result_
|
||||
|
||||
|
||||
class AsyncPlaywright:
|
||||
@classmethod
|
||||
@asynccontextmanager
|
||||
async def new_page(cls, **kwargs) -> AsyncGenerator[Page, None]:
|
||||
"""获取一个新页面
|
||||
|
||||
参数:
|
||||
user_agent: 请求头
|
||||
"""
|
||||
browser = get_browser()
|
||||
ctx = await browser.new_context(**kwargs)
|
||||
page = await ctx.new_page()
|
||||
try:
|
||||
yield page
|
||||
finally:
|
||||
await page.close()
|
||||
await ctx.close()
|
||||
|
||||
@classmethod
|
||||
async def screenshot(
|
||||
cls,
|
||||
url: str,
|
||||
path: Path | str,
|
||||
element: str | list[str],
|
||||
*,
|
||||
wait_time: int | None = None,
|
||||
viewport_size: Dict[str, int] | None = None,
|
||||
wait_until: (
|
||||
Literal["domcontentloaded", "load", "networkidle"] | None
|
||||
) = "networkidle",
|
||||
timeout: float | None = None,
|
||||
type_: Literal["jpeg", "png"] | None = None,
|
||||
user_agent: str | None = None,
|
||||
**kwargs,
|
||||
) -> Image | None:
|
||||
"""截图,该方法仅用于简单快捷截图,复杂截图请操作 page
|
||||
|
||||
参数:
|
||||
url: 网址
|
||||
path: 存储路径
|
||||
element: 元素选择
|
||||
wait_time: 等待截取超时时间
|
||||
viewport_size: 窗口大小
|
||||
wait_until: 等待类型
|
||||
timeout: 超时限制
|
||||
type_: 保存类型
|
||||
"""
|
||||
if viewport_size is None:
|
||||
viewport_size = dict(width=2560, height=1080)
|
||||
if isinstance(path, str):
|
||||
path = Path(path)
|
||||
wait_time = wait_time * 1000 if wait_time else None
|
||||
if isinstance(element, str):
|
||||
element_list = [element]
|
||||
else:
|
||||
element_list = element
|
||||
async with cls.new_page(
|
||||
viewport=viewport_size,
|
||||
user_agent=user_agent,
|
||||
**kwargs,
|
||||
) as page:
|
||||
await page.goto(url, timeout=timeout, wait_until=wait_until)
|
||||
card = page
|
||||
for e in element_list:
|
||||
if not card:
|
||||
return None
|
||||
card = await card.wait_for_selector(e, timeout=wait_time)
|
||||
if card:
|
||||
await card.screenshot(path=path, timeout=timeout, type=type_)
|
||||
return Image(path)
|
||||
return None
|
||||
|
||||
|
||||
class UrlPathNumberNotEqual(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BrowserIsNone(Exception):
|
||||
pass
|
||||
@ -1,2 +1,339 @@
|
||||
from ._build_image import BuildImage
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Awaitable, Callable
|
||||
|
||||
from nonebot.utils import is_coroutine_callable
|
||||
|
||||
from ._build_image import BuildImage, ColorAlias
|
||||
from ._build_mat import BuildMat
|
||||
from ._image_template import ImageTemplate, RowStyle
|
||||
|
||||
# TODO: text2image 长度错误
|
||||
|
||||
|
||||
async def text2image(
|
||||
text: str,
|
||||
auto_parse: bool = True,
|
||||
font_size: int = 20,
|
||||
color: str | tuple[int, int, int] = (255, 255, 255),
|
||||
font: str = "HYWenHei-85W.ttf",
|
||||
font_color: str | tuple[int, int, int] = (0, 0, 0),
|
||||
padding: int | tuple[int, int, int, int] = 0,
|
||||
_add_height: float = 0,
|
||||
) -> BuildImage:
|
||||
"""解析文本并转为图片
|
||||
使用标签
|
||||
<f> </f>
|
||||
可选配置项
|
||||
font: str -> 特殊文本字体
|
||||
fs / font_size: int -> 特殊文本大小
|
||||
fc / font_color: Union[str, Tuple[int, int, int]] -> 特殊文本颜色
|
||||
示例
|
||||
在不在,<f font=YSHaoShenTi-2.ttf font_size=30 font_color=red>HibiKi小姐</f>,
|
||||
你最近还好吗,<f font_size=15 font_color=black>我非常想你</f>,这段时间我非常不好过,
|
||||
<f font_size=25>抽卡抽不到金色</f>,这让我很痛苦
|
||||
参数:
|
||||
text: 文本
|
||||
auto_parse: 是否自动解析,否则原样发送
|
||||
font_size: 普通字体大小
|
||||
color: 背景颜色
|
||||
font: 普通字体
|
||||
font_color: 普通字体颜色
|
||||
padding: 文本外边距,元组类型时为 (上,左,下,右)
|
||||
_add_height: 由于get_size无法返回正确的高度,采用手动方式额外添加高度
|
||||
"""
|
||||
if not text:
|
||||
raise ValueError("文本转图片 text 不能为空...")
|
||||
pw = ph = top_padding = left_padding = 0
|
||||
if padding:
|
||||
if isinstance(padding, int):
|
||||
pw = padding * 2
|
||||
ph = padding * 2
|
||||
top_padding = left_padding = padding
|
||||
elif isinstance(padding, tuple):
|
||||
pw = padding[0] + padding[2]
|
||||
ph = padding[1] + padding[3]
|
||||
top_padding = padding[0]
|
||||
left_padding = padding[1]
|
||||
_font = BuildImage.load_font(font, font_size)
|
||||
if auto_parse and re.search(r"<f(.*)>(.*)</f>", text):
|
||||
_data = []
|
||||
new_text = ""
|
||||
placeholder_index = 0
|
||||
for s in text.split("</f>"):
|
||||
r = re.search(r"<f(.*)>(.*)", s)
|
||||
if r:
|
||||
start, end = r.span()
|
||||
if start != 0 and (t := s[:start]):
|
||||
new_text += t
|
||||
_data.append(
|
||||
[
|
||||
(start, end),
|
||||
f"[placeholder_{placeholder_index}]",
|
||||
r.group(1).strip(),
|
||||
r.group(2),
|
||||
]
|
||||
)
|
||||
new_text += f"[placeholder_{placeholder_index}]"
|
||||
placeholder_index += 1
|
||||
new_text += text.split("</f>")[-1]
|
||||
image_list = []
|
||||
current_placeholder_index = 0
|
||||
# 切分换行,每行为单张图片
|
||||
for s in new_text.split("\n"):
|
||||
_tmp_text = s
|
||||
img_width = 0
|
||||
img_height = BuildImage.get_text_size("正", _font)[1]
|
||||
_tmp_index = current_placeholder_index
|
||||
for _ in range(s.count("[placeholder_")):
|
||||
placeholder = _data[_tmp_index]
|
||||
if "font_size" in placeholder[2]:
|
||||
r = re.search(r"font_size=['\"]?(\d+)", placeholder[2])
|
||||
if r:
|
||||
w, h = BuildImage.get_text_size(
|
||||
placeholder[3], font, int(r.group(1))
|
||||
)
|
||||
img_height = img_height if img_height > h else h
|
||||
img_width += w
|
||||
else:
|
||||
img_width += BuildImage.get_text_size(placeholder[3], _font)[0]
|
||||
_tmp_text = _tmp_text.replace(f"[placeholder_{_tmp_index}]", "")
|
||||
_tmp_index += 1
|
||||
img_width += BuildImage.get_text_size(_tmp_text, _font)[0]
|
||||
# 开始画图
|
||||
A = BuildImage(
|
||||
img_width, img_height, color=color, font=font, font_size=font_size
|
||||
)
|
||||
basic_font_h = A.getsize("正")[1]
|
||||
current_width = 0
|
||||
# 遍历占位符
|
||||
for _ in range(s.count("[placeholder_")):
|
||||
if not s.startswith(f"[placeholder_{current_placeholder_index}]"):
|
||||
slice_ = s.split(f"[placeholder_{current_placeholder_index}]")
|
||||
await A.text(
|
||||
(current_width, A.height - basic_font_h - 1),
|
||||
slice_[0],
|
||||
font_color,
|
||||
)
|
||||
current_width += A.getsize(slice_[0])[0]
|
||||
placeholder = _data[current_placeholder_index]
|
||||
# 解析配置
|
||||
_font = font
|
||||
_font_size = font_size
|
||||
_font_color = font_color
|
||||
for e in placeholder[2].split():
|
||||
if e.startswith("font="):
|
||||
_font = e.split("=")[-1]
|
||||
if e.startswith("font_size=") or e.startswith("fs="):
|
||||
_font_size = int(e.split("=")[-1])
|
||||
if _font_size > 1000:
|
||||
_font_size = 1000
|
||||
if _font_size < 1:
|
||||
_font_size = 1
|
||||
if e.startswith("font_color") or e.startswith("fc="):
|
||||
_font_color = e.split("=")[-1]
|
||||
text_img = await BuildImage.build_text_image(
|
||||
placeholder[3], font=_font, size=_font_size, font_color=_font_color
|
||||
)
|
||||
_img_h = (
|
||||
int(A.height / 2 - text_img.height / 2)
|
||||
if new_text == "[placeholder_0]"
|
||||
else A.height - text_img.height
|
||||
)
|
||||
await A.paste(text_img, (current_width, _img_h - 1))
|
||||
current_width += text_img.width
|
||||
s = s[
|
||||
s.index(f"[placeholder_{current_placeholder_index}]")
|
||||
+ len(f"[placeholder_{current_placeholder_index}]") :
|
||||
]
|
||||
current_placeholder_index += 1
|
||||
if s:
|
||||
slice_ = s.split(f"[placeholder_{current_placeholder_index}]")
|
||||
await A.text((current_width, A.height - basic_font_h), slice_[0])
|
||||
current_width += A.getsize(slice_[0])[0]
|
||||
await A.crop((0, 0, current_width, A.height))
|
||||
# A.show()
|
||||
image_list.append(A)
|
||||
height = 0
|
||||
width = 0
|
||||
for img in image_list:
|
||||
height += img.h
|
||||
width = width if width > img.w else img.w
|
||||
width += pw
|
||||
height += ph
|
||||
A = BuildImage(width + left_padding, height + top_padding, color=color)
|
||||
current_height = top_padding
|
||||
for img in image_list:
|
||||
await A.paste(img, (left_padding, current_height))
|
||||
current_height += img.h
|
||||
else:
|
||||
width = 0
|
||||
height = 0
|
||||
_, h = BuildImage.get_text_size("正", _font)
|
||||
line_height = int(font_size / 3)
|
||||
image_list = []
|
||||
for s in text.split("\n"):
|
||||
w, _ = BuildImage.get_text_size(s.strip() or "正", _font)
|
||||
height += h + line_height
|
||||
width = width if width > w else w
|
||||
image_list.append(
|
||||
await BuildImage.build_text_image(
|
||||
s.strip(), font, font_size, font_color
|
||||
)
|
||||
)
|
||||
width += pw
|
||||
height += ph
|
||||
A = BuildImage(
|
||||
width + left_padding,
|
||||
height + top_padding + 2,
|
||||
color=color,
|
||||
)
|
||||
cur_h = ph
|
||||
for img in image_list:
|
||||
await A.paste(img, (pw, cur_h))
|
||||
cur_h += img.height + line_height
|
||||
return A
|
||||
|
||||
|
||||
def group_image(image_list: list[BuildImage]) -> tuple[list[list[BuildImage]], int]:
|
||||
"""
|
||||
说明:
|
||||
根据图片大小进行分组
|
||||
参数:
|
||||
image_list: 排序图片列表
|
||||
"""
|
||||
image_list.sort(key=lambda x: x.height, reverse=True)
|
||||
max_image = max(image_list, key=lambda x: x.height)
|
||||
|
||||
image_list.remove(max_image)
|
||||
max_h = max_image.height
|
||||
total_w = 0
|
||||
|
||||
# 图片分组
|
||||
image_group = [[max_image]]
|
||||
is_use = []
|
||||
surplus_list = image_list[:]
|
||||
|
||||
for image in image_list:
|
||||
if image.uid not in is_use:
|
||||
group = [image]
|
||||
is_use.append(image.uid)
|
||||
curr_h = image.height
|
||||
while True:
|
||||
surplus_list = [x for x in surplus_list if x.uid not in is_use]
|
||||
for tmp in surplus_list:
|
||||
temp_h = curr_h + tmp.height + 10
|
||||
if temp_h < max_h or abs(max_h - temp_h) < 100:
|
||||
curr_h += tmp.height + 15
|
||||
is_use.append(tmp.uid)
|
||||
group.append(tmp)
|
||||
break
|
||||
else:
|
||||
break
|
||||
total_w += max([x.width for x in group]) + 15
|
||||
image_group.append(group)
|
||||
while surplus_list:
|
||||
surplus_list = [x for x in surplus_list if x.uid not in is_use]
|
||||
if not surplus_list:
|
||||
break
|
||||
surplus_list.sort(key=lambda x: x.height, reverse=True)
|
||||
for img in surplus_list:
|
||||
if img.uid not in is_use:
|
||||
_w = 0
|
||||
index = -1
|
||||
for i, ig in enumerate(image_group):
|
||||
if s := sum([x.height for x in ig]) > _w:
|
||||
_w = s
|
||||
index = i
|
||||
if index != -1:
|
||||
image_group[index].append(img)
|
||||
is_use.append(img.uid)
|
||||
|
||||
max_h = 0
|
||||
max_w = 0
|
||||
for ig in image_group:
|
||||
if (_h := sum([x.height + 15 for x in ig])) > max_h:
|
||||
max_h = _h
|
||||
max_w += max([x.width for x in ig]) + 30
|
||||
is_use.clear()
|
||||
while abs(max_h - max_w) > 200 and len(image_group) - 1 >= len(image_group[-1]):
|
||||
for img in image_group[-1]:
|
||||
_min_h = 999999
|
||||
_min_index = -1
|
||||
for i, ig in enumerate(image_group):
|
||||
# if i not in is_use and (_h := sum([x.h for x in ig]) + img.h) > _min_h:
|
||||
if (_h := sum([x.height for x in ig]) + img.height) < _min_h:
|
||||
_min_h = _h
|
||||
_min_index = i
|
||||
is_use.append(_min_index)
|
||||
image_group[_min_index].append(img)
|
||||
max_w -= max([x.width for x in image_group[-1]]) - 30
|
||||
image_group.pop(-1)
|
||||
max_h = max([sum([x.height + 15 for x in ig]) for ig in image_group])
|
||||
return image_group, max(max_h + 250, max_w + 70)
|
||||
|
||||
|
||||
async def build_sort_image(
|
||||
image_group: list[list[BuildImage]],
|
||||
h: int | None = None,
|
||||
padding_top: int = 200,
|
||||
color: ColorAlias = (
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
),
|
||||
background_path: Path | None = None,
|
||||
background_handle: Callable[[BuildImage], Awaitable] | None = None,
|
||||
) -> BuildImage:
|
||||
"""
|
||||
说明:
|
||||
对group_image的图片进行组装
|
||||
参数:
|
||||
image_group: 分组图片列表
|
||||
h: max(宽,高),一般为group_image的返回值,有值时,图片必定为正方形
|
||||
padding_top: 图像列表与最顶层间距
|
||||
color: 背景颜色
|
||||
background_path: 背景图片文件夹路径(随机)
|
||||
background_handle: 背景图额外操作
|
||||
"""
|
||||
bk_file = None
|
||||
if background_path:
|
||||
random_bk = os.listdir(background_path)
|
||||
if random_bk:
|
||||
bk_file = random.choice(random_bk)
|
||||
image_w = 0
|
||||
image_h = 0
|
||||
if not h:
|
||||
for ig in image_group:
|
||||
_w = max([x.width + 30 for x in ig])
|
||||
image_w += _w + 30
|
||||
_h = sum([x.height + 10 for x in ig])
|
||||
if _h > image_h:
|
||||
image_h = _h
|
||||
image_h += padding_top
|
||||
else:
|
||||
image_w = h
|
||||
image_h = h
|
||||
A = BuildImage(
|
||||
image_w,
|
||||
image_h,
|
||||
font_size=24,
|
||||
font="CJGaoDeGuo.otf",
|
||||
color=color,
|
||||
background=(background_path / bk_file) if background_path and bk_file else None,
|
||||
)
|
||||
if background_handle:
|
||||
if is_coroutine_callable(background_handle):
|
||||
await background_handle(A)
|
||||
else:
|
||||
background_handle(A)
|
||||
curr_w = 50
|
||||
for ig in image_group:
|
||||
curr_h = padding_top - 20
|
||||
for img in ig:
|
||||
await A.paste(img, (curr_w, curr_h))
|
||||
curr_h += img.height + 10
|
||||
curr_w += max([x.width for x in ig]) + 30
|
||||
return A
|
||||
|
||||
50
zhenxun/utils/user_agent.py
Normal file
50
zhenxun/utils/user_agent.py
Normal file
@ -0,0 +1,50 @@
|
||||
import random
|
||||
|
||||
user_agent = [
|
||||
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; InfoPath.3; rv:11.0) like Gecko",
|
||||
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)",
|
||||
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)",
|
||||
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1",
|
||||
"Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1",
|
||||
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11",
|
||||
"Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Avant Browser)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)",
|
||||
"Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
|
||||
"Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
|
||||
"Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
|
||||
"Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
|
||||
"MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
|
||||
"Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) Presto/2.8.149 Version/11.10",
|
||||
"Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13",
|
||||
"Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.337 Mobile Safari/534.1+",
|
||||
"Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0",
|
||||
"Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124",
|
||||
"Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; HTC; Titan)",
|
||||
"UCWEB7.0.2.37/28/999",
|
||||
"NOKIA5700/ UCWEB7.0.2.37/28/999",
|
||||
"Openwave/ UCWEB7.0.2.37/28/999",
|
||||
"Mozilla/4.0 (compatible; MSIE 6.0; ) Opera/UCWEB7.0.2.37/28/999",
|
||||
# iPhone 6:
|
||||
"Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25",
|
||||
]
|
||||
|
||||
|
||||
def get_user_agent():
|
||||
return {"User-Agent": random.choice(user_agent)}
|
||||
|
||||
|
||||
def get_user_agent_str():
|
||||
return random.choice(user_agent)
|
||||
@ -1,11 +1,42 @@
|
||||
import os
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
|
||||
class WithdrawManager:
|
||||
"""
|
||||
消息撤回
|
||||
"""
|
||||
|
||||
_data = {}
|
||||
|
||||
@classmethod
|
||||
def append(cls, message_id: str, second: int):
|
||||
"""添加一个撤回消息id和时间
|
||||
|
||||
参数:
|
||||
message_id: 撤回消息id
|
||||
time: 延迟时间
|
||||
"""
|
||||
cls._data[message_id] = second
|
||||
|
||||
@classmethod
|
||||
def remove(cls, message_id: str):
|
||||
"""删除一个数据
|
||||
|
||||
参数:
|
||||
message_id: 撤回消息id
|
||||
"""
|
||||
if message_id in cls._data:
|
||||
del cls._data[message_id]
|
||||
|
||||
|
||||
class ResourceDirManager:
|
||||
"""
|
||||
临时文件管理器
|
||||
@ -45,6 +76,69 @@ class ResourceDirManager:
|
||||
cls.__tree_append(path)
|
||||
|
||||
|
||||
class CountLimiter:
|
||||
"""
|
||||
次数检测工具,检测调用次数是否超过设定值
|
||||
"""
|
||||
|
||||
def __init__(self, max_count: int):
|
||||
self.count = defaultdict(int)
|
||||
self.max_count = max_count
|
||||
|
||||
def add(self, key: Any):
|
||||
self.count[key] += 1
|
||||
|
||||
def check(self, key: Any) -> bool:
|
||||
if self.count[key] >= self.max_count:
|
||||
self.count[key] = 0
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class UserBlockLimiter:
|
||||
"""
|
||||
检测用户是否正在调用命令
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.flag_data = defaultdict(bool)
|
||||
self.time = time.time()
|
||||
|
||||
def set_true(self, key: Any):
|
||||
self.time = time.time()
|
||||
self.flag_data[key] = True
|
||||
|
||||
def set_false(self, key: Any):
|
||||
self.flag_data[key] = False
|
||||
|
||||
def check(self, key: Any) -> bool:
|
||||
if time.time() - self.time > 30:
|
||||
self.set_false(key)
|
||||
return False
|
||||
return self.flag_data[key]
|
||||
|
||||
|
||||
class FreqLimiter:
|
||||
"""
|
||||
命令冷却,检测用户是否处于冷却状态
|
||||
"""
|
||||
|
||||
def __init__(self, default_cd_seconds: int):
|
||||
self.next_time = defaultdict(float)
|
||||
self.default_cd = default_cd_seconds
|
||||
|
||||
def check(self, key: Any) -> bool:
|
||||
return time.time() >= self.next_time[key]
|
||||
|
||||
def start_cd(self, key: Any, cd_time: int = 0):
|
||||
self.next_time[key] = time.time() + (
|
||||
cd_time if cd_time > 0 else self.default_cd
|
||||
)
|
||||
|
||||
def left_time(self, key: Any) -> float:
|
||||
return self.next_time[key] - time.time()
|
||||
|
||||
|
||||
async def get_user_avatar(uid: int | str) -> bytes | None:
|
||||
"""快捷获取用户头像
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user