0
# Error Handling
1
2
Comprehensive error handling system covering all Discord API errors, command framework errors, interaction errors, and custom exception types with proper error recovery patterns and best practices for robust Discord bot development.
3
4
## Capabilities
5
6
### Base Discord Exceptions
7
8
Core exception hierarchy for all Discord-related errors and failures.
9
10
```python { .api }
11
class DiscordException(Exception):
12
"""Base exception class for all Discord-related errors."""
13
14
class ClientException(DiscordException):
15
"""Exception for client operation failures."""
16
17
class InvalidData(ClientException):
18
"""Exception for malformed or invalid data from Discord API."""
19
20
class InvalidArgument(ClientException):
21
"""Exception for invalid arguments passed to functions."""
22
23
class LoginFailure(ClientException):
24
"""Exception for bot authentication failures."""
25
26
class NoMoreItems(ClientException):
27
"""Exception when async iterator has no more items."""
28
29
class GatewayNotFound(DiscordException):
30
"""Exception when gateway URL cannot be found."""
31
32
class ConnectionClosed(ClientException):
33
"""Exception when WebSocket connection is closed."""
34
35
def __init__(self, socket: WebSocketClientProtocol, *, shard_id: Optional[int] = None):
36
"""
37
Initialize connection closed exception.
38
39
Parameters:
40
- socket: WebSocket connection
41
- shard_id: Shard ID if applicable
42
"""
43
44
code: int
45
reason: str
46
shard_id: Optional[int]
47
48
class PrivilegedIntentsRequired(ClientException):
49
"""Exception for missing privileged intents."""
50
51
def __init__(self, shard_id: Optional[int] = None):
52
"""
53
Initialize privileged intents exception.
54
55
Parameters:
56
- shard_id: Shard ID if applicable
57
"""
58
59
shard_id: Optional[int]
60
61
class SessionStartLimitReached(ClientException):
62
"""Exception when session start limit is reached."""
63
```
64
65
### HTTP Exceptions
66
67
HTTP request and API error handling for Discord REST API interactions.
68
69
```python { .api }
70
class HTTPException(DiscordException):
71
"""Exception for HTTP request failures."""
72
73
def __init__(self, response: ClientResponse, message: Union[str, Dict[str, Any]]):
74
"""
75
Initialize HTTP exception.
76
77
Parameters:
78
- response: HTTP response object
79
- message: Error message or error data
80
"""
81
82
response: ClientResponse
83
status: int
84
code: int
85
text: str
86
87
class Forbidden(HTTPException):
88
"""Exception for 403 Forbidden HTTP errors."""
89
90
class NotFound(HTTPException):
91
"""Exception for 404 Not Found HTTP errors."""
92
93
class DiscordServerError(HTTPException):
94
"""Exception for 5xx server errors from Discord."""
95
96
class RateLimited(HTTPException):
97
"""Exception for rate limit errors (429)."""
98
99
def __init__(self, response: ClientResponse, message: Dict[str, Any]):
100
"""
101
Initialize rate limit exception.
102
103
Parameters:
104
- response: HTTP response
105
- message: Rate limit data
106
"""
107
108
retry_after: float
109
is_global: bool
110
```
111
112
### Interaction Exceptions
113
114
Errors specific to Discord interactions and UI components.
115
116
```python { .api }
117
class InteractionException(DiscordException):
118
"""Base exception for interaction-related errors."""
119
120
class InteractionResponded(InteractionException):
121
"""Exception when interaction has already been responded to."""
122
123
def __init__(self, interaction: Interaction):
124
"""
125
Initialize interaction responded exception.
126
127
Parameters:
128
- interaction: The interaction that was already responded to
129
"""
130
131
interaction: Interaction
132
133
class InteractionNotResponded(InteractionException):
134
"""Exception when interaction has not been responded to."""
135
136
def __init__(self, interaction: Interaction):
137
"""
138
Initialize interaction not responded exception.
139
140
Parameters:
141
- interaction: The interaction that needs a response
142
"""
143
144
interaction: Interaction
145
146
class InteractionTimedOut(InteractionException):
147
"""Exception when interaction times out."""
148
149
def __init__(self, interaction: Interaction):
150
"""
151
Initialize interaction timeout exception.
152
153
Parameters:
154
- interaction: The timed out interaction
155
"""
156
157
interaction: Interaction
158
159
class InteractionNotEditable(InteractionException):
160
"""Exception when interaction response cannot be edited."""
161
162
def __init__(self, interaction: Interaction):
163
"""
164
Initialize interaction not editable exception.
165
166
Parameters:
167
- interaction: The non-editable interaction
168
"""
169
170
interaction: Interaction
171
172
class ModalChainNotSupported(InteractionException):
173
"""Exception when modal chaining is attempted but not supported."""
174
175
class WebhookTokenMissing(DiscordException):
176
"""Exception when webhook token is required but missing."""
177
178
class LocalizationKeyError(DiscordException):
179
"""Exception for localization key errors."""
180
181
def __init__(self, key: str):
182
"""
183
Initialize localization key error.
184
185
Parameters:
186
- key: The missing localization key
187
"""
188
189
key: str
190
```
191
192
### Command Framework Exceptions
193
194
Comprehensive error handling for the message-based command framework.
195
196
```python { .api }
197
class CommandError(DiscordException):
198
"""Base exception for command-related errors."""
199
200
def __init__(self, message: str = None, *args):
201
"""
202
Initialize command error.
203
204
Parameters:
205
- message: Error message
206
- args: Additional arguments
207
"""
208
209
class CommandNotFound(CommandError):
210
"""Exception when command is not found."""
211
212
class MissingRequiredArgument(UserInputError):
213
"""Exception for missing required command arguments."""
214
215
def __init__(self, param: Parameter):
216
"""
217
Initialize missing argument exception.
218
219
Parameters:
220
- param: Missing parameter information
221
"""
222
223
param: Parameter
224
225
class TooManyArguments(UserInputError):
226
"""Exception when too many arguments are provided."""
227
228
class BadArgument(UserInputError):
229
"""Exception for invalid argument values or types."""
230
231
class BadUnionArgument(UserInputError):
232
"""Exception for failed union type conversions."""
233
234
def __init__(self, param: Parameter, converters: List[Converter], errors: List[CommandError]):
235
"""
236
Initialize bad union argument exception.
237
238
Parameters:
239
- param: Parameter that failed conversion
240
- converters: Attempted converters
241
- errors: Conversion errors
242
"""
243
244
param: Parameter
245
converters: List[Converter]
246
errors: List[CommandError]
247
248
class BadLiteralArgument(UserInputError):
249
"""Exception for failed literal type conversions."""
250
251
def __init__(self, param: Parameter, literals: List[Any], errors: List[CommandError]):
252
"""
253
Initialize bad literal argument exception.
254
255
Parameters:
256
- param: Parameter that failed conversion
257
- literals: Expected literal values
258
- errors: Conversion errors
259
"""
260
261
param: Parameter
262
literals: List[Any]
263
errors: List[CommandError]
264
265
class ArgumentParsingError(UserInputError):
266
"""Exception for general argument parsing failures."""
267
268
class UnexpectedQuoteError(ArgumentParsingError):
269
"""Exception for unexpected quotes in arguments."""
270
271
def __init__(self, quote: str):
272
"""
273
Initialize unexpected quote error.
274
275
Parameters:
276
- quote: The unexpected quote character
277
"""
278
279
quote: str
280
281
class InvalidEndOfQuotedStringError(ArgumentParsingError):
282
"""Exception for invalid end of quoted string."""
283
284
def __init__(self, char: str):
285
"""
286
Initialize invalid end of quoted string error.
287
288
Parameters:
289
- char: The invalid character
290
"""
291
292
char: str
293
294
class ExpectedClosingQuoteError(ArgumentParsingError):
295
"""Exception for missing closing quote."""
296
297
def __init__(self, close_quote: str):
298
"""
299
Initialize expected closing quote error.
300
301
Parameters:
302
- close_quote: Expected closing quote character
303
"""
304
305
close_quote: str
306
307
class CheckFailure(CommandError):
308
"""Exception when command check fails."""
309
310
class CheckAnyFailure(CheckFailure):
311
"""Exception when all command checks fail."""
312
313
def __init__(self, checks: List[Check], errors: List[CheckFailure]):
314
"""
315
Initialize check any failure exception.
316
317
Parameters:
318
- checks: Failed checks
319
- errors: Individual check errors
320
"""
321
322
checks: List[Check]
323
errors: List[CheckFailure]
324
325
class PrivateMessageOnly(CheckFailure):
326
"""Exception for DM-only commands used in guilds."""
327
328
class NoPrivateMessage(CheckFailure):
329
"""Exception for guild-only commands used in DMs."""
330
331
class NotOwner(CheckFailure):
332
"""Exception when non-owner tries to use owner-only command."""
333
334
class MissingRole(CheckFailure):
335
"""Exception for missing required role."""
336
337
def __init__(self, missing_role: Union[str, int]):
338
"""
339
Initialize missing role exception.
340
341
Parameters:
342
- missing_role: Required role name or ID
343
"""
344
345
missing_role: Union[str, int]
346
347
class BotMissingRole(CheckFailure):
348
"""Exception when bot is missing required role."""
349
350
def __init__(self, missing_role: Union[str, int]):
351
"""
352
Initialize bot missing role exception.
353
354
Parameters:
355
- missing_role: Required role name or ID
356
"""
357
358
missing_role: Union[str, int]
359
360
class MissingAnyRole(CheckFailure):
361
"""Exception when missing any of the required roles."""
362
363
def __init__(self, missing_roles: List[Union[str, int]]):
364
"""
365
Initialize missing any role exception.
366
367
Parameters:
368
- missing_roles: List of required role names or IDs
369
"""
370
371
missing_roles: List[Union[str, int]]
372
373
class BotMissingAnyRole(CheckFailure):
374
"""Exception when bot is missing any of the required roles."""
375
376
def __init__(self, missing_roles: List[Union[str, int]]):
377
"""
378
Initialize bot missing any role exception.
379
380
Parameters:
381
- missing_roles: List of required role names or IDs
382
"""
383
384
missing_roles: List[Union[str, int]]
385
386
class MissingPermissions(CheckFailure):
387
"""Exception for missing required permissions."""
388
389
def __init__(self, missing_permissions: List[str]):
390
"""
391
Initialize missing permissions exception.
392
393
Parameters:
394
- missing_permissions: List of missing permission names
395
"""
396
397
missing_permissions: List[str]
398
399
class BotMissingPermissions(CheckFailure):
400
"""Exception when bot is missing required permissions."""
401
402
def __init__(self, missing_permissions: List[str]):
403
"""
404
Initialize bot missing permissions exception.
405
406
Parameters:
407
- missing_permissions: List of missing permission names
408
"""
409
410
missing_permissions: List[str]
411
412
class NSFWChannelRequired(CheckFailure):
413
"""Exception for NSFW commands used in non-NSFW channels."""
414
415
class DisabledCommand(CommandError):
416
"""Exception when command is disabled."""
417
418
class CommandInvokeError(CommandError):
419
"""Exception wrapping errors during command execution."""
420
421
def __init__(self, e: Exception):
422
"""
423
Initialize command invoke error.
424
425
Parameters:
426
- e: Original exception that was raised
427
"""
428
429
original: Exception
430
431
class CommandOnCooldown(CommandError):
432
"""Exception when command is on cooldown."""
433
434
def __init__(self, cooldown: Cooldown, retry_after: float, type: BucketType):
435
"""
436
Initialize command on cooldown exception.
437
438
Parameters:
439
- cooldown: Cooldown configuration
440
- retry_after: Time until command can be used again
441
- type: Cooldown bucket type
442
"""
443
444
cooldown: Cooldown
445
retry_after: float
446
type: BucketType
447
448
class MaxConcurrencyReached(CommandError):
449
"""Exception when maximum command concurrency is reached."""
450
451
def __init__(self, number: int, per: BucketType):
452
"""
453
Initialize max concurrency exception.
454
455
Parameters:
456
- number: Maximum allowed concurrent uses
457
- per: Concurrency bucket type
458
"""
459
460
number: int
461
per: BucketType
462
463
class UserInputError(CommandError):
464
"""Base exception for user input errors."""
465
466
class ConversionError(CommandError):
467
"""Exception for type conversion failures."""
468
469
def __init__(self, converter: Converter, original: Exception):
470
"""
471
Initialize conversion error.
472
473
Parameters:
474
- converter: Converter that failed
475
- original: Original exception
476
"""
477
478
converter: Converter
479
original: Exception
480
481
class ExtensionError(DiscordException):
482
"""Base exception for extension-related errors."""
483
484
def __init__(self, message: str = None, *args, name: str):
485
"""
486
Initialize extension error.
487
488
Parameters:
489
- message: Error message
490
- args: Additional arguments
491
- name: Extension name
492
"""
493
494
name: str
495
496
class ExtensionAlreadyLoaded(ExtensionError):
497
"""Exception when extension is already loaded."""
498
499
class ExtensionNotLoaded(ExtensionError):
500
"""Exception when extension is not loaded."""
501
502
class NoEntryPointError(ExtensionError):
503
"""Exception when extension has no setup function."""
504
505
class ExtensionFailed(ExtensionError):
506
"""Exception when extension fails to load."""
507
508
def __init__(self, name: str, original: Exception):
509
"""
510
Initialize extension failed exception.
511
512
Parameters:
513
- name: Extension name
514
- original: Original exception
515
"""
516
517
original: Exception
518
519
class ExtensionNotFound(ExtensionError):
520
"""Exception when extension module is not found."""
521
```
522
523
### Application Command Exceptions
524
525
Errors specific to slash commands and application commands.
526
527
```python { .api }
528
class ApplicationCommandError(DiscordException):
529
"""Base exception for application command errors."""
530
531
class ApplicationCommandInvokeError(ApplicationCommandError):
532
"""Exception wrapping errors during application command execution."""
533
534
def __init__(self, e: Exception):
535
"""
536
Initialize application command invoke error.
537
538
Parameters:
539
- e: Original exception that was raised
540
"""
541
542
original: Exception
543
544
class ApplicationCommandOnCooldown(ApplicationCommandError):
545
"""Exception when application command is on cooldown."""
546
547
def __init__(self, cooldown: Cooldown, retry_after: float, type: BucketType):
548
"""
549
Initialize application command cooldown exception.
550
551
Parameters:
552
- cooldown: Cooldown configuration
553
- retry_after: Time until command can be used again
554
- type: Cooldown bucket type
555
"""
556
557
cooldown: Cooldown
558
retry_after: float
559
type: BucketType
560
561
class ApplicationCommandCheckFailure(ApplicationCommandError):
562
"""Exception when application command check fails."""
563
```
564
565
### Error Event Handlers
566
567
Event system for handling and responding to various error conditions.
568
569
```python { .api }
570
@bot.event
571
async def on_error(event: str, *args, **kwargs):
572
"""
573
Called when an error occurs in event handlers.
574
575
Parameters:
576
- event: Event name that caused the error
577
- args: Event arguments
578
- kwargs: Event keyword arguments
579
"""
580
581
@bot.event
582
async def on_command_error(ctx: Context, error: CommandError):
583
"""
584
Called when command execution raises an error.
585
586
Parameters:
587
- ctx: Command context
588
- error: Exception that was raised
589
"""
590
591
@bot.event
592
async def on_application_command_error(inter: ApplicationCommandInteraction, error: Exception):
593
"""
594
Called when application command execution raises an error.
595
596
Parameters:
597
- inter: Application command interaction
598
- error: Exception that was raised
599
"""
600
601
async def on_slash_command_error(inter: ApplicationCommandInteraction, error: Exception):
602
"""
603
Called when slash command execution raises an error.
604
605
Parameters:
606
- inter: Slash command interaction
607
- error: Exception that was raised
608
"""
609
610
async def on_user_command_error(inter: ApplicationCommandInteraction, error: Exception):
611
"""
612
Called when user command execution raises an error.
613
614
Parameters:
615
- inter: User command interaction
616
- error: Exception that was raised
617
"""
618
619
async def on_message_command_error(inter: ApplicationCommandInteraction, error: Exception):
620
"""
621
Called when message command execution raises an error.
622
623
Parameters:
624
- inter: Message command interaction
625
- error: Exception that was raised
626
"""
627
```
628
629
### Error Recovery and Logging
630
631
Utilities for error recovery, logging, and debugging assistance.
632
633
```python { .api }
634
class ErrorHandler:
635
"""Error handling and recovery utilities."""
636
637
def __init__(self, bot: Bot):
638
self.bot = bot
639
self.error_log = []
640
self.error_counts = defaultdict(int)
641
642
def log_error(self, error: Exception, context: str = None):
643
"""
644
Log error with context information.
645
646
Parameters:
647
- error: Exception to log
648
- context: Additional context information
649
"""
650
651
def get_error_embed(self, error: Exception, ctx: Context = None) -> Embed:
652
"""
653
Create error embed for user display.
654
655
Parameters:
656
- error: Exception to format
657
- ctx: Command context if available
658
659
Returns:
660
Formatted error embed
661
"""
662
663
async def handle_http_error(self, error: HTTPException, ctx: Context = None) -> bool:
664
"""
665
Handle HTTP errors with appropriate user feedback.
666
667
Parameters:
668
- error: HTTP exception
669
- ctx: Command context if available
670
671
Returns:
672
True if error was handled
673
"""
674
675
async def handle_permission_error(self, error: CheckFailure, ctx: Context) -> bool:
676
"""
677
Handle permission-related errors.
678
679
Parameters:
680
- error: Permission check failure
681
- ctx: Command context
682
683
Returns:
684
True if error was handled
685
"""
686
687
async def handle_cooldown_error(self, error: CommandOnCooldown, ctx: Context) -> bool:
688
"""
689
Handle cooldown errors with retry information.
690
691
Parameters:
692
- error: Cooldown exception
693
- ctx: Command context
694
695
Returns:
696
True if error was handled
697
"""
698
699
async def handle_argument_error(self, error: UserInputError, ctx: Context) -> bool:
700
"""
701
Handle argument parsing and conversion errors.
702
703
Parameters:
704
- error: User input error
705
- ctx: Command context
706
707
Returns:
708
True if error was handled
709
"""
710
711
def format_traceback(error: Exception) -> str:
712
"""
713
Format exception traceback for logging.
714
715
Parameters:
716
- error: Exception to format
717
718
Returns:
719
Formatted traceback string
720
"""
721
722
def get_error_cause(error: Exception) -> str:
723
"""
724
Get human-readable error cause.
725
726
Parameters:
727
- error: Exception to analyze
728
729
Returns:
730
Error cause description
731
"""
732
733
async def safe_send(channel: Messageable, content: str = None, **kwargs) -> Optional[Message]:
734
"""
735
Safely send message with error handling.
736
737
Parameters:
738
- channel: Channel to send to
739
- content: Message content
740
- kwargs: Additional send parameters
741
742
Returns:
743
Sent message if successful, None otherwise
744
"""
745
746
def retry_on_error(*exceptions: Type[Exception], max_retries: int = 3, delay: float = 1.0):
747
"""
748
Decorator for retrying operations on specific errors.
749
750
Parameters:
751
- exceptions: Exception types to retry on
752
- max_retries: Maximum number of retry attempts
753
- delay: Delay between retries in seconds
754
755
Returns:
756
Decorated function with retry logic
757
"""
758
759
async def with_timeout(coro: Awaitable, timeout: float, default: Any = None) -> Any:
760
"""
761
Execute coroutine with timeout protection.
762
763
Parameters:
764
- coro: Coroutine to execute
765
- timeout: Timeout in seconds
766
- default: Default value on timeout
767
768
Returns:
769
Coroutine result or default value
770
"""
771
```
772
773
## Usage Examples
774
775
### Basic Error Handling Setup
776
777
```python
778
import disnake
779
from disnake.ext import commands
780
import traceback
781
import logging
782
783
# Set up logging
784
logging.basicConfig(level=logging.INFO)
785
logger = logging.getLogger('discord_bot')
786
787
bot = commands.Bot(command_prefix='!', intents=disnake.Intents.all())
788
789
@bot.event
790
async def on_ready():
791
print(f'Bot ready: {bot.user}')
792
793
@bot.event
794
async def on_error(event, *args, **kwargs):
795
"""Handle errors in event handlers."""
796
logger.error(f'Error in event {event}:', exc_info=True)
797
798
# Log to error channel if available
799
error_channel = bot.get_channel(ERROR_CHANNEL_ID) # Replace with actual channel ID
800
if error_channel:
801
embed = disnake.Embed(
802
title=f"Error in {event}",
803
description=f"```py\n{traceback.format_exc()[:1900]}\n```",
804
color=0xff0000,
805
timestamp=disnake.utils.utcnow()
806
)
807
try:
808
await error_channel.send(embed=embed)
809
except:
810
pass # Avoid error loops
811
812
@bot.event
813
async def on_command_error(ctx, error):
814
"""Comprehensive command error handling."""
815
816
# Ignore these errors
817
if isinstance(error, (commands.CommandNotFound, commands.DisabledCommand)):
818
return
819
820
# Extract original error from CommandInvokeError
821
if isinstance(error, commands.CommandInvokeError):
822
error = error.original
823
824
# User input errors
825
elif isinstance(error, commands.MissingRequiredArgument):
826
embed = disnake.Embed(
827
title="Missing Argument",
828
description=f"You're missing the `{error.param.name}` parameter.",
829
color=0xff9900
830
)
831
embed.add_field(name="Usage", value=f"`{ctx.prefix}{ctx.command.qualified_name} {ctx.command.signature}`")
832
await ctx.send(embed=embed)
833
return
834
835
elif isinstance(error, commands.BadArgument):
836
embed = disnake.Embed(
837
title="Invalid Argument",
838
description=f"Invalid argument provided: {error}",
839
color=0xff9900
840
)
841
embed.add_field(name="Usage", value=f"`{ctx.prefix}{ctx.command.qualified_name} {ctx.command.signature}`")
842
await ctx.send(embed=embed)
843
return
844
845
elif isinstance(error, commands.TooManyArguments):
846
embed = disnake.Embed(
847
title="Too Many Arguments",
848
description="You provided too many arguments for this command.",
849
color=0xff9900
850
)
851
embed.add_field(name="Usage", value=f"`{ctx.prefix}{ctx.command.qualified_name} {ctx.command.signature}`")
852
await ctx.send(embed=embed)
853
return
854
855
# Permission errors
856
elif isinstance(error, commands.MissingPermissions):
857
missing = ', '.join(error.missing_permissions)
858
embed = disnake.Embed(
859
title="Missing Permissions",
860
description=f"You need the following permissions: **{missing}**",
861
color=0xff0000
862
)
863
await ctx.send(embed=embed)
864
return
865
866
elif isinstance(error, commands.BotMissingPermissions):
867
missing = ', '.join(error.missing_permissions)
868
embed = disnake.Embed(
869
title="Bot Missing Permissions",
870
description=f"I need the following permissions: **{missing}**",
871
color=0xff0000
872
)
873
await ctx.send(embed=embed)
874
return
875
876
elif isinstance(error, commands.MissingRole):
877
embed = disnake.Embed(
878
title="Missing Role",
879
description=f"You need the **{error.missing_role}** role to use this command.",
880
color=0xff0000
881
)
882
await ctx.send(embed=embed)
883
return
884
885
elif isinstance(error, commands.MissingAnyRole):
886
roles = ', '.join(str(role) for role in error.missing_roles)
887
embed = disnake.Embed(
888
title="Missing Role",
889
description=f"You need one of these roles: **{roles}**",
890
color=0xff0000
891
)
892
await ctx.send(embed=embed)
893
return
894
895
elif isinstance(error, commands.NotOwner):
896
embed = disnake.Embed(
897
title="Owner Only",
898
description="Only the bot owner can use this command.",
899
color=0xff0000
900
)
901
await ctx.send(embed=embed)
902
return
903
904
elif isinstance(error, commands.NoPrivateMessage):
905
embed = disnake.Embed(
906
title="Guild Only",
907
description="This command cannot be used in private messages.",
908
color=0xff0000
909
)
910
await ctx.send(embed=embed)
911
return
912
913
elif isinstance(error, commands.PrivateMessageOnly):
914
embed = disnake.Embed(
915
title="DM Only",
916
description="This command can only be used in private messages.",
917
color=0xff0000
918
)
919
await ctx.send(embed=embed)
920
return
921
922
elif isinstance(error, commands.NSFWChannelRequired):
923
embed = disnake.Embed(
924
title="NSFW Channel Required",
925
description="This command can only be used in NSFW channels.",
926
color=0xff0000
927
)
928
await ctx.send(embed=embed)
929
return
930
931
# Cooldown errors
932
elif isinstance(error, commands.CommandOnCooldown):
933
embed = disnake.Embed(
934
title="Command on Cooldown",
935
description=f"Try again in **{error.retry_after:.2f}** seconds.",
936
color=0xff9900
937
)
938
await ctx.send(embed=embed, delete_after=error.retry_after)
939
return
940
941
elif isinstance(error, commands.MaxConcurrencyReached):
942
embed = disnake.Embed(
943
title="Max Concurrency Reached",
944
description="This command is already being used by too many people. Try again later.",
945
color=0xff9900
946
)
947
await ctx.send(embed=embed)
948
return
949
950
# HTTP errors
951
elif isinstance(error, disnake.Forbidden):
952
embed = disnake.Embed(
953
title="Forbidden",
954
description="I don't have permission to perform this action.",
955
color=0xff0000
956
)
957
await ctx.send(embed=embed)
958
return
959
960
elif isinstance(error, disnake.NotFound):
961
embed = disnake.Embed(
962
title="Not Found",
963
description="The requested resource could not be found.",
964
color=0xff0000
965
)
966
await ctx.send(embed=embed)
967
return
968
969
elif isinstance(error, disnake.HTTPException):
970
embed = disnake.Embed(
971
title="HTTP Error",
972
description=f"An HTTP error occurred: {error.text}",
973
color=0xff0000
974
)
975
await ctx.send(embed=embed)
976
return
977
978
# Unknown error
979
else:
980
embed = disnake.Embed(
981
title="Unexpected Error",
982
description="An unexpected error occurred. The developers have been notified.",
983
color=0xff0000
984
)
985
await ctx.send(embed=embed)
986
987
# Log the error
988
logger.error(f'Unexpected error in command {ctx.command}:', exc_info=error)
989
990
# Send to error channel
991
error_channel = bot.get_channel(ERROR_CHANNEL_ID)
992
if error_channel:
993
error_embed = disnake.Embed(
994
title=f"Command Error: {ctx.command.qualified_name}",
995
color=0xff0000,
996
timestamp=disnake.utils.utcnow()
997
)
998
error_embed.add_field(name="User", value=f"{ctx.author} ({ctx.author.id})", inline=True)
999
error_embed.add_field(name="Guild", value=f"{ctx.guild} ({ctx.guild.id})" if ctx.guild else "DM", inline=True)
1000
error_embed.add_field(name="Channel", value=f"#{ctx.channel} ({ctx.channel.id})", inline=True)
1001
error_embed.add_field(name="Command", value=f"`{ctx.message.content}`", inline=False)
1002
error_embed.add_field(
1003
name="Error",
1004
value=f"```py\n{traceback.format_exception(type(error), error, error.__traceback__)[-1][:1000]}\n```",
1005
inline=False
1006
)
1007
1008
try:
1009
await error_channel.send(embed=error_embed)
1010
except:
1011
pass
1012
1013
bot.run('YOUR_BOT_TOKEN')
1014
```
1015
1016
### Application Command Error Handling
1017
1018
```python
1019
@bot.event
1020
async def on_application_command_error(inter, error):
1021
"""Handle application command errors."""
1022
1023
# Extract original error
1024
if isinstance(error, disnake.ApplicationCommandInvokeError):
1025
error = error.original
1026
1027
# Create base embed
1028
embed = disnake.Embed(color=0xff0000, timestamp=disnake.utils.utcnow())
1029
1030
# Handle specific errors
1031
if isinstance(error, commands.MissingPermissions):
1032
missing = ', '.join(error.missing_permissions)
1033
embed.title = "Missing Permissions"
1034
embed.description = f"You need: **{missing}**"
1035
1036
elif isinstance(error, commands.BotMissingPermissions):
1037
missing = ', '.join(error.missing_permissions)
1038
embed.title = "Bot Missing Permissions"
1039
embed.description = f"I need: **{missing}**"
1040
1041
elif isinstance(error, commands.MissingRole):
1042
embed.title = "Missing Role"
1043
embed.description = f"You need the **{error.missing_role}** role."
1044
1045
elif isinstance(error, commands.MissingAnyRole):
1046
roles = ', '.join(str(role) for role in error.missing_roles)
1047
embed.title = "Missing Role"
1048
embed.description = f"You need one of: **{roles}**"
1049
1050
elif isinstance(error, commands.NotOwner):
1051
embed.title = "Owner Only"
1052
embed.description = "Only the bot owner can use this command."
1053
1054
elif isinstance(error, commands.NoPrivateMessage):
1055
embed.title = "Guild Only"
1056
embed.description = "This command cannot be used in DMs."
1057
1058
elif isinstance(error, commands.CommandOnCooldown):
1059
embed.title = "Command on Cooldown"
1060
embed.description = f"Try again in **{error.retry_after:.2f}** seconds."
1061
embed.color = 0xff9900
1062
1063
elif isinstance(error, disnake.Forbidden):
1064
embed.title = "Forbidden"
1065
embed.description = "I don't have permission to perform this action."
1066
1067
elif isinstance(error, disnake.NotFound):
1068
embed.title = "Not Found"
1069
embed.description = "The requested resource was not found."
1070
1071
elif isinstance(error, ValueError):
1072
embed.title = "Invalid Input"
1073
embed.description = "Please check your input and try again."
1074
embed.color = 0xff9900
1075
1076
else:
1077
embed.title = "Unexpected Error"
1078
embed.description = "An unexpected error occurred."
1079
1080
# Log unexpected errors
1081
logger.error(f'Unexpected slash command error:', exc_info=error)
1082
1083
# Send error response
1084
try:
1085
if inter.response.is_done():
1086
await inter.followup.send(embed=embed, ephemeral=True)
1087
else:
1088
await inter.response.send_message(embed=embed, ephemeral=True)
1089
except:
1090
# If we can't send the error message, log it
1091
logger.error(f'Failed to send error response for interaction {inter.id}')
1092
1093
# Command-specific error handlers
1094
@bot.slash_command()
1095
async def divide(inter, a: float, b: float):
1096
"""Divide two numbers."""
1097
try:
1098
result = a / b
1099
await inter.response.send_message(f"{a} ÷ {b} = {result}")
1100
except ZeroDivisionError:
1101
embed = disnake.Embed(
1102
title="Math Error",
1103
description="Cannot divide by zero!",
1104
color=0xff0000
1105
)
1106
await inter.response.send_message(embed=embed, ephemeral=True)
1107
1108
@bot.slash_command()
1109
async def risky_operation(inter):
1110
"""Command that might fail."""
1111
try:
1112
# Potentially risky operation
1113
result = await some_api_call()
1114
await inter.response.send_message(f"Result: {result}")
1115
1116
except aiohttp.ClientError as e:
1117
embed = disnake.Embed(
1118
title="Network Error",
1119
description="Failed to connect to external service. Try again later.",
1120
color=0xff9900
1121
)
1122
await inter.response.send_message(embed=embed, ephemeral=True)
1123
logger.warning(f'API call failed: {e}')
1124
1125
except asyncio.TimeoutError:
1126
embed = disnake.Embed(
1127
title="Timeout",
1128
description="The operation timed out. Try again later.",
1129
color=0xff9900
1130
)
1131
await inter.response.send_message(embed=embed, ephemeral=True)
1132
1133
except Exception as e:
1134
embed = disnake.Embed(
1135
title="Error",
1136
description="An unexpected error occurred.",
1137
color=0xff0000
1138
)
1139
await inter.response.send_message(embed=embed, ephemeral=True)
1140
logger.error(f'Unexpected error in risky_operation: {e}', exc_info=True)
1141
```
1142
1143
### Advanced Error Recovery System
1144
1145
```python
1146
import asyncio
1147
import functools
1148
from typing import Optional, Callable, Any
1149
from datetime import datetime, timedelta
1150
1151
class ErrorHandler:
1152
"""Advanced error handling and recovery system."""
1153
1154
def __init__(self, bot):
1155
self.bot = bot
1156
self.error_counts = {}
1157
self.last_errors = {}
1158
self.recovery_strategies = {}
1159
1160
def register_recovery_strategy(self, error_type: type, strategy: Callable):
1161
"""Register a recovery strategy for specific error types."""
1162
self.recovery_strategies[error_type] = strategy
1163
1164
async def handle_error(self, error: Exception, context: dict = None) -> bool:
1165
"""
1166
Handle error with recovery strategies.
1167
1168
Returns True if error was handled and recovered from.
1169
"""
1170
error_type = type(error)
1171
error_key = f"{error_type.__name__}:{str(error)}"
1172
1173
# Track error frequency
1174
now = datetime.utcnow()
1175
if error_key not in self.error_counts:
1176
self.error_counts[error_key] = []
1177
1178
self.error_counts[error_key].append(now)
1179
1180
# Clean old entries (last hour)
1181
cutoff = now - timedelta(hours=1)
1182
self.error_counts[error_key] = [
1183
t for t in self.error_counts[error_key] if t > cutoff
1184
]
1185
1186
# Check if error is recurring
1187
recent_count = len(self.error_counts[error_key])
1188
if recent_count > 5:
1189
logger.warning(f'Recurring error detected: {error_key} ({recent_count} times)')
1190
1191
# Try recovery strategy
1192
if error_type in self.recovery_strategies:
1193
try:
1194
return await self.recovery_strategies[error_type](error, context)
1195
except Exception as recovery_error:
1196
logger.error(f'Recovery strategy failed: {recovery_error}')
1197
1198
return False
1199
1200
# Initialize error handler
1201
error_handler = ErrorHandler(bot)
1202
1203
# Recovery strategies
1204
async def recover_from_forbidden(error: disnake.Forbidden, context: dict) -> bool:
1205
"""Recovery strategy for Forbidden errors."""
1206
if context and 'channel' in context:
1207
channel = context['channel']
1208
1209
# Try to send error message to a different channel
1210
if hasattr(channel, 'guild') and channel.guild:
1211
# Find a channel we can send to
1212
for text_channel in channel.guild.text_channels:
1213
try:
1214
perms = text_channel.permissions_for(channel.guild.me)
1215
if perms.send_messages:
1216
await text_channel.send(
1217
f"⚠️ I don't have permission to perform an action in {channel.mention}. "
1218
f"Please check my permissions."
1219
)
1220
return True
1221
except:
1222
continue
1223
1224
return False
1225
1226
async def recover_from_not_found(error: disnake.NotFound, context: dict) -> bool:
1227
"""Recovery strategy for NotFound errors."""
1228
if context and 'interaction' in context:
1229
inter = context['interaction']
1230
1231
# If interaction is not found, it might have expired
1232
try:
1233
if not inter.response.is_done():
1234
await inter.response.send_message(
1235
"This interaction has expired. Please try the command again.",
1236
ephemeral=True
1237
)
1238
return True
1239
except:
1240
pass
1241
1242
return False
1243
1244
async def recover_from_rate_limit(error: disnake.HTTPException, context: dict) -> bool:
1245
"""Recovery strategy for rate limits."""
1246
if error.status == 429: # Rate limited
1247
retry_after = getattr(error, 'retry_after', 1)
1248
logger.info(f'Rate limited, waiting {retry_after} seconds')
1249
1250
await asyncio.sleep(retry_after)
1251
1252
# Try to notify about the delay
1253
if context and 'ctx' in context:
1254
ctx = context['ctx']
1255
try:
1256
await ctx.send(
1257
f"⏳ Rate limited. Retrying in {retry_after} seconds...",
1258
delete_after=retry_after + 5
1259
)
1260
except:
1261
pass
1262
1263
return True
1264
1265
return False
1266
1267
# Register recovery strategies
1268
error_handler.register_recovery_strategy(disnake.Forbidden, recover_from_forbidden)
1269
error_handler.register_recovery_strategy(disnake.NotFound, recover_from_not_found)
1270
error_handler.register_recovery_strategy(disnake.HTTPException, recover_from_rate_limit)
1271
1272
def with_error_recovery(func):
1273
"""Decorator to add error recovery to functions."""
1274
@functools.wraps(func)
1275
async def wrapper(*args, **kwargs):
1276
try:
1277
return await func(*args, **kwargs)
1278
except Exception as e:
1279
context = {
1280
'function': func.__name__,
1281
'args': args,
1282
'kwargs': kwargs
1283
}
1284
1285
# Try to extract useful context
1286
for arg in args:
1287
if isinstance(arg, (commands.Context, disnake.ApplicationCommandInteraction)):
1288
context['ctx'] = arg
1289
if hasattr(arg, 'channel'):
1290
context['channel'] = arg.channel
1291
if hasattr(arg, 'guild'):
1292
context['guild'] = arg.guild
1293
break
1294
1295
# Attempt recovery
1296
recovered = await error_handler.handle_error(e, context)
1297
1298
if not recovered:
1299
# Re-raise if recovery failed
1300
raise
1301
1302
return wrapper
1303
1304
# Retry decorator
1305
def retry_on_error(*error_types, max_retries=3, delay=1.0, backoff=2.0):
1306
"""Decorator to retry function calls on specific errors."""
1307
def decorator(func):
1308
@functools.wraps(func)
1309
async def wrapper(*args, **kwargs):
1310
last_exception = None
1311
1312
for attempt in range(max_retries + 1):
1313
try:
1314
return await func(*args, **kwargs)
1315
except error_types as e:
1316
last_exception = e
1317
1318
if attempt == max_retries:
1319
break
1320
1321
wait_time = delay * (backoff ** attempt)
1322
logger.info(f'Retrying {func.__name__} in {wait_time}s (attempt {attempt + 1}/{max_retries})')
1323
await asyncio.sleep(wait_time)
1324
1325
# All retries failed
1326
raise last_exception
1327
1328
return wrapper
1329
return decorator
1330
1331
# Usage examples
1332
@bot.command()
1333
@with_error_recovery
1334
@retry_on_error(disnake.HTTPException, max_retries=3, delay=2.0)
1335
async def reliable_command(ctx):
1336
"""A command with error recovery and retry logic."""
1337
# This might fail with HTTPException
1338
await ctx.send("This command has error recovery!")
1339
1340
@bot.slash_command()
1341
@with_error_recovery
1342
async def fetch_data(inter, url: str):
1343
"""Fetch data from URL with error handling."""
1344
try:
1345
async with aiohttp.ClientSession() as session:
1346
async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
1347
if resp.status == 200:
1348
data = await resp.text()
1349
await inter.response.send_message(f"Data length: {len(data)} characters")
1350
else:
1351
await inter.response.send_message(f"HTTP {resp.status}: {resp.reason}")
1352
1353
except asyncio.TimeoutError:
1354
await inter.response.send_message("⏰ Request timed out", ephemeral=True)
1355
1356
except aiohttp.ClientError as e:
1357
await inter.response.send_message(f"🌐 Network error: {e}", ephemeral=True)
1358
1359
# Safe execution context manager
1360
class SafeExecution:
1361
"""Context manager for safe code execution with error handling."""
1362
1363
def __init__(self, ctx_or_inter, *,
1364
error_message="An error occurred",
1365
log_errors=True,
1366
reraise=False):
1367
self.ctx_or_inter = ctx_or_inter
1368
self.error_message = error_message
1369
self.log_errors = log_errors
1370
self.reraise = reraise
1371
1372
async def __aenter__(self):
1373
return self
1374
1375
async def __aexit__(self, exc_type, exc_val, exc_tb):
1376
if exc_type is not None:
1377
if self.log_errors:
1378
logger.error(f'Error in safe execution: {exc_val}', exc_info=exc_val)
1379
1380
# Send error message
1381
try:
1382
if isinstance(self.ctx_or_inter, commands.Context):
1383
await self.ctx_or_inter.send(self.error_message)
1384
else:
1385
if self.ctx_or_inter.response.is_done():
1386
await self.ctx_or_inter.followup.send(self.error_message, ephemeral=True)
1387
else:
1388
await self.ctx_or_inter.response.send_message(self.error_message, ephemeral=True)
1389
except:
1390
pass # Avoid error loops
1391
1392
# Suppress exception unless reraise is True
1393
return not self.reraise
1394
1395
# Usage of SafeExecution
1396
@bot.command()
1397
async def safe_command(ctx):
1398
"""Command using safe execution context manager."""
1399
async with SafeExecution(ctx, error_message="Failed to process your request"):
1400
# Potentially risky operation
1401
result = await some_risky_operation()
1402
await ctx.send(f"Result: {result}")
1403
1404
# Error monitoring and alerts
1405
class ErrorMonitor:
1406
"""Monitor errors and send alerts when thresholds are exceeded."""
1407
1408
def __init__(self, bot, alert_channel_id: int, threshold: int = 10):
1409
self.bot = bot
1410
self.alert_channel_id = alert_channel_id
1411
self.threshold = threshold
1412
self.error_window = timedelta(minutes=15)
1413
self.recent_errors = []
1414
1415
def record_error(self, error: Exception, context: str = None):
1416
"""Record an error occurrence."""
1417
now = datetime.utcnow()
1418
self.recent_errors.append({
1419
'timestamp': now,
1420
'error': str(error),
1421
'type': type(error).__name__,
1422
'context': context
1423
})
1424
1425
# Clean old errors
1426
cutoff = now - self.error_window
1427
self.recent_errors = [e for e in self.recent_errors if e['timestamp'] > cutoff]
1428
1429
# Check if we should send an alert
1430
if len(self.recent_errors) >= self.threshold:
1431
asyncio.create_task(self._send_alert())
1432
1433
async def _send_alert(self):
1434
"""Send error alert to monitoring channel."""
1435
channel = self.bot.get_channel(self.alert_channel_id)
1436
if not channel:
1437
return
1438
1439
error_types = {}
1440
for error in self.recent_errors:
1441
error_type = error['type']
1442
error_types[error_type] = error_types.get(error_type, 0) + 1
1443
1444
embed = disnake.Embed(
1445
title="🚨 Error Alert",
1446
description=f"**{len(self.recent_errors)}** errors in the last 15 minutes",
1447
color=0xff0000,
1448
timestamp=disnake.utils.utcnow()
1449
)
1450
1451
error_summary = '\n'.join([f"**{error_type}**: {count}" for error_type, count in error_types.items()])
1452
embed.add_field(name="Error Types", value=error_summary, inline=False)
1453
1454
try:
1455
await channel.send(embed=embed)
1456
except:
1457
pass
1458
1459
# Clear recent errors after alert
1460
self.recent_errors.clear()
1461
1462
# Initialize error monitor
1463
error_monitor = ErrorMonitor(bot, ERROR_ALERT_CHANNEL_ID)
1464
1465
# Update error handlers to use monitor
1466
@bot.event
1467
async def on_command_error(ctx, error):
1468
"""Enhanced command error handler with monitoring."""
1469
error_monitor.record_error(error, f'Command: {ctx.command}')
1470
1471
# Original error handling code here...
1472
# [Previous error handling implementation]
1473
1474
@bot.event
1475
async def on_application_command_error(inter, error):
1476
"""Enhanced application command error handler with monitoring."""
1477
error_monitor.record_error(error, f'Slash Command: {inter.data.name}')
1478
1479
# Original error handling code here...
1480
# [Previous error handling implementation]
1481
```