A modern, feature-rich, and async-ready API wrapper for Discord written in Python
—
Helper functions and utilities for common Discord operations including OAuth URLs, snowflake handling, time formatting, markdown processing, and various convenience functions for Discord bot development.
Utilities for generating OAuth URLs and handling Discord authentication.
def oauth_url(
client_id: int,
*,
permissions: Optional[Permissions] = None,
guild: Optional[Guild] = None,
redirect_uri: Optional[str] = None,
scopes: Optional[List[str]] = None,
disable_guild_select: bool = False
) -> str:
"""
Generate OAuth2 authorization URL for bot invitation.
Parameters:
- client_id: Bot's application ID
- permissions: Permissions to request
- guild: Specific guild to add bot to
- redirect_uri: URL to redirect to after authorization
- scopes: OAuth2 scopes to request (defaults to ['bot'])
- disable_guild_select: Whether to disable guild selection
Returns:
str: OAuth2 authorization URL
"""
def oauth_url_from_client(
client: Client,
*,
permissions: Optional[Permissions] = None,
guild: Optional[Guild] = None,
redirect_uri: Optional[str] = None,
scopes: Optional[List[str]] = None,
disable_guild_select: bool = False
) -> str:
"""
Generate OAuth2 URL from client instance.
Parameters:
- client: Discord client instance
- permissions: Permissions to request
- guild: Specific guild to add bot to
- redirect_uri: URL to redirect to after authorization
- scopes: OAuth2 scopes to request
- disable_guild_select: Whether to disable guild selection
Returns:
str: OAuth2 authorization URL
"""Functions for working with Discord snowflake IDs and extracting timestamp information.
def snowflake_time(id: int) -> datetime:
"""
Extract creation timestamp from Discord snowflake ID.
Parameters:
- id: Discord snowflake ID
Returns:
datetime: UTC timestamp when the snowflake was created
"""
def time_snowflake(dt: datetime, high: bool = False) -> int:
"""
Generate a snowflake ID from a timestamp.
Parameters:
- dt: Datetime to convert to snowflake
- high: Whether to generate highest or lowest possible snowflake for the timestamp
Returns:
int: Snowflake ID
"""
DISCORD_EPOCH: int = 1420070400000
"""Discord epoch timestamp (January 1, 2015)."""Helper functions for time formatting and Discord timestamp formatting.
def utcnow() -> datetime:
"""
Get current UTC datetime.
Returns:
datetime: Current UTC timestamp
"""
def compute_timedelta(dt: datetime) -> float:
"""
Compute time delta between datetime and now.
Parameters:
- dt: Target datetime
Returns:
float: Seconds until target datetime
"""
async def sleep_until(when: datetime, *, result: Any = None) -> Any:
"""
Sleep until a specific datetime.
Parameters:
- when: Datetime to sleep until
- result: Value to return after sleeping
Returns:
Any: The result parameter value
"""
def format_dt(dt: datetime, style: Optional[str] = None) -> str:
"""
Format datetime for Discord timestamp display.
Parameters:
- dt: Datetime to format
- style: Format style ('t', 'T', 'd', 'D', 'f', 'F', 'R')
- 't': Short time (16:20)
- 'T': Long time (16:20:30)
- 'd': Short date (20/04/2021)
- 'D': Long date (20 April 2021)
- 'f': Short date/time (default)
- 'F': Long date/time
- 'R': Relative time (2 months ago)
Returns:
str: Discord timestamp string (<t:timestamp:style>)
"""Functions for handling markdown, mentions, and text formatting.
def escape_markdown(text: str, *, as_needed: bool = False, ignore_links: bool = True) -> str:
"""
Escape Discord markdown characters in text.
Parameters:
- text: Text to escape
- as_needed: Only escape characters that would affect formatting
- ignore_links: Don't escape characters in URLs
Returns:
str: Text with markdown characters escaped
"""
def escape_mentions(text: str) -> str:
"""
Escape user, channel, and role mentions in text.
Parameters:
- text: Text to escape mentions in
Returns:
str: Text with mentions escaped
"""
def remove_markdown(text: str, *, ignore_links: bool = True) -> str:
"""
Remove Discord markdown formatting from text.
Parameters:
- text: Text to remove markdown from
- ignore_links: Don't remove markdown from URLs
Returns:
str: Text with markdown formatting removed
"""
def clean_content(content: str, *, fix_channel_mentions: bool = False) -> str:
"""
Clean message content by resolving mentions to readable text.
Parameters:
- content: Message content to clean
- fix_channel_mentions: Whether to convert channel mentions to #channel-name
Returns:
str: Cleaned content with mentions resolved
"""Helper functions for working with sequences and collections.
def find(predicate: Callable[[Any], bool], seq: Sequence[Any]) -> Optional[Any]:
"""
Find first element in sequence matching predicate.
Parameters:
- predicate: Function returning True for matching element
- seq: Sequence to search
Returns:
Any: First matching element, or None if not found
"""
def get(iterable: Iterable[Any], **attrs: Any) -> Optional[Any]:
"""
Find first element with matching attributes.
Parameters:
- iterable: Iterable to search
- attrs: Attributes to match (name=value pairs)
Returns:
Any: First matching element, or None if not found
"""
def as_chunks(iterator: Iterator[Any], max_size: int) -> Iterator[List[Any]]:
"""
Split iterator into chunks of maximum size.
Parameters:
- iterator: Iterator to split
- max_size: Maximum chunk size
Yields:
List[Any]: Chunks of iterator items
"""
T = TypeVar('T')
class SequenceProxy(Generic[T]):
"""
Proxy for sequences with filtering and transformation.
"""
def __init__(self, proxied: Sequence[T], *, predicate: Optional[Callable[[T], bool]] = None): ...
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[T]: ...
def __getitem__(self, idx: Union[int, slice]) -> Union[T, Sequence[T]]: ...
def filter(self, predicate: Callable[[T], bool]) -> SequenceProxy[T]:
"""Filter sequence by predicate."""
def map(self, func: Callable[[T], Any]) -> SequenceProxy[Any]:
"""Transform sequence elements."""Helper functions and classes for async programming patterns.
async def maybe_coroutine(f: Union[Callable[..., Any], Callable[..., Awaitable[Any]]], *args: Any, **kwargs: Any) -> Any:
"""
Call function whether it's a coroutine or regular function.
Parameters:
- f: Function to call (sync or async)
- args: Positional arguments
- kwargs: Keyword arguments
Returns:
Any: Function result
"""
def setup_logging(
*,
handler: Optional[logging.Handler] = None,
formatter: Optional[logging.Formatter] = None,
level: int = logging.INFO,
root: bool = True
) -> None:
"""
Set up logging for discord.py library.
Parameters:
- handler: Log handler to use (defaults to StreamHandler)
- formatter: Log formatter to use
- level: Logging level
- root: Whether to set up root logger
"""
class _MissingSentinel:
"""Sentinel class for missing values."""
def __bool__(self) -> bool:
return False
def __repr__(self) -> str:
return '...'
MISSING: Any = _MissingSentinel()
"""Sentinel value for missing/unset parameters."""Functions for handling files and data conversion.
def parse_time(timestamp: str) -> Optional[datetime]:
"""
Parse ISO 8601 timestamp string.
Parameters:
- timestamp: ISO timestamp string
Returns:
datetime: Parsed datetime, or None if invalid
"""
def copy_doc(original: Callable) -> Callable:
"""
Decorator to copy docstring from another function.
Parameters:
- original: Function to copy docstring from
Returns:
Callable: Decorator function
"""
def cached_slot_property(name: str) -> property:
"""
Create a cached property that stores value in __slots__.
Parameters:
- name: Slot name to store cached value
Returns:
property: Cached property descriptor
"""
def generate_snowflake() -> int:
"""
Generate a unique snowflake ID.
Returns:
int: Generated snowflake ID
"""Helper functions for working with Discord permissions.
def permissions_in_channel(
member: Member,
channel: GuildChannel,
*,
ignore_timeout: bool = False
) -> Permissions:
"""
Calculate member's permissions in a channel.
Parameters:
- member: Guild member
- channel: Guild channel
- ignore_timeout: Whether to ignore member timeout
Returns:
Permissions: Member's effective permissions in the channel
"""
def permissions_for_roles(
roles: List[Role],
channel: Optional[GuildChannel] = None
) -> Permissions:
"""
Calculate combined permissions for a list of roles.
Parameters:
- roles: List of roles
- channel: Channel for permission overwrites (optional)
Returns:
Permissions: Combined permissions
"""Helper functions for working with messages and message references.
def message_reference_from_url(url: str) -> Optional[MessageReference]:
"""
Create MessageReference from Discord message URL.
Parameters:
- url: Discord message URL
Returns:
MessageReference: Reference object, or None if invalid URL
"""
def jump_url_from_ids(guild_id: Optional[int], channel_id: int, message_id: int) -> str:
"""
Create Discord message jump URL from IDs.
Parameters:
- guild_id: Guild ID (None for DM)
- channel_id: Channel ID
- message_id: Message ID
Returns:
str: Discord message jump URL
"""
def resolve_invite(invite: Union[str, Invite]) -> str:
"""
Resolve invite to invite code.
Parameters:
- invite: Invite object or invite URL/code
Returns:
str: Invite code
"""
def resolve_template(template: Union[str, Template]) -> str:
"""
Resolve template to template code.
Parameters:
- template: Template object or template URL/code
Returns:
str: Template code
"""Utility context managers for common Discord operations.
class Typing:
"""
Context manager for typing indicator.
"""
def __init__(self, messageable: Messageable): ...
async def __aenter__(self) -> Typing:
"""Start typing indicator."""
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
"""Stop typing indicator."""
def typing(messageable: Messageable) -> Typing:
"""
Create typing context manager.
Parameters:
- messageable: Channel or messageable to type in
Returns:
Typing: Typing context manager
"""import discord
from discord.utils import *
# Generate OAuth URL
bot_id = 123456789012345678
permissions = discord.Permissions(send_messages=True, read_messages=True)
invite_url = oauth_url(bot_id, permissions=permissions)
print(f"Invite URL: {invite_url}")
# Work with snowflakes
user_id = 98765432109876543
creation_time = snowflake_time(user_id)
print(f"User created at: {creation_time}")
# Generate snowflake for specific time
import datetime
specific_time = datetime.datetime(2023, 1, 1, tzinfo=datetime.timezone.utc)
snowflake_id = time_snowflake(specific_time)
print(f"Snowflake for {specific_time}: {snowflake_id}")import discord
from discord.utils import *
# Escape markdown
text_with_markdown = "This has **bold** and *italic* text!"
escaped_text = escape_markdown(text_with_markdown)
print(f"Escaped: {escaped_text}")
# Remove markdown
clean_text = remove_markdown(text_with_markdown)
print(f"Clean: {clean_text}")
# Escape mentions
text_with_mentions = "Hello <@123456789> and <#987654321>!"
escaped_mentions = escape_mentions(text_with_mentions)
print(f"Escaped mentions: {escaped_mentions}")
# Format Discord timestamps
now = discord.utils.utcnow()
timestamp_formats = {
't': format_dt(now, 't'), # Short time
'T': format_dt(now, 'T'), # Long time
'd': format_dt(now, 'd'), # Short date
'D': format_dt(now, 'D'), # Long date
'f': format_dt(now, 'f'), # Short date/time
'F': format_dt(now, 'F'), # Long date/time
'R': format_dt(now, 'R'), # Relative time
}
for style, formatted in timestamp_formats.items():
print(f"Style '{style}': {formatted}")import discord
from discord.utils import *
# Example with guild members
async def find_member_examples(guild: discord.Guild):
# Find member by name
member = find(lambda m: m.name == "example_user", guild.members)
if member:
print(f"Found member: {member}")
# Get member by attribute
member = get(guild.members, name="example_user", discriminator="1234")
if member:
print(f"Found member: {member}")
# Find member with specific role
admin_role = get(guild.roles, name="Admin")
if admin_role:
admin = find(lambda m: admin_role in m.roles, guild.members)
if admin:
print(f"Found admin: {admin}")
# Split members into chunks
member_chunks = list(as_chunks(iter(guild.members), 10))
print(f"Split {len(guild.members)} members into {len(member_chunks)} chunks")
# Use SequenceProxy for filtering
online_members = SequenceProxy(guild.members).filter(
lambda m: m.status != discord.Status.offline
)
print(f"Online members: {len(online_members)}")import discord
from discord.ext import commands
from discord.utils import *
import asyncio
class UtilityBot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
intents.message_content = True
super().__init__(command_prefix='!', intents=intents)
async def setup_hook(self):
print(f"Bot is ready! Invite URL: {oauth_url_from_client(self)}")
bot = UtilityBot()
@bot.command()
async def userinfo(ctx, *, user: discord.User = None):
"""Get information about a user."""
user = user or ctx.author
# Use snowflake utility
created_at = snowflake_time(user.id)
embed = discord.Embed(title=f"User Info: {user}", color=0x0099ff)
embed.set_thumbnail(url=user.display_avatar.url)
embed.add_field(name="ID", value=user.id, inline=True)
embed.add_field(name="Created", value=format_dt(created_at, 'F'), inline=True)
embed.add_field(name="Account Age", value=format_dt(created_at, 'R'), inline=True)
# If in guild, show member info
if ctx.guild and isinstance(user, discord.Member):
embed.add_field(name="Joined", value=format_dt(user.joined_at, 'F'), inline=True)
embed.add_field(name="Member Since", value=format_dt(user.joined_at, 'R'), inline=True)
embed.add_field(name="Roles", value=len(user.roles) - 1, inline=True)
# Show top role
top_role = get(user.roles[1:], position=max(r.position for r in user.roles[1:]))
if top_role:
embed.add_field(name="Top Role", value=top_role.mention, inline=True)
await ctx.send(embed=embed)
@bot.command()
async def serverstat(ctx):
"""Show server statistics."""
guild = ctx.guild
if not guild:
await ctx.send("This command can only be used in a server!")
return
# Use collection utilities
online_members = len([m for m in guild.members if m.status != discord.Status.offline])
bot_count = len([m for m in guild.members if m.bot])
human_count = len(guild.members) - bot_count
# Channel statistics
text_channels = len(guild.text_channels)
voice_channels = len(guild.voice_channels)
categories = len(guild.categories)
embed = discord.Embed(title=f"{guild.name} Statistics", color=0x00ff00)
embed.set_thumbnail(url=guild.icon.url if guild.icon else None)
# Member stats
embed.add_field(name="👥 Members", value=f"""
Total: {guild.member_count}
Online: {online_members}
Humans: {human_count}
Bots: {bot_count}
""", inline=True)
# Channel stats
embed.add_field(name="📁 Channels", value=f"""
Text: {text_channels}
Voice: {voice_channels}
Categories: {categories}
Total: {text_channels + voice_channels}
""", inline=True)
# Server info
embed.add_field(name="ℹ️ Server Info", value=f"""
Created: {format_dt(guild.created_at, 'R')}
Owner: {guild.owner.mention if guild.owner else 'Unknown'}
Roles: {len(guild.roles)}
Emojis: {len(guild.emojis)}
""", inline=True)
await ctx.send(embed=embed)
@bot.command()
async def clean_text(ctx, *, text: str):
"""Demonstrate text cleaning utilities."""
embed = discord.Embed(title="Text Cleaning Example", color=0x9932cc)
# Original text
embed.add_field(name="Original", value=f"```{text}```", inline=False)
# Escaped markdown
escaped_md = escape_markdown(text)
embed.add_field(name="Escaped Markdown", value=f"```{escaped_md}```", inline=False)
# Removed markdown
no_markdown = remove_markdown(text)
embed.add_field(name="Removed Markdown", value=f"```{no_markdown}```", inline=False)
# Escaped mentions
escaped_mentions = escape_mentions(text)
embed.add_field(name="Escaped Mentions", value=f"```{escaped_mentions}```", inline=False)
await ctx.send(embed=embed)
@bot.command()
async def type_demo(ctx):
"""Demonstrate typing context manager."""
async with ctx.typing():
await asyncio.sleep(3) # Simulate work
await ctx.send("Done with typing indicator!")
@bot.command()
async def find_role(ctx, *, role_name: str):
"""Find a role by name (case insensitive)."""
# Use find utility
role = find(lambda r: r.name.lower() == role_name.lower(), ctx.guild.roles)
if role:
embed = discord.Embed(title=f"Role: {role.name}", color=role.color)
embed.add_field(name="ID", value=role.id, inline=True)
embed.add_field(name="Created", value=format_dt(role.created_at, 'R'), inline=True)
embed.add_field(name="Members", value=len(role.members), inline=True)
embed.add_field(name="Mentionable", value=role.mentionable, inline=True)
embed.add_field(name="Hoisted", value=role.hoist, inline=True)
embed.add_field(name="Position", value=role.position, inline=True)
await ctx.send(embed=embed)
else:
await ctx.send(f"Role '{role_name}' not found!")
@bot.command()
async def invite_link(ctx, permissions: str = None):
"""Generate bot invite link with optional permissions."""
perms = None
if permissions:
# Parse basic permissions
perm_dict = {}
for perm in permissions.split(','):
perm = perm.strip().lower()
if perm in ['admin', 'administrator']:
perm_dict['administrator'] = True
elif perm in ['manage', 'manage_guild']:
perm_dict['manage_guild'] = True
elif perm in ['kick', 'kick_members']:
perm_dict['kick_members'] = True
elif perm in ['ban', 'ban_members']:
perm_dict['ban_members'] = True
elif perm in ['messages', 'send_messages']:
perm_dict['send_messages'] = True
elif perm in ['embed', 'embed_links']:
perm_dict['embed_links'] = True
if perm_dict:
perms = discord.Permissions(**perm_dict)
# Generate invite URL
invite_url = oauth_url_from_client(ctx.bot, permissions=perms)
embed = discord.Embed(
title="Bot Invite Link",
description=f"[Click here to invite {ctx.bot.user.name}]({invite_url})",
color=0x0099ff
)
if perms:
embed.add_field(
name="Requested Permissions",
value=', '.join([perm.replace('_', ' ').title() for perm, value in perms if value]),
inline=False
)
await ctx.send(embed=embed)
# Error handling with utilities
@bot.event
async def on_command_error(ctx, error):
if isinstance(error, commands.CommandNotFound):
return
# Log error with timestamp
print(f"[{utcnow()}] Error in command {ctx.command}: {error}")
await ctx.send("An error occurred while processing the command.")
bot.run('YOUR_BOT_TOKEN')import discord
from discord.utils import *
import asyncio
async def scheduled_tasks_example():
"""Example of using sleep_until for scheduled tasks."""
# Sleep until a specific time
target_time = utcnow().replace(hour=12, minute=0, second=0, microsecond=0)
if target_time < utcnow():
target_time += datetime.timedelta(days=1) # Tomorrow if time has passed
print(f"Sleeping until {format_dt(target_time, 'F')}")
await sleep_until(target_time)
print("Woke up at scheduled time!")
# Sleep for a computed duration
future_time = utcnow() + datetime.timedelta(minutes=5)
duration = compute_timedelta(future_time)
print(f"Sleeping for {duration} seconds")
await asyncio.sleep(duration)
print("Done sleeping!")
# Run the example
# asyncio.run(scheduled_tasks_example())Install with Tessl CLI
npx tessl i tessl/pypi-discord-py