[commands] Split process_commands into lower level bits.

This commit is contained in:
Rapptz 2017-01-14 19:15:32 -05:00
parent 87dc2f4dd3
commit ce9d5b4f4a
2 changed files with 108 additions and 59 deletions

View File

@ -164,17 +164,6 @@ class BotBase(GroupMixin):
# internal helpers
@asyncio.coroutine
def _get_prefix(self, message):
prefix = self.command_prefix
if callable(prefix):
ret = prefix(self, message)
if asyncio.iscoroutine(ret):
ret = yield from ret
return ret
else:
return prefix
@asyncio.coroutine
def _run_extra(self, coro, event_name, *args, **kwargs):
try:
@ -551,6 +540,105 @@ class BotBase(GroupMixin):
# command processing
@asyncio.coroutine
def get_prefix(self, message):
"""|coro|
Retrieves the prefix the bot is listening to
with the message as a context.
Parameters
-----------
message: :class:`discord.Message`
The message context to get the prefix of.
Returns
--------
Union[List[str], str]
A list of prefixes or a single prefix that the bot is
listening for.
"""
prefix = self.command_prefix
if callable(prefix):
ret = prefix(self, message)
if asyncio.iscoroutine(ret):
ret = yield from ret
return ret
else:
return prefix
@asyncio.coroutine
def get_context(self, message):
"""|coro|
Returns the invocation context from the message.
This is a more low-level counter-part for :meth:`process_message`
to allow users more fine grained control over the processing.
The returned context is not guaranteed to be a valid invocation
context, :attr:`Context.valid` must be checked to make sure it is.
If the context is not valid then it is not a valid candidate to be
invoked under :meth:`invoke`.
Parameters
-----------
message: :class:`discord.Message`
The message to get the invocation context from.
Returns
--------
:class:`Context`
The invocation context.
"""
view = StringView(message.content)
ctx = Context(prefix=None, view=view, bot=self, message=message)
if self._skip_check(message.author.id, self.user.id):
return ctx
prefix = yield from self.get_prefix(message)
invoked_prefix = prefix
if not isinstance(prefix, (tuple, list)):
if not view.skip_string(prefix):
return ctx
else:
invoked_prefix = discord.utils.find(view.skip_string, prefix)
if invoked_prefix is None:
return ctx
invoker = view.get_word()
ctx.invoked_with = invoker
ctx.prefix = invoked_prefix
ctx.command = self.commands.get(invoker)
return ctx
@asyncio.coroutine
def invoke(self, ctx):
"""|coro|
Invokes the command given under the invocation context and
handles all the internal event dispatch mechanisms.
Parameters
-----------
ctx: :class:`Context`
The invocation context to invoke.
"""
if ctx.command is not None:
self.dispatch('command', ctx)
try:
yield from ctx.command.invoke(ctx)
except CommandError as e:
ctx.command.dispatch_error(e, ctx)
else:
self.dispatch('command_completion', ctx)
elif ctx.invoked_with:
exc = CommandNotFound('Command "{}" is not found'.format(ctx.invoked_with))
self.dispatch('command_error', exc, ctx)
@asyncio.coroutine
def process_commands(self, message):
"""|coro|
@ -563,60 +651,16 @@ class BotBase(GroupMixin):
event. If you choose to override the :func:`on_message` event, then
you should invoke this coroutine as well.
Warning
--------
This function is necessary for :meth:`say`, :meth:`whisper`,
:meth:`type`, :meth:`reply`, and :meth:`upload` to work due to the
way they are written. It is also required for the :func:`on_command`
and :func:`on_command_completion` events.
This is built using other low level tools, and is equivalent to a
call to :meth:`get_context` followed by a call to :meth:`invoke`.
Parameters
-----------
message : discord.Message
The message to process commands for.
"""
_internal_channel = message.channel
_internal_author = message.author
view = StringView(message.content)
if self._skip_check(message.author.id, self.user.id):
return
prefix = yield from self._get_prefix(message)
invoked_prefix = prefix
if not isinstance(prefix, (tuple, list)):
if not view.skip_string(prefix):
return
else:
invoked_prefix = discord.utils.find(view.skip_string, prefix)
if invoked_prefix is None:
return
invoker = view.get_word()
tmp = {
'bot': self,
'invoked_with': invoker,
'message': message,
'view': view,
'prefix': invoked_prefix
}
ctx = Context(**tmp)
del tmp
if invoker in self.commands:
command = self.commands[invoker]
self.dispatch('command', command, ctx)
try:
yield from command.invoke(ctx)
except CommandError as e:
ctx.command.dispatch_error(e, ctx)
else:
self.dispatch('command_completion', command, ctx)
elif invoker:
exc = CommandNotFound('Command "{}" is not found'.format(invoker))
self.dispatch('command_error', exc, ctx)
ctx = yield from self.get_context(message)
yield from self.invoke(ctx)
@asyncio.coroutine
def on_message(self, message):

View File

@ -117,6 +117,11 @@ class Context(discord.abc.Messageable):
ret = yield from command.callback(*arguments, **kwargs)
return ret
@property
def valid(self):
"""Checks if the invocation context is valid to be invoked with."""
return self.prefix is not None and self.command is not None
@asyncio.coroutine
def _get_channel(self):
return self.channel