mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-05-10 15:59:50 +00:00
[commands] Add support for cogs.
Cogs are basically class instances that have commands and event listeners. They allow for better organisation and grouping of commands and state. Similar to subclassing discord.Client.
This commit is contained in:
parent
968e5514cb
commit
ec6b1997ad
@ -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
|
||||
|
@ -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))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user