nonebot-plugin-onebot-luckperms 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nonebot_plugin_onebot_luckperms-0.1.0/LICENSE +21 -0
- nonebot_plugin_onebot_luckperms-0.1.0/PKG-INFO +23 -0
- nonebot_plugin_onebot_luckperms-0.1.0/README.md +483 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/__init__.py +127 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/adapter/__init__.py +17 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/adapter/context.py +31 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/adapter/identity.py +64 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/adapter/permission.py +157 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/commands/__init__.py +3 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/commands/admin.py +857 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/config.py +20 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/core/__init__.py +20 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/core/cache.py +44 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/core/context_provider.py +74 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/core/engine.py +162 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/core/exceptions.py +6 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/core/models.py +123 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/core/registry.py +72 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/message.py +126 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/py.typed +0 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/storage/__init__.py +29 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/storage/memory.py +46 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/storage/protocol.py +21 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/storage/redis.py +162 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/storage/sqlite.py +189 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/webeditor/__init__.py +9 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/webeditor/bytebin.py +71 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/webeditor/manager.py +228 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/webeditor/session.py +109 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms/webeditor/websocket.py +180 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms.egg-info/PKG-INFO +23 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms.egg-info/SOURCES.txt +40 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms.egg-info/dependency_links.txt +1 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms.egg-info/requires.txt +17 -0
- nonebot_plugin_onebot_luckperms-0.1.0/nonebot_plugin_onebot_luckperms.egg-info/top_level.txt +1 -0
- nonebot_plugin_onebot_luckperms-0.1.0/pyproject.toml +39 -0
- nonebot_plugin_onebot_luckperms-0.1.0/setup.cfg +4 -0
- nonebot_plugin_onebot_luckperms-0.1.0/tests/test_borderline.py +125 -0
- nonebot_plugin_onebot_luckperms-0.1.0/tests/test_context.py +189 -0
- nonebot_plugin_onebot_luckperms-0.1.0/tests/test_models.py +205 -0
- nonebot_plugin_onebot_luckperms-0.1.0/tests/test_storage.py +99 -0
- nonebot_plugin_onebot_luckperms-0.1.0/tests/test_webeditor.py +180 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 WisTex TechSero Ltd. Co.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nonebot-plugin-onebot-luckperms
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LuckPerms-style permission system for NoneBot2 + OneBot
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/XChen446/nonebot-plugin-onebot-luckperms
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: nonebot2>=2.5.0
|
|
10
|
+
Requires-Dist: nonebot-adapter-onebot>=2.4.6
|
|
11
|
+
Requires-Dist: pydantic>=2.13.4
|
|
12
|
+
Requires-Dist: aiohttp>=3.14.1
|
|
13
|
+
Requires-Dist: pyyaml>=6.0
|
|
14
|
+
Requires-Dist: aiosqlite>=0.22.1
|
|
15
|
+
Provides-Extra: redis
|
|
16
|
+
Requires-Dist: redis>=8.0; extra == "redis"
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest>=9.0; extra == "test"
|
|
19
|
+
Requires-Dist: pytest-asyncio>=1.4; extra == "test"
|
|
20
|
+
Provides-Extra: all
|
|
21
|
+
Requires-Dist: nonebot-plugin-onebot-luckperms[redis]; extra == "all"
|
|
22
|
+
Requires-Dist: nonebot-plugin-onebot-luckperms[test]; extra == "all"
|
|
23
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
# nonebot-plugin-onebot-luckperms(OBLP)
|
|
2
|
+
|
|
3
|
+
LuckPerms 风格的权限节点管理系统,专为 NoneBot2 + OneBot 适配器设计。
|
|
4
|
+
|
|
5
|
+
> English: [README_EN.md](./README_EN.md)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 目录
|
|
10
|
+
|
|
11
|
+
- [安装](#安装)
|
|
12
|
+
- [快速开始](#快速开始)
|
|
13
|
+
- [配置](#配置)
|
|
14
|
+
- [命令列表](#命令列表)
|
|
15
|
+
- [内建 Context 键](#内建-context-键)
|
|
16
|
+
- [权限节点注册(面向开发者)](#权限节点注册面向开发者)
|
|
17
|
+
- [ContextProvider(面向开发者)](#contextprovider面向开发者)
|
|
18
|
+
- [API 参考](#api-参考)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 安装
|
|
23
|
+
|
|
24
|
+
### 通过 NB-CLI(推荐)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
nb plugin install nonebot-plugin-onebot-luckperms
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 通过 pip
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install nonebot-plugin-onebot-luckperms
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
如需 Redis 后端:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install nonebot-plugin-onebot-luckperms[redis]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
全部安装:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install nonebot-plugin-onebot-luckperms[all]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 快速开始
|
|
51
|
+
|
|
52
|
+
在 NoneBot2 的 `pyproject.toml` 或 `.env` 中添加插件:
|
|
53
|
+
|
|
54
|
+
```toml
|
|
55
|
+
[tool.nonebot]
|
|
56
|
+
plugins = ["nonebot_plugin_onebot_luckperms"]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
插件加载后,所有 `/lp` 命令默认仅 **SUPERUSER**(在 `env` 中配置 `SUPERUSER=123456`)可执行。
|
|
60
|
+
|
|
61
|
+
### 给用户一个权限
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
/lp user 123456 permission set myplugin.ban true
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 创建一个组并分配权限
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
/lp creategroup admin 100
|
|
71
|
+
/lp group admin permission set myplugin.kick true
|
|
72
|
+
/lp user 123456 parent add admin
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 设置仅在特定群生效的权限
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
/lp user 123456 permission set myplugin.mute true group_id=987654
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 临时权限(30 分钟后自动过期)
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
/lp user 123456 permission settemp myplugin.ban true 30m
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 打开 Web Editor
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
/lp editor
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
在群聊中打开时,editor 会自动附带该群所有成员 QQ 到用户列表。
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 配置
|
|
98
|
+
|
|
99
|
+
在 `.env` 或 `pyproject.toml` 中(前缀 `OBLP_`):
|
|
100
|
+
|
|
101
|
+
| 环境变量 | 默认值 | 说明 |
|
|
102
|
+
|---------|--------|------|
|
|
103
|
+
| `OBLP_STORE_TYPE` | `sqlite` | 存储后端:`memory` / `sqlite` / `redis` |
|
|
104
|
+
| `OBLP_SQLITE_PATH` | `./data/oblp/permissions.db` | SQLite 文件路径 |
|
|
105
|
+
| `OBLP_REDIS_URL` | `redis://localhost:6379/0` | Redis 连接 URL |
|
|
106
|
+
| `OBLP_DEFAULT_GROUP_OWNER` | `["luckperms.help"]` | 群主自动继承的权限节点 |
|
|
107
|
+
| `OBLP_DEFAULT_GROUP_ADMIN` | `[]` | 群管理自动继承的权限节点 |
|
|
108
|
+
| `OBLP_DEFAULT_GROUP_MEMBER` | `[]` | 普通成员自动继承的权限节点 |
|
|
109
|
+
| `OBLP_SUPERUSER_INHERIT` | `["luckperms.*"]` | SUPERUSER 自动继承的权限节点 |
|
|
110
|
+
| `OBLP_CACHE_TTL` | `300` | 权限缓存时间(秒),`0` 禁用缓存 |
|
|
111
|
+
| `OBLP_MESSAGE_FILE` | `./data/oblp/messages.yml` | 自定义回复消息的 YAML 文件路径 |
|
|
112
|
+
| `OBLP_DEBUG_MODE` | `false` | 开启后打印每次权限判定日志 |
|
|
113
|
+
|
|
114
|
+
### 配置示例
|
|
115
|
+
|
|
116
|
+
```ini
|
|
117
|
+
SUPERUSER=123456
|
|
118
|
+
OBLP_STORE_TYPE=sqlite
|
|
119
|
+
OBLP_SQLITE_PATH=./data/oblp/permissions.db
|
|
120
|
+
OBLP_DEFAULT_GROUP_ADMIN=["myplugin.mute","myplugin.kick"]
|
|
121
|
+
OBLP_DEFAULT_GROUP_MEMBER=[]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 命令列表
|
|
127
|
+
|
|
128
|
+
所有命令均以 `/lp` 开头。每个命令都受对应的 `luckperms.*` 权限节点保护。
|
|
129
|
+
|
|
130
|
+
SUPERUSER 默认拥有 `luckperms.*`(全部权限)。群主默认拥有 `luckperms.help`(仅能看帮助)。
|
|
131
|
+
|
|
132
|
+
### 通用命令
|
|
133
|
+
|
|
134
|
+
| 命令 | 所需权限节点 | 说明 |
|
|
135
|
+
|------|-------------|------|
|
|
136
|
+
| `/lp help` | `luckperms.help` | 显示帮助 |
|
|
137
|
+
| `/lp sync` | `luckperms.sync` | 重新加载数据 |
|
|
138
|
+
| `/lp info` | `luckperms.info` | 显示统计信息 |
|
|
139
|
+
| `/lp editor` | `luckperms.editor` | 打开 LuckPerms Web Editor |
|
|
140
|
+
| `/lp applyedits <code>` | `luckperms.applyedits` | 应用 Web Editor 的 edits |
|
|
141
|
+
|
|
142
|
+
### 用户管理
|
|
143
|
+
|
|
144
|
+
| 命令 | 所需权限节点 | 说明 |
|
|
145
|
+
|------|-------------|------|
|
|
146
|
+
| `/lp user <id> info` | `luckperms.user.info` | 查看用户详情 |
|
|
147
|
+
| `/lp user <id> permission ...` | `luckperms.user.permission` | 管理用户权限节点 |
|
|
148
|
+
| `/lp user <id> parent ...` | `luckperms.user.parent` | 管理用户组 |
|
|
149
|
+
| `/lp user <id> promote <track>` | `luckperms.user.promote` | 晋升 |
|
|
150
|
+
| `/lp user <id> demote <track>` | `luckperms.user.demote` | 降级 |
|
|
151
|
+
| `/lp user <id> clear [ctx...]` | `luckperms.user.clear` | 清空所有权限节点 |
|
|
152
|
+
| `/lp user <id> clone <target>` | `luckperms.user.clone` | 克隆用户 |
|
|
153
|
+
| `/lp user <id> editor` | `luckperms.editor` | 在 editor 中编辑此用户 |
|
|
154
|
+
| `/lp user create <id>` | `luckperms.user.create` | 创建用户 |
|
|
155
|
+
| `/lp user delete <id>` | `luckperms.user.delete` | 删除用户 |
|
|
156
|
+
| `/lp user list` | `luckperms.user.list` | 列出所有用户 |
|
|
157
|
+
|
|
158
|
+
### 组管理
|
|
159
|
+
|
|
160
|
+
| 命令 | 所需权限节点 | 说明 |
|
|
161
|
+
|------|-------------|------|
|
|
162
|
+
| `/lp group <name> info` | `luckperms.group.info` | 查看组详情 |
|
|
163
|
+
| `/lp group <name> permission ...` | `luckperms.group.permission` | 管理组权限 |
|
|
164
|
+
| `/lp group <name> parent ...` | `luckperms.group.parent` | 继承管理 |
|
|
165
|
+
| `/lp group <name> setweight <weight>` | `luckperms.group.setweight` | 设置权重 |
|
|
166
|
+
| `/lp group <name> setdisplayname <name>` | `luckperms.group.setdisplayname` | 设置显示名 |
|
|
167
|
+
| `/lp group <name> clear [ctx...]` | `luckperms.group.clear` | 清空权限 |
|
|
168
|
+
| `/lp group <name> rename <new>` | `luckperms.group.rename` | 重命名组 |
|
|
169
|
+
| `/lp group <name> clone <new>` | `luckperms.group.clone` | 克隆组 |
|
|
170
|
+
| `/lp group <name> listmembers` | `luckperms.group.listmembers` | 列出组成员 |
|
|
171
|
+
| `/lp group <name> editor` | `luckperms.editor` | 在 editor 中编辑此组 |
|
|
172
|
+
| `/lp creategroup <name> [weight]` | `luckperms.group.create` | 创建组 |
|
|
173
|
+
| `/lp deletegroup <name>` | `luckperms.group.delete` | 删除组 |
|
|
174
|
+
| `/lp listgroups` | `luckperms.group.list` | 列出所有组 |
|
|
175
|
+
|
|
176
|
+
### 轨道管理(预留)
|
|
177
|
+
|
|
178
|
+
| 命令 | 所需权限节点 | 说明 |
|
|
179
|
+
|------|-------------|------|
|
|
180
|
+
| `/lp track <name> ...` | `luckperms.*` | 轨道操作 |
|
|
181
|
+
|
|
182
|
+
### 系统命令
|
|
183
|
+
|
|
184
|
+
| 命令 | 所需权限节点 | 说明 |
|
|
185
|
+
|------|-------------|------|
|
|
186
|
+
| `/lp check <user> <node> [ctx...]` | `luckperms.check` | 模拟权限检查 |
|
|
187
|
+
| `/lp tree [scope]` | `luckperms.tree` | 权限继承树可视化 |
|
|
188
|
+
|
|
189
|
+
### 命令执行反馈
|
|
190
|
+
|
|
191
|
+
无权限时(用户没有所需的 `luckperms.xxx` 节点),bot 回复 `deny` 消息。
|
|
192
|
+
|
|
193
|
+
### 自定义回复消息
|
|
194
|
+
|
|
195
|
+
所有用户可见的回复文本均存储在 `OBLP_MESSAGE_FILE` 指向的 YAML 文件中(默认 `./data/oblp/messages.yml`)。
|
|
196
|
+
插件首次运行时会自动生成此文件。你可以编辑它来自定义所有回复,支持占位符:
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
# data/oblp/messages.yml
|
|
200
|
+
deny: "你没有权限执行此命令。"
|
|
201
|
+
deny_hint: "请联系管理员开通对应权限节点。"
|
|
202
|
+
perm_set: "{entity}: {action} {node}"
|
|
203
|
+
perm_temp: "{entity}: 临时{action} {node} ({duration})"
|
|
204
|
+
editor_opened: "Web 编辑器已开启: {url}"
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
可用占位符:
|
|
208
|
+
| 占位符 | 说明 |
|
|
209
|
+
|--------|------|
|
|
210
|
+
| `{user_id}` | 用户 ID |
|
|
211
|
+
| `{node}` | 权限节点名 |
|
|
212
|
+
| `{entity}` | 用户或组 ID |
|
|
213
|
+
| `{action}` | grant 或 deny |
|
|
214
|
+
| `{duration}` | 持续时间 |
|
|
215
|
+
| `{context}` | 上下文键值对 |
|
|
216
|
+
| `{url}` | Editor URL |
|
|
217
|
+
| `{code}` | Edit code |
|
|
218
|
+
| `{error}` | 错误描述 |
|
|
219
|
+
| `{name}` | 组名 |
|
|
220
|
+
| `{weight}` | 组权重 |
|
|
221
|
+
|
|
222
|
+
有权限时的执行反馈示例:
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
/lp sync
|
|
226
|
+
→ Data reloaded
|
|
227
|
+
|
|
228
|
+
/lp user 123456 permission set myplugin.ban true
|
|
229
|
+
→ 123456: grant myplugin.ban
|
|
230
|
+
|
|
231
|
+
/lp user 123456 permission settemp myplugin.kick false 30m
|
|
232
|
+
→ 123456: temp deny myplugin.kick (30m)
|
|
233
|
+
|
|
234
|
+
/lp group admin permission set myplugin.mute true group_id=987654
|
|
235
|
+
→ admin: grant myplugin.mute (ctx: {'group_id': '987654'})
|
|
236
|
+
|
|
237
|
+
/lp check 123456 myplugin.ban
|
|
238
|
+
→ Check: user=123456, node=myplugin.ban
|
|
239
|
+
Context: (empty)
|
|
240
|
+
Result: ALLOW
|
|
241
|
+
Resolved nodes (2):
|
|
242
|
+
myplugin.ban=grant
|
|
243
|
+
luckperms.*=grant
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 默认角色权限
|
|
247
|
+
|
|
248
|
+
| 角色 | 默认拥有的节点 | 说明 |
|
|
249
|
+
|------|--------------|------|
|
|
250
|
+
| SUPERUSER | `luckperms.*` | 全部命令 |
|
|
251
|
+
| 群主 (owner) | `luckperms.help` | 仅可看帮助(可配置) |
|
|
252
|
+
| 管理 (admin) | *(空)* | 无内建命令权限(可配置) |
|
|
253
|
+
| 成员 (member) | *(空)* | 无内建命令权限(可配置) |
|
|
254
|
+
|
|
255
|
+
可通过 `OBLP_DEFAULT_GROUP_OWNER`、`OBLP_DEFAULT_GROUP_ADMIN`、`OBLP_DEFAULT_GROUP_MEMBER` 配置。
|
|
256
|
+
|
|
257
|
+
### 临时权限 duration 格式
|
|
258
|
+
|
|
259
|
+
| 写法 | 含义 |
|
|
260
|
+
|------|------|
|
|
261
|
+
| `30s` | 30 秒 |
|
|
262
|
+
| `5m` | 5 分钟 |
|
|
263
|
+
| `2h` | 2 小时 |
|
|
264
|
+
| `1d` | 1 天 |
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 内建 Context 键
|
|
269
|
+
|
|
270
|
+
权限节点可以通过 `key=value` 附加上下文约束,使节点仅在特定环境中生效。由系统自动注入:
|
|
271
|
+
|
|
272
|
+
| Context 键 | 类型 | 来源 | 示例值 |
|
|
273
|
+
|-----------|------|---------|--------|
|
|
274
|
+
| `platform` | `str` | 适配器类型 | `onebot_v11`, `onebot_v12` |
|
|
275
|
+
| `user_id` | `str` | 发送者 ID | `123456` |
|
|
276
|
+
| `group_id` | `str` | 群号(群聊时) | `987654` |
|
|
277
|
+
| `role` | `str` | 群内角色 | `owner`, `admin`, `member`, `superuser` |
|
|
278
|
+
|
|
279
|
+
第三方插件可通过 ContextProvider(见下文)注册自定义 Context 键。
|
|
280
|
+
|
|
281
|
+
### Context 使用示例
|
|
282
|
+
|
|
283
|
+
仅群 123456 中有效:
|
|
284
|
+
|
|
285
|
+
```text
|
|
286
|
+
/lp user 123456 permission set myplugin.ban true group_id=123456
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
仅群管理时有效:
|
|
290
|
+
|
|
291
|
+
```text
|
|
292
|
+
/lp user 123456 permission set myplugin.kick true role=admin
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
仅群 123456 且为群主时有效:
|
|
296
|
+
|
|
297
|
+
```text
|
|
298
|
+
/lp group admin permission set myplugin.ban true group_id=123456 role=owner
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## 权限节点注册(面向开发者)
|
|
304
|
+
|
|
305
|
+
### 基本用法
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
from nonebot import on_command
|
|
309
|
+
from nonebot_plugin_onebot_luckperms import register_node, require
|
|
310
|
+
|
|
311
|
+
# 在插件加载时注册节点
|
|
312
|
+
register_node("myplugin.ban", "禁言用户", default=False)
|
|
313
|
+
|
|
314
|
+
# 绑定到命令
|
|
315
|
+
ban = on_command("ban", permission=require("myplugin.ban"))
|
|
316
|
+
|
|
317
|
+
@ban.handle()
|
|
318
|
+
async def _():
|
|
319
|
+
await ban.send("已执行")
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### 带默认上下文的注册
|
|
323
|
+
|
|
324
|
+
```python
|
|
325
|
+
from nonebot_plugin_onebot_luckperms import register_node, ContextSet
|
|
326
|
+
|
|
327
|
+
# 仅群主默认拥有此权限
|
|
328
|
+
register_node("myplugin.admincmd", "管理命令", default=True,
|
|
329
|
+
contexts=ContextSet({"role": "owner"}))
|
|
330
|
+
|
|
331
|
+
# 仅在特定群 + admin 时默认拥有
|
|
332
|
+
register_node("myplugin.vipcmd", "VIP 命令", default=True,
|
|
333
|
+
contexts=ContextSet({"group_id": "123456", "role": "admin"}))
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### require / require_any / require_all
|
|
337
|
+
|
|
338
|
+
```python
|
|
339
|
+
from nonebot_plugin_onebot_luckperms import require, require_any, require_all
|
|
340
|
+
from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN
|
|
341
|
+
|
|
342
|
+
# 单节点
|
|
343
|
+
cmd = on_command("cmd", permission=require("myplugin.cmd"))
|
|
344
|
+
|
|
345
|
+
# 多节点 OR
|
|
346
|
+
cmd = on_command("cmd", permission=require_any("node.a", "node.b"))
|
|
347
|
+
|
|
348
|
+
# 多节点 AND
|
|
349
|
+
cmd = on_command("cmd", permission=require_all("node.a", "node.b"))
|
|
350
|
+
|
|
351
|
+
# 与原生权限组合
|
|
352
|
+
cmd = on_command("cmd", permission=require("myplugin.cmd") | GROUP_ADMIN)
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### 获取权限上下文
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
from nonebot_plugin_onebot_luckperms import get_context
|
|
359
|
+
|
|
360
|
+
@cmd.handle()
|
|
361
|
+
async def _():
|
|
362
|
+
ctx = get_context()
|
|
363
|
+
if not ctx:
|
|
364
|
+
return
|
|
365
|
+
print(ctx.user.user_id) # "123456"
|
|
366
|
+
print(ctx.identity.role) # "admin"
|
|
367
|
+
print(ctx.resolved_nodes) # {"myplugin.ban": True, ...}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## ContextProvider(面向开发者)
|
|
373
|
+
|
|
374
|
+
ContextProvider 允许第三方插件向全局权限判定环境注入自定义 Context 键值对。
|
|
375
|
+
|
|
376
|
+
### 定义并注册
|
|
377
|
+
|
|
378
|
+
```python
|
|
379
|
+
from nonebot_plugin_onebot_luckperms import register_context_provider
|
|
380
|
+
|
|
381
|
+
class BindProvider:
|
|
382
|
+
context_keys = {"bind"} # 声明此 provider 会注入的键
|
|
383
|
+
|
|
384
|
+
async def __call__(self, bot, event, current_ctx):
|
|
385
|
+
# 从数据库或 API 查询用户的绑定状态
|
|
386
|
+
return {"bind": "true"} # 键值对会合并到判定上下文中
|
|
387
|
+
|
|
388
|
+
register_context_provider(BindProvider())
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
注册后,其他所有插件都可以利用这个 Context 来控制权限生效范围:
|
|
392
|
+
|
|
393
|
+
```python
|
|
394
|
+
register_node("someplugin.premium", default=True,
|
|
395
|
+
contexts=ContextSet({"bind": "true"}))
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### 冲突检测
|
|
399
|
+
|
|
400
|
+
如果两个不同的 Provider 声明了相同的 `context_keys`,后者注册时会抛出 `DuplicateProviderError`:
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
ProviderA: context_keys = {"bind"} # 注册成功
|
|
404
|
+
ProviderB: context_keys = {"bind"} # → DuplicateProviderError!
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## API 参考
|
|
410
|
+
|
|
411
|
+
### 用户端函数
|
|
412
|
+
|
|
413
|
+
| 函数 | 说明 |
|
|
414
|
+
|------|------|
|
|
415
|
+
| `require(node_key, contexts=None) -> Permission` | 单节点权限检查器 |
|
|
416
|
+
| `require_any(*node_keys, contexts=None) -> Permission` | OR 权限检查器 |
|
|
417
|
+
| `require_all(*node_keys, contexts=None) -> Permission` | AND 权限检查器 |
|
|
418
|
+
| `get_context() -> Optional[LPContext]` | 在 Handler 中获取权限上下文 |
|
|
419
|
+
| `get_store() -> Optional[PermissionStore]` | 获取当前存储后端 |
|
|
420
|
+
|
|
421
|
+
### 开发者端函数
|
|
422
|
+
|
|
423
|
+
| 函数 | 说明 |
|
|
424
|
+
|------|------|
|
|
425
|
+
| `register_node(key, description="", default=False, contexts=None)` | 注册权限节点 |
|
|
426
|
+
| `register_context_provider(provider)` | 注册全局 ContextProvider |
|
|
427
|
+
| `set_resolver(resolver)` | 覆盖 IdentityResolver |
|
|
428
|
+
| `ContextSet(data={})` | 上下文集合 |
|
|
429
|
+
| `PermissionNode(key, value=True, expiry=None, contexts=ContextSet())` | 权限节点 |
|
|
430
|
+
|
|
431
|
+
### 模型类
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
@dataclass(frozen=True)
|
|
435
|
+
class ContextSet:
|
|
436
|
+
data: Dict[str, str]
|
|
437
|
+
def with_context(self, key, value) -> ContextSet
|
|
438
|
+
def matches(self, requirement) -> bool
|
|
439
|
+
def is_empty(self) -> bool
|
|
440
|
+
|
|
441
|
+
@dataclass
|
|
442
|
+
class PermissionNode:
|
|
443
|
+
key: str
|
|
444
|
+
value: bool
|
|
445
|
+
expiry: Optional[int]
|
|
446
|
+
contexts: ContextSet
|
|
447
|
+
def is_expired(self) -> bool
|
|
448
|
+
def applies_in(self, ctx) -> bool
|
|
449
|
+
|
|
450
|
+
@dataclass(frozen=True)
|
|
451
|
+
class LPContext:
|
|
452
|
+
user: User
|
|
453
|
+
identity: Identity
|
|
454
|
+
query_options: QueryOptions
|
|
455
|
+
resolved_nodes: Dict[str, bool]
|
|
456
|
+
matched_node: Optional[str]
|
|
457
|
+
timestamp: float
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## 测试
|
|
463
|
+
|
|
464
|
+
```bash
|
|
465
|
+
pytest tests/ -v
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
当前 91 项测试全部通过。
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## 架构
|
|
473
|
+
|
|
474
|
+
```
|
|
475
|
+
Layer 4: NoneBot Integration (adapter/) — require(), IdentityResolver, LPContext
|
|
476
|
+
Layer 3: Core Engine (core/) — 纯 Python,不依赖 NoneBot
|
|
477
|
+
Layer 2: Storage (storage/) — Memory / SQLite / Redis
|
|
478
|
+
Layer 1: Config & Bootstrap (config/) — Pydantic 配置
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## License
|
|
482
|
+
|
|
483
|
+
MIT
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from nonebot.plugin import PluginMetadata
|
|
4
|
+
|
|
5
|
+
from .core.registry import register_node
|
|
6
|
+
from .core.models import ContextSet, PermissionNode
|
|
7
|
+
from .core.context_provider import register_context_provider, ContextProvider
|
|
8
|
+
from .storage import get_store
|
|
9
|
+
from .adapter import set_resolver, require, require_any, require_all, get_context
|
|
10
|
+
from .adapter.context import LPContext
|
|
11
|
+
from .config import OBLPConfig, oblp_config
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("oblp")
|
|
14
|
+
|
|
15
|
+
__version__ = "0.1.0"
|
|
16
|
+
|
|
17
|
+
__plugin_meta__ = PluginMetadata(
|
|
18
|
+
name="nonebot-plugin-onebot-luckperms",
|
|
19
|
+
description="LuckPerms-style permission system for NoneBot2 + OneBot",
|
|
20
|
+
usage="Register nodes with register_node(), use require() in command matchers",
|
|
21
|
+
type="library",
|
|
22
|
+
homepage="https://github.com/XChen446/nonebot-plugin-onebot-luckperms",
|
|
23
|
+
config=OBLPConfig,
|
|
24
|
+
supported_adapters={"~onebot.v11"},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"register_node",
|
|
29
|
+
"require",
|
|
30
|
+
"require_any",
|
|
31
|
+
"require_all",
|
|
32
|
+
"get_context",
|
|
33
|
+
"set_resolver",
|
|
34
|
+
"ContextSet",
|
|
35
|
+
"PermissionNode",
|
|
36
|
+
"oblp_config",
|
|
37
|
+
"get_store",
|
|
38
|
+
"LPContext",
|
|
39
|
+
"register_context_provider",
|
|
40
|
+
"ContextProvider",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
_store_initialized = False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _register_builtin_nodes():
|
|
47
|
+
register_node("luckperms.*", "Access to all LuckPerms commands", default=True)
|
|
48
|
+
register_node("luckperms.help", "View LuckPerms help", default=False)
|
|
49
|
+
register_node("luckperms.info", "View LuckPerms info", default=False)
|
|
50
|
+
register_node("luckperms.sync", "Reload LuckPerms data", default=False)
|
|
51
|
+
register_node("luckperms.editor", "Open LuckPerms Web Editor", default=False)
|
|
52
|
+
register_node("luckperms.applyedits", "Apply Web Editor edits", default=False)
|
|
53
|
+
register_node("luckperms.check", "Check user permissions", default=False)
|
|
54
|
+
register_node("luckperms.tree", "View permission inheritance tree", default=False)
|
|
55
|
+
register_node("luckperms.user.info", "View user details", default=False)
|
|
56
|
+
register_node("luckperms.user.permission", "Manage user permissions", default=False)
|
|
57
|
+
register_node("luckperms.user.parent", "Manage user group membership", default=False)
|
|
58
|
+
register_node("luckperms.user.promote", "Promote user", default=False)
|
|
59
|
+
register_node("luckperms.user.demote", "Demote user", default=False)
|
|
60
|
+
register_node("luckperms.user.clear", "Clear user permissions", default=False)
|
|
61
|
+
register_node("luckperms.user.clone", "Clone user", default=False)
|
|
62
|
+
register_node("luckperms.user.create", "Create user", default=False)
|
|
63
|
+
register_node("luckperms.user.delete", "Delete user", default=False)
|
|
64
|
+
register_node("luckperms.user.list", "List all users", default=False)
|
|
65
|
+
register_node("luckperms.group.info", "View group details", default=False)
|
|
66
|
+
register_node("luckperms.group.permission", "Manage group permissions", default=False)
|
|
67
|
+
register_node("luckperms.group.parent", "Manage group inheritance", default=False)
|
|
68
|
+
register_node("luckperms.group.setweight", "Set group weight", default=False)
|
|
69
|
+
register_node("luckperms.group.setdisplayname", "Set group display name", default=False)
|
|
70
|
+
register_node("luckperms.group.clear", "Clear group permissions", default=False)
|
|
71
|
+
register_node("luckperms.group.rename", "Rename group", default=False)
|
|
72
|
+
register_node("luckperms.group.clone", "Clone group", default=False)
|
|
73
|
+
register_node("luckperms.group.listmembers", "List group members", default=False)
|
|
74
|
+
register_node("luckperms.group.create", "Create group", default=False)
|
|
75
|
+
register_node("luckperms.group.delete", "Delete group", default=False)
|
|
76
|
+
register_node("luckperms.group.list", "List all groups", default=False)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
_register_builtin_nodes()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def _init():
|
|
83
|
+
global _store_initialized
|
|
84
|
+
|
|
85
|
+
if _store_initialized:
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
from nonebot import get_plugin_config
|
|
89
|
+
from .config import OBLPConfig
|
|
90
|
+
from .storage import init_store
|
|
91
|
+
from .adapter.identity import set_resolver as _set_resolver, OneBotV11Resolver
|
|
92
|
+
from .commands import register_admin_commands
|
|
93
|
+
from .message import load_messages, _export_defaults
|
|
94
|
+
|
|
95
|
+
raw_cfg = get_plugin_config(OBLPConfig)
|
|
96
|
+
|
|
97
|
+
for field in raw_cfg.model_fields:
|
|
98
|
+
setattr(oblp_config, field, getattr(raw_cfg, field))
|
|
99
|
+
|
|
100
|
+
store_type = oblp_config.store_type
|
|
101
|
+
kwargs = {}
|
|
102
|
+
if store_type == "sqlite":
|
|
103
|
+
kwargs["db_path"] = oblp_config.sqlite_path
|
|
104
|
+
elif store_type == "redis":
|
|
105
|
+
kwargs["redis_url"] = oblp_config.redis_url
|
|
106
|
+
|
|
107
|
+
store = init_store(store_type, **kwargs)
|
|
108
|
+
await store.load_all()
|
|
109
|
+
_set_resolver(OneBotV11Resolver())
|
|
110
|
+
register_admin_commands()
|
|
111
|
+
|
|
112
|
+
await load_messages(oblp_config.message_file)
|
|
113
|
+
try:
|
|
114
|
+
await _export_defaults(oblp_config.message_file)
|
|
115
|
+
except Exception:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
_store_initialized = True
|
|
119
|
+
logger.info(f"OBLP initialized (store: {store_type})")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
from nonebot import get_driver
|
|
124
|
+
driver = get_driver()
|
|
125
|
+
driver.on_startup(_init)
|
|
126
|
+
except (ValueError, RuntimeError, ImportError):
|
|
127
|
+
logger.debug("NoneBot not initialized, OBLP will init on first use")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from .identity import Identity, IdentityResolver, OneBotV11Resolver, set_resolver
|
|
4
|
+
from .permission import require, require_any, require_all
|
|
5
|
+
from .context import get_context, LPContext
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Identity",
|
|
9
|
+
"IdentityResolver",
|
|
10
|
+
"OneBotV11Resolver",
|
|
11
|
+
"set_resolver",
|
|
12
|
+
"require",
|
|
13
|
+
"require_any",
|
|
14
|
+
"require_all",
|
|
15
|
+
"get_context",
|
|
16
|
+
"LPContext",
|
|
17
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextvars
|
|
4
|
+
import time
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Dict, Optional, Any
|
|
7
|
+
|
|
8
|
+
from ..core.models import User, QueryOptions, ContextSet
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class LPContext:
|
|
13
|
+
user: User
|
|
14
|
+
identity: "Identity"
|
|
15
|
+
query_options: QueryOptions
|
|
16
|
+
resolved_nodes: Dict[str, bool]
|
|
17
|
+
matched_node: Optional[str] = None
|
|
18
|
+
timestamp: float = field(default_factory=time.time)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_lp_context: contextvars.ContextVar[Optional[LPContext]] = contextvars.ContextVar(
|
|
22
|
+
"oblp_context", default=None
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_context() -> Optional[LPContext]:
|
|
27
|
+
return _lp_context.get()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def set_context(ctx: Optional[LPContext]):
|
|
31
|
+
_lp_context.set(ctx)
|