0
# Permissions and Security
1
2
Discord permissions system, role management, and security features including comprehensive permission handling, role hierarchy, access control for commands and features, and security best practices for Discord bot development.
3
4
## Capabilities
5
6
### Permissions System
7
8
Discord's bitfield-based permission system for fine-grained access control.
9
10
```python { .api }
11
class Permissions:
12
def __init__(self, value: int = 0, **kwargs):
13
"""
14
Initialize permissions object.
15
16
Parameters:
17
- value: Raw permission integer value
18
- kwargs: Individual permission flags
19
"""
20
21
value: int
22
23
@classmethod
24
def none(cls) -> Permissions:
25
"""Create permissions with no flags set."""
26
27
@classmethod
28
def all(cls) -> Permissions:
29
"""Create permissions with all flags set."""
30
31
@classmethod
32
def all_channel(cls) -> Permissions:
33
"""Create permissions with all channel-applicable flags."""
34
35
@classmethod
36
def general(cls) -> Permissions:
37
"""Create permissions with general user flags."""
38
39
@classmethod
40
def text(cls) -> Permissions:
41
"""Create permissions for text channels."""
42
43
@classmethod
44
def voice(cls) -> Permissions:
45
"""Create permissions for voice channels."""
46
47
@classmethod
48
def stage(cls) -> Permissions:
49
"""Create permissions for stage channels."""
50
51
@classmethod
52
def stage_moderator(cls) -> Permissions:
53
"""Create stage moderator permissions."""
54
55
@classmethod
56
def advanced(cls) -> Permissions:
57
"""Create advanced user permissions."""
58
59
def update(self, **kwargs) -> Permissions:
60
"""
61
Update permissions with new values.
62
63
Parameters:
64
- kwargs: Permission flags to update
65
66
Returns:
67
New permissions object
68
"""
69
70
def handle_overwrite(self, allow: int, deny: int) -> None:
71
"""
72
Apply permission overwrite.
73
74
Parameters:
75
- allow: Allowed permission bits
76
- deny: Denied permission bits
77
"""
78
79
# General Permissions
80
@property
81
def create_instant_invite(self) -> bool:
82
"""Create instant invite."""
83
84
@property
85
def kick_members(self) -> bool:
86
"""Kick members."""
87
88
@property
89
def ban_members(self) -> bool:
90
"""Ban members."""
91
92
@property
93
def administrator(self) -> bool:
94
"""Administrator (bypasses all permission checks)."""
95
96
@property
97
def manage_channels(self) -> bool:
98
"""Manage channels (create, edit, delete)."""
99
100
@property
101
def manage_guild(self) -> bool:
102
"""Manage server settings."""
103
104
@property
105
def add_reactions(self) -> bool:
106
"""Add reactions to messages."""
107
108
@property
109
def view_audit_log(self) -> bool:
110
"""View audit log."""
111
112
@property
113
def priority_speaker(self) -> bool:
114
"""Priority speaker in voice channels."""
115
116
@property
117
def stream(self) -> bool:
118
"""Video stream in voice channels."""
119
120
@property
121
def view_channel(self) -> bool:
122
"""View channels."""
123
124
@property
125
def send_messages(self) -> bool:
126
"""Send messages in text channels."""
127
128
@property
129
def send_tts_messages(self) -> bool:
130
"""Send text-to-speech messages."""
131
132
@property
133
def manage_messages(self) -> bool:
134
"""Manage messages (delete, pin)."""
135
136
@property
137
def embed_links(self) -> bool:
138
"""Embed links in messages."""
139
140
@property
141
def attach_files(self) -> bool:
142
"""Attach files to messages."""
143
144
@property
145
def read_message_history(self) -> bool:
146
"""Read message history."""
147
148
@property
149
def mention_everyone(self) -> bool:
150
"""Mention @everyone, @here, and all roles."""
151
152
@property
153
def use_external_emojis(self) -> bool:
154
"""Use emojis from other servers."""
155
156
@property
157
def view_guild_insights(self) -> bool:
158
"""View server insights."""
159
160
@property
161
def connect(self) -> bool:
162
"""Connect to voice channels."""
163
164
@property
165
def speak(self) -> bool:
166
"""Speak in voice channels."""
167
168
@property
169
def mute_members(self) -> bool:
170
"""Mute members in voice channels."""
171
172
@property
173
def deafen_members(self) -> bool:
174
"""Deafen members in voice channels."""
175
176
@property
177
def move_members(self) -> bool:
178
"""Move members between voice channels."""
179
180
@property
181
def use_voice_activation(self) -> bool:
182
"""Use voice activation (push-to-talk not required)."""
183
184
@property
185
def change_nickname(self) -> bool:
186
"""Change own nickname."""
187
188
@property
189
def manage_nicknames(self) -> bool:
190
"""Manage other members' nicknames."""
191
192
@property
193
def manage_roles(self) -> bool:
194
"""Manage roles and permissions."""
195
196
@property
197
def manage_webhooks(self) -> bool:
198
"""Manage webhooks."""
199
200
@property
201
def manage_emojis_and_stickers(self) -> bool:
202
"""Manage emojis and stickers."""
203
204
@property
205
def manage_emojis(self) -> bool:
206
"""Manage emojis (legacy alias)."""
207
208
@property
209
def use_slash_commands(self) -> bool:
210
"""Use slash commands."""
211
212
@property
213
def use_application_commands(self) -> bool:
214
"""Use application commands."""
215
216
@property
217
def request_to_speak(self) -> bool:
218
"""Request to speak in stage channels."""
219
220
@property
221
def manage_events(self) -> bool:
222
"""Manage scheduled events."""
223
224
@property
225
def manage_threads(self) -> bool:
226
"""Manage threads."""
227
228
@property
229
def create_public_threads(self) -> bool:
230
"""Create public threads."""
231
232
@property
233
def create_private_threads(self) -> bool:
234
"""Create private threads."""
235
236
@property
237
def use_external_stickers(self) -> bool:
238
"""Use stickers from other servers."""
239
240
@property
241
def send_messages_in_threads(self) -> bool:
242
"""Send messages in threads."""
243
244
@property
245
def use_embedded_activities(self) -> bool:
246
"""Use activities in voice channels."""
247
248
@property
249
def moderate_members(self) -> bool:
250
"""Moderate members (timeout)."""
251
252
@property
253
def use_soundboard(self) -> bool:
254
"""Use soundboard."""
255
256
@property
257
def create_expressions(self) -> bool:
258
"""Create expressions."""
259
260
@property
261
def use_external_sounds(self) -> bool:
262
"""Use external sounds."""
263
264
@property
265
def send_voice_messages(self) -> bool:
266
"""Send voice messages."""
267
268
@property
269
def set_voice_channel_status(self) -> bool:
270
"""Set voice channel status."""
271
272
def is_subset(self, other: Permissions) -> bool:
273
"""
274
Check if permissions are subset of another.
275
276
Parameters:
277
- other: Permissions to compare with
278
279
Returns:
280
True if this is subset of other
281
"""
282
283
def is_superset(self, other: Permissions) -> bool:
284
"""
285
Check if permissions are superset of another.
286
287
Parameters:
288
- other: Permissions to compare with
289
290
Returns:
291
True if this is superset of other
292
"""
293
294
def is_strict_subset(self, other: Permissions) -> bool:
295
"""
296
Check if permissions are strict subset of another.
297
298
Parameters:
299
- other: Permissions to compare with
300
301
Returns:
302
True if this is strict subset of other
303
"""
304
305
def is_strict_superset(self, other: Permissions) -> bool:
306
"""
307
Check if permissions are strict superset of another.
308
309
Parameters:
310
- other: Permissions to compare with
311
312
Returns:
313
True if this is strict superset of other
314
"""
315
```
316
317
### Permission Overwrites
318
319
Channel-specific permission overrides for roles and members.
320
321
```python { .api }
322
class PermissionOverwrite:
323
def __init__(self, **kwargs):
324
"""
325
Initialize permission overwrite.
326
327
Parameters:
328
- kwargs: Permission flags (can be True, False, or None)
329
"""
330
331
@classmethod
332
def from_pair(cls, allow: Permissions, deny: Permissions) -> PermissionOverwrite:
333
"""
334
Create overwrite from allow/deny permission pairs.
335
336
Parameters:
337
- allow: Allowed permissions
338
- deny: Denied permissions
339
340
Returns:
341
Permission overwrite
342
"""
343
344
def pair(self) -> Tuple[Permissions, Permissions]:
345
"""
346
Get allow/deny permission pair.
347
348
Returns:
349
Tuple of (allow, deny) permissions
350
"""
351
352
def update(self, **kwargs) -> PermissionOverwrite:
353
"""
354
Update overwrite with new values.
355
356
Parameters:
357
- kwargs: Permission flags to update
358
359
Returns:
360
New overwrite object
361
"""
362
363
def is_empty(self) -> bool:
364
"""
365
Check if overwrite has any settings.
366
367
Returns:
368
True if no permissions are set
369
"""
370
371
# All permission properties available as Optional[bool]
372
create_instant_invite: Optional[bool]
373
kick_members: Optional[bool]
374
ban_members: Optional[bool]
375
administrator: Optional[bool]
376
manage_channels: Optional[bool]
377
manage_guild: Optional[bool]
378
add_reactions: Optional[bool]
379
view_audit_log: Optional[bool]
380
priority_speaker: Optional[bool]
381
stream: Optional[bool]
382
view_channel: Optional[bool]
383
send_messages: Optional[bool]
384
send_tts_messages: Optional[bool]
385
manage_messages: Optional[bool]
386
embed_links: Optional[bool]
387
attach_files: Optional[bool]
388
read_message_history: Optional[bool]
389
mention_everyone: Optional[bool]
390
use_external_emojis: Optional[bool]
391
view_guild_insights: Optional[bool]
392
connect: Optional[bool]
393
speak: Optional[bool]
394
mute_members: Optional[bool]
395
deafen_members: Optional[bool]
396
move_members: Optional[bool]
397
use_voice_activation: Optional[bool]
398
change_nickname: Optional[bool]
399
manage_nicknames: Optional[bool]
400
manage_roles: Optional[bool]
401
manage_webhooks: Optional[bool]
402
manage_emojis_and_stickers: Optional[bool]
403
use_application_commands: Optional[bool]
404
request_to_speak: Optional[bool]
405
manage_events: Optional[bool]
406
manage_threads: Optional[bool]
407
create_public_threads: Optional[bool]
408
create_private_threads: Optional[bool]
409
use_external_stickers: Optional[bool]
410
send_messages_in_threads: Optional[bool]
411
use_embedded_activities: Optional[bool]
412
moderate_members: Optional[bool]
413
use_soundboard: Optional[bool]
414
create_expressions: Optional[bool]
415
use_external_sounds: Optional[bool]
416
send_voice_messages: Optional[bool]
417
set_voice_channel_status: Optional[bool]
418
```
419
420
### Role System
421
422
Role-based permission management with hierarchy and role properties.
423
424
```python { .api }
425
class Role:
426
def __init__(self): ...
427
428
id: int
429
name: str
430
guild: Guild
431
color: Colour
432
colour: Colour
433
hoist: bool
434
position: int
435
managed: bool
436
mentionable: bool
437
permissions: Permissions
438
tags: Optional[RoleTags]
439
icon: Optional[Asset]
440
unicode_emoji: Optional[str]
441
flags: RoleFlags
442
443
@property
444
def display_icon(self) -> Optional[Asset]:
445
"""Role display icon."""
446
447
@property
448
def created_at(self) -> datetime:
449
"""When role was created."""
450
451
@property
452
def mention(self) -> str:
453
"""String to mention role."""
454
455
@property
456
def members(self) -> List[Member]:
457
"""Members with this role."""
458
459
def is_default(self) -> bool:
460
"""
461
Check if role is @everyone.
462
463
Returns:
464
True if default role
465
"""
466
467
def is_bot_managed(self) -> bool:
468
"""
469
Check if role is managed by a bot.
470
471
Returns:
472
True if bot managed
473
"""
474
475
def is_premium_subscriber(self) -> bool:
476
"""
477
Check if role is Nitro booster role.
478
479
Returns:
480
True if premium subscriber role
481
"""
482
483
def is_integration(self) -> bool:
484
"""
485
Check if role is integration role.
486
487
Returns:
488
True if integration role
489
"""
490
491
async def edit(
492
self,
493
*,
494
name: str = ...,
495
permissions: Permissions = ...,
496
colour: Union[Colour, int] = ...,
497
color: Union[Colour, int] = ...,
498
hoist: bool = ...,
499
display_icon: Optional[Union[bytes, str]] = ...,
500
unicode_emoji: Optional[str] = ...,
501
mentionable: bool = ...,
502
position: int = ...,
503
reason: Optional[str] = None
504
) -> Role:
505
"""
506
Edit role properties.
507
508
Parameters:
509
- name: Role name
510
- permissions: Role permissions
511
- colour/color: Role color
512
- hoist: Show separately in member list
513
- display_icon: Role icon bytes or unicode emoji
514
- unicode_emoji: Unicode emoji for role icon
515
- mentionable: Allow role mentions
516
- position: Role position in hierarchy
517
- reason: Audit log reason
518
519
Returns:
520
Updated role
521
"""
522
523
async def delete(self, *, reason: Optional[str] = None) -> None:
524
"""
525
Delete the role.
526
527
Parameters:
528
- reason: Audit log reason
529
"""
530
531
class RoleTags:
532
"""Role tag metadata."""
533
534
def __init__(self): ...
535
536
bot_id: Optional[int]
537
integration_id: Optional[int]
538
premium_subscriber: Optional[bool]
539
subscription_listing_id: Optional[int]
540
available_for_purchase: Optional[bool]
541
guild_connections: Optional[bool]
542
543
class RoleFlags:
544
"""Role flags bitfield."""
545
546
def __init__(self, value: int = 0): ...
547
548
@property
549
def in_prompt(self) -> bool:
550
"""Role can be selected in onboarding."""
551
```
552
553
### Security Checks
554
555
Command permission checking system for access control and security.
556
557
```python { .api }
558
def has_role(name: Union[str, int]):
559
"""
560
Check if user has specific role.
561
562
Parameters:
563
- name: Role name or ID
564
565
Returns:
566
Check decorator
567
"""
568
569
def has_any_role(*names: Union[str, int]):
570
"""
571
Check if user has any of the specified roles.
572
573
Parameters:
574
- names: Role names or IDs
575
576
Returns:
577
Check decorator
578
"""
579
580
def has_permissions(**perms: bool):
581
"""
582
Check if user has specific permissions in channel.
583
584
Parameters:
585
- perms: Permission flags that must be True
586
587
Returns:
588
Check decorator
589
"""
590
591
def has_guild_permissions(**perms: bool):
592
"""
593
Check if user has guild-wide permissions.
594
595
Parameters:
596
- perms: Permission flags that must be True
597
598
Returns:
599
Check decorator
600
"""
601
602
def bot_has_permissions(**perms: bool):
603
"""
604
Check if bot has specific permissions in channel.
605
606
Parameters:
607
- perms: Permission flags that must be True
608
609
Returns:
610
Check decorator
611
"""
612
613
def bot_has_guild_permissions(**perms: bool):
614
"""
615
Check if bot has guild-wide permissions.
616
617
Parameters:
618
- perms: Permission flags that must be True
619
620
Returns:
621
Check decorator
622
"""
623
624
def is_owner():
625
"""
626
Check if user is bot owner.
627
628
Returns:
629
Check decorator
630
"""
631
632
def guild_only():
633
"""
634
Check if command is used in a guild.
635
636
Returns:
637
Check decorator
638
"""
639
640
def dm_only():
641
"""
642
Check if command is used in DMs.
643
644
Returns:
645
Check decorator
646
"""
647
648
def is_nsfw():
649
"""
650
Check if channel is marked as NSFW.
651
652
Returns:
653
Check decorator
654
"""
655
656
def check(predicate: Callable[[Context], bool]):
657
"""
658
Create custom permission check.
659
660
Parameters:
661
- predicate: Function that takes context and returns bool
662
663
Returns:
664
Check decorator
665
"""
666
667
def check_any(*checks: Check):
668
"""
669
Check that passes if any of the provided checks pass.
670
671
Parameters:
672
- checks: Check decorators
673
674
Returns:
675
Check decorator
676
"""
677
678
async def has_role_check(ctx: Context, name: Union[str, int]) -> bool:
679
"""
680
Check if context author has role.
681
682
Parameters:
683
- ctx: Command context
684
- name: Role name or ID
685
686
Returns:
687
True if user has role
688
"""
689
690
async def has_any_role_check(ctx: Context, *names: Union[str, int]) -> bool:
691
"""
692
Check if context author has any of the specified roles.
693
694
Parameters:
695
- ctx: Command context
696
- names: Role names or IDs
697
698
Returns:
699
True if user has any role
700
"""
701
702
async def has_permissions_check(ctx: Context, **perms: bool) -> bool:
703
"""
704
Check if context author has permissions.
705
706
Parameters:
707
- ctx: Command context
708
- perms: Required permissions
709
710
Returns:
711
True if user has permissions
712
"""
713
714
async def bot_has_permissions_check(ctx: Context, **perms: bool) -> bool:
715
"""
716
Check if bot has permissions.
717
718
Parameters:
719
- ctx: Command context
720
- perms: Required permissions
721
722
Returns:
723
True if bot has permissions
724
"""
725
726
async def is_owner_check(ctx: Context) -> bool:
727
"""
728
Check if user is bot owner.
729
730
Parameters:
731
- ctx: Command context
732
733
Returns:
734
True if user is owner
735
"""
736
737
class MissingRole(CheckFailure):
738
"""Exception for missing role checks."""
739
740
def __init__(self, missing_role: Union[str, int]): ...
741
742
class MissingAnyRole(CheckFailure):
743
"""Exception for missing any role checks."""
744
745
def __init__(self, missing_roles: List[Union[str, int]]): ...
746
747
class MissingPermissions(CheckFailure):
748
"""Exception for missing permissions."""
749
750
def __init__(self, missing_permissions: List[str]): ...
751
752
class BotMissingPermissions(CheckFailure):
753
"""Exception for bot missing permissions."""
754
755
def __init__(self, missing_permissions: List[str]): ...
756
757
class NotOwner(CheckFailure):
758
"""Exception for non-owner access."""
759
760
class NoPrivateMessage(CheckFailure):
761
"""Exception for guild-only commands in DMs."""
762
763
class PrivateMessageOnly(CheckFailure):
764
"""Exception for DM-only commands in guilds."""
765
766
class NSFWChannelRequired(CheckFailure):
767
"""Exception for NSFW commands in non-NSFW channels."""
768
```
769
770
### Application Command Permissions
771
772
Permission system for slash commands and context menu commands.
773
774
```python { .api }
775
class ApplicationCommandPermissions:
776
"""Application command permissions for a target."""
777
778
def __init__(self): ...
779
780
id: int
781
type: ApplicationCommandPermissionType
782
permission: bool
783
784
@classmethod
785
def from_role(cls, role: Role, permission: bool = True) -> ApplicationCommandPermissions:
786
"""
787
Create permissions from role.
788
789
Parameters:
790
- role: Role target
791
- permission: Whether to allow or deny
792
793
Returns:
794
Application command permissions
795
"""
796
797
@classmethod
798
def from_user(cls, user: Union[User, Member], permission: bool = True) -> ApplicationCommandPermissions:
799
"""
800
Create permissions from user.
801
802
Parameters:
803
- user: User target
804
- permission: Whether to allow or deny
805
806
Returns:
807
Application command permissions
808
"""
809
810
@classmethod
811
def from_channel(cls, channel: GuildChannel, permission: bool = True) -> ApplicationCommandPermissions:
812
"""
813
Create permissions from channel.
814
815
Parameters:
816
- channel: Channel target
817
- permission: Whether to allow or deny
818
819
Returns:
820
Application command permissions
821
"""
822
823
class GuildApplicationCommandPermissions:
824
"""Guild-specific application command permissions."""
825
826
def __init__(self): ...
827
828
id: int
829
application_id: int
830
guild_id: int
831
permissions: List[ApplicationCommandPermissions]
832
833
@property
834
def guild(self) -> Optional[Guild]:
835
"""Guild these permissions apply to."""
836
837
@property
838
def command(self) -> Optional[APIApplicationCommand]:
839
"""Command these permissions apply to."""
840
841
# Permission decorators for application commands
842
def default_member_permissions(**perms: bool):
843
"""
844
Set default member permissions for application commands.
845
846
Parameters:
847
- perms: Required permissions
848
849
Returns:
850
Decorator for application commands
851
"""
852
853
def guild_only():
854
"""Mark application command as guild-only."""
855
856
def dm_permission(enabled: bool = True):
857
"""
858
Set DM permission for application commands.
859
860
Parameters:
861
- enabled: Whether command can be used in DMs
862
863
Returns:
864
Decorator for application commands
865
"""
866
```
867
868
### Security Features
869
870
Additional security features and utilities for bot protection.
871
872
```python { .api }
873
class SecurityManager:
874
"""Security management utilities."""
875
876
def __init__(self, bot: Bot):
877
self.bot = bot
878
self.rate_limits = {}
879
self.blacklisted_users = set()
880
self.trusted_users = set()
881
882
def is_blacklisted(self, user_id: int) -> bool:
883
"""
884
Check if user is blacklisted.
885
886
Parameters:
887
- user_id: User ID to check
888
889
Returns:
890
True if blacklisted
891
"""
892
893
def blacklist_user(self, user_id: int) -> None:
894
"""
895
Add user to blacklist.
896
897
Parameters:
898
- user_id: User ID to blacklist
899
"""
900
901
def whitelist_user(self, user_id: int) -> None:
902
"""
903
Remove user from blacklist.
904
905
Parameters:
906
- user_id: User ID to remove
907
"""
908
909
def is_trusted(self, user_id: int) -> bool:
910
"""
911
Check if user is trusted.
912
913
Parameters:
914
- user_id: User ID to check
915
916
Returns:
917
True if trusted
918
"""
919
920
def add_trusted_user(self, user_id: int) -> None:
921
"""
922
Add user to trusted list.
923
924
Parameters:
925
- user_id: User ID to trust
926
"""
927
928
def check_rate_limit(self, user_id: int, command: str, limit: int = 5, window: int = 60) -> bool:
929
"""
930
Check command rate limit for user.
931
932
Parameters:
933
- user_id: User ID
934
- command: Command name
935
- limit: Maximum uses per window
936
- window: Time window in seconds
937
938
Returns:
939
True if within rate limit
940
"""
941
942
def security_check():
943
"""Security check decorator for sensitive operations."""
944
945
async def predicate(ctx: Context) -> bool:
946
# Check if user is blacklisted
947
if hasattr(ctx.bot, 'security') and ctx.bot.security.is_blacklisted(ctx.author.id):
948
return False
949
950
# Additional security checks
951
return True
952
953
return check(predicate)
954
955
def owner_or_trusted():
956
"""Check for bot owner or trusted user."""
957
958
async def predicate(ctx: Context) -> bool:
959
if await ctx.bot.is_owner(ctx.author):
960
return True
961
962
if hasattr(ctx.bot, 'security') and ctx.bot.security.is_trusted(ctx.author.id):
963
return True
964
965
return False
966
967
return check(predicate)
968
969
def require_hierarchy(target_param: str = 'member'):
970
"""
971
Check role hierarchy for moderation commands.
972
973
Parameters:
974
- target_param: Parameter name for target member
975
976
Returns:
977
Check decorator
978
"""
979
980
def decorator(func):
981
async def wrapper(*args, **kwargs):
982
ctx = args[0] if args else None
983
984
if not isinstance(ctx, Context):
985
return await func(*args, **kwargs)
986
987
# Get target member from parameters
988
target = kwargs.get(target_param)
989
if not isinstance(target, Member):
990
return await func(*args, **kwargs)
991
992
# Check hierarchy
993
if target.top_role >= ctx.author.top_role and ctx.author != ctx.guild.owner:
994
raise CheckFailure("You cannot perform this action on this member due to role hierarchy.")
995
996
if target.top_role >= ctx.me.top_role:
997
raise CheckFailure("I cannot perform this action on this member due to role hierarchy.")
998
999
return await func(*args, **kwargs)
1000
1001
return wrapper
1002
1003
return decorator
1004
1005
def audit_log(action: str):
1006
"""
1007
Log command usage for auditing.
1008
1009
Parameters:
1010
- action: Action description
1011
1012
Returns:
1013
Command decorator
1014
"""
1015
1016
def decorator(func):
1017
async def wrapper(*args, **kwargs):
1018
ctx = args[0] if args else None
1019
1020
if isinstance(ctx, Context):
1021
print(f"AUDIT: {ctx.author} ({ctx.author.id}) used {func.__name__} in {ctx.guild.name if ctx.guild else 'DM'}: {action}")
1022
1023
return await func(*args, **kwargs)
1024
1025
return wrapper
1026
1027
return decorator
1028
```
1029
1030
### OAuth and App Permissions
1031
1032
OAuth URL generation and application permission management.
1033
1034
```python { .api }
1035
def oauth_url(
1036
client_id: int,
1037
*,
1038
permissions: Permissions = None,
1039
guild: Guild = None,
1040
redirect_uri: str = None,
1041
scopes: Iterable[str] = None,
1042
disable_guild_select: bool = False,
1043
state: str = None
1044
) -> str:
1045
"""
1046
Generate OAuth2 authorization URL.
1047
1048
Parameters:
1049
- client_id: Application client ID
1050
- permissions: Bot permissions to request
1051
- guild: Pre-select guild for bot invitation
1052
- redirect_uri: OAuth redirect URI
1053
- scopes: OAuth scopes to request
1054
- disable_guild_select: Disable guild selection
1055
- state: OAuth state parameter
1056
1057
Returns:
1058
OAuth2 URL
1059
"""
1060
1061
class ApplicationFlags:
1062
"""Application flags bitfield."""
1063
1064
def __init__(self, value: int = 0): ...
1065
1066
@property
1067
def application_auto_moderation_rule_create_badge(self) -> bool:
1068
"""Application has auto-moderation rule create badge."""
1069
1070
@property
1071
def gateway_presence(self) -> bool:
1072
"""Application can read presence data."""
1073
1074
@property
1075
def gateway_presence_limited(self) -> bool:
1076
"""Application has limited presence data access."""
1077
1078
@property
1079
def gateway_guild_members(self) -> bool:
1080
"""Application can read guild member data."""
1081
1082
@property
1083
def gateway_guild_members_limited(self) -> bool:
1084
"""Application has limited guild member data access."""
1085
1086
@property
1087
def verification_pending_guild_limit(self) -> bool:
1088
"""Application has unusual growth and pending verification."""
1089
1090
@property
1091
def embedded(self) -> bool:
1092
"""Application is embedded within Discord client."""
1093
1094
@property
1095
def gateway_message_content(self) -> bool:
1096
"""Application can read message content."""
1097
1098
@property
1099
def gateway_message_content_limited(self) -> bool:
1100
"""Application has limited message content access."""
1101
1102
@property
1103
def application_command_badge(self) -> bool:
1104
"""Application has application command badge."""
1105
1106
class AppInfo:
1107
"""Application information."""
1108
1109
def __init__(self): ...
1110
1111
id: int
1112
name: str
1113
icon: Optional[Asset]
1114
description: str
1115
rpc_origins: Optional[List[str]]
1116
bot_public: bool
1117
bot_require_code_grant: bool
1118
bot: Optional[User]
1119
owner: Optional[User]
1120
team: Optional[Team]
1121
summary: str
1122
verify_key: str
1123
guild_id: Optional[int]
1124
guild: Optional[Guild]
1125
primary_sku_id: Optional[int]
1126
slug: Optional[str]
1127
cover_image: Optional[Asset]
1128
flags: ApplicationFlags
1129
approximate_guild_count: Optional[int]
1130
redirect_uris: Optional[List[str]]
1131
interactions_endpoint_url: Optional[str]
1132
role_connections_verification_url: Optional[str]
1133
tags: Optional[List[str]]
1134
install_params: Optional[ApplicationInstallParams]
1135
custom_install_url: Optional[str]
1136
1137
@property
1138
def icon_url(self) -> Optional[str]:
1139
"""Application icon URL."""
1140
1141
@property
1142
def cover_image_url(self) -> Optional[str]:
1143
"""Application cover image URL."""
1144
1145
class ApplicationInstallParams:
1146
"""Application installation parameters."""
1147
1148
def __init__(self): ...
1149
1150
scopes: List[str]
1151
permissions: Permissions
1152
```
1153
1154
## Usage Examples
1155
1156
### Basic Permission Checking
1157
1158
```python
1159
import disnake
1160
from disnake.ext import commands
1161
1162
bot = commands.Bot(command_prefix='!', intents=disnake.Intents.all())
1163
1164
# Basic permission checks
1165
@bot.command()
1166
@commands.has_permissions(manage_messages=True)
1167
async def clear(ctx, amount: int = 5):
1168
"""Clear messages (requires Manage Messages)."""
1169
if amount > 100:
1170
return await ctx.send("Cannot clear more than 100 messages.")
1171
1172
deleted = await ctx.channel.purge(limit=amount)
1173
await ctx.send(f"Cleared {len(deleted)} messages.", delete_after=5)
1174
1175
@bot.command()
1176
@commands.has_guild_permissions(ban_members=True)
1177
@commands.bot_has_guild_permissions(ban_members=True)
1178
async def ban(ctx, member: disnake.Member, *, reason="No reason provided"):
1179
"""Ban a member (requires Ban Members for both user and bot)."""
1180
if member.top_role >= ctx.author.top_role and ctx.author != ctx.guild.owner:
1181
return await ctx.send("You cannot ban this member due to role hierarchy.")
1182
1183
await member.ban(reason=reason)
1184
await ctx.send(f"Banned {member} for: {reason}")
1185
1186
@bot.command()
1187
@commands.has_any_role('Admin', 'Moderator', 'Helper')
1188
async def warn(ctx, member: disnake.Member, *, reason):
1189
"""Warn a member (requires Admin, Moderator, or Helper role)."""
1190
# Implementation here
1191
await ctx.send(f"Warned {member} for: {reason}")
1192
1193
@bot.command()
1194
@commands.is_owner()
1195
async def shutdown(ctx):
1196
"""Shutdown bot (owner only)."""
1197
await ctx.send("Shutting down...")
1198
await bot.close()
1199
1200
@bot.command()
1201
@commands.guild_only()
1202
async def serverinfo(ctx):
1203
"""Server info (guild only)."""
1204
guild = ctx.guild
1205
embed = disnake.Embed(title=guild.name)
1206
embed.add_field(name="Members", value=guild.member_count)
1207
await ctx.send(embed=embed)
1208
1209
@bot.command()
1210
@commands.dm_only()
1211
async def profile(ctx):
1212
"""User profile (DM only for privacy)."""
1213
await ctx.send("Your private profile information...")
1214
1215
@bot.command()
1216
@commands.is_nsfw()
1217
async def nsfw_command(ctx):
1218
"""NSFW command (requires NSFW channel)."""
1219
await ctx.send("This is NSFW content.")
1220
1221
# Error handling for permission failures
1222
@bot.event
1223
async def on_command_error(ctx, error):
1224
if isinstance(error, commands.MissingPermissions):
1225
missing = ', '.join(error.missing_permissions)
1226
await ctx.send(f"You're missing permissions: {missing}")
1227
1228
elif isinstance(error, commands.BotMissingPermissions):
1229
missing = ', '.join(error.missing_permissions)
1230
await ctx.send(f"I'm missing permissions: {missing}")
1231
1232
elif isinstance(error, commands.MissingRole):
1233
await ctx.send(f"You need the {error.missing_role} role to use this command.")
1234
1235
elif isinstance(error, commands.MissingAnyRole):
1236
roles = ', '.join(str(role) for role in error.missing_roles)
1237
await ctx.send(f"You need one of these roles: {roles}")
1238
1239
elif isinstance(error, commands.NotOwner):
1240
await ctx.send("Only the bot owner can use this command.")
1241
1242
elif isinstance(error, commands.NoPrivateMessage):
1243
await ctx.send("This command cannot be used in private messages.")
1244
1245
elif isinstance(error, commands.PrivateMessageOnly):
1246
await ctx.send("This command can only be used in private messages.")
1247
1248
elif isinstance(error, commands.NSFWChannelRequired):
1249
await ctx.send("This command can only be used in NSFW channels.")
1250
```
1251
1252
### Custom Permission Checks
1253
1254
```python
1255
def is_staff():
1256
"""Custom check for staff members."""
1257
async def predicate(ctx):
1258
staff_roles = ['Owner', 'Admin', 'Moderator', 'Helper']
1259
user_roles = [role.name for role in ctx.author.roles]
1260
return any(role in staff_roles for role in user_roles)
1261
1262
return commands.check(predicate)
1263
1264
def has_higher_role_than(target_param='member'):
1265
"""Check if user has higher role than target."""
1266
def predicate(ctx):
1267
target = ctx.kwargs.get(target_param) if hasattr(ctx, 'kwargs') else None
1268
if not isinstance(target, disnake.Member):
1269
return True # Skip check if target not found
1270
1271
return ctx.author.top_role > target.top_role or ctx.author == ctx.guild.owner
1272
1273
return commands.check(predicate)
1274
1275
def channel_locked():
1276
"""Check if channel is in maintenance mode."""
1277
async def predicate(ctx):
1278
# Check if channel has "locked" in topic or name
1279
if hasattr(ctx.channel, 'topic') and ctx.channel.topic:
1280
if 'locked' in ctx.channel.topic.lower():
1281
return False
1282
1283
if 'locked' in ctx.channel.name.lower():
1284
return False
1285
1286
return True
1287
1288
return commands.check(predicate)
1289
1290
def cooldown_bypass():
1291
"""Bypass cooldowns for trusted users."""
1292
def predicate(ctx):
1293
# Check if user should bypass cooldowns
1294
bypass_roles = ['Owner', 'Admin']
1295
user_roles = [role.name for role in ctx.author.roles]
1296
return any(role in bypass_roles for role in user_roles)
1297
1298
return commands.check(predicate)
1299
1300
# Using custom checks
1301
@bot.command()
1302
@is_staff()
1303
@has_higher_role_than('target')
1304
async def timeout(ctx, target: disnake.Member, duration: str, *, reason="No reason"):
1305
"""Timeout a member (staff only, must have higher role)."""
1306
# Parse duration and apply timeout
1307
await ctx.send(f"Timed out {target} for {duration}")
1308
1309
@bot.command()
1310
@channel_locked()
1311
async def chat(ctx, *, message):
1312
"""Chat command (disabled in locked channels)."""
1313
await ctx.send(f"{ctx.author.mention}: {message}")
1314
1315
@bot.command()
1316
@commands.cooldown(1, 30, commands.BucketType.user)
1317
@cooldown_bypass()
1318
async def expensive_command(ctx):
1319
"""Expensive command with cooldown bypass for admins."""
1320
await ctx.send("This is an expensive operation...")
1321
```
1322
1323
### Role Management System
1324
1325
```python
1326
@bot.group()
1327
@commands.has_permissions(manage_roles=True)
1328
async def role(ctx):
1329
"""Role management commands."""
1330
if ctx.invoked_subcommand is None:
1331
await ctx.send_help(ctx.command)
1332
1333
@role.command()
1334
async def create(ctx, name: str, color: disnake.Color = None, hoist: bool = False, mentionable: bool = True):
1335
"""Create a new role with specified properties."""
1336
if color is None:
1337
color = disnake.Color.default()
1338
1339
try:
1340
role = await ctx.guild.create_role(
1341
name=name,
1342
color=color,
1343
hoist=hoist,
1344
mentionable=mentionable,
1345
reason=f"Role created by {ctx.author}"
1346
)
1347
1348
embed = disnake.Embed(
1349
title="Role Created",
1350
description=f"Successfully created {role.mention}",
1351
color=role.color
1352
)
1353
embed.add_field(name="Name", value=role.name)
1354
embed.add_field(name="Color", value=f"#{role.color.value:06x}")
1355
embed.add_field(name="Hoisted", value=hoist)
1356
embed.add_field(name="Mentionable", value=mentionable)
1357
1358
await ctx.send(embed=embed)
1359
1360
except disnake.Forbidden:
1361
await ctx.send("I don't have permission to create roles.")
1362
except disnake.HTTPException as e:
1363
await ctx.send(f"Failed to create role: {e}")
1364
1365
@role.command()
1366
async def delete(ctx, role: disnake.Role):
1367
"""Delete a role."""
1368
if role >= ctx.author.top_role and ctx.author != ctx.guild.owner:
1369
return await ctx.send("You cannot delete this role due to hierarchy.")
1370
1371
if role >= ctx.me.top_role:
1372
return await ctx.send("I cannot delete this role due to hierarchy.")
1373
1374
if role.is_default():
1375
return await ctx.send("Cannot delete the @everyone role.")
1376
1377
role_name = role.name
1378
await role.delete(reason=f"Role deleted by {ctx.author}")
1379
await ctx.send(f"Deleted role: {role_name}")
1380
1381
@role.command()
1382
async def edit(ctx, role: disnake.Role, *, properties):
1383
"""Edit role properties (format: name=NewName color=#ff0000 hoist=true)."""
1384
if role >= ctx.author.top_role and ctx.author != ctx.guild.owner:
1385
return await ctx.send("You cannot edit this role due to hierarchy.")
1386
1387
# Parse properties
1388
changes = {}
1389
for prop in properties.split():
1390
if '=' in prop:
1391
key, value = prop.split('=', 1)
1392
1393
if key == 'name':
1394
changes['name'] = value
1395
elif key == 'color':
1396
try:
1397
if value.startswith('#'):
1398
changes['color'] = disnake.Color(int(value[1:], 16))
1399
else:
1400
changes['color'] = getattr(disnake.Color, value.lower())()
1401
except (ValueError, AttributeError):
1402
return await ctx.send(f"Invalid color: {value}")
1403
elif key in ['hoist', 'mentionable']:
1404
changes[key] = value.lower() in ['true', '1', 'yes']
1405
1406
if not changes:
1407
return await ctx.send("No valid properties provided.")
1408
1409
try:
1410
await role.edit(reason=f"Role edited by {ctx.author}", **changes)
1411
await ctx.send(f"Successfully edited {role.mention}")
1412
except disnake.Forbidden:
1413
await ctx.send("I don't have permission to edit this role.")
1414
except disnake.HTTPException as e:
1415
await ctx.send(f"Failed to edit role: {e}")
1416
1417
@role.command()
1418
async def give(ctx, member: disnake.Member, role: disnake.Role):
1419
"""Give a role to a member."""
1420
if role >= ctx.author.top_role and ctx.author != ctx.guild.owner:
1421
return await ctx.send("You cannot assign this role due to hierarchy.")
1422
1423
if role >= ctx.me.top_role:
1424
return await ctx.send("I cannot assign this role due to hierarchy.")
1425
1426
if role in member.roles:
1427
return await ctx.send(f"{member} already has the {role.name} role.")
1428
1429
try:
1430
await member.add_roles(role, reason=f"Role assigned by {ctx.author}")
1431
await ctx.send(f"Gave {role.name} to {member.mention}")
1432
except disnake.Forbidden:
1433
await ctx.send("I don't have permission to assign roles.")
1434
1435
@role.command()
1436
async def remove(ctx, member: disnake.Member, role: disnake.Role):
1437
"""Remove a role from a member."""
1438
if role >= ctx.author.top_role and ctx.author != ctx.guild.owner:
1439
return await ctx.send("You cannot manage this role due to hierarchy.")
1440
1441
if role not in member.roles:
1442
return await ctx.send(f"{member} doesn't have the {role.name} role.")
1443
1444
try:
1445
await member.remove_roles(role, reason=f"Role removed by {ctx.author}")
1446
await ctx.send(f"Removed {role.name} from {member.mention}")
1447
except disnake.Forbidden:
1448
await ctx.send("I don't have permission to remove roles.")
1449
1450
@role.command()
1451
async def info(ctx, role: disnake.Role):
1452
"""Display detailed role information."""
1453
embed = disnake.Embed(title=f"Role: {role.name}", color=role.color)
1454
1455
embed.add_field(name="ID", value=role.id, inline=True)
1456
embed.add_field(name="Position", value=role.position, inline=True)
1457
embed.add_field(name="Color", value=f"#{role.color.value:06x}", inline=True)
1458
embed.add_field(name="Hoisted", value=role.hoist, inline=True)
1459
embed.add_field(name="Mentionable", value=role.mentionable, inline=True)
1460
embed.add_field(name="Managed", value=role.managed, inline=True)
1461
embed.add_field(name="Members", value=len(role.members), inline=True)
1462
embed.add_field(name="Created", value=f"<t:{int(role.created_at.timestamp())}:F>", inline=True)
1463
1464
if role.tags:
1465
tags = []
1466
if role.tags.bot_id:
1467
tags.append(f"Bot Role (ID: {role.tags.bot_id})")
1468
if role.tags.integration_id:
1469
tags.append(f"Integration Role (ID: {role.tags.integration_id})")
1470
if role.tags.premium_subscriber:
1471
tags.append("Nitro Booster Role")
1472
1473
if tags:
1474
embed.add_field(name="Tags", value="\n".join(tags), inline=False)
1475
1476
# Show permissions
1477
perms = []
1478
for perm, value in role.permissions:
1479
if value:
1480
perms.append(perm.replace('_', ' ').title())
1481
1482
if perms:
1483
embed.add_field(
1484
name=f"Permissions ({len(perms)})",
1485
value=", ".join(perms) if len(", ".join(perms)) < 1024 else f"{len(perms)} permissions",
1486
inline=False
1487
)
1488
1489
await ctx.send(embed=embed)
1490
1491
@role.command()
1492
async def members(ctx, role: disnake.Role):
1493
"""List all members with a specific role."""
1494
members = role.members
1495
1496
if not members:
1497
return await ctx.send(f"No members have the {role.name} role.")
1498
1499
embed = disnake.Embed(
1500
title=f"Members with {role.name} ({len(members)})",
1501
color=role.color
1502
)
1503
1504
# Split members into pages if too many
1505
member_list = [f"{member.mention} ({member})" for member in members]
1506
1507
if len(member_list) <= 20:
1508
embed.description = "\n".join(member_list)
1509
await ctx.send(embed=embed)
1510
else:
1511
# Paginate results
1512
for i in range(0, len(member_list), 20):
1513
page_members = member_list[i:i+20]
1514
embed.description = "\n".join(page_members)
1515
embed.set_footer(text=f"Page {i//20 + 1}/{(len(member_list)-1)//20 + 1}")
1516
await ctx.send(embed=embed)
1517
```
1518
1519
### Permission Overwrite Management
1520
1521
```python
1522
@bot.group()
1523
@commands.has_permissions(manage_channels=True)
1524
async def perms(ctx):
1525
"""Channel permission management."""
1526
if ctx.invoked_subcommand is None:
1527
await ctx.send_help(ctx.command)
1528
1529
@perms.command()
1530
async def allow(ctx, target: Union[disnake.Member, disnake.Role], channel: disnake.GuildChannel = None, *permissions):
1531
"""Allow permissions for a member or role in a channel."""
1532
if channel is None:
1533
channel = ctx.channel
1534
1535
if not permissions:
1536
return await ctx.send("Please specify permissions to allow.")
1537
1538
# Get current overwrites or create new
1539
overwrites = channel.overwrites_for(target)
1540
1541
# Set permissions to True
1542
changes = {}
1543
for perm in permissions:
1544
perm_name = perm.lower().replace('-', '_').replace(' ', '_')
1545
if hasattr(overwrites, perm_name):
1546
changes[perm_name] = True
1547
1548
if not changes:
1549
return await ctx.send("No valid permissions specified.")
1550
1551
# Update overwrites
1552
overwrites.update(**changes)
1553
1554
try:
1555
await channel.set_permissions(target, overwrite=overwrites, reason=f"Permissions allowed by {ctx.author}")
1556
1557
perms_str = ", ".join(changes.keys()).replace('_', ' ').title()
1558
await ctx.send(f"Allowed {perms_str} for {target.mention} in {channel.mention}")
1559
1560
except disnake.Forbidden:
1561
await ctx.send("I don't have permission to manage channel permissions.")
1562
except disnake.HTTPException as e:
1563
await ctx.send(f"Failed to update permissions: {e}")
1564
1565
@perms.command()
1566
async def deny(ctx, target: Union[disnake.Member, disnake.Role], channel: disnake.GuildChannel = None, *permissions):
1567
"""Deny permissions for a member or role in a channel."""
1568
if channel is None:
1569
channel = ctx.channel
1570
1571
if not permissions:
1572
return await ctx.send("Please specify permissions to deny.")
1573
1574
overwrites = channel.overwrites_for(target)
1575
1576
changes = {}
1577
for perm in permissions:
1578
perm_name = perm.lower().replace('-', '_').replace(' ', '_')
1579
if hasattr(overwrites, perm_name):
1580
changes[perm_name] = False
1581
1582
if not changes:
1583
return await ctx.send("No valid permissions specified.")
1584
1585
overwrites.update(**changes)
1586
1587
try:
1588
await channel.set_permissions(target, overwrite=overwrites, reason=f"Permissions denied by {ctx.author}")
1589
1590
perms_str = ", ".join(changes.keys()).replace('_', ' ').title()
1591
await ctx.send(f"Denied {perms_str} for {target.mention} in {channel.mention}")
1592
1593
except disnake.Forbidden:
1594
await ctx.send("I don't have permission to manage channel permissions.")
1595
1596
@perms.command()
1597
async def reset(ctx, target: Union[disnake.Member, disnake.Role], channel: disnake.GuildChannel = None, *permissions):
1598
"""Reset permissions to inherit from role/default for a member or role."""
1599
if channel is None:
1600
channel = ctx.channel
1601
1602
if not permissions:
1603
# Reset all overwrites
1604
try:
1605
await channel.set_permissions(target, overwrite=None, reason=f"All permissions reset by {ctx.author}")
1606
await ctx.send(f"Reset all permissions for {target.mention} in {channel.mention}")
1607
except disnake.Forbidden:
1608
await ctx.send("I don't have permission to manage channel permissions.")
1609
return
1610
1611
overwrites = channel.overwrites_for(target)
1612
1613
changes = {}
1614
for perm in permissions:
1615
perm_name = perm.lower().replace('-', '_').replace(' ', '_')
1616
if hasattr(overwrites, perm_name):
1617
changes[perm_name] = None
1618
1619
if not changes:
1620
return await ctx.send("No valid permissions specified.")
1621
1622
overwrites.update(**changes)
1623
1624
try:
1625
await channel.set_permissions(target, overwrite=overwrites, reason=f"Permissions reset by {ctx.author}")
1626
1627
perms_str = ", ".join(changes.keys()).replace('_', ' ').title()
1628
await ctx.send(f"Reset {perms_str} for {target.mention} in {channel.mention}")
1629
1630
except disnake.Forbidden:
1631
await ctx.send("I don't have permission to manage channel permissions.")
1632
1633
@perms.command()
1634
async def view(ctx, target: Union[disnake.Member, disnake.Role] = None, channel: disnake.GuildChannel = None):
1635
"""View permissions for a member or role in a channel."""
1636
if channel is None:
1637
channel = ctx.channel
1638
1639
if target is None:
1640
target = ctx.author
1641
1642
if isinstance(target, disnake.Member):
1643
permissions = channel.permissions_for(target)
1644
title = f"Permissions for {target} in #{channel.name}"
1645
else:
1646
# For roles, show overwrites
1647
overwrites = channel.overwrites_for(target)
1648
title = f"Permission overwrites for {target} in #{channel.name}"
1649
1650
embed = disnake.Embed(title=title, color=0x00ff00)
1651
1652
if isinstance(target, disnake.Member):
1653
# Show actual permissions
1654
allowed = []
1655
denied = []
1656
1657
for perm, value in permissions:
1658
perm_display = perm.replace('_', ' ').title()
1659
if value:
1660
allowed.append(perm_display)
1661
else:
1662
denied.append(perm_display)
1663
1664
if allowed:
1665
embed.add_field(
1666
name=f"✅ Allowed ({len(allowed)})",
1667
value=", ".join(allowed[:20]) + ("..." if len(allowed) > 20 else ""),
1668
inline=False
1669
)
1670
1671
if denied:
1672
embed.add_field(
1673
name=f"❌ Denied ({len(denied)})",
1674
value=", ".join(denied[:20]) + ("..." if len(denied) > 20 else ""),
1675
inline=False
1676
)
1677
1678
else:
1679
# Show overwrites
1680
allowed_overwrites = []
1681
denied_overwrites = []
1682
neutral_overwrites = []
1683
1684
for perm, value in overwrites:
1685
perm_display = perm.replace('_', ' ').title()
1686
if value is True:
1687
allowed_overwrites.append(perm_display)
1688
elif value is False:
1689
denied_overwrites.append(perm_display)
1690
else:
1691
neutral_overwrites.append(perm_display)
1692
1693
if allowed_overwrites:
1694
embed.add_field(
1695
name="✅ Explicitly Allowed",
1696
value=", ".join(allowed_overwrites),
1697
inline=False
1698
)
1699
1700
if denied_overwrites:
1701
embed.add_field(
1702
name="❌ Explicitly Denied",
1703
value=", ".join(denied_overwrites),
1704
inline=False
1705
)
1706
1707
if not allowed_overwrites and not denied_overwrites:
1708
embed.description = "No permission overwrites set."
1709
1710
await ctx.send(embed=embed)
1711
```
1712
1713
### Security System Implementation
1714
1715
```python
1716
import json
1717
import asyncio
1718
from collections import defaultdict, deque
1719
from datetime import datetime, timedelta
1720
1721
class SecuritySystem:
1722
"""Comprehensive security system for Discord bots."""
1723
1724
def __init__(self, bot):
1725
self.bot = bot
1726
self.blacklist = set()
1727
self.whitelist = set()
1728
self.trusted_guilds = set()
1729
self.rate_limits = defaultdict(lambda: defaultdict(deque))
1730
self.failed_attempts = defaultdict(int)
1731
self.load_security_data()
1732
1733
def load_security_data(self):
1734
"""Load security data from file."""
1735
try:
1736
with open('security.json', 'r') as f:
1737
data = json.load(f)
1738
self.blacklist = set(data.get('blacklist', []))
1739
self.whitelist = set(data.get('whitelist', []))
1740
self.trusted_guilds = set(data.get('trusted_guilds', []))
1741
except FileNotFoundError:
1742
pass
1743
1744
def save_security_data(self):
1745
"""Save security data to file."""
1746
data = {
1747
'blacklist': list(self.blacklist),
1748
'whitelist': list(self.whitelist),
1749
'trusted_guilds': list(self.trusted_guilds)
1750
}
1751
with open('security.json', 'w') as f:
1752
json.dump(data, f, indent=2)
1753
1754
def is_blacklisted(self, user_id: int) -> bool:
1755
"""Check if user is blacklisted."""
1756
return user_id in self.blacklist
1757
1758
def is_whitelisted(self, user_id: int) -> bool:
1759
"""Check if user is whitelisted."""
1760
return user_id in self.whitelist
1761
1762
def is_trusted_guild(self, guild_id: int) -> bool:
1763
"""Check if guild is trusted."""
1764
return guild_id in self.trusted_guilds
1765
1766
def blacklist_user(self, user_id: int, reason: str = None):
1767
"""Add user to blacklist."""
1768
self.blacklist.add(user_id)
1769
self.save_security_data()
1770
print(f"Blacklisted user {user_id}: {reason}")
1771
1772
def whitelist_user(self, user_id: int):
1773
"""Add user to whitelist."""
1774
self.whitelist.add(user_id)
1775
self.blacklist.discard(user_id)
1776
self.save_security_data()
1777
1778
def check_rate_limit(self, user_id: int, action: str, limit: int = 5, window: int = 60) -> bool:
1779
"""Check if user is within rate limit for action."""
1780
now = datetime.utcnow()
1781
user_actions = self.rate_limits[user_id][action]
1782
1783
# Remove old entries
1784
while user_actions and user_actions[0] < now - timedelta(seconds=window):
1785
user_actions.popleft()
1786
1787
# Check limit
1788
if len(user_actions) >= limit:
1789
self.failed_attempts[user_id] += 1
1790
1791
# Auto-blacklist after too many failures
1792
if self.failed_attempts[user_id] >= 10:
1793
self.blacklist_user(user_id, "Excessive rate limit violations")
1794
1795
return False
1796
1797
# Add current attempt
1798
user_actions.append(now)
1799
return True
1800
1801
def log_suspicious_activity(self, user_id: int, guild_id: int, activity: str):
1802
"""Log suspicious activity."""
1803
timestamp = datetime.utcnow().isoformat()
1804
print(f"SECURITY WARNING [{timestamp}]: User {user_id} in guild {guild_id}: {activity}")
1805
1806
# Could save to database or send to logging channel
1807
1808
def scan_message(self, message: disnake.Message) -> bool:
1809
"""Scan message for security threats."""
1810
content = message.content.lower()
1811
1812
# Check for common threats
1813
threats = [
1814
'discord.gg/', # Invite links
1815
'http://', # Potentially unsafe links
1816
'@everyone', # Mass mentions
1817
'nitro', # Scam keywords
1818
'free', # Scam keywords
1819
]
1820
1821
threat_count = sum(1 for threat in threats if threat in content)
1822
1823
if threat_count >= 2:
1824
self.log_suspicious_activity(
1825
message.author.id,
1826
message.guild.id if message.guild else 0,
1827
f"Suspicious message content (threats: {threat_count})"
1828
)
1829
return False
1830
1831
return True
1832
1833
# Initialize security system
1834
security = SecuritySystem(bot)
1835
1836
# Security middleware
1837
@bot.check
1838
async def security_check(ctx):
1839
"""Global security check for all commands."""
1840
# Skip checks for whitelisted users
1841
if security.is_whitelisted(ctx.author.id):
1842
return True
1843
1844
# Block blacklisted users
1845
if security.is_blacklisted(ctx.author.id):
1846
await ctx.send("You are not authorized to use this bot.", delete_after=5)
1847
return False
1848
1849
# Rate limit check
1850
if not security.check_rate_limit(ctx.author.id, 'command', limit=10, window=60):
1851
await ctx.send("You're being rate limited. Please slow down.", delete_after=5)
1852
return False
1853
1854
return True
1855
1856
@bot.event
1857
async def on_message(message):
1858
"""Security monitoring for messages."""
1859
if message.author.bot:
1860
return
1861
1862
# Security scan
1863
if not security.scan_message(message):
1864
try:
1865
await message.delete()
1866
await message.channel.send(
1867
f"{message.author.mention}, your message was flagged by security systems.",
1868
delete_after=10
1869
)
1870
except disnake.NotFound:
1871
pass
1872
1873
await bot.process_commands(message)
1874
1875
# Security management commands
1876
@bot.group()
1877
@commands.is_owner()
1878
async def security(ctx):
1879
"""Security management commands."""
1880
if ctx.invoked_subcommand is None:
1881
await ctx.send_help(ctx.command)
1882
1883
@security.command()
1884
async def blacklist(ctx, user: disnake.User, *, reason="No reason provided"):
1885
"""Blacklist a user."""
1886
security.blacklist_user(user.id, reason)
1887
await ctx.send(f"Blacklisted {user} ({user.id}): {reason}")
1888
1889
@security.command()
1890
async def whitelist(ctx, user: disnake.User):
1891
"""Whitelist a user."""
1892
security.whitelist_user(user.id)
1893
await ctx.send(f"Whitelisted {user} ({user.id})")
1894
1895
@security.command()
1896
async def status(ctx, user: disnake.User = None):
1897
"""Check security status of a user."""
1898
if user is None:
1899
user = ctx.author
1900
1901
embed = disnake.Embed(title=f"Security Status: {user}")
1902
1903
if security.is_blacklisted(user.id):
1904
embed.color = 0xff0000
1905
embed.add_field(name="Status", value="❌ BLACKLISTED", inline=False)
1906
elif security.is_whitelisted(user.id):
1907
embed.color = 0x00ff00
1908
embed.add_field(name="Status", value="✅ WHITELISTED", inline=False)
1909
else:
1910
embed.color = 0xffff00
1911
embed.add_field(name="Status", value="⚠️ NORMAL", inline=False)
1912
1913
# Rate limit info
1914
command_attempts = len(security.rate_limits[user.id]['command'])
1915
embed.add_field(name="Recent Commands", value=f"{command_attempts}/10 (last 60s)", inline=True)
1916
1917
failed_attempts = security.failed_attempts.get(user.id, 0)
1918
embed.add_field(name="Failed Attempts", value=failed_attempts, inline=True)
1919
1920
await ctx.send(embed=embed)
1921
1922
@security.command()
1923
async def trust_guild(ctx, guild_id: int = None):
1924
"""Mark a guild as trusted."""
1925
if guild_id is None:
1926
guild_id = ctx.guild.id
1927
1928
security.trusted_guilds.add(guild_id)
1929
security.save_security_data()
1930
1931
guild = bot.get_guild(guild_id)
1932
guild_name = guild.name if guild else f"Guild {guild_id}"
1933
await ctx.send(f"Marked {guild_name} as trusted.")
1934
1935
@security.command()
1936
async def stats(ctx):
1937
"""Show security statistics."""
1938
embed = disnake.Embed(title="Security Statistics", color=0x0099ff)
1939
1940
embed.add_field(name="Blacklisted Users", value=len(security.blacklist), inline=True)
1941
embed.add_field(name="Whitelisted Users", value=len(security.whitelist), inline=True)
1942
embed.add_field(name="Trusted Guilds", value=len(security.trusted_guilds), inline=True)
1943
1944
active_rate_limits = len([uid for uid, actions in security.rate_limits.items() if any(actions.values())])
1945
embed.add_field(name="Active Rate Limits", value=active_rate_limits, inline=True)
1946
1947
total_failures = sum(security.failed_attempts.values())
1948
embed.add_field(name="Total Failed Attempts", value=total_failures, inline=True)
1949
1950
await ctx.send(embed=embed)
1951
```