Browse Source

[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.
pull/239/head
Khazhismel Kumykov 9 years ago
committed by Rapptz
parent
commit
33a69681fc
  1. 32
      discord/ext/commands/bot.py
  2. 113
      discord/ext/commands/core.py

32
discord/ext/commands/bot.py

@ -29,11 +29,12 @@ import discord
import inspect import inspect
import importlib import importlib
import sys import sys
import traceback
from .core import GroupMixin, Command, command from .core import GroupMixin, Command, command
from .view import StringView from .view import StringView
from .context import Context from .context import Context
from .errors import CommandNotFound from .errors import CommandNotFound, CommandError
from .formatter import HelpFormatter from .formatter import HelpFormatter
def _get_variable(name): def _get_variable(name):
@ -247,6 +248,26 @@ class Bot(GroupMixin, discord.Client):
coro = self._run_extra(event, event_name, *args, **kwargs) coro = self._run_extra(event, event_name, *args, **kwargs)
discord.compat.create_task(coro, loop=self.loop) 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 # utility "send_*" functions
def say(self, *args, **kwargs): def say(self, *args, **kwargs):
@ -618,8 +639,13 @@ class Bot(GroupMixin, discord.Client):
command = self.commands[invoker] command = self.commands[invoker]
self.dispatch('command', command, ctx) self.dispatch('command', command, ctx)
ctx.command = command ctx.command = command
yield from command.invoke(ctx) try:
self.dispatch('command_completion', command, ctx) 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: else:
exc = CommandNotFound('Command "{}" is not found'.format(invoker)) exc = CommandNotFound('Command "{}" is not found'.format(invoker))
self.dispatch('command_error', exc, ctx) self.dispatch('command_error', exc, ctx)

113
discord/ext/commands/core.py

@ -44,7 +44,10 @@ def inject_context(ctx, coro):
_internal_channel = ctx.message.channel _internal_channel = ctx.message.channel
_internal_author = ctx.message.author _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 ret
return wrapped return wrapped
@ -306,72 +309,60 @@ class Command:
@asyncio.coroutine @asyncio.coroutine
def _parse_arguments(self, ctx): def _parse_arguments(self, ctx):
try: ctx.args = [] if self.instance is None else [self.instance]
ctx.args = [] if self.instance is None else [self.instance] ctx.kwargs = {}
ctx.kwargs = {} args = ctx.args
args = ctx.args kwargs = ctx.kwargs
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
except CommandError as e: first = True
self.handle_local_error(e, ctx) view = ctx.view
ctx.bot.dispatch('command_error', e, ctx) iterator = iter(self.params.items())
return False
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 return True
def _verify_checks(self, ctx): def _verify_checks(self, ctx):
try: if not self.enabled:
if not self.enabled: raise DisabledCommand('{0.name} command is disabled'.format(self))
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.can_run(ctx): if self.no_pm and ctx.message.channel.is_private:
raise CheckFailure('The check functions for command {0.name} failed.'.format(self)) raise NoPrivateMessage('This command cannot be used in private messages.')
except CommandError as exc:
self.handle_local_error(exc, ctx)
ctx.bot.dispatch('command_error', exc, ctx)
return False
if not self.can_run(ctx):
raise CheckFailure('The check functions for command {0.name} failed.'.format(self))
return True return True
@asyncio.coroutine @asyncio.coroutine

Loading…
Cancel
Save