nonebot-plugin-onebot-luckperms 0.1.0__py3-none-any.whl
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/__init__.py +127 -0
- nonebot_plugin_onebot_luckperms/adapter/__init__.py +17 -0
- nonebot_plugin_onebot_luckperms/adapter/context.py +31 -0
- nonebot_plugin_onebot_luckperms/adapter/identity.py +64 -0
- nonebot_plugin_onebot_luckperms/adapter/permission.py +157 -0
- nonebot_plugin_onebot_luckperms/commands/__init__.py +3 -0
- nonebot_plugin_onebot_luckperms/commands/admin.py +857 -0
- nonebot_plugin_onebot_luckperms/config.py +20 -0
- nonebot_plugin_onebot_luckperms/core/__init__.py +20 -0
- nonebot_plugin_onebot_luckperms/core/cache.py +44 -0
- nonebot_plugin_onebot_luckperms/core/context_provider.py +74 -0
- nonebot_plugin_onebot_luckperms/core/engine.py +162 -0
- nonebot_plugin_onebot_luckperms/core/exceptions.py +6 -0
- nonebot_plugin_onebot_luckperms/core/models.py +123 -0
- nonebot_plugin_onebot_luckperms/core/registry.py +72 -0
- nonebot_plugin_onebot_luckperms/message.py +126 -0
- nonebot_plugin_onebot_luckperms/py.typed +0 -0
- nonebot_plugin_onebot_luckperms/storage/__init__.py +29 -0
- nonebot_plugin_onebot_luckperms/storage/memory.py +46 -0
- nonebot_plugin_onebot_luckperms/storage/protocol.py +21 -0
- nonebot_plugin_onebot_luckperms/storage/redis.py +162 -0
- nonebot_plugin_onebot_luckperms/storage/sqlite.py +189 -0
- nonebot_plugin_onebot_luckperms/webeditor/__init__.py +9 -0
- nonebot_plugin_onebot_luckperms/webeditor/bytebin.py +71 -0
- nonebot_plugin_onebot_luckperms/webeditor/manager.py +228 -0
- nonebot_plugin_onebot_luckperms/webeditor/session.py +109 -0
- nonebot_plugin_onebot_luckperms/webeditor/websocket.py +180 -0
- nonebot_plugin_onebot_luckperms-0.1.0.dist-info/METADATA +23 -0
- nonebot_plugin_onebot_luckperms-0.1.0.dist-info/RECORD +32 -0
- nonebot_plugin_onebot_luckperms-0.1.0.dist-info/WHEEL +5 -0
- nonebot_plugin_onebot_luckperms-0.1.0.dist-info/licenses/LICENSE +21 -0
- nonebot_plugin_onebot_luckperms-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from nonebot import on_command
|
|
6
|
+
from nonebot.adapters import Bot, Event
|
|
7
|
+
from nonebot.params import CommandArg
|
|
8
|
+
from nonebot.matcher import Matcher
|
|
9
|
+
|
|
10
|
+
from ..core.models import ContextSet, PermissionNode, User, Group, QueryOptions
|
|
11
|
+
from ..core.registry import NodeRegistry
|
|
12
|
+
from ..core.engine import PermissionEngine
|
|
13
|
+
from ..core.cache import PermissionCache
|
|
14
|
+
from ..core.context_provider import get_context_providers
|
|
15
|
+
from ..storage import get_store
|
|
16
|
+
from ..adapter.identity import get_resolver, Identity
|
|
17
|
+
from ..adapter.context import LPContext, set_context
|
|
18
|
+
from ..config import oblp_config
|
|
19
|
+
from ..message import msg
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger("oblp")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _parse_context(args: list) -> ContextSet:
|
|
25
|
+
ctx = {}
|
|
26
|
+
for a in args:
|
|
27
|
+
if "=" in a:
|
|
28
|
+
k, v = a.split("=", 1)
|
|
29
|
+
ctx[k] = v
|
|
30
|
+
return ContextSet.from_dict(ctx)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _parse_duration(dur_str: str) -> Optional[int]:
|
|
34
|
+
if not dur_str:
|
|
35
|
+
return None
|
|
36
|
+
dur_str = dur_str.lower()
|
|
37
|
+
multipliers = {"s": 1, "m": 60, "h": 3600, "d": 86400}
|
|
38
|
+
suffix = dur_str[-1]
|
|
39
|
+
if suffix in multipliers:
|
|
40
|
+
try:
|
|
41
|
+
return int(dur_str[:-1]) * multipliers[suffix]
|
|
42
|
+
except ValueError:
|
|
43
|
+
return None
|
|
44
|
+
try:
|
|
45
|
+
return int(dur_str)
|
|
46
|
+
except ValueError:
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def _check_lp(bot: Bot, event: Event, matcher: Matcher, node_key: str) -> bool:
|
|
51
|
+
"""Check luckperms node + fallback luckperms.*, send deny message on failure."""
|
|
52
|
+
resolver = get_resolver()
|
|
53
|
+
store = get_store()
|
|
54
|
+
if resolver is None or store is None:
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
identity = await resolver.resolve(bot, event)
|
|
59
|
+
except Exception:
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
ctx = ContextSet.from_dict({
|
|
63
|
+
"platform": identity.platform,
|
|
64
|
+
"user_id": identity.user_id,
|
|
65
|
+
"role": identity.role,
|
|
66
|
+
})
|
|
67
|
+
if identity.group_id:
|
|
68
|
+
ctx = ctx.with_context("group_id", identity.group_id)
|
|
69
|
+
|
|
70
|
+
for provider in get_context_providers():
|
|
71
|
+
try:
|
|
72
|
+
extra = await provider(bot, event, ctx)
|
|
73
|
+
if extra:
|
|
74
|
+
for k, v in extra.items():
|
|
75
|
+
ctx = ctx.with_context(k, v)
|
|
76
|
+
except Exception:
|
|
77
|
+
logger.exception(f"ContextProvider error")
|
|
78
|
+
|
|
79
|
+
opts = QueryOptions(mode="contextual", contexts=ctx, flags={"include_inherited", "resolve_inheritance"})
|
|
80
|
+
|
|
81
|
+
user_obj = await store.get_user(identity.user_id)
|
|
82
|
+
if user_obj is None:
|
|
83
|
+
user_obj = User(user_id=identity.user_id)
|
|
84
|
+
|
|
85
|
+
resolved = await user_obj.get_effective_nodes(store, opts, ctx)
|
|
86
|
+
|
|
87
|
+
if PermissionEngine.check(resolved, node_key):
|
|
88
|
+
return True
|
|
89
|
+
if node_key != "luckperms.*" and PermissionEngine.check(resolved, "luckperms.*"):
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
await matcher.send(msg("deny"))
|
|
94
|
+
await matcher.send(msg("deny_hint"))
|
|
95
|
+
except Exception:
|
|
96
|
+
pass
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
CLP = None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def register_admin_commands():
|
|
104
|
+
global CLP
|
|
105
|
+
CLP = on_command("lp", aliases={"luckperms"}, block=True)
|
|
106
|
+
CLP.handle()(_handler)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
async def _handler(bot: Bot, event: Event, matcher: Matcher, arg=CommandArg()):
|
|
110
|
+
store = get_store()
|
|
111
|
+
if store is None:
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
raw = arg.extract_plain_text().strip()
|
|
115
|
+
parts = raw.split()
|
|
116
|
+
if not parts:
|
|
117
|
+
if await _check_lp(bot, event, matcher, "luckperms.help"):
|
|
118
|
+
await matcher.send(
|
|
119
|
+
"LuckPerms commands:\n"
|
|
120
|
+
" sync / info / editor / applyedits <code>\n"
|
|
121
|
+
" creategroup / deletegroup / listgroups\n"
|
|
122
|
+
" createtrack / deletetrack / listtracks\n"
|
|
123
|
+
" check <user> <node> [ctx...]\n"
|
|
124
|
+
" tree [scope] [player]\n"
|
|
125
|
+
" user <user> <action> ...\n"
|
|
126
|
+
" group <group> <action> ...\n"
|
|
127
|
+
" track <track> <action> ..."
|
|
128
|
+
)
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
cmd = parts[0].lower()
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
# ── General commands ────────────────────────────────
|
|
135
|
+
|
|
136
|
+
if cmd in ("help", "?"):
|
|
137
|
+
if await _check_lp(bot, event, matcher, "luckperms.help"):
|
|
138
|
+
await matcher.send(
|
|
139
|
+
"LuckPerms commands:\n"
|
|
140
|
+
" sync / info / editor / applyedits <code>\n"
|
|
141
|
+
" creategroup / deletegroup / listgroups\n"
|
|
142
|
+
" createtrack / deletetrack / listtracks\n"
|
|
143
|
+
" check <user> <node> [ctx...]\n"
|
|
144
|
+
" tree [scope] [player]\n"
|
|
145
|
+
" user <user> <action> ...\n"
|
|
146
|
+
" group <group> <action> ...\n"
|
|
147
|
+
" track <track> <action> ..."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
elif cmd == "sync":
|
|
151
|
+
if not await _check_lp(bot, event, matcher, "luckperms.sync"):
|
|
152
|
+
return
|
|
153
|
+
await store.load_all()
|
|
154
|
+
PermissionCache.invalidate_all()
|
|
155
|
+
await matcher.send(msg("data_reloaded"))
|
|
156
|
+
|
|
157
|
+
elif cmd == "info":
|
|
158
|
+
if not await _check_lp(bot, event, matcher, "luckperms.info"):
|
|
159
|
+
return
|
|
160
|
+
groups = await store.list_groups()
|
|
161
|
+
users = await store.list_users()
|
|
162
|
+
await matcher.send(f"OBLP v0.1.0\nUsers: {len(users)}\nGroups: {len(groups)}\nStore: {type(store).__name__}")
|
|
163
|
+
|
|
164
|
+
elif cmd == "editor":
|
|
165
|
+
if not await _check_lp(bot, event, matcher, "luckperms.editor"):
|
|
166
|
+
return
|
|
167
|
+
await _cmd_editor(bot, event, matcher, store, parts[1:])
|
|
168
|
+
|
|
169
|
+
elif cmd == "applyedits":
|
|
170
|
+
if not await _check_lp(bot, event, matcher, "luckperms.applyedits"):
|
|
171
|
+
return
|
|
172
|
+
if len(parts) < 2:
|
|
173
|
+
await matcher.finish("Usage: /lp applyedits <code>")
|
|
174
|
+
await _apply_edits(matcher, parts[1])
|
|
175
|
+
|
|
176
|
+
elif cmd == "creategroup":
|
|
177
|
+
if not await _check_lp(bot, event, matcher, "luckperms.group.create"):
|
|
178
|
+
return
|
|
179
|
+
name = parts[1] if len(parts) > 1 else None
|
|
180
|
+
if not name:
|
|
181
|
+
await matcher.finish("Usage: /lp creategroup <name> [weight]")
|
|
182
|
+
weight = int(parts[2]) if len(parts) > 2 else 0
|
|
183
|
+
existing = await store.get_group(name)
|
|
184
|
+
if existing:
|
|
185
|
+
await matcher.finish(f"Group {name} already exists")
|
|
186
|
+
await store.save_group(Group(name=name, weight=weight))
|
|
187
|
+
await matcher.send(f"Group {name} created (weight={weight})")
|
|
188
|
+
|
|
189
|
+
elif cmd == "deletegroup":
|
|
190
|
+
if not await _check_lp(bot, event, matcher, "luckperms.group.delete"):
|
|
191
|
+
return
|
|
192
|
+
if len(parts) < 2:
|
|
193
|
+
await matcher.finish("Usage: /lp deletegroup <name>")
|
|
194
|
+
await store.delete_group(parts[1])
|
|
195
|
+
await matcher.send(f"Group {parts[1]} deleted")
|
|
196
|
+
|
|
197
|
+
elif cmd == "listgroups":
|
|
198
|
+
if not await _check_lp(bot, event, matcher, "luckperms.group.list"):
|
|
199
|
+
return
|
|
200
|
+
groups = await store.list_groups()
|
|
201
|
+
if not groups:
|
|
202
|
+
await matcher.send("No groups")
|
|
203
|
+
else:
|
|
204
|
+
lines = ["Groups:"]
|
|
205
|
+
for gname in groups:
|
|
206
|
+
g = await store.get_group(gname)
|
|
207
|
+
if g:
|
|
208
|
+
lines.append(f" {g.name} (weight={g.weight}, parents={g.parents})")
|
|
209
|
+
await matcher.send("\n".join(lines))
|
|
210
|
+
|
|
211
|
+
elif cmd == "check":
|
|
212
|
+
if not await _check_lp(bot, event, matcher, "luckperms.check"):
|
|
213
|
+
return
|
|
214
|
+
if len(parts) < 3:
|
|
215
|
+
await matcher.finish("Usage: /lp check <user> <node> [ctx...]")
|
|
216
|
+
await _cmd_check(matcher, store, parts[1], parts[2], parts[3:])
|
|
217
|
+
|
|
218
|
+
elif cmd == "tree":
|
|
219
|
+
if not await _check_lp(bot, event, matcher, "luckperms.tree"):
|
|
220
|
+
return
|
|
221
|
+
await _cmd_tree(matcher, store, parts[1:])
|
|
222
|
+
|
|
223
|
+
elif cmd == "user":
|
|
224
|
+
if len(parts) < 2:
|
|
225
|
+
await matcher.finish("Usage: /lp user <user> info|permission|parent|meta|editor|promote|demote|showtracks|clear|clone")
|
|
226
|
+
await _cmd_user(bot, event, matcher, store, parts[1], parts[2:])
|
|
227
|
+
|
|
228
|
+
elif cmd == "group":
|
|
229
|
+
if len(parts) < 2:
|
|
230
|
+
await matcher.finish("Usage: /lp group <group> info|permission|parent|meta|editor|setweight|setdisplayname|showtracks|clear|rename|clone|listmembers")
|
|
231
|
+
await _cmd_group(bot, event, matcher, store, parts[1], parts[2:])
|
|
232
|
+
|
|
233
|
+
elif cmd == "track":
|
|
234
|
+
if len(parts) < 2:
|
|
235
|
+
await matcher.finish("Usage: /lp track <track> info|editor|append|insert|remove|clear|rename|clone")
|
|
236
|
+
await _cmd_track(matcher, store, parts[1], parts[2:])
|
|
237
|
+
|
|
238
|
+
else:
|
|
239
|
+
if await _check_lp(bot, event, matcher, "luckperms.help"):
|
|
240
|
+
await matcher.send(f"Unknown command: {cmd}. Use /lp help")
|
|
241
|
+
|
|
242
|
+
except Exception as e:
|
|
243
|
+
logger.exception("Command error")
|
|
244
|
+
await matcher.finish(f"Error: {e}")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# ── User ──────────────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
async def _cmd_user(bot: Bot, event: Event, matcher: Matcher, store, user_id: str, args):
|
|
250
|
+
if not args:
|
|
251
|
+
await matcher.finish("Usage: /lp user <user> info|permission|parent|meta|editor|promote|demote|showtracks|clear|clone")
|
|
252
|
+
|
|
253
|
+
action = args[0].lower()
|
|
254
|
+
|
|
255
|
+
if action == "info":
|
|
256
|
+
if not await _check_lp(bot, event, matcher, "luckperms.user.info"):
|
|
257
|
+
return
|
|
258
|
+
user = await store.get_user(user_id)
|
|
259
|
+
if user is None:
|
|
260
|
+
await matcher.finish(f"User {user_id} not found")
|
|
261
|
+
lines = [
|
|
262
|
+
f"User: {user.user_id}",
|
|
263
|
+
f"Username: {user.username or 'N/A'}",
|
|
264
|
+
f"Primary Group: {user.primary_group or 'N/A'}",
|
|
265
|
+
f"Groups: {', '.join(user.groups) if user.groups else 'None'}",
|
|
266
|
+
f"Nodes ({len(user.nodes)}):",
|
|
267
|
+
]
|
|
268
|
+
for n in user.nodes:
|
|
269
|
+
ctx_str = n.contexts.to_dict()
|
|
270
|
+
ctx_info = f" ctx={ctx_str}" if ctx_str else ""
|
|
271
|
+
expiry_info = f" expires={n.expiry}" if n.expiry else ""
|
|
272
|
+
lines.append(f" {n.key}={'grant' if n.value else 'deny'}{ctx_info}{expiry_info}")
|
|
273
|
+
await matcher.send("\n".join(lines))
|
|
274
|
+
|
|
275
|
+
elif action == "permission":
|
|
276
|
+
if not await _check_lp(bot, event, matcher, "luckperms.user.permission"):
|
|
277
|
+
return
|
|
278
|
+
await _cmd_permission(matcher, store, user_id, is_user=True, args=args[1:])
|
|
279
|
+
|
|
280
|
+
elif action == "parent":
|
|
281
|
+
if not await _check_lp(bot, event, matcher, "luckperms.user.parent"):
|
|
282
|
+
return
|
|
283
|
+
await _cmd_parent(matcher, store, user_id, is_user=True, args=args[1:])
|
|
284
|
+
|
|
285
|
+
elif action == "meta":
|
|
286
|
+
if not await _check_lp(bot, event, matcher, "luckperms.user.permission"):
|
|
287
|
+
return
|
|
288
|
+
await _cmd_meta(matcher, store, user_id, is_user=True, args=args[1:])
|
|
289
|
+
|
|
290
|
+
elif action == "editor":
|
|
291
|
+
if not await _check_lp(bot, event, matcher, "luckperms.editor"):
|
|
292
|
+
return
|
|
293
|
+
await _cmd_editor(bot, event, matcher, store, [])
|
|
294
|
+
|
|
295
|
+
elif action == "promote":
|
|
296
|
+
if not await _check_lp(bot, event, matcher, "luckperms.user.promote"):
|
|
297
|
+
return
|
|
298
|
+
if len(args) < 2:
|
|
299
|
+
await matcher.finish("Usage: /lp user <user> promote <track> [ctx...]")
|
|
300
|
+
track_name = args[1]
|
|
301
|
+
user = await store.get_user(user_id)
|
|
302
|
+
if user is None:
|
|
303
|
+
user = User(user_id=user_id)
|
|
304
|
+
if track_name not in user.groups:
|
|
305
|
+
user.groups.append(track_name)
|
|
306
|
+
await store.save_user(user)
|
|
307
|
+
await matcher.send(f"User {user_id} promoted to {track_name}")
|
|
308
|
+
|
|
309
|
+
elif action == "demote":
|
|
310
|
+
if not await _check_lp(bot, event, matcher, "luckperms.user.demote"):
|
|
311
|
+
return
|
|
312
|
+
if len(args) < 2:
|
|
313
|
+
await matcher.finish("Usage: /lp user <user> demote <track> [ctx...]")
|
|
314
|
+
track_name = args[1]
|
|
315
|
+
user = await store.get_user(user_id)
|
|
316
|
+
if user is None:
|
|
317
|
+
await matcher.finish(f"User {user_id} not found")
|
|
318
|
+
user.groups = [g for g in user.groups if g != track_name]
|
|
319
|
+
await store.save_user(user)
|
|
320
|
+
await matcher.send(f"User {user_id} demoted from {track_name}")
|
|
321
|
+
|
|
322
|
+
elif action == "showtracks":
|
|
323
|
+
if not await _check_lp(bot, event, matcher, "luckperms.user.info"):
|
|
324
|
+
return
|
|
325
|
+
user = await store.get_user(user_id)
|
|
326
|
+
if user is None:
|
|
327
|
+
await matcher.finish(f"User {user_id} not found")
|
|
328
|
+
await matcher.send(f"Groups for {user_id}: {', '.join(user.groups) if user.groups else 'None'}")
|
|
329
|
+
|
|
330
|
+
elif action == "clear":
|
|
331
|
+
if not await _check_lp(bot, event, matcher, "luckperms.user.clear"):
|
|
332
|
+
return
|
|
333
|
+
ctx = _parse_context(args[1:])
|
|
334
|
+
user = await store.get_user(user_id)
|
|
335
|
+
if user:
|
|
336
|
+
if ctx.is_empty():
|
|
337
|
+
user.nodes = []
|
|
338
|
+
else:
|
|
339
|
+
user.nodes = [n for n in user.nodes if not n.applies_in(ctx)]
|
|
340
|
+
await store.save_user(user)
|
|
341
|
+
await matcher.send(f"User {user_id} permissions cleared")
|
|
342
|
+
|
|
343
|
+
elif action == "clone":
|
|
344
|
+
if not await _check_lp(bot, event, matcher, "luckperms.user.clone"):
|
|
345
|
+
return
|
|
346
|
+
if len(args) < 2:
|
|
347
|
+
await matcher.finish("Usage: /lp user <user> clone <target_user>")
|
|
348
|
+
target = args[1]
|
|
349
|
+
src = await store.get_user(user_id)
|
|
350
|
+
if src is None:
|
|
351
|
+
await matcher.finish(f"User {user_id} not found")
|
|
352
|
+
dst = User(user_id=target, username=src.username, primary_group=src.primary_group,
|
|
353
|
+
groups=list(src.groups), nodes=[PermissionNode(**n.__dict__) for n in src.nodes])
|
|
354
|
+
await store.save_user(dst)
|
|
355
|
+
await matcher.send(f"User {user_id} cloned to {target}")
|
|
356
|
+
|
|
357
|
+
else:
|
|
358
|
+
await matcher.finish(f"Unknown user action: {action}")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
# ── Group ─────────────────────────────────────────────────────────
|
|
362
|
+
|
|
363
|
+
async def _cmd_group(bot: Bot, event: Event, matcher: Matcher, store, name: str, args):
|
|
364
|
+
if not args:
|
|
365
|
+
await matcher.finish("Usage: /lp group <group> info|permission|parent|meta|editor|setweight|setdisplayname|showtracks|clear|rename|clone|listmembers")
|
|
366
|
+
|
|
367
|
+
action = args[0].lower()
|
|
368
|
+
|
|
369
|
+
if action == "info":
|
|
370
|
+
if not await _check_lp(bot, event, matcher, "luckperms.group.info"):
|
|
371
|
+
return
|
|
372
|
+
group = await store.get_group(name)
|
|
373
|
+
if group is None:
|
|
374
|
+
await matcher.finish(f"Group {name} not found")
|
|
375
|
+
lines = [
|
|
376
|
+
f"Group: {group.name}",
|
|
377
|
+
f"Display Name: {group.display_name or 'N/A'}",
|
|
378
|
+
f"Weight: {group.weight}",
|
|
379
|
+
f"Parents: {', '.join(group.parents) if group.parents else 'None'}",
|
|
380
|
+
f"Nodes ({len(group.nodes)}):",
|
|
381
|
+
]
|
|
382
|
+
for n in group.nodes:
|
|
383
|
+
ctx_str = n.contexts.to_dict()
|
|
384
|
+
ctx_info = f" ctx={ctx_str}" if ctx_str else ""
|
|
385
|
+
lines.append(f" {n.key}={'grant' if n.value else 'deny'}{ctx_info}")
|
|
386
|
+
await matcher.send("\n".join(lines))
|
|
387
|
+
|
|
388
|
+
elif action == "permission":
|
|
389
|
+
if not await _check_lp(bot, event, matcher, "luckperms.group.permission"):
|
|
390
|
+
return
|
|
391
|
+
await _cmd_permission(matcher, store, name, is_user=False, args=args[1:])
|
|
392
|
+
|
|
393
|
+
elif action == "parent":
|
|
394
|
+
if not await _check_lp(bot, event, matcher, "luckperms.group.parent"):
|
|
395
|
+
return
|
|
396
|
+
await _cmd_parent(matcher, store, name, is_user=False, args=args[1:])
|
|
397
|
+
|
|
398
|
+
elif action == "meta":
|
|
399
|
+
if not await _check_lp(bot, event, matcher, "luckperms.group.permission"):
|
|
400
|
+
return
|
|
401
|
+
await _cmd_meta(matcher, store, name, is_user=False, args=args[1:])
|
|
402
|
+
|
|
403
|
+
elif action == "editor":
|
|
404
|
+
if not await _check_lp(bot, event, matcher, "luckperms.editor"):
|
|
405
|
+
return
|
|
406
|
+
await _cmd_editor(bot, event, matcher, store, [])
|
|
407
|
+
|
|
408
|
+
elif action == "setweight":
|
|
409
|
+
if not await _check_lp(bot, event, matcher, "luckperms.group.setweight"):
|
|
410
|
+
return
|
|
411
|
+
if len(args) < 2:
|
|
412
|
+
await matcher.finish("Usage: /lp group <group> setweight <weight>")
|
|
413
|
+
group = await store.get_group(name)
|
|
414
|
+
if group is None:
|
|
415
|
+
await matcher.finish(f"Group {name} not found")
|
|
416
|
+
group.weight = int(args[1])
|
|
417
|
+
await store.save_group(group)
|
|
418
|
+
await matcher.send(f"Group {name} weight set to {args[1]}")
|
|
419
|
+
|
|
420
|
+
elif action == "setdisplayname":
|
|
421
|
+
if not await _check_lp(bot, event, matcher, "luckperms.group.setdisplayname"):
|
|
422
|
+
return
|
|
423
|
+
if len(args) < 2:
|
|
424
|
+
await matcher.finish("Usage: /lp group <group> setdisplayname <name>")
|
|
425
|
+
group = await store.get_group(name)
|
|
426
|
+
if group is None:
|
|
427
|
+
await matcher.finish(f"Group {name} not found")
|
|
428
|
+
group.display_name = args[1]
|
|
429
|
+
await store.save_group(group)
|
|
430
|
+
await matcher.send(f"Group {name} display name set to {args[1]}")
|
|
431
|
+
|
|
432
|
+
elif action == "showtracks":
|
|
433
|
+
if not await _check_lp(bot, event, matcher, "luckperms.group.info"):
|
|
434
|
+
return
|
|
435
|
+
group = await store.get_group(name)
|
|
436
|
+
if group is None:
|
|
437
|
+
await matcher.finish(f"Group {name} not found")
|
|
438
|
+
await matcher.send(f"Parents of {name}: {', '.join(group.parents) if group.parents else 'None'}")
|
|
439
|
+
|
|
440
|
+
elif action == "clear":
|
|
441
|
+
if not await _check_lp(bot, event, matcher, "luckperms.group.clear"):
|
|
442
|
+
return
|
|
443
|
+
ctx = _parse_context(args[1:])
|
|
444
|
+
group = await store.get_group(name)
|
|
445
|
+
if group:
|
|
446
|
+
if ctx.is_empty():
|
|
447
|
+
group.nodes = []
|
|
448
|
+
else:
|
|
449
|
+
group.nodes = [n for n in group.nodes if not n.applies_in(ctx)]
|
|
450
|
+
await store.save_group(group)
|
|
451
|
+
await matcher.send(f"Group {name} permissions cleared")
|
|
452
|
+
|
|
453
|
+
elif action == "rename":
|
|
454
|
+
if not await _check_lp(bot, event, matcher, "luckperms.group.rename"):
|
|
455
|
+
return
|
|
456
|
+
if len(args) < 2:
|
|
457
|
+
await matcher.finish("Usage: /lp group <group> rename <new_name>")
|
|
458
|
+
group = await store.get_group(name)
|
|
459
|
+
if group is None:
|
|
460
|
+
await matcher.finish(f"Group {name} not found")
|
|
461
|
+
await store.delete_group(name)
|
|
462
|
+
group.name = args[1]
|
|
463
|
+
await store.save_group(group)
|
|
464
|
+
await matcher.send(f"Group {name} renamed to {args[1]}")
|
|
465
|
+
|
|
466
|
+
elif action == "clone":
|
|
467
|
+
if not await _check_lp(bot, event, matcher, "luckperms.group.clone"):
|
|
468
|
+
return
|
|
469
|
+
if len(args) < 2:
|
|
470
|
+
await matcher.finish("Usage: /lp group <group> clone <new_name>")
|
|
471
|
+
group = await store.get_group(name)
|
|
472
|
+
if group is None:
|
|
473
|
+
await matcher.finish(f"Group {name} not found")
|
|
474
|
+
new_name = args[1]
|
|
475
|
+
new_group = Group(name=new_name, display_name=group.display_name, weight=group.weight,
|
|
476
|
+
nodes=[PermissionNode(**n.__dict__) for n in group.nodes],
|
|
477
|
+
parents=list(group.parents))
|
|
478
|
+
await store.save_group(new_group)
|
|
479
|
+
await matcher.send(f"Group {name} cloned to {new_name}")
|
|
480
|
+
|
|
481
|
+
elif action == "listmembers":
|
|
482
|
+
if not await _check_lp(bot, event, matcher, "luckperms.group.listmembers"):
|
|
483
|
+
return
|
|
484
|
+
users = await store.list_users()
|
|
485
|
+
members = []
|
|
486
|
+
for uid in users:
|
|
487
|
+
u = await store.get_user(uid)
|
|
488
|
+
if u and name in u.groups:
|
|
489
|
+
members.append(uid)
|
|
490
|
+
if not members:
|
|
491
|
+
await matcher.send(f"No members in group {name}")
|
|
492
|
+
else:
|
|
493
|
+
await matcher.send(f"Members of {name}:\n" + "\n".join(f" {m}" for m in members))
|
|
494
|
+
|
|
495
|
+
else:
|
|
496
|
+
await matcher.finish(f"Unknown group action: {action}")
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
# ── Track ─────────────────────────────────────────────────────────
|
|
500
|
+
|
|
501
|
+
async def _cmd_track(matcher: Matcher, store, track_name: str, args):
|
|
502
|
+
if not args:
|
|
503
|
+
await matcher.finish("Usage: /lp track <track> info|editor|append|insert|remove|clear|rename|clone")
|
|
504
|
+
action = args[0].lower()
|
|
505
|
+
await matcher.send(f"Track commands not yet implemented (stub)")
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
# ── Permission subcommand ─────────────────────────────────────────
|
|
509
|
+
|
|
510
|
+
async def _cmd_permission(matcher: Matcher, store, entity_id: str, is_user: bool, args):
|
|
511
|
+
if not args:
|
|
512
|
+
await matcher.finish("Usage: permission info|set|unset|settemp|unsettemp|check|clear")
|
|
513
|
+
|
|
514
|
+
sub = args[0].lower()
|
|
515
|
+
entity = await (store.get_user(entity_id) if is_user else store.get_group(entity_id))
|
|
516
|
+
if entity is None:
|
|
517
|
+
entity = User(user_id=entity_id) if is_user else Group(name=entity_id)
|
|
518
|
+
|
|
519
|
+
async def _save():
|
|
520
|
+
if is_user:
|
|
521
|
+
await store.save_user(entity)
|
|
522
|
+
else:
|
|
523
|
+
await store.save_group(entity)
|
|
524
|
+
|
|
525
|
+
if sub == "info":
|
|
526
|
+
if not entity.nodes:
|
|
527
|
+
await matcher.send("No permission nodes")
|
|
528
|
+
else:
|
|
529
|
+
lines = [f"Permissions for {entity_id}:"]
|
|
530
|
+
for n in entity.nodes:
|
|
531
|
+
ctx_str = n.contexts.to_dict()
|
|
532
|
+
ctx_info = f" ctx={ctx_str}" if ctx_str else ""
|
|
533
|
+
expiry_info = f" expires={n.expiry}" if n.expiry else ""
|
|
534
|
+
lines.append(f" {n.key}={'grant' if n.value else 'deny'}{ctx_info}{expiry_info}")
|
|
535
|
+
await matcher.send("\n".join(lines))
|
|
536
|
+
return
|
|
537
|
+
|
|
538
|
+
elif sub == "set":
|
|
539
|
+
if len(args) < 2:
|
|
540
|
+
await matcher.finish("Usage: permission set <node> <true|false> [ctx...]")
|
|
541
|
+
node_key = args[1]
|
|
542
|
+
value = True
|
|
543
|
+
idx = 2
|
|
544
|
+
if len(args) > 2 and args[2].lower() in ("true", "false", "grant", "deny"):
|
|
545
|
+
value = args[2].lower() in ("true", "grant")
|
|
546
|
+
idx = 3
|
|
547
|
+
ctx = _parse_context(args[idx:])
|
|
548
|
+
node = PermissionNode(key=node_key, value=value, contexts=ctx)
|
|
549
|
+
entity.nodes = [n for n in entity.nodes if not (n.key == node.key and n.contexts == node.contexts)]
|
|
550
|
+
entity.nodes.append(node)
|
|
551
|
+
await _save()
|
|
552
|
+
info = f"{'grant' if value else 'deny'} {node_key}"
|
|
553
|
+
if not ctx.is_empty():
|
|
554
|
+
info += f" (ctx: {ctx.to_dict()})"
|
|
555
|
+
await matcher.send(f"{entity_id}: {info}")
|
|
556
|
+
|
|
557
|
+
elif sub == "unset":
|
|
558
|
+
if len(args) < 2:
|
|
559
|
+
await matcher.finish("Usage: permission unset <node> [ctx...]")
|
|
560
|
+
node_key = args[1]
|
|
561
|
+
ctx = _parse_context(args[2:])
|
|
562
|
+
if ctx.is_empty():
|
|
563
|
+
entity.nodes = [n for n in entity.nodes if n.key != node_key]
|
|
564
|
+
else:
|
|
565
|
+
entity.nodes = [n for n in entity.nodes if not (n.key == node_key and n.contexts == ctx)]
|
|
566
|
+
await _save()
|
|
567
|
+
await matcher.send(f"{entity_id}: removed {node_key}")
|
|
568
|
+
|
|
569
|
+
elif sub == "settemp":
|
|
570
|
+
if len(args) < 4:
|
|
571
|
+
await matcher.finish("Usage: permission settemp <node> <true|false> <duration> [ctx...]")
|
|
572
|
+
node_key = args[1]
|
|
573
|
+
value = args[2].lower() in ("true", "grant")
|
|
574
|
+
duration = _parse_duration(args[3])
|
|
575
|
+
if duration is None:
|
|
576
|
+
await matcher.finish("Invalid duration. Use e.g. 30s, 5m, 2h, 1d")
|
|
577
|
+
ctx = _parse_context(args[4:])
|
|
578
|
+
expiry = int(time.time()) + duration
|
|
579
|
+
node = PermissionNode(key=node_key, value=value, expiry=expiry, contexts=ctx)
|
|
580
|
+
entity.nodes = [n for n in entity.nodes if not (n.key == node.key and n.contexts == node.contexts)]
|
|
581
|
+
entity.nodes.append(node)
|
|
582
|
+
await _save()
|
|
583
|
+
await matcher.send(f"{entity_id}: temp {'grant' if value else 'deny'} {node_key} ({args[3]})")
|
|
584
|
+
|
|
585
|
+
elif sub == "unsettemp":
|
|
586
|
+
if len(args) < 2:
|
|
587
|
+
await matcher.finish("Usage: permission unsettemp <node> [duration] [ctx...]")
|
|
588
|
+
node_key = args[1]
|
|
589
|
+
entity.nodes = [n for n in entity.nodes if n.key != node_key]
|
|
590
|
+
await _save()
|
|
591
|
+
await matcher.send(f"{entity_id}: removed temp {node_key}")
|
|
592
|
+
|
|
593
|
+
elif sub == "check":
|
|
594
|
+
if len(args) < 2:
|
|
595
|
+
await matcher.finish("Usage: permission check <node>")
|
|
596
|
+
node_key = args[1]
|
|
597
|
+
ctx = _parse_context(args[2:])
|
|
598
|
+
if is_user:
|
|
599
|
+
opts = type("Opts", (), {
|
|
600
|
+
"mode": "contextual",
|
|
601
|
+
"contexts": ctx,
|
|
602
|
+
"flags": {"include_inherited", "resolve_inheritance"},
|
|
603
|
+
})()
|
|
604
|
+
resolved = await entity.get_effective_nodes(store, opts) if isinstance(entity, User) else {}
|
|
605
|
+
else:
|
|
606
|
+
resolved = {}
|
|
607
|
+
for n in entity.nodes:
|
|
608
|
+
if not n.is_expired() and n.applies_in(ctx):
|
|
609
|
+
resolved[n.key] = n.value
|
|
610
|
+
result = PermissionEngine.check(resolved, node_key)
|
|
611
|
+
await matcher.send(f"{entity_id} check {node_key}: {'ALLOW' if result else 'DENY'}")
|
|
612
|
+
|
|
613
|
+
elif sub == "clear":
|
|
614
|
+
ctx = _parse_context(args[1:])
|
|
615
|
+
if ctx.is_empty():
|
|
616
|
+
entity.nodes = []
|
|
617
|
+
else:
|
|
618
|
+
entity.nodes = [n for n in entity.nodes if not n.applies_in(ctx)]
|
|
619
|
+
await _save()
|
|
620
|
+
await matcher.send(f"{entity_id}: permissions cleared")
|
|
621
|
+
|
|
622
|
+
else:
|
|
623
|
+
await matcher.finish(f"Unknown permission action: {sub}")
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
# ── Parent subcommand ─────────────────────────────────────────────
|
|
627
|
+
|
|
628
|
+
async def _cmd_parent(matcher: Matcher, store, entity_id: str, is_user: bool, args):
|
|
629
|
+
if not args:
|
|
630
|
+
await matcher.finish("Usage: parent info|set|add|remove|settrack|addtemp|removetemp|clear|cleartrack|switchprimarygroup")
|
|
631
|
+
|
|
632
|
+
sub = args[0].lower()
|
|
633
|
+
entity = await (store.get_user(entity_id) if is_user else store.get_group(entity_id))
|
|
634
|
+
if entity is None:
|
|
635
|
+
entity = User(user_id=entity_id) if is_user else Group(name=entity_id)
|
|
636
|
+
|
|
637
|
+
def _get_parents():
|
|
638
|
+
return entity.groups if is_user else entity.parents
|
|
639
|
+
|
|
640
|
+
async def _save():
|
|
641
|
+
if is_user:
|
|
642
|
+
await store.save_user(entity)
|
|
643
|
+
else:
|
|
644
|
+
await store.save_group(entity)
|
|
645
|
+
|
|
646
|
+
async def _add_parent(gname: str):
|
|
647
|
+
current = _get_parents()
|
|
648
|
+
if gname not in current:
|
|
649
|
+
if is_user:
|
|
650
|
+
entity.groups.append(gname)
|
|
651
|
+
else:
|
|
652
|
+
entity.parents.append(gname)
|
|
653
|
+
await _save()
|
|
654
|
+
|
|
655
|
+
async def _remove_parent(gname: str):
|
|
656
|
+
if is_user:
|
|
657
|
+
entity.groups = [g for g in entity.groups if g != gname]
|
|
658
|
+
else:
|
|
659
|
+
entity.parents = [p for p in entity.parents if p != gname]
|
|
660
|
+
await _save()
|
|
661
|
+
|
|
662
|
+
if sub == "info":
|
|
663
|
+
parents = _get_parents()
|
|
664
|
+
if not parents:
|
|
665
|
+
await matcher.send(f"{entity_id} has no parents")
|
|
666
|
+
else:
|
|
667
|
+
await matcher.send(f"Parents of {entity_id}:\n" + "\n".join(f" {p}" for p in parents))
|
|
668
|
+
|
|
669
|
+
elif sub == "set":
|
|
670
|
+
if len(args) < 2:
|
|
671
|
+
await matcher.finish("Usage: parent set <group> [ctx...]")
|
|
672
|
+
ctx = _parse_context(args[2:])
|
|
673
|
+
if is_user:
|
|
674
|
+
entity.groups = []
|
|
675
|
+
else:
|
|
676
|
+
entity.parents = []
|
|
677
|
+
await _add_parent(args[1])
|
|
678
|
+
|
|
679
|
+
elif sub == "add":
|
|
680
|
+
if len(args) < 2:
|
|
681
|
+
await matcher.finish("Usage: parent add <group> [ctx...]")
|
|
682
|
+
await _add_parent(args[1])
|
|
683
|
+
await matcher.send(f"Added parent {args[1]} to {entity_id}")
|
|
684
|
+
|
|
685
|
+
elif sub == "remove":
|
|
686
|
+
if len(args) < 2:
|
|
687
|
+
await matcher.finish("Usage: parent remove <group> [ctx...]")
|
|
688
|
+
await _remove_parent(args[1])
|
|
689
|
+
await matcher.send(f"Removed parent {args[1]} from {entity_id}")
|
|
690
|
+
|
|
691
|
+
elif sub in ("settrack", "addtemp", "removetemp", "cleartrack"):
|
|
692
|
+
await matcher.send(f"'{sub}' not yet implemented")
|
|
693
|
+
|
|
694
|
+
elif sub == "clear":
|
|
695
|
+
ctx = _parse_context(args[1:])
|
|
696
|
+
if is_user:
|
|
697
|
+
entity.groups = []
|
|
698
|
+
else:
|
|
699
|
+
entity.parents = []
|
|
700
|
+
await _save()
|
|
701
|
+
await matcher.send(f"{entity_id}: parents cleared")
|
|
702
|
+
|
|
703
|
+
elif sub == "switchprimarygroup":
|
|
704
|
+
if len(args) < 2:
|
|
705
|
+
await matcher.finish("Usage: parent switchprimarygroup <group>")
|
|
706
|
+
if is_user:
|
|
707
|
+
entity.primary_group = args[1]
|
|
708
|
+
await _save()
|
|
709
|
+
await matcher.send(f"User {entity_id} primary group set to {args[1]}")
|
|
710
|
+
else:
|
|
711
|
+
await matcher.finish("switchprimarygroup only applies to users")
|
|
712
|
+
|
|
713
|
+
else:
|
|
714
|
+
await matcher.finish(f"Unknown parent action: {sub}")
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
# ── Meta subcommand (stub) ────────────────────────────────────────
|
|
718
|
+
|
|
719
|
+
async def _cmd_meta(matcher: Matcher, store, entity_id: str, is_user: bool, args):
|
|
720
|
+
if not args:
|
|
721
|
+
await matcher.finish("Usage: meta info|set|unset|settemp|unsettemp|addprefix|addsuffix|setprefix|setsuffix|removeprefix|removesuffix|clear")
|
|
722
|
+
await matcher.send("Meta commands not yet implemented")
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
# ── Check ─────────────────────────────────────────────────────────
|
|
726
|
+
|
|
727
|
+
async def _cmd_check(matcher: Matcher, store, user_id: str, node_key: str, ctx_args):
|
|
728
|
+
ctx = _parse_context(ctx_args)
|
|
729
|
+
opts = type("Opts", (), {
|
|
730
|
+
"mode": "contextual",
|
|
731
|
+
"contexts": ctx,
|
|
732
|
+
"flags": {"include_inherited", "resolve_inheritance"},
|
|
733
|
+
})()
|
|
734
|
+
user = await store.get_user(user_id)
|
|
735
|
+
if user is None:
|
|
736
|
+
user = User(user_id=user_id)
|
|
737
|
+
resolved = await user.get_effective_nodes(store, opts)
|
|
738
|
+
result = PermissionEngine.check(resolved, node_key)
|
|
739
|
+
lines = [
|
|
740
|
+
f"Check: user={user_id}, node={node_key}",
|
|
741
|
+
f"Context: {ctx.to_dict() or '(empty)'}",
|
|
742
|
+
f"Result: {'ALLOW' if result else 'DENY'}",
|
|
743
|
+
f"Resolved nodes ({len(resolved)}):",
|
|
744
|
+
]
|
|
745
|
+
for k, v in sorted(resolved.items()):
|
|
746
|
+
lines.append(f" {k}={'grant' if v else 'deny'}")
|
|
747
|
+
await matcher.send("\n".join(lines))
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
# ── Tree ──────────────────────────────────────────────────────────
|
|
751
|
+
|
|
752
|
+
async def _cmd_tree(matcher: Matcher, store, args):
|
|
753
|
+
scope = args[0] if args else None
|
|
754
|
+
user = await store.get_user(scope) if scope else None
|
|
755
|
+
group = await store.get_group(scope) if scope else None
|
|
756
|
+
|
|
757
|
+
if user:
|
|
758
|
+
lines = [f"User: {user.user_id}"]
|
|
759
|
+
for gname in user.groups:
|
|
760
|
+
g = await store.get_group(gname)
|
|
761
|
+
if g:
|
|
762
|
+
lines.append(f" └── Group: {g.name} (weight={g.weight})")
|
|
763
|
+
for n in g.nodes:
|
|
764
|
+
lines.append(f" ├── {n.key}={'grant' if n.value else 'deny'}")
|
|
765
|
+
await matcher.send("\n".join(lines))
|
|
766
|
+
elif group:
|
|
767
|
+
lines = [f"Group: {group.name} (weight={group.weight})"]
|
|
768
|
+
for n in group.nodes:
|
|
769
|
+
lines.append(f" ├── {n.key}={'grant' if n.value else 'deny'}")
|
|
770
|
+
await matcher.send("\n".join(lines))
|
|
771
|
+
else:
|
|
772
|
+
groups = await store.list_groups()
|
|
773
|
+
if not groups:
|
|
774
|
+
await matcher.send("No groups")
|
|
775
|
+
else:
|
|
776
|
+
lines = ["Groups:"]
|
|
777
|
+
for gname in groups:
|
|
778
|
+
g = await store.get_group(gname)
|
|
779
|
+
if g:
|
|
780
|
+
lines.append(f" {g.name} (weight={g.weight})")
|
|
781
|
+
for n in g.nodes:
|
|
782
|
+
lines.append(f" ├── {n.key}={'grant' if n.value else 'deny'}")
|
|
783
|
+
await matcher.send("\n".join(lines))
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
# ── Editor ────────────────────────────────────────────────────────
|
|
787
|
+
|
|
788
|
+
async def _cmd_editor(bot: Bot, event: Event, matcher: Matcher, store, args):
|
|
789
|
+
from ..webeditor.manager import to_webeditor_payload, apply_webeditor_changes
|
|
790
|
+
from ..webeditor.session import WebEditorSession
|
|
791
|
+
from ..adapter.identity import get_resolver
|
|
792
|
+
|
|
793
|
+
resolver = get_resolver()
|
|
794
|
+
group_members: list[str] | None = None
|
|
795
|
+
extra_users: list[str] = []
|
|
796
|
+
chat_type = "unknown"
|
|
797
|
+
|
|
798
|
+
try:
|
|
799
|
+
identity = await resolver.resolve(bot, event) if resolver else None
|
|
800
|
+
except Exception:
|
|
801
|
+
identity = None
|
|
802
|
+
|
|
803
|
+
if identity:
|
|
804
|
+
chat_type = identity.platform
|
|
805
|
+
if identity.group_id:
|
|
806
|
+
try:
|
|
807
|
+
from nonebot.adapters.onebot.v11 import Bot as V11Bot
|
|
808
|
+
if isinstance(bot, V11Bot):
|
|
809
|
+
member_list = await bot.get_group_member_list(group_id=int(identity.group_id))
|
|
810
|
+
group_members = [str(m["user_id"]) for m in member_list]
|
|
811
|
+
extra_users = group_members
|
|
812
|
+
except Exception:
|
|
813
|
+
pass
|
|
814
|
+
chat_type = f"group({identity.group_id})"
|
|
815
|
+
else:
|
|
816
|
+
extra_users = [identity.user_id]
|
|
817
|
+
|
|
818
|
+
try:
|
|
819
|
+
payload = await to_webeditor_payload(extra_user_ids=extra_users)
|
|
820
|
+
except RuntimeError as e:
|
|
821
|
+
await matcher.finish(f"Editor error: {e}")
|
|
822
|
+
|
|
823
|
+
payload["metadata"]["context"] = {
|
|
824
|
+
"chat_type": chat_type,
|
|
825
|
+
"group_members": len(group_members) if group_members else 0,
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
session = WebEditorSession(
|
|
829
|
+
get_payload=lambda: payload,
|
|
830
|
+
apply_changes=lambda p: None,
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
async def _apply(p):
|
|
834
|
+
await apply_webeditor_changes(p)
|
|
835
|
+
session.apply_changes = _apply
|
|
836
|
+
|
|
837
|
+
try:
|
|
838
|
+
url = await session.open()
|
|
839
|
+
await matcher.send(msg("editor_opened", url=url))
|
|
840
|
+
except Exception as e:
|
|
841
|
+
await matcher.finish(msg("editor_failed", error=str(e)))
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
async def _apply_edits(matcher: Matcher, code: str):
|
|
845
|
+
from ..webeditor.manager import apply_webeditor_changes
|
|
846
|
+
from ..webeditor.bytebin import BytebinClient
|
|
847
|
+
|
|
848
|
+
try:
|
|
849
|
+
client = BytebinClient()
|
|
850
|
+
payload = await client.download(code)
|
|
851
|
+
await apply_webeditor_changes(payload)
|
|
852
|
+
store = get_store()
|
|
853
|
+
if store:
|
|
854
|
+
await store.save_all()
|
|
855
|
+
await matcher.send(msg("editor_apply_ok", code=code))
|
|
856
|
+
except Exception as e:
|
|
857
|
+
await matcher.finish(f"Apply failed: {e}")
|