Skip to content

Commit

Permalink
Merge pull request #138 from Pycord-Development/restructure
Browse files Browse the repository at this point in the history
Merge restructure into slash
  • Loading branch information
BobDotCom authored Oct 19, 2021
2 parents 14607fe + 6f809db commit 6ac921b
Show file tree
Hide file tree
Showing 13 changed files with 131 additions and 77 deletions.
2 changes: 1 addition & 1 deletion discord/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
from .components import *
from .threads import *
from .bot import *
from .app import *
from .commands import *
from .cog import Cog
from .welcome_screen import *

Expand Down
60 changes: 51 additions & 9 deletions discord/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,22 @@
DEALINGS IN THE SOFTWARE.
"""

from __future__ import annotations # will probably need in future for type hinting
from __future__ import annotations

import asyncio
import collections
import inspect
import traceback
from .app.errors import ApplicationCommandError, CheckFailure
from .commands.errors import CheckFailure

from typing import Callable, Optional
from typing import List, Optional, Union

import sys

from .client import Client
from .shard import AutoShardedClient
from .utils import get, async_all
from .app import (
from .commands import (
SlashCommand,
SlashCommandGroup,
MessageCommand,
Expand Down Expand Up @@ -72,6 +75,13 @@ def __init__(self, *args, **kwargs) -> None:
def pending_application_commands(self):
return self._pending_application_commands

@property
def commands(self) -> List[Union[ApplicationCommand, ...]]:
commands = list(self.application_commands.values())
if self._supports_prefixed_commands:
commands += self.prefixed_commands
return commands

def add_application_command(self, command: ApplicationCommand) -> None:
"""Adds a :class:`.ApplicationCommand` into the internal list of commands.
Expand Down Expand Up @@ -354,13 +364,31 @@ class be provided, it must be similar enough to


class BotBase(ApplicationCommandMixin, CogMixin):
_supports_prefixed_commands = False
# TODO I think
def __init__(self, *args, **kwargs):
def __init__(self, description=None, *args, **options):
# super(Client, self).__init__(*args, **kwargs)
# I replaced ^ with v and it worked
super().__init__(*args, **kwargs)
self.debug_guild = kwargs.pop("debug_guild", None)
self.debug_guilds = kwargs.pop("debug_guilds", None)
super().__init__(*args, **options)
self.extra_events = {} # TYPE: Dict[str, List[CoroFunc]]
self.__cogs = {} # TYPE: Dict[str, Cog]
self.__extensions = {} # TYPE: Dict[str, types.ModuleType]
self._checks = [] # TYPE: List[Check]
self._check_once = []
self._before_invoke = None
self._after_invoke = None
self.description = inspect.cleandoc(description) if description else ''
self.owner_id = options.get('owner_id')
self.owner_ids = options.get('owner_ids', set())

self.debug_guild = options.pop("debug_guild", None) # TODO: remove or reimplement
self.debug_guilds = options.pop("debug_guilds", None)

if self.owner_id and self.owner_ids:
raise TypeError('Both owner_id and owner_ids are set.')

if self.owner_ids and not isinstance(self.owner_ids, collections.abc.Collection):
raise TypeError(f'owner_ids must be a collection not {self.owner_ids.__class__!r}')

if self.debug_guild:
if self.debug_guilds is None:
Expand Down Expand Up @@ -570,6 +598,20 @@ class Bot(BotBase, Client):
Attributes
-----------
description: :class:`str`
The content prefixed into the default help message.
owner_id: Optional[:class:`int`]
The user ID that owns the bot. If this is not set and is then queried via
:meth:`.is_owner` then it is fetched automatically using
:meth:`~.Bot.application_info`.
owner_ids: Optional[Collection[:class:`int`]]
The user IDs that owns the bot. This is similar to :attr:`owner_id`.
If this is not set and the application is team based, then it is
fetched automatically using :meth:`~.Bot.application_info`.
For performance reasons it is recommended to use a :class:`set`
for the collection. You cannot set both ``owner_id`` and ``owner_ids``.
.. versionadded:: 1.3
debug_guild: Optional[:class:`int`]
Guild ID of a guild to use for testing commands. Prevents setting global commands
in favor of guild commands, which update instantly.
Expand All @@ -591,4 +633,4 @@ class AutoShardedBot(BotBase, AutoShardedClient):
.. versionadded:: 2.0
"""

pass
pass
22 changes: 15 additions & 7 deletions discord/cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@
import discord.utils
import types
from . import errors
from .app import SlashCommand, UserCommand, MessageCommand, ApplicationCommand#, _BaseCommand
from .commands import SlashCommand, UserCommand, MessageCommand, ApplicationCommand

from typing import Any, Callable, Mapping, ClassVar, Dict, Generator, List, Optional, TYPE_CHECKING, Tuple, TypeVar, Type, Union
from typing import Any, Callable, Mapping, ClassVar, Dict, Generator, List, Optional, TYPE_CHECKING, Tuple, TypeVar, Type

from .app.commands import _BaseCommand
from .commands.commands import _BaseCommand

if TYPE_CHECKING:
from .app import InteractionContext, ApplicationCommand
from .commands import InteractionContext, ApplicationCommand

__all__ = (
'CogMeta',
Expand Down Expand Up @@ -239,15 +239,23 @@ def get_commands(self) -> List[ApplicationCommand]:
r"""
Returns
--------
List[:class:`.Command`]
A :class:`list` of :class:`.Command`\s that are
List[:class:`.ApplicationCommand`]
A :class:`list` of :class:`.ApplicationCommand`\s that are
defined inside this cog.
.. note::
This does not include subcommands.
"""
return [c for c in self.__cog_commands__ if c.parent is None]
return [
c for c in (
c for c in self.__cog_commands__
if not isinstance(c, (SlashCommand, MessageCommand, UserCommand))
) if c.parent is None
] + [
c for c in self.__cog_commands__
if isinstance(c, (SlashCommand, MessageCommand, UserCommand))
]

@property
def qualified_name(self) -> str:
Expand Down
File renamed without changes.
48 changes: 27 additions & 21 deletions discord/app/commands.py → discord/commands/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from collections import OrderedDict
from typing import Any, Callable, Dict, List, Optional, Union

from ..enums import OptionType, ChannelType
from ..enums import SlashCommandOptionType, SlashCommandChannelType
from ..member import Member
from ..user import User
from ..message import Message
Expand Down Expand Up @@ -338,6 +338,7 @@ def __init__(self, func: Callable, *args, **kwargs) -> None:
def parse_options(self, params) -> List[Option]:
final_options = []

params = self._get_signature_parameters()
if list(params.items())[0][0] == "self":
temp = list(params.items())
temp.pop(0)
Expand Down Expand Up @@ -368,7 +369,7 @@ def parse_options(self, params) -> List[Option]:
else:
option = Option(
option.__args__, "No description provided"
)
)

if not isinstance(option, Option):
option = Option(option, "No description provided")
Expand Down Expand Up @@ -403,7 +404,7 @@ def to_dict(self) -> Dict:
"options": [o.to_dict() for o in self.options],
}
if self.is_subcommand:
as_dict["type"] = OptionType.sub_command.value
as_dict["type"] = SlashCommandOptionType.sub_command.value

return as_dict

Expand All @@ -415,27 +416,30 @@ def __eq__(self, other) -> bool:
)

async def _invoke(self, ctx: ApplicationContext) -> None:
# TODO: Parse the args better, apply custom converters etc.
# TODO: Parse the args better
kwargs = {}
for arg in ctx.interaction.data.get("options", []):
op = find(lambda x: x.name == arg["name"], self.options)
arg = arg["value"]

# Checks if input_type is user, role or channel
if (
OptionType.user.value
SlashCommandOptionType.user.value
<= op.input_type.value
<= OptionType.role.value
<= SlashCommandOptionType.role.value
):
name = "member" if op.input_type.name == "user" else op.input_type.name
arg = await get_or_fetch(ctx.guild, name, int(arg), default=int(arg))

elif op.input_type == OptionType.mentionable:
elif op.input_type == SlashCommandOptionType.mentionable:
arg_id = int(arg)
arg = await get_or_fetch(ctx.guild, "member", arg_id)
if arg is None:
arg = ctx.guild.get_role(arg_id) or arg_id

elif op.input_type == SlashCommandOptionType.string and op._converter is not None:
arg = await op._converter.convert(ctx, arg)

kwargs[op.name] = arg

for o in self.options:
Expand Down Expand Up @@ -488,10 +492,10 @@ def _update_copy(self, kwargs: Dict[str, Any]):
return self.copy()

channel_type_map = {
'TextChannel': ChannelType.text,
'VoiceChannel': ChannelType.voice,
'StageChannel': ChannelType.stage_voice,
'CategoryChannel': ChannelType.category
'TextChannel': SlashCommandOptionType.text,
'VoiceChannel': SlashCommandOptionType.voice,
'StageChannel': SlashCommandOptionType.stage_voice,
'CategoryChannel': SlashCommandOptionType.category
}

class Option:
Expand All @@ -500,11 +504,15 @@ def __init__(
) -> None:
self.name: Optional[str] = kwargs.pop("name", None)
self.description = description or "No description provided"

self.channel_types: List[ChannelType] = kwargs.pop("channel_types", [])
if not isinstance(input_type, OptionType):
self.input_type = OptionType.from_datatype(input_type)
if self.input_type == OptionType.channel:
self._converter = None
self.channel_types: List[SlashCommandOptionType] = kwargs.pop("channel_types", [])
if not isinstance(input_type, SlashCommandOptionType):
to_assign = input_type() if isinstance(input_type, type) else input_type
_type = SlashCommandOptionType.from_datatype(to_assign.__class__)
if _type == SlashCommandOptionType.custom:
self._converter = to_assign
input_type = SlashCommandOptionType.string
elif _type == SlashCommandOptionType.channel:
if not isinstance(input_type, tuple):
input_type = (input_type,)
for i in input_type:
Expand All @@ -513,9 +521,7 @@ def __init__(

channel_type = channel_type_map[i.__name__]
self.channel_types.append(channel_type)
else:
self.input_type = input_type

self.input_type = input_type
self.required: bool = kwargs.pop("required", True)
self.choices: List[OptionChoice] = [
o if isinstance(o, OptionChoice) else OptionChoice(o)
Expand All @@ -533,7 +539,7 @@ def to_dict(self) -> Dict:
}
if self.channel_types:
as_dict["channel_types"] = [t.value for t in self.channel_types]

return as_dict


Expand Down Expand Up @@ -577,7 +583,7 @@ def __init__(
validate_chat_input_name(name)
validate_chat_input_description(description)
super().__init__(
OptionType.sub_command_group,
SlashCommandOptionType.sub_command_group,
name=name,
description=description,
)
Expand Down
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions discord/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,9 @@ def from_datatype(cls, datatype):
if issubclass(datatype, float):
return cls.number

if hasattr(datatype, "convert"):
return cls.custom

if datatype.__name__ == "Member":
return cls.user
if datatype.__name__ in [
Expand Down
50 changes: 19 additions & 31 deletions discord/ext/commands/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,28 +121,13 @@ def __repr__(self):
_default = _DefaultRepr()

class BotBase(GroupMixin):
def __init__(self, command_prefix=when_mentioned, help_command=_default, description=None, **options):
_supports_prefixed_commands = True
def __init__(self, command_prefix=when_mentioned, help_command=_default, **options):
super().__init__(**options)
self.command_prefix = command_prefix
self.extra_events: Dict[str, List[CoroFunc]] = {}
self.__cogs: Dict[str, Cog] = {}
self.__extensions: Dict[str, types.ModuleType] = {}
self._checks: List[Check] = []
self._check_once = []
self._before_invoke = None
self._after_invoke = None
self._help_command = None
self.description = inspect.cleandoc(description) if description else ''
self.owner_id = options.get('owner_id')
self.owner_ids = options.get('owner_ids', set())
self.strip_after_prefix = options.get('strip_after_prefix', False)

if self.owner_id and self.owner_ids:
raise TypeError('Both owner_id and owner_ids are set.')

if self.owner_ids and not isinstance(self.owner_ids, collections.abc.Collection):
raise TypeError(f'owner_ids must be a collection not {self.owner_ids.__class__!r}')

if help_command is _default:
self.help_command = DefaultHelpCommand()
else:
Expand Down Expand Up @@ -1034,6 +1019,23 @@ async def process_commands(self, message: Message) -> None:
async def on_message(self, message):
await self.process_commands(message)


















class Bot(BotBase, discord.Bot):
"""Represents a discord bot.
Expand Down Expand Up @@ -1079,24 +1081,10 @@ class Bot(BotBase, discord.Bot):
Whether the commands should be case insensitive. Defaults to ``False``. This
attribute does not carry over to groups. You must set it to every group if
you require group commands to be case insensitive as well.
description: :class:`str`
The content prefixed into the default help message.
help_command: Optional[:class:`.HelpCommand`]
The help command implementation to use. This can be dynamically
set at runtime. To remove the help command pass ``None``. For more
information on implementing a help command, see :ref:`ext_commands_help_command`.
owner_id: Optional[:class:`int`]
The user ID that owns the bot. If this is not set and is then queried via
:meth:`.is_owner` then it is fetched automatically using
:meth:`~.Bot.application_info`.
owner_ids: Optional[Collection[:class:`int`]]
The user IDs that owns the bot. This is similar to :attr:`owner_id`.
If this is not set and the application is team based, then it is
fetched automatically using :meth:`~.Bot.application_info`.
For performance reasons it is recommended to use a :class:`set`
for the collection. You cannot set both ``owner_id`` and ``owner_ids``.
.. versionadded:: 1.3
strip_after_prefix: :class:`bool`
Whether to strip whitespace characters after encountering the command
prefix. This allows for ``! hello`` and ``!hello`` to both work if
Expand Down
Loading

0 comments on commit 6ac921b

Please sign in to comment.