aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--discord/ext/commands/bot.py110
-rw-r--r--discord/ext/commands/core.py24
2 files changed, 128 insertions, 6 deletions
diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py
index 5387df1a..fbdaa75a 100644
--- a/discord/ext/commands/bot.py
+++ b/discord/ext/commands/bot.py
@@ -28,7 +28,7 @@ import asyncio
import discord
import inspect
-from .core import GroupMixin
+from .core import GroupMixin, Command
from .view import StringView
from .context import Context
from .errors import CommandNotFound
@@ -67,6 +67,7 @@ class Bot(GroupMixin, discord.Client):
super().__init__(**options)
self.command_prefix = command_prefix
self.extra_events = {}
+ self.cogs = {}
# internal helpers
@@ -231,20 +232,40 @@ class Bot(GroupMixin, discord.Client):
name = func.__name__ if name is None else name
if not asyncio.iscoroutinefunction(func):
- func = asyncio.coroutine(func)
+ raise discord.ClientException('Listeners must be coroutines')
if name in self.extra_events:
self.extra_events[name].append(func)
else:
self.extra_events[name] = [func]
+ def remove_listener(self, func, name=None):
+ """Removes a listener from the pool of listeners.
+
+ Parameters
+ -----------
+ func
+ The function that was used as a listener to remove.
+ name
+ The name of the event we want to remove. Defaults to
+ ``func.__name__``.
+ """
+
+ name = func.__name__ if name is None else name
+
+ if name in self.extra_events:
+ try:
+ self.extra_events[name].remove(func)
+ except ValueError:
+ pass
+
+
def listen(self, name=None):
"""A decorator that registers another function as an external
event listener. Basically this allows you to listen to multiple
events from different places e.g. such as :func:`discord.on_ready`
- If the function being listened to is not a coroutine, it makes it into
- a coroutine a la :meth:`Client.async_event`.
+ The functions being listened to must be a coroutine.
Examples
---------
@@ -262,6 +283,11 @@ class Bot(GroupMixin, discord.Client):
print('two')
Would print one and two in an unspecified order.
+
+ Raises
+ -------
+ discord.ClientException
+ The function being listened to is not a coroutine.
"""
def decorator(func):
@@ -270,6 +296,82 @@ class Bot(GroupMixin, discord.Client):
return decorator
+ # cogs
+
+ def add_cog(self, cog):
+ """Adds a "cog" to the bot.
+
+ A cog is a class that has its own event listeners and commands.
+
+ They are meant as a way to organize multiple relevant commands
+ into a singular class that shares some state or no state at all.
+
+ More information will be documented soon.
+
+ Parameters
+ -----------
+ cog
+ The cog to register to the bot.
+ """
+
+ self.cogs[type(cog).__name__] = cog
+ members = inspect.getmembers(cog)
+ for name, member in members:
+ # register commands the cog has
+ if isinstance(member, Command):
+ member.instance = cog
+ if member.parent is None:
+ self.add_command(member)
+ continue
+
+ # register event listeners the cog has
+ if name.startswith('on_'):
+ self.add_listener(member)
+
+ def get_cog(self, name):
+ """Gets the cog instance requested.
+
+ If the cog is not found, ``None`` is returned instead.
+
+ Parameters
+ -----------
+ name : str
+ The name of the cog you are requesting.
+ """
+ return self.cogs.get(name)
+
+ def remove_cog(self, name):
+ """Removes a cog the bot.
+
+ All registered commands and event listeners that the
+ cog has registered will be removed as well.
+
+ If no cog is found then ``None`` is returned, otherwise
+ the cog instance that is being removed is returned.
+
+ Parameters
+ -----------
+ name : str
+ The name of the cog to remove.
+ """
+
+ cog = self.cogs.pop(name, None)
+ if cog is None:
+ return cog
+
+ members = inspect.getmembers(cog)
+ for name, member in members:
+ # remove commands the cog has
+ if isinstance(member, Command):
+ member.instance = None
+ if member.parent is None:
+ self.remove_command(member.name)
+ continue
+
+ # remove event listeners the cog has
+ if name.startswith('on_'):
+ self.remove_listener(member)
+
# command processing
@asyncio.coroutine
diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py
index b86211de..b73f2ac8 100644
--- a/discord/ext/commands/core.py
+++ b/discord/ext/commands/core.py
@@ -71,6 +71,9 @@ class Command:
If the command is invoked while it is disabled, then
:exc:`DisabledCommand` is raised to the :func:`on_command_error`
event. Defaults to ``True``.
+ parent : Optional[command]
+ The parent command that this command belongs to. ``None`` is there
+ isn't one.
checks
A list of predicates that verifies if the command could be executed
with the given :class:`Context` as the sole parameter. If an exception
@@ -90,6 +93,9 @@ class Command:
signature = inspect.signature(callback)
self.params = signature.parameters.copy()
self.checks = kwargs.get('checks', [])
+ self.module = inspect.getmodule(callback)
+ self.instance = None
+ self.parent = None
def _receive_item(self, message, argument, regex, receiver, generator):
match = re.match(regex, argument)
@@ -181,14 +187,25 @@ class Command:
def _parse_arguments(self, ctx):
try:
- ctx.args = []
+ ctx.args = [] if self.instance is None else [self.instance]
ctx.kwargs = {}
args = ctx.args
kwargs = ctx.kwargs
first = True
view = ctx.view
- for name, param in self.params.items():
+ 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
@@ -272,6 +289,9 @@ class GroupMixin:
if not isinstance(command, Command):
raise TypeError('The command passed must be a subclass of Command')
+ if isinstance(self, Command):
+ command.parent = self
+
if command.name in self.commands:
raise discord.ClientException('Command {0.name} is already registered.'.format(command))