A modern, easy-to-use, feature-rich async-ready API wrapper for Discord written in Python
—
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.
Discord's bitfield-based permission system for fine-grained access control.
class Permissions:
def __init__(self, value: int = 0, **kwargs):
"""
Initialize permissions object.
Parameters:
- value: Raw permission integer value
- kwargs: Individual permission flags
"""
value: int
@classmethod
def none(cls) -> Permissions:
"""Create permissions with no flags set."""
@classmethod
def all(cls) -> Permissions:
"""Create permissions with all flags set."""
@classmethod
def all_channel(cls) -> Permissions:
"""Create permissions with all channel-applicable flags."""
@classmethod
def general(cls) -> Permissions:
"""Create permissions with general user flags."""
@classmethod
def text(cls) -> Permissions:
"""Create permissions for text channels."""
@classmethod
def voice(cls) -> Permissions:
"""Create permissions for voice channels."""
@classmethod
def stage(cls) -> Permissions:
"""Create permissions for stage channels."""
@classmethod
def stage_moderator(cls) -> Permissions:
"""Create stage moderator permissions."""
@classmethod
def advanced(cls) -> Permissions:
"""Create advanced user permissions."""
def update(self, **kwargs) -> Permissions:
"""
Update permissions with new values.
Parameters:
- kwargs: Permission flags to update
Returns:
New permissions object
"""
def handle_overwrite(self, allow: int, deny: int) -> None:
"""
Apply permission overwrite.
Parameters:
- allow: Allowed permission bits
- deny: Denied permission bits
"""
# General Permissions
@property
def create_instant_invite(self) -> bool:
"""Create instant invite."""
@property
def kick_members(self) -> bool:
"""Kick members."""
@property
def ban_members(self) -> bool:
"""Ban members."""
@property
def administrator(self) -> bool:
"""Administrator (bypasses all permission checks)."""
@property
def manage_channels(self) -> bool:
"""Manage channels (create, edit, delete)."""
@property
def manage_guild(self) -> bool:
"""Manage server settings."""
@property
def add_reactions(self) -> bool:
"""Add reactions to messages."""
@property
def view_audit_log(self) -> bool:
"""View audit log."""
@property
def priority_speaker(self) -> bool:
"""Priority speaker in voice channels."""
@property
def stream(self) -> bool:
"""Video stream in voice channels."""
@property
def view_channel(self) -> bool:
"""View channels."""
@property
def send_messages(self) -> bool:
"""Send messages in text channels."""
@property
def send_tts_messages(self) -> bool:
"""Send text-to-speech messages."""
@property
def manage_messages(self) -> bool:
"""Manage messages (delete, pin)."""
@property
def embed_links(self) -> bool:
"""Embed links in messages."""
@property
def attach_files(self) -> bool:
"""Attach files to messages."""
@property
def read_message_history(self) -> bool:
"""Read message history."""
@property
def mention_everyone(self) -> bool:
"""Mention @everyone, @here, and all roles."""
@property
def use_external_emojis(self) -> bool:
"""Use emojis from other servers."""
@property
def view_guild_insights(self) -> bool:
"""View server insights."""
@property
def connect(self) -> bool:
"""Connect to voice channels."""
@property
def speak(self) -> bool:
"""Speak in voice channels."""
@property
def mute_members(self) -> bool:
"""Mute members in voice channels."""
@property
def deafen_members(self) -> bool:
"""Deafen members in voice channels."""
@property
def move_members(self) -> bool:
"""Move members between voice channels."""
@property
def use_voice_activation(self) -> bool:
"""Use voice activation (push-to-talk not required)."""
@property
def change_nickname(self) -> bool:
"""Change own nickname."""
@property
def manage_nicknames(self) -> bool:
"""Manage other members' nicknames."""
@property
def manage_roles(self) -> bool:
"""Manage roles and permissions."""
@property
def manage_webhooks(self) -> bool:
"""Manage webhooks."""
@property
def manage_emojis_and_stickers(self) -> bool:
"""Manage emojis and stickers."""
@property
def manage_emojis(self) -> bool:
"""Manage emojis (legacy alias)."""
@property
def use_slash_commands(self) -> bool:
"""Use slash commands."""
@property
def use_application_commands(self) -> bool:
"""Use application commands."""
@property
def request_to_speak(self) -> bool:
"""Request to speak in stage channels."""
@property
def manage_events(self) -> bool:
"""Manage scheduled events."""
@property
def manage_threads(self) -> bool:
"""Manage threads."""
@property
def create_public_threads(self) -> bool:
"""Create public threads."""
@property
def create_private_threads(self) -> bool:
"""Create private threads."""
@property
def use_external_stickers(self) -> bool:
"""Use stickers from other servers."""
@property
def send_messages_in_threads(self) -> bool:
"""Send messages in threads."""
@property
def use_embedded_activities(self) -> bool:
"""Use activities in voice channels."""
@property
def moderate_members(self) -> bool:
"""Moderate members (timeout)."""
@property
def use_soundboard(self) -> bool:
"""Use soundboard."""
@property
def create_expressions(self) -> bool:
"""Create expressions."""
@property
def use_external_sounds(self) -> bool:
"""Use external sounds."""
@property
def send_voice_messages(self) -> bool:
"""Send voice messages."""
@property
def set_voice_channel_status(self) -> bool:
"""Set voice channel status."""
def is_subset(self, other: Permissions) -> bool:
"""
Check if permissions are subset of another.
Parameters:
- other: Permissions to compare with
Returns:
True if this is subset of other
"""
def is_superset(self, other: Permissions) -> bool:
"""
Check if permissions are superset of another.
Parameters:
- other: Permissions to compare with
Returns:
True if this is superset of other
"""
def is_strict_subset(self, other: Permissions) -> bool:
"""
Check if permissions are strict subset of another.
Parameters:
- other: Permissions to compare with
Returns:
True if this is strict subset of other
"""
def is_strict_superset(self, other: Permissions) -> bool:
"""
Check if permissions are strict superset of another.
Parameters:
- other: Permissions to compare with
Returns:
True if this is strict superset of other
"""Channel-specific permission overrides for roles and members.
class PermissionOverwrite:
def __init__(self, **kwargs):
"""
Initialize permission overwrite.
Parameters:
- kwargs: Permission flags (can be True, False, or None)
"""
@classmethod
def from_pair(cls, allow: Permissions, deny: Permissions) -> PermissionOverwrite:
"""
Create overwrite from allow/deny permission pairs.
Parameters:
- allow: Allowed permissions
- deny: Denied permissions
Returns:
Permission overwrite
"""
def pair(self) -> Tuple[Permissions, Permissions]:
"""
Get allow/deny permission pair.
Returns:
Tuple of (allow, deny) permissions
"""
def update(self, **kwargs) -> PermissionOverwrite:
"""
Update overwrite with new values.
Parameters:
- kwargs: Permission flags to update
Returns:
New overwrite object
"""
def is_empty(self) -> bool:
"""
Check if overwrite has any settings.
Returns:
True if no permissions are set
"""
# All permission properties available as Optional[bool]
create_instant_invite: Optional[bool]
kick_members: Optional[bool]
ban_members: Optional[bool]
administrator: Optional[bool]
manage_channels: Optional[bool]
manage_guild: Optional[bool]
add_reactions: Optional[bool]
view_audit_log: Optional[bool]
priority_speaker: Optional[bool]
stream: Optional[bool]
view_channel: Optional[bool]
send_messages: Optional[bool]
send_tts_messages: Optional[bool]
manage_messages: Optional[bool]
embed_links: Optional[bool]
attach_files: Optional[bool]
read_message_history: Optional[bool]
mention_everyone: Optional[bool]
use_external_emojis: Optional[bool]
view_guild_insights: Optional[bool]
connect: Optional[bool]
speak: Optional[bool]
mute_members: Optional[bool]
deafen_members: Optional[bool]
move_members: Optional[bool]
use_voice_activation: Optional[bool]
change_nickname: Optional[bool]
manage_nicknames: Optional[bool]
manage_roles: Optional[bool]
manage_webhooks: Optional[bool]
manage_emojis_and_stickers: Optional[bool]
use_application_commands: Optional[bool]
request_to_speak: Optional[bool]
manage_events: Optional[bool]
manage_threads: Optional[bool]
create_public_threads: Optional[bool]
create_private_threads: Optional[bool]
use_external_stickers: Optional[bool]
send_messages_in_threads: Optional[bool]
use_embedded_activities: Optional[bool]
moderate_members: Optional[bool]
use_soundboard: Optional[bool]
create_expressions: Optional[bool]
use_external_sounds: Optional[bool]
send_voice_messages: Optional[bool]
set_voice_channel_status: Optional[bool]Role-based permission management with hierarchy and role properties.
class Role:
def __init__(self): ...
id: int
name: str
guild: Guild
color: Colour
colour: Colour
hoist: bool
position: int
managed: bool
mentionable: bool
permissions: Permissions
tags: Optional[RoleTags]
icon: Optional[Asset]
unicode_emoji: Optional[str]
flags: RoleFlags
@property
def display_icon(self) -> Optional[Asset]:
"""Role display icon."""
@property
def created_at(self) -> datetime:
"""When role was created."""
@property
def mention(self) -> str:
"""String to mention role."""
@property
def members(self) -> List[Member]:
"""Members with this role."""
def is_default(self) -> bool:
"""
Check if role is @everyone.
Returns:
True if default role
"""
def is_bot_managed(self) -> bool:
"""
Check if role is managed by a bot.
Returns:
True if bot managed
"""
def is_premium_subscriber(self) -> bool:
"""
Check if role is Nitro booster role.
Returns:
True if premium subscriber role
"""
def is_integration(self) -> bool:
"""
Check if role is integration role.
Returns:
True if integration role
"""
async def edit(
self,
*,
name: str = ...,
permissions: Permissions = ...,
colour: Union[Colour, int] = ...,
color: Union[Colour, int] = ...,
hoist: bool = ...,
display_icon: Optional[Union[bytes, str]] = ...,
unicode_emoji: Optional[str] = ...,
mentionable: bool = ...,
position: int = ...,
reason: Optional[str] = None
) -> Role:
"""
Edit role properties.
Parameters:
- name: Role name
- permissions: Role permissions
- colour/color: Role color
- hoist: Show separately in member list
- display_icon: Role icon bytes or unicode emoji
- unicode_emoji: Unicode emoji for role icon
- mentionable: Allow role mentions
- position: Role position in hierarchy
- reason: Audit log reason
Returns:
Updated role
"""
async def delete(self, *, reason: Optional[str] = None) -> None:
"""
Delete the role.
Parameters:
- reason: Audit log reason
"""
class RoleTags:
"""Role tag metadata."""
def __init__(self): ...
bot_id: Optional[int]
integration_id: Optional[int]
premium_subscriber: Optional[bool]
subscription_listing_id: Optional[int]
available_for_purchase: Optional[bool]
guild_connections: Optional[bool]
class RoleFlags:
"""Role flags bitfield."""
def __init__(self, value: int = 0): ...
@property
def in_prompt(self) -> bool:
"""Role can be selected in onboarding."""Command permission checking system for access control and security.
def has_role(name: Union[str, int]):
"""
Check if user has specific role.
Parameters:
- name: Role name or ID
Returns:
Check decorator
"""
def has_any_role(*names: Union[str, int]):
"""
Check if user has any of the specified roles.
Parameters:
- names: Role names or IDs
Returns:
Check decorator
"""
def has_permissions(**perms: bool):
"""
Check if user has specific permissions in channel.
Parameters:
- perms: Permission flags that must be True
Returns:
Check decorator
"""
def has_guild_permissions(**perms: bool):
"""
Check if user has guild-wide permissions.
Parameters:
- perms: Permission flags that must be True
Returns:
Check decorator
"""
def bot_has_permissions(**perms: bool):
"""
Check if bot has specific permissions in channel.
Parameters:
- perms: Permission flags that must be True
Returns:
Check decorator
"""
def bot_has_guild_permissions(**perms: bool):
"""
Check if bot has guild-wide permissions.
Parameters:
- perms: Permission flags that must be True
Returns:
Check decorator
"""
def is_owner():
"""
Check if user is bot owner.
Returns:
Check decorator
"""
def guild_only():
"""
Check if command is used in a guild.
Returns:
Check decorator
"""
def dm_only():
"""
Check if command is used in DMs.
Returns:
Check decorator
"""
def is_nsfw():
"""
Check if channel is marked as NSFW.
Returns:
Check decorator
"""
def check(predicate: Callable[[Context], bool]):
"""
Create custom permission check.
Parameters:
- predicate: Function that takes context and returns bool
Returns:
Check decorator
"""
def check_any(*checks: Check):
"""
Check that passes if any of the provided checks pass.
Parameters:
- checks: Check decorators
Returns:
Check decorator
"""
async def has_role_check(ctx: Context, name: Union[str, int]) -> bool:
"""
Check if context author has role.
Parameters:
- ctx: Command context
- name: Role name or ID
Returns:
True if user has role
"""
async def has_any_role_check(ctx: Context, *names: Union[str, int]) -> bool:
"""
Check if context author has any of the specified roles.
Parameters:
- ctx: Command context
- names: Role names or IDs
Returns:
True if user has any role
"""
async def has_permissions_check(ctx: Context, **perms: bool) -> bool:
"""
Check if context author has permissions.
Parameters:
- ctx: Command context
- perms: Required permissions
Returns:
True if user has permissions
"""
async def bot_has_permissions_check(ctx: Context, **perms: bool) -> bool:
"""
Check if bot has permissions.
Parameters:
- ctx: Command context
- perms: Required permissions
Returns:
True if bot has permissions
"""
async def is_owner_check(ctx: Context) -> bool:
"""
Check if user is bot owner.
Parameters:
- ctx: Command context
Returns:
True if user is owner
"""
class MissingRole(CheckFailure):
"""Exception for missing role checks."""
def __init__(self, missing_role: Union[str, int]): ...
class MissingAnyRole(CheckFailure):
"""Exception for missing any role checks."""
def __init__(self, missing_roles: List[Union[str, int]]): ...
class MissingPermissions(CheckFailure):
"""Exception for missing permissions."""
def __init__(self, missing_permissions: List[str]): ...
class BotMissingPermissions(CheckFailure):
"""Exception for bot missing permissions."""
def __init__(self, missing_permissions: List[str]): ...
class NotOwner(CheckFailure):
"""Exception for non-owner access."""
class NoPrivateMessage(CheckFailure):
"""Exception for guild-only commands in DMs."""
class PrivateMessageOnly(CheckFailure):
"""Exception for DM-only commands in guilds."""
class NSFWChannelRequired(CheckFailure):
"""Exception for NSFW commands in non-NSFW channels."""Permission system for slash commands and context menu commands.
class ApplicationCommandPermissions:
"""Application command permissions for a target."""
def __init__(self): ...
id: int
type: ApplicationCommandPermissionType
permission: bool
@classmethod
def from_role(cls, role: Role, permission: bool = True) -> ApplicationCommandPermissions:
"""
Create permissions from role.
Parameters:
- role: Role target
- permission: Whether to allow or deny
Returns:
Application command permissions
"""
@classmethod
def from_user(cls, user: Union[User, Member], permission: bool = True) -> ApplicationCommandPermissions:
"""
Create permissions from user.
Parameters:
- user: User target
- permission: Whether to allow or deny
Returns:
Application command permissions
"""
@classmethod
def from_channel(cls, channel: GuildChannel, permission: bool = True) -> ApplicationCommandPermissions:
"""
Create permissions from channel.
Parameters:
- channel: Channel target
- permission: Whether to allow or deny
Returns:
Application command permissions
"""
class GuildApplicationCommandPermissions:
"""Guild-specific application command permissions."""
def __init__(self): ...
id: int
application_id: int
guild_id: int
permissions: List[ApplicationCommandPermissions]
@property
def guild(self) -> Optional[Guild]:
"""Guild these permissions apply to."""
@property
def command(self) -> Optional[APIApplicationCommand]:
"""Command these permissions apply to."""
# Permission decorators for application commands
def default_member_permissions(**perms: bool):
"""
Set default member permissions for application commands.
Parameters:
- perms: Required permissions
Returns:
Decorator for application commands
"""
def guild_only():
"""Mark application command as guild-only."""
def dm_permission(enabled: bool = True):
"""
Set DM permission for application commands.
Parameters:
- enabled: Whether command can be used in DMs
Returns:
Decorator for application commands
"""Additional security features and utilities for bot protection.
class SecurityManager:
"""Security management utilities."""
def __init__(self, bot: Bot):
self.bot = bot
self.rate_limits = {}
self.blacklisted_users = set()
self.trusted_users = set()
def is_blacklisted(self, user_id: int) -> bool:
"""
Check if user is blacklisted.
Parameters:
- user_id: User ID to check
Returns:
True if blacklisted
"""
def blacklist_user(self, user_id: int) -> None:
"""
Add user to blacklist.
Parameters:
- user_id: User ID to blacklist
"""
def whitelist_user(self, user_id: int) -> None:
"""
Remove user from blacklist.
Parameters:
- user_id: User ID to remove
"""
def is_trusted(self, user_id: int) -> bool:
"""
Check if user is trusted.
Parameters:
- user_id: User ID to check
Returns:
True if trusted
"""
def add_trusted_user(self, user_id: int) -> None:
"""
Add user to trusted list.
Parameters:
- user_id: User ID to trust
"""
def check_rate_limit(self, user_id: int, command: str, limit: int = 5, window: int = 60) -> bool:
"""
Check command rate limit for user.
Parameters:
- user_id: User ID
- command: Command name
- limit: Maximum uses per window
- window: Time window in seconds
Returns:
True if within rate limit
"""
def security_check():
"""Security check decorator for sensitive operations."""
async def predicate(ctx: Context) -> bool:
# Check if user is blacklisted
if hasattr(ctx.bot, 'security') and ctx.bot.security.is_blacklisted(ctx.author.id):
return False
# Additional security checks
return True
return check(predicate)
def owner_or_trusted():
"""Check for bot owner or trusted user."""
async def predicate(ctx: Context) -> bool:
if await ctx.bot.is_owner(ctx.author):
return True
if hasattr(ctx.bot, 'security') and ctx.bot.security.is_trusted(ctx.author.id):
return True
return False
return check(predicate)
def require_hierarchy(target_param: str = 'member'):
"""
Check role hierarchy for moderation commands.
Parameters:
- target_param: Parameter name for target member
Returns:
Check decorator
"""
def decorator(func):
async def wrapper(*args, **kwargs):
ctx = args[0] if args else None
if not isinstance(ctx, Context):
return await func(*args, **kwargs)
# Get target member from parameters
target = kwargs.get(target_param)
if not isinstance(target, Member):
return await func(*args, **kwargs)
# Check hierarchy
if target.top_role >= ctx.author.top_role and ctx.author != ctx.guild.owner:
raise CheckFailure("You cannot perform this action on this member due to role hierarchy.")
if target.top_role >= ctx.me.top_role:
raise CheckFailure("I cannot perform this action on this member due to role hierarchy.")
return await func(*args, **kwargs)
return wrapper
return decorator
def audit_log(action: str):
"""
Log command usage for auditing.
Parameters:
- action: Action description
Returns:
Command decorator
"""
def decorator(func):
async def wrapper(*args, **kwargs):
ctx = args[0] if args else None
if isinstance(ctx, Context):
print(f"AUDIT: {ctx.author} ({ctx.author.id}) used {func.__name__} in {ctx.guild.name if ctx.guild else 'DM'}: {action}")
return await func(*args, **kwargs)
return wrapper
return decoratorOAuth URL generation and application permission management.
def oauth_url(
client_id: int,
*,
permissions: Permissions = None,
guild: Guild = None,
redirect_uri: str = None,
scopes: Iterable[str] = None,
disable_guild_select: bool = False,
state: str = None
) -> str:
"""
Generate OAuth2 authorization URL.
Parameters:
- client_id: Application client ID
- permissions: Bot permissions to request
- guild: Pre-select guild for bot invitation
- redirect_uri: OAuth redirect URI
- scopes: OAuth scopes to request
- disable_guild_select: Disable guild selection
- state: OAuth state parameter
Returns:
OAuth2 URL
"""
class ApplicationFlags:
"""Application flags bitfield."""
def __init__(self, value: int = 0): ...
@property
def application_auto_moderation_rule_create_badge(self) -> bool:
"""Application has auto-moderation rule create badge."""
@property
def gateway_presence(self) -> bool:
"""Application can read presence data."""
@property
def gateway_presence_limited(self) -> bool:
"""Application has limited presence data access."""
@property
def gateway_guild_members(self) -> bool:
"""Application can read guild member data."""
@property
def gateway_guild_members_limited(self) -> bool:
"""Application has limited guild member data access."""
@property
def verification_pending_guild_limit(self) -> bool:
"""Application has unusual growth and pending verification."""
@property
def embedded(self) -> bool:
"""Application is embedded within Discord client."""
@property
def gateway_message_content(self) -> bool:
"""Application can read message content."""
@property
def gateway_message_content_limited(self) -> bool:
"""Application has limited message content access."""
@property
def application_command_badge(self) -> bool:
"""Application has application command badge."""
class AppInfo:
"""Application information."""
def __init__(self): ...
id: int
name: str
icon: Optional[Asset]
description: str
rpc_origins: Optional[List[str]]
bot_public: bool
bot_require_code_grant: bool
bot: Optional[User]
owner: Optional[User]
team: Optional[Team]
summary: str
verify_key: str
guild_id: Optional[int]
guild: Optional[Guild]
primary_sku_id: Optional[int]
slug: Optional[str]
cover_image: Optional[Asset]
flags: ApplicationFlags
approximate_guild_count: Optional[int]
redirect_uris: Optional[List[str]]
interactions_endpoint_url: Optional[str]
role_connections_verification_url: Optional[str]
tags: Optional[List[str]]
install_params: Optional[ApplicationInstallParams]
custom_install_url: Optional[str]
@property
def icon_url(self) -> Optional[str]:
"""Application icon URL."""
@property
def cover_image_url(self) -> Optional[str]:
"""Application cover image URL."""
class ApplicationInstallParams:
"""Application installation parameters."""
def __init__(self): ...
scopes: List[str]
permissions: Permissionsimport disnake
from disnake.ext import commands
bot = commands.Bot(command_prefix='!', intents=disnake.Intents.all())
# Basic permission checks
@bot.command()
@commands.has_permissions(manage_messages=True)
async def clear(ctx, amount: int = 5):
"""Clear messages (requires Manage Messages)."""
if amount > 100:
return await ctx.send("Cannot clear more than 100 messages.")
deleted = await ctx.channel.purge(limit=amount)
await ctx.send(f"Cleared {len(deleted)} messages.", delete_after=5)
@bot.command()
@commands.has_guild_permissions(ban_members=True)
@commands.bot_has_guild_permissions(ban_members=True)
async def ban(ctx, member: disnake.Member, *, reason="No reason provided"):
"""Ban a member (requires Ban Members for both user and bot)."""
if member.top_role >= ctx.author.top_role and ctx.author != ctx.guild.owner:
return await ctx.send("You cannot ban this member due to role hierarchy.")
await member.ban(reason=reason)
await ctx.send(f"Banned {member} for: {reason}")
@bot.command()
@commands.has_any_role('Admin', 'Moderator', 'Helper')
async def warn(ctx, member: disnake.Member, *, reason):
"""Warn a member (requires Admin, Moderator, or Helper role)."""
# Implementation here
await ctx.send(f"Warned {member} for: {reason}")
@bot.command()
@commands.is_owner()
async def shutdown(ctx):
"""Shutdown bot (owner only)."""
await ctx.send("Shutting down...")
await bot.close()
@bot.command()
@commands.guild_only()
async def serverinfo(ctx):
"""Server info (guild only)."""
guild = ctx.guild
embed = disnake.Embed(title=guild.name)
embed.add_field(name="Members", value=guild.member_count)
await ctx.send(embed=embed)
@bot.command()
@commands.dm_only()
async def profile(ctx):
"""User profile (DM only for privacy)."""
await ctx.send("Your private profile information...")
@bot.command()
@commands.is_nsfw()
async def nsfw_command(ctx):
"""NSFW command (requires NSFW channel)."""
await ctx.send("This is NSFW content.")
# Error handling for permission failures
@bot.event
async def on_command_error(ctx, error):
if isinstance(error, commands.MissingPermissions):
missing = ', '.join(error.missing_permissions)
await ctx.send(f"You're missing permissions: {missing}")
elif isinstance(error, commands.BotMissingPermissions):
missing = ', '.join(error.missing_permissions)
await ctx.send(f"I'm missing permissions: {missing}")
elif isinstance(error, commands.MissingRole):
await ctx.send(f"You need the {error.missing_role} role to use this command.")
elif isinstance(error, commands.MissingAnyRole):
roles = ', '.join(str(role) for role in error.missing_roles)
await ctx.send(f"You need one of these roles: {roles}")
elif isinstance(error, commands.NotOwner):
await ctx.send("Only the bot owner can use this command.")
elif isinstance(error, commands.NoPrivateMessage):
await ctx.send("This command cannot be used in private messages.")
elif isinstance(error, commands.PrivateMessageOnly):
await ctx.send("This command can only be used in private messages.")
elif isinstance(error, commands.NSFWChannelRequired):
await ctx.send("This command can only be used in NSFW channels.")def is_staff():
"""Custom check for staff members."""
async def predicate(ctx):
staff_roles = ['Owner', 'Admin', 'Moderator', 'Helper']
user_roles = [role.name for role in ctx.author.roles]
return any(role in staff_roles for role in user_roles)
return commands.check(predicate)
def has_higher_role_than(target_param='member'):
"""Check if user has higher role than target."""
def predicate(ctx):
target = ctx.kwargs.get(target_param) if hasattr(ctx, 'kwargs') else None
if not isinstance(target, disnake.Member):
return True # Skip check if target not found
return ctx.author.top_role > target.top_role or ctx.author == ctx.guild.owner
return commands.check(predicate)
def channel_locked():
"""Check if channel is in maintenance mode."""
async def predicate(ctx):
# Check if channel has "locked" in topic or name
if hasattr(ctx.channel, 'topic') and ctx.channel.topic:
if 'locked' in ctx.channel.topic.lower():
return False
if 'locked' in ctx.channel.name.lower():
return False
return True
return commands.check(predicate)
def cooldown_bypass():
"""Bypass cooldowns for trusted users."""
def predicate(ctx):
# Check if user should bypass cooldowns
bypass_roles = ['Owner', 'Admin']
user_roles = [role.name for role in ctx.author.roles]
return any(role in bypass_roles for role in user_roles)
return commands.check(predicate)
# Using custom checks
@bot.command()
@is_staff()
@has_higher_role_than('target')
async def timeout(ctx, target: disnake.Member, duration: str, *, reason="No reason"):
"""Timeout a member (staff only, must have higher role)."""
# Parse duration and apply timeout
await ctx.send(f"Timed out {target} for {duration}")
@bot.command()
@channel_locked()
async def chat(ctx, *, message):
"""Chat command (disabled in locked channels)."""
await ctx.send(f"{ctx.author.mention}: {message}")
@bot.command()
@commands.cooldown(1, 30, commands.BucketType.user)
@cooldown_bypass()
async def expensive_command(ctx):
"""Expensive command with cooldown bypass for admins."""
await ctx.send("This is an expensive operation...")@bot.group()
@commands.has_permissions(manage_roles=True)
async def role(ctx):
"""Role management commands."""
if ctx.invoked_subcommand is None:
await ctx.send_help(ctx.command)
@role.command()
async def create(ctx, name: str, color: disnake.Color = None, hoist: bool = False, mentionable: bool = True):
"""Create a new role with specified properties."""
if color is None:
color = disnake.Color.default()
try:
role = await ctx.guild.create_role(
name=name,
color=color,
hoist=hoist,
mentionable=mentionable,
reason=f"Role created by {ctx.author}"
)
embed = disnake.Embed(
title="Role Created",
description=f"Successfully created {role.mention}",
color=role.color
)
embed.add_field(name="Name", value=role.name)
embed.add_field(name="Color", value=f"#{role.color.value:06x}")
embed.add_field(name="Hoisted", value=hoist)
embed.add_field(name="Mentionable", value=mentionable)
await ctx.send(embed=embed)
except disnake.Forbidden:
await ctx.send("I don't have permission to create roles.")
except disnake.HTTPException as e:
await ctx.send(f"Failed to create role: {e}")
@role.command()
async def delete(ctx, role: disnake.Role):
"""Delete a role."""
if role >= ctx.author.top_role and ctx.author != ctx.guild.owner:
return await ctx.send("You cannot delete this role due to hierarchy.")
if role >= ctx.me.top_role:
return await ctx.send("I cannot delete this role due to hierarchy.")
if role.is_default():
return await ctx.send("Cannot delete the @everyone role.")
role_name = role.name
await role.delete(reason=f"Role deleted by {ctx.author}")
await ctx.send(f"Deleted role: {role_name}")
@role.command()
async def edit(ctx, role: disnake.Role, *, properties):
"""Edit role properties (format: name=NewName color=#ff0000 hoist=true)."""
if role >= ctx.author.top_role and ctx.author != ctx.guild.owner:
return await ctx.send("You cannot edit this role due to hierarchy.")
# Parse properties
changes = {}
for prop in properties.split():
if '=' in prop:
key, value = prop.split('=', 1)
if key == 'name':
changes['name'] = value
elif key == 'color':
try:
if value.startswith('#'):
changes['color'] = disnake.Color(int(value[1:], 16))
else:
changes['color'] = getattr(disnake.Color, value.lower())()
except (ValueError, AttributeError):
return await ctx.send(f"Invalid color: {value}")
elif key in ['hoist', 'mentionable']:
changes[key] = value.lower() in ['true', '1', 'yes']
if not changes:
return await ctx.send("No valid properties provided.")
try:
await role.edit(reason=f"Role edited by {ctx.author}", **changes)
await ctx.send(f"Successfully edited {role.mention}")
except disnake.Forbidden:
await ctx.send("I don't have permission to edit this role.")
except disnake.HTTPException as e:
await ctx.send(f"Failed to edit role: {e}")
@role.command()
async def give(ctx, member: disnake.Member, role: disnake.Role):
"""Give a role to a member."""
if role >= ctx.author.top_role and ctx.author != ctx.guild.owner:
return await ctx.send("You cannot assign this role due to hierarchy.")
if role >= ctx.me.top_role:
return await ctx.send("I cannot assign this role due to hierarchy.")
if role in member.roles:
return await ctx.send(f"{member} already has the {role.name} role.")
try:
await member.add_roles(role, reason=f"Role assigned by {ctx.author}")
await ctx.send(f"Gave {role.name} to {member.mention}")
except disnake.Forbidden:
await ctx.send("I don't have permission to assign roles.")
@role.command()
async def remove(ctx, member: disnake.Member, role: disnake.Role):
"""Remove a role from a member."""
if role >= ctx.author.top_role and ctx.author != ctx.guild.owner:
return await ctx.send("You cannot manage this role due to hierarchy.")
if role not in member.roles:
return await ctx.send(f"{member} doesn't have the {role.name} role.")
try:
await member.remove_roles(role, reason=f"Role removed by {ctx.author}")
await ctx.send(f"Removed {role.name} from {member.mention}")
except disnake.Forbidden:
await ctx.send("I don't have permission to remove roles.")
@role.command()
async def info(ctx, role: disnake.Role):
"""Display detailed role information."""
embed = disnake.Embed(title=f"Role: {role.name}", color=role.color)
embed.add_field(name="ID", value=role.id, inline=True)
embed.add_field(name="Position", value=role.position, inline=True)
embed.add_field(name="Color", value=f"#{role.color.value:06x}", inline=True)
embed.add_field(name="Hoisted", value=role.hoist, inline=True)
embed.add_field(name="Mentionable", value=role.mentionable, inline=True)
embed.add_field(name="Managed", value=role.managed, inline=True)
embed.add_field(name="Members", value=len(role.members), inline=True)
embed.add_field(name="Created", value=f"<t:{int(role.created_at.timestamp())}:F>", inline=True)
if role.tags:
tags = []
if role.tags.bot_id:
tags.append(f"Bot Role (ID: {role.tags.bot_id})")
if role.tags.integration_id:
tags.append(f"Integration Role (ID: {role.tags.integration_id})")
if role.tags.premium_subscriber:
tags.append("Nitro Booster Role")
if tags:
embed.add_field(name="Tags", value="\n".join(tags), inline=False)
# Show permissions
perms = []
for perm, value in role.permissions:
if value:
perms.append(perm.replace('_', ' ').title())
if perms:
embed.add_field(
name=f"Permissions ({len(perms)})",
value=", ".join(perms) if len(", ".join(perms)) < 1024 else f"{len(perms)} permissions",
inline=False
)
await ctx.send(embed=embed)
@role.command()
async def members(ctx, role: disnake.Role):
"""List all members with a specific role."""
members = role.members
if not members:
return await ctx.send(f"No members have the {role.name} role.")
embed = disnake.Embed(
title=f"Members with {role.name} ({len(members)})",
color=role.color
)
# Split members into pages if too many
member_list = [f"{member.mention} ({member})" for member in members]
if len(member_list) <= 20:
embed.description = "\n".join(member_list)
await ctx.send(embed=embed)
else:
# Paginate results
for i in range(0, len(member_list), 20):
page_members = member_list[i:i+20]
embed.description = "\n".join(page_members)
embed.set_footer(text=f"Page {i//20 + 1}/{(len(member_list)-1)//20 + 1}")
await ctx.send(embed=embed)@bot.group()
@commands.has_permissions(manage_channels=True)
async def perms(ctx):
"""Channel permission management."""
if ctx.invoked_subcommand is None:
await ctx.send_help(ctx.command)
@perms.command()
async def allow(ctx, target: Union[disnake.Member, disnake.Role], channel: disnake.GuildChannel = None, *permissions):
"""Allow permissions for a member or role in a channel."""
if channel is None:
channel = ctx.channel
if not permissions:
return await ctx.send("Please specify permissions to allow.")
# Get current overwrites or create new
overwrites = channel.overwrites_for(target)
# Set permissions to True
changes = {}
for perm in permissions:
perm_name = perm.lower().replace('-', '_').replace(' ', '_')
if hasattr(overwrites, perm_name):
changes[perm_name] = True
if not changes:
return await ctx.send("No valid permissions specified.")
# Update overwrites
overwrites.update(**changes)
try:
await channel.set_permissions(target, overwrite=overwrites, reason=f"Permissions allowed by {ctx.author}")
perms_str = ", ".join(changes.keys()).replace('_', ' ').title()
await ctx.send(f"Allowed {perms_str} for {target.mention} in {channel.mention}")
except disnake.Forbidden:
await ctx.send("I don't have permission to manage channel permissions.")
except disnake.HTTPException as e:
await ctx.send(f"Failed to update permissions: {e}")
@perms.command()
async def deny(ctx, target: Union[disnake.Member, disnake.Role], channel: disnake.GuildChannel = None, *permissions):
"""Deny permissions for a member or role in a channel."""
if channel is None:
channel = ctx.channel
if not permissions:
return await ctx.send("Please specify permissions to deny.")
overwrites = channel.overwrites_for(target)
changes = {}
for perm in permissions:
perm_name = perm.lower().replace('-', '_').replace(' ', '_')
if hasattr(overwrites, perm_name):
changes[perm_name] = False
if not changes:
return await ctx.send("No valid permissions specified.")
overwrites.update(**changes)
try:
await channel.set_permissions(target, overwrite=overwrites, reason=f"Permissions denied by {ctx.author}")
perms_str = ", ".join(changes.keys()).replace('_', ' ').title()
await ctx.send(f"Denied {perms_str} for {target.mention} in {channel.mention}")
except disnake.Forbidden:
await ctx.send("I don't have permission to manage channel permissions.")
@perms.command()
async def reset(ctx, target: Union[disnake.Member, disnake.Role], channel: disnake.GuildChannel = None, *permissions):
"""Reset permissions to inherit from role/default for a member or role."""
if channel is None:
channel = ctx.channel
if not permissions:
# Reset all overwrites
try:
await channel.set_permissions(target, overwrite=None, reason=f"All permissions reset by {ctx.author}")
await ctx.send(f"Reset all permissions for {target.mention} in {channel.mention}")
except disnake.Forbidden:
await ctx.send("I don't have permission to manage channel permissions.")
return
overwrites = channel.overwrites_for(target)
changes = {}
for perm in permissions:
perm_name = perm.lower().replace('-', '_').replace(' ', '_')
if hasattr(overwrites, perm_name):
changes[perm_name] = None
if not changes:
return await ctx.send("No valid permissions specified.")
overwrites.update(**changes)
try:
await channel.set_permissions(target, overwrite=overwrites, reason=f"Permissions reset by {ctx.author}")
perms_str = ", ".join(changes.keys()).replace('_', ' ').title()
await ctx.send(f"Reset {perms_str} for {target.mention} in {channel.mention}")
except disnake.Forbidden:
await ctx.send("I don't have permission to manage channel permissions.")
@perms.command()
async def view(ctx, target: Union[disnake.Member, disnake.Role] = None, channel: disnake.GuildChannel = None):
"""View permissions for a member or role in a channel."""
if channel is None:
channel = ctx.channel
if target is None:
target = ctx.author
if isinstance(target, disnake.Member):
permissions = channel.permissions_for(target)
title = f"Permissions for {target} in #{channel.name}"
else:
# For roles, show overwrites
overwrites = channel.overwrites_for(target)
title = f"Permission overwrites for {target} in #{channel.name}"
embed = disnake.Embed(title=title, color=0x00ff00)
if isinstance(target, disnake.Member):
# Show actual permissions
allowed = []
denied = []
for perm, value in permissions:
perm_display = perm.replace('_', ' ').title()
if value:
allowed.append(perm_display)
else:
denied.append(perm_display)
if allowed:
embed.add_field(
name=f"✅ Allowed ({len(allowed)})",
value=", ".join(allowed[:20]) + ("..." if len(allowed) > 20 else ""),
inline=False
)
if denied:
embed.add_field(
name=f"❌ Denied ({len(denied)})",
value=", ".join(denied[:20]) + ("..." if len(denied) > 20 else ""),
inline=False
)
else:
# Show overwrites
allowed_overwrites = []
denied_overwrites = []
neutral_overwrites = []
for perm, value in overwrites:
perm_display = perm.replace('_', ' ').title()
if value is True:
allowed_overwrites.append(perm_display)
elif value is False:
denied_overwrites.append(perm_display)
else:
neutral_overwrites.append(perm_display)
if allowed_overwrites:
embed.add_field(
name="✅ Explicitly Allowed",
value=", ".join(allowed_overwrites),
inline=False
)
if denied_overwrites:
embed.add_field(
name="❌ Explicitly Denied",
value=", ".join(denied_overwrites),
inline=False
)
if not allowed_overwrites and not denied_overwrites:
embed.description = "No permission overwrites set."
await ctx.send(embed=embed)import json
import asyncio
from collections import defaultdict, deque
from datetime import datetime, timedelta
class SecuritySystem:
"""Comprehensive security system for Discord bots."""
def __init__(self, bot):
self.bot = bot
self.blacklist = set()
self.whitelist = set()
self.trusted_guilds = set()
self.rate_limits = defaultdict(lambda: defaultdict(deque))
self.failed_attempts = defaultdict(int)
self.load_security_data()
def load_security_data(self):
"""Load security data from file."""
try:
with open('security.json', 'r') as f:
data = json.load(f)
self.blacklist = set(data.get('blacklist', []))
self.whitelist = set(data.get('whitelist', []))
self.trusted_guilds = set(data.get('trusted_guilds', []))
except FileNotFoundError:
pass
def save_security_data(self):
"""Save security data to file."""
data = {
'blacklist': list(self.blacklist),
'whitelist': list(self.whitelist),
'trusted_guilds': list(self.trusted_guilds)
}
with open('security.json', 'w') as f:
json.dump(data, f, indent=2)
def is_blacklisted(self, user_id: int) -> bool:
"""Check if user is blacklisted."""
return user_id in self.blacklist
def is_whitelisted(self, user_id: int) -> bool:
"""Check if user is whitelisted."""
return user_id in self.whitelist
def is_trusted_guild(self, guild_id: int) -> bool:
"""Check if guild is trusted."""
return guild_id in self.trusted_guilds
def blacklist_user(self, user_id: int, reason: str = None):
"""Add user to blacklist."""
self.blacklist.add(user_id)
self.save_security_data()
print(f"Blacklisted user {user_id}: {reason}")
def whitelist_user(self, user_id: int):
"""Add user to whitelist."""
self.whitelist.add(user_id)
self.blacklist.discard(user_id)
self.save_security_data()
def check_rate_limit(self, user_id: int, action: str, limit: int = 5, window: int = 60) -> bool:
"""Check if user is within rate limit for action."""
now = datetime.utcnow()
user_actions = self.rate_limits[user_id][action]
# Remove old entries
while user_actions and user_actions[0] < now - timedelta(seconds=window):
user_actions.popleft()
# Check limit
if len(user_actions) >= limit:
self.failed_attempts[user_id] += 1
# Auto-blacklist after too many failures
if self.failed_attempts[user_id] >= 10:
self.blacklist_user(user_id, "Excessive rate limit violations")
return False
# Add current attempt
user_actions.append(now)
return True
def log_suspicious_activity(self, user_id: int, guild_id: int, activity: str):
"""Log suspicious activity."""
timestamp = datetime.utcnow().isoformat()
print(f"SECURITY WARNING [{timestamp}]: User {user_id} in guild {guild_id}: {activity}")
# Could save to database or send to logging channel
def scan_message(self, message: disnake.Message) -> bool:
"""Scan message for security threats."""
content = message.content.lower()
# Check for common threats
threats = [
'discord.gg/', # Invite links
'http://', # Potentially unsafe links
'@everyone', # Mass mentions
'nitro', # Scam keywords
'free', # Scam keywords
]
threat_count = sum(1 for threat in threats if threat in content)
if threat_count >= 2:
self.log_suspicious_activity(
message.author.id,
message.guild.id if message.guild else 0,
f"Suspicious message content (threats: {threat_count})"
)
return False
return True
# Initialize security system
security = SecuritySystem(bot)
# Security middleware
@bot.check
async def security_check(ctx):
"""Global security check for all commands."""
# Skip checks for whitelisted users
if security.is_whitelisted(ctx.author.id):
return True
# Block blacklisted users
if security.is_blacklisted(ctx.author.id):
await ctx.send("You are not authorized to use this bot.", delete_after=5)
return False
# Rate limit check
if not security.check_rate_limit(ctx.author.id, 'command', limit=10, window=60):
await ctx.send("You're being rate limited. Please slow down.", delete_after=5)
return False
return True
@bot.event
async def on_message(message):
"""Security monitoring for messages."""
if message.author.bot:
return
# Security scan
if not security.scan_message(message):
try:
await message.delete()
await message.channel.send(
f"{message.author.mention}, your message was flagged by security systems.",
delete_after=10
)
except disnake.NotFound:
pass
await bot.process_commands(message)
# Security management commands
@bot.group()
@commands.is_owner()
async def security(ctx):
"""Security management commands."""
if ctx.invoked_subcommand is None:
await ctx.send_help(ctx.command)
@security.command()
async def blacklist(ctx, user: disnake.User, *, reason="No reason provided"):
"""Blacklist a user."""
security.blacklist_user(user.id, reason)
await ctx.send(f"Blacklisted {user} ({user.id}): {reason}")
@security.command()
async def whitelist(ctx, user: disnake.User):
"""Whitelist a user."""
security.whitelist_user(user.id)
await ctx.send(f"Whitelisted {user} ({user.id})")
@security.command()
async def status(ctx, user: disnake.User = None):
"""Check security status of a user."""
if user is None:
user = ctx.author
embed = disnake.Embed(title=f"Security Status: {user}")
if security.is_blacklisted(user.id):
embed.color = 0xff0000
embed.add_field(name="Status", value="❌ BLACKLISTED", inline=False)
elif security.is_whitelisted(user.id):
embed.color = 0x00ff00
embed.add_field(name="Status", value="✅ WHITELISTED", inline=False)
else:
embed.color = 0xffff00
embed.add_field(name="Status", value="⚠️ NORMAL", inline=False)
# Rate limit info
command_attempts = len(security.rate_limits[user.id]['command'])
embed.add_field(name="Recent Commands", value=f"{command_attempts}/10 (last 60s)", inline=True)
failed_attempts = security.failed_attempts.get(user.id, 0)
embed.add_field(name="Failed Attempts", value=failed_attempts, inline=True)
await ctx.send(embed=embed)
@security.command()
async def trust_guild(ctx, guild_id: int = None):
"""Mark a guild as trusted."""
if guild_id is None:
guild_id = ctx.guild.id
security.trusted_guilds.add(guild_id)
security.save_security_data()
guild = bot.get_guild(guild_id)
guild_name = guild.name if guild else f"Guild {guild_id}"
await ctx.send(f"Marked {guild_name} as trusted.")
@security.command()
async def stats(ctx):
"""Show security statistics."""
embed = disnake.Embed(title="Security Statistics", color=0x0099ff)
embed.add_field(name="Blacklisted Users", value=len(security.blacklist), inline=True)
embed.add_field(name="Whitelisted Users", value=len(security.whitelist), inline=True)
embed.add_field(name="Trusted Guilds", value=len(security.trusted_guilds), inline=True)
active_rate_limits = len([uid for uid, actions in security.rate_limits.items() if any(actions.values())])
embed.add_field(name="Active Rate Limits", value=active_rate_limits, inline=True)
total_failures = sum(security.failed_attempts.values())
embed.add_field(name="Total Failed Attempts", value=total_failures, inline=True)
await ctx.send(embed=embed)Install with Tessl CLI
npx tessl i tessl/pypi-disnake