A modern, easy-to-use, feature-rich async-ready API wrapper for Discord written in Python
—
User and member objects representing Discord users and guild members with comprehensive profile information, permissions, voice states, and user-specific operations including member management and user data access.
Basic Discord user representation with profile information and account details.
class User:
def __init__(self): ...
id: int
name: str
discriminator: str
avatar: Optional[Asset]
banner: Optional[Asset]
accent_color: Optional[Colour]
accent_colour: Optional[Colour]
bot: bool
system: bool
public_flags: PublicUserFlags
avatar_decoration: Optional[Asset]
clan: Optional[UserClan]
@property
def display_name(self) -> str:
"""User's display name (global name or username)."""
@property
def global_name(self) -> Optional[str]:
"""User's global display name."""
@property
def mention(self) -> str:
"""String to mention the user."""
@property
def display_avatar(self) -> Asset:
"""User's display avatar (avatar or default)."""
@property
def default_avatar(self) -> Asset:
"""User's default avatar."""
@property
def avatar_decoration_sku_id(self) -> Optional[int]:
"""Avatar decoration SKU ID."""
def mentioned_in(self, message: Message) -> bool:
"""
Check if user is mentioned in a message.
Parameters:
- message: Message to check
Returns:
True if user is mentioned
"""
async def create_dm(self) -> DMChannel:
"""
Create a DM channel with this user.
Returns:
DM channel object
"""
def dm_channel(self) -> Optional[DMChannel]:
"""
Get cached DM channel with this user.
Returns:
DM channel if cached
"""
async def send(
self,
content: Optional[str] = None,
*,
tts: bool = False,
embed: Optional[Embed] = None,
embeds: Optional[List[Embed]] = None,
file: Optional[File] = None,
files: Optional[List[File]] = None,
allowed_mentions: Optional[AllowedMentions] = None,
reference: Optional[Union[Message, MessageReference, PartialMessage]] = None,
mention_author: Optional[bool] = None,
view: Optional[View] = None,
components: Optional[Union[ActionRow, List[ActionRow], List[List[Component]], List[Component]]] = None,
delete_after: Optional[float] = None,
suppress_embeds: bool = False,
flags: Optional[MessageFlags] = None
) -> Message:
"""
Send a direct message to this user.
Parameters:
- content: Message text content
- tts: Whether message should be read with text-to-speech
- embed: Single embed to include
- embeds: List of embeds to include (max 10)
- file: Single file to attach
- files: List of files to attach (max 10)
- allowed_mentions: Controls @ mentions in the message
- reference: Message to reply to
- mention_author: Whether to mention the author when replying
- view: UI components view
- components: Raw components to include
- delete_after: Seconds after which to delete the message
- suppress_embeds: Whether to suppress embeds
- flags: Message flags
Returns:
The sent message
"""
async def mutual_guilds(self) -> List[Guild]:
"""
Get guilds shared with the bot.
Returns:
List of mutual guilds
"""
def is_friend(self) -> bool:
"""
Check if user is friends with the bot.
Returns:
True if friends
"""
def is_blocked(self) -> bool:
"""
Check if user is blocked by the bot.
Returns:
True if blocked
"""
async def profile(self) -> UserProfile:
"""
Fetch user's full profile.
Returns:
User profile with additional information
"""Special user object representing the bot's own user account with additional capabilities.
class ClientUser(User):
def __init__(self): ...
verified: bool
locale: Optional[str]
mfa_enabled: bool
async def edit(
self,
*,
username: str = ...,
avatar: Optional[bytes] = ...,
banner: Optional[bytes] = ...
) -> ClientUser:
"""
Edit the bot's profile.
Parameters:
- username: New username
- avatar: New avatar bytes
- banner: New banner bytes
Returns:
Updated client user
"""Guild member representation extending user with guild-specific information and capabilities.
class Member(User):
def __init__(self): ...
guild: Guild
joined_at: Optional[datetime]
premium_since: Optional[datetime]
roles: List[Role]
activities: List[Activity]
status: Status
raw_status: str
mobile_status: Status
desktop_status: Status
web_status: Status
nick: Optional[str]
pending: bool
timed_out_until: Optional[datetime]
flags: MemberFlags
avatar: Optional[Asset]
banner: Optional[Asset]
communication_disabled_until: Optional[datetime]
@property
def display_name(self) -> str:
"""Member's display name (nick, global name, or username)."""
@property
def mention(self) -> str:
"""String to mention the member."""
@property
def display_avatar(self) -> Asset:
"""Member's display avatar (guild avatar, user avatar, or default)."""
@property
def guild_avatar(self) -> Optional[Asset]:
"""Member's guild-specific avatar."""
@property
def activity(self) -> Optional[Activity]:
"""Member's primary activity."""
@property
def colour(self) -> Colour:
"""Member's role color."""
@property
def color(self) -> Colour:
"""Member's role color (alias)."""
@property
def top_role(self) -> Role:
"""Member's highest role."""
@property
def guild_permissions(self) -> Permissions:
"""Member's guild-wide permissions."""
@property
def voice(self) -> Optional[VoiceState]:
"""Member's voice state."""
def permissions_in(self, channel: GuildChannel) -> Permissions:
"""
Get member's permissions in a specific channel.
Parameters:
- channel: Guild channel
Returns:
Permissions in the channel
"""
async def add_roles(
self,
*roles: Role,
reason: Optional[str] = None,
atomic: bool = True
) -> None:
"""
Add roles to the member.
Parameters:
- roles: Roles to add
- reason: Audit log reason
- atomic: Whether to add all roles in single API call
"""
async def remove_roles(
self,
*roles: Role,
reason: Optional[str] = None,
atomic: bool = True
) -> None:
"""
Remove roles from the member.
Parameters:
- roles: Roles to remove
- reason: Audit log reason
- atomic: Whether to remove all roles in single API call
"""
async def edit(
self,
*,
nick: Optional[str] = ...,
mute: bool = ...,
deafen: bool = ...,
suppress: bool = ...,
roles: List[Role] = ...,
voice_channel: Optional[VoiceChannel] = ...,
reason: Optional[str] = None,
timed_out_until: Optional[Union[datetime, float]] = ...,
communication_disabled_until: Optional[Union[datetime, float]] = ...,
flags: MemberFlags = ...
) -> Member:
"""
Edit the member.
Parameters:
- nick: New nickname (None to remove)
- mute: Whether to server mute in voice
- deafen: Whether to server deafen in voice
- suppress: Whether to suppress in stage channel
- roles: New role list (replaces current roles)
- voice_channel: Voice channel to move to
- reason: Audit log reason
- timed_out_until: Timeout duration
- communication_disabled_until: Communication timeout
- flags: Member flags
Returns:
Updated member
"""
async def timeout(
self,
duration: Optional[Union[float, datetime, timedelta]] = None,
*,
reason: Optional[str] = None
) -> Member:
"""
Timeout the member.
Parameters:
- duration: Timeout duration (None to remove timeout)
- reason: Audit log reason
Returns:
Updated member
"""
async def kick(self, *, reason: Optional[str] = None) -> None:
"""
Kick the member from the guild.
Parameters:
- reason: Audit log reason
"""
async def ban(
self,
*,
reason: Optional[str] = None,
delete_message_days: int = 1,
delete_message_seconds: Optional[int] = None
) -> None:
"""
Ban the member from the guild.
Parameters:
- reason: Audit log reason
- delete_message_days: Days of messages to delete (deprecated)
- delete_message_seconds: Seconds of messages to delete
"""
async def unban(self, *, reason: Optional[str] = None) -> None:
"""
Unban the member.
Parameters:
- reason: Audit log reason
"""
async def move_to(self, channel: Optional[VoiceChannel], *, reason: Optional[str] = None) -> None:
"""
Move member to a voice channel.
Parameters:
- channel: Voice channel to move to (None to disconnect)
- reason: Audit log reason
"""
async def request_to_speak(self) -> None:
"""Request to speak in a stage channel."""
async def fetch_message(self, id: int) -> Message:
"""
Fetch a message sent by this member.
Parameters:
- id: Message ID
Returns:
Message object
"""
def mentioned_in(self, message: Message) -> bool:
"""
Check if member is mentioned in a message.
Parameters:
- message: Message to check
Returns:
True if member is mentioned
"""
def is_timed_out(self) -> bool:
"""
Check if member is currently timed out.
Returns:
True if timed out
"""
def is_on_mobile(self) -> bool:
"""
Check if member is on mobile.
Returns:
True if on mobile
"""Member voice connection and activity information.
class VoiceState:
def __init__(self): ...
session_id: str
channel: Optional[Union[VoiceChannel, StageChannel]]
user_id: int
member: Optional[Member]
deaf: bool
mute: bool
self_deaf: bool
self_mute: bool
self_stream: bool
self_video: bool
suppress: bool
requested_to_speak_at: Optional[datetime]
@property
def guild(self) -> Optional[Guild]:
"""Guild the voice state is in."""
@property
def user(self) -> Optional[User]:
"""User the voice state belongs to."""
def is_afk(self) -> bool:
"""
Check if user is in AFK channel.
Returns:
True if in AFK channel
"""Extended user information available through profile fetching.
class UserProfile:
def __init__(self): ...
user: User
connected_accounts: List[ConnectedAccount]
premium_since: Optional[datetime]
premium_type: Optional[PremiumType]
nitro_subscription: Optional[NitroSubscription]
mutual_guilds: List[PartialGuild]
mutual_friends: List[User]
premium_guild_since: Optional[datetime]
legacy_username: Optional[str]
@property
def nitro(self) -> bool:
"""Whether user has Nitro subscription."""
@property
def hypesquad(self) -> Optional[HypeSquadHouse]:
"""User's HypeSquad house."""User activity and presence information including games, streaming, and custom status.
class Activity:
def __init__(self): ...
type: ActivityType
name: str
url: Optional[str]
created_at: datetime
timestamps: Optional[ActivityTimestamps]
application_id: Optional[int]
details: Optional[str]
state: Optional[str]
emoji: Optional[PartialEmoji]
party: Optional[ActivityParty]
assets: Optional[ActivityAssets]
secrets: Optional[ActivitySecrets]
instance: bool
flags: Optional[ActivityFlags]
buttons: List[str]
@property
def start(self) -> Optional[datetime]:
"""Activity start time."""
@property
def end(self) -> Optional[datetime]:
"""Activity end time."""
class Game(Activity):
"""Gaming activity."""
def __init__(self, name: str):
"""
Initialize a game activity.
Parameters:
- name: Game name
"""
class Streaming(Activity):
"""Streaming activity."""
def __init__(self, *, name: str, url: str):
"""
Initialize a streaming activity.
Parameters:
- name: Stream title
- url: Stream URL
"""
@property
def twitch_name(self) -> Optional[str]:
"""Twitch channel name."""
class CustomActivity(Activity):
"""Custom status activity."""
def __init__(self, name: str, *, emoji: Optional[Union[str, Emoji, PartialEmoji]] = None):
"""
Initialize a custom activity.
Parameters:
- name: Status text
- emoji: Status emoji
"""User relationship and external account connection information.
class Relationship:
def __init__(self): ...
user: User
type: RelationshipType
nickname: Optional[str]
since: Optional[datetime]
class ConnectedAccount:
def __init__(self): ...
id: str
name: str
type: str
revoked: bool
integrations: List[Integration]
verified: bool
friend_sync: bool
show_activity: bool
visibility: intMember-specific flags and configuration options.
class MemberFlags:
"""Member flags bitfield."""
def __init__(self, value: int = 0): ...
@classmethod
def none(cls) -> MemberFlags:
"""No flags set."""
@classmethod
def all(cls) -> MemberFlags:
"""All flags set."""
@property
def did_rejoin(self) -> bool:
"""Whether member rejoined the guild."""
@property
def completed_onboarding(self) -> bool:
"""Whether member completed onboarding."""
@property
def bypasses_verification(self) -> bool:
"""Whether member bypasses verification."""
@property
def started_onboarding(self) -> bool:
"""Whether member started onboarding."""
class PublicUserFlags:
"""Public user flags bitfield."""
def __init__(self, value: int = 0): ...
@property
def staff(self) -> bool:
"""Discord Staff."""
@property
def partner(self) -> bool:
"""Discord Partner."""
@property
def hypesquad(self) -> bool:
"""HypeSquad Events."""
@property
def bug_hunter(self) -> bool:
"""Bug Hunter Level 1."""
@property
def hypesquad_bravery(self) -> bool:
"""HypeSquad Bravery."""
@property
def hypesquad_brilliance(self) -> bool:
"""HypeSquad Brilliance."""
@property
def hypesquad_balance(self) -> bool:
"""HypeSquad Balance."""
@property
def early_supporter(self) -> bool:
"""Early Supporter."""
@property
def bug_hunter_level_2(self) -> bool:
"""Bug Hunter Level 2."""
@property
def verified_bot(self) -> bool:
"""Verified Bot."""
@property
def verified_developer(self) -> bool:
"""Verified Bot Developer."""
@property
def certified_moderator(self) -> bool:
"""Discord Certified Moderator."""
@property
def bot_http_interactions(self) -> bool:
"""Bot uses HTTP interactions."""
@property
def active_developer(self) -> bool:
"""Active Developer."""import disnake
from disnake.ext import commands
bot = commands.Bot(command_prefix='!', intents=disnake.Intents.all())
@bot.command()
async def userinfo(ctx, user: disnake.User = None):
"""Display user information."""
if user is None:
user = ctx.author
embed = disnake.Embed(title=f"User Information", color=0x00ff00)
embed.set_thumbnail(url=user.display_avatar.url)
embed.add_field(name="Username", value=f"{user}", inline=True)
embed.add_field(name="ID", value=user.id, inline=True)
embed.add_field(name="Bot", value="Yes" if user.bot else "No", inline=True)
if user.global_name:
embed.add_field(name="Display Name", value=user.global_name, inline=True)
embed.add_field(name="Created", value=f"<t:{int(user.created_at.timestamp())}:F>", inline=False)
# User flags
flags = []
if user.public_flags.staff:
flags.append("Discord Staff")
if user.public_flags.partner:
flags.append("Discord Partner")
if user.public_flags.verified_developer:
flags.append("Verified Bot Developer")
if user.public_flags.early_supporter:
flags.append("Early Supporter")
if flags:
embed.add_field(name="Badges", value=", ".join(flags), inline=False)
await ctx.send(embed=embed)
@bot.command()
async def memberinfo(ctx, member: disnake.Member = None):
"""Display member information."""
if member is None:
member = ctx.author
embed = disnake.Embed(title=f"Member Information", color=member.color)
embed.set_thumbnail(url=member.display_avatar.url)
embed.add_field(name="Username", value=f"{member}", inline=True)
embed.add_field(name="Display Name", value=member.display_name, inline=True)
embed.add_field(name="ID", value=member.id, inline=True)
embed.add_field(name="Joined Server", value=f"<t:{int(member.joined_at.timestamp())}:F>" if member.joined_at else "Unknown", inline=False)
embed.add_field(name="Joined Discord", value=f"<t:{int(member.created_at.timestamp())}:F>", inline=False)
# Roles (excluding @everyone)
roles = [role.mention for role in member.roles[1:]]
if roles:
embed.add_field(name=f"Roles ({len(roles)})", value=" ".join(roles), inline=False)
# Status and activity
status_emojis = {
disnake.Status.online: "🟢",
disnake.Status.idle: "🟡",
disnake.Status.dnd: "🔴",
disnake.Status.offline: "⚫"
}
embed.add_field(name="Status", value=f"{status_emojis.get(member.status, '❓')} {member.status.name.title()}", inline=True)
if member.activity:
activity = member.activity
activity_type = {
disnake.ActivityType.playing: "Playing",
disnake.ActivityType.streaming: "Streaming",
disnake.ActivityType.listening: "Listening to",
disnake.ActivityType.watching: "Watching",
disnake.ActivityType.custom: "Custom Status",
disnake.ActivityType.competing: "Competing in"
}
embed.add_field(
name="Activity",
value=f"{activity_type.get(activity.type, 'Unknown')} {activity.name}",
inline=True
)
# Boost status
if member.premium_since:
embed.add_field(name="Boosting Since", value=f"<t:{int(member.premium_since.timestamp())}:F>", inline=True)
# Timeout status
if member.is_timed_out():
embed.add_field(name="Timed Out Until", value=f"<t:{int(member.timed_out_until.timestamp())}:F>", inline=False)
await ctx.send(embed=embed)
@bot.command()
async def avatar(ctx, user: disnake.User = None):
"""Display user's avatar."""
if user is None:
user = ctx.author
embed = disnake.Embed(title=f"{user}'s Avatar", color=0x00ff00)
embed.set_image(url=user.display_avatar.url)
# Add different format links
formats = ['PNG', 'JPEG', 'WEBP']
if not user.display_avatar.is_animated():
format_links = [f"[{fmt}]({user.display_avatar.replace(format=fmt.lower(), size=1024).url})" for fmt in formats]
else:
formats.append('GIF')
format_links = [f"[{fmt}]({user.display_avatar.replace(format=fmt.lower(), size=1024).url})" for fmt in formats]
embed.add_field(name="Formats", value=" | ".join(format_links), inline=False)
await ctx.send(embed=embed)@bot.command()
async def nick(ctx, member: disnake.Member, *, nickname: str = None):
"""Change a member's nickname."""
if not ctx.author.guild_permissions.manage_nicknames:
return await ctx.send("You don't have permission to manage nicknames.")
if member.top_role >= ctx.author.top_role and member != ctx.author:
return await ctx.send("You cannot manage this member's nickname.")
old_nick = member.display_name
await member.edit(nick=nickname, reason=f"Changed by {ctx.author}")
new_nick = nickname or member.name
await ctx.send(f"Changed {old_nick}'s nickname to {new_nick}")
@bot.command()
async def timeout(ctx, member: disnake.Member, duration: str, *, reason="No reason provided"):
"""Timeout a member."""
if not ctx.author.guild_permissions.moderate_members:
return await ctx.send("You don't have permission to timeout members.")
if member.top_role >= ctx.author.top_role:
return await ctx.send("You cannot timeout this member.")
# Parse duration (simple implementation)
duration_map = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
if duration[-1].lower() in duration_map:
try:
time_value = int(duration[:-1])
time_unit = duration[-1].lower()
timeout_seconds = time_value * duration_map[time_unit]
if timeout_seconds > 2419200: # 28 days max
return await ctx.send("Timeout duration cannot exceed 28 days.")
timeout_until = disnake.utils.utcnow() + timedelta(seconds=timeout_seconds)
await member.timeout(timeout_until, reason=reason)
await ctx.send(f"Timed out {member.mention} for {duration} - {reason}")
except ValueError:
await ctx.send("Invalid duration format. Use format like: 1h, 30m, 2d")
else:
await ctx.send("Invalid duration format. Use format like: 1h, 30m, 2d")
@bot.command()
async def untimeout(ctx, member: disnake.Member):
"""Remove timeout from a member."""
if not ctx.author.guild_permissions.moderate_members:
return await ctx.send("You don't have permission to manage timeouts.")
if not member.is_timed_out():
return await ctx.send("This member is not timed out.")
await member.timeout(None, reason=f"Timeout removed by {ctx.author}")
await ctx.send(f"Removed timeout from {member.mention}")
@bot.command()
async def move(ctx, member: disnake.Member, channel: disnake.VoiceChannel = None):
"""Move a member to a voice channel."""
if not ctx.author.guild_permissions.move_members:
return await ctx.send("You don't have permission to move members.")
if not member.voice:
return await ctx.send("Member is not in a voice channel.")
old_channel = member.voice.channel
await member.move_to(channel, reason=f"Moved by {ctx.author}")
if channel:
await ctx.send(f"Moved {member.mention} from {old_channel} to {channel}")
else:
await ctx.send(f"Disconnected {member.mention} from {old_channel}")
@bot.command()
async def mute(ctx, member: disnake.Member, *, reason="No reason provided"):
"""Server mute a member."""
if not ctx.author.guild_permissions.mute_members:
return await ctx.send("You don't have permission to mute members.")
if not member.voice:
return await ctx.send("Member is not in a voice channel.")
await member.edit(mute=True, reason=reason)
await ctx.send(f"Server muted {member.mention} - {reason}")
@bot.command()
async def unmute(ctx, member: disnake.Member):
"""Server unmute a member."""
if not ctx.author.guild_permissions.mute_members:
return await ctx.send("You don't have permission to unmute members.")
if not member.voice:
return await ctx.send("Member is not in a voice channel.")
await member.edit(mute=False, reason=f"Unmuted by {ctx.author}")
await ctx.send(f"Server unmuted {member.mention}")@bot.command()
async def status(ctx, member: disnake.Member = None):
"""Show detailed status information for a member."""
if member is None:
member = ctx.author
embed = disnake.Embed(title=f"{member.display_name}'s Status", color=member.color)
embed.set_thumbnail(url=member.display_avatar.url)
# Overall status
status_info = {
disnake.Status.online: ("🟢", "Online"),
disnake.Status.idle: ("🟡", "Idle"),
disnake.Status.dnd: ("🔴", "Do Not Disturb"),
disnake.Status.offline: ("⚫", "Offline"),
disnake.Status.invisible: ("⚫", "Invisible")
}
emoji, status_name = status_info.get(member.status, ("❓", "Unknown"))
embed.add_field(name="Overall Status", value=f"{emoji} {status_name}", inline=True)
# Platform-specific status
platforms = []
if member.desktop_status != disnake.Status.offline:
platforms.append(f"🖥️ Desktop: {member.desktop_status.name.title()}")
if member.mobile_status != disnake.Status.offline:
platforms.append(f"📱 Mobile: {member.mobile_status.name.title()}")
if member.web_status != disnake.Status.offline:
platforms.append(f"🌐 Web: {member.web_status.name.title()}")
if platforms:
embed.add_field(name="Platform Status", value="\n".join(platforms), inline=False)
# Activities
if member.activities:
activity_list = []
for activity in member.activities:
activity_type = {
disnake.ActivityType.playing: "🎮 Playing",
disnake.ActivityType.streaming: "🔴 Streaming",
disnake.ActivityType.listening: "🎵 Listening to",
disnake.ActivityType.watching: "📺 Watching",
disnake.ActivityType.custom: "💭 Custom",
disnake.ActivityType.competing: "🏆 Competing in"
}
act_type = activity_type.get(activity.type, "❓ Unknown")
activity_list.append(f"{act_type} {activity.name}")
# Add details for specific activities
if hasattr(activity, 'details') and activity.details:
activity_list.append(f" └ {activity.details}")
if hasattr(activity, 'state') and activity.state:
activity_list.append(f" └ {activity.state}")
embed.add_field(name="Activities", value="\n".join(activity_list), inline=False)
# Voice state
if member.voice:
voice_info = []
voice_info.append(f"Channel: {member.voice.channel.mention}")
voice_flags = []
if member.voice.deaf:
voice_flags.append("🔇 Server Deafened")
if member.voice.mute:
voice_flags.append("🔇 Server Muted")
if member.voice.self_deaf:
voice_flags.append("🔇 Self Deafened")
if member.voice.self_mute:
voice_flags.append("🔇 Self Muted")
if member.voice.self_stream:
voice_flags.append("📹 Streaming")
if member.voice.self_video:
voice_flags.append("📹 Camera On")
if voice_flags:
voice_info.extend(voice_flags)
embed.add_field(name="Voice Status", value="\n".join(voice_info), inline=False)
await ctx.send(embed=embed)
@bot.event
async def on_member_update(before, after):
"""Track member status changes."""
# Only track status changes for important members or in specific guilds
if before.status != after.status:
# Log status changes
print(f"{after} changed status from {before.status} to {after.status}")
if before.activities != after.activities:
# Log activity changes
print(f"{after} changed activities")
@bot.command()
async def whoisplaying(ctx, *, game: str):
"""Find members playing a specific game."""
members_playing = []
for member in ctx.guild.members:
for activity in member.activities:
if (activity.type == disnake.ActivityType.playing and
game.lower() in activity.name.lower()):
members_playing.append(member)
break
if not members_playing:
return await ctx.send(f"No one is currently playing '{game}'")
embed = disnake.Embed(
title=f"Members playing '{game}'",
description="\n".join([f"• {member.mention}" for member in members_playing[:20]]),
color=0x00ff00
)
if len(members_playing) > 20:
embed.set_footer(text=f"... and {len(members_playing) - 20} more")
await ctx.send(embed=embed)@bot.command()
async def massrole(ctx, action: str, role: disnake.Role, *, criteria: str = "all"):
"""Mass role management."""
if not ctx.author.guild_permissions.manage_roles:
return await ctx.send("You don't have permission to manage roles.")
if role >= ctx.author.top_role:
return await ctx.send("You cannot manage this role.")
if action.lower() not in ['add', 'remove']:
return await ctx.send("Action must be 'add' or 'remove'")
# Define member filters
filters = {
'all': lambda m: not m.bot,
'bots': lambda m: m.bot,
'online': lambda m: m.status == disnake.Status.online,
'boosters': lambda m: m.premium_since is not None,
'nobots': lambda m: not m.bot,
'humans': lambda m: not m.bot
}
member_filter = filters.get(criteria.lower(), filters['all'])
# Get members to modify
if action.lower() == 'add':
targets = [m for m in ctx.guild.members if member_filter(m) and role not in m.roles]
else:
targets = [m for m in ctx.guild.members if member_filter(m) and role in m.roles]
if not targets:
return await ctx.send(f"No members found matching criteria '{criteria}' for {action}")
# Confirm action
embed = disnake.Embed(
title="Mass Role Operation",
description=f"**Action:** {action.title()} role {role.mention}\n**Criteria:** {criteria}\n**Affected Members:** {len(targets)}",
color=0xff9900
)
embed.add_field(
name="Preview (first 10)",
value="\n".join([f"• {m.display_name}" for m in targets[:10]]),
inline=False
)
if len(targets) > 10:
embed.set_footer(text=f"... and {len(targets) - 10} more members")
embed.add_field(name="Confirm", value="React with ✅ to proceed", inline=False)
msg = await ctx.send(embed=embed)
await msg.add_reaction("✅")
await msg.add_reaction("❌")
def check(reaction, user):
return user == ctx.author and str(reaction.emoji) in ["✅", "❌"]
try:
reaction, _ = await bot.wait_for('reaction_add', timeout=30.0, check=check)
if str(reaction.emoji) == "❌":
return await ctx.send("Operation cancelled.")
# Perform mass operation
success_count = 0
failed_count = 0
for member in targets:
try:
if action.lower() == 'add':
await member.add_roles(role, reason=f"Mass role operation by {ctx.author}")
else:
await member.remove_roles(role, reason=f"Mass role operation by {ctx.author}")
success_count += 1
except Exception:
failed_count += 1
result_embed = disnake.Embed(
title="Mass Role Operation Complete",
color=0x00ff00 if failed_count == 0 else 0xff9900
)
result_embed.add_field(name="Successful", value=success_count, inline=True)
result_embed.add_field(name="Failed", value=failed_count, inline=True)
await ctx.send(embed=result_embed)
except asyncio.TimeoutError:
await ctx.send("Operation timed out.")
@bot.command()
async def cleanup_members(ctx, days: int = 30):
"""Clean up members who haven't been active."""
if not ctx.author.guild_permissions.kick_members:
return await ctx.send("You don't have permission to kick members.")
if not 1 <= days <= 365:
return await ctx.send("Days must be between 1 and 365.")
cutoff_date = disnake.utils.utcnow() - timedelta(days=days)
# Find inactive members
inactive_members = []
for member in ctx.guild.members:
if (member.bot or
member == ctx.guild.owner or
member.guild_permissions.administrator):
continue
# Check if member has been active recently
last_activity = member.joined_at or member.created_at
# This is a simplified check - in practice you might want to track
# last message times, voice activity, etc.
if last_activity < cutoff_date:
inactive_members.append(member)
if not inactive_members:
return await ctx.send(f"No inactive members found (inactive for {days} days).")
embed = disnake.Embed(
title="Cleanup Inactive Members",
description=f"Found {len(inactive_members)} members inactive for {days}+ days",
color=0xff0000
)
embed.add_field(
name="Members to Remove (first 10)",
value="\n".join([f"• {m.display_name} (joined: <t:{int(m.joined_at.timestamp())}:d>)" for m in inactive_members[:10]]),
inline=False
)
if len(inactive_members) > 10:
embed.set_footer(text=f"... and {len(inactive_members) - 10} more members")
embed.add_field(name="Confirm", value="React with ✅ to proceed with cleanup", inline=False)
msg = await ctx.send(embed=embed)
await msg.add_reaction("✅")
await msg.add_reaction("❌")
def check(reaction, user):
return user == ctx.author and str(reaction.emoji) in ["✅", "❌"]
try:
reaction, _ = await bot.wait_for('reaction_add', timeout=60.0, check=check)
if str(reaction.emoji) == "❌":
return await ctx.send("Cleanup cancelled.")
# Perform cleanup
success_count = 0
failed_count = 0
for member in inactive_members:
try:
await member.kick(reason=f"Inactive for {days}+ days - cleaned up by {ctx.author}")
success_count += 1
await asyncio.sleep(1) # Rate limit protection
except Exception:
failed_count += 1
result_embed = disnake.Embed(
title="Member Cleanup Complete",
color=0x00ff00 if failed_count == 0 else 0xff9900
)
result_embed.add_field(name="Removed", value=success_count, inline=True)
result_embed.add_field(name="Failed", value=failed_count, inline=True)
await ctx.send(embed=result_embed)
except asyncio.TimeoutError:
await ctx.send("Cleanup operation timed out.")Install with Tessl CLI
npx tessl i tessl/pypi-disnake