aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--discord/ext/commands/bot.py142
-rw-r--r--discord/ext/commands/context.py5
2 files changed, 98 insertions, 49 deletions
diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py
index fb0e8cba..8c17c63b 100644
--- a/discord/ext/commands/bot.py
+++ b/discord/ext/commands/bot.py
@@ -165,17 +165,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:
yield from coro(*args, **kwargs)
@@ -552,73 +541,128 @@ class BotBase(GroupMixin):
# command processing
@asyncio.coroutine
- def process_commands(self, message):
+ def get_prefix(self, message):
"""|coro|
- This function processes the commands that have been registered
- to the bot and other groups. Without this coroutine, none of the
- commands will be triggered.
+ Retrieves the prefix the bot is listening to
+ with the message as a context.
- By default, this coroutine is called inside the :func:`on_message`
- event. If you choose to override the :func:`on_message` event, then
- you should invoke this coroutine as well.
+ Parameters
+ -----------
+ message: :class:`discord.Message`
+ The message context to get the prefix of.
- Warning
+ Returns
--------
- 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.
+ 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 : discord.Message
- The message to process commands for.
+ message: :class:`discord.Message`
+ The message to get the invocation context from.
+
+ Returns
+ --------
+ :class:`Context`
+ The invocation context.
"""
- _internal_channel = message.channel
- _internal_author = message.author
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
+ return ctx
- prefix = yield from self._get_prefix(message)
+ prefix = yield from self.get_prefix(message)
invoked_prefix = prefix
if not isinstance(prefix, (tuple, list)):
if not view.skip_string(prefix):
- return
+ return ctx
else:
invoked_prefix = discord.utils.find(view.skip_string, prefix)
if invoked_prefix is None:
- return
-
+ return ctx
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)
+ 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 command.invoke(ctx)
+ yield from ctx.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_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|
+
+ This function processes the commands that have been registered
+ to the bot and other groups. Without this coroutine, none of the
+ commands will be triggered.
+
+ By default, this coroutine is called inside the :func:`on_message`
+ event. If you choose to override the :func:`on_message` event, then
+ you should invoke this coroutine as well.
+
+ 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.
+ """
+ ctx = yield from self.get_context(message)
+ yield from self.invoke(ctx)
+
+ @asyncio.coroutine
def on_message(self, message):
yield from self.process_commands(message)
diff --git a/discord/ext/commands/context.py b/discord/ext/commands/context.py
index 59f09117..c1d120f3 100644
--- a/discord/ext/commands/context.py
+++ b/discord/ext/commands/context.py
@@ -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