├── .gitignore
├── LICENSE
├── README.md
├── docs_image
├── help.png
├── tt.jpg
├── tt1.png
├── tt2.png
├── tt3.png
└── tt4.png
├── nonebot_plugin_zxpm
├── __init__.py
├── commands
│ ├── __init__.py
│ ├── zxpm_add_group
│ │ ├── __init__.py
│ │ └── data_source.py
│ ├── zxpm_admin_watch
│ │ └── __init__.py
│ ├── zxpm_ban
│ │ ├── __init__.py
│ │ └── _data_source.py
│ ├── zxpm_bot_manage
│ │ ├── __init__.py
│ │ ├── bot_switch.py
│ │ ├── command.py
│ │ ├── full_function.py
│ │ └── plugin.py
│ ├── zxpm_help
│ │ ├── __init__.py
│ │ └── _data_source.py
│ ├── zxpm_hooks
│ │ ├── __init__.py
│ │ ├── _auth_checker.py
│ │ ├── zxpm_auth_hook.py
│ │ └── zxpm_ban_hook.py
│ ├── zxpm_init
│ │ ├── __init__.py
│ │ └── manager.py
│ ├── zxpm_plugin_switch
│ │ ├── __init__.py
│ │ ├── _data_source.py
│ │ └── command.py
│ ├── zxpm_set_admin
│ │ └── __init__.py
│ └── zxpm_super_group
│ │ └── __init__.py
├── config.py
├── db_connect
│ └── __init__.py
├── enum.py
├── exception.py
├── extra
│ ├── __init__.py
│ └── limit.py
├── log.py
├── models
│ ├── ban_console.py
│ ├── bot_console.py
│ ├── group_console.py
│ ├── level_user.py
│ ├── plugin_info.py
│ └── plugin_limit.py
├── rules.py
└── utils
│ ├── build_image.py
│ ├── image_template.py
│ └── utils.py
├── poetry.lock
└── pyproject.toml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
110 | .pdm.toml
111 | .pdm-python
112 | .pdm-build/
113 |
114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
115 | __pypackages__/
116 |
117 | # Celery stuff
118 | celerybeat-schedule
119 | celerybeat.pid
120 |
121 | # SageMath parsed files
122 | *.sage.py
123 |
124 | # Environments
125 | .env
126 | .venv
127 | env/
128 | venv/
129 | ENV/
130 | env.bak/
131 | venv.bak/
132 |
133 | # Spyder project settings
134 | .spyderproject
135 | .spyproject
136 |
137 | # Rope project settings
138 | .ropeproject
139 |
140 | # mkdocs documentation
141 | /site
142 |
143 | # mypy
144 | .mypy_cache/
145 | .dmypy.json
146 | dmypy.json
147 |
148 | # Pyre type checker
149 | .pyre/
150 |
151 | # pytype static type analyzer
152 | .pytype/
153 |
154 | # Cython debug symbols
155 | cython_debug/
156 |
157 | # PyCharm
158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
160 | # and can be added to the global gitignore or merged into this file. For a more nuclear
161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
162 | #.idea/
163 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | # nonebot-plugin-zxpm
14 |
15 | _✨ 基于 [NoneBot2](https://github.com/nonebot/nonebot2) 的一个 插件管理插件 ✨_
16 |
17 | 
18 | 
19 | 
20 | [](https://github.com/HibiKier/zhenxun_bot/blob/main/LICENSE)
21 |
22 |
23 |
24 | ## 📖 介绍
25 |
26 | [小真寻](https://github.com/HibiKier/zhenxun_bot) 的插件权限管理系统,提供了
27 |
28 | - **细致的插件开关(全局关闭可以使用群白名单忽略)**
29 | - **Ban 群组/用户(消息屏蔽)**
30 | - **插件 Cd,Count,Block 限制(配置文件)**
31 | - 群管监测(权限自动设置)
32 | - 用户权限设置(超级用户设置)
33 | - 一个简单的帮助查看
34 |
35 | 继承了真寻的优良传统,高贵的**超级用户**不受权限控制,除非插件额外限制
36 |
37 | > [!NOTE]
38 | >
39 | > 小真寻也很可爱呀,也会很喜欢你!
40 | >
41 | >
42 | >

43 | >

44 | >

45 | >
46 |
47 | ## 💿 安装
48 |
49 | ```python
50 | pip install nonebot-plugin-zxpm
51 | ```
52 |
53 | ```python
54 | nb plugin install nonebot-plugin-zxpm
55 | ```
56 |
57 | ## 🎁 使用
58 |
59 | > [!IMPORTANT]
60 | > ZXPM 对插件进行了分类
61 | > `NORMAL`: 普通插件,没有特定标记的情况下都为这个类型
62 | > `ADMIN`: 群组管理员插件
63 | > `SUPERUSER`: 超级用户插件
64 | > `SUPER_AND_ADMIN`: 超级用户用于与管理员插件
65 | > `DEPENDANT`: 依赖插件,一般为没有主动触发命令的插件,受权限控制
66 | > `HIDDEN`: 隐藏插件,一般为没有主动触发命令的插件,不受权限控制,如消息统计
67 | > `PARENT`: 父插件,仅仅标记
68 | >
69 | > ZXPM 权限管理严格
70 | > 普通用户无法查看`ADMIN`,`SUPERUSER`,`SUPER_AND_ADMIN`插件的帮助
71 | > 权限管理员用户无法查看`SUPERUSER`插件的帮助
72 |
73 | ## ⚙️ 配置
74 |
75 | | 配置 | 类型 | 默认值 | 说明 |
76 | | :---------------------- | :--: | :---------------------------: | ---------------------------------------------------------------- |
77 | | zxpm_data_path | str | data/zxpm | 数据存储路径 |
78 | | zxpm_db_url | str | (`zxpm_data_path`)/db/zxpm.db | 数据库地址 URL,默认为 sqlite,存储路径在`zxpm_data_path`下 |
79 | | zxpm_notice_info_cd | int | 300 | 群/用户权限检测等各种检测提示信息 cd,为 0 时或永久 ban 时不提醒 |
80 | | zxpm_ban_reply | str | 才不会给你发消息. | 用户被 ban 时回复消息,为空时不回复 |
81 | | zxpm_ban_level | int | 5 | 使用 ban 功能的对应权限 |
82 | | zxpm_switch_level | int | 1 | 使用开关功能的对应权限 |
83 | | zxpm_admin_default_auth | int | 5 | 群组管理员默认权限 |
84 | | zxpm_font | str | msyh.ttc | 作图时字体
85 | | zxpm_limit_superuser | bool | False | 是否限制超级用户 |
86 |
87 | ## 🎉 帮助
88 |
89 | | 指令 | 格式 | 权限等级 | 参数 | 作用 | 示例 |
90 | | :--: | :---------: | :------: | :--: | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
91 | | zxpm | zxpm [名称] | 0 | -s | 提供一个简单详情帮助图片 | `zxpm Ban`: 查看 Ban 的用户帮助
`zxpm Ban -s`: 查看 Ban 的超级用户帮助
`zxpm 40`: 查看 id 为 40 的插件帮助(id 通过给 bot 发送`插件列表`获取) |
92 |
93 | > [!NOTE]
94 | > 这里的指令只是写了个大概,功能开关指令比较复杂
95 | > 建议对 bot 发送 `zxpm ZXPM插件管理` 和 `zxpm ZXPM插件管理 -s` 来查看详细帮助
96 | > ZXPM 内部维护了一个插件数据库,收集所有含有`PluginMetaData`的插件
97 | > 对于其他插件,同样也可以使用`zxpm`来获取帮助信息
98 |
99 | ## 权限设置
100 |
101 | | 指令 | 格式 | 权限等级 | 参数 | 作用 | 示例 |
102 | | :------: | :-------------------: | :-------: | :--: | ------------------------ | ------------------ |
103 | | 添加权限 | 添加权限 [level] [at] | SUPERUSER | | 为用户提供插件管理等权限 | `添加权限 5 @user` |
104 | | 删除权限 | 删除权限 [at] | SUPERUSER | | 为用户提供插件管理等权限 | `删除权限 @user` |
105 |
106 | ## 白名单设置
107 |
108 | | 指令 | 格式 | 权限等级 | 参数 | 作用 | 示例 |
109 | | :------: | :-------------------: | :-------: | :--: | ------------------------ | ------------------ |
110 | | 添加群白名单 | 添加群白名单 ?[gid] | SUPERUSER | | 标记群为白名单中 | `添加群白名单`: 在群组中可以不加群号,默认当前群组
`添加群白名单 123123`: 指定群号 |
111 | | 删除群白名单 | 删除群白名单 ?[gid] | SUPERUSER | | 删除群为白名单中 | 同上 |
112 |
113 | ## Ban/unBan
114 |
115 | **群组管理员**
116 |
117 | | 指令 | 格式 | 权限等级 | 参数 | 作用 | 示例 |
118 | | :---: | :------------------------------: | :------: | :---: | --------------------------------------------------- | ------------------------------------------------------ |
119 | | ban | ban [At 用户] ?[-t [时长(分钟)]] | 5 | -t -g | 屏蔽用户或群组消息,权限等级低无法 Ban 等级高的用户 | `ban @用户`: 永久 ban
`ban @用户 -t 10`: ban 十分钟 |
120 | | unban | unban [At 用户] | 5 | | 解除蔽用户或群组消息 @user | `unban @用户`: 放出来 |
121 |
122 | **超级用户**
123 |
124 | ```
125 | ban [At用户/用户Id] ?[-t [时长]]
126 | unban --id [idx] : 通过id来进行unban操作
127 | ban列表: 获取所有Ban数据
128 |
129 | 群组ban列表: 获取群组Ban数据
130 | 用户ban列表: 获取用户Ban数据
131 |
132 | ban列表 -u [用户Id]: 查找指定用户ban数据
133 | ban列表 -g [群组Id]: 查找指定群组ban数据
134 | 示例:
135 | ban列表 -u 123456789 : 查找用户123456789的ban数据
136 | ban列表 -g 123456789 : 查找群组123456789的ban数据
137 |
138 | 私聊下:
139 | 示例:
140 | ban 123456789 : 永久拉黑用户123456789
141 | ban 123456789 -t 100 : 拉黑用户123456789 100分钟
142 |
143 | ban -g 999999 : 拉黑群组为999999的群组
144 | ban -g 999999 -t 100 : 拉黑群组为999999的群组 100分钟
145 |
146 | unban 123456789 : 从小黑屋中拉出来
147 | unban -g 999999 : 将群组9999999从小黑屋中拉出来
148 | ```
149 |
150 | ## 插件控制
151 |
152 | **群组管理员**
153 |
154 | ```
155 | 格式:
156 | 开启/关闭[功能名称] : 开关功能
157 | 开启/关闭所有插件 : 开启/关闭当前群组所有插件状态
158 | 醒来 : 结束休眠
159 | 休息吧 : 群组休眠, 不会再响应命令
160 |
161 | 示例:
162 | 开启签到 : 开启签到
163 | 关闭签到 : 关闭签到
164 | ```
165 |
166 | **超级用户**
167 |
168 | ```
169 | 插件列表
170 | 开启/关闭[功能名称] ?[-t ["private", "p", "group", "g"](关闭类型)] ?[-g 群组Id]
171 |
172 | 开启/关闭插件df[功能名称]: 开启/关闭指定插件进群默认状态
173 | = 开启插件echo -df
174 | = 关闭插件echo -df
175 | 开启/关闭所有插件df: 开启/关闭所有插件进群默认状态
176 | 开启/关闭所有插件:
177 | 私聊中: 开启/关闭所有插件全局状态
178 | 群组中: 开启/关闭当前群组所有插件状态
179 |
180 | 私聊下:
181 | 示例:
182 | 开启签到 : 全局开启签到
183 | 关闭签到 : 全局关闭签到
184 | 关闭签到 -t p : 全局私聊关闭签到
185 | 关闭签到 -g 12345678 : 关闭群组12345678的签到功能(普通管理员无法开启)
186 |
187 | bot插件列表: bot插件列表状态 : bot插件列表
188 | bot开启/关闭所有插件 : bot所有插件开关
189 | bot开启/关闭插件[插件名称] : bot插件开关
190 | bot休眠 : bot休眠,屏蔽所有消息
191 | bot醒来 : bot醒来
192 | ```
193 |
194 | **可修改配置文件**
195 |
196 | 在`(zxpm_data_path)/configs`路径下有以 下 3 个配置文件,且文件中已提供参数解释
197 | 默认路径: `data/zxpm/configs`
198 |
199 | - `plugin2block.yaml`: 插件阻塞配置
200 | 例如:
201 |
202 | ```
203 | sign_in:
204 | status: true
205 | check_type: ALL
206 | watch_type: USER
207 | result: "你签那么快干什么啦"
208 | ```
209 |
210 | - `plugin2cd.yaml`: 插件 CD 配置
211 | 例如:
212 |
213 | ```
214 | sign_in:
215 | status: true
216 | check_type: ALL
217 | watch_type: USER
218 | result: 告辞
219 | cd: 12
220 | ```
221 |
222 | - `plugin2count.yaml`: 插件每日次数配置
223 | 例如:
224 |
225 | ```
226 | help:
227 | status: false
228 | watch_type: GROUP
229 | result: 再看就揍死你
230 | max_count: 2
231 | ```
232 |
233 | ## ❤ 感谢
234 |
235 | - 可爱的小真寻 Bot [`zhenxun_bot`](https://github.com/HibiKier/zhenxun_bot): 我谢我自己,桀桀桀
236 |
--------------------------------------------------------------------------------
/docs_image/help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HibiKier/nonebot-plugin-zxpm/85bdba47cb30edc826876b6c6a3a864b7db54ccc/docs_image/help.png
--------------------------------------------------------------------------------
/docs_image/tt.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HibiKier/nonebot-plugin-zxpm/85bdba47cb30edc826876b6c6a3a864b7db54ccc/docs_image/tt.jpg
--------------------------------------------------------------------------------
/docs_image/tt1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HibiKier/nonebot-plugin-zxpm/85bdba47cb30edc826876b6c6a3a864b7db54ccc/docs_image/tt1.png
--------------------------------------------------------------------------------
/docs_image/tt2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HibiKier/nonebot-plugin-zxpm/85bdba47cb30edc826876b6c6a3a864b7db54ccc/docs_image/tt2.png
--------------------------------------------------------------------------------
/docs_image/tt3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HibiKier/nonebot-plugin-zxpm/85bdba47cb30edc826876b6c6a3a864b7db54ccc/docs_image/tt3.png
--------------------------------------------------------------------------------
/docs_image/tt4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HibiKier/nonebot-plugin-zxpm/85bdba47cb30edc826876b6c6a3a864b7db54ccc/docs_image/tt4.png
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/__init__.py:
--------------------------------------------------------------------------------
1 | from nonebot import require
2 | from nonebot.plugin import PluginMetadata, inherit_supported_adapters
3 |
4 | from .config import Config
5 | from .enum import PluginType
6 | from .extra import PluginExtraData
7 |
8 | require("nonebot_plugin_alconna")
9 | require("nonebot_plugin_session")
10 | require("nonebot_plugin_uninfo")
11 |
12 | from .commands import * # noqa: F403
13 |
14 | __plugin_meta__ = PluginMetadata(
15 | name="ZXPM插件管理",
16 | description="真寻的插件管理系统",
17 | usage="""
18 | 包含了插件功能开关,群组/用户ban,群管监测,设置权限功能
19 | 并提供一个简单的帮助接口,可以通过 zxpm [名称] 来获取帮助
20 | 可以通过 -s 参数来获取该功能超级用户帮助
21 | 例如:
22 | zxpm ban
23 | zxpm ban -s
24 | """,
25 | type="application",
26 | homepage="https://github.com/HibiKier/nonebot-plugin-zxpm",
27 | config=Config,
28 | supported_adapters=inherit_supported_adapters(
29 | "nonebot_plugin_alconna",
30 | "nonebot_plugin_session",
31 | "nonebot_plugin_uninfo",
32 | ),
33 | extra=PluginExtraData(plugin_type=PluginType.PARENT).to_dict(),
34 | )
35 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/__init__.py:
--------------------------------------------------------------------------------
1 | import contextlib
2 |
3 | import nonebot
4 | from nonebot.adapters import Bot
5 | from nonebot_plugin_uninfo import get_interface
6 |
7 | from ..log import logger
8 | from ..models.bot_console import BotConsole
9 | from ..models.group_console import GroupConsole
10 | from ..models.plugin_info import PluginInfo
11 | from .zxpm_ban import * # noqa: F403
12 | from .zxpm_help import * # noqa: F403
13 | from .zxpm_hooks import * # noqa: F403
14 | from .zxpm_init import * # noqa: F403
15 | from .zxpm_plugin_switch import * # noqa: F403
16 | from .zxpm_set_admin import * # noqa: F403
17 | from .zxpm_super_group import * # noqa: F403
18 |
19 | driver = nonebot.get_driver()
20 |
21 | with contextlib.suppress(ImportError):
22 | from nonebot.adapters.onebot.v11 import GroupIncreaseNoticeEvent # noqa: F401
23 |
24 | from .zxpm_add_group import * # noqa: F403
25 | from .zxpm_admin_watch import * # noqa: F403
26 |
27 |
28 | @driver.on_bot_connect
29 | async def _(bot: Bot):
30 | """更新bot群组信息
31 |
32 | 参数:
33 | bot: Bot
34 | """
35 | try:
36 | if interface := get_interface(bot):
37 | scens = await interface.get_scenes()
38 | group_list = [(s.id, s.name) for s in scens if s.is_group]
39 | db_group_list = await GroupConsole.all().values_list("group_id", flat=True)
40 | block_modules = await PluginInfo.filter(
41 | load_status=True, default_status=False
42 | ).values_list("module", flat=True)
43 | block_modules = [f"<{module}" for module in block_modules]
44 | create_list = []
45 | for gid, name in group_list:
46 | if gid not in db_group_list:
47 | group = GroupConsole(group_id=gid, group_name=name)
48 | if block_modules:
49 | group.block_plugin = ",".join(block_modules) + ","
50 | logger.debug(f"Bot: {bot.self_id} 添加创建群组Id: {group.group_id}")
51 | create_list.append(group)
52 | if create_list:
53 | await GroupConsole.bulk_create(create_list, 10)
54 | logger.debug(
55 | f"更新Bot: {bot.self_id} 共创建 {len(create_list)} 条群组数据..."
56 | )
57 | except Exception as e:
58 | logger.error(f"获取Bot: {bot.self_id} 群组发生错误...", e=e)
59 | logger.debug(f"Bot: {bot.self_id} 建立连接...")
60 | if not await BotConsole.exists(bot_id=bot.self_id):
61 | await BotConsole.create(bot_id=bot.self_id)
62 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_add_group/__init__.py:
--------------------------------------------------------------------------------
1 | from nonebot import on_notice
2 | from nonebot.adapters import Bot
3 | from nonebot.adapters.onebot.v11 import GroupIncreaseNoticeEvent
4 | from nonebot.adapters.onebot.v12 import GroupMemberIncreaseEvent
5 | from nonebot.plugin import PluginMetadata
6 |
7 | from ...enum import PluginType
8 | from ...extra import PluginExtraData
9 | from ...models.group_console import GroupConsole
10 | from ...rules import notice_rule
11 | from .data_source import GroupManager
12 |
13 | __plugin_meta__ = PluginMetadata(
14 | name="QQ群事件处理",
15 | description="群事件处理",
16 | usage="",
17 | extra=PluginExtraData(
18 | author="HibiKier",
19 | version="0.1",
20 | plugin_type=PluginType.HIDDEN,
21 | ).to_dict(),
22 | )
23 |
24 | group_increase_handle = on_notice(
25 | priority=1,
26 | block=False,
27 | rule=notice_rule([GroupIncreaseNoticeEvent, GroupMemberIncreaseEvent]),
28 | )
29 | """群员增加处理"""
30 |
31 |
32 | @group_increase_handle.handle()
33 | async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent):
34 | user_id = str(event.user_id)
35 | group_id = str(event.group_id)
36 | if user_id == bot.self_id:
37 | """新成员为bot本身"""
38 | group, _ = await GroupConsole.get_or_create(
39 | group_id=group_id, channel_id__isnull=True
40 | )
41 | await GroupManager.add_bot(bot, group_id, group)
42 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_add_group/data_source.py:
--------------------------------------------------------------------------------
1 | from nonebot.adapters import Bot
2 |
3 | from ...config import ZxpmConfig
4 | from ...log import logger
5 | from ...models.group_console import GroupConsole
6 | from ...models.level_user import LevelUser
7 | from ...models.plugin_info import PluginInfo
8 |
9 |
10 | class GroupManager:
11 | @classmethod
12 | async def __handle_add_group(
13 | cls, bot: Bot, group_id: str, group: GroupConsole | None
14 | ):
15 | """允许群组并设置群认证,默认群功能开关
16 |
17 | 参数:
18 | bot: Bot
19 | group_id: 群组id
20 | group: GroupConsole
21 | """
22 | block_plugin = ""
23 | if plugin_list := await PluginInfo.filter(default_status=False).all():
24 | for plugin in plugin_list:
25 | block_plugin += f"<{plugin.module},"
26 | group_info = await bot.get_group_info(group_id=group_id)
27 | if group:
28 | group.block_plugin = block_plugin
29 | await group.save(update_fields=["block_plugin"])
30 | else:
31 | await GroupConsole.create(
32 | group_id=group_info["group_id"],
33 | group_name=group_info["group_name"],
34 | max_member_count=group_info["max_member_count"],
35 | member_count=group_info["member_count"],
36 | block_plugin=block_plugin,
37 | )
38 |
39 | @classmethod
40 | async def __refresh_level(cls, bot: Bot, group_id: str):
41 | """刷新权限
42 |
43 | 参数:
44 | bot: Bot
45 | group_id: 群组id
46 | """
47 | admin_default_auth = ZxpmConfig.zxpm_admin_default_auth
48 | member_list = await bot.get_group_member_list(group_id=group_id)
49 | member_id_list = [str(user_info["user_id"]) for user_info in member_list]
50 | flag2u = await LevelUser.filter(
51 | user_id__in=member_id_list, group_id=group_id
52 | ).values_list("user_id", flat=True)
53 | # 即刻刷新权限
54 | for user_info in member_list:
55 | user_id = user_info["user_id"]
56 | role = user_info["role"]
57 | if user_id in bot.config.superusers:
58 | await LevelUser.set_level(user_id, user_info["group_id"], 9)
59 | logger.debug(
60 | "添加超级用户权限: 9",
61 | "入群检测",
62 | session=user_id,
63 | group_id=user_info["group_id"],
64 | )
65 | elif (
66 | admin_default_auth is not None
67 | and role in ["owner", "admin"]
68 | and user_id not in flag2u
69 | ):
70 | await LevelUser.set_level(
71 | user_id,
72 | user_info["group_id"],
73 | admin_default_auth,
74 | )
75 | logger.debug(
76 | f"添加默认群管理员权限: {admin_default_auth}",
77 | "入群检测",
78 | session=user_id,
79 | group_id=user_info["group_id"],
80 | )
81 |
82 | @classmethod
83 | async def add_bot(cls, bot: Bot, group_id: str, group: GroupConsole | None):
84 | """拉入bot
85 |
86 | 参数:
87 | bot: Bot
88 | operator_id: 操作者id
89 | group_id: 群组id
90 | group: GroupConsole
91 | """
92 | await cls.__handle_add_group(bot, group_id, group)
93 | """刷新群管理员权限"""
94 | await cls.__refresh_level(bot, group_id)
95 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_admin_watch/__init__.py:
--------------------------------------------------------------------------------
1 | from nonebot import on_notice
2 | from nonebot.adapters.onebot.v11 import GroupAdminNoticeEvent
3 | from nonebot.plugin import PluginMetadata
4 |
5 | from ...config import ZxpmConfig
6 | from ...enum import PluginType
7 | from ...extra import PluginExtraData
8 | from ...log import logger
9 | from ...models.level_user import LevelUser
10 | from ...rules import notice_rule
11 |
12 | __plugin_meta__ = PluginMetadata(
13 | name="群管理员变动监测",
14 | description="""检测群管理员变动, 添加与删除管理员默认权限,
15 | 当配置项 ADMIN_DEFAULT_AUTH 为空时, 不会添加管理员权限""",
16 | usage="",
17 | extra=PluginExtraData(
18 | author="HibiKier",
19 | version="0.1",
20 | plugin_type=PluginType.HIDDEN,
21 | ).to_dict(),
22 | )
23 |
24 |
25 | admin_notice = on_notice(priority=5, rule=notice_rule(GroupAdminNoticeEvent))
26 |
27 |
28 | @admin_notice.handle()
29 | async def _(event: GroupAdminNoticeEvent):
30 | if event.sub_type == "set":
31 | admin_default_auth = ZxpmConfig.zxpm_admin_default_auth
32 | if admin_default_auth is not None:
33 | await LevelUser.set_level(
34 | str(event.user_id),
35 | str(event.group_id),
36 | admin_default_auth,
37 | )
38 | logger.info(
39 | f"成为管理员,添加权限: {admin_default_auth}",
40 | "群管理员变动监测",
41 | session=event.user_id,
42 | group_id=event.group_id,
43 | )
44 | else:
45 | logger.warning(
46 | "配置项 MODULE: [admin_bot_manage] |"
47 | " KEY: [ADMIN_DEFAULT_AUTH] 为空"
48 | )
49 | elif event.sub_type == "unset":
50 | await LevelUser.delete_level(str(event.user_id), str(event.group_id))
51 | logger.info(
52 | "撤销群管理员, 取消权限等级",
53 | "群管理员变动监测",
54 | session=event.user_id,
55 | group_id=event.group_id,
56 | )
57 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_ban/__init__.py:
--------------------------------------------------------------------------------
1 | from arclet.alconna import Args
2 | from nonebot.adapters import Bot
3 | from nonebot.permission import SUPERUSER
4 | from nonebot.plugin import PluginMetadata
5 | from nonebot_plugin_alconna import (
6 | Alconna,
7 | Arparma,
8 | At,
9 | Match,
10 | Option,
11 | on_alconna,
12 | store_true,
13 | )
14 | from nonebot_plugin_session import EventSession
15 |
16 | from ...config import ZxpmConfig
17 | from ...enum import PluginType
18 | from ...extra import PluginExtraData
19 | from ...log import logger
20 | from ...rules import admin_check
21 | from ...utils.utils import MessageUtils
22 | from ._data_source import BanManage
23 |
24 | __plugin_meta__ = PluginMetadata(
25 | name="Ban",
26 | description="你被逮捕了!丢进小黑屋!封禁用户以及群组,屏蔽消息",
27 | usage="""
28 | 普通管理员
29 | 格式:
30 | ban [At用户] ?[-t [时长(分钟)]]
31 |
32 | 示例:
33 | ban @用户 : 永久拉黑用户
34 | ban @用户 -t 100 : 拉黑用户100分钟
35 | unban @用户 : 从小黑屋中拉出来
36 | """.strip(),
37 | extra=PluginExtraData(
38 | author="HibiKier",
39 | version="0.1",
40 | plugin_type=PluginType.SUPER_AND_ADMIN,
41 | admin_level=ZxpmConfig.zxpm_ban_level,
42 | superuser_help="""
43 | 超级管理员额外命令
44 | 格式:
45 | ban [At用户/用户Id] ?[-t [时长]]
46 | unban --id [idx] : 通过id来进行unban操作
47 | ban列表: 获取所有Ban数据
48 |
49 | 群组ban列表: 获取群组Ban数据
50 | 用户ban列表: 获取用户Ban数据
51 |
52 | ban列表 -u [用户Id]: 查找指定用户ban数据
53 | ban列表 -g [群组Id]: 查找指定群组ban数据
54 | 示例:
55 | ban列表 -u 123456789 : 查找用户123456789的ban数据
56 | ban列表 -g 123456789 : 查找群组123456789的ban数据
57 |
58 | 私聊下:
59 | 示例:
60 | ban 123456789 : 永久拉黑用户123456789
61 | ban 123456789 -t 100 : 拉黑用户123456789 100分钟
62 |
63 | ban -g 999999 : 拉黑群组为999999的群组
64 | ban -g 999999 -t 100 : 拉黑群组为999999的群组 100分钟
65 |
66 | unban 123456789 : 从小黑屋中拉出来
67 | unban -g 999999 : 将群组9999999从小黑屋中拉出来
68 | """,
69 | ).to_dict(),
70 | )
71 |
72 |
73 | _ban_matcher = on_alconna(
74 | Alconna(
75 | "ban",
76 | Args["user?", [str, At]],
77 | Option("-g|--group", Args["group_id", str]),
78 | Option("-t|--time", Args["duration", int]),
79 | ),
80 | rule=admin_check(ZxpmConfig.zxpm_ban_level),
81 | priority=5,
82 | block=True,
83 | )
84 |
85 | _unban_matcher = on_alconna(
86 | Alconna(
87 | "unban",
88 | Args["user?", [str, At]],
89 | Option("-g|--group", Args["group_id", str]),
90 | Option("--id", Args["idx", int]),
91 | ),
92 | rule=admin_check(ZxpmConfig.zxpm_ban_level),
93 | priority=5,
94 | block=True,
95 | )
96 |
97 | _status_matcher = on_alconna(
98 | Alconna(
99 | "ban列表",
100 | Option("-u", Args["user_id", str], help_text="查找用户"),
101 | Option("-g", Args["group_id", str], help_text="查找群组"),
102 | Option("--user", action=store_true, help_text="过滤用户"),
103 | Option("--group", action=store_true, help_text="过滤群组"),
104 | ),
105 | permission=SUPERUSER,
106 | priority=1,
107 | block=True,
108 | )
109 |
110 | _status_matcher.shortcut(
111 | "用户ban列表",
112 | command="ban列表",
113 | arguments=["--user"],
114 | prefix=True,
115 | )
116 |
117 | _status_matcher.shortcut(
118 | "群组ban列表",
119 | command="ban列表",
120 | arguments=["--group"],
121 | prefix=True,
122 | )
123 |
124 |
125 | @_status_matcher.handle()
126 | async def _(
127 | arparma: Arparma,
128 | user_id: Match[str],
129 | group_id: Match[str],
130 | ):
131 | filter_type = None
132 | if arparma.find("user"):
133 | filter_type = "user"
134 | if arparma.find("group"):
135 | filter_type = "group"
136 | _user_id = user_id.result if user_id.available else None
137 | _group_id = group_id.result if group_id.available else None
138 | if image := await BanManage.build_ban_image(filter_type, _user_id, _group_id):
139 | await MessageUtils.build_message(image).finish(reply_to=True)
140 | else:
141 | await MessageUtils.build_message("数据为空捏...").finish(reply_to=True)
142 |
143 |
144 | @_ban_matcher.handle()
145 | async def _(
146 | bot: Bot,
147 | session: EventSession,
148 | arparma: Arparma,
149 | user: Match[str | At],
150 | duration: Match[int],
151 | group_id: Match[str],
152 | ):
153 | user_id = ""
154 | if not session.id1:
155 | await MessageUtils.build_message("用户id为空...").finish(reply_to=True)
156 | if user.available:
157 | if isinstance(user.result, At):
158 | user_id = user.result.target
159 | else:
160 | if session.id1 not in bot.config.superusers:
161 | await MessageUtils.build_message("权限不足捏...").finish(reply_to=True)
162 | user_id = user.result
163 | _duration = duration.result * 60 if duration.available else -1
164 | _duration_text = f"{duration.result} 分钟" if duration.available else " 到世界湮灭"
165 | if (gid := session.id3 or session.id2) and not group_id.available:
166 | if (
167 | not user_id
168 | or user_id == bot.self_id
169 | and session.id1 not in bot.config.superusers
170 | ):
171 | _duration = 0.5
172 | await MessageUtils.build_message("倒反天罡,小小管理速速退下!").send()
173 | await BanManage.ban(session.id1, gid, 30, session, True)
174 | _duration_text = "半 分钟"
175 | logger.info("尝试ban Bot 反被拿下", arparma.header_result, session=session)
176 | await MessageUtils.build_message(
177 | [
178 | "对 ",
179 | At(flag="user", target=session.id1),
180 | " 狠狠惩戒了一番,一脚踢进了小黑屋!"
181 | f" 在里面乖乖呆 {_duration_text}吧!",
182 | ]
183 | ).finish(reply_to=True)
184 | await BanManage.ban(
185 | user_id, gid, _duration, session, session.id1 in bot.config.superusers
186 | )
187 | logger.info(
188 | "管理员Ban",
189 | arparma.header_result,
190 | session=session,
191 | target=f"{gid}:{user_id}",
192 | )
193 | await MessageUtils.build_message(
194 | [
195 | "对 ",
196 | (
197 | At(flag="user", target=user_id)
198 | if isinstance(user.result, At)
199 | else user_id
200 | ), # type: ignore
201 | " 狠狠惩戒了一番,一脚踢进了小黑屋!",
202 | f" 在里面乖乖呆 {_duration_text} 吧!",
203 | ]
204 | ).finish(reply_to=True)
205 | elif session.id1 in bot.config.superusers:
206 | _group_id = group_id.result if group_id.available else None
207 | await BanManage.ban(user_id, _group_id, _duration, session, True)
208 | logger.info(
209 | "超级用户Ban",
210 | arparma.header_result,
211 | session=session,
212 | target=f"{_group_id}:{user_id}",
213 | )
214 | at_msg = user_id or f"群组:{_group_id}"
215 | await MessageUtils.build_message(
216 | f"对 {at_msg} 狠狠惩戒了一番,一脚踢进了小黑屋!"
217 | ).finish(reply_to=True)
218 |
219 |
220 | @_unban_matcher.handle()
221 | async def _(
222 | bot: Bot,
223 | session: EventSession,
224 | arparma: Arparma,
225 | user: Match[str | At],
226 | group_id: Match[str],
227 | idx: Match[int],
228 | ):
229 | user_id = ""
230 | _idx = idx.result if idx.available else None
231 | if user.available:
232 | if isinstance(user.result, At):
233 | user_id = user.result.target
234 | else:
235 | if session.id1 not in bot.config.superusers:
236 | await MessageUtils.build_message("权限不足捏...").finish(reply_to=True)
237 | user_id = user.result
238 | if gid := session.id3 or session.id2:
239 | if group_id.available:
240 | gid = group_id.result
241 | is_unban, result = await BanManage.unban(
242 | user_id, gid, session, _idx, session.id1 in bot.config.superusers
243 | )
244 | if not is_unban:
245 | await MessageUtils.build_message(result).finish(reply_to=True)
246 | logger.info(
247 | "管理员UnBan",
248 | arparma.header_result,
249 | session=session,
250 | target=f"{gid}:{result}",
251 | )
252 | await MessageUtils.build_message(
253 | [
254 | "将 ",
255 | (
256 | At(flag="user", target=user_id)
257 | if isinstance(user.result, At)
258 | else result
259 | ), # type: ignore
260 | " 从黑屋中拉了出来并急救了一下!",
261 | ]
262 | ).finish(reply_to=True)
263 | elif session.id1 in bot.config.superusers:
264 | _group_id = group_id.result if group_id.available else None
265 | is_unban, result = await BanManage.unban(
266 | user_id, _group_id, session, _idx, True
267 | )
268 | if not is_unban:
269 | await MessageUtils.build_message(result).finish(reply_to=True)
270 | logger.info(
271 | "超级用户UnBan",
272 | arparma.header_result,
273 | session=session,
274 | target=f"{_group_id}:{user_id}",
275 | )
276 | at_msg = user_id or f"群组:{result}"
277 | await MessageUtils.build_message(
278 | f"对 {at_msg} 从黑屋中拉了出来并急救了一下!"
279 | ).finish(reply_to=True)
280 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_ban/_data_source.py:
--------------------------------------------------------------------------------
1 | import time
2 | from typing import Literal
3 |
4 | from nonebot_plugin_session import EventSession
5 |
6 | from ...models.ban_console import BanConsole
7 | from ...models.level_user import LevelUser
8 | from ...utils.image_template import ImageTemplate
9 |
10 |
11 | class BanManage:
12 | @classmethod
13 | async def build_ban_image(
14 | cls,
15 | filter_type: Literal["group", "user"] | None,
16 | user_id: str | None = None,
17 | group_id: str | None = None,
18 | ) -> bytes:
19 | """构造Ban列表图片
20 |
21 | 参数:
22 | filter_type: 过滤类型
23 | user_id: 用户id
24 | group_id: 群组id
25 |
26 | 返回:
27 | bytes: Ban列表图片
28 | """
29 | data_list = None
30 | query = BanConsole
31 | if user_id:
32 | query = query.filter(user_id=user_id)
33 | elif group_id:
34 | query = query.filter(group_id=group_id)
35 | elif filter_type == "user":
36 | query = query.filter(group_id__isnull=True)
37 | elif filter_type == "group":
38 | query = query.filter(user_id__isnull=True)
39 | data_list = await query.all()
40 | if not data_list:
41 | return None
42 | column_name = [
43 | "ID",
44 | "用户ID",
45 | "群组ID",
46 | "BAN LEVEL",
47 | "剩余时长(分钟)",
48 | "操作员ID",
49 | ]
50 | row_data = []
51 | for data in data_list:
52 | duration = int((data.ban_time + data.duration - time.time()) / 60)
53 | if data.duration < 0:
54 | duration = "∞"
55 | row_data.append(
56 | [
57 | data.id,
58 | data.user_id,
59 | data.group_id,
60 | data.ban_level,
61 | duration,
62 | data.operator,
63 | ]
64 | )
65 | return (
66 | await ImageTemplate.table_page(
67 | "Ban / UnBan 列表", "在黑屋中狠狠调教!", column_name, row_data
68 | )
69 | ).pic2bytes()
70 |
71 | @classmethod
72 | async def is_ban(cls, user_id: str, group_id: str | None):
73 | """判断用户是否被ban
74 |
75 | 参数:
76 | user_id: 用户id
77 |
78 | 返回:
79 | bool: 是否被ban
80 | """
81 | return await BanConsole.is_ban(user_id, group_id)
82 |
83 | @classmethod
84 | async def unban(
85 | cls,
86 | user_id: str | None,
87 | group_id: str | None,
88 | session: EventSession,
89 | idx: int | None = None,
90 | is_superuser: bool = False,
91 | ) -> tuple[bool, str]:
92 | """unban目标用户
93 |
94 | 参数:
95 | user_id: 用户id
96 | group_id: 群组id
97 | session: Session
98 | idx: 指定id
99 | is_superuser: 是否为超级用户操作
100 |
101 | 返回:
102 | tuple[bool, str]: 是否unban成功, 群组/用户id或提示
103 | """
104 | user_level = 9999
105 | if not is_superuser and user_id and session.id1:
106 | user_level = await LevelUser.get_user_level(session.id1, group_id)
107 | if idx:
108 | ban_data = await BanConsole.get_or_none(id=idx)
109 | if not ban_data:
110 | return False, "该用户/群组不在黑名单中不足捏..."
111 | if ban_data.ban_level > user_level:
112 | return False, "unBan权限等级不足捏..."
113 | await ban_data.delete()
114 | return True, str(ban_data.user_id or ban_data.group_id)
115 | elif await BanConsole.check_ban_level(user_id, group_id, user_level):
116 | await BanConsole.unban(user_id, group_id)
117 | return True, str(group_id)
118 | return False, "该用户/群组不在黑名单中不足捏..."
119 |
120 | @classmethod
121 | async def ban(
122 | cls,
123 | user_id: str | None,
124 | group_id: str | None,
125 | duration: int,
126 | session: EventSession,
127 | is_superuser: bool,
128 | ):
129 | """ban掉目标用户
130 |
131 | 参数:
132 | user_id: 用户id
133 | group_id: 群组id
134 | duration: 时长,秒
135 | session: Session
136 | is_superuser: 是否为超级用户操作
137 | """
138 | level = 9999
139 | if not is_superuser and user_id and session.id1:
140 | level = await LevelUser.get_user_level(session.id1, group_id)
141 | await BanConsole.ban(user_id, group_id, level, duration, session.id1)
142 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_bot_manage/__init__.py:
--------------------------------------------------------------------------------
1 | import nonebot
2 | from nonebot.adapters import Bot
3 | from nonebot.plugin import PluginMetadata
4 | from zhenxun_utils.common_utils import CommonUtils
5 | from zhenxun_utils.enum import PluginType
6 | from zhenxun_utils.platform import PlatformUtils
7 |
8 | from ...models.bot_console import BotConsole
9 | from ...models.plugin_info import PluginInfo
10 | from .bot_switch import * # noqa: F403
11 | from .plugin import * # noqa: F403
12 |
13 | driver = nonebot.get_driver()
14 |
15 | __plugin_meta__ = PluginMetadata(
16 | name="Bot管理",
17 | description="指定bot对象的功能/被动开关和状态",
18 | usage="""
19 | 指令:
20 | bot被动状态 : bot的被动技能状态
21 | bot开启/关闭被动[被动名称] : 被动技能开关
22 | bot开启/关闭所有被动 : 所有被动技能开关
23 | bot插件列表: bot插件列表状态 : bot插件列表
24 | bot开启/关闭所有插件 : 所有插件开关
25 | bot开启/关闭插件[插件名称] : 插件开关
26 | bot休眠 : bot休眠,屏蔽所有消息
27 | bot醒来 : bot醒来
28 | """.strip(),
29 | )
30 |
31 | from zhenxun_utils.log import logger
32 |
33 |
34 | @driver.on_bot_connect
35 | async def init_bot_console(bot: Bot):
36 | """初始化Bot管理
37 |
38 | 参数:
39 | bot: Bot
40 | """
41 |
42 | async def _filter_blocked_items(
43 | items_list: list[str], block_list: list[str]
44 | ) -> list[str]:
45 | """过滤被block的项目
46 |
47 | 参数:
48 | items_list: 需要过滤的项目列表
49 | block_list: block列表
50 |
51 | 返回:
52 | list: 过滤后且经过格式化的项目列表
53 | """
54 | return [item for item in items_list if item not in block_list]
55 |
56 | plugin_list = [
57 | plugin.module
58 | for plugin in await PluginInfo.get_plugins(
59 | plugin_type__in=[PluginType.NORMAL, PluginType.DEPENDANT, PluginType.ADMIN]
60 | )
61 | ]
62 | platform = PlatformUtils.get_platform(bot)
63 | bot_data, created = await BotConsole.get_or_create(
64 | bot_id=bot.self_id, platform=platform
65 | )
66 |
67 | if not created:
68 | plugin_list = await _filter_blocked_items(
69 | plugin_list, await bot_data.get_plugins(bot.self_id, False)
70 | )
71 |
72 | bot_data.available_plugins = CommonUtils.convert_module_format(plugin_list)
73 | await bot_data.save(update_fields=["available_plugins"])
74 | logger.info("初始化Bot管理完成...")
75 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_bot_manage/bot_switch.py:
--------------------------------------------------------------------------------
1 | from nonebot_plugin_alconna import AlconnaMatch, Match
2 | from nonebot_plugin_uninfo import Uninfo
3 | from zhenxun_utils.log import logger
4 | from zhenxun_utils.message import MessageUtils
5 |
6 | from ...models.bot_console import BotConsole
7 | from .command import bot_manage
8 |
9 |
10 | @bot_manage.assign("bot_switch.enable")
11 | async def enable_bot_switch(
12 | session: Uninfo,
13 | bot_id: Match[str] = AlconnaMatch("bot_id"),
14 | ):
15 | if not bot_id.available:
16 | await MessageUtils.build_message("bot_id 不能为空").finish()
17 |
18 | else:
19 | logger.info(
20 | f"开启 {bot_id.result} ",
21 | "bot_manage.bot_switch.enable",
22 | session=session,
23 | )
24 | try:
25 | await BotConsole.set_bot_status(True, bot_id.result)
26 | except ValueError:
27 | await MessageUtils.build_message(f"bot_id {bot_id.result} 不存在").finish()
28 |
29 | await MessageUtils.build_message(f"已开启 {bot_id.result} ").finish()
30 |
31 |
32 | @bot_manage.assign("bot_switch.disable")
33 | async def diasble_bot_switch(
34 | session: Uninfo,
35 | bot_id: Match[str] = AlconnaMatch("bot_id"),
36 | ):
37 | if not bot_id.available:
38 | await MessageUtils.build_message("bot_id 不能为空").finish()
39 |
40 | else:
41 | logger.info(
42 | f"禁用 {bot_id.result} ",
43 | "bot_manage.bot_switch.disable",
44 | session=session,
45 | )
46 | try:
47 | await BotConsole.set_bot_status(False, bot_id.result)
48 | except ValueError:
49 | await MessageUtils.build_message(f"bot_id {bot_id.result} 不存在").finish()
50 |
51 | await MessageUtils.build_message(f"已禁用 {bot_id.result} ").finish()
52 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_bot_manage/command.py:
--------------------------------------------------------------------------------
1 | from arclet.alconna import Alconna, Args, Option, Subcommand
2 | from arclet.alconna.action import store_false
3 | from nonebot.permission import SUPERUSER
4 | from nonebot_plugin_alconna import on_alconna
5 |
6 | bot_manage = on_alconna(
7 | Alconna(
8 | "bot_manage",
9 | Subcommand(
10 | "task",
11 | Option(
12 | "list",
13 | action=store_false,
14 | help_text="查看 bot_id 下的所有可用被动",
15 | ),
16 | Option("-b|--bot", Args["bot_id", str], help_text="指定 bot_id"),
17 | Subcommand(
18 | "enable",
19 | Args["feature_name?", str],
20 | ),
21 | Subcommand(
22 | "disable",
23 | Args["feature_name?", str],
24 | ),
25 | ),
26 | Subcommand(
27 | "plugin",
28 | Option(
29 | "list",
30 | action=store_false,
31 | help_text="查看 bot_id 下的所有可用插件",
32 | ),
33 | Option("-b|--bot", Args["bot_id", str], help_text="指定 bot_id"),
34 | Subcommand(
35 | "enable",
36 | Args["plugin_name?", str],
37 | ),
38 | Subcommand(
39 | "disable",
40 | Args["plugin_name?", str],
41 | ),
42 | ),
43 | Subcommand(
44 | "full_function",
45 | Subcommand(
46 | "enable",
47 | Args["bot_id?", str],
48 | ),
49 | Subcommand(
50 | "disable",
51 | Args["bot_id?", str],
52 | ),
53 | ),
54 | Subcommand(
55 | "bot_switch",
56 | Subcommand(
57 | "enable",
58 | Args["bot_id?", str],
59 | ),
60 | Subcommand(
61 | "disable",
62 | Args["bot_id?", str],
63 | ),
64 | ),
65 | ),
66 | permission=SUPERUSER,
67 | priority=5,
68 | block=True,
69 | )
70 |
71 | bot_manage.shortcut(
72 | r"bot被动状态",
73 | command="bot_manage",
74 | arguments=["task", "list"],
75 | prefix=True,
76 | )
77 |
78 | bot_manage.shortcut(
79 | r"bot开启被动\s*(?P.+)",
80 | command="bot_manage",
81 | arguments=["task", "enable", "{name}"],
82 | prefix=True,
83 | )
84 |
85 | bot_manage.shortcut(
86 | r"bot关闭被动\s*(?P.+)",
87 | command="bot_manage",
88 | arguments=["task", "disable", "{name}"],
89 | prefix=True,
90 | )
91 |
92 | bot_manage.shortcut(
93 | r"bot开启(全部|所有)被动",
94 | command="bot_manage",
95 | arguments=["task", "enable"],
96 | prefix=True,
97 | )
98 |
99 | bot_manage.shortcut(
100 | r"bot关闭(全部|所有)被动",
101 | command="bot_manage",
102 | arguments=["task", "disable"],
103 | prefix=True,
104 | )
105 |
106 | bot_manage.shortcut(
107 | r"bot插件列表",
108 | command="bot_manage",
109 | arguments=["plugin", "list"],
110 | prefix=True,
111 | )
112 |
113 | bot_manage.shortcut(
114 | r"bot开启(全部|所有)插件",
115 | command="bot_manage",
116 | arguments=["plugin", "enable"],
117 | prefix=True,
118 | )
119 |
120 | bot_manage.shortcut(
121 | r"bot关闭(全部|所有)插件",
122 | command="bot_manage",
123 | arguments=["plugin", "disable"],
124 | prefix=True,
125 | )
126 |
127 | bot_manage.shortcut(
128 | r"bot开启\s*(?P.+)",
129 | command="bot_manage",
130 | arguments=["plugin", "enable", "{name}"],
131 | prefix=True,
132 | )
133 |
134 | bot_manage.shortcut(
135 | r"bot关闭\s*(?P.+)",
136 | command="bot_manage",
137 | arguments=["plugin", "disable", "{name}"],
138 | prefix=True,
139 | )
140 |
141 | bot_manage.shortcut(
142 | r"bot休眠\s*(?P.+)?",
143 | command="bot_manage",
144 | arguments=["bot_switch", "disable", "{bot_id}"],
145 | prefix=True,
146 | )
147 |
148 | bot_manage.shortcut(
149 | r"bot醒来\s*(?P.+)?",
150 | command="bot_manage",
151 | arguments=["bot_switch", "enable", "{bot_id}"],
152 | prefix=True,
153 | )
154 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_bot_manage/full_function.py:
--------------------------------------------------------------------------------
1 | from nonebot_plugin_alconna import AlconnaMatch, Match
2 | from nonebot_plugin_uninfo import Uninfo
3 | from zhenxun_utils.log import logger
4 | from zhenxun_utils.message import MessageUtils
5 |
6 | from ...models.bot_console import BotConsole
7 | from .command import bot_manage
8 |
9 |
10 | @bot_manage.assign("full_function.enable")
11 | async def enable_full_function(
12 | session: Uninfo,
13 | bot_id: Match[str] = AlconnaMatch("bot_id"),
14 | ):
15 | if not bot_id.available:
16 | await MessageUtils.build_message("bot_id 不能为空").finish()
17 |
18 | else:
19 | logger.info(
20 | f"开启 {bot_id.result} 的所有可用插件及被动",
21 | "bot_manage.full_function.enable",
22 | session=session,
23 | )
24 | await BotConsole.enable_all(bot_id.result, "tasks")
25 | await BotConsole.enable_all(bot_id.result, "plugins")
26 |
27 | await MessageUtils.build_message(
28 | f"已开启 {bot_id.result} 的所有插件及被动"
29 | ).finish()
30 |
31 |
32 | @bot_manage.assign("full_function.disable")
33 | async def diasble_full_function(
34 | session: Uninfo,
35 | bot_id: Match[str] = AlconnaMatch("bot_id"),
36 | ):
37 | if not bot_id.available:
38 | await MessageUtils.build_message("bot_id 不能为空").finish()
39 |
40 | else:
41 | logger.info(
42 | f"禁用 {bot_id.result} 的所有可用插件及被动",
43 | "bot_manage.full_function.disable",
44 | session=session,
45 | )
46 | await BotConsole.disable_all(bot_id.result, "tasks")
47 | await BotConsole.disable_all(bot_id.result, "plugins")
48 |
49 | await MessageUtils.build_message(
50 | f"已禁用 {bot_id.result} 的所有插件及被动"
51 | ).finish()
52 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_bot_manage/plugin.py:
--------------------------------------------------------------------------------
1 | from nonebot_plugin_alconna import AlconnaMatch, Match
2 | from nonebot_plugin_uninfo import Uninfo
3 | from zhenxun_utils._build_image import BuildImage
4 | from zhenxun_utils._image_template import ImageTemplate, RowStyle
5 | from zhenxun_utils.enum import PluginType
6 | from zhenxun_utils.log import logger
7 | from zhenxun_utils.message import MessageUtils
8 |
9 | from ...models.bot_console import BotConsole
10 | from ...models.plugin_info import PluginInfo
11 | from .command import bot_manage
12 |
13 |
14 | def task_row_style(column: str, text: str) -> RowStyle:
15 | """被动技能文本风格
16 |
17 | 参数:
18 | column: 表头
19 | text: 文本内容
20 |
21 | 返回:
22 | RowStyle: RowStyle
23 | """
24 | style = RowStyle()
25 | if column in {"全局状态"}:
26 | style.font_color = "#67C23A" if text == "开启" else "#F56C6C"
27 | return style
28 |
29 |
30 | @bot_manage.assign("plugin.list")
31 | async def bot_plugin(session: Uninfo, bot_id: Match[str] = AlconnaMatch("bot_id")):
32 | logger.info("获取全部 bot 的所有可用插件", "bot_manage.plugin", session=session)
33 | column_name = [
34 | "ID",
35 | "模块",
36 | "名称",
37 | "全局状态",
38 | "禁用类型",
39 | "加载状态",
40 | "菜单分类",
41 | "作者",
42 | "版本",
43 | "金币花费",
44 | ]
45 | if bot_id.available:
46 | data_dict = {
47 | bot_id.result: await BotConsole.get_plugins(
48 | bot_id=bot_id.result, status=False
49 | )
50 | }
51 | else:
52 | data_dict = await BotConsole.get_plugins(status=False)
53 | db_plugin_list = await PluginInfo.filter(
54 | load_status=True, plugin_type__not=PluginType.HIDDEN
55 | ).all()
56 | img_list = []
57 | for __bot_id, tk in data_dict.items():
58 | column_data = [
59 | [
60 | plugin.id,
61 | plugin.module,
62 | plugin.name,
63 | "开启" if plugin.module not in tk else "关闭",
64 | plugin.block_type,
65 | "SUCCESS" if plugin.load_status else "ERROR",
66 | plugin.menu_type,
67 | plugin.author,
68 | plugin.version,
69 | plugin.cost_gold,
70 | ]
71 | for plugin in db_plugin_list
72 | ]
73 | img = await ImageTemplate.table_page(
74 | f"{__bot_id}插件列表",
75 | None,
76 | column_name,
77 | column_data,
78 | text_style=task_row_style,
79 | )
80 | img_list.append(img)
81 | result = await BuildImage.auto_paste(img_list, 3)
82 | await MessageUtils.build_message(result).finish()
83 |
84 |
85 | @bot_manage.assign("plugin.enable")
86 | async def enable_plugin(
87 | session: Uninfo,
88 | plugin_name: Match[str] = AlconnaMatch("plugin_name"),
89 | bot_id: Match[str] = AlconnaMatch("bot_id"),
90 | ):
91 | if plugin_name.available:
92 | plugin: PluginInfo | None = await PluginInfo.get_plugin(name=plugin_name.result)
93 | if not plugin:
94 | await MessageUtils.build_message("未找到该插件...").finish()
95 | if bot_id.available:
96 | logger.info(
97 | f"开启 {bot_id.result} 的插件 {plugin_name.result}",
98 | "bot_manage.plugin.disable",
99 | session=session,
100 | )
101 | await BotConsole.enable_plugin(bot_id.result, plugin.module)
102 | await MessageUtils.build_message(
103 | f"已开启 {bot_id.result} 的插件 {plugin_name.result}"
104 | ).finish()
105 | else:
106 | logger.info(
107 | f"开启全部 bot 的插件: {plugin_name.result}",
108 | "bot_manage.plugin.disable",
109 | session=session,
110 | )
111 | await BotConsole.enable_plugin(None, plugin.module)
112 | await MessageUtils.build_message(
113 | f"已禁用全部 bot 的插件: {plugin_name.result}"
114 | ).finish()
115 | elif bot_id.available:
116 | logger.info(
117 | f"开启 {bot_id.result} 全部插件",
118 | "bot_manage.plugin.disable",
119 | session=session,
120 | )
121 | await BotConsole.enable_all(bot_id.result, "plugins")
122 | await MessageUtils.build_message(f"已开启 {bot_id.result} 全部插件").finish()
123 | else:
124 | bot_id_list = await BotConsole.annotate().values_list("bot_id", flat=True)
125 | for __bot_id in bot_id_list:
126 | await BotConsole.enable_all(__bot_id, "plugins") # type: ignore
127 | logger.info(
128 | "开启全部 bot 全部插件",
129 | "bot_manage.plugin.disable",
130 | session=session,
131 | )
132 | await MessageUtils.build_message("开启全部 bot 全部插件").finish()
133 |
134 |
135 | @bot_manage.assign("plugin.disable")
136 | async def disable_plugin(
137 | session: Uninfo,
138 | plugin_name: Match[str] = AlconnaMatch("plugin_name"),
139 | bot_id: Match[str] = AlconnaMatch("bot_id"),
140 | ):
141 | if plugin_name.available:
142 | plugin = await PluginInfo.get_plugin(name=plugin_name.result)
143 | if not plugin:
144 | await MessageUtils.build_message("未找到该插件...").finish()
145 | if bot_id.available:
146 | logger.info(
147 | f"禁用 {bot_id.result} 的插件 {plugin_name.result}",
148 | "bot_manage.plugin.disable",
149 | session=session,
150 | )
151 | await BotConsole.disable_plugin(bot_id.result, plugin.module)
152 | await MessageUtils.build_message(
153 | f"已禁用 {bot_id.result} 的插件 {plugin_name.result}"
154 | ).finish()
155 | else:
156 | logger.info(
157 | f"禁用全部 bot 的插件: {plugin_name.result}",
158 | "bot_manage.plugin.disable",
159 | session=session,
160 | )
161 | await BotConsole.disable_plugin(None, plugin.module)
162 | await MessageUtils.build_message(
163 | f"已禁用全部 bot 的插件: {plugin_name.result}"
164 | ).finish()
165 | elif bot_id.available:
166 | logger.info(
167 | f"禁用 {bot_id.result} 全部插件",
168 | "bot_manage.plugin.disable",
169 | session=session,
170 | )
171 | await BotConsole.disable_all(bot_id.result, "plugins")
172 | await MessageUtils.build_message(f"已禁用 {bot_id.result} 全部插件").finish()
173 | else:
174 | bot_id_list = await BotConsole.annotate().values_list("bot_id", flat=True)
175 | for __bot_id in bot_id_list:
176 | await BotConsole.disable_all(__bot_id, "plugins") # type: ignore
177 | logger.info(
178 | "禁用全部 bot 全部插件",
179 | "bot_manage.plugin.disable",
180 | session=session,
181 | )
182 | await MessageUtils.build_message("禁用全部 bot 全部插件").finish()
183 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_help/__init__.py:
--------------------------------------------------------------------------------
1 | from nonebot.adapters import Bot
2 | from nonebot.plugin import PluginMetadata
3 | from nonebot_plugin_alconna import (
4 | Alconna,
5 | AlconnaQuery,
6 | Args,
7 | Option,
8 | Query,
9 | on_alconna,
10 | store_true,
11 | )
12 | from nonebot_plugin_session import EventSession
13 |
14 | from ...enum import PluginType
15 | from ...extra import PluginExtraData
16 | from ...log import logger
17 | from ...utils.utils import MessageUtils
18 | from ._data_source import get_plugin_help
19 |
20 | __plugin_meta__ = PluginMetadata(
21 | name="ZXPM帮助",
22 | description="ZXPM帮助,通过 ZXPM [名称]来获取帮助指令",
23 | usage="",
24 | extra=PluginExtraData(
25 | author="HibiKier",
26 | version="0.1",
27 | plugin_type=PluginType.DEPENDANT,
28 | ).to_dict(),
29 | )
30 |
31 |
32 | _matcher = on_alconna(
33 | Alconna(
34 | "zxpm",
35 | Args["name", str],
36 | Option("-s|--superuser", action=store_true, help_text="超级用户帮助"),
37 | ),
38 | aliases={"ZXPM"},
39 | priority=1,
40 | block=True,
41 | )
42 |
43 |
44 | @_matcher.handle()
45 | async def _(
46 | bot: Bot,
47 | name: str,
48 | session: EventSession,
49 | is_superuser: Query[bool] = AlconnaQuery("superuser.value", False),
50 | ):
51 | if not session.id1:
52 | await MessageUtils.build_message("用户id为空...").finish()
53 | _is_superuser = is_superuser.result if is_superuser.available else False
54 | if _is_superuser and session.id1 not in bot.config.superusers:
55 | _is_superuser = False
56 | if result := await get_plugin_help(session.id1, name, _is_superuser):
57 | await MessageUtils.build_message(result).send(reply_to=True)
58 | else:
59 | await MessageUtils.build_message("没有此功能的帮助信息...").send(reply_to=True)
60 | logger.info(f"查看帮助详情: {name}", "帮助", session=session)
61 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_help/_data_source.py:
--------------------------------------------------------------------------------
1 | import nonebot
2 |
3 | from ...enum import PluginType
4 | from ...models.level_user import LevelUser
5 | from ...models.plugin_info import PluginInfo
6 | from ...utils.image_template import ImageTemplate
7 |
8 | driver = nonebot.get_driver()
9 |
10 |
11 | async def get_user_allow_help(user_id: str) -> list[PluginType]:
12 | """获取用户可访问插件类型列表
13 |
14 | 参数:
15 | user_id: 用户id
16 |
17 | 返回:
18 | list[PluginType]: 插件类型列表
19 | """
20 | type_list = [PluginType.NORMAL, PluginType.DEPENDANT]
21 | for level in await LevelUser.filter(user_id=user_id).values_list(
22 | "user_level", flat=True
23 | ):
24 | if level > 0: # type: ignore
25 | type_list.extend((PluginType.ADMIN, PluginType.SUPER_AND_ADMIN))
26 | break
27 | if user_id in driver.config.superusers:
28 | type_list.append(PluginType.SUPERUSER)
29 | return type_list
30 |
31 |
32 | async def get_plugin_help(user_id: str, name: str, is_superuser: bool) -> str | bytes:
33 | """获取功能的帮助信息
34 |
35 | 参数:
36 | user_id: 用户id
37 | name: 插件名称或id
38 | is_superuser: 是否为超级用户
39 | """
40 | type_list = await get_user_allow_help(user_id)
41 | if name.isdigit():
42 | plugin = await PluginInfo.get_or_none(id=int(name), plugin_type__in=type_list)
43 | else:
44 | plugin = await PluginInfo.get_or_none(
45 | name__iexact=name, load_status=True, plugin_type__in=type_list
46 | )
47 | if plugin:
48 | _plugin = nonebot.get_plugin_by_module_name(plugin.module_path)
49 | if _plugin and _plugin.metadata:
50 | items = None
51 | if is_superuser:
52 | extra = _plugin.metadata.extra
53 | if usage := extra.get("superuser_help"):
54 | items = {
55 | "简介": _plugin.metadata.description,
56 | "用法": usage,
57 | }
58 | else:
59 | items = {
60 | "简介": _plugin.metadata.description,
61 | "用法": _plugin.metadata.usage,
62 | }
63 | if items:
64 | return (await ImageTemplate.hl_page(plugin.name, items)).pic2bytes()
65 | return "糟糕! 该功能没有帮助喔..."
66 | return "没有查找到这个功能噢..."
67 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_hooks/__init__.py:
--------------------------------------------------------------------------------
1 | from .zxpm_auth_hook import * # noqa: F403
2 | from .zxpm_ban_hook import * # noqa: F403
3 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_hooks/_auth_checker.py:
--------------------------------------------------------------------------------
1 | import contextlib
2 |
3 | from nonebot.adapters import Bot, Event
4 | from nonebot.exception import IgnoredException
5 | from nonebot.matcher import Matcher
6 | from nonebot_plugin_alconna import At, UniMsg
7 | from nonebot_plugin_session import EventSession
8 | from pydantic import BaseModel
9 |
10 | from ...config import ZxpmConfig
11 | from ...enum import BlockType, LimitWatchType, PluginLimitType, PluginType
12 | from ...extra.limit import CountLimiter, FreqLimiter, UserBlockLimiter
13 | from ...log import logger
14 | from ...models.bot_console import BotConsole
15 | from ...models.group_console import GroupConsole
16 | from ...models.level_user import LevelUser
17 | from ...models.plugin_info import PluginInfo
18 | from ...models.plugin_limit import PluginLimit
19 | from ...utils.utils import MessageUtils
20 |
21 |
22 | class Limit(BaseModel):
23 | limit: PluginLimit
24 | limiter: FreqLimiter | UserBlockLimiter | CountLimiter
25 |
26 | class Config:
27 | arbitrary_types_allowed = True
28 |
29 |
30 | class LimitManage:
31 | add_module = [] # noqa: RUF012
32 |
33 | cd_limit: dict[str, Limit] = {} # noqa: RUF012
34 | block_limit: dict[str, Limit] = {} # noqa: RUF012
35 | count_limit: dict[str, Limit] = {} # noqa: RUF012
36 |
37 | @classmethod
38 | def add_limit(cls, limit: PluginLimit):
39 | """添加限制
40 |
41 | 参数:
42 | limit: PluginLimit
43 | """
44 | if limit.module not in cls.add_module:
45 | cls.add_module.append(limit.module)
46 | if limit.limit_type == PluginLimitType.BLOCK:
47 | cls.block_limit[limit.module] = Limit(
48 | limit=limit, limiter=UserBlockLimiter()
49 | )
50 | elif limit.limit_type == PluginLimitType.CD:
51 | cls.cd_limit[limit.module] = Limit(
52 | limit=limit, limiter=FreqLimiter(limit.cd)
53 | )
54 | elif limit.limit_type == PluginLimitType.COUNT:
55 | cls.count_limit[limit.module] = Limit(
56 | limit=limit, limiter=CountLimiter(limit.max_count)
57 | )
58 |
59 | @classmethod
60 | def unblock(
61 | cls, module: str, user_id: str, group_id: str | None, channel_id: str | None
62 | ):
63 | """解除插件block
64 |
65 | 参数:
66 | module: 模块名
67 | user_id: 用户id
68 | group_id: 群组id
69 | channel_id: 频道id
70 | """
71 | if limit_model := cls.block_limit.get(module):
72 | limit = limit_model.limit
73 | limiter: UserBlockLimiter = limit_model.limiter # type: ignore
74 | key_type = user_id
75 | if group_id and limit.watch_type == LimitWatchType.GROUP:
76 | key_type = channel_id or group_id
77 | limiter.set_false(key_type)
78 |
79 | @classmethod
80 | async def check(
81 | cls,
82 | module: str,
83 | user_id: str,
84 | group_id: str | None,
85 | channel_id: str | None,
86 | session: EventSession,
87 | ):
88 | """检测限制
89 |
90 | 参数:
91 | module: 模块名
92 | user_id: 用户id
93 | group_id: 群组id
94 | channel_id: 频道id
95 | session: Session
96 |
97 | 异常:
98 | IgnoredException: IgnoredException
99 | """
100 | if limit_model := cls.cd_limit.get(module):
101 | await cls.__check(limit_model, user_id, group_id, channel_id, session)
102 | if limit_model := cls.block_limit.get(module):
103 | await cls.__check(limit_model, user_id, group_id, channel_id, session)
104 | if limit_model := cls.count_limit.get(module):
105 | await cls.__check(limit_model, user_id, group_id, channel_id, session)
106 |
107 | @classmethod
108 | async def __check(
109 | cls,
110 | limit_model: Limit | None,
111 | user_id: str,
112 | group_id: str | None,
113 | channel_id: str | None,
114 | session: EventSession,
115 | ):
116 | """检测限制
117 |
118 | 参数:
119 | limit_model: Limit
120 | user_id: 用户id
121 | group_id: 群组id
122 | channel_id: 频道id
123 | session: Session
124 |
125 | 异常:
126 | IgnoredException: IgnoredException
127 | """
128 | if not limit_model:
129 | return
130 | limit = limit_model.limit
131 | limiter = limit_model.limiter
132 | is_limit = (
133 | LimitWatchType.ALL
134 | or (group_id and limit.watch_type == LimitWatchType.GROUP)
135 | or (not group_id and limit.watch_type == LimitWatchType.USER)
136 | )
137 | key_type = user_id
138 | if group_id and limit.watch_type == LimitWatchType.GROUP:
139 | key_type = channel_id or group_id
140 | if is_limit and not limiter.check(key_type):
141 | if limit.result:
142 | await MessageUtils.build_message(limit.result).send()
143 | logger.debug(
144 | f"{limit.module}({limit.limit_type}) 正在限制中...",
145 | "HOOK",
146 | session=session,
147 | )
148 | raise IgnoredException(f"{limit.module} 正在限制中...")
149 | else:
150 | if isinstance(limiter, FreqLimiter):
151 | limiter.start_cd(key_type)
152 | if isinstance(limiter, UserBlockLimiter):
153 | limiter.set_true(key_type)
154 | if isinstance(limiter, CountLimiter):
155 | limiter.increase(key_type)
156 |
157 |
158 | class IsSuperuserException(Exception):
159 | pass
160 |
161 |
162 | class AuthChecker:
163 | """
164 | 权限检查
165 | """
166 |
167 | def __init__(self):
168 | check_notice_info_cd = ZxpmConfig.zxpm_notice_info_cd
169 | self._flmt = FreqLimiter(check_notice_info_cd)
170 | self._flmt_g = FreqLimiter(check_notice_info_cd)
171 | self._flmt_s = FreqLimiter(check_notice_info_cd)
172 | self._flmt_c = FreqLimiter(check_notice_info_cd)
173 |
174 | def is_send_limit_message(self, plugin: PluginInfo, sid: str) -> bool:
175 | """是否发送提示消息
176 |
177 | 参数:
178 | plugin: PluginInfo
179 |
180 | 返回:
181 | bool: 是否发送提示消息
182 | """
183 | if ZxpmConfig.zxpm_notice_info_cd == 0:
184 | return False
185 | if plugin.plugin_type == PluginType.DEPENDANT:
186 | return False
187 | return plugin.module != "ai" if self._flmt_s.check(sid) else False
188 |
189 | async def auth(
190 | self,
191 | matcher: Matcher,
192 | event: Event,
193 | bot: Bot,
194 | session: EventSession,
195 | message: UniMsg,
196 | ):
197 | """权限检查
198 |
199 | 参数:
200 | matcher: matcher
201 | bot: bot
202 | session: EventSession
203 | message: UniMsg
204 | """
205 | is_ignore = False
206 | user_id = session.id1
207 | group_id = session.id3
208 | channel_id = session.id2
209 | if not group_id:
210 | group_id = channel_id
211 | channel_id = None
212 | try:
213 | from nonebot.adapters.onebot.v11 import PokeNotifyEvent
214 |
215 | if matcher.type == "notice" and not isinstance(event, PokeNotifyEvent):
216 | """过滤除poke外的notice"""
217 | return
218 | except ImportError:
219 | if matcher.type == "notice":
220 | return
221 | if user_id and matcher.plugin and (module_path := matcher.plugin.module_name):
222 | if plugin := await PluginInfo.get_or_none(module_path=module_path):
223 | if plugin.plugin_type == PluginType.HIDDEN:
224 | logger.debug("插件为HIDDEN,已跳过...")
225 | return
226 | try:
227 | if session.id1 not in bot.config.superusers:
228 | await self.auth_bot(plugin, bot.self_id)
229 | await self.auth_group(plugin, session, message)
230 | await self.auth_admin(plugin, session)
231 | await self.auth_plugin(plugin, session, event)
232 | await self.auth_limit(plugin, session)
233 | except IsSuperuserException:
234 | logger.debug(
235 | "超级用户或被ban跳过权限检测...", "HOOK", session=session
236 | )
237 | except IgnoredException:
238 | is_ignore = True
239 | LimitManage.unblock(
240 | matcher.plugin.name, user_id, group_id, channel_id
241 | )
242 | except AssertionError as e:
243 | is_ignore = True
244 | logger.debug("消息无法发送", session=session, e=e)
245 | if is_ignore:
246 | raise IgnoredException("权限检测 ignore")
247 |
248 | async def auth_limit(self, plugin: PluginInfo, session: EventSession):
249 | """插件限制
250 |
251 | 参数:
252 | plugin: PluginInfo
253 | session: EventSession
254 | """
255 | user_id = session.id1
256 | group_id = session.id3
257 | channel_id = session.id2
258 | if not group_id:
259 | group_id = channel_id
260 | channel_id = None
261 | limit_list: list[PluginLimit] = await plugin.plugin_limit.filter(
262 | status=True
263 | ).all() # type: ignore
264 | for limit in limit_list:
265 | LimitManage.add_limit(limit)
266 | if user_id:
267 | await LimitManage.check(
268 | plugin.module, user_id, group_id, channel_id, session
269 | )
270 |
271 | async def auth_plugin(
272 | self, plugin: PluginInfo, session: EventSession, event: Event
273 | ):
274 | """插件状态
275 |
276 | 参数:
277 | plugin: PluginInfo
278 | session: EventSession
279 | """
280 | group_id = session.id3
281 | channel_id = session.id2
282 | if not group_id:
283 | group_id = channel_id
284 | channel_id = None
285 | if user_id := session.id1:
286 | is_poke = False
287 | with contextlib.suppress(ImportError):
288 | from nonebot.adapters.onebot.v11 import PokeNotifyEvent
289 |
290 | is_poke = isinstance(event, PokeNotifyEvent)
291 | if group_id:
292 | sid = group_id or user_id
293 | if await GroupConsole.is_superuser_block_plugin(
294 | group_id, plugin.module
295 | ):
296 | """超级用户群组插件状态"""
297 | if self.is_send_limit_message(plugin, sid) and not is_poke:
298 | self._flmt_s.start_cd(group_id or user_id)
299 | await MessageUtils.build_message(
300 | "超级管理员禁用了该群此功能..."
301 | ).send(reply_to=True)
302 | logger.debug(
303 | f"{plugin.name}({plugin.module}) 超级管理员禁用了该群此功能...",
304 | "HOOK",
305 | session=session,
306 | )
307 | raise IgnoredException("超级管理员禁用了该群此功能...")
308 | if await GroupConsole.is_normal_block_plugin(group_id, plugin.module):
309 | """群组插件状态"""
310 | if self.is_send_limit_message(plugin, sid) and not is_poke:
311 | self._flmt_s.start_cd(group_id or user_id)
312 | await MessageUtils.build_message("该群未开启此功能...").send(
313 | reply_to=True
314 | )
315 | logger.debug(
316 | f"{plugin.name}({plugin.module}) 未开启此功能...",
317 | "HOOK",
318 | session=session,
319 | )
320 | raise IgnoredException("该群未开启此功能...")
321 | if not plugin.status and plugin.block_type == BlockType.GROUP:
322 | """全局群组禁用"""
323 | try:
324 | if self.is_send_limit_message(plugin, sid) and not is_poke:
325 | self._flmt_c.start_cd(group_id)
326 | await MessageUtils.build_message(
327 | "该功能在群组中已被禁用..."
328 | ).send(reply_to=True)
329 | except Exception as e:
330 | logger.error(
331 | "auth_plugin 发送消息失败", "HOOK", session=session, e=e
332 | )
333 | logger.debug(
334 | f"{plugin.name}({plugin.module}) 该插件在群组中已被禁用...",
335 | "HOOK",
336 | session=session,
337 | )
338 | raise IgnoredException("该插件在群组中已被禁用...")
339 | else:
340 | sid = user_id
341 | if not plugin.status and plugin.block_type == BlockType.PRIVATE:
342 | """全局私聊禁用"""
343 | try:
344 | if self.is_send_limit_message(plugin, sid) and not is_poke:
345 | self._flmt_c.start_cd(user_id)
346 | await MessageUtils.build_message(
347 | "该功能在私聊中已被禁用..."
348 | ).send()
349 | except Exception as e:
350 | logger.error(
351 | "auth_admin 发送消息失败", "HOOK", session=session, e=e
352 | )
353 | logger.debug(
354 | f"{plugin.name}({plugin.module}) 该插件在私聊中已被禁用...",
355 | "HOOK",
356 | session=session,
357 | )
358 | raise IgnoredException("该插件在私聊中已被禁用...")
359 | if not plugin.status and plugin.block_type == BlockType.ALL:
360 | """全局状态"""
361 | if group_id and await GroupConsole.is_super_group(group_id):
362 | raise IsSuperuserException()
363 | logger.debug(
364 | f"{plugin.name}({plugin.module}) 全局未开启此功能...",
365 | "HOOK",
366 | session=session,
367 | )
368 | if self.is_send_limit_message(plugin, sid) and not is_poke:
369 | self._flmt_s.start_cd(group_id or user_id)
370 | await MessageUtils.build_message("全局未开启此功能...").send()
371 | raise IgnoredException("全局未开启此功能...")
372 |
373 | async def auth_bot(self, plugin: PluginInfo, bot_id: str):
374 | """机器人权限
375 |
376 | 参数:
377 | plugin: PluginInfo
378 | bot_id: bot_id
379 | """
380 | if not await BotConsole.get_bot_status(bot_id):
381 | logger.debug("Bot休眠中阻断权限检测...", "AuthChecker")
382 | raise IgnoredException("BotConsole休眠权限检测 ignore")
383 | if await BotConsole.is_block_plugin(bot_id, plugin.module):
384 | logger.debug(
385 | f"Bot插件 {plugin.name}({plugin.module}) 权限检查结果为关闭...",
386 | "AuthChecker",
387 | )
388 | raise IgnoredException("BotConsole插件权限检测 ignore")
389 |
390 | async def auth_admin(self, plugin: PluginInfo, session: EventSession):
391 | """管理员命令 个人权限
392 |
393 | 参数:
394 | plugin: PluginInfo
395 | session: EventSession
396 | """
397 | user_id = session.id1
398 | if user_id and plugin.admin_level:
399 | if group_id := session.id3 or session.id2:
400 | if not await LevelUser.check_level(
401 | user_id, group_id, plugin.admin_level
402 | ):
403 | try:
404 | if self._flmt.check(user_id):
405 | self._flmt.start_cd(user_id)
406 | await MessageUtils.build_message(
407 | [
408 | At(flag="user", target=user_id),
409 | f"你的权限不足喔,"
410 | f"该功能需要的权限等级: {plugin.admin_level}",
411 | ]
412 | ).send(reply_to=True)
413 | except Exception as e:
414 | logger.error(
415 | "auth_admin 发送消息失败", "HOOK", session=session, e=e
416 | )
417 | logger.debug(
418 | f"{plugin.name}({plugin.module}) 管理员权限不足...",
419 | "HOOK",
420 | session=session,
421 | )
422 | raise IgnoredException("管理员权限不足...")
423 | elif not await LevelUser.check_level(user_id, None, plugin.admin_level):
424 | try:
425 | await MessageUtils.build_message(
426 | f"你的权限不足喔,该功能需要的权限等级: {plugin.admin_level}"
427 | ).send()
428 | except Exception as e:
429 | logger.error(
430 | "auth_admin 发送消息失败", "HOOK", session=session, e=e
431 | )
432 | logger.debug(
433 | f"{plugin.name}({plugin.module}) 管理员权限不足...",
434 | "HOOK",
435 | session=session,
436 | )
437 | raise IgnoredException("权限不足")
438 |
439 | async def auth_group(
440 | self, plugin: PluginInfo, session: EventSession, message: UniMsg
441 | ):
442 | """群黑名单检测 群总开关检测
443 |
444 | 参数:
445 | plugin: PluginInfo
446 | session: EventSession
447 | message: UniMsg
448 | """
449 | if not (group_id := session.id3 or session.id2):
450 | return
451 | text = message.extract_plain_text()
452 | group = await GroupConsole.get_group(group_id)
453 | if not group:
454 | """群不存在"""
455 | raise IgnoredException("群不存在")
456 | if group.level < 0:
457 | """群权限小于0"""
458 | logger.debug(
459 | "群黑名单, 群权限-1...",
460 | "HOOK",
461 | session=session,
462 | )
463 | raise IgnoredException("群黑名单")
464 | if not group.status:
465 | """群休眠"""
466 | if text.strip() != "醒来":
467 | logger.debug("群休眠状态...", "HOOK", session=session)
468 | raise IgnoredException("群休眠状态")
469 | if plugin.level > group.level:
470 | """插件等级大于群等级"""
471 | logger.debug(
472 | f"{plugin.name}({plugin.module}) 群等级限制.."
473 | f"该功能需要的群等级: {plugin.level}..",
474 | "HOOK",
475 | session=session,
476 | )
477 | raise IgnoredException(f"{plugin.name}({plugin.module}) 群等级限制...")
478 |
479 |
480 | checker = AuthChecker()
481 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_hooks/zxpm_auth_hook.py:
--------------------------------------------------------------------------------
1 | from nonebot.adapters import Bot, Event
2 | from nonebot.matcher import Matcher
3 | from nonebot.message import run_postprocessor, run_preprocessor
4 | from nonebot_plugin_alconna import UniMsg
5 | from nonebot_plugin_session import EventSession
6 |
7 | from ._auth_checker import LimitManage, checker
8 |
9 |
10 | # # 权限检测
11 | @run_preprocessor
12 | async def _(
13 | matcher: Matcher, event: Event, bot: Bot, session: EventSession, message: UniMsg
14 | ):
15 | await checker.auth(
16 | matcher,
17 | event,
18 | bot,
19 | session,
20 | message,
21 | )
22 |
23 |
24 | # 解除命令block阻塞
25 | @run_postprocessor
26 | async def _(
27 | matcher: Matcher,
28 | exception: Exception | None,
29 | bot: Bot,
30 | event: Event,
31 | session: EventSession,
32 | ):
33 | user_id = session.id1
34 | group_id = session.id3
35 | channel_id = session.id2
36 | if not group_id:
37 | group_id = channel_id
38 | channel_id = None
39 | if user_id and matcher.plugin:
40 | module = matcher.plugin.name
41 | LimitManage.unblock(module, user_id, group_id, channel_id)
42 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_hooks/zxpm_ban_hook.py:
--------------------------------------------------------------------------------
1 | from nonebot.adapters import Bot, Event
2 | from nonebot.exception import IgnoredException
3 | from nonebot.matcher import Matcher
4 | from nonebot.message import run_preprocessor
5 | from nonebot.typing import T_State
6 | from nonebot_plugin_alconna import At
7 | from nonebot_plugin_session import EventSession
8 |
9 | from ...config import ZxpmConfig
10 | from ...extra import PluginType
11 | from ...extra.limit import FreqLimiter
12 | from ...log import logger
13 | from ...models.ban_console import BanConsole
14 | from ...models.group_console import GroupConsole
15 | from ...utils.utils import MessageUtils
16 |
17 | _flmt = FreqLimiter(300)
18 |
19 |
20 | # 检查是否被ban
21 | @run_preprocessor
22 | async def _(
23 | matcher: Matcher, bot: Bot, event: Event, state: T_State, session: EventSession
24 | ):
25 | if plugin := matcher.plugin:
26 | if metadata := plugin.metadata:
27 | extra = metadata.extra
28 | if extra.get("plugin_type") in [PluginType.HIDDEN, PluginType.DEPENDANT]:
29 | return
30 | user_id = session.id1
31 | group_id = session.id3 or session.id2
32 | if group_id:
33 | if user_id in bot.config.superusers:
34 | return
35 | if await BanConsole.is_ban(None, group_id):
36 | logger.debug("群组处于黑名单中...", "ban_hook")
37 | raise IgnoredException("群组处于黑名单中...")
38 | if g := await GroupConsole.get_group(group_id):
39 | if g.level < 0:
40 | logger.debug("群黑名单, 群权限-1...", "ban_hook")
41 | raise IgnoredException("群黑名单, 群权限-1..")
42 | if user_id:
43 | ban_result = ZxpmConfig.zxpm_ban_reply
44 | if user_id in bot.config.superusers:
45 | return
46 | if await BanConsole.is_ban(user_id, group_id):
47 | time = await BanConsole.check_ban_time(user_id, group_id)
48 | if time == -1:
49 | time_str = "∞"
50 | else:
51 | time = abs(int(time))
52 | if time < 60:
53 | time_str = f"{time!s} 秒"
54 | else:
55 | minute = int(time / 60)
56 | if minute > 60:
57 | hours = minute // 60
58 | minute %= 60
59 | time_str = f"{hours} 小时 {minute}分钟"
60 | else:
61 | time_str = f"{minute} 分钟"
62 | if (
63 | time != -1
64 | and ban_result
65 | and _flmt.check(user_id)
66 | and ZxpmConfig.zxpm_ban_reply
67 | ):
68 | _flmt.start_cd(user_id)
69 | await MessageUtils.build_message(
70 | [
71 | At(flag="user", target=user_id),
72 | f"{ban_result}\n在..在 {time_str} 后才会理你喔",
73 | ]
74 | ).send()
75 | logger.debug("用户处于黑名单中...", "ban_hook")
76 | raise IgnoredException("用户处于黑名单中...")
77 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_init/__init__.py:
--------------------------------------------------------------------------------
1 | import nonebot
2 | from nonebot import get_loaded_plugins
3 | from nonebot.drivers import Driver
4 | from nonebot.plugin import Plugin, PluginMetadata
5 | from ruamel.yaml import YAML
6 |
7 | from ...enum import PluginType
8 | from ...extra import PluginExtraData, PluginSetting
9 | from ...log import logger
10 | from ...models.plugin_info import PluginInfo
11 | from ...models.plugin_limit import PluginLimit
12 | from .manager import manager
13 |
14 | _yaml = YAML(pure=True)
15 | _yaml.allow_unicode = True
16 | _yaml.indent = 2
17 |
18 | driver: Driver = nonebot.get_driver()
19 |
20 |
21 | async def _handle_setting(
22 | plugin: Plugin,
23 | plugin_list: list[PluginInfo],
24 | limit_list: list[PluginLimit],
25 | ):
26 | """处理插件设置
27 |
28 | 参数:
29 | plugin: Plugin
30 | plugin_list: 插件列表
31 | limit_list: 插件限制列表
32 | """
33 | metadata = plugin.metadata
34 | if not metadata:
35 | if not plugin.sub_plugins:
36 | return
37 | """父插件"""
38 | metadata = PluginMetadata(name=plugin.name, description="", usage="")
39 | extra = metadata.extra
40 | extra_data = PluginExtraData(**extra)
41 | logger.debug(f"{metadata.name}:{plugin.name} -> {extra}", "初始化插件数据")
42 | setting = extra_data.setting or PluginSetting()
43 | if metadata.type == "library":
44 | extra_data.plugin_type = PluginType.HIDDEN
45 | if extra_data.plugin_type == PluginType.HIDDEN:
46 | extra_data.menu_type = ""
47 | if plugin.sub_plugins:
48 | extra_data.plugin_type = PluginType.PARENT
49 | plugin_list.append(
50 | PluginInfo(
51 | module=plugin.name,
52 | module_path=plugin.module_name,
53 | name=metadata.name,
54 | author=extra_data.author,
55 | version=extra_data.version,
56 | level=setting.level,
57 | default_status=setting.default_status,
58 | limit_superuser=setting.limit_superuser,
59 | menu_type=extra_data.menu_type,
60 | cost_gold=setting.cost_gold,
61 | plugin_type=extra_data.plugin_type,
62 | admin_level=extra_data.admin_level,
63 | parent=(plugin.parent_plugin.module_name if plugin.parent_plugin else None),
64 | )
65 | )
66 | if extra_data.limits:
67 | limit_list.extend(
68 | PluginLimit(
69 | module=plugin.name,
70 | module_path=plugin.module_name,
71 | limit_type=limit._type,
72 | watch_type=limit.watch_type,
73 | status=limit.status,
74 | check_type=limit.check_type,
75 | result=limit.result,
76 | cd=getattr(limit, "cd", None),
77 | max_count=getattr(limit, "max_count", None),
78 | )
79 | for limit in extra_data.limits
80 | )
81 |
82 |
83 | @driver.on_startup
84 | async def _():
85 | """
86 | 初始化插件数据配置
87 | """
88 | plugin_list: list[PluginInfo] = []
89 | limit_list: list[PluginLimit] = []
90 | module2id = {}
91 | load_plugin = []
92 | if module_list := await PluginInfo.all().values("id", "module_path"):
93 | module2id = {m["module_path"]: m["id"] for m in module_list}
94 | for plugin in get_loaded_plugins():
95 | load_plugin.append(plugin.module_name)
96 | await _handle_setting(plugin, plugin_list, limit_list)
97 | create_list = []
98 | update_list = []
99 | for plugin in plugin_list:
100 | if plugin.module_path not in module2id:
101 | create_list.append(plugin)
102 | else:
103 | plugin.id = module2id[plugin.module_path]
104 | await plugin.save(
105 | update_fields=[
106 | "name",
107 | "author",
108 | "version",
109 | "admin_level",
110 | "plugin_type",
111 | ]
112 | )
113 | update_list.append(plugin)
114 | if create_list:
115 | await PluginInfo.bulk_create(create_list, 10)
116 | await PluginInfo.filter(module_path__in=load_plugin).update(load_status=True)
117 | await PluginInfo.filter(module_path__not_in=load_plugin).update(load_status=False)
118 | manager.init()
119 | if limit_list:
120 | for limit in limit_list:
121 | if not manager.exist(limit.module_path, limit.limit_type):
122 | """不存在,添加"""
123 | manager.add(limit.module_path, limit)
124 | manager.save_file()
125 | await manager.load_to_db()
126 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_init/manager.py:
--------------------------------------------------------------------------------
1 | from copy import deepcopy
2 | from pathlib import Path
3 |
4 | from ruamel.yaml import YAML
5 |
6 | from ...config import ZxpmConfig
7 | from ...enum import BlockType, LimitCheckType, PluginLimitType
8 | from ...extra.limit import BaseBlock, PluginCdBlock, PluginCountBlock
9 | from ...log import logger
10 | from ...models.plugin_info import PluginInfo
11 | from ...models.plugin_limit import PluginLimit
12 |
13 | _yaml = YAML(pure=True)
14 | _yaml.indent = 2
15 | _yaml.allow_unicode = True
16 |
17 |
18 | CD_TEST = """需要cd的功能
19 | 自定义的功能需要cd也可以在此配置
20 | key:模块名称
21 | cd:cd 时长(秒)
22 | status:此限制的开关状态
23 | check_type:'PRIVATE'/'GROUP'/'ALL',限制私聊/群聊/全部
24 | watch_type:监听对象,以user_id或group_id作为键来限制,'USER':用户id,'GROUP':群id
25 | 示例:'USER':用户N秒内触发1次,'GROUP':群N秒内触发1次
26 | result 为 "" 或 None 时则不回复
27 | result示例:"你冲的太快了,先生,请稍后再冲"
28 | result回复:"老色批你冲的太快了,欧尼酱先生,请稍后再冲@老色批"
29 | """
30 |
31 |
32 | BLOCK_TEST = """用户调用阻塞
33 | 即 当用户调用此功能还未结束时
34 | 用发送消息阻止用户重复调用此命令直到该命令结束
35 | key:模块名称
36 | status:此限制的开关状态
37 | check_type:'PRIVATE'/'GROUP'/'ALL',限制私聊/群聊/全部
38 | watch_type:监听对象,以user_id或group_id作为键来限制,'USER':用户id,'GROUP':群id
39 | 示例:'USER':阻塞用户,'group':阻塞群聊
40 | result 为 "" 或 None 时则不回复
41 | result示例:"你冲的太快了,先生,请稍后再冲"
42 | result回复:"老色批你冲的太快了,欧尼酱先生,请稍后再冲@老色批"
43 | """
44 |
45 | COUNT_TEST = """命令每日次数限制
46 | 即 用户/群聊 每日可调用命令的次数 [数据内存存储,重启将会重置]
47 | 每日调用直到 00:00 刷新
48 | key:模块名称
49 | max_count: 每日调用上限
50 | status:此限制的开关状态
51 | watch_type:监听对象,以user_id或group_id作为键来限制,'USER':用户id,'GROUP':群id
52 | 示例:'USER':用户上限,'group':群聊上限
53 | result 为 "" 或 None 时则不回复
54 | result示例:"你冲的太快了,先生,请稍后再冲"
55 | result回复:"老色批你冲的太快了,欧尼酱先生,请稍后再冲@老色批"
56 | """
57 |
58 |
59 | class Manager:
60 | """
61 | 插件命令 cd 管理器
62 | """
63 |
64 | def __init__(self):
65 | if isinstance(ZxpmConfig.zxpm_data_path, str):
66 | ZxpmConfig.zxpm_data_path = Path(ZxpmConfig.zxpm_data_path)
67 | BASE_PATH = ZxpmConfig.zxpm_data_path / "configs"
68 | BASE_PATH.mkdir(parents=True, exist_ok=True)
69 | self.cd_file = BASE_PATH / "plugins2cd.yaml"
70 | self.block_file = BASE_PATH / "plugins2block.yaml"
71 | self.count_file = BASE_PATH / "plugins2count.yaml"
72 | self.cd_data = {}
73 | self.block_data = {}
74 | self.count_data = {}
75 |
76 | def add(
77 | self,
78 | module: str,
79 | data: BaseBlock | PluginCdBlock | PluginCountBlock | PluginLimit,
80 | ):
81 | """添加限制"""
82 | if isinstance(data, PluginLimit):
83 | check_type = BlockType.ALL
84 | if LimitCheckType.GROUP == data.check_type:
85 | check_type = BlockType.GROUP
86 | elif LimitCheckType.PRIVATE == data.check_type:
87 | check_type = BlockType.PRIVATE
88 | if data.limit_type == PluginLimitType.CD:
89 | data = PluginCdBlock(
90 | status=data.status,
91 | check_type=check_type,
92 | watch_type=data.watch_type,
93 | result=data.result,
94 | cd=data.cd,
95 | )
96 | elif data.limit_type == PluginLimitType.BLOCK:
97 | data = BaseBlock(
98 | status=data.status,
99 | check_type=check_type,
100 | watch_type=data.watch_type,
101 | result=data.result,
102 | )
103 | elif data.limit_type == PluginLimitType.COUNT:
104 | data = PluginCountBlock(
105 | status=data.status,
106 | watch_type=data.watch_type,
107 | result=data.result,
108 | max_count=data.max_count,
109 | )
110 | if isinstance(data, PluginCdBlock):
111 | self.cd_data[module] = data
112 | elif isinstance(data, PluginCountBlock):
113 | self.count_data[module] = data
114 | elif isinstance(data, BaseBlock):
115 | self.block_data[module] = data
116 |
117 | def exist(self, module: str, type: PluginLimitType):
118 | """是否存在"""
119 | if type == PluginLimitType.CD:
120 | return module in self.cd_data
121 | elif type == PluginLimitType.BLOCK:
122 | return module in self.block_data
123 | elif type == PluginLimitType.COUNT:
124 | return module in self.count_data
125 |
126 | def init(self):
127 | if not self.cd_file.exists():
128 | self.save_cd_file()
129 | if not self.block_file.exists():
130 | self.save_block_file()
131 | if not self.count_file.exists():
132 | self.save_count_file()
133 | self.__load_file()
134 |
135 | def __load_file(self):
136 | self.__load_block_file()
137 | self.__load_cd_file()
138 | self.__load_count_file()
139 |
140 | def save_file(self):
141 | """保存文件"""
142 | self.save_cd_file()
143 | self.save_block_file()
144 | self.save_count_file()
145 |
146 | def save_cd_file(self):
147 | """保存文件"""
148 | self._extracted_from_save_file_3("PluginCdLimit", CD_TEST, self.cd_data)
149 |
150 | def save_block_file(self):
151 | """保存文件"""
152 | self._extracted_from_save_file_3(
153 | "PluginBlockLimit", BLOCK_TEST, self.block_data
154 | )
155 |
156 | def save_count_file(self):
157 | """保存文件"""
158 | self._extracted_from_save_file_3(
159 | "PluginCountLimit", COUNT_TEST, self.count_data
160 | )
161 |
162 | def _extracted_from_save_file_3(self, type_: str, after: str, data: dict):
163 | """保存文件
164 |
165 | 参数:
166 | type_: 类型参数
167 | after: 备注
168 | """
169 | temp_data = deepcopy(data)
170 | if not temp_data:
171 | temp_data = {
172 | "test": {
173 | "status": False,
174 | "check_type": "ALL",
175 | "limit_type": "USER",
176 | "result": "你冲的太快了,请稍后再冲",
177 | }
178 | }
179 | if type_ == "PluginCdLimit":
180 | temp_data["test"]["cd"] = 5
181 | elif type_ == "PluginCountLimit":
182 | temp_data["test"]["max_count"] = 5
183 | del temp_data["test"]["check_type"]
184 | else:
185 | for v in temp_data:
186 | temp_data[v] = temp_data[v].dict()
187 | if check_type := temp_data[v].get("check_type"):
188 | temp_data[v]["check_type"] = str(check_type)
189 | if watch_type := temp_data[v].get("watch_type"):
190 | temp_data[v]["watch_type"] = str(watch_type)
191 | if type_ == "PluginCountLimit":
192 | del temp_data[v]["check_type"]
193 | file = self.block_file
194 | if type_ == "PluginCdLimit":
195 | file = self.cd_file
196 | elif type_ == "PluginCountLimit":
197 | file = self.count_file
198 | with open(file, "w", encoding="utf8") as f:
199 | _yaml.dump({type_: temp_data}, f)
200 | with open(file, encoding="utf8") as rf:
201 | _data = _yaml.load(rf)
202 | _data.yaml_set_comment_before_after_key(after=after, key=type_)
203 | with open(file, "w", encoding="utf8") as wf:
204 | _yaml.dump(_data, wf)
205 |
206 | def __load_cd_file(self):
207 | self.cd_data: dict[str, PluginCdBlock] = {}
208 | if self.cd_file.exists():
209 | with open(self.cd_file, encoding="utf8") as f:
210 | temp = _yaml.load(f)
211 | if "PluginCdLimit" in temp.keys():
212 | for k, v in temp["PluginCdLimit"].items():
213 | self.cd_data[k] = PluginCdBlock.parse_obj(v)
214 |
215 | def __load_block_file(self):
216 | self.block_data: dict[str, BaseBlock] = {}
217 | if self.block_file.exists():
218 | with open(self.block_file, encoding="utf8") as f:
219 | temp = _yaml.load(f)
220 | if "PluginBlockLimit" in temp.keys():
221 | for k, v in temp["PluginBlockLimit"].items():
222 | self.block_data[k] = BaseBlock.parse_obj(v)
223 |
224 | def __load_count_file(self):
225 | self.count_data: dict[str, PluginCountBlock] = {}
226 | if self.count_file.exists():
227 | with open(self.count_file, encoding="utf8") as f:
228 | temp = _yaml.load(f)
229 | if "PluginCountLimit" in temp.keys():
230 | for k, v in temp["PluginCountLimit"].items():
231 | self.count_data[k] = PluginCountBlock.parse_obj(v)
232 |
233 | def __replace_data(
234 | self,
235 | db_data: PluginLimit | None,
236 | limit: PluginCdBlock | BaseBlock | PluginCountBlock,
237 | ) -> PluginLimit:
238 | """替换数据"""
239 | if not db_data:
240 | db_data = PluginLimit()
241 | db_data.status = limit.status
242 | check_type = LimitCheckType.ALL
243 | if BlockType.GROUP == limit.check_type:
244 | check_type = LimitCheckType.GROUP
245 | elif BlockType.PRIVATE == limit.check_type:
246 | check_type = LimitCheckType.PRIVATE
247 | db_data.check_type = check_type
248 | db_data.watch_type = limit.watch_type
249 | db_data.result = limit.result or ""
250 | return db_data
251 |
252 | def __set_data(
253 | self,
254 | k: str,
255 | db_data: PluginLimit | None,
256 | limit: PluginCdBlock | BaseBlock | PluginCountBlock,
257 | limit_type: PluginLimitType,
258 | module2plugin: dict[str, PluginInfo],
259 | ) -> tuple[PluginLimit, bool]:
260 | """设置数据
261 |
262 | 参数:
263 | k: 模块名
264 | db_data: 数据库数据
265 | limit: 文件数据
266 | limit_type: 限制类型
267 | module2plugin: 模块:插件信息
268 |
269 | 返回:
270 | tuple[PluginLimit, bool]: PluginLimit,是否创建
271 | """
272 | if not db_data:
273 | return (
274 | PluginLimit(
275 | module=k,
276 | module_path=module2plugin[k].module_path,
277 | limit_type=limit_type,
278 | plugin=module2plugin[k],
279 | cd=getattr(limit, "cd", None),
280 | max_count=getattr(limit, "max_count", None),
281 | status=limit.status,
282 | check_type=limit.check_type,
283 | watch_type=limit.watch_type,
284 | result=limit.result,
285 | ),
286 | True,
287 | )
288 | db_data = self.__replace_data(db_data, limit)
289 | if limit_type == PluginLimitType.CD:
290 | db_data.cd = limit.cd # type: ignore
291 | if limit_type == PluginLimitType.COUNT:
292 | db_data.max_count = limit.max_count # type: ignore
293 | return db_data, False
294 |
295 | def __get_file_data(self, limit_type: PluginLimitType) -> dict:
296 | """获取文件数据
297 |
298 | 参数:
299 | limit_type: 限制类型
300 |
301 | 返回:
302 | dict: 文件数据
303 | """
304 | if limit_type == PluginLimitType.CD:
305 | return self.cd_data
306 | elif limit_type == PluginLimitType.COUNT:
307 | return self.count_data
308 | else:
309 | return self.block_data
310 |
311 | def __set_db_limits(
312 | self,
313 | db_limits: list[PluginLimit],
314 | module2plugin: dict[str, PluginInfo],
315 | limit_type: PluginLimitType,
316 | ) -> tuple[list[PluginLimit], list[PluginLimit], list[int]]:
317 | """更新限制数据
318 |
319 | 参数:
320 | db_limits: 数据库limits
321 | module2plugin: 模块:插件信息
322 |
323 | 返回:
324 | tuple[list[PluginLimit], list[PluginLimit]]: 创建列表,更新列表
325 | """
326 | update_list = []
327 | create_list = []
328 | delete_list = []
329 | db_type_limits = [
330 | limit for limit in db_limits if limit.limit_type == limit_type
331 | ]
332 | if data := self.__get_file_data(limit_type):
333 | db_type_limit_modules = [
334 | (limit.module, limit.id) for limit in db_type_limits
335 | ]
336 | delete_list.extend(
337 | id for module, id in db_type_limit_modules if module not in data.keys()
338 | )
339 | for k, v in data.items():
340 | if not module2plugin.get(k):
341 | if k != "test":
342 | logger.warning(
343 | f"插件模块 {k} 未加载,已过滤当前 {v._type} 限制..."
344 | )
345 | continue
346 | db_data = [limit for limit in db_type_limits if limit.module == k]
347 | db_data, is_create = self.__set_data(
348 | k, db_data[0] if db_data else None, v, limit_type, module2plugin
349 | )
350 | if is_create:
351 | create_list.append(db_data)
352 | else:
353 | update_list.append(db_data)
354 | else:
355 | delete_list = [limit.id for limit in db_type_limits]
356 | return create_list, update_list, delete_list
357 |
358 | async def __set_all_limit(
359 | self,
360 | ) -> tuple[list[PluginLimit], list[PluginLimit], list[int]]:
361 | """获取所有插件限制数据
362 |
363 | 返回:
364 | tuple[list[PluginLimit], list[PluginLimit]]: 创建列表,更新列表
365 | """
366 | db_limits = await PluginLimit.all()
367 | modules = set(
368 | list(self.cd_data.keys())
369 | + list(self.block_data.keys())
370 | + list(self.count_data.keys())
371 | )
372 | plugins = await PluginInfo.get_plugins(module__in=modules)
373 | module2plugin = {p.module: p for p in plugins}
374 | create_list, update_list, delete_list = self.__set_db_limits(
375 | db_limits, module2plugin, PluginLimitType.CD
376 | )
377 | create_list1, update_list1, delete_list1 = self.__set_db_limits(
378 | db_limits, module2plugin, PluginLimitType.COUNT
379 | )
380 | create_list2, update_list2, delete_list2 = self.__set_db_limits(
381 | db_limits, module2plugin, PluginLimitType.BLOCK
382 | )
383 | all_create = create_list + create_list1 + create_list2
384 | all_update = update_list + update_list1 + update_list2
385 | all_delete = delete_list + delete_list1 + delete_list2
386 | return all_create, all_update, all_delete
387 |
388 | async def load_to_db(self):
389 | """读取配置文件"""
390 |
391 | create_list, update_list, delete_list = await self.__set_all_limit()
392 | if create_list:
393 | await PluginLimit.bulk_create(create_list)
394 | if update_list:
395 | for limit in update_list:
396 | await limit.save(
397 | update_fields=[
398 | "status",
399 | "check_type",
400 | "watch_type",
401 | "result",
402 | "cd",
403 | "max_count",
404 | ]
405 | )
406 | # TODO: tortoise.exceptions.OperationalError:syntax error at or near "GROUP"
407 | # await PluginLimit.bulk_update(
408 | # update_list,
409 | # ["status", "check_type", "watch_type", "result", "cd", "max_count"],
410 | # )
411 | if delete_list:
412 | await PluginLimit.filter(id__in=delete_list).delete()
413 | cnt = await PluginLimit.filter(status=True).count()
414 | logger.info(f"已经加载 {cnt} 个插件限制.")
415 |
416 |
417 | manager = Manager()
418 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_plugin_switch/__init__.py:
--------------------------------------------------------------------------------
1 | from nonebot.adapters import Bot
2 | from nonebot.plugin import PluginMetadata
3 | from nonebot_plugin_alconna import AlconnaQuery, Arparma, Match, Query
4 | from nonebot_plugin_session import EventSession
5 |
6 | from ...config import ZxpmConfig
7 | from ...enum import BlockType, PluginType
8 | from ...extra import PluginExtraData
9 | from ...log import logger
10 | from ...utils.utils import MessageUtils
11 | from ._data_source import PluginManage, build_plugin
12 | from .command import _group_status_matcher, _status_matcher
13 |
14 | __plugin_meta__ = PluginMetadata(
15 | name="ZXPM功能开关",
16 | description="对群组内的功能限制,超级用户可以对群组以及全局的功能被动开关限制",
17 | usage="""
18 | 普通管理员
19 | 格式:
20 | 开启/关闭[功能名称] : 开关功能
21 | 开启/关闭所有插件 : 开启/关闭当前群组所有插件状态
22 | 醒来 : 结束休眠
23 | 休息吧 : 群组休眠, 不会再响应命令
24 |
25 | 示例:
26 | 开启签到 : 开启签到
27 | 关闭签到 : 关闭签到
28 |
29 | """.strip(),
30 | extra=PluginExtraData(
31 | author="HibiKier",
32 | version="0.1",
33 | plugin_type=PluginType.SUPER_AND_ADMIN,
34 | admin_level=ZxpmConfig.zxpm_switch_level,
35 | superuser_help="""
36 | 格式:
37 | 插件列表
38 | 开启/关闭[功能名称] ?[-t ["private", "p", "group", "g"](关闭类型)] ?[-g 群组Id]
39 |
40 | 开启/关闭插件df[功能名称]: 开启/关闭指定插件进群默认状态
41 | = 开启插件echo -df
42 | = 关闭插件echo -df
43 | 开启/关闭所有插件df: 开启/关闭所有插件进群默认状态
44 | 开启/关闭所有插件:
45 | 私聊中: 开启/关闭所有插件全局状态
46 | 群组中: 开启/关闭当前群组所有插件状态
47 |
48 |
49 | 私聊下:
50 | 示例:
51 | 开启签到 : 全局开启签到
52 | 关闭签到 : 全局关闭签到
53 | 关闭签到 -t p : 全局私聊关闭签到
54 | 关闭签到 -g 12345678 : 关闭群组12345678的签到功能(普通管理员无法开启)
55 | """,
56 | ).to_dict(),
57 | )
58 |
59 |
60 | @_status_matcher.assign("$main")
61 | async def _(
62 | bot: Bot,
63 | session: EventSession,
64 | arparma: Arparma,
65 | ):
66 | if session.id1 in bot.config.superusers:
67 | image = await build_plugin()
68 | logger.info(
69 | "查看功能列表",
70 | arparma.header_result,
71 | session=session,
72 | )
73 | await MessageUtils.build_message(image.pic2bytes()).finish(reply_to=True)
74 | else:
75 | await MessageUtils.build_message("权限不足捏...").finish(reply_to=True)
76 |
77 |
78 | @_status_matcher.assign("open")
79 | async def _(
80 | bot: Bot,
81 | session: EventSession,
82 | arparma: Arparma,
83 | plugin_name: Match[str],
84 | group: Match[str],
85 | task: Query[bool] = AlconnaQuery("task.value", False),
86 | default_status: Query[bool] = AlconnaQuery("default.value", False),
87 | all: Query[bool] = AlconnaQuery("all.value", False),
88 | ):
89 | if not all.result and not plugin_name.available:
90 | await MessageUtils.build_message("请输入功能名称").finish(reply_to=True)
91 | name = plugin_name.result
92 | if gid := session.id3 or session.id2:
93 | """修改当前群组的数据"""
94 | if session.id1 in bot.config.superusers and default_status.result:
95 | """单个插件的进群默认修改"""
96 | result = await PluginManage.set_default_status(name, True)
97 | logger.info(
98 | f"超级用户开启 {name} 功能进群默认开关",
99 | arparma.header_result,
100 | session=session,
101 | )
102 | elif all.result:
103 | """所有插件"""
104 | result = await PluginManage.set_all_plugin_status(
105 | True, default_status.result, gid
106 | )
107 | logger.info(
108 | "开启群组中全部功能",
109 | arparma.header_result,
110 | session=session,
111 | )
112 | else:
113 | result = await PluginManage.unblock_group_plugin(name, gid)
114 | logger.info(f"开启功能 {name}", arparma.header_result, session=session)
115 | await MessageUtils.build_message(result).finish(reply_to=True)
116 | elif session.id1 in bot.config.superusers:
117 | """私聊"""
118 | group_id = group.result if group.available else None
119 | if all.result:
120 | result = await PluginManage.set_all_plugin_status(
121 | True, default_status.result, group_id
122 | )
123 | logger.info(
124 | "超级用户开启全部功能全局开关"
125 | f" {f'指定群组: {group_id}' if group_id else ''}",
126 | arparma.header_result,
127 | session=session,
128 | )
129 | await MessageUtils.build_message(result).finish(reply_to=True)
130 | if default_status.result:
131 | result = await PluginManage.set_default_status(name, True)
132 | logger.info(
133 | f"超级用户开启 {name} 功能进群默认开关",
134 | arparma.header_result,
135 | session=session,
136 | target=group_id,
137 | )
138 | await MessageUtils.build_message(result).finish(reply_to=True)
139 | result = await PluginManage.superuser_unblock(name, None, group_id)
140 | logger.info(
141 | f"超级用户开启功能 {name}",
142 | arparma.header_result,
143 | session=session,
144 | target=group_id,
145 | )
146 | await MessageUtils.build_message(result).finish(reply_to=True)
147 |
148 |
149 | @_status_matcher.assign("close")
150 | async def _(
151 | bot: Bot,
152 | session: EventSession,
153 | arparma: Arparma,
154 | plugin_name: Match[str],
155 | block_type: Match[str],
156 | group: Match[str],
157 | task: Query[bool] = AlconnaQuery("task.value", False),
158 | default_status: Query[bool] = AlconnaQuery("default.value", False),
159 | all: Query[bool] = AlconnaQuery("all.value", False),
160 | ):
161 | if not all.result and not plugin_name.available:
162 | await MessageUtils.build_message("请输入功能名称").finish(reply_to=True)
163 | name = plugin_name.result
164 | if gid := session.id3 or session.id2:
165 | """修改当前群组的数据"""
166 | if session.id1 in bot.config.superusers and default_status.result:
167 | """单个插件的进群默认修改"""
168 | result = await PluginManage.set_default_status(name, False)
169 | logger.info(
170 | f"超级用户开启 {name} 功能进群默认开关",
171 | arparma.header_result,
172 | session=session,
173 | )
174 | elif all.result:
175 | """所有插件"""
176 | result = await PluginManage.set_all_plugin_status(
177 | False, default_status.result, gid
178 | )
179 | logger.info("关闭群组中全部功能", arparma.header_result, session=session)
180 | else:
181 | result = await PluginManage.block_group_plugin(name, gid)
182 | logger.info(f"关闭功能 {name}", arparma.header_result, session=session)
183 | await MessageUtils.build_message(result).finish(reply_to=True)
184 | elif session.id1 in bot.config.superusers:
185 | group_id = group.result if group.available else None
186 | if all.result:
187 | result = await PluginManage.set_all_plugin_status(
188 | False, default_status.result, group_id
189 | )
190 | logger.info(
191 | "超级用户关闭全部功能全局开关"
192 | f" {f'指定群组: {group_id}' if group_id else ''}",
193 | arparma.header_result,
194 | session=session,
195 | )
196 | await MessageUtils.build_message(result).finish(reply_to=True)
197 | if default_status.result:
198 | result = await PluginManage.set_default_status(name, False)
199 | logger.info(
200 | f"超级用户关闭 {name} 功能进群默认开关",
201 | arparma.header_result,
202 | session=session,
203 | target=group_id,
204 | )
205 | await MessageUtils.build_message(result).finish(reply_to=True)
206 | _type = BlockType.ALL
207 | if block_type.result in ["p", "private"]:
208 | if block_type.available:
209 | _type = BlockType.PRIVATE
210 | elif block_type.result in ["g", "group"]:
211 | if block_type.available:
212 | _type = BlockType.GROUP
213 | result = await PluginManage.superuser_block(name, _type, group_id)
214 | logger.info(
215 | f"超级用户关闭功能 {name}, 禁用类型: {_type}",
216 | arparma.header_result,
217 | session=session,
218 | target=group_id,
219 | )
220 | await MessageUtils.build_message(result).finish(reply_to=True)
221 |
222 |
223 | @_group_status_matcher.handle()
224 | async def _(
225 | session: EventSession,
226 | arparma: Arparma,
227 | status: str,
228 | ):
229 | if gid := session.id3 or session.id2:
230 | if status == "sleep":
231 | await PluginManage.sleep(gid)
232 | logger.info("进行休眠", arparma.header_result, session=session)
233 | await MessageUtils.build_message("那我先睡觉了...").finish()
234 | else:
235 | if await PluginManage.is_wake(gid):
236 | await MessageUtils.build_message("我还醒着呢!").finish()
237 | await PluginManage.wake(gid)
238 | logger.info("醒来", arparma.header_result, session=session)
239 | await MessageUtils.build_message("呜..醒来了...").finish()
240 | return MessageUtils.build_message("群组id为空...").send()
241 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_plugin_switch/_data_source.py:
--------------------------------------------------------------------------------
1 | from ...enum import BlockType, PluginType
2 | from ...models.group_console import GroupConsole
3 | from ...models.plugin_info import PluginInfo
4 | from ...utils.image_template import BuildImage, ImageTemplate, RowStyle
5 |
6 |
7 | class GroupInfoNotFound(Exception):
8 | """
9 | 群组未找到
10 | """
11 |
12 | pass
13 |
14 |
15 | def plugin_row_style(column: str, text: str) -> RowStyle:
16 | """被动技能文本风格
17 |
18 | 参数:
19 | column: 表头
20 | text: 文本内容
21 |
22 | 返回:
23 | RowStyle: RowStyle
24 | """
25 | style = RowStyle()
26 | if (
27 | column == "全局状态"
28 | and text == "开启"
29 | or column != "全局状态"
30 | and column == "加载状态"
31 | and text == "SUCCESS"
32 | ):
33 | style.font_color = "#67C23A"
34 | elif column in {"全局状态", "加载状态"}:
35 | style.font_color = "#F56C6C"
36 | return style
37 |
38 |
39 | async def build_plugin() -> BuildImage:
40 | column_name = [
41 | "ID",
42 | "模块",
43 | "名称",
44 | "全局状态",
45 | "禁用类型",
46 | "加载状态",
47 | "菜单分类",
48 | "作者",
49 | "版本",
50 | "金币花费",
51 | ]
52 | plugin_list = await PluginInfo.filter(plugin_type__not=PluginType.HIDDEN).all()
53 | column_data = [
54 | [
55 | plugin.id,
56 | plugin.module,
57 | plugin.name,
58 | "开启" if plugin.status else "关闭",
59 | plugin.block_type,
60 | "SUCCESS" if plugin.load_status else "ERROR",
61 | plugin.menu_type,
62 | plugin.author,
63 | plugin.version,
64 | plugin.cost_gold,
65 | ]
66 | for plugin in plugin_list
67 | ]
68 | return await ImageTemplate.table_page(
69 | "Plugin",
70 | "插件状态",
71 | column_name,
72 | column_data,
73 | text_style=plugin_row_style,
74 | )
75 |
76 |
77 | def task_row_style(column: str, text: str) -> RowStyle:
78 | """被动技能文本风格
79 |
80 | 参数:
81 | column: 表头
82 | text: 文本内容
83 |
84 | 返回:
85 | RowStyle: RowStyle
86 | """
87 | style = RowStyle()
88 | if column in {"群组状态", "全局状态"}:
89 | style.font_color = "#67C23A" if text == "开启" else "#F56C6C"
90 | return style
91 |
92 |
93 | class PluginManage:
94 | @classmethod
95 | async def set_default_status(cls, plugin_name: str, status: bool) -> str:
96 | """设置插件进群默认状态
97 |
98 | 参数:
99 | plugin_name: 插件名称
100 | status: 状态
101 |
102 | 返回:
103 | str: 返回信息
104 | """
105 | if plugin_name.isdigit():
106 | plugin = await PluginInfo.get_or_none(id=int(plugin_name))
107 | else:
108 | plugin = await PluginInfo.get_or_none(
109 | name=plugin_name, load_status=True, plugin_type__not=PluginType.PARENT
110 | )
111 | if plugin:
112 | plugin.default_status = status
113 | await plugin.save(update_fields=["default_status"])
114 | status_text = "开启" if status else "关闭"
115 | return f"成功将 {plugin.name} 进群默认状态修改为: {status_text}"
116 | return "没有找到这个功能喔..."
117 |
118 | @classmethod
119 | async def set_all_plugin_status(
120 | cls, status: bool, is_default: bool = False, group_id: str | None = None
121 | ) -> str:
122 | """修改所有插件状态
123 |
124 | 参数:
125 | status: 状态
126 | is_default: 是否进群默认.
127 | group_id: 指定群组id.
128 |
129 | 返回:
130 | str: 返回信息
131 | """
132 | if is_default:
133 | await PluginInfo.filter(plugin_type=PluginType.NORMAL).update(
134 | default_status=status
135 | )
136 | return f'成功将所有功能进群默认状态修改为: {"开启" if status else "关闭"}'
137 | if group_id:
138 | if group := await GroupConsole.get_or_none(
139 | group_id=group_id, channel_id__isnull=True
140 | ):
141 | module_list = await PluginInfo.filter(
142 | plugin_type=PluginType.NORMAL
143 | ).values_list("module", flat=True)
144 | if status:
145 | for module in module_list:
146 | group.block_plugin = group.block_plugin.replace(
147 | f"<{module},", ""
148 | )
149 | else:
150 | module_list = [f"<{module}" for module in module_list]
151 | group.block_plugin = ",".join(module_list) + "," # type: ignore
152 | await group.save(update_fields=["block_plugin"])
153 | return f'成功将此群组所有功能状态修改为: {"开启" if status else "关闭"}'
154 | return "获取群组失败..."
155 | await PluginInfo.filter(plugin_type=PluginType.NORMAL).update(
156 | status=status, block_type=None if status else BlockType.ALL
157 | )
158 | return f'成功将所有功能全局状态修改为: {"开启" if status else "关闭"}'
159 |
160 | @classmethod
161 | async def is_wake(cls, group_id: str) -> bool:
162 | """是否醒来
163 |
164 | 参数:
165 | group_id: 群组id
166 |
167 | 返回:
168 | bool: 是否醒来
169 | """
170 | if c := await GroupConsole.get_or_none(
171 | group_id=group_id, channel_id__isnull=True
172 | ):
173 | return c.status
174 | return False
175 |
176 | @classmethod
177 | async def sleep(cls, group_id: str):
178 | """休眠
179 |
180 | 参数:
181 | group_id: 群组id
182 | """
183 | await GroupConsole.filter(group_id=group_id, channel_id__isnull=True).update(
184 | status=False
185 | )
186 |
187 | @classmethod
188 | async def wake(cls, group_id: str):
189 | """醒来
190 |
191 | 参数:
192 | group_id: 群组id
193 | """
194 | await GroupConsole.filter(group_id=group_id, channel_id__isnull=True).update(
195 | status=True
196 | )
197 |
198 | @classmethod
199 | async def block(cls, module: str):
200 | """禁用
201 |
202 | 参数:
203 | module: 模块名
204 | """
205 | await PluginInfo.filter(module=module).update(status=False)
206 |
207 | @classmethod
208 | async def unblock(cls, module: str):
209 | """启用
210 |
211 | 参数:
212 | module: 模块名
213 | """
214 | await PluginInfo.filter(module=module).update(status=True)
215 |
216 | @classmethod
217 | async def block_group_plugin(cls, plugin_name: str, group_id: str) -> str:
218 | """禁用群组插件
219 |
220 | 参数:
221 | plugin_name: 插件名称
222 | group_id: 群组id
223 |
224 | 返回:
225 | str: 返回信息
226 | """
227 | return await cls._change_group_plugin(plugin_name, group_id, False)
228 |
229 | @classmethod
230 | async def unblock_group_plugin(cls, plugin_name: str, group_id: str) -> str:
231 | """启用群组插件
232 |
233 | 参数:
234 | plugin_name: 插件名称
235 | group_id: 群组id
236 |
237 | 返回:
238 | str: 返回信息
239 | """
240 | return await cls._change_group_plugin(plugin_name, group_id, True)
241 |
242 | @classmethod
243 | async def _change_group_plugin(
244 | cls, plugin_name: str, group_id: str, status: bool
245 | ) -> str:
246 | """修改群组插件状态
247 |
248 | 参数:
249 | plugin_name: 插件名称
250 | group_id: 群组id
251 | status: 插件状态
252 |
253 | 返回:
254 | str: 返回信息
255 | """
256 |
257 | if plugin_name.isdigit():
258 | plugin = await PluginInfo.get_or_none(id=int(plugin_name))
259 | else:
260 | plugin = await PluginInfo.get_or_none(
261 | name=plugin_name, load_status=True, plugin_type__not=PluginType.PARENT
262 | )
263 | if plugin:
264 | status_str = "开启" if status else "关闭"
265 | if status:
266 | if await GroupConsole.is_normal_block_plugin(group_id, plugin.module):
267 | await GroupConsole.set_unblock_plugin(group_id, plugin.module)
268 | return f"已成功{status_str} {plugin.name} 功能!"
269 | elif not await GroupConsole.is_normal_block_plugin(group_id, plugin.module):
270 | await GroupConsole.set_block_plugin(group_id, plugin.module)
271 | return f"已成功{status_str} {plugin.name} 功能!"
272 | return f"该功能已经{status_str}了喔,不要重复{status_str}..."
273 | return "没有找到这个功能喔..."
274 |
275 | @classmethod
276 | async def superuser_block(
277 | cls, plugin_name: str, block_type: BlockType | None, group_id: str | None
278 | ) -> str:
279 | """超级用户禁用插件
280 |
281 | 参数:
282 | plugin_name: 插件名称
283 | block_type: 禁用类型
284 | group_id: 群组id
285 |
286 | 返回:
287 | str: 返回信息
288 | """
289 | if plugin_name.isdigit():
290 | plugin = await PluginInfo.get_or_none(id=int(plugin_name))
291 | else:
292 | plugin = await PluginInfo.get_or_none(
293 | name=plugin_name, load_status=True, plugin_type__not=PluginType.PARENT
294 | )
295 | if plugin:
296 | if group_id:
297 | if not await GroupConsole.is_superuser_block_plugin(
298 | group_id, plugin.module
299 | ):
300 | await GroupConsole.set_block_plugin(group_id, plugin.module, True)
301 | return f"已成功关闭群组 {group_id} 的 {plugin_name} 功能!"
302 | return "此群组该功能已被超级用户关闭,不要重复关闭..."
303 | plugin.block_type = block_type
304 | plugin.status = not bool(block_type)
305 | await plugin.save(update_fields=["status", "block_type"])
306 | if not block_type:
307 | return f"已成功将 {plugin.name} 全局启用!"
308 | if block_type == BlockType.ALL:
309 | return f"已成功将 {plugin.name} 全局关闭!"
310 | if block_type == BlockType.GROUP:
311 | return f"已成功将 {plugin.name} 全局群组关闭!"
312 | if block_type == BlockType.PRIVATE:
313 | return f"已成功将 {plugin.name} 全局私聊关闭!"
314 | return "没有找到这个功能喔..."
315 |
316 | @classmethod
317 | async def superuser_unblock(
318 | cls, plugin_name: str, block_type: BlockType | None, group_id: str | None
319 | ) -> str:
320 | """超级用户开启插件
321 |
322 | 参数:
323 | plugin_name: 插件名称
324 | block_type: 禁用类型
325 | group_id: 群组id
326 |
327 | 返回:
328 | str: 返回信息
329 | """
330 | if plugin_name.isdigit():
331 | plugin = await PluginInfo.get_or_none(id=int(plugin_name))
332 | else:
333 | plugin = await PluginInfo.get_or_none(
334 | name=plugin_name, load_status=True, plugin_type__not=PluginType.PARENT
335 | )
336 | if plugin:
337 | if group_id:
338 | if await GroupConsole.is_superuser_block_plugin(
339 | group_id, plugin.module
340 | ):
341 | await GroupConsole.set_unblock_plugin(group_id, plugin.module, True)
342 | return f"已成功开启群组 {group_id} 的 {plugin_name} 功能!"
343 | return "此群组该功能已被超级用户开启,不要重复开启..."
344 | plugin.block_type = block_type
345 | plugin.status = not bool(block_type)
346 | await plugin.save(update_fields=["status", "block_type"])
347 | if not block_type:
348 | return f"已成功将 {plugin.name} 全局启用!"
349 | if block_type == BlockType.ALL:
350 | return f"已成功将 {plugin.name} 全局开启!"
351 | if block_type == BlockType.GROUP:
352 | return f"已成功将 {plugin.name} 全局群组开启!"
353 | if block_type == BlockType.PRIVATE:
354 | return f"已成功将 {plugin.name} 全局私聊开启!"
355 | return "没有找到这个功能喔..."
356 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_plugin_switch/command.py:
--------------------------------------------------------------------------------
1 | from nonebot.rule import to_me
2 | from nonebot_plugin_alconna import (
3 | Alconna,
4 | Args,
5 | Option,
6 | Subcommand,
7 | on_alconna,
8 | store_true,
9 | )
10 |
11 | from ...config import ZxpmConfig
12 | from ...rules import admin_check, ensure_group
13 |
14 | _status_matcher = on_alconna(
15 | Alconna(
16 | "switch",
17 | Option("-df|--default", action=store_true, help_text="进群默认开关"),
18 | Option("--all", action=store_true, help_text="全部插件/被动"),
19 | Option("-g|--group", Args["group?", str], help_text="指定群组"),
20 | Subcommand(
21 | "open",
22 | Args["plugin_name?", [str, int]],
23 | ),
24 | Subcommand(
25 | "close",
26 | Args["plugin_name?", [str, int]],
27 | Option(
28 | "-t|--type",
29 | Args["block_type?", ["all", "a", "private", "p", "group", "g"]],
30 | ),
31 | ),
32 | ),
33 | rule=admin_check(ZxpmConfig.zxpm_switch_level),
34 | priority=5,
35 | block=True,
36 | )
37 |
38 | _group_status_matcher = on_alconna(
39 | Alconna("group-status", Args["status", ["sleep", "wake"]]),
40 | rule=admin_check(ZxpmConfig.zxpm_switch_level) & ensure_group & to_me(),
41 | priority=5,
42 | block=True,
43 | )
44 |
45 | _status_matcher.shortcut(
46 | r"插件列表",
47 | command="switch",
48 | arguments=[],
49 | prefix=True,
50 | )
51 |
52 | _status_matcher.shortcut(
53 | r"开启(插件|功能)df(?P.+)",
54 | command="switch",
55 | arguments=["open", "{name}", "-df"],
56 | prefix=True,
57 | )
58 |
59 | _status_matcher.shortcut(
60 | r"关闭(插件|功能)df(?P.+)",
61 | command="switch",
62 | arguments=["close", "{name}", "-df"],
63 | prefix=True,
64 | )
65 |
66 |
67 | _status_matcher.shortcut(
68 | r"开启所有(插件|功能)",
69 | command="switch",
70 | arguments=["open", "s", "--all"],
71 | prefix=True,
72 | )
73 |
74 | _status_matcher.shortcut(
75 | r"开启所有(插件|功能)df",
76 | command="switch",
77 | arguments=["open", "s", "-df", "--all"],
78 | prefix=True,
79 | )
80 |
81 | _status_matcher.shortcut(
82 | r"开启(?P.+)",
83 | command="switch",
84 | arguments=["open", "{name}"],
85 | prefix=True,
86 | )
87 |
88 |
89 | _status_matcher.shortcut(
90 | r"关闭所有(插件|功能)",
91 | command="switch",
92 | arguments=["close", "s", "--all"],
93 | prefix=True,
94 | )
95 |
96 | _status_matcher.shortcut(
97 | r"关闭所有(插件|功能)df",
98 | command="switch",
99 | arguments=["close", "s", "-df", "--all"],
100 | prefix=True,
101 | )
102 |
103 | _status_matcher.shortcut(
104 | r"关闭(?P.+)",
105 | command="switch",
106 | arguments=["close", "{name}"],
107 | prefix=True,
108 | )
109 |
110 |
111 | _group_status_matcher.shortcut(
112 | r"醒来",
113 | command="group-status",
114 | arguments=["wake"],
115 | prefix=True,
116 | )
117 |
118 | _group_status_matcher.shortcut(
119 | r"休息吧",
120 | command="group-status",
121 | arguments=["sleep"],
122 | prefix=True,
123 | )
124 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_set_admin/__init__.py:
--------------------------------------------------------------------------------
1 | from nonebot.permission import SUPERUSER
2 | from nonebot.plugin import PluginMetadata
3 | from nonebot_plugin_alconna import (
4 | Alconna,
5 | Args,
6 | Arparma,
7 | At,
8 | Match,
9 | Option,
10 | Subcommand,
11 | on_alconna,
12 | )
13 | from nonebot_plugin_session import EventSession, SessionLevel
14 |
15 | from ...enum import PluginType
16 | from ...extra import PluginExtraData
17 | from ...log import logger
18 | from ...models.level_user import LevelUser
19 | from ...utils.utils import MessageUtils
20 |
21 | __plugin_meta__ = PluginMetadata(
22 | name="用户权限管理",
23 | description="设置用户权限",
24 | usage="""
25 | 权限设置 add [level: 权限等级] [at: at对象或用户id] ?[-g gid: 群组]
26 | 权限设置 delete [at: at对象或用户id] ?[-g gid: 群组]
27 |
28 | 添加权限 5 @user
29 | 权限设置 add 5 422 -g 352352
30 |
31 | 删除权限 @user
32 | 删除权限 1234123 -g 123123
33 |
34 | """.strip(),
35 | extra=PluginExtraData(
36 | author="HibiKier",
37 | version="0.1",
38 | plugin_type=PluginType.SUPERUSER,
39 | ).dict(),
40 | )
41 |
42 |
43 | _matcher = on_alconna(
44 | Alconna(
45 | "权限设置",
46 | Subcommand(
47 | "add",
48 | Args["level", int]["uid", [str, At]],
49 | help_text="添加权限",
50 | ),
51 | Subcommand("delete", Args["uid", [str, At]], help_text="删除权限"),
52 | Option("-g|--group", Args["gid", str], help_text="指定群组"),
53 | ),
54 | permission=SUPERUSER,
55 | priority=5,
56 | block=True,
57 | )
58 |
59 | _matcher.shortcut(
60 | "添加权限",
61 | command="权限设置",
62 | arguments=["add", "{%0}"],
63 | prefix=True,
64 | )
65 |
66 | _matcher.shortcut(
67 | "删除权限",
68 | command="权限设置",
69 | arguments=["delete", "{%0}"],
70 | prefix=True,
71 | )
72 |
73 |
74 | @_matcher.assign("add")
75 | async def _(
76 | session: EventSession,
77 | arparma: Arparma,
78 | level: int,
79 | gid: Match[str],
80 | uid: str | At,
81 | ):
82 | group_id = gid.result if gid.available else session.id3 or session.id2
83 | if group_id:
84 | if isinstance(uid, At):
85 | uid = uid.target
86 | user = await LevelUser.get_or_none(user_id=uid, group_id=group_id)
87 | old_level = user.user_level if user else 0
88 | await LevelUser.set_level(uid, group_id, level, 1)
89 | logger.info(
90 | f"修改权限: {old_level} -> {level}", arparma.header_result, session=session
91 | )
92 | if session.level in [SessionLevel.LEVEL2, SessionLevel.LEVEL3]:
93 | await MessageUtils.build_message(
94 | [
95 | "成功为 ",
96 | At(flag="user", target=uid),
97 | f" 设置权限:{old_level} -> {level}",
98 | ]
99 | ).finish(reply_to=True)
100 | await MessageUtils.build_message(
101 | f"成功为 \n群组:{group_id}\n用户:{uid} \n"
102 | f"设置权限!\n权限:{old_level} -> {level}"
103 | ).finish()
104 | await MessageUtils.build_message("设置权限时群组不能为空...").finish()
105 |
106 |
107 | @_matcher.assign("delete")
108 | async def _(
109 | session: EventSession,
110 | arparma: Arparma,
111 | gid: Match[str],
112 | uid: str | At,
113 | ):
114 | group_id = gid.result if gid.available else session.id3 or session.id2
115 | if group_id:
116 | if isinstance(uid, At):
117 | uid = uid.target
118 | if user := await LevelUser.get_or_none(user_id=uid, group_id=group_id):
119 | await user.delete()
120 | if session.level in [SessionLevel.LEVEL2, SessionLevel.LEVEL3]:
121 | logger.info(
122 | f"删除权限: {user.user_level} -> 0",
123 | arparma.header_result,
124 | session=session,
125 | )
126 | await MessageUtils.build_message(
127 | ["成功删除 ", At(flag="user", target=uid), " 的权限等级!"]
128 | ).finish(reply_to=True)
129 | logger.info(
130 | f"删除群组用户权限: {user.user_level} -> 0",
131 | arparma.header_result,
132 | session=session,
133 | )
134 | await MessageUtils.build_message(
135 | f"成功删除 \n群组:{group_id}\n用户:{uid} \n"
136 | f"的权限等级!\n权限:{user.user_level} -> 0"
137 | ).finish()
138 | await MessageUtils.build_message("对方目前暂无权限喔...").finish()
139 | await MessageUtils.build_message("设置权限时群组不能为空...").finish()
140 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/commands/zxpm_super_group/__init__.py:
--------------------------------------------------------------------------------
1 | from nonebot import logger
2 | from nonebot.permission import SUPERUSER
3 | from nonebot.plugin import PluginMetadata
4 | from nonebot_plugin_alconna import (
5 | Alconna,
6 | Args,
7 | Arparma,
8 | Match,
9 | Option,
10 | Subcommand,
11 | on_alconna,
12 | store_true,
13 | )
14 | from nonebot_plugin_session import EventSession
15 |
16 | from ...enum import PluginType
17 | from ...extra import PluginExtraData
18 | from ...models.group_console import GroupConsole
19 | from ...utils.utils import MessageUtils
20 |
21 | __plugin_meta__ = PluginMetadata(
22 | name="群白名单",
23 | description="群白名单",
24 | usage="""
25 | 群白名单
26 | 添加/删除群白名单,当在群组中这五个命令且没有指定群号时,默认指定当前群组
27 | 指令:
28 | 格式:
29 | group-manage super-handle [群组Id] [--del 删除操作] : 添加/删除群白名单
30 |
31 | 快捷:
32 | group-manage super-handle : 添加/删除群白名单
33 |
34 | 示例:
35 | 添加/删除群白名单 1234567 : 添加/删除 1234567 为群白名单
36 | """.strip(),
37 | extra=PluginExtraData(
38 | author="HibiKier",
39 | version="0.1",
40 | plugin_type=PluginType.SUPERUSER,
41 | ).dict(),
42 | )
43 |
44 |
45 | _matcher = on_alconna(
46 | Alconna(
47 | "group-manage",
48 | Option("--delete", action=store_true, help_text="删除"),
49 | Subcommand(
50 | "super-handle",
51 | Args["group_id?", str],
52 | help_text="添加/删除群白名单",
53 | ),
54 | ),
55 | permission=SUPERUSER,
56 | priority=1,
57 | block=True,
58 | )
59 |
60 |
61 | _matcher.shortcut(
62 | "添加群白名单(?P.*?)",
63 | command="group-manage",
64 | arguments=["super-handle", "{gid}"],
65 | prefix=True,
66 | )
67 |
68 | _matcher.shortcut(
69 | "删除群白名单(?P.*?)",
70 | command="group-manage",
71 | arguments=["super-handle", "{gid}", "--delete"],
72 | prefix=True,
73 | )
74 |
75 |
76 | @_matcher.assign("super-handle")
77 | async def _(session: EventSession, arparma: Arparma, group_id: Match[str]):
78 | if group_id.available:
79 | gid = group_id.result
80 | else:
81 | gid = session.id3 or session.id2
82 | if not gid:
83 | await MessageUtils.build_message("群组id不能为空!").finish(reply_to=True)
84 | group, _ = await GroupConsole.get_or_create(group_id=gid)
85 | s = "删除" if arparma.find("delete") else "添加"
86 | group.is_super = not arparma.find("delete")
87 | await group.save(update_fields=["is_super"])
88 | await MessageUtils.build_message(f"{s}群白名单成功!").send(reply_to=True)
89 | logger.info(f"{s}群白名单: {gid}")
90 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/config.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import nonebot
4 | from pydantic import BaseModel
5 |
6 | DEFAULT_DATA_PATH = Path() / "data" / "zxpm"
7 |
8 |
9 | class Config(BaseModel):
10 | zxpm_data_path: str | Path = str(DEFAULT_DATA_PATH.absolute())
11 | """数据存储路径"""
12 | zxpm_db_url: str | None = None
13 | """DB_URL"""
14 | zxpm_notice_info_cd: int = 300
15 | """群/用户权限检测等各种检测提示信息cd,为0时不提醒"""
16 | zxpm_ban_reply: str = "才不会给你发消息."
17 | """用户被ban时回复消息,为空时不回复"""
18 | zxpm_ban_level: int = 5
19 | """使用ban功能的对应权限"""
20 | zxpm_switch_level: int = 1
21 | """群组插件开关管理对应权限"""
22 | zxpm_admin_default_auth: int = 5
23 | """群组管理员默认权限"""
24 | zxpm_font: str = "msyh.ttc"
25 | """字体"""
26 | zxpm_limit_superuser: bool = False
27 | """是否限制超管权限"""
28 |
29 |
30 | ZxpmConfig = nonebot.get_plugin_config(Config)
31 |
32 | if isinstance(ZxpmConfig.zxpm_data_path, str):
33 | ZxpmConfig.zxpm_data_path = Path(ZxpmConfig.zxpm_data_path)
34 | ZxpmConfig.zxpm_data_path.mkdir(parents=True, exist_ok=True)
35 | if ZxpmConfig.zxpm_db_url:
36 | if ZxpmConfig.zxpm_db_url.startswith("sqlite"):
37 | db_path = ZxpmConfig.zxpm_db_url.split(":")[-1]
38 | Path(db_path).parent.mkdir(parents=True, exist_ok=True)
39 | else:
40 | db_path = ZxpmConfig.zxpm_data_path / "db" / "zxpm.db"
41 | db_path.parent.mkdir(parents=True, exist_ok=True)
42 | ZxpmConfig.zxpm_db_url = f"sqlite:{db_path.absolute()!s}"
43 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/db_connect/__init__.py:
--------------------------------------------------------------------------------
1 | import nonebot
2 | from tortoise import Tortoise
3 | from tortoise.connection import connections
4 | from tortoise.models import Model as Model_
5 |
6 | from ..config import ZxpmConfig
7 | from ..exception import DbConnectError
8 | from ..log import logger
9 |
10 | driver = nonebot.get_driver()
11 |
12 | MODELS: list[str] = []
13 |
14 |
15 | class Model(Model_):
16 | """
17 | 自动添加模块
18 |
19 | Args:
20 | Model_: Model
21 | """
22 |
23 | def __init_subclass__(cls, **kwargs):
24 | MODELS.append(cls.__module__)
25 |
26 |
27 | @driver.on_startup
28 | async def _():
29 | try:
30 | await Tortoise.init(
31 | db_url=ZxpmConfig.zxpm_db_url,
32 | modules={"models": MODELS},
33 | timezone="Asia/Shanghai",
34 | )
35 | await Tortoise.generate_schemas()
36 | logger.info("ZXPM数据库加载完成!")
37 | except Exception as e:
38 | raise DbConnectError(f"ZXPM数据库连接错误... e:{e}") from e
39 |
40 |
41 | @driver.on_shutdown
42 | async def disconnect():
43 | await connections.close_all()
44 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/enum.py:
--------------------------------------------------------------------------------
1 | from strenum import StrEnum
2 |
3 |
4 | class PluginType(StrEnum):
5 | """
6 | 插件类型
7 | """
8 |
9 | SUPERUSER = "SUPERUSER"
10 | """超级用户"""
11 | ADMIN = "ADMIN"
12 | """管理员"""
13 | SUPER_AND_ADMIN = "ADMIN_SUPER"
14 | """管理员以及超级用户"""
15 | NORMAL = "NORMAL"
16 | """普通插件"""
17 | DEPENDANT = "DEPENDANT"
18 | """依赖插件,一般为没有主动触发命令的插件,受权限控制"""
19 | HIDDEN = "HIDDEN"
20 | """隐藏插件,一般为没有主动触发命令的插件,不受权限控制,如消息统计"""
21 | PARENT = "PARENT"
22 | """父插件,仅仅标记"""
23 |
24 |
25 | class BlockType(StrEnum):
26 | """
27 | 禁用状态
28 | """
29 |
30 | PRIVATE = "PRIVATE"
31 | GROUP = "GROUP"
32 | ALL = "ALL"
33 |
34 |
35 | class PluginLimitType(StrEnum):
36 | """
37 | 插件限制类型
38 | """
39 |
40 | CD = "CD"
41 | COUNT = "COUNT"
42 | BLOCK = "BLOCK"
43 |
44 |
45 | class LimitCheckType(StrEnum):
46 | """
47 | 插件限制类型
48 | """
49 |
50 | PRIVATE = "PRIVATE"
51 | GROUP = "GROUP"
52 | ALL = "ALL"
53 |
54 |
55 | class LimitWatchType(StrEnum):
56 | """
57 | 插件限制监听对象
58 | """
59 |
60 | USER = "USER"
61 | GROUP = "GROUP"
62 | ALL = "ALL"
63 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/exception.py:
--------------------------------------------------------------------------------
1 | class UserAndGroupIsNone(Exception):
2 | """
3 | 用户/群组Id都为空
4 | """
5 |
6 | pass
7 |
8 |
9 | class DbConnectError(Exception):
10 | """
11 | 数据库连接错误
12 | """
13 |
14 | pass
15 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/extra/__init__.py:
--------------------------------------------------------------------------------
1 | from nonebot.compat import PYDANTIC_V2
2 | from pydantic import BaseModel
3 |
4 | from ..enum import PluginType
5 | from .limit import BaseBlock, PluginCdBlock, PluginCountBlock
6 |
7 |
8 | class PluginSetting(BaseModel):
9 | """
10 | 插件基本配置
11 | """
12 |
13 | level: int = 5
14 | """群权限等级"""
15 | default_status: bool = True
16 | """进群默认开关状态"""
17 | limit_superuser: bool = False
18 | """是否限制超级用户"""
19 | cost_gold: int = 0
20 | """调用插件花费金币"""
21 |
22 |
23 | class PluginExtraData(BaseModel):
24 | """
25 | 插件扩展信息
26 | """
27 |
28 | author: str | None = None
29 | """作者"""
30 | version: str | None = None
31 | """版本"""
32 | plugin_type: PluginType = PluginType.NORMAL
33 | """插件类型"""
34 | menu_type: str = "功能"
35 | """菜单类型"""
36 | admin_level: int | None = None
37 | """管理员插件所需权限等级"""
38 | setting: PluginSetting | None = None
39 | """插件基本配置"""
40 | limits: list[BaseBlock | PluginCdBlock | PluginCountBlock] | None = None
41 | """插件限制"""
42 | superuser_help: str | None = None
43 | """超级用户帮助"""
44 |
45 | def to_dict(self):
46 | return self.model_dump() if PYDANTIC_V2 else self.dict()
47 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/extra/limit.py:
--------------------------------------------------------------------------------
1 | import time
2 | from collections import defaultdict
3 | from datetime import datetime
4 | from typing import Any
5 |
6 | import pytz
7 | from pydantic import BaseModel
8 |
9 | from ..enum import BlockType, LimitWatchType, PluginLimitType
10 |
11 |
12 | class BaseBlock(BaseModel):
13 | """
14 | 插件阻断基本类(插件阻断限制)
15 | """
16 |
17 | status: bool = True
18 | """限制状态"""
19 | check_type: BlockType = BlockType.ALL
20 | """检查类型"""
21 | watch_type: LimitWatchType = LimitWatchType.USER
22 | """监听对象"""
23 | result: str | None = None
24 | """阻断时回复内容"""
25 | _type: PluginLimitType = PluginLimitType.BLOCK
26 | """类型"""
27 |
28 |
29 | class PluginCdBlock(BaseBlock):
30 | """
31 | 插件cd限制
32 | """
33 |
34 | cd: int = 5
35 | """cd"""
36 | _type: PluginLimitType = PluginLimitType.CD
37 | """类型"""
38 |
39 |
40 | class PluginCountBlock(BaseBlock):
41 | """
42 | 插件次数限制
43 | """
44 |
45 | max_count: int
46 | """最大调用次数"""
47 | _type: PluginLimitType = PluginLimitType.COUNT
48 | """类型"""
49 |
50 |
51 | class CountLimiter:
52 | """
53 | 每日调用命令次数限制
54 | """
55 |
56 | tz = pytz.timezone("Asia/Shanghai")
57 |
58 | def __init__(self, max_num):
59 | self.today = -1
60 | self.count = defaultdict(int)
61 | self.max = max_num
62 |
63 | def check(self, key) -> bool:
64 | day = datetime.now(self.tz).day
65 | if day != self.today:
66 | self.today = day
67 | self.count.clear()
68 | return self.count[key] < self.max
69 |
70 | def get_num(self, key):
71 | return self.count[key]
72 |
73 | def increase(self, key, num=1):
74 | self.count[key] += num
75 |
76 | def reset(self, key):
77 | self.count[key] = 0
78 |
79 |
80 | class UserBlockLimiter:
81 | """
82 | 检测用户是否正在调用命令
83 | """
84 |
85 | def __init__(self):
86 | self.flag_data = defaultdict(bool)
87 | self.time = time.time()
88 |
89 | def set_true(self, key: Any):
90 | self.time = time.time()
91 | self.flag_data[key] = True
92 |
93 | def set_false(self, key: Any):
94 | self.flag_data[key] = False
95 |
96 | def check(self, key: Any) -> bool:
97 | if time.time() - self.time > 30:
98 | self.set_false(key)
99 | return not self.flag_data[key]
100 |
101 |
102 | class FreqLimiter:
103 | """
104 | 命令冷却,检测用户是否处于冷却状态
105 | """
106 |
107 | def __init__(self, default_cd_seconds: int):
108 | self.next_time = defaultdict(float)
109 | self.default_cd = default_cd_seconds
110 |
111 | def check(self, key: Any) -> bool:
112 | return time.time() >= self.next_time[key]
113 |
114 | def start_cd(self, key: Any, cd_time: int = 0):
115 | self.next_time[key] = time.time() + (
116 | cd_time if cd_time > 0 else self.default_cd
117 | )
118 |
119 | def left_time(self, key: Any) -> float:
120 | return self.next_time[key] - time.time()
121 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/log.py:
--------------------------------------------------------------------------------
1 | from typing import Any, overload
2 |
3 | from loguru import logger as logger_
4 | from nonebot_plugin_session import Session
5 |
6 |
7 | class logger:
8 | TEMPLATE_A = "Adapter[{}] {}"
9 | TEMPLATE_B = "Adapter[{}] [{}]: {}"
10 | TEMPLATE_C = "Adapter[{}] 用户[{}] 触发 [{}]: {}"
11 | TEMPLATE_D = "Adapter[{}] 群聊[{}] 用户[{}] 触发"
12 | " [{}]: {}"
13 | TEMPLATE_E = "Adapter[{}] 群聊[{}] 用户[{}] 触发"
14 | " [{}] [Target]({}): {}"
15 |
16 | TEMPLATE_ADAPTER = "Adapter[{}] "
17 | TEMPLATE_USER = "用户[{}] "
18 | TEMPLATE_GROUP = "群聊[{}] "
19 | TEMPLATE_COMMAND = "CMD[{}] "
20 | TEMPLATE_PLATFORM = "平台[{}] "
21 | TEMPLATE_TARGET = "[Target]([{}]) "
22 |
23 | SUCCESS_TEMPLATE = "[{}]: {} | 参数[{}] 返回: [{}]"
24 |
25 | WARNING_TEMPLATE = "[{}]: {}"
26 |
27 | ERROR_TEMPLATE = "[{}]: {}"
28 |
29 | @overload
30 | @classmethod
31 | def info(
32 | cls,
33 | info: str,
34 | command: str | None = None,
35 | *,
36 | session: int | str | None = None,
37 | group_id: int | str | None = None,
38 | adapter: str | None = None,
39 | target: Any = None,
40 | platform: str | None = None,
41 | ): ...
42 |
43 | @overload
44 | @classmethod
45 | def info(
46 | cls,
47 | info: str,
48 | command: str | None = None,
49 | *,
50 | session: Session | None = None,
51 | target: Any = None,
52 | platform: str | None = None,
53 | ): ...
54 |
55 | @classmethod
56 | def info(
57 | cls,
58 | info: str,
59 | command: str | None = None,
60 | *,
61 | session: int | str | Session | None = None,
62 | group_id: int | str | None = None,
63 | adapter: str | None = None,
64 | target: Any = None,
65 | platform: str | None = None,
66 | ):
67 | user_id: str | None = session # type: ignore
68 | if isinstance(session, Session):
69 | user_id = session.id1
70 | adapter = session.bot_type
71 | if session.id3:
72 | group_id = f"{session.id3}:{session.id2}"
73 | elif session.id2:
74 | group_id = f"{session.id2}"
75 | platform = platform or session.platform
76 | template = cls.__parser_template(
77 | info, command, user_id, group_id, adapter, target, platform
78 | )
79 | try:
80 | logger_.opt(colors=True).info(template)
81 | except Exception:
82 | logger_.info(template)
83 |
84 | @classmethod
85 | def success(
86 | cls,
87 | info: str,
88 | command: str,
89 | param: dict[str, Any] | None = None,
90 | result: str = "",
91 | ):
92 | param_str = ""
93 | if param:
94 | param_str = ",".join([f"{k}:{v}" for k, v in param.items()])
95 | logger_.opt(colors=True).success(
96 | cls.SUCCESS_TEMPLATE.format(command, info, param_str, result)
97 | )
98 |
99 | @overload
100 | @classmethod
101 | def warning(
102 | cls,
103 | info: str,
104 | command: str | None = None,
105 | *,
106 | session: int | str | None = None,
107 | group_id: int | str | None = None,
108 | adapter: str | None = None,
109 | target: Any = None,
110 | platform: str | None = None,
111 | e: Exception | None = None,
112 | ): ...
113 |
114 | @overload
115 | @classmethod
116 | def warning(
117 | cls,
118 | info: str,
119 | command: str | None = None,
120 | *,
121 | session: Session | None = None,
122 | adapter: str | None = None,
123 | target: Any = None,
124 | platform: str | None = None,
125 | e: Exception | None = None,
126 | ): ...
127 |
128 | @classmethod
129 | def warning(
130 | cls,
131 | info: str,
132 | command: str | None = None,
133 | *,
134 | session: int | str | Session | None = None,
135 | group_id: int | str | None = None,
136 | adapter: str | None = None,
137 | target: Any = None,
138 | platform: str | None = None,
139 | e: Exception | None = None,
140 | ):
141 | user_id: str | None = session # type: ignore
142 | if isinstance(session, Session):
143 | user_id = session.id1
144 | adapter = session.bot_type
145 | if session.id3:
146 | group_id = f"{session.id3}:{session.id2}"
147 | elif session.id2:
148 | group_id = f"{session.id2}"
149 | platform = platform or session.platform
150 | template = cls.__parser_template(
151 | info, command, user_id, group_id, adapter, target, platform
152 | )
153 | if e:
154 | template += f" || 错误{type(e)}: {e}"
155 | try:
156 | logger_.opt(colors=True).warning(template)
157 | except Exception as e:
158 | logger_.warning(template)
159 |
160 | @overload
161 | @classmethod
162 | def error(
163 | cls,
164 | info: str,
165 | command: str | None = None,
166 | *,
167 | session: int | str | None = None,
168 | group_id: int | str | None = None,
169 | adapter: str | None = None,
170 | target: Any = None,
171 | platform: str | None = None,
172 | e: Exception | None = None,
173 | ): ...
174 |
175 | @overload
176 | @classmethod
177 | def error(
178 | cls,
179 | info: str,
180 | command: str | None = None,
181 | *,
182 | session: Session | None = None,
183 | target: Any = None,
184 | platform: str | None = None,
185 | e: Exception | None = None,
186 | ): ...
187 |
188 | @classmethod
189 | def error(
190 | cls,
191 | info: str,
192 | command: str | None = None,
193 | *,
194 | session: int | str | Session | None = None,
195 | group_id: int | str | None = None,
196 | adapter: str | None = None,
197 | target: Any = None,
198 | platform: str | None = None,
199 | e: Exception | None = None,
200 | ):
201 | user_id: str | None = session # type: ignore
202 | if isinstance(session, Session):
203 | user_id = session.id1
204 | adapter = session.bot_type
205 | if session.id3:
206 | group_id = f"{session.id3}:{session.id2}"
207 | elif session.id2:
208 | group_id = f"{session.id2}"
209 | platform = platform or session.platform
210 | template = cls.__parser_template(
211 | info, command, user_id, group_id, adapter, target, platform
212 | )
213 | if e:
214 | template += f" || 错误 {type(e)}: {e}"
215 | try:
216 | logger_.opt(colors=True).error(template)
217 | except Exception as e:
218 | logger_.error(template)
219 |
220 | @overload
221 | @classmethod
222 | def debug(
223 | cls,
224 | info: str,
225 | command: str | None = None,
226 | *,
227 | session: int | str | None = None,
228 | group_id: int | str | None = None,
229 | adapter: str | None = None,
230 | target: Any = None,
231 | platform: str | None = None,
232 | e: Exception | None = None,
233 | ): ...
234 |
235 | @overload
236 | @classmethod
237 | def debug(
238 | cls,
239 | info: str,
240 | command: str | None = None,
241 | *,
242 | session: Session | None = None,
243 | target: Any = None,
244 | platform: str | None = None,
245 | e: Exception | None = None,
246 | ): ...
247 |
248 | @classmethod
249 | def debug(
250 | cls,
251 | info: str,
252 | command: str | None = None,
253 | *,
254 | session: int | str | Session | None = None,
255 | group_id: int | str | None = None,
256 | adapter: str | None = None,
257 | target: Any = None,
258 | platform: str | None = None,
259 | e: Exception | None = None,
260 | ):
261 | user_id: str | None = session # type: ignore
262 | if isinstance(session, Session):
263 | user_id = session.id1
264 | adapter = session.bot_type
265 | if session.id3:
266 | group_id = f"{session.id3}:{session.id2}"
267 | elif session.id2:
268 | group_id = f"{session.id2}"
269 | platform = platform or session.platform
270 | template = cls.__parser_template(
271 | info, command, user_id, group_id, adapter, target, platform
272 | )
273 | if e:
274 | template += f" || 错误 {type(e)}: {e}"
275 | try:
276 | logger_.opt(colors=True).debug(template)
277 | except Exception as e:
278 | logger_.debug(template)
279 |
280 | @classmethod
281 | def __parser_template(
282 | cls,
283 | info: str,
284 | command: str | None = None,
285 | user_id: int | str | None = None,
286 | group_id: int | str | None = None,
287 | adapter: str | None = None,
288 | target: Any = None,
289 | platform: str | None = None,
290 | ) -> str:
291 | arg_list = []
292 | template = ""
293 | if adapter is not None:
294 | template += cls.TEMPLATE_ADAPTER
295 | arg_list.append(adapter)
296 | if platform is not None:
297 | template += cls.TEMPLATE_PLATFORM
298 | arg_list.append(platform)
299 | if group_id is not None:
300 | template += cls.TEMPLATE_GROUP
301 | arg_list.append(group_id)
302 | if user_id is not None:
303 | template += cls.TEMPLATE_USER
304 | arg_list.append(user_id)
305 | if command is not None:
306 | template += cls.TEMPLATE_COMMAND
307 | arg_list.append(command)
308 | if target is not None:
309 | template += cls.TEMPLATE_TARGET
310 | arg_list.append(target)
311 | arg_list.append(info)
312 | template += "{}"
313 | return template.format(*arg_list)
314 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/models/ban_console.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | from tortoise import fields
4 | from typing_extensions import Self
5 |
6 | from ..db_connect import Model
7 | from ..exception import UserAndGroupIsNone
8 | from ..log import logger
9 |
10 |
11 | class BanConsole(Model):
12 | id = fields.IntField(pk=True, generated=True, auto_increment=True)
13 | """自增id"""
14 | user_id = fields.CharField(255, null=True)
15 | """用户id"""
16 | group_id = fields.CharField(255, null=True)
17 | """群组id"""
18 | ban_level = fields.IntField()
19 | """使用ban命令的用户等级"""
20 | ban_time = fields.BigIntField()
21 | """ban开始的时间"""
22 | duration = fields.BigIntField()
23 | """ban时长"""
24 | operator = fields.CharField(255)
25 | """使用Ban命令的用户"""
26 |
27 | class Meta: # type: ignore
28 | table = "zxpm_ban_console"
29 | table_description = "封禁人员/群组数据表"
30 |
31 | @classmethod
32 | async def _get_data(cls, user_id: str | None, group_id: str | None) -> Self | None:
33 | """获取数据
34 |
35 | 参数:
36 | user_id: 用户id
37 | group_id: 群组id
38 |
39 | 异常:
40 | UserAndGroupIsNone: 用户id和群组id都为空
41 |
42 | 返回:
43 | Self | None: Self
44 | """
45 | if not user_id and not group_id:
46 | raise UserAndGroupIsNone()
47 | if user_id:
48 | return (
49 | await cls.get_or_none(user_id=user_id, group_id=group_id)
50 | if group_id
51 | else await cls.get_or_none(user_id=user_id, group_id__isnull=True)
52 | )
53 | else:
54 | return await cls.get_or_none(user_id="", group_id=group_id)
55 |
56 | @classmethod
57 | async def check_ban_level(
58 | cls, user_id: str | None, group_id: str | None, level: int
59 | ) -> bool:
60 | """检测ban掉目标的用户与unban用户的权限等级大小
61 |
62 | 参数:
63 | user_id: 用户id
64 | group_id: 群组id
65 | level: 权限等级
66 |
67 | 返回:
68 | bool: 权限判断,能否unban
69 | """
70 | user = await cls._get_data(user_id, group_id)
71 | if user:
72 | logger.debug(
73 | f"检测用户被ban等级,user_level: {user.ban_level},level: {level}",
74 | target=f"{group_id}:{user_id}",
75 | )
76 | return user.ban_level <= level
77 | return False
78 |
79 | @classmethod
80 | async def check_ban_time(
81 | cls, user_id: str | None, group_id: str | None = None
82 | ) -> int:
83 | """检测用户被ban时长
84 |
85 | 参数:
86 | user_id: 用户id
87 |
88 | 返回:
89 | int: ban剩余时长,-1时为永久ban,0表示未被ban
90 | """
91 | logger.debug("获取用户ban时长", target=f"{group_id}:{user_id}")
92 | user = await cls._get_data(user_id, group_id)
93 | if not user and user_id:
94 | user = await cls._get_data(user_id, None)
95 | if user:
96 | if user.duration == -1:
97 | return -1
98 | _time = time.time() - (user.ban_time + user.duration)
99 | return 0 if _time > 0 else int(time.time() - user.ban_time - user.duration)
100 | return 0
101 |
102 | @classmethod
103 | async def is_ban(cls, user_id: str | None, group_id: str | None = None) -> bool:
104 | """判断用户是否被ban
105 |
106 | 参数:
107 | user_id: 用户id
108 |
109 | 返回:
110 | bool: 是否被ban
111 | """
112 | logger.debug("检测是否被ban", target=f"{group_id}:{user_id}")
113 | if await cls.check_ban_time(user_id, group_id):
114 | return True
115 | else:
116 | await cls.unban(user_id, group_id)
117 | return False
118 |
119 | @classmethod
120 | async def ban(
121 | cls,
122 | user_id: str | None,
123 | group_id: str | None,
124 | ban_level: int,
125 | duration: int,
126 | operator: str | None = None,
127 | ):
128 | """ban掉目标用户
129 |
130 | 参数:
131 | user_id: 用户id
132 | group_id: 群组id
133 | ban_level: 使用命令者的权限等级
134 | duration: 时长,分钟,-1时为永久
135 | operator: 操作者id
136 | """
137 | logger.debug(
138 | f"封禁用户/群组,等级:{ban_level},时长: {duration}",
139 | target=f"{group_id}:{user_id}",
140 | )
141 | target = await cls._get_data(user_id, group_id)
142 | if target:
143 | await cls.unban(user_id, group_id)
144 | await cls.create(
145 | user_id=user_id,
146 | group_id=group_id,
147 | ban_level=ban_level,
148 | ban_time=int(time.time()),
149 | duration=duration,
150 | operator=operator or 0,
151 | )
152 |
153 | @classmethod
154 | async def unban(cls, user_id: str | None, group_id: str | None = None) -> bool:
155 | """unban用户
156 |
157 | 参数:
158 | user_id: 用户id
159 | group_id: 群组id
160 |
161 | 返回:
162 | bool: 是否被ban
163 | """
164 | user = await cls._get_data(user_id, group_id)
165 | if user:
166 | logger.debug("解除封禁", target=f"{group_id}:{user_id}")
167 | await user.delete()
168 | return True
169 | return False
170 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/models/bot_console.py:
--------------------------------------------------------------------------------
1 | from typing import Literal, overload
2 |
3 | from tortoise import fields
4 |
5 | from ..db_connect import Model
6 |
7 |
8 | class BotConsole(Model):
9 | id = fields.IntField(pk=True, generated=True, auto_increment=True)
10 | """自增id"""
11 | bot_id = fields.CharField(255, unique=True, description="bot_id")
12 | """bot_id"""
13 | status = fields.BooleanField(default=True, description="Bot状态")
14 | """Bot状态"""
15 | create_time = fields.DatetimeField(auto_now_add=True, description="创建时间")
16 | """创建时间"""
17 | platform = fields.CharField(255, null=True, description="平台")
18 | """平台"""
19 | block_plugins = fields.TextField(default="", description="禁用插件")
20 | """禁用插件"""
21 | block_tasks = fields.TextField(default="", description="禁用被动技能")
22 | """禁用被动技能"""
23 | available_plugins = fields.TextField(default="", description="可用插件")
24 | """可用插件"""
25 | available_tasks = fields.TextField(default="", description="可用被动技能")
26 | """可用被动技能"""
27 |
28 | class Meta: # type: ignore
29 | table = "bot_console"
30 | table_description = "Bot数据表"
31 |
32 | @staticmethod
33 | def format(name: str) -> str:
34 | return f"<{name},"
35 |
36 | @overload
37 | @classmethod
38 | async def get_bot_status(cls) -> list[tuple[str, bool]]: ...
39 |
40 | @overload
41 | @classmethod
42 | async def get_bot_status(cls, bot_id: str) -> bool: ...
43 |
44 | @classmethod
45 | async def get_bot_status(
46 | cls, bot_id: str | None = None
47 | ) -> list[tuple[str, bool]] | bool:
48 | """
49 | 获取bot状态
50 |
51 | 参数:
52 | bot_id (str, optional): bot_id. Defaults to None.
53 |
54 | 返回:
55 | list[tuple[str, bool]] | bool: bot状态
56 | """
57 | if not bot_id:
58 | return await cls.all().values_list("bot_id", "status")
59 | result = await cls.get_or_none(bot_id=bot_id)
60 | return result.status if result else False
61 |
62 | @overload
63 | @classmethod
64 | async def get_tasks(cls) -> list[tuple[str, list[str]]]: ...
65 |
66 | @overload
67 | @classmethod
68 | async def get_tasks(cls, bot_id: str) -> list[str]: ...
69 |
70 | @overload
71 | @classmethod
72 | async def get_tasks(cls, *, status: bool) -> dict[str, list[str]]: ...
73 |
74 | @overload
75 | @classmethod
76 | async def get_tasks(cls, bot_id: str, status: bool = True) -> list[str]: ...
77 |
78 | @classmethod
79 | async def get_tasks(cls, bot_id: str | None = None, status: bool | None = True):
80 | """
81 | 获取bot被动技能
82 |
83 | 参数:
84 | bot_id (str | None, optional): bot_id. Defaults to None.
85 | status (bool | None, optional): 被动状态. Defaults to True.
86 |
87 | 返回:
88 | list[tuple[str, str]] | str: 被动技能
89 | """
90 | if not bot_id:
91 | task_field: Literal["available_tasks", "block_tasks"] = (
92 | "available_tasks" if status else "block_tasks"
93 | )
94 | data_list = await cls.all().values_list("bot_id", task_field)
95 | return {k: cls.convert_module_format(v) for k, v in data_list}
96 | result = await cls.get_or_none(bot_id=bot_id)
97 | if result:
98 | tasks = result.available_tasks if status else result.block_tasks
99 | return cls.convert_module_format(tasks)
100 | return []
101 |
102 | @overload
103 | @classmethod
104 | async def get_plugins(cls) -> dict[str, list[str]]: ...
105 |
106 | @overload
107 | @classmethod
108 | async def get_plugins(cls, bot_id: str) -> list[str]: ...
109 |
110 | @overload
111 | @classmethod
112 | async def get_plugins(cls, *, status: bool) -> dict[str, list[str]]: ...
113 |
114 | @overload
115 | @classmethod
116 | async def get_plugins(cls, bot_id: str, status: bool = True) -> list[str]: ...
117 |
118 | @classmethod
119 | async def get_plugins(cls, bot_id: str | None = None, status: bool = True):
120 | """
121 | 获取bot插件
122 |
123 | 参数:
124 | bot_id (str | None, optional): bot_id. Defaults to None.
125 | status (bool, optional): 插件状态. Defaults to True.
126 |
127 | 返回:
128 | list[tuple[str, str]] | str: 插件
129 | """
130 | if not bot_id:
131 | plugin_field = "available_plugins" if status else "block_plugins"
132 | data_list = await cls.all().values_list("bot_id", plugin_field)
133 | return {k: cls.convert_module_format(v) for k, v in data_list}
134 |
135 | result = await cls.get_or_none(bot_id=bot_id)
136 | if result:
137 | plugins = result.available_plugins if status else result.block_plugins
138 | return cls.convert_module_format(plugins)
139 | return []
140 |
141 | @classmethod
142 | async def set_bot_status(cls, status: bool, bot_id: str | None = None) -> None:
143 | """
144 | 设置bot状态
145 |
146 | 参数:
147 | status (bool): 状态
148 | bot_id (str, optional): bot_id. Defaults to None.
149 |
150 | Raises:
151 | ValueError: 未找到 bot_id
152 | """
153 | if bot_id:
154 | affected_rows = await cls.filter(bot_id=bot_id).update(status=status)
155 | if not affected_rows:
156 | raise ValueError(f"未找到 bot_id: {bot_id}")
157 | else:
158 | await cls.all().update(status=status)
159 |
160 | @overload
161 | @classmethod
162 | def convert_module_format(cls, data: str) -> list[str]: ...
163 |
164 | @overload
165 | @classmethod
166 | def convert_module_format(cls, data: list[str]) -> str: ...
167 |
168 | @classmethod
169 | def convert_module_format(cls, data: str | list[str]) -> str | list[str]:
170 | """
171 | 在 ` None:
192 | """
193 | 在 from_field 和 to_field 之间移动指定的 data
194 |
195 | 参数:
196 | bot_id (str): 目标 bot 的 ID
197 | from_field (str): 源字段名称
198 | to_field (str): 目标字段名称
199 | data (str): 要插入的内容
200 |
201 | Raises:
202 | ValueError: 如果 data 不在 from_field 和 to_field 中
203 | """
204 | bot_data, _ = await cls.get_or_create(bot_id=bot_id)
205 | formatted_data = cls.format(data)
206 |
207 | from_list: str = getattr(bot_data, from_field)
208 | to_list: str = getattr(bot_data, to_field)
209 |
210 | if formatted_data not in (from_list + to_list):
211 | raise ValueError(f"{data} 不在源字段和目标字段中")
212 |
213 | if formatted_data in from_list:
214 | from_list = from_list.replace(formatted_data, "", 1)
215 | if formatted_data not in to_list:
216 | to_list += formatted_data
217 |
218 | setattr(bot_data, from_field, from_list)
219 | setattr(bot_data, to_field, to_list)
220 |
221 | await bot_data.save(update_fields=[from_field, to_field])
222 |
223 | @classmethod
224 | async def disable_plugin(cls, bot_id: str | None, plugin_name: str) -> None:
225 | """
226 | 禁用插件
227 |
228 | 参数:
229 | bot_id (str | None): bot_id
230 | plugin_name (str): 插件名称
231 | """
232 | if bot_id:
233 | await cls._toggle_field(
234 | bot_id,
235 | "available_plugins",
236 | "block_plugins",
237 | plugin_name,
238 | )
239 | else:
240 | bot_list = await cls.all()
241 | for bot in bot_list:
242 | await cls._toggle_field(
243 | bot.bot_id,
244 | "available_plugins",
245 | "block_plugins",
246 | plugin_name,
247 | )
248 |
249 | @classmethod
250 | async def enable_plugin(cls, bot_id: str | None, plugin_name: str) -> None:
251 | """
252 | 启用插件
253 |
254 | 参数:
255 | bot_id (str | None): bot_id
256 | plugin_name (str): 插件名称
257 | """
258 | if bot_id:
259 | await cls._toggle_field(
260 | bot_id,
261 | "block_plugins",
262 | "available_plugins",
263 | plugin_name,
264 | )
265 | else:
266 | bot_list = await cls.all()
267 | for bot in bot_list:
268 | await cls._toggle_field(
269 | bot.bot_id,
270 | "block_plugins",
271 | "available_plugins",
272 | plugin_name,
273 | )
274 |
275 | @classmethod
276 | async def disable_task(cls, bot_id: str | None, task_name: str) -> None:
277 | """
278 | 禁用被动技能
279 |
280 | 参数:
281 | bot_id (str | None): bot_id
282 | task_name (str): 被动技能名称
283 | """
284 | if bot_id:
285 | await cls._toggle_field(
286 | bot_id,
287 | "available_tasks",
288 | "block_tasks",
289 | task_name,
290 | )
291 | else:
292 | bot_list = await cls.all()
293 | for bot in bot_list:
294 | await cls._toggle_field(
295 | bot.bot_id,
296 | "available_tasks",
297 | "block_tasks",
298 | task_name,
299 | )
300 |
301 | @classmethod
302 | async def enable_task(cls, bot_id: str | None, task_name: str) -> None:
303 | """
304 | 启用被动技能
305 |
306 | 参数:
307 | bot_id (str | None): bot_id
308 | task_name (str): 被动技能名称
309 | """
310 | if bot_id:
311 | await cls._toggle_field(
312 | bot_id,
313 | "block_tasks",
314 | "available_tasks",
315 | task_name,
316 | )
317 | else:
318 | bot_list = await cls.all()
319 | for bot in bot_list:
320 | await cls._toggle_field(
321 | bot.bot_id,
322 | "block_tasks",
323 | "available_tasks",
324 | task_name,
325 | )
326 |
327 | @classmethod
328 | async def disable_all(
329 | cls,
330 | bot_id: str,
331 | feat: Literal["plugins", "tasks"],
332 | ) -> None:
333 | """
334 | 禁用全部插件或被动技能
335 |
336 | 参数:
337 | bot_id (str): bot_id
338 | feat (Literal["plugins", "tasks"]): 插件或被动技能
339 | """
340 | bot_data, _ = await cls.get_or_create(bot_id=bot_id)
341 | if feat == "plugins":
342 | available_plugins = cls.convert_module_format(bot_data.available_plugins)
343 | block_plugins = cls.convert_module_format(bot_data.block_plugins)
344 | bot_data.block_plugins = cls.convert_module_format(
345 | available_plugins + block_plugins
346 | )
347 | bot_data.available_plugins = ""
348 | elif feat == "tasks":
349 | available_tasks = cls.convert_module_format(bot_data.available_tasks)
350 | block_tasks = cls.convert_module_format(bot_data.block_tasks)
351 | bot_data.block_tasks = cls.convert_module_format(
352 | available_tasks + block_tasks
353 | )
354 | bot_data.available_tasks = ""
355 | await bot_data.save(
356 | update_fields=[
357 | "available_tasks",
358 | "block_tasks",
359 | "available_plugins",
360 | "block_plugins",
361 | ]
362 | )
363 |
364 | @classmethod
365 | async def enable_all(
366 | cls,
367 | bot_id: str,
368 | feat: Literal["plugins", "tasks"],
369 | ) -> None:
370 | """
371 | 启用全部插件或被动技能
372 |
373 | 参数:
374 | bot_id (str): bot_id
375 | feat (Literal["plugins", "tasks"]): 插件或被动技能
376 | """
377 | bot_data, _ = await cls.get_or_create(bot_id=bot_id)
378 | if feat == "plugins":
379 | available_plugins = cls.convert_module_format(bot_data.available_plugins)
380 | block_plugins = cls.convert_module_format(bot_data.block_plugins)
381 | bot_data.available_plugins = cls.convert_module_format(
382 | available_plugins + block_plugins
383 | )
384 | bot_data.block_plugins = ""
385 | elif feat == "tasks":
386 | available_tasks = cls.convert_module_format(bot_data.available_tasks)
387 | block_tasks = cls.convert_module_format(bot_data.block_tasks)
388 | bot_data.available_tasks = cls.convert_module_format(
389 | available_tasks + block_tasks
390 | )
391 | bot_data.block_tasks = ""
392 | await bot_data.save(
393 | update_fields=[
394 | "available_tasks",
395 | "block_tasks",
396 | "available_plugins",
397 | "block_plugins",
398 | ]
399 | )
400 |
401 | @classmethod
402 | async def is_block_plugin(cls, bot_id: str, plugin_name: str) -> bool:
403 | """
404 | 检查插件是否被禁用
405 |
406 | 参数:
407 | bot_id (str): bot_id
408 | plugin_name (str): 插件某款
409 |
410 | 返回:
411 | bool: 是否被禁用
412 | """
413 | bot_data, _ = await cls.get_or_create(bot_id=bot_id)
414 | return cls.format(plugin_name) in bot_data.block_plugins
415 |
416 | @classmethod
417 | async def is_block_task(cls, bot_id: str, task_name: str) -> bool:
418 | """
419 | 检查被动技能是否被禁用
420 |
421 | 参数:
422 | bot_id (str): bot_id
423 | task_name (str): 被动技能名称
424 |
425 | 返回:
426 | bool: 是否被禁用
427 | """
428 | bot_data, _ = await cls.get_or_create(bot_id=bot_id)
429 | return cls.format(task_name) in bot_data.block_tasks
430 |
431 | @classmethod
432 | async def _run_script(cls):
433 | return []
434 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/models/group_console.py:
--------------------------------------------------------------------------------
1 | from tortoise import fields
2 | from typing_extensions import Self
3 |
4 | from ..db_connect import Model
5 |
6 |
7 | class GroupConsole(Model):
8 | id = fields.IntField(pk=True, generated=True, auto_increment=True)
9 | """自增id"""
10 | group_id = fields.CharField(255, description="群组id")
11 | """群聊id"""
12 | channel_id = fields.CharField(255, null=True, description="频道id")
13 | """频道id"""
14 | group_name = fields.TextField(default="", description="群组名称")
15 | """群聊名称"""
16 | max_member_count = fields.IntField(default=0, description="最大人数")
17 | """最大人数"""
18 | member_count = fields.IntField(default=0, description="当前人数")
19 | """当前人数"""
20 | status = fields.BooleanField(default=True, description="群状态")
21 | """群状态"""
22 | level = fields.IntField(default=5, description="群权限")
23 | """群权限"""
24 | is_super = fields.BooleanField(
25 | default=False, description="超级用户指定,可以使用全局关闭的功能"
26 | )
27 | """超级用户指定群,可以使用全局关闭的功能"""
28 | block_plugin = fields.TextField(default="", description="禁用插件")
29 | """禁用插件"""
30 | superuser_block_plugin = fields.TextField(
31 | default="", description="超级用户禁用插件"
32 | )
33 |
34 | class Meta: # type: ignore
35 | table = "zxpm_group_console"
36 | table_description = "群组信息表"
37 | unique_together = ("group_id", "channel_id")
38 |
39 | @classmethod
40 | async def get_group(
41 | cls, group_id: str, channel_id: str | None = None
42 | ) -> Self | None:
43 | """获取群组
44 |
45 | 参数:
46 | group_id: 群组id
47 | channel_id: 频道id.
48 |
49 | 返回:
50 | Self: GroupConsole
51 | """
52 | if channel_id:
53 | return await cls.get_or_none(group_id=group_id, channel_id=channel_id)
54 | return await cls.get_or_none(group_id=group_id, channel_id__isnull=True)
55 |
56 | @classmethod
57 | async def is_super_group(cls, group_id: str) -> bool:
58 | """是否超级用户指定群
59 |
60 | 参数:
61 | group_id: 群组id
62 |
63 | 返回:
64 | bool: 是否超级用户指定群
65 | """
66 | return group.is_super if (group := await cls.get_group(group_id)) else False
67 |
68 | @classmethod
69 | async def is_superuser_block_plugin(cls, group_id: str, module: str) -> bool:
70 | """查看群组是否超级用户禁用功能
71 |
72 | 参数:
73 | group_id: 群组id
74 | module: 模块名称
75 |
76 | 返回:
77 | bool: 是否禁用被动
78 | """
79 | return await cls.exists(
80 | group_id=group_id,
81 | superuser_block_plugin__contains=f"<{module},",
82 | )
83 |
84 | @classmethod
85 | async def is_block_plugin(cls, group_id: str, module: str) -> bool:
86 | """查看群组是否禁用插件
87 |
88 | 参数:
89 | group_id: 群组id
90 | plugin: 插件名称
91 |
92 | 返回:
93 | bool: 是否禁用插件
94 | """
95 | return await cls.exists(
96 | group_id=group_id, block_plugin__contains=f"<{module},"
97 | ) or await cls.exists(
98 | group_id=group_id, superuser_block_plugin__contains=f"<{module},"
99 | )
100 |
101 | @classmethod
102 | async def set_block_plugin(
103 | cls,
104 | group_id: str,
105 | module: str,
106 | is_superuser: bool = False,
107 | ):
108 | """禁用群组插件
109 |
110 | 参数:
111 | group_id: 群组id
112 | task: 任务模块
113 | is_superuser: 是否为超级用户
114 | """
115 | group, _ = await cls.get_or_create(group_id=group_id)
116 | if is_superuser:
117 | if f"<{module}," not in group.superuser_block_plugin:
118 | group.superuser_block_plugin += f"<{module},"
119 | elif f"<{module}," not in group.block_plugin:
120 | group.block_plugin += f"<{module},"
121 | await group.save(update_fields=["block_plugin", "superuser_block_plugin"])
122 |
123 | @classmethod
124 | async def set_unblock_plugin(
125 | cls,
126 | group_id: str,
127 | module: str,
128 | is_superuser: bool = False,
129 | ):
130 | """禁用群组插件
131 |
132 | 参数:
133 | group_id: 群组id
134 | task: 任务模块
135 | is_superuser: 是否为超级用户
136 | """
137 | group, _ = await cls.get_or_create(group_id=group_id)
138 | if is_superuser:
139 | if f"<{module}," in group.superuser_block_plugin:
140 | group.superuser_block_plugin = group.superuser_block_plugin.replace(
141 | f"<{module},", ""
142 | )
143 | elif f"<{module}," in group.block_plugin:
144 | group.block_plugin = group.block_plugin.replace(f"<{module},", "")
145 | await group.save(update_fields=["block_plugin", "superuser_block_plugin"])
146 |
147 | @classmethod
148 | async def is_normal_block_plugin(
149 | cls, group_id: str, module: str, channel_id: str | None = None
150 | ) -> bool:
151 | """查看群组是否禁用功能
152 |
153 | 参数:
154 | group_id: 群组id
155 | module: 模块名称
156 | channel_id: 频道id
157 |
158 | 返回:
159 | bool: 是否禁用被动
160 | """
161 | return await cls.exists(
162 | group_id=group_id,
163 | channel_id=channel_id,
164 | block_plugin__contains=f"<{module},",
165 | )
166 |
167 | @classmethod
168 | async def is_superuser_block_task(cls, group_id: str, task: str) -> bool:
169 | """查看群组是否超级用户禁用被动
170 |
171 | 参数:
172 | group_id: 群组id
173 | task: 模块名称
174 |
175 | 返回:
176 | bool: 是否禁用被动
177 | """
178 | return await cls.exists(
179 | group_id=group_id,
180 | superuser_block_task__contains=f"<{task},",
181 | )
182 |
183 | @classmethod
184 | async def is_block_task(
185 | cls, group_id: str, task: str, channel_id: str | None = None
186 | ) -> bool:
187 | """查看群组是否禁用被动
188 |
189 | 参数:
190 | group_id: 群组id
191 | task: 任务模块
192 | channel_id: 频道id
193 |
194 | 返回:
195 | bool: 是否禁用被动
196 | """
197 | if not channel_id:
198 | return await cls.exists(
199 | group_id=group_id,
200 | channel_id__isnull=True,
201 | block_task__contains=f"<{task},",
202 | ) or await cls.exists(
203 | group_id=group_id,
204 | channel_id__isnull=True,
205 | superuser_block_task__contains=f"<{task},",
206 | )
207 | return await cls.exists(
208 | group_id=group_id, channel_id=channel_id, block_task__contains=f"<{task},"
209 | ) or await cls.exists(
210 | group_id=group_id,
211 | channel_id__isnull=True,
212 | superuser_block_task__contains=f"<{task},",
213 | )
214 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/models/level_user.py:
--------------------------------------------------------------------------------
1 | from tortoise import fields
2 |
3 | from ..db_connect import Model
4 |
5 |
6 | class LevelUser(Model):
7 | id = fields.IntField(pk=True, generated=True, auto_increment=True)
8 | """自增id"""
9 | user_id = fields.CharField(255)
10 | """用户id"""
11 | group_id = fields.CharField(255)
12 | """群聊id"""
13 | user_level = fields.BigIntField()
14 | """用户权限等级"""
15 | group_flag = fields.IntField(default=0)
16 | """特殊标记,是否随群管理员变更而设置权限"""
17 |
18 | class Meta: # type: ignore
19 | table = "zxpm_level_users"
20 | table_description = "用户权限数据库"
21 | unique_together = ("user_id", "group_id")
22 |
23 | @classmethod
24 | async def get_user_level(cls, user_id: str, group_id: str | None) -> int:
25 | """获取用户在群内的等级
26 |
27 | 参数:
28 | user_id: 用户id
29 | group_id: 群组id
30 |
31 | 返回:
32 | int: 权限等级
33 | """
34 | if not group_id:
35 | return 0
36 | if user := await cls.get_or_none(user_id=user_id, group_id=group_id):
37 | return user.user_level
38 | return 0
39 |
40 | @classmethod
41 | async def set_level(
42 | cls,
43 | user_id: str,
44 | group_id: str,
45 | level: int,
46 | group_flag: int = 0,
47 | ):
48 | """设置用户在群内的权限
49 |
50 | 参数:
51 | user_id: 用户id
52 | group_id: 群组id
53 | level: 权限等级
54 | group_flag: 是否被自动更新刷新权限 0:是, 1:否.
55 | """
56 | await cls.update_or_create(
57 | user_id=user_id,
58 | group_id=group_id,
59 | defaults={
60 | "user_level": level,
61 | "group_flag": group_flag,
62 | },
63 | )
64 |
65 | @classmethod
66 | async def delete_level(cls, user_id: str, group_id: str) -> bool:
67 | """删除用户权限
68 |
69 | 参数:
70 | user_id: 用户id
71 | group_id: 群组id
72 |
73 | 返回:
74 | bool: 是否含有用户权限
75 | """
76 | if user := await cls.get_or_none(user_id=user_id, group_id=group_id):
77 | await user.delete()
78 | return True
79 | return False
80 |
81 | @classmethod
82 | async def check_level(cls, user_id: str, group_id: str | None, level: int) -> bool:
83 | """检查用户权限等级是否大于 level
84 |
85 | 参数:
86 | user_id: 用户id
87 | group_id: 群组id
88 | level: 权限等级
89 |
90 | 返回:
91 | bool: 是否大于level
92 | """
93 | if group_id:
94 | if user := await cls.get_or_none(user_id=user_id, group_id=group_id):
95 | return user.user_level >= level
96 | else:
97 | if user_list := await cls.filter(user_id=user_id).all():
98 | user = max(user_list, key=lambda x: x.user_level)
99 | return user.user_level >= level
100 | return False
101 |
102 | @classmethod
103 | async def is_group_flag(cls, user_id: str, group_id: str) -> bool:
104 | """检测是否会被自动更新刷新权限
105 |
106 | 参数:
107 | user_id: 用户id
108 | group_id: 群组id
109 |
110 | 返回:
111 | bool: 是否会被自动更新权限刷新
112 | """
113 | if user := await cls.get_or_none(user_id=user_id, group_id=group_id):
114 | return user.group_flag == 1
115 | return False
116 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/models/plugin_info.py:
--------------------------------------------------------------------------------
1 | from tortoise import fields
2 | from typing_extensions import Self
3 |
4 | from ..db_connect import Model
5 | from ..enum import BlockType, PluginType
6 | from .plugin_limit import PluginLimit # noqa: F401
7 |
8 |
9 | class PluginInfo(Model):
10 | id = fields.IntField(pk=True, generated=True, auto_increment=True)
11 | """自增id"""
12 | module = fields.CharField(255, description="模块名")
13 | """模块名"""
14 | module_path = fields.CharField(255, description="模块路径", unique=True)
15 | """模块路径"""
16 | name = fields.CharField(255, description="插件名称")
17 | """插件名称"""
18 | status = fields.BooleanField(default=True, description="全局开关状态")
19 | """全局开关状态"""
20 | block_type: BlockType | None = fields.CharEnumField(
21 | BlockType, default=None, null=True, description="禁用类型"
22 | )
23 | """禁用类型"""
24 | load_status = fields.BooleanField(default=True, description="加载状态")
25 | """加载状态"""
26 | author = fields.CharField(255, null=True, description="作者")
27 | """作者"""
28 | version = fields.CharField(max_length=255, null=True, description="版本")
29 | """版本"""
30 | level = fields.IntField(default=5, description="所需群权限")
31 | """所需群权限"""
32 | default_status = fields.BooleanField(default=True, description="进群默认开关状态")
33 | """进群默认开关状态"""
34 | limit_superuser = fields.BooleanField(default=False, description="是否限制超级用户")
35 | """是否限制超级用户"""
36 | menu_type = fields.CharField(max_length=255, default="", description="菜单类型")
37 | """菜单类型"""
38 | plugin_type = fields.CharEnumField(PluginType, null=True, description="插件类型")
39 | """插件类型"""
40 | cost_gold = fields.IntField(default=0, description="调用插件所需金币")
41 | """调用插件所需金币"""
42 | plugin_limit = fields.ReverseRelation["PluginLimit"]
43 | """插件限制"""
44 | admin_level = fields.IntField(default=0, null=True, description="调用所需权限等级")
45 | """调用所需权限等级"""
46 | is_delete = fields.BooleanField(default=False, description="是否删除")
47 | """是否删除"""
48 | parent = fields.CharField(max_length=255, null=True, description="父插件")
49 | """父插件"""
50 |
51 | class Meta: # type: ignore
52 | table = "zxpm_plugin_info"
53 | table_description = "插件基本信息"
54 |
55 | @classmethod
56 | async def get_plugin(cls, load_status: bool = True, **kwargs) -> Self | None:
57 | """获取插件列表
58 |
59 | 参数:
60 | load_status: 加载状态.
61 |
62 | 返回:
63 | Self | None: 插件
64 | """
65 | return await cls.get_or_none(load_status=load_status, **kwargs)
66 |
67 | @classmethod
68 | async def get_plugins(cls, load_status: bool = True, **kwargs) -> list[Self]:
69 | """获取插件列表
70 |
71 | 参数:
72 | load_status: 加载状态.
73 |
74 | 返回:
75 | list[Self]: 插件列表
76 | """
77 | return await cls.filter(load_status=load_status, **kwargs).all()
78 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/models/plugin_limit.py:
--------------------------------------------------------------------------------
1 | from tortoise import fields
2 |
3 | from ..db_connect import Model
4 | from ..enum import LimitCheckType, LimitWatchType, PluginLimitType
5 |
6 |
7 | class PluginLimit(Model):
8 | id = fields.IntField(pk=True, generated=True, auto_increment=True)
9 | """自增id"""
10 | module = fields.CharField(255, description="模块名")
11 | """模块名"""
12 | module_path = fields.CharField(255, description="模块路径")
13 | """模块路径"""
14 | plugin = fields.ForeignKeyField(
15 | "models.PluginInfo",
16 | related_name="plugin_limit",
17 | on_delete=fields.CASCADE,
18 | description="所属插件",
19 | )
20 | """所属插件"""
21 | limit_type = fields.CharEnumField(PluginLimitType, description="限制类型")
22 | """限制类型"""
23 | watch_type = fields.CharEnumField(LimitWatchType, description="监听类型")
24 | """监听类型"""
25 | status = fields.BooleanField(default=True, description="限制的开关状态")
26 | """限制的开关状态"""
27 | check_type = fields.CharEnumField(
28 | LimitCheckType, default=LimitCheckType.ALL, description="检查类型"
29 | )
30 | """检查类型"""
31 | result = fields.CharField(max_length=255, null=True, description="返回信息")
32 | """返回信息"""
33 | cd = fields.IntField(null=True, description="cd")
34 | """cd"""
35 | max_count = fields.IntField(null=True, description="最大调用次数")
36 | """最大调用次数"""
37 |
38 | class Meta: # type: ignore
39 | table = "zxpm_plugin_limit"
40 | table_description = "插件限制"
41 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/rules.py:
--------------------------------------------------------------------------------
1 | from nonebot.adapters import Bot, Event
2 | from nonebot.internal.rule import Rule
3 | from nonebot.permission import SUPERUSER
4 | from nonebot_plugin_session import EventSession, SessionLevel
5 |
6 | from .models.level_user import LevelUser
7 |
8 |
9 | def ensure_group(session: EventSession) -> bool:
10 | """
11 | 是否在群聊中
12 |
13 | 参数:
14 | session: session
15 |
16 | 返回:
17 | bool: bool
18 | """
19 | return session.level in [SessionLevel.LEVEL2, SessionLevel.LEVEL3]
20 |
21 |
22 | def admin_check(a: int | None = None) -> Rule:
23 | """
24 | 管理员权限等级检查
25 |
26 | 参数:
27 | a: 权限等级或 配置项 module
28 | key: 配置项 key.
29 |
30 | 返回:
31 | Rule: Rule
32 | """
33 |
34 | async def _rule(bot: Bot, event: Event, session: EventSession) -> bool:
35 | if await SUPERUSER(bot, event):
36 | return True
37 | if session.id1 and session.id2:
38 | level = a
39 | if level is not None:
40 | return bool(
41 | await LevelUser.check_level(session.id1, session.id2, int(level))
42 | )
43 | return False
44 |
45 | return Rule(_rule)
46 |
47 |
48 | def notice_rule(event_type: type | list[type]) -> Rule:
49 | """
50 | Notice限制
51 |
52 | 参数:
53 | event_type: Event类型
54 |
55 | 返回:
56 | Rule: Rule
57 | """
58 |
59 | async def _rule(event: Event) -> bool:
60 | if isinstance(event_type, list):
61 | for et in event_type:
62 | if isinstance(event, et):
63 | return True
64 | else:
65 | return isinstance(event, event_type)
66 | return False
67 |
68 | return Rule(_rule)
69 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/utils/build_image.py:
--------------------------------------------------------------------------------
1 | import math
2 | import uuid
3 | from io import BytesIO
4 | from pathlib import Path
5 | from typing import Literal, TypeAlias, overload
6 |
7 | from nonebot.utils import run_sync
8 | from PIL import Image, ImageDraw, ImageFont
9 | from PIL.Image import Image as tImage
10 | from PIL.ImageFont import FreeTypeFont
11 | from typing_extensions import Self
12 |
13 | from ..config import ZxpmConfig
14 |
15 | ModeType = Literal[
16 | "1", "CMYK", "F", "HSV", "I", "L", "LAB", "P", "RGB", "RGBA", "RGBX", "YCbCr"
17 | ]
18 | """图片类型"""
19 |
20 | ColorAlias: TypeAlias = str | tuple[int, int, int] | tuple[int, int, int, int] | None
21 |
22 | CenterType = Literal["center", "height", "width"]
23 | """
24 | 粘贴居中
25 |
26 | center: 水平垂直居中
27 |
28 | height: 垂直居中
29 |
30 | width: 水平居中
31 | """
32 |
33 | DEFAULT_FONT = ImageFont.load_default()
34 |
35 |
36 | class BuildImage:
37 | """
38 | 快捷生成图片与操作图片的工具类
39 | """
40 |
41 | def __init__(
42 | self,
43 | width: int = 0,
44 | height: int = 0,
45 | color: ColorAlias = (255, 255, 255),
46 | mode: ModeType = "RGBA",
47 | font: str | Path | FreeTypeFont = "HYWenHei-85W.ttf",
48 | font_size: int = 20,
49 | background: str | BytesIO | Path | bytes | None = None,
50 | ) -> None:
51 | self.uid = uuid.uuid1()
52 | self.width = width
53 | self.height = height
54 | self.color = color
55 | self.font_size = font_size
56 | self.font = self.load_font(font_size)
57 | if background:
58 | if isinstance(background, bytes):
59 | self.markImg = Image.open(BytesIO(background))
60 | else:
61 | self.markImg = Image.open(background)
62 | if width and height:
63 | self.markImg = self.markImg.resize((width, height), Image.LANCZOS)
64 | else:
65 | self.width = self.markImg.width
66 | self.height = self.markImg.height
67 | elif width and height:
68 | self.markImg = Image.new(mode, (width, height), color) # type: ignore
69 | else:
70 | raise ValueError("长度和宽度不能为空...")
71 | self.draw = ImageDraw.Draw(self.markImg)
72 |
73 | @property
74 | def size(self) -> tuple[int, int]:
75 | return self.markImg.size
76 |
77 | @classmethod
78 | def open(cls, path: str | Path | bytes) -> Self:
79 | """打开图片
80 |
81 | 参数:
82 | path: 图片路径
83 |
84 | 返回:
85 | Self: BuildImage
86 | """
87 | return cls(background=path)
88 |
89 | @classmethod
90 | async def build_text_image(
91 | cls,
92 | text: str,
93 | font: str | FreeTypeFont | Path = "HYWenHei-85W.ttf",
94 | size: int = 10,
95 | font_color: str | tuple[int, int, int] = (0, 0, 0),
96 | color: ColorAlias = None,
97 | padding: int | tuple[int, int, int, int] | None = None,
98 | ) -> Self:
99 | """构建文本图片
100 |
101 | 参数:
102 | text: 文本
103 | font: 字体路径
104 | size: 字体大小
105 | font_color: 字体颜色.
106 | color: 背景颜色
107 | padding: 外边距
108 |
109 | 返回:
110 | Self: Self
111 | """
112 | if not text.strip():
113 | return cls(1, 1)
114 | _font = None
115 | if isinstance(font, FreeTypeFont):
116 | _font = font
117 | elif isinstance(font, str | Path):
118 | _font = cls.load_font(size)
119 | width, height = cls.get_text_size(text, _font)
120 | if isinstance(padding, int):
121 | width += padding * 2
122 | height += padding * 2
123 | elif isinstance(padding, tuple):
124 | width += padding[1] + padding[3]
125 | height += padding[0] + padding[2]
126 | markImg = cls(width, height, color, font=_font)
127 | await markImg.text(
128 | (0, 0), text, fill=font_color, font=_font, center_type="center"
129 | )
130 | return markImg
131 |
132 | @classmethod
133 | async def auto_paste(
134 | cls,
135 | img_list: list[Self | tImage],
136 | row: int,
137 | space: int = 10,
138 | padding: int = 50,
139 | color: ColorAlias = (255, 255, 255),
140 | background: str | BytesIO | Path | None = None,
141 | ) -> Self:
142 | """自动贴图
143 |
144 | 参数:
145 | img_list: 图片列表
146 | row: 一行图片的数量
147 | space: 图片之间的间距.
148 | padding: 外边距.
149 | color: 图片背景颜色.
150 | background: 图片背景图片.
151 |
152 | 返回:
153 | Self: Self
154 | """
155 | if not img_list:
156 | raise ValueError("贴图类别为空...")
157 | width, height = img_list[0].size
158 | background_width = width * row + space * (row - 1) + padding * 2
159 | row_count = math.ceil(len(img_list) / row)
160 | if row_count == 1:
161 | background_width = (
162 | sum(img.width for img in img_list) + space * (row - 1) + padding * 2
163 | )
164 | background_height = height * row_count + space * (row_count - 1) + padding * 2
165 | background_image = cls(
166 | background_width, background_height, color=color, background=background
167 | )
168 | _cur_width, _cur_height = padding, padding
169 | for img in img_list:
170 | await background_image.paste(img, (_cur_width, _cur_height))
171 | _cur_width += space + img.width
172 | if _cur_width + padding >= background_image.width:
173 | _cur_height += space + img.height
174 | _cur_width = padding
175 | return background_image
176 |
177 | @classmethod
178 | def load_font(cls, font_size: int = 10) -> FreeTypeFont:
179 | """加载字体
180 |
181 | 参数:
182 | font: 字体名称
183 | font_size: 字体大小
184 |
185 | 返回:
186 | FreeTypeFont: 字体
187 | """
188 | return ImageFont.truetype(ZxpmConfig.zxpm_font, font_size)
189 |
190 | @overload
191 | @classmethod
192 | def get_text_size(
193 | cls, text: str, font: FreeTypeFont | None = None
194 | ) -> tuple[int, int]: ...
195 |
196 | @overload
197 | @classmethod
198 | def get_text_size(
199 | cls, text: str, font: str | None = None, font_size: int = 10
200 | ) -> tuple[int, int]: ...
201 |
202 | @classmethod
203 | def get_text_size(
204 | cls,
205 | text: str,
206 | font: str | FreeTypeFont | None = "HYWenHei-85W.ttf",
207 | font_size: int = 10,
208 | ) -> tuple[int, int]: # sourcery skip: remove-unnecessary-cast
209 | """获取该字体下文本需要的长宽
210 |
211 | 参数:
212 | text: 文本内容
213 | font: 字体名称或FreeTypeFont
214 | font_size: 字体大小
215 |
216 | 返回:
217 | tuple[int, int]: 长宽
218 | """
219 | _font = font
220 | if font and isinstance(font, str):
221 | _font = cls.load_font(font_size)
222 | temp_image = Image.new("RGB", (1, 1), (255, 255, 255))
223 | draw = ImageDraw.Draw(temp_image)
224 | text_box = draw.textbbox((0, 0), str(text), font=_font) # type: ignore
225 | text_width = text_box[2] - text_box[0]
226 | text_height = text_box[3] - text_box[1]
227 | return text_width, text_height + 10
228 | # return _font.getsize(str(text)) # type: ignore
229 |
230 | def getsize(self, msg: str) -> tuple[int, int]:
231 | # sourcery skip: remove-unnecessary-cast
232 | """
233 | 获取文字在该图片 font_size 下所需要的空间
234 |
235 | 参数:
236 | msg: 文本
237 |
238 | 返回:
239 | tuple[int, int]: 长宽
240 | """
241 | temp_image = Image.new("RGB", (1, 1), (255, 255, 255))
242 | draw = ImageDraw.Draw(temp_image)
243 | text_box = draw.textbbox((0, 0), str(msg), font=self.font)
244 | text_width = text_box[2] - text_box[0]
245 | text_height = text_box[3] - text_box[1]
246 | return text_width, text_height + 10
247 | # return self.font.getsize(msg) # type: ignore
248 |
249 | def __center_xy(
250 | self,
251 | pos: tuple[int, int],
252 | width: int,
253 | height: int,
254 | center_type: CenterType | None,
255 | ) -> tuple[int, int]:
256 | """
257 | 根据居中类型定位xy
258 |
259 | 参数:
260 | pos: 定位
261 | image: image
262 | center_type: 居中类型
263 |
264 | 返回:
265 | tuple[int, int]: 定位
266 | """
267 | # _width, _height = pos
268 | if self.width and self.height:
269 | if center_type == "center":
270 | width = int((self.width - width) / 2)
271 | height = int((self.height - height) / 2)
272 | elif center_type == "width":
273 | width = int((self.width - width) / 2)
274 | height = pos[1]
275 | elif center_type == "height":
276 | width = pos[0]
277 | height = int((self.height - height) / 2)
278 | return width, height
279 |
280 | @run_sync
281 | def paste(
282 | self,
283 | image: Self | tImage,
284 | pos: tuple[int, int] = (0, 0),
285 | center_type: CenterType | None = None,
286 | ) -> Self:
287 | """贴图
288 |
289 | 参数:
290 | image: BuildImage 或 Image
291 | pos: 定位.
292 | center_type: 居中.
293 |
294 | 返回:
295 | BuildImage: Self
296 |
297 | 异常:
298 | ValueError: 居中类型错误
299 | """
300 | if center_type and center_type not in ["center", "height", "width"]:
301 | raise ValueError("center_type must be 'center', 'width' or 'height'")
302 | _image = image
303 | if isinstance(image, BuildImage):
304 | _image = image.markImg
305 | if _image.width and _image.height and center_type:
306 | pos = self.__center_xy(pos, _image.width, _image.height, center_type)
307 | try:
308 | self.markImg.paste(_image, pos, _image) # type: ignore
309 | except ValueError:
310 | self.markImg.paste(_image, pos) # type: ignore
311 | return self
312 |
313 | @run_sync
314 | def text(
315 | self,
316 | pos: tuple[int, int],
317 | text: str,
318 | fill: str | tuple[int, int, int] = (0, 0, 0),
319 | center_type: CenterType | None = None,
320 | font: FreeTypeFont | str | Path | None = None,
321 | font_size: int = 10,
322 | ) -> Self: # sourcery skip: remove-unnecessary-cast
323 | """
324 | 在图片上添加文字
325 |
326 | 参数:
327 | pos: 文字位置
328 | text: 文字内容
329 | fill: 文字颜色.
330 | center_type: 居中类型.
331 | font: 字体.
332 | font_size: 字体大小.
333 |
334 | 返回:
335 | BuildImage: Self
336 |
337 | 异常:
338 | ValueError: 居中类型错误
339 | """
340 | if center_type and center_type not in ["center", "height", "width"]:
341 | raise ValueError("center_type must be 'center', 'width' or 'height'")
342 | max_length_text = ""
343 | sentence = str(text).split("\n")
344 | for x in sentence:
345 | max_length_text = x if len(x) > len(max_length_text) else max_length_text
346 | if font:
347 | if not isinstance(font, FreeTypeFont):
348 | font = self.load_font(font_size)
349 | else:
350 | font = self.font
351 | if center_type:
352 | ttf_w, ttf_h = self.getsize(max_length_text) # type: ignore
353 | # ttf_h = ttf_h * len(sentence)
354 | pos = self.__center_xy(pos, ttf_w, ttf_h, center_type)
355 | self.draw.text(pos, str(text), fill=fill, font=font)
356 | return self
357 |
358 | def show(self):
359 | """
360 | 说明:
361 | 显示图片
362 | """
363 | self.markImg.show()
364 |
365 | def pic2bytes(self) -> bytes:
366 | """获取bytes
367 |
368 | 返回:
369 | bytes: bytes
370 | """
371 | buf = BytesIO()
372 | self.markImg.save(buf, format="PNG")
373 | return buf.getvalue()
374 |
375 | @run_sync
376 | def line(
377 | self,
378 | xy: tuple[int, int, int, int],
379 | fill: tuple[int, int, int] | str = "#D8DEE4",
380 | width: int = 1,
381 | ) -> Self:
382 | """
383 | 画线
384 |
385 | 参数:
386 | xy: 坐标
387 | fill: 填充.
388 | width: 线宽.
389 |
390 | 返回:
391 | BuildImage: Self
392 | """
393 | self.draw.line(xy, fill, width)
394 | return self
395 |
396 | @run_sync
397 | def circle_corner(
398 | self,
399 | radii: int = 30,
400 | point_list: list[Literal["lt", "rt", "lb", "rb"]] | None = None,
401 | ) -> Self:
402 | """
403 | 矩形四角变圆
404 |
405 | 参数:
406 | radii: 半径.
407 | point_list: 需要变化的角.
408 |
409 | 返回:
410 | BuildImage: Self
411 | """
412 | if point_list is None:
413 | point_list = ["lt", "rt", "lb", "rb"]
414 | # 画圆(用于分离4个角)
415 | img = self.markImg.convert("RGBA")
416 | alpha = img.split()[-1]
417 | circle = Image.new("L", (radii * 2, radii * 2), 0)
418 | draw = ImageDraw.Draw(circle)
419 | draw.ellipse((0, 0, radii * 2, radii * 2), fill=255) # 黑色方形内切白色圆形
420 | w, h = img.size
421 | if "lt" in point_list:
422 | alpha.paste(circle.crop((0, 0, radii, radii)), (0, 0))
423 | if "rt" in point_list:
424 | alpha.paste(circle.crop((radii, 0, radii * 2, radii)), (w - radii, 0))
425 | if "lb" in point_list:
426 | alpha.paste(circle.crop((0, radii, radii, radii * 2)), (0, h - radii))
427 | if "rb" in point_list:
428 | alpha.paste(
429 | circle.crop((radii, radii, radii * 2, radii * 2)),
430 | (w - radii, h - radii),
431 | )
432 | img.putalpha(alpha)
433 | self.markImg = img
434 | self.draw = ImageDraw.Draw(self.markImg)
435 | return self
436 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/utils/image_template.py:
--------------------------------------------------------------------------------
1 | import random
2 | from collections.abc import Callable
3 | from io import BytesIO
4 | from pathlib import Path
5 |
6 | from PIL.ImageFont import FreeTypeFont
7 | from pydantic import BaseModel
8 |
9 | from .build_image import BuildImage
10 |
11 |
12 | class RowStyle(BaseModel):
13 | font: FreeTypeFont | str | Path | None = "HYWenHei-85W.ttf"
14 | """字体"""
15 | font_size: int = 20
16 | """字体大小"""
17 | font_color: str | tuple[int, int, int] = (0, 0, 0)
18 | """字体颜色"""
19 |
20 | class Config:
21 | arbitrary_types_allowed = True
22 |
23 |
24 | class ImageTemplate:
25 | color_list = ["#C2CEFE", "#FFA94C", "#3FE6A0", "#D1D4F5"] # noqa: RUF012
26 |
27 | @classmethod
28 | async def hl_page(
29 | cls,
30 | head_text: str,
31 | items: dict[str, str],
32 | row_space: int = 10,
33 | padding: int = 30,
34 | ) -> BuildImage:
35 | """列文档 (如插件帮助)
36 |
37 | 参数:
38 | head_text: 头标签文本
39 | items: 列内容
40 | row_space: 列间距.
41 | padding: 间距.
42 |
43 | 返回:
44 | BuildImage: 图片
45 | """
46 | font = BuildImage.load_font(20)
47 | width, height = BuildImage.get_text_size(head_text, font)
48 | for title, item in items.items():
49 | title_width, title_height = await cls.__get_text_size(title, font)
50 | it_width, it_height = await cls.__get_text_size(item, font)
51 | width = max([width, title_width, it_width])
52 | height += title_height + it_height
53 | width = max([width + padding * 2 + 100, 300])
54 | height = max([height + padding * 2 + 150, 100])
55 | A = BuildImage(width + padding * 2, height + padding * 2, color="#FAF9FE")
56 | top_head = BuildImage(width, 100, color="#FFFFFF", font_size=40)
57 | await top_head.line((0, 1, width, 1), "#C2CEFE", 2)
58 | await top_head.text((15, 20), head_text, "#9FA3B2", "center")
59 | await top_head.circle_corner()
60 | await A.paste(top_head, (0, 20), "width")
61 | _min_width = top_head.width - 60
62 | cur_h = top_head.height + 35 + row_space * len(items)
63 | for title, item in items.items():
64 | title_width, title_height = BuildImage.get_text_size(title, font)
65 | title_background = BuildImage(
66 | title_width + 6, title_height + 10, font=font, color="#C1CDFF"
67 | )
68 | await title_background.text((3, 5), title)
69 | await title_background.circle_corner(5)
70 | _text_width, _text_height = await cls.__get_text_size(item, font)
71 | _width = max([title_background.width, _text_width, _min_width])
72 | text_image = await cls.__build_text_image(
73 | item, _width, _text_height, font, color="#FDFCFA"
74 | )
75 | B = BuildImage(_width + 20, title_height + text_image.height + 40)
76 | await B.paste(title_background, (10, 10))
77 | await B.paste(text_image, (10, 20 + title_background.height))
78 | await B.line((0, 0, 0, B.height), random.choice(cls.color_list))
79 | await A.paste(B, (0, cur_h), "width")
80 | cur_h += B.height + row_space
81 | return A
82 |
83 | @classmethod
84 | async def table_page(
85 | cls,
86 | head_text: str,
87 | tip_text: str | None,
88 | column_name: list[str],
89 | data_list: list[list[str | tuple[Path | BuildImage, int, int]]],
90 | row_space: int = 35,
91 | column_space: int = 30,
92 | padding: int = 5,
93 | text_style: Callable[[str, str], RowStyle] | None = None,
94 | ) -> BuildImage:
95 | """表格页
96 |
97 | 参数:
98 | head_text: 标题文本.
99 | tip_text: 标题注释.
100 | column_name: 表头列表.
101 | data_list: 数据列表.
102 | row_space: 行间距.
103 | column_space: 列间距.
104 | padding: 文本内间距.
105 | text_style: 文本样式.
106 |
107 | 返回:
108 | BuildImage: 表格图片
109 | """
110 | font = BuildImage.load_font(font_size=50)
111 | min_width, _ = BuildImage.get_text_size(head_text, font)
112 | table = await cls.table(
113 | column_name,
114 | data_list,
115 | row_space,
116 | column_space,
117 | padding,
118 | text_style,
119 | )
120 | await table.circle_corner()
121 | table_bk = BuildImage(
122 | max(table.width, min_width) + 100, table.height + 50, "#EAEDF2"
123 | )
124 | await table_bk.paste(table, center_type="center")
125 | height = table_bk.height + 200
126 | background = BuildImage(table_bk.width, height, (255, 255, 255), font_size=50)
127 | await background.paste(table_bk, (0, 200))
128 | await background.text((0, 50), head_text, "#334762", center_type="width")
129 | if tip_text:
130 | text_image = await BuildImage.build_text_image(tip_text, size=22)
131 | await background.paste(text_image, (0, 110), center_type="width")
132 | return background
133 |
134 | @classmethod
135 | async def table(
136 | cls,
137 | column_name: list[str],
138 | data_list: list[list[str | tuple[Path | BuildImage, int, int]]],
139 | row_space: int = 25,
140 | column_space: int = 10,
141 | padding: int = 5,
142 | text_style: Callable[[str, str], RowStyle] | None = None,
143 | ) -> BuildImage:
144 | """表格
145 |
146 | 参数:
147 | column_name: 表头列表
148 | data_list: 数据列表
149 | row_space: 行间距.
150 | column_space: 列间距.
151 | padding: 文本内间距.
152 | text_style: 文本样式.
153 | min_width: 最低宽度
154 |
155 | 返回:
156 | BuildImage: 表格图片
157 | """
158 | font = BuildImage.load_font(20)
159 | column_data = []
160 | for i in range(len(column_name)):
161 | c = []
162 | for lst in data_list:
163 | if len(lst) > i:
164 | c.append(lst[i])
165 | else:
166 | c.append("")
167 | column_data.append(c)
168 | build_data_list = []
169 | _, base_h = BuildImage.get_text_size("A", font)
170 | for i, column_list in enumerate(column_data):
171 | name_width, _ = BuildImage.get_text_size(column_name[i], font)
172 | _temp = {"width": name_width, "data": column_list}
173 | for s in column_list:
174 | if isinstance(s, tuple):
175 | w = s[1]
176 | else:
177 | w, _ = BuildImage.get_text_size(s, font)
178 | if w > _temp["width"]:
179 | _temp["width"] = w
180 | build_data_list.append(_temp)
181 | column_image_list = []
182 | column_name_image_list: list[BuildImage] = []
183 | for i, data in enumerate(build_data_list):
184 | column_name_image = await BuildImage.build_text_image(
185 | column_name[i], font, 12, "#C8CCCF"
186 | )
187 | column_name_image_list.append(column_name_image)
188 | max_h = max(c.height for c in column_name_image_list)
189 | for i, data in enumerate(build_data_list):
190 | width = data["width"] + padding * 2
191 | height = (base_h + row_space) * (len(data["data"]) + 1) + padding * 2
192 | background = BuildImage(width, height, (255, 255, 255))
193 | column_name_image = column_name_image_list[i]
194 | await background.paste(column_name_image, (0, 20), center_type="width")
195 | cur_h = max_h + row_space + 20
196 | for item in data["data"]:
197 | style = RowStyle(font=font)
198 | if text_style:
199 | style = text_style(column_name[i], item)
200 | if isinstance(item, tuple):
201 | """图片"""
202 | data, width, height = item
203 | image_ = None
204 | if isinstance(data, Path):
205 | image_ = BuildImage(width, height, background=data)
206 | elif isinstance(data, bytes):
207 | image_ = BuildImage(width, height, background=BytesIO(data))
208 | elif isinstance(data, BuildImage):
209 | image_ = data
210 | if image_:
211 | await background.paste(image_, (padding, cur_h))
212 | else:
213 | await background.text(
214 | (padding, cur_h),
215 | item if item is not None else "",
216 | style.font_color,
217 | font=style.font,
218 | font_size=style.font_size,
219 | )
220 | cur_h += base_h + row_space
221 | column_image_list.append(background)
222 | # height = max([bk.height for bk in column_image_list])
223 | # width = sum([bk.width for bk in column_image_list])
224 | return await BuildImage.auto_paste(
225 | column_image_list, len(column_image_list), column_space
226 | )
227 |
228 | @classmethod
229 | async def __build_text_image(
230 | cls,
231 | text: str,
232 | width: int,
233 | height: int,
234 | font: FreeTypeFont,
235 | font_color: str | tuple[int, int, int] = (0, 0, 0),
236 | color: str | tuple[int, int, int] = (255, 255, 255),
237 | ) -> BuildImage:
238 | """文本转图片
239 |
240 | 参数:
241 | text: 文本
242 | width: 宽度
243 | height: 长度
244 | font: 字体
245 | font_color: 文本颜色
246 | color: 背景颜色
247 |
248 | 返回:
249 | BuildImage: 文本转图片
250 | """
251 | _, h = BuildImage.get_text_size("A", font)
252 | A = BuildImage(width, height, color=color)
253 | cur_h = 0
254 | for s in text.split("\n"):
255 | text_image = await BuildImage.build_text_image(
256 | s, font, font_color=font_color
257 | )
258 | await A.paste(text_image, (0, cur_h))
259 | cur_h += h
260 | return A
261 |
262 | @classmethod
263 | async def __get_text_size(
264 | cls,
265 | text: str,
266 | font: FreeTypeFont,
267 | ) -> tuple[int, int]:
268 | """获取文本所占大小
269 |
270 | 参数:
271 | text: 文本
272 | font: 字体
273 |
274 | 返回:
275 | tuple[int, int]: 宽, 高
276 | """
277 | width = 0
278 | height = 0
279 | _, h = BuildImage.get_text_size("A", font)
280 | for s in text.split("\n"):
281 | s = s.strip() or "A"
282 | w, _ = BuildImage.get_text_size(s, font)
283 | width = max(width, w)
284 | height += h
285 | return width, height
286 |
--------------------------------------------------------------------------------
/nonebot_plugin_zxpm/utils/utils.py:
--------------------------------------------------------------------------------
1 | from io import BytesIO
2 | from pathlib import Path
3 |
4 | import nonebot
5 | from nonebot_plugin_alconna import At, AtAll, Image, Text, UniMessage, Video, Voice
6 | from pydantic import BaseModel
7 |
8 | from ..log import logger
9 |
10 | driver = nonebot.get_driver()
11 |
12 | MESSAGE_TYPE = (
13 | str
14 | | int
15 | | float
16 | | Path
17 | | bytes
18 | | BytesIO
19 | | At
20 | | AtAll
21 | | Image
22 | | Text
23 | | Voice
24 | | Video
25 | )
26 |
27 |
28 | class Config(BaseModel):
29 | image_to_bytes: bool = False
30 |
31 |
32 | class MessageUtils:
33 | @classmethod
34 | def __build_message(cls, msg_list: list[MESSAGE_TYPE]) -> list[Text | Image]:
35 | """构造消息
36 |
37 | 参数:
38 | msg_list: 消息列表
39 |
40 | 返回:
41 | list[Text | Text]: 构造完成的消息列表
42 | """
43 | message_list = []
44 | for msg in msg_list:
45 | if isinstance(msg, Image | Text | At | AtAll | Video | Voice):
46 | message_list.append(msg)
47 | elif isinstance(msg, str | int | float):
48 | message_list.append(Text(str(msg)))
49 | elif isinstance(msg, Path):
50 | if msg.exists():
51 | message_list.append(Image(path=msg))
52 | else:
53 | logger.warning(f"图片路径不存在: {msg}")
54 | elif isinstance(msg, bytes):
55 | message_list.append(Image(raw=msg))
56 | elif isinstance(msg, BytesIO):
57 | message_list.append(Image(raw=msg))
58 | return message_list
59 |
60 | @classmethod
61 | def build_message(
62 | cls, msg_list: MESSAGE_TYPE | list[MESSAGE_TYPE | list[MESSAGE_TYPE]]
63 | ) -> UniMessage:
64 | """构造消息
65 |
66 | 参数:
67 | msg_list: 消息列表
68 |
69 | 返回:
70 | UniMessage: 构造完成的消息列表
71 | """
72 | message_list = []
73 | if not isinstance(msg_list, list):
74 | msg_list = [msg_list]
75 | for m in msg_list:
76 | _data = m if isinstance(m, list) else [m]
77 | message_list += cls.__build_message(_data) # type: ignore
78 | return UniMessage(message_list)
79 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "nonebot-plugin-zxpm"
3 | version = "0.2.7"
4 | description = "真寻的插件管理系统"
5 | authors = ["HibiKier <775757368@qq.com>"]
6 | license = "AGPL3.0"
7 | readme = "README.md"
8 | homepage = "https://github.com/HibiKier/nonebot-plugin-zxpm"
9 | repository = "https://github.com/HibiKier/nonebot-plugin-zxpm"
10 |
11 | [tool.poetry.dependencies]
12 | python = "^3.10"
13 | nonebot2 = "^2.3.3"
14 | nonebot-plugin-alconna = ">=0.46.3,<1.0.0"
15 | nonebot-plugin-session = ">=0.3.2"
16 | ruamel-yaml = "^0.18.6"
17 | strenum = "^0.4.15"
18 | nonebot-plugin-uninfo = ">=0.6.0"
19 | zhenxun-utils = "^0.2.2"
20 | pytz = "^2025.1"
21 | tortoise-orm = "^0.20"
22 |
23 |
24 | [tool.black]
25 | line-length = 88
26 | target-version = ["py39", "py310", "py311", "py312"]
27 | include = '\.pyi?$'
28 | extend-exclude = '''
29 | '''
30 |
31 | [tool.ruff]
32 | line-length = 88
33 | target-version = "py310"
34 |
35 | [tool.ruff.lint]
36 | select = [
37 | "F", # Pyflakes
38 | "W", # pycodestyle warnings
39 | "E", # pycodestyle errors
40 | "UP", # pyupgrade
41 | "ASYNC", # flake8-async
42 | "C4", # flake8-comprehensions
43 | "T10", # flake8-debugger
44 | "T20", # flake8-print
45 | "PYI", # flake8-pyi
46 | "PT", # flake8-pytest-style
47 | "Q", # flake8-quotes
48 | "RUF", # Ruff-specific rules
49 | ]
50 | ignore = [
51 | "E402", # module-import-not-at-top-of-file
52 | "UP037", # quoted-annotation
53 | "RUF001", # ambiguous-unicode-character-string
54 | "RUF002", # ambiguous-unicode-character-docstring
55 | "RUF003", # ambiguous-unicode-character-comment
56 | ]
57 |
58 | [tool.ruff.lint.flake8-pytest-style]
59 | fixture-parentheses = false
60 | mark-parentheses = false
61 |
62 | [tool.pyright]
63 | pythonVersion = "3.10"
64 | pythonPlatform = "All"
65 | defineConstant = { PYDANTIC_V2 = true }
66 | executionEnvironments = [
67 | { root = "./tests", extraPaths = [
68 | "./",
69 | ] },
70 | { root = "./" },
71 | ]
72 |
73 | typeCheckingMode = "standard"
74 | reportShadowedImports = false
75 | disableBytesTypePromotions = true
76 |
77 | [tool.pytest.ini_options]
78 | asyncio_mode = "auto"
79 |
80 | [build-system]
81 | requires = ["poetry-core>=1.0.0"]
82 | build-backend = "poetry.core.masonry.api"
83 |
--------------------------------------------------------------------------------