-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathpermissions.py
More file actions
82 lines (57 loc) · 2.48 KB
/
Copy pathpermissions.py
File metadata and controls
82 lines (57 loc) · 2.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
from __future__ import annotations
from enum import IntFlag
from typing import TYPE_CHECKING, Protocol, runtime_checkable
import discord
from discord.ext import commands
if TYPE_CHECKING:
from bot import SlopSoil
class Role(IntFlag):
"""Permission roles using cascading bitflags.
Each role's bit pattern is a superset of every less-privileged role, so the
single check ``(user_role & required) == required`` enforces the full
hierarchy without any special-casing:
ADMIN (0b111) ⊇ FRIEND (0b110) ⊇ VIEWER (0b100)
NONE (0b000) has no bits set and therefore fails every role check.
"""
NONE = 0b000 # unrecognized user — no access
VIEWER = 0b100 # any member of a guild the bot is in
FRIEND = 0b110 # on the bot's friends list
ADMIN = 0b111 # listed in ALLOWED_USER_IDS
@runtime_checkable
class _BotView(Protocol):
"""Minimal bot interface required by get_user_role — easy to mock in tests."""
allowed_ids: set[int]
relationships: list
guilds: list
def get_user_role(bot: _BotView, user_id: int) -> Role:
"""Return the highest Role that applies to *user_id*.
Evaluated in priority order: admin > friend > guild member > none.
Accepts any object that satisfies _BotView (the real SlopSoil bot or a
test double).
"""
if user_id in bot.allowed_ids:
return Role.ADMIN
if any(
r.type == discord.RelationshipType.friend and r.user.id == user_id
for r in bot.relationships
):
return Role.FRIEND
if any(guild.get_member(user_id) is not None for guild in bot.guilds):
return Role.VIEWER
return Role.NONE
def require_role(role: Role) -> commands.check:
"""Decorator that restricts a command to users whose role satisfies *role*.
Passes when ``(user_role & role) == role``, i.e. the user's role contains
all bits of the required role. Because ADMIN's bit pattern includes every
lower-privilege role's bits, admins automatically pass every check.
Usage::
@require_role(Role.VIEWER) # viewer, friend, or admin
@require_role(Role.FRIEND) # friend or admin only
@require_role(Role.ADMIN) # admin only
"""
async def predicate(ctx: commands.Context) -> bool:
if ctx.bot.user and ctx.author.id == ctx.bot.user.id:
return True
user_role = get_user_role(ctx.bot, ctx.author.id) # type: ignore[arg-type]
return (user_role & role) == role
return commands.check(predicate)