Skip to content

Commit 0a11015

Browse files
+ Custom Comamnd check
1 parent ef3b9ed commit 0a11015

File tree

1 file changed

+206
-0
lines changed

1 file changed

+206
-0
lines changed

src/check.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import discord
2+
from discord import app_commands
3+
from discord.ext import commands
4+
from discord import abc
5+
from config import Config
6+
7+
from typing import (
8+
NoReturn, Literal, Union, Optional,
9+
List, Sequence, TypedDict
10+
)
11+
12+
13+
class PermissionRequirement(TypedDict):
14+
"""
15+
Permission Requirement
16+
17+
Parameters
18+
---------------
19+
:param type: Wheter to whitelist or blacklist this condition
20+
:param query: The permission scope
21+
:param value: The permission value
22+
"""
23+
type: Literal['wl', 'bl']
24+
query: Literal['in_channel', 'has_role', 'has_permission', 'is_developer', 'minimum_role']
25+
value: Optional[Union[str, int]]
26+
27+
class PermissionGate(TypedDict):
28+
"""
29+
Permission Gate
30+
31+
Parameters
32+
----------
33+
:param type: Whether the permission gate is required or optional
34+
:param requirement: The permission requirement
35+
"""
36+
type: Literal['required', 'optional']
37+
requirement: PermissionRequirement
38+
39+
class HybridContext:
40+
"""
41+
Hybrid Context derrived from any command context
42+
"""
43+
44+
author: Union[discord.User, discord.Member]
45+
is_developer: bool
46+
guild: Optional[discord.Guild]
47+
channel: Optional[Union[abc.GuildChannel, abc.PrivateChannel, discord.Thread]]
48+
49+
def __init__(self, __ctx: Union[discord.Interaction, commands.Context]) -> None:
50+
"""
51+
Converts a command context or app command context into a HybridContext
52+
53+
Parameters
54+
----------
55+
:param __ctx: The command context
56+
"""
57+
self.author = (isinstance(__ctx, discord.Interaction) and __ctx.user) or __ctx.author
58+
self.is_developer = (self.author.id == Config.DEVELOPER_USER_ID)
59+
self.guild = __ctx.guild
60+
self.channel = __ctx.channel
61+
62+
63+
64+
def _validate_group(ctx: HybridContext, group: Sequence[PermissionGate]) -> bool:
65+
required: List[bool] = []
66+
optional: List[bool] = []
67+
68+
for gate in group:
69+
passFlag = False
70+
71+
match gate['requirement']['query']:
72+
case 'is_developer':
73+
passFlag = (gate['requirement']['type'] == 'wl') and ctx.is_developer
74+
break
75+
76+
case 'has_role':
77+
if ctx.guild:
78+
passFlag = (gate['requirement']['type'] == 'wl') and any(
79+
gate['requirement']['value'] in [role.id, role.name]
80+
for role in ctx.author.roles
81+
)
82+
83+
case 'has_permission':
84+
if ctx.guild:
85+
passFlag = (gate['requirement']['type'] == 'wl') and getattr(
86+
ctx.author.guild_permissions,
87+
gate['requirement']['value']
88+
)
89+
90+
case 'in_channel':
91+
if ctx.guild:
92+
passFlag = (gate['requirement']['type'] == 'wl') and (
93+
gate['requirement']['value'] == ctx.channel.id
94+
)
95+
96+
case 'minimum_role':
97+
if ctx.guild:
98+
role = ctx.guild.get_role(gate['requirement']['value'])
99+
passFlag = (gate['requirement']['type'] == 'wl') and role and (
100+
ctx.author.top_role >= role
101+
)
102+
103+
if gate['type'] == 'required':
104+
required.append(passFlag)
105+
else:
106+
optional.append(passFlag)
107+
108+
return bool(all(required) and any(optional))
109+
110+
111+
112+
def validate(__ctx: Union[discord.Interaction, commands.Context], *gates: Sequence[Union[PermissionGate, Sequence[PermissionGate]]]) -> bool:
113+
"""
114+
Validates a members access to a command
115+
116+
Parameters
117+
----------
118+
:param __ctx: Command Context [legacy and app command supported]
119+
:param gates: List of permission gates
120+
"""
121+
ctx = HybridContext(__ctx)
122+
123+
ungrouped: List[PermissionGate] = []
124+
grouped: List[List[PermissionGate]] = []
125+
126+
# Separate grouped and ungrouped
127+
for gate in gates:
128+
if isinstance(gate, PermissionGate):
129+
ungrouped.append(gate)
130+
else:
131+
grouped.append(gate)
132+
133+
# Validate
134+
joined: List[List[PermissionGate]] = [ungrouped, *grouped]
135+
for gate in joined:
136+
passFlag = _validate_group(ctx, gate)
137+
if not passFlag:
138+
return False
139+
140+
return True
141+
142+
143+
144+
class Protected():
145+
def app(*clauses: Union[PermissionGate, Sequence[PermissionGate]]):
146+
"""
147+
Protect app command usage
148+
149+
Unclaused permission gates are treated as one clause
150+
All clauses muts pass for the user to be allowed access to the command
151+
152+
Parameters
153+
----------
154+
:param *: Permission gates or clausees of permission gates
155+
"""
156+
async def _run(interaction):
157+
return validate(interaction, *clauses)
158+
return app_commands.check(_run)
159+
160+
def legacy(*clauses: Union[PermissionGate, Sequence[PermissionGate]]):
161+
"""
162+
Protect legacy command usage
163+
164+
Unclaused permission gates are treated as one clause
165+
All clauses muts pass for the user to be allowed access to the command
166+
167+
Parameters
168+
----------
169+
:param *: Permission gates or clausees of permission gates
170+
"""
171+
def _run(ctx):
172+
return validate(ctx, *clauses)
173+
return commands.check(_run)
174+
175+
176+
177+
class PermissionPreset:
178+
"""Permission Presets for repeatedly used permissions"""
179+
180+
Developer: PermissionRequirement = {
181+
'origin': 'guild',
182+
'type': 'wl',
183+
'query': 'is_developer',
184+
'value': None
185+
}
186+
Admin: PermissionRequirement = {
187+
'origin': 'guild',
188+
'type': 'wl',
189+
'query': 'has_permission',
190+
'value': 'administrator'
191+
}
192+
WithinServer: PermissionRequirement = {
193+
'origin': 'guild',
194+
'type': 'wl',
195+
'query': 'in_guild',
196+
'value': Config.GUILD_ID
197+
}
198+
Is_Member: PermissionRequirement = {
199+
'origin': 'data',
200+
'type': 'wl',
201+
'query': 'minimum_role',
202+
'value': 'Community'
203+
}
204+
205+
def __init__(self, *args, **kwargs) -> NoReturn:
206+
raise NotImplementedError('Non Instantiable')

0 commit comments

Comments
 (0)