0
# Utilities & Helpers
1
2
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.
3
4
## Capabilities
5
6
### Authentication & URLs
7
8
Utilities for generating OAuth URLs and handling Discord authentication.
9
10
```python { .api }
11
def oauth_url(
12
client_id: int,
13
*,
14
permissions: Optional[Permissions] = None,
15
guild: Optional[Guild] = None,
16
redirect_uri: Optional[str] = None,
17
scopes: Optional[List[str]] = None,
18
disable_guild_select: bool = False
19
) -> str:
20
"""
21
Generate OAuth2 authorization URL for bot invitation.
22
23
Parameters:
24
- client_id: Bot's application ID
25
- permissions: Permissions to request
26
- guild: Specific guild to add bot to
27
- redirect_uri: URL to redirect to after authorization
28
- scopes: OAuth2 scopes to request (defaults to ['bot'])
29
- disable_guild_select: Whether to disable guild selection
30
31
Returns:
32
str: OAuth2 authorization URL
33
"""
34
35
def oauth_url_from_client(
36
client: Client,
37
*,
38
permissions: Optional[Permissions] = None,
39
guild: Optional[Guild] = None,
40
redirect_uri: Optional[str] = None,
41
scopes: Optional[List[str]] = None,
42
disable_guild_select: bool = False
43
) -> str:
44
"""
45
Generate OAuth2 URL from client instance.
46
47
Parameters:
48
- client: Discord client instance
49
- permissions: Permissions to request
50
- guild: Specific guild to add bot to
51
- redirect_uri: URL to redirect to after authorization
52
- scopes: OAuth2 scopes to request
53
- disable_guild_select: Whether to disable guild selection
54
55
Returns:
56
str: OAuth2 authorization URL
57
"""
58
```
59
60
### Snowflake Utilities
61
62
Functions for working with Discord snowflake IDs and extracting timestamp information.
63
64
```python { .api }
65
def snowflake_time(id: int) -> datetime:
66
"""
67
Extract creation timestamp from Discord snowflake ID.
68
69
Parameters:
70
- id: Discord snowflake ID
71
72
Returns:
73
datetime: UTC timestamp when the snowflake was created
74
"""
75
76
def time_snowflake(dt: datetime, high: bool = False) -> int:
77
"""
78
Generate a snowflake ID from a timestamp.
79
80
Parameters:
81
- dt: Datetime to convert to snowflake
82
- high: Whether to generate highest or lowest possible snowflake for the timestamp
83
84
Returns:
85
int: Snowflake ID
86
"""
87
88
DISCORD_EPOCH: int = 1420070400000
89
"""Discord epoch timestamp (January 1, 2015)."""
90
```
91
92
### Time & Date Utilities
93
94
Helper functions for time formatting and Discord timestamp formatting.
95
96
```python { .api }
97
def utcnow() -> datetime:
98
"""
99
Get current UTC datetime.
100
101
Returns:
102
datetime: Current UTC timestamp
103
"""
104
105
def compute_timedelta(dt: datetime) -> float:
106
"""
107
Compute time delta between datetime and now.
108
109
Parameters:
110
- dt: Target datetime
111
112
Returns:
113
float: Seconds until target datetime
114
"""
115
116
async def sleep_until(when: datetime, *, result: Any = None) -> Any:
117
"""
118
Sleep until a specific datetime.
119
120
Parameters:
121
- when: Datetime to sleep until
122
- result: Value to return after sleeping
123
124
Returns:
125
Any: The result parameter value
126
"""
127
128
def format_dt(dt: datetime, style: Optional[str] = None) -> str:
129
"""
130
Format datetime for Discord timestamp display.
131
132
Parameters:
133
- dt: Datetime to format
134
- style: Format style ('t', 'T', 'd', 'D', 'f', 'F', 'R')
135
- 't': Short time (16:20)
136
- 'T': Long time (16:20:30)
137
- 'd': Short date (20/04/2021)
138
- 'D': Long date (20 April 2021)
139
- 'f': Short date/time (default)
140
- 'F': Long date/time
141
- 'R': Relative time (2 months ago)
142
143
Returns:
144
str: Discord timestamp string (<t:timestamp:style>)
145
"""
146
```
147
148
### Text Processing
149
150
Functions for handling markdown, mentions, and text formatting.
151
152
```python { .api }
153
def escape_markdown(text: str, *, as_needed: bool = False, ignore_links: bool = True) -> str:
154
"""
155
Escape Discord markdown characters in text.
156
157
Parameters:
158
- text: Text to escape
159
- as_needed: Only escape characters that would affect formatting
160
- ignore_links: Don't escape characters in URLs
161
162
Returns:
163
str: Text with markdown characters escaped
164
"""
165
166
def escape_mentions(text: str) -> str:
167
"""
168
Escape user, channel, and role mentions in text.
169
170
Parameters:
171
- text: Text to escape mentions in
172
173
Returns:
174
str: Text with mentions escaped
175
"""
176
177
def remove_markdown(text: str, *, ignore_links: bool = True) -> str:
178
"""
179
Remove Discord markdown formatting from text.
180
181
Parameters:
182
- text: Text to remove markdown from
183
- ignore_links: Don't remove markdown from URLs
184
185
Returns:
186
str: Text with markdown formatting removed
187
"""
188
189
def clean_content(content: str, *, fix_channel_mentions: bool = False) -> str:
190
"""
191
Clean message content by resolving mentions to readable text.
192
193
Parameters:
194
- content: Message content to clean
195
- fix_channel_mentions: Whether to convert channel mentions to #channel-name
196
197
Returns:
198
str: Cleaned content with mentions resolved
199
"""
200
```
201
202
### Collection Utilities
203
204
Helper functions for working with sequences and collections.
205
206
```python { .api }
207
def find(predicate: Callable[[Any], bool], seq: Sequence[Any]) -> Optional[Any]:
208
"""
209
Find first element in sequence matching predicate.
210
211
Parameters:
212
- predicate: Function returning True for matching element
213
- seq: Sequence to search
214
215
Returns:
216
Any: First matching element, or None if not found
217
"""
218
219
def get(iterable: Iterable[Any], **attrs: Any) -> Optional[Any]:
220
"""
221
Find first element with matching attributes.
222
223
Parameters:
224
- iterable: Iterable to search
225
- attrs: Attributes to match (name=value pairs)
226
227
Returns:
228
Any: First matching element, or None if not found
229
"""
230
231
def as_chunks(iterator: Iterator[Any], max_size: int) -> Iterator[List[Any]]:
232
"""
233
Split iterator into chunks of maximum size.
234
235
Parameters:
236
- iterator: Iterator to split
237
- max_size: Maximum chunk size
238
239
Yields:
240
List[Any]: Chunks of iterator items
241
"""
242
243
T = TypeVar('T')
244
245
class SequenceProxy(Generic[T]):
246
"""
247
Proxy for sequences with filtering and transformation.
248
"""
249
def __init__(self, proxied: Sequence[T], *, predicate: Optional[Callable[[T], bool]] = None): ...
250
251
def __len__(self) -> int: ...
252
def __iter__(self) -> Iterator[T]: ...
253
def __getitem__(self, idx: Union[int, slice]) -> Union[T, Sequence[T]]: ...
254
255
def filter(self, predicate: Callable[[T], bool]) -> SequenceProxy[T]:
256
"""Filter sequence by predicate."""
257
258
def map(self, func: Callable[[T], Any]) -> SequenceProxy[Any]:
259
"""Transform sequence elements."""
260
```
261
262
### Async Utilities
263
264
Helper functions and classes for async programming patterns.
265
266
```python { .api }
267
async def maybe_coroutine(f: Union[Callable[..., Any], Callable[..., Awaitable[Any]]], *args: Any, **kwargs: Any) -> Any:
268
"""
269
Call function whether it's a coroutine or regular function.
270
271
Parameters:
272
- f: Function to call (sync or async)
273
- args: Positional arguments
274
- kwargs: Keyword arguments
275
276
Returns:
277
Any: Function result
278
"""
279
280
def setup_logging(
281
*,
282
handler: Optional[logging.Handler] = None,
283
formatter: Optional[logging.Formatter] = None,
284
level: int = logging.INFO,
285
root: bool = True
286
) -> None:
287
"""
288
Set up logging for discord.py library.
289
290
Parameters:
291
- handler: Log handler to use (defaults to StreamHandler)
292
- formatter: Log formatter to use
293
- level: Logging level
294
- root: Whether to set up root logger
295
"""
296
297
class _MissingSentinel:
298
"""Sentinel class for missing values."""
299
def __bool__(self) -> bool:
300
return False
301
302
def __repr__(self) -> str:
303
return '...'
304
305
MISSING: Any = _MissingSentinel()
306
"""Sentinel value for missing/unset parameters."""
307
```
308
309
### File & Data Utilities
310
311
Functions for handling files and data conversion.
312
313
```python { .api }
314
def parse_time(timestamp: str) -> Optional[datetime]:
315
"""
316
Parse ISO 8601 timestamp string.
317
318
Parameters:
319
- timestamp: ISO timestamp string
320
321
Returns:
322
datetime: Parsed datetime, or None if invalid
323
"""
324
325
def copy_doc(original: Callable) -> Callable:
326
"""
327
Decorator to copy docstring from another function.
328
329
Parameters:
330
- original: Function to copy docstring from
331
332
Returns:
333
Callable: Decorator function
334
"""
335
336
def cached_slot_property(name: str) -> property:
337
"""
338
Create a cached property that stores value in __slots__.
339
340
Parameters:
341
- name: Slot name to store cached value
342
343
Returns:
344
property: Cached property descriptor
345
"""
346
347
def generate_snowflake() -> int:
348
"""
349
Generate a unique snowflake ID.
350
351
Returns:
352
int: Generated snowflake ID
353
"""
354
```
355
356
### Permission Utilities
357
358
Helper functions for working with Discord permissions.
359
360
```python { .api }
361
def permissions_in_channel(
362
member: Member,
363
channel: GuildChannel,
364
*,
365
ignore_timeout: bool = False
366
) -> Permissions:
367
"""
368
Calculate member's permissions in a channel.
369
370
Parameters:
371
- member: Guild member
372
- channel: Guild channel
373
- ignore_timeout: Whether to ignore member timeout
374
375
Returns:
376
Permissions: Member's effective permissions in the channel
377
"""
378
379
def permissions_for_roles(
380
roles: List[Role],
381
channel: Optional[GuildChannel] = None
382
) -> Permissions:
383
"""
384
Calculate combined permissions for a list of roles.
385
386
Parameters:
387
- roles: List of roles
388
- channel: Channel for permission overwrites (optional)
389
390
Returns:
391
Permissions: Combined permissions
392
"""
393
```
394
395
### Message Utilities
396
397
Helper functions for working with messages and message references.
398
399
```python { .api }
400
def message_reference_from_url(url: str) -> Optional[MessageReference]:
401
"""
402
Create MessageReference from Discord message URL.
403
404
Parameters:
405
- url: Discord message URL
406
407
Returns:
408
MessageReference: Reference object, or None if invalid URL
409
"""
410
411
def jump_url_from_ids(guild_id: Optional[int], channel_id: int, message_id: int) -> str:
412
"""
413
Create Discord message jump URL from IDs.
414
415
Parameters:
416
- guild_id: Guild ID (None for DM)
417
- channel_id: Channel ID
418
- message_id: Message ID
419
420
Returns:
421
str: Discord message jump URL
422
"""
423
424
def resolve_invite(invite: Union[str, Invite]) -> str:
425
"""
426
Resolve invite to invite code.
427
428
Parameters:
429
- invite: Invite object or invite URL/code
430
431
Returns:
432
str: Invite code
433
"""
434
435
def resolve_template(template: Union[str, Template]) -> str:
436
"""
437
Resolve template to template code.
438
439
Parameters:
440
- template: Template object or template URL/code
441
442
Returns:
443
str: Template code
444
"""
445
```
446
447
### Context Managers
448
449
Utility context managers for common Discord operations.
450
451
```python { .api }
452
class Typing:
453
"""
454
Context manager for typing indicator.
455
"""
456
def __init__(self, messageable: Messageable): ...
457
458
async def __aenter__(self) -> Typing:
459
"""Start typing indicator."""
460
461
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
462
"""Stop typing indicator."""
463
464
def typing(messageable: Messageable) -> Typing:
465
"""
466
Create typing context manager.
467
468
Parameters:
469
- messageable: Channel or messageable to type in
470
471
Returns:
472
Typing: Typing context manager
473
"""
474
```
475
476
## Usage Examples
477
478
### Basic Utility Usage
479
480
```python
481
import discord
482
from discord.utils import *
483
484
# Generate OAuth URL
485
bot_id = 123456789012345678
486
permissions = discord.Permissions(send_messages=True, read_messages=True)
487
invite_url = oauth_url(bot_id, permissions=permissions)
488
print(f"Invite URL: {invite_url}")
489
490
# Work with snowflakes
491
user_id = 98765432109876543
492
creation_time = snowflake_time(user_id)
493
print(f"User created at: {creation_time}")
494
495
# Generate snowflake for specific time
496
import datetime
497
specific_time = datetime.datetime(2023, 1, 1, tzinfo=datetime.timezone.utc)
498
snowflake_id = time_snowflake(specific_time)
499
print(f"Snowflake for {specific_time}: {snowflake_id}")
500
```
501
502
### Text Processing Examples
503
504
```python
505
import discord
506
from discord.utils import *
507
508
# Escape markdown
509
text_with_markdown = "This has **bold** and *italic* text!"
510
escaped_text = escape_markdown(text_with_markdown)
511
print(f"Escaped: {escaped_text}")
512
513
# Remove markdown
514
clean_text = remove_markdown(text_with_markdown)
515
print(f"Clean: {clean_text}")
516
517
# Escape mentions
518
text_with_mentions = "Hello <@123456789> and <#987654321>!"
519
escaped_mentions = escape_mentions(text_with_mentions)
520
print(f"Escaped mentions: {escaped_mentions}")
521
522
# Format Discord timestamps
523
now = discord.utils.utcnow()
524
timestamp_formats = {
525
't': format_dt(now, 't'), # Short time
526
'T': format_dt(now, 'T'), # Long time
527
'd': format_dt(now, 'd'), # Short date
528
'D': format_dt(now, 'D'), # Long date
529
'f': format_dt(now, 'f'), # Short date/time
530
'F': format_dt(now, 'F'), # Long date/time
531
'R': format_dt(now, 'R'), # Relative time
532
}
533
534
for style, formatted in timestamp_formats.items():
535
print(f"Style '{style}': {formatted}")
536
```
537
538
### Collection Utilities Examples
539
540
```python
541
import discord
542
from discord.utils import *
543
544
# Example with guild members
545
async def find_member_examples(guild: discord.Guild):
546
# Find member by name
547
member = find(lambda m: m.name == "example_user", guild.members)
548
if member:
549
print(f"Found member: {member}")
550
551
# Get member by attribute
552
member = get(guild.members, name="example_user", discriminator="1234")
553
if member:
554
print(f"Found member: {member}")
555
556
# Find member with specific role
557
admin_role = get(guild.roles, name="Admin")
558
if admin_role:
559
admin = find(lambda m: admin_role in m.roles, guild.members)
560
if admin:
561
print(f"Found admin: {admin}")
562
563
# Split members into chunks
564
member_chunks = list(as_chunks(iter(guild.members), 10))
565
print(f"Split {len(guild.members)} members into {len(member_chunks)} chunks")
566
567
# Use SequenceProxy for filtering
568
online_members = SequenceProxy(guild.members).filter(
569
lambda m: m.status != discord.Status.offline
570
)
571
print(f"Online members: {len(online_members)}")
572
```
573
574
### Advanced Utility Bot
575
576
```python
577
import discord
578
from discord.ext import commands
579
from discord.utils import *
580
import asyncio
581
582
class UtilityBot(commands.Bot):
583
def __init__(self):
584
intents = discord.Intents.default()
585
intents.message_content = True
586
super().__init__(command_prefix='!', intents=intents)
587
588
async def setup_hook(self):
589
print(f"Bot is ready! Invite URL: {oauth_url_from_client(self)}")
590
591
bot = UtilityBot()
592
593
@bot.command()
594
async def userinfo(ctx, *, user: discord.User = None):
595
"""Get information about a user."""
596
user = user or ctx.author
597
598
# Use snowflake utility
599
created_at = snowflake_time(user.id)
600
601
embed = discord.Embed(title=f"User Info: {user}", color=0x0099ff)
602
embed.set_thumbnail(url=user.display_avatar.url)
603
embed.add_field(name="ID", value=user.id, inline=True)
604
embed.add_field(name="Created", value=format_dt(created_at, 'F'), inline=True)
605
embed.add_field(name="Account Age", value=format_dt(created_at, 'R'), inline=True)
606
607
# If in guild, show member info
608
if ctx.guild and isinstance(user, discord.Member):
609
embed.add_field(name="Joined", value=format_dt(user.joined_at, 'F'), inline=True)
610
embed.add_field(name="Member Since", value=format_dt(user.joined_at, 'R'), inline=True)
611
embed.add_field(name="Roles", value=len(user.roles) - 1, inline=True)
612
613
# Show top role
614
top_role = get(user.roles[1:], position=max(r.position for r in user.roles[1:]))
615
if top_role:
616
embed.add_field(name="Top Role", value=top_role.mention, inline=True)
617
618
await ctx.send(embed=embed)
619
620
@bot.command()
621
async def serverstat(ctx):
622
"""Show server statistics."""
623
guild = ctx.guild
624
if not guild:
625
await ctx.send("This command can only be used in a server!")
626
return
627
628
# Use collection utilities
629
online_members = len([m for m in guild.members if m.status != discord.Status.offline])
630
bot_count = len([m for m in guild.members if m.bot])
631
human_count = len(guild.members) - bot_count
632
633
# Channel statistics
634
text_channels = len(guild.text_channels)
635
voice_channels = len(guild.voice_channels)
636
categories = len(guild.categories)
637
638
embed = discord.Embed(title=f"{guild.name} Statistics", color=0x00ff00)
639
embed.set_thumbnail(url=guild.icon.url if guild.icon else None)
640
641
# Member stats
642
embed.add_field(name="👥 Members", value=f"""
643
Total: {guild.member_count}
644
Online: {online_members}
645
Humans: {human_count}
646
Bots: {bot_count}
647
""", inline=True)
648
649
# Channel stats
650
embed.add_field(name="📁 Channels", value=f"""
651
Text: {text_channels}
652
Voice: {voice_channels}
653
Categories: {categories}
654
Total: {text_channels + voice_channels}
655
""", inline=True)
656
657
# Server info
658
embed.add_field(name="ℹ️ Server Info", value=f"""
659
Created: {format_dt(guild.created_at, 'R')}
660
Owner: {guild.owner.mention if guild.owner else 'Unknown'}
661
Roles: {len(guild.roles)}
662
Emojis: {len(guild.emojis)}
663
""", inline=True)
664
665
await ctx.send(embed=embed)
666
667
@bot.command()
668
async def clean_text(ctx, *, text: str):
669
"""Demonstrate text cleaning utilities."""
670
embed = discord.Embed(title="Text Cleaning Example", color=0x9932cc)
671
672
# Original text
673
embed.add_field(name="Original", value=f"```{text}```", inline=False)
674
675
# Escaped markdown
676
escaped_md = escape_markdown(text)
677
embed.add_field(name="Escaped Markdown", value=f"```{escaped_md}```", inline=False)
678
679
# Removed markdown
680
no_markdown = remove_markdown(text)
681
embed.add_field(name="Removed Markdown", value=f"```{no_markdown}```", inline=False)
682
683
# Escaped mentions
684
escaped_mentions = escape_mentions(text)
685
embed.add_field(name="Escaped Mentions", value=f"```{escaped_mentions}```", inline=False)
686
687
await ctx.send(embed=embed)
688
689
@bot.command()
690
async def type_demo(ctx):
691
"""Demonstrate typing context manager."""
692
async with ctx.typing():
693
await asyncio.sleep(3) # Simulate work
694
await ctx.send("Done with typing indicator!")
695
696
@bot.command()
697
async def find_role(ctx, *, role_name: str):
698
"""Find a role by name (case insensitive)."""
699
# Use find utility
700
role = find(lambda r: r.name.lower() == role_name.lower(), ctx.guild.roles)
701
702
if role:
703
embed = discord.Embed(title=f"Role: {role.name}", color=role.color)
704
embed.add_field(name="ID", value=role.id, inline=True)
705
embed.add_field(name="Created", value=format_dt(role.created_at, 'R'), inline=True)
706
embed.add_field(name="Members", value=len(role.members), inline=True)
707
embed.add_field(name="Mentionable", value=role.mentionable, inline=True)
708
embed.add_field(name="Hoisted", value=role.hoist, inline=True)
709
embed.add_field(name="Position", value=role.position, inline=True)
710
711
await ctx.send(embed=embed)
712
else:
713
await ctx.send(f"Role '{role_name}' not found!")
714
715
@bot.command()
716
async def invite_link(ctx, permissions: str = None):
717
"""Generate bot invite link with optional permissions."""
718
perms = None
719
720
if permissions:
721
# Parse basic permissions
722
perm_dict = {}
723
for perm in permissions.split(','):
724
perm = perm.strip().lower()
725
if perm in ['admin', 'administrator']:
726
perm_dict['administrator'] = True
727
elif perm in ['manage', 'manage_guild']:
728
perm_dict['manage_guild'] = True
729
elif perm in ['kick', 'kick_members']:
730
perm_dict['kick_members'] = True
731
elif perm in ['ban', 'ban_members']:
732
perm_dict['ban_members'] = True
733
elif perm in ['messages', 'send_messages']:
734
perm_dict['send_messages'] = True
735
elif perm in ['embed', 'embed_links']:
736
perm_dict['embed_links'] = True
737
738
if perm_dict:
739
perms = discord.Permissions(**perm_dict)
740
741
# Generate invite URL
742
invite_url = oauth_url_from_client(ctx.bot, permissions=perms)
743
744
embed = discord.Embed(
745
title="Bot Invite Link",
746
description=f"[Click here to invite {ctx.bot.user.name}]({invite_url})",
747
color=0x0099ff
748
)
749
750
if perms:
751
embed.add_field(
752
name="Requested Permissions",
753
value=', '.join([perm.replace('_', ' ').title() for perm, value in perms if value]),
754
inline=False
755
)
756
757
await ctx.send(embed=embed)
758
759
# Error handling with utilities
760
@bot.event
761
async def on_command_error(ctx, error):
762
if isinstance(error, commands.CommandNotFound):
763
return
764
765
# Log error with timestamp
766
print(f"[{utcnow()}] Error in command {ctx.command}: {error}")
767
await ctx.send("An error occurred while processing the command.")
768
769
bot.run('YOUR_BOT_TOKEN')
770
```
771
772
### Sleep and Timing Examples
773
774
```python
775
import discord
776
from discord.utils import *
777
import asyncio
778
779
async def scheduled_tasks_example():
780
"""Example of using sleep_until for scheduled tasks."""
781
782
# Sleep until a specific time
783
target_time = utcnow().replace(hour=12, minute=0, second=0, microsecond=0)
784
if target_time < utcnow():
785
target_time += datetime.timedelta(days=1) # Tomorrow if time has passed
786
787
print(f"Sleeping until {format_dt(target_time, 'F')}")
788
await sleep_until(target_time)
789
print("Woke up at scheduled time!")
790
791
# Sleep for a computed duration
792
future_time = utcnow() + datetime.timedelta(minutes=5)
793
duration = compute_timedelta(future_time)
794
print(f"Sleeping for {duration} seconds")
795
await asyncio.sleep(duration)
796
print("Done sleeping!")
797
798
# Run the example
799
# asyncio.run(scheduled_tasks_example())
800
```