aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKhazhismel Kumykov <[email protected]>2016-06-04 21:21:54 -0400
committerRapptz <[email protected]>2016-06-04 21:22:10 -0400
commit33a69681fccc2c3b6b6eb60a66cdcf3a183f7a18 (patch)
tree2781b79f7280805c5e5a98d6776ce18d3deb54c9
parentAdd `bot` keyword argument to login via static token. (diff)
downloaddiscord.py-33a69681fccc2c3b6b6eb60a66cdcf3a183f7a18.tar.xz
discord.py-33a69681fccc2c3b6b6eb60a66cdcf3a183f7a18.zip
[commands] Dispatch command_error on command exec error.
Provide fallback on_command_error - will only fire if no cog handlers and no local handler. Propagate exceptions in checks and argument parsing to bot.
-rw-r--r--discord/ext/commands/bot.py32
-rw-r--r--discord/ext/commands/core.py113
2 files changed, 81 insertions, 64 deletions
diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py
index 768e7cc1..59a498d7 100644
--- a/discord/ext/commands/bot.py
+++ b/discord/ext/commands/bot.py
@@ -29,11 +29,12 @@ import discord
import inspect
import importlib
import sys
+import traceback
from .core import GroupMixin, Command, command
from .view import StringView
from .context import Context
-from .errors import CommandNotFound
+from .errors import CommandNotFound, CommandError
from .formatter import HelpFormatter
def _get_variable(name):
@@ -247,6 +248,26 @@ class Bot(GroupMixin, discord.Client):
coro = self._run_extra(event, event_name, *args, **kwargs)
discord.compat.create_task(coro, loop=self.loop)
+ @asyncio.coroutine
+ def on_command_error(self, exception, context):
+ """|coro|
+
+ The default command error handler provided by the bot.
+
+ By default this prints to ``sys.stderr`` however it could be
+ overridden to have a different implementation.
+
+ This only fires if you do not specify any listeners for command error.
+ """
+ if self.extra_events.get('on_command_error', None):
+ return
+
+ if hasattr(context.command, "on_error"):
+ return
+
+ print('Ignoring exception in command {}'.format(context.command), file=sys.stderr)
+ traceback.print_exception(type(exception), exception, exception.__traceback__, file=sys.stderr)
+
# utility "send_*" functions
def say(self, *args, **kwargs):
@@ -618,8 +639,13 @@ class Bot(GroupMixin, discord.Client):
command = self.commands[invoker]
self.dispatch('command', command, ctx)
ctx.command = command
- yield from command.invoke(ctx)
- self.dispatch('command_completion', command, ctx)
+ try:
+ yield from command.invoke(ctx)
+ except CommandError as e:
+ command.handle_local_error(e, ctx)
+ self.dispatch('command_error', e, ctx)
+ else:
+ self.dispatch('command_completion', command, ctx)
else:
exc = CommandNotFound('Command "{}" is not found'.format(invoker))
self.dispatch('command_error', exc, ctx)
diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py
index bc095375..65967bdf 100644
--- a/discord/ext/commands/core.py
+++ b/discord/ext/commands/core.py
@@ -44,7 +44,10 @@ def inject_context(ctx, coro):
_internal_channel = ctx.message.channel
_internal_author = ctx.message.author
- ret = yield from coro(*args, **kwargs)
+ try:
+ ret = yield from coro(*args, **kwargs)
+ except Exception as e:
+ raise CommandError("Exception raised while executing command") from e
return ret
return wrapped
@@ -306,72 +309,60 @@ class Command:
@asyncio.coroutine
def _parse_arguments(self, ctx):
- try:
- ctx.args = [] if self.instance is None else [self.instance]
- ctx.kwargs = {}
- args = ctx.args
- kwargs = ctx.kwargs
-
- first = True
- view = ctx.view
- iterator = iter(self.params.items())
-
- if self.instance is not None:
- # we have 'self' as the first parameter so just advance
- # the iterator and resume parsing
- try:
- next(iterator)
- except StopIteration:
- fmt = 'Callback for {0.name} command is missing "self" parameter.'
- raise discord.ClientException(fmt.format(self))
-
- for name, param in iterator:
- if first and self.pass_context:
- args.append(ctx)
- first = False
- continue
-
- if param.kind == param.POSITIONAL_OR_KEYWORD:
- transformed = yield from self.transform(ctx, param)
- args.append(transformed)
- elif param.kind == param.KEYWORD_ONLY:
- # kwarg only param denotes "consume rest" semantics
- if self.rest_is_raw:
- converter = self._get_converter(param)
- argument = view.read_rest()
- kwargs[name] = yield from self.do_conversion(ctx.bot, ctx.message, converter, argument)
- else:
- kwargs[name] = yield from self.transform(ctx, param)
- break
- elif param.kind == param.VAR_POSITIONAL:
- while not view.eof:
- try:
- transformed = yield from self.transform(ctx, param)
- args.append(transformed)
- except RuntimeError:
- break
+ ctx.args = [] if self.instance is None else [self.instance]
+ ctx.kwargs = {}
+ args = ctx.args
+ kwargs = ctx.kwargs
- except CommandError as e:
- self.handle_local_error(e, ctx)
- ctx.bot.dispatch('command_error', e, ctx)
- return False
+ first = True
+ view = ctx.view
+ iterator = iter(self.params.items())
+
+ if self.instance is not None:
+ # we have 'self' as the first parameter so just advance
+ # the iterator and resume parsing
+ try:
+ next(iterator)
+ except StopIteration:
+ fmt = 'Callback for {0.name} command is missing "self" parameter.'
+ raise discord.ClientException(fmt.format(self))
+
+ for name, param in iterator:
+ if first and self.pass_context:
+ args.append(ctx)
+ first = False
+ continue
+
+ if param.kind == param.POSITIONAL_OR_KEYWORD:
+ transformed = yield from self.transform(ctx, param)
+ args.append(transformed)
+ elif param.kind == param.KEYWORD_ONLY:
+ # kwarg only param denotes "consume rest" semantics
+ if self.rest_is_raw:
+ converter = self._get_converter(param)
+ argument = view.read_rest()
+ kwargs[name] = yield from self.do_conversion(ctx.bot, ctx.message, converter, argument)
+ else:
+ kwargs[name] = yield from self.transform(ctx, param)
+ break
+ elif param.kind == param.VAR_POSITIONAL:
+ while not view.eof:
+ try:
+ transformed = yield from self.transform(ctx, param)
+ args.append(transformed)
+ except RuntimeError:
+ break
return True
def _verify_checks(self, ctx):
- try:
- if not self.enabled:
- raise DisabledCommand('{0.name} command is disabled'.format(self))
-
- if self.no_pm and ctx.message.channel.is_private:
- raise NoPrivateMessage('This command cannot be used in private messages.')
+ if not self.enabled:
+ raise DisabledCommand('{0.name} command is disabled'.format(self))
- if not self.can_run(ctx):
- raise CheckFailure('The check functions for command {0.name} failed.'.format(self))
- except CommandError as exc:
- self.handle_local_error(exc, ctx)
- ctx.bot.dispatch('command_error', exc, ctx)
- return False
+ if self.no_pm and ctx.message.channel.is_private:
+ raise NoPrivateMessage('This command cannot be used in private messages.')
+ if not self.can_run(ctx):
+ raise CheckFailure('The check functions for command {0.name} failed.'.format(self))
return True
@asyncio.coroutine